From e90b72ec3082588b296956730bd2930cf8d5d671 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 10 Jul 2026 11:31:46 +0800 Subject: refactor: promote new architecture to repository root Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior. --- .clang-format | 35 + .clang-tidy | 23 + .gitignore | 2 +- Makefile | 458 ++++-- README.md | 4 +- arch/riscv/config.cc | 276 ++++ arch/riscv/defconfig | 39 + arch/riscv/include/arch/config.h | 106 ++ arch/riscv/include/arch/context.h | 33 + arch/riscv/include/arch/cpu.h | 95 ++ arch/riscv/include/arch/extable.h | 55 + arch/riscv/include/arch/fpstate.h | 94 ++ arch/riscv/include/arch/mmu.h | 78 + arch/riscv/include/arch/pmap.h | 49 + arch/riscv/include/arch/random.h | 27 + arch/riscv/include/arch/sbi.h | 111 ++ arch/riscv/include/arch/tlb.h | 19 + arch/riscv/include/arch/trap.h | 94 ++ arch/riscv/include/arch/trapframe.h | 140 ++ arch/riscv/include/asm/csr.h | 37 + arch/riscv/include/asm/page.h | 62 + arch/riscv/include/asm/pgtable.h | 55 + arch/riscv/include/asm/sections.h | 50 + arch/riscv/kernel/asm-offsets.c | 29 + arch/riscv/kernel/head.S | 317 ++++ arch/riscv/kernel/lumeos.lds.S | 120 ++ arch/riscv/kernel/signal.cc | 163 ++ arch/riscv/lib/random.cc | 21 + arch/riscv/mm/init.cc | 241 +++ arch/riscv/mm/pmap.cc | 361 +++++ arch/riscv/mm/tlb_md.cc | 17 + arch/riscv/module.mk | 18 + arch/riscv/syscall/syscall_md.cc | 42 + arch/riscv/trap/extable.cc | 92 ++ arch/riscv/trap/fpu.S | 103 ++ arch/riscv/trap/swtch.S | 96 ++ arch/riscv/trap/trap_md.cc | 241 +++ arch/riscv/trap/trap_vector.S | 283 ++++ arch/riscv/uaccess/uaccess_memcpy.S | 117 ++ drivers/base/bus.cc | 32 + drivers/base/core.cc | 156 ++ drivers/base/driver.cc | 72 + drivers/base/module.mk | 9 + drivers/base/platform.cc | 231 +++ drivers/console.cc | 119 ++ drivers/module.mk | 11 + drivers/plic.cc | 259 +++ drivers/tty/module.mk | 5 + drivers/tty/n_tty.cc | 539 +++++++ drivers/tty/tty_core.cc | 536 +++++++ drivers/tty/tty_uart.cc | 251 +++ drivers/uart.cc | 181 +++ fdt/device_node.cc | 166 ++ fdt/fdt.cc | 625 ++++++++ fdt/module.mk | 3 + fs/chardev.cc | 43 + fs/dcache.cc | 287 ++++ fs/devfs.cc | 137 ++ fs/fd.cc | 216 +++ fs/file_table.cc | 164 ++ fs/fs_struct.cc | 90 ++ fs/initramfs.cc | 285 ++++ fs/inode.cc | 166 ++ fs/module.mk | 17 + fs/namei.cc | 250 +++ fs/pipe.cc | 278 ++++ fs/ramfs/file.cc | 216 +++ fs/ramfs/inode.cc | 164 ++ fs/ramfs/super.cc | 154 ++ fs/super.cc | 95 ++ fs/vfs_init.cc | 78 + include/common/fcntl.h | 14 - include/common/types.h | 32 - include/drivers/plic.h | 22 - include/drivers/uart.h | 38 - include/drivers/uart_color.h | 12 - include/drivers/virtio.h | 108 -- include/fs/fat32.h | 122 -- include/fs/file.h | 78 - include/fs/fs.h | 105 -- include/fs/stat.h | 27 - include/kernel/buf.h | 35 - include/kernel/config.h | 12 - include/kernel/cpu.h | 24 - include/kernel/elf.h | 51 - include/kernel/exec.h | 11 - include/kernel/fdt.h | 45 - include/kernel/mm.h | 32 - include/kernel/pmm.h | 60 - include/kernel/proc.h | 34 - include/kernel/proc_def.h | 75 - include/kernel/riscv.h | 404 ----- include/kernel/runqueue.h | 21 - include/kernel/sbi.h | 29 - include/kernel/slab.h | 15 - include/kernel/spinlock.h | 53 - include/kernel/sync.h | 43 - include/kernel/syscall.h | 85 - include/kernel/timer.h | 17 - include/kernel/trap.h | 55 - include/lib/errno.h | 168 -- include/lib/string.h | 19 - include/libc-shim/bits/alltypes.h | 15 + include/libc-shim/endian.h | 9 + include/libc-shim/features.h | 12 + include/libc-shim/limits.h | 17 + include/libc-shim/stdint.h | 21 + include/libc-shim/string.h | 110 ++ include/libc-shim/strings.h | 40 + include/lume/addr.h | 91 ++ include/lume/addr_types.h | 29 + include/lume/args.h | 28 + include/lume/atomic.h | 76 + include/lume/bitops.h | 120 ++ include/lume/chardev.h | 45 + include/lume/clone.h | 34 + include/lume/compiler.h | 54 + include/lume/config.h | 100 ++ include/lume/console.h | 77 + include/lume/container_of.h | 17 + include/lume/dcache.h | 143 ++ include/lume/device.h | 75 + include/lume/device_node.h | 64 + include/lume/driver.h | 24 + include/lume/elf.h | 150 ++ include/lume/errno.h | 186 +++ include/lume/fdt.h | 85 + include/lume/fdt_prop.h | 24 + include/lume/fdtable.h | 49 + include/lume/features.h | 40 + include/lume/file.h | 132 ++ include/lume/fs.h | 310 ++++ include/lume/fs_struct.h | 39 + include/lume/fs_types.h | 197 +++ include/lume/init.h | 33 + include/lume/irq.h | 84 + include/lume/kbuild.h | 40 + include/lume/kernel_map.h | 55 + include/lume/klog.h | 200 +++ include/lume/kobject.h | 57 + include/lume/kprintf.h | 39 + include/lume/kref.h | 46 + include/lume/libfdt.h | 8 + include/lume/libfdt_env.h | 78 + include/lume/limits.h | 48 + include/lume/list.h | 346 ++++ include/lume/lumeos.lds.h | 193 +++ include/lume/memblock.h | 53 + include/lume/mm_layout.h | 73 + include/lume/mman.h | 21 + include/lume/mount.h | 52 + include/lume/mutex.h | 63 + include/lume/namei.h | 32 + include/lume/new.h | 18 + include/lume/page.h | 151 ++ include/lume/page_cache.h | 82 + include/lume/pgrp.h | 125 ++ include/lume/pgtable_types.h | 69 + include/lume/pid.h | 29 + include/lume/pipe.h | 52 + include/lume/platform_device.h | 64 + include/lume/pmm.h | 108 ++ include/lume/poll.h | 68 + include/lume/ptrace.h | 76 + include/lume/random.h | 38 + include/lume/rbtree.h | 207 +++ include/lume/rbtree_augmented.h | 252 +++ include/lume/rbtree_types.h | 28 + include/lume/sched.h | 102 ++ include/lume/sched_class.h | 35 + include/lume/selftest.h | 65 + include/lume/setup.h | 42 + include/lume/signal.h | 60 + include/lume/signal_nr.h | 54 + include/lume/signal_types.h | 263 ++++ include/lume/slab.h | 28 + include/lume/smp.h | 74 + include/lume/stdarg.h | 11 + include/lume/stringify.h | 14 + include/lume/syscall.h | 38 + include/lume/syscall_nr.h | 82 + include/lume/task.h | 144 ++ include/lume/timer.h | 43 + include/lume/tlb.h | 48 + include/lume/trap.h | 109 ++ include/lume/tty.h | 165 ++ include/lume/tty_ioctl.h | 202 +++ include/lume/types.h | 78 + include/lume/uaccess.h | 27 + include/lume/uuid.h | 207 +++ include/lume/vmm.h | 115 ++ include/lume/wait.h | 31 + include/lume/waitqueue.h | 79 + include/ulib/assert.h | 25 - include/ulib/cstdlib | 57 - include/ulib/cstring | 37 - include/ulib/ctypes.h | 31 - include/ulib/cxx.cc | 37 - include/ulib/errno.h | 170 -- include/ulib/iostream | 115 -- include/ulib/limits.h | 26 - include/ulib/stb_sprintf.h | 1916 ---------------------- include/ulib/stdio.h | 101 -- include/ulib/stdlib.h | 100 -- include/ulib/string.h | 74 - include/ulib/types.h | 25 - include/user/user.h | 75 - kernel/exec.cc | 659 ++++++++ kernel/irq.cc | 106 ++ kernel/main.cc | 468 ++++++ kernel/module.mk | 26 + kernel/pgrp.cc | 148 ++ kernel/pid.cc | 63 + kernel/poll.cc | 250 +++ kernel/ptrace.cc | 92 ++ kernel/sched_class_o1.cc | 81 + kernel/sched_core.cc | 571 +++++++ kernel/sched_internal.h | 115 ++ kernel/signal.cc | 442 ++++++ kernel/smp.cc | 112 ++ kernel/sync/cpu_state.h | 35 + kernel/sync/mutex.cc | 156 ++ kernel/sync/spinlock.cc | 120 ++ kernel/sync/spinlock.h | 61 + kernel/sys_fs.cc | 679 ++++++++ kernel/sys_info.cc | 49 + kernel/sys_misc.cc | 360 +++++ kernel/sys_mm.cc | 178 +++ kernel/sys_process.cc | 185 +++ kernel/sys_signal.cc | 351 +++++ kernel/syscall.cc | 208 +++ kernel/task.cc | 780 +++++++++ kernel/timer.cc | 118 ++ kernel/trap.cc | 258 +++ kernel/uaccess.cc | 53 + kernel/waitqueue.cc | 144 ++ lib/cxx_abi.cc | 37 + lib/fdt.c | 2 + lib/fdt_addresses.c | 2 + lib/fdt_empty_tree.c | 2 + lib/fdt_ro.c | 2 + lib/fdt_rw.c | 2 + lib/fdt_strerror.c | 2 + lib/fdt_sw.c | 2 + lib/fdt_wip.c | 2 + lib/klog.cc | 574 +++++++ lib/kobject.cc | 82 + lib/kprintf.cc | 91 ++ lib/module.mk | 66 + lib/printf.c | 914 +++++++++++ lib/printf.h | 129 ++ lib/random.cc | 152 ++ lib/rbtree.cc | 596 +++++++ lib/string/COPYRIGHT | 193 +++ lib/string/aarch64/memcpy.S | 186 +++ lib/string/aarch64/memset.S | 115 ++ lib/string/arm/__aeabi_memcpy.s | 45 + lib/string/arm/__aeabi_memset.s | 31 + lib/string/arm/memcpy.S | 479 ++++++ lib/string/bcmp.c | 11 + lib/string/bcopy.c | 11 + lib/string/bzero.c | 11 + lib/string/explicit_bzero.c | 11 + lib/string/i386/memcpy.s | 32 + lib/string/i386/memmove.s | 22 + lib/string/i386/memset.s | 76 + lib/string/index.c | 11 + lib/string/memccpy.c | 37 + lib/string/memchr.c | 30 + lib/string/memcmp.c | 11 + lib/string/memcpy.c | 127 ++ lib/string/memmem.c | 152 ++ lib/string/memmove.c | 45 + lib/string/mempcpy.c | 10 + lib/string/memrchr.c | 14 + lib/string/memset.c | 93 ++ lib/string/rindex.c | 11 + lib/string/stpcpy.c | 32 + lib/string/stpncpy.c | 35 + lib/string/strcat.c | 10 + lib/string/strchr.c | 10 + lib/string/strchrnul.c | 31 + lib/string/strcmp.c | 10 + lib/string/strcpy.c | 10 + lib/string/strcspn.c | 20 + lib/string/strlcat.c | 12 + lib/string/strlcpy.c | 37 + lib/string/strlen.c | 25 + lib/string/strncat.c | 13 + lib/string/strncmp.c | 12 + lib/string/strncpy.c | 10 + lib/string/strnlen.c | 10 + lib/string/strpbrk.c | 10 + lib/string/strrchr.c | 9 + lib/string/strsep.c | 16 + lib/string/strspn.c | 23 + lib/string/strstr.c | 157 ++ lib/string/strtok.c | 16 + lib/string/strtok_r.c | 15 + lib/string/x86_64/memcpy.s | 25 + lib/string/x86_64/memmove.s | 16 + lib/string/x86_64/memset.s | 72 + linker.ld | 48 - lume-legacy/Makefile | 141 ++ lume-legacy/include/common/fcntl.h | 14 + lume-legacy/include/common/types.h | 32 + lume-legacy/include/drivers/plic.h | 22 + lume-legacy/include/drivers/uart.h | 38 + lume-legacy/include/drivers/uart_color.h | 12 + lume-legacy/include/drivers/virtio.h | 108 ++ lume-legacy/include/fs/fat32.h | 122 ++ lume-legacy/include/fs/file.h | 78 + lume-legacy/include/fs/fs.h | 105 ++ lume-legacy/include/fs/stat.h | 27 + lume-legacy/include/kernel/buf.h | 35 + lume-legacy/include/kernel/config.h | 12 + lume-legacy/include/kernel/cpu.h | 24 + lume-legacy/include/kernel/elf.h | 51 + lume-legacy/include/kernel/exec.h | 11 + lume-legacy/include/kernel/fdt.h | 45 + lume-legacy/include/kernel/mm.h | 32 + lume-legacy/include/kernel/pmm.h | 60 + lume-legacy/include/kernel/proc.h | 34 + lume-legacy/include/kernel/proc_def.h | 75 + lume-legacy/include/kernel/riscv.h | 404 +++++ lume-legacy/include/kernel/runqueue.h | 21 + lume-legacy/include/kernel/sbi.h | 29 + lume-legacy/include/kernel/slab.h | 15 + lume-legacy/include/kernel/spinlock.h | 53 + lume-legacy/include/kernel/sync.h | 43 + lume-legacy/include/kernel/syscall.h | 85 + lume-legacy/include/kernel/timer.h | 17 + lume-legacy/include/kernel/trap.h | 55 + lume-legacy/include/lib/errno.h | 168 ++ lume-legacy/include/lib/string.h | 19 + lume-legacy/include/ulib/assert.h | 25 + lume-legacy/include/ulib/cstdlib | 57 + lume-legacy/include/ulib/cstring | 37 + lume-legacy/include/ulib/ctypes.h | 31 + lume-legacy/include/ulib/cxx.cc | 37 + lume-legacy/include/ulib/errno.h | 170 ++ lume-legacy/include/ulib/iostream | 115 ++ lume-legacy/include/ulib/limits.h | 26 + lume-legacy/include/ulib/stb_sprintf.h | 1916 ++++++++++++++++++++++ lume-legacy/include/ulib/stdio.h | 101 ++ lume-legacy/include/ulib/stdlib.h | 100 ++ lume-legacy/include/ulib/string.h | 74 + lume-legacy/include/ulib/types.h | 25 + lume-legacy/include/user/user.h | 75 + lume-legacy/linker.ld | 48 + lume-legacy/src/boot/entry.S | 47 + lume-legacy/src/drivers/plic.cc | 77 + lume-legacy/src/drivers/uart.cc | 204 +++ lume-legacy/src/drivers/virtio_dist.cc | 382 +++++ lume-legacy/src/fs/fat32.cc | 806 ++++++++++ lume-legacy/src/fs/file.cc | 358 +++++ lume-legacy/src/fs/fs.cc | 268 ++++ lume-legacy/src/kernel/bio.cc | 139 ++ lume-legacy/src/kernel/config.cc | 13 + lume-legacy/src/kernel/cxx.cc | 78 + lume-legacy/src/kernel/fdt.cc | 273 ++++ lume-legacy/src/kernel/initcode.S | 11 + lume-legacy/src/kernel/main.cc | 79 + lume-legacy/src/kernel/mm/pmm.cc | 305 ++++ lume-legacy/src/kernel/mm/slab.cc | 259 +++ lume-legacy/src/kernel/mm/vm.cc | 459 ++++++ lume-legacy/src/kernel/proc/exec.cc | 260 +++ lume-legacy/src/kernel/proc/proc.cc | 654 ++++++++ lume-legacy/src/kernel/proc/swtch.S | 37 + lume-legacy/src/kernel/sbi.cc | 52 + lume-legacy/src/kernel/spinlock.cc | 199 +++ lume-legacy/src/kernel/syscall.cc | 698 ++++++++ lume-legacy/src/kernel/timer.cc | 66 + lume-legacy/src/kernel/trap/handler.cc | 217 +++ lume-legacy/src/kernel/trap/trampoline.S | 101 ++ lume-legacy/src/kernel/trap/trap.S | 90 ++ lume-legacy/src/lib/string.cc | 88 ++ lume-legacy/src/ulib/assert.c | 12 + lume-legacy/src/ulib/cxx.cc | 35 + lume-legacy/src/ulib/iostream.cpp | 12 + lume-legacy/src/ulib/stdio.c | 1056 +++++++++++++ lume-legacy/src/ulib/stdlib.c | 543 +++++++ lume-legacy/src/ulib/string.c | 335 ++++ lume-legacy/src/ulib/syscalls.c | 153 ++ lume-legacy/src/user/cat.cc | 52 + lume-legacy/src/user/cp.cc | 52 + lume-legacy/src/user/echo.cc | 26 + lume-legacy/src/user/entry.S | 12 + lume-legacy/src/user/grep.cc | 72 + lume-legacy/src/user/init.cc | 64 + lume-legacy/src/user/kill.cc | 27 + lume-legacy/src/user/ln.cc | 22 + lume-legacy/src/user/ls.cc | 116 ++ lume-legacy/src/user/mkdir.cc | 25 + lume-legacy/src/user/mv.cc | 30 + lume-legacy/src/user/rm.cc | 28 + lume-legacy/src/user/sh.cc | 423 +++++ lume-legacy/src/user/touch.cc | 31 + lume-legacy/src/user/user.ld | 36 + lume-legacy/src/user/usys.S | 38 + lume-legacy/src/user/wc.cc | 60 + mm/memblock.cc | 257 +++ mm/module.mk | 7 + mm/page_cache.cc | 252 +++ mm/pmm.cc | 528 +++++++ mm/slab.cc | 321 ++++ mm/slab.h | 83 + mm/tlb.cc | 90 ++ mm/vma.cc | 640 ++++++++ mm/vmm.cc | 235 +++ na/.clang-format | 35 - na/.clang-tidy | 23 - na/Makefile | 419 ----- na/arch/riscv/config.cc | 276 ---- na/arch/riscv/defconfig | 39 - na/arch/riscv/include/arch/config.h | 106 -- na/arch/riscv/include/arch/context.h | 33 - na/arch/riscv/include/arch/cpu.h | 95 -- na/arch/riscv/include/arch/extable.h | 55 - na/arch/riscv/include/arch/fpstate.h | 94 -- na/arch/riscv/include/arch/mmu.h | 78 - na/arch/riscv/include/arch/pmap.h | 49 - na/arch/riscv/include/arch/random.h | 27 - na/arch/riscv/include/arch/sbi.h | 111 -- na/arch/riscv/include/arch/tlb.h | 19 - na/arch/riscv/include/arch/trap.h | 94 -- na/arch/riscv/include/arch/trapframe.h | 140 -- na/arch/riscv/include/asm/csr.h | 37 - na/arch/riscv/include/asm/page.h | 62 - na/arch/riscv/include/asm/pgtable.h | 55 - na/arch/riscv/include/asm/sections.h | 50 - na/arch/riscv/kernel/asm-offsets.c | 29 - na/arch/riscv/kernel/head.S | 317 ---- na/arch/riscv/kernel/lumeos.lds.S | 120 -- na/arch/riscv/kernel/signal.cc | 163 -- na/arch/riscv/lib/random.cc | 21 - na/arch/riscv/mm/init.cc | 241 --- na/arch/riscv/mm/pmap.cc | 361 ----- na/arch/riscv/mm/tlb_md.cc | 17 - na/arch/riscv/module.mk | 18 - na/arch/riscv/syscall/syscall_md.cc | 42 - na/arch/riscv/trap/extable.cc | 92 -- na/arch/riscv/trap/fpu.S | 103 -- na/arch/riscv/trap/swtch.S | 96 -- na/arch/riscv/trap/trap_md.cc | 241 --- na/arch/riscv/trap/trap_vector.S | 283 ---- na/arch/riscv/uaccess/uaccess_memcpy.S | 117 -- na/drivers/base/bus.cc | 32 - na/drivers/base/core.cc | 156 -- na/drivers/base/driver.cc | 72 - na/drivers/base/module.mk | 9 - na/drivers/base/platform.cc | 231 --- na/drivers/console.cc | 119 -- na/drivers/module.mk | 11 - na/drivers/plic.cc | 259 --- na/drivers/tty/module.mk | 5 - na/drivers/tty/n_tty.cc | 539 ------- na/drivers/tty/tty_core.cc | 536 ------- na/drivers/tty/tty_uart.cc | 251 --- na/drivers/uart.cc | 181 --- na/fdt/device_node.cc | 166 -- na/fdt/fdt.cc | 625 -------- na/fdt/module.mk | 3 - na/fs/chardev.cc | 43 - na/fs/dcache.cc | 287 ---- na/fs/devfs.cc | 137 -- na/fs/fd.cc | 216 --- na/fs/file_table.cc | 164 -- na/fs/fs_struct.cc | 90 -- na/fs/initramfs.cc | 285 ---- na/fs/inode.cc | 166 -- na/fs/module.mk | 17 - na/fs/namei.cc | 250 --- na/fs/pipe.cc | 278 ---- na/fs/ramfs/file.cc | 216 --- na/fs/ramfs/inode.cc | 164 -- na/fs/ramfs/super.cc | 154 -- na/fs/super.cc | 95 -- na/fs/vfs_init.cc | 78 - na/include/libc-shim/bits/alltypes.h | 15 - na/include/libc-shim/endian.h | 9 - na/include/libc-shim/features.h | 12 - na/include/libc-shim/limits.h | 17 - na/include/libc-shim/stdint.h | 21 - na/include/libc-shim/string.h | 110 -- na/include/libc-shim/strings.h | 40 - na/include/lume/addr.h | 91 -- na/include/lume/addr_types.h | 29 - na/include/lume/args.h | 28 - na/include/lume/atomic.h | 76 - na/include/lume/bitops.h | 120 -- na/include/lume/chardev.h | 45 - na/include/lume/clone.h | 34 - na/include/lume/compiler.h | 54 - na/include/lume/config.h | 100 -- na/include/lume/console.h | 77 - na/include/lume/container_of.h | 17 - na/include/lume/dcache.h | 143 -- na/include/lume/device.h | 75 - na/include/lume/device_node.h | 64 - na/include/lume/driver.h | 24 - na/include/lume/elf.h | 150 -- na/include/lume/errno.h | 186 --- na/include/lume/fdt.h | 85 - na/include/lume/fdt_prop.h | 24 - na/include/lume/fdtable.h | 49 - na/include/lume/features.h | 40 - na/include/lume/file.h | 132 -- na/include/lume/fs.h | 310 ---- na/include/lume/fs_struct.h | 39 - na/include/lume/fs_types.h | 197 --- na/include/lume/init.h | 33 - na/include/lume/irq.h | 84 - na/include/lume/kbuild.h | 40 - na/include/lume/kernel_map.h | 55 - na/include/lume/klog.h | 200 --- na/include/lume/kobject.h | 57 - na/include/lume/kprintf.h | 39 - na/include/lume/kref.h | 46 - na/include/lume/libfdt.h | 8 - na/include/lume/libfdt_env.h | 78 - na/include/lume/limits.h | 48 - na/include/lume/list.h | 346 ---- na/include/lume/lumeos.lds.h | 193 --- na/include/lume/memblock.h | 53 - na/include/lume/mm_layout.h | 73 - na/include/lume/mman.h | 21 - na/include/lume/mount.h | 52 - na/include/lume/mutex.h | 63 - na/include/lume/namei.h | 32 - na/include/lume/new.h | 18 - na/include/lume/page.h | 151 -- na/include/lume/page_cache.h | 82 - na/include/lume/pgrp.h | 125 -- na/include/lume/pgtable_types.h | 69 - na/include/lume/pid.h | 29 - na/include/lume/pipe.h | 52 - na/include/lume/platform_device.h | 64 - na/include/lume/pmm.h | 108 -- na/include/lume/poll.h | 68 - na/include/lume/ptrace.h | 76 - na/include/lume/random.h | 38 - na/include/lume/rbtree.h | 207 --- na/include/lume/rbtree_augmented.h | 252 --- na/include/lume/rbtree_types.h | 28 - na/include/lume/sched.h | 102 -- na/include/lume/sched_class.h | 35 - na/include/lume/selftest.h | 65 - na/include/lume/setup.h | 42 - na/include/lume/signal.h | 60 - na/include/lume/signal_nr.h | 54 - na/include/lume/signal_types.h | 263 ---- na/include/lume/slab.h | 28 - na/include/lume/smp.h | 74 - na/include/lume/stdarg.h | 11 - na/include/lume/stringify.h | 14 - na/include/lume/syscall.h | 38 - na/include/lume/syscall_nr.h | 82 - na/include/lume/task.h | 144 -- na/include/lume/timer.h | 43 - na/include/lume/tlb.h | 48 - na/include/lume/trap.h | 109 -- na/include/lume/tty.h | 165 -- na/include/lume/tty_ioctl.h | 202 --- na/include/lume/types.h | 78 - na/include/lume/uaccess.h | 27 - na/include/lume/uuid.h | 207 --- na/include/lume/vmm.h | 115 -- na/include/lume/wait.h | 31 - na/include/lume/waitqueue.h | 79 - na/kernel/exec.cc | 659 -------- na/kernel/irq.cc | 106 -- na/kernel/main.cc | 468 ------ na/kernel/module.mk | 26 - na/kernel/pgrp.cc | 148 -- na/kernel/pid.cc | 63 - na/kernel/poll.cc | 250 --- na/kernel/ptrace.cc | 92 -- na/kernel/sched_class_o1.cc | 81 - na/kernel/sched_core.cc | 571 ------- na/kernel/sched_internal.h | 115 -- na/kernel/signal.cc | 442 ------ na/kernel/smp.cc | 112 -- na/kernel/sync/cpu_state.h | 35 - na/kernel/sync/mutex.cc | 156 -- na/kernel/sync/spinlock.cc | 120 -- na/kernel/sync/spinlock.h | 61 - na/kernel/sys_fs.cc | 679 -------- na/kernel/sys_info.cc | 49 - na/kernel/sys_misc.cc | 360 ----- na/kernel/sys_mm.cc | 178 --- na/kernel/sys_process.cc | 185 --- na/kernel/sys_signal.cc | 351 ----- na/kernel/syscall.cc | 208 --- na/kernel/task.cc | 780 --------- na/kernel/timer.cc | 118 -- na/kernel/trap.cc | 258 --- na/kernel/uaccess.cc | 53 - na/kernel/waitqueue.cc | 144 -- na/lib/cxx_abi.cc | 37 - na/lib/fdt.c | 2 - na/lib/fdt_addresses.c | 2 - na/lib/fdt_empty_tree.c | 2 - na/lib/fdt_ro.c | 2 - na/lib/fdt_rw.c | 2 - na/lib/fdt_strerror.c | 2 - na/lib/fdt_sw.c | 2 - na/lib/fdt_wip.c | 2 - na/lib/klog.cc | 574 ------- na/lib/kobject.cc | 82 - na/lib/kprintf.cc | 91 -- na/lib/module.mk | 66 - na/lib/printf.c | 914 ----------- na/lib/printf.h | 129 -- na/lib/random.cc | 152 -- na/lib/rbtree.cc | 596 ------- na/lib/string/COPYRIGHT | 193 --- na/lib/string/aarch64/memcpy.S | 186 --- na/lib/string/aarch64/memset.S | 115 -- na/lib/string/arm/__aeabi_memcpy.s | 45 - na/lib/string/arm/__aeabi_memset.s | 31 - na/lib/string/arm/memcpy.S | 479 ------ na/lib/string/bcmp.c | 11 - na/lib/string/bcopy.c | 11 - na/lib/string/bzero.c | 11 - na/lib/string/explicit_bzero.c | 11 - na/lib/string/i386/memcpy.s | 32 - na/lib/string/i386/memmove.s | 22 - na/lib/string/i386/memset.s | 76 - na/lib/string/index.c | 11 - na/lib/string/memccpy.c | 37 - na/lib/string/memchr.c | 30 - na/lib/string/memcmp.c | 11 - na/lib/string/memcpy.c | 127 -- na/lib/string/memmem.c | 152 -- na/lib/string/memmove.c | 45 - na/lib/string/mempcpy.c | 10 - na/lib/string/memrchr.c | 14 - na/lib/string/memset.c | 93 -- na/lib/string/rindex.c | 11 - na/lib/string/stpcpy.c | 32 - na/lib/string/stpncpy.c | 35 - na/lib/string/strcat.c | 10 - na/lib/string/strchr.c | 10 - na/lib/string/strchrnul.c | 31 - na/lib/string/strcmp.c | 10 - na/lib/string/strcpy.c | 10 - na/lib/string/strcspn.c | 20 - na/lib/string/strlcat.c | 12 - na/lib/string/strlcpy.c | 37 - na/lib/string/strlen.c | 25 - na/lib/string/strncat.c | 13 - na/lib/string/strncmp.c | 12 - na/lib/string/strncpy.c | 10 - na/lib/string/strnlen.c | 10 - na/lib/string/strpbrk.c | 10 - na/lib/string/strrchr.c | 9 - na/lib/string/strsep.c | 16 - na/lib/string/strspn.c | 23 - na/lib/string/strstr.c | 157 -- na/lib/string/strtok.c | 16 - na/lib/string/strtok_r.c | 15 - na/lib/string/x86_64/memcpy.s | 25 - na/lib/string/x86_64/memmove.s | 16 - na/lib/string/x86_64/memset.s | 72 - na/mm/memblock.cc | 257 --- na/mm/module.mk | 7 - na/mm/page_cache.cc | 252 --- na/mm/pmm.cc | 528 ------- na/mm/slab.cc | 321 ---- na/mm/slab.h | 83 - na/mm/tlb.cc | 90 -- na/mm/vma.cc | 640 -------- na/mm/vmm.cc | 235 --- na/scripts/Kbuild.include | 110 -- na/scripts/Kconfig.mk | 238 --- na/scripts/dtc/libfdt/fdt.c | 347 ---- na/scripts/dtc/libfdt/fdt.h | 66 - na/scripts/dtc/libfdt/fdt_addresses.c | 101 -- na/scripts/dtc/libfdt/fdt_empty_tree.c | 38 - na/scripts/dtc/libfdt/fdt_overlay.c | 1101 ------------- na/scripts/dtc/libfdt/fdt_ro.c | 888 ----------- na/scripts/dtc/libfdt/fdt_rw.c | 512 ------ na/scripts/dtc/libfdt/fdt_strerror.c | 60 - na/scripts/dtc/libfdt/fdt_sw.c | 384 ----- na/scripts/dtc/libfdt/fdt_wip.c | 94 -- na/scripts/dtc/libfdt/libfdt.h | 2540 ------------------------------ na/scripts/dtc/libfdt/libfdt_env.h | 69 - na/scripts/dtc/libfdt/libfdt_internal.h | 202 --- na/scripts/extract-asm-offsets.sed | 18 - na/scripts/gen_initramfs.sh | 250 --- na/test/module.mk | 30 - na/test/selftest.cc | 83 - na/test/test_atomic.cc | 102 -- na/test/test_dcache.cc | 232 --- na/test/test_driver_model.cc | 209 --- na/test/test_exec.cc | 447 ------ na/test/test_fdt.cc | 50 - na/test/test_klog.cc | 175 -- na/test/test_mutex.cc | 183 --- na/test/test_namei.cc | 173 -- na/test/test_page_cache.cc | 180 --- na/test/test_pid.cc | 106 -- na/test/test_pipe_devfs.cc | 420 ----- na/test/test_pmm.cc | 283 ---- na/test/test_random.cc | 49 - na/test/test_rbtree.cc | 119 -- na/test/test_sched.cc | 257 --- na/test/test_signal.cc | 193 --- na/test/test_slab.cc | 255 --- na/test/test_smp.cc | 136 -- na/test/test_spinlock.cc | 135 -- na/test/test_sys_fs_extra.cc | 238 --- na/test/test_sys_mm.cc | 178 --- na/test/test_sys_process.cc | 93 -- na/test/test_trap.cc | 197 --- na/test/test_uaccess.cc | 97 -- na/test/test_vfs_io.cc | 345 ---- na/test/test_vma.cc | 282 ---- na/test/test_vmm.cc | 58 - na/test/test_waitqueue.cc | 91 -- na/usr/default_cpio_list | 24 - na/usr/gen_init_cpio.c | 781 --------- scripts/Kbuild.include | 110 ++ scripts/Kconfig.mk | 238 +++ scripts/dtc/libfdt/fdt.c | 347 ++++ scripts/dtc/libfdt/fdt.h | 66 + scripts/dtc/libfdt/fdt_addresses.c | 101 ++ scripts/dtc/libfdt/fdt_empty_tree.c | 38 + scripts/dtc/libfdt/fdt_overlay.c | 1101 +++++++++++++ scripts/dtc/libfdt/fdt_ro.c | 888 +++++++++++ scripts/dtc/libfdt/fdt_rw.c | 512 ++++++ scripts/dtc/libfdt/fdt_strerror.c | 60 + scripts/dtc/libfdt/fdt_sw.c | 384 +++++ scripts/dtc/libfdt/fdt_wip.c | 94 ++ scripts/dtc/libfdt/libfdt.h | 2540 ++++++++++++++++++++++++++++++ scripts/dtc/libfdt/libfdt_env.h | 69 + scripts/dtc/libfdt/libfdt_internal.h | 202 +++ scripts/extract-asm-offsets.sed | 18 + scripts/gen_initramfs.sh | 250 +++ src/boot/entry.S | 47 - src/drivers/plic.cc | 77 - src/drivers/uart.cc | 204 --- src/drivers/virtio_dist.cc | 382 ----- src/fs/fat32.cc | 806 ---------- src/fs/file.cc | 358 ----- src/fs/fs.cc | 268 ---- src/kernel/bio.cc | 139 -- src/kernel/config.cc | 13 - src/kernel/cxx.cc | 78 - src/kernel/fdt.cc | 273 ---- src/kernel/initcode.S | 11 - src/kernel/main.cc | 79 - src/kernel/mm/pmm.cc | 305 ---- src/kernel/mm/slab.cc | 259 --- src/kernel/mm/vm.cc | 459 ------ src/kernel/proc/exec.cc | 260 --- src/kernel/proc/proc.cc | 654 -------- src/kernel/proc/swtch.S | 37 - src/kernel/sbi.cc | 52 - src/kernel/spinlock.cc | 199 --- src/kernel/syscall.cc | 698 -------- src/kernel/timer.cc | 66 - src/kernel/trap/handler.cc | 217 --- src/kernel/trap/trampoline.S | 101 -- src/kernel/trap/trap.S | 90 -- src/lib/string.cc | 88 -- src/ulib/assert.c | 12 - src/ulib/cxx.cc | 35 - src/ulib/iostream.cpp | 12 - src/ulib/stdio.c | 1056 ------------- src/ulib/stdlib.c | 543 ------- src/ulib/string.c | 335 ---- src/ulib/syscalls.c | 153 -- src/user/cat.cc | 52 - src/user/cp.cc | 52 - src/user/echo.cc | 26 - src/user/entry.S | 12 - src/user/grep.cc | 72 - src/user/init.cc | 64 - src/user/kill.cc | 27 - src/user/ln.cc | 22 - src/user/ls.cc | 116 -- src/user/mkdir.cc | 25 - src/user/mv.cc | 30 - src/user/rm.cc | 28 - src/user/sh.cc | 423 ----- src/user/touch.cc | 31 - src/user/user.ld | 36 - src/user/usys.S | 38 - src/user/wc.cc | 60 - test/module.mk | 30 + test/selftest.cc | 83 + test/test_atomic.cc | 102 ++ test/test_dcache.cc | 232 +++ test/test_driver_model.cc | 209 +++ test/test_exec.cc | 447 ++++++ test/test_fdt.cc | 50 + test/test_klog.cc | 175 ++ test/test_mutex.cc | 183 +++ test/test_namei.cc | 173 ++ test/test_page_cache.cc | 180 +++ test/test_pid.cc | 106 ++ test/test_pipe_devfs.cc | 420 +++++ test/test_pmm.cc | 283 ++++ test/test_random.cc | 49 + test/test_rbtree.cc | 119 ++ test/test_sched.cc | 257 +++ test/test_signal.cc | 193 +++ test/test_slab.cc | 255 +++ test/test_smp.cc | 136 ++ test/test_spinlock.cc | 135 ++ test/test_sys_fs_extra.cc | 238 +++ test/test_sys_mm.cc | 178 +++ test/test_sys_process.cc | 93 ++ test/test_trap.cc | 197 +++ test/test_uaccess.cc | 97 ++ test/test_vfs_io.cc | 345 ++++ test/test_vma.cc | 282 ++++ test/test_vmm.cc | 58 + test/test_waitqueue.cc | 91 ++ usr/default_cpio_list | 24 + usr/gen_init_cpio.c | 781 +++++++++ 823 files changed, 59223 insertions(+), 59223 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 arch/riscv/config.cc create mode 100644 arch/riscv/defconfig create mode 100644 arch/riscv/include/arch/config.h create mode 100644 arch/riscv/include/arch/context.h create mode 100644 arch/riscv/include/arch/cpu.h create mode 100644 arch/riscv/include/arch/extable.h create mode 100644 arch/riscv/include/arch/fpstate.h create mode 100644 arch/riscv/include/arch/mmu.h create mode 100644 arch/riscv/include/arch/pmap.h create mode 100644 arch/riscv/include/arch/random.h create mode 100644 arch/riscv/include/arch/sbi.h create mode 100644 arch/riscv/include/arch/tlb.h create mode 100644 arch/riscv/include/arch/trap.h create mode 100644 arch/riscv/include/arch/trapframe.h create mode 100644 arch/riscv/include/asm/csr.h create mode 100644 arch/riscv/include/asm/page.h create mode 100644 arch/riscv/include/asm/pgtable.h create mode 100644 arch/riscv/include/asm/sections.h create mode 100644 arch/riscv/kernel/asm-offsets.c create mode 100644 arch/riscv/kernel/head.S create mode 100644 arch/riscv/kernel/lumeos.lds.S create mode 100644 arch/riscv/kernel/signal.cc create mode 100644 arch/riscv/lib/random.cc create mode 100644 arch/riscv/mm/init.cc create mode 100644 arch/riscv/mm/pmap.cc create mode 100644 arch/riscv/mm/tlb_md.cc create mode 100644 arch/riscv/module.mk create mode 100644 arch/riscv/syscall/syscall_md.cc create mode 100644 arch/riscv/trap/extable.cc create mode 100644 arch/riscv/trap/fpu.S create mode 100644 arch/riscv/trap/swtch.S create mode 100644 arch/riscv/trap/trap_md.cc create mode 100644 arch/riscv/trap/trap_vector.S create mode 100644 arch/riscv/uaccess/uaccess_memcpy.S create mode 100644 drivers/base/bus.cc create mode 100644 drivers/base/core.cc create mode 100644 drivers/base/driver.cc create mode 100644 drivers/base/module.mk create mode 100644 drivers/base/platform.cc create mode 100644 drivers/console.cc create mode 100644 drivers/module.mk create mode 100644 drivers/plic.cc create mode 100644 drivers/tty/module.mk create mode 100644 drivers/tty/n_tty.cc create mode 100644 drivers/tty/tty_core.cc create mode 100644 drivers/tty/tty_uart.cc create mode 100644 drivers/uart.cc create mode 100644 fdt/device_node.cc create mode 100644 fdt/fdt.cc create mode 100644 fdt/module.mk create mode 100644 fs/chardev.cc create mode 100644 fs/dcache.cc create mode 100644 fs/devfs.cc create mode 100644 fs/fd.cc create mode 100644 fs/file_table.cc create mode 100644 fs/fs_struct.cc create mode 100644 fs/initramfs.cc create mode 100644 fs/inode.cc create mode 100644 fs/module.mk create mode 100644 fs/namei.cc create mode 100644 fs/pipe.cc create mode 100644 fs/ramfs/file.cc create mode 100644 fs/ramfs/inode.cc create mode 100644 fs/ramfs/super.cc create mode 100644 fs/super.cc create mode 100644 fs/vfs_init.cc delete mode 100644 include/common/fcntl.h delete mode 100644 include/common/types.h delete mode 100644 include/drivers/plic.h delete mode 100644 include/drivers/uart.h delete mode 100644 include/drivers/uart_color.h delete mode 100644 include/drivers/virtio.h delete mode 100644 include/fs/fat32.h delete mode 100644 include/fs/file.h delete mode 100644 include/fs/fs.h delete mode 100644 include/fs/stat.h delete mode 100644 include/kernel/buf.h delete mode 100644 include/kernel/config.h delete mode 100644 include/kernel/cpu.h delete mode 100644 include/kernel/elf.h delete mode 100644 include/kernel/exec.h delete mode 100644 include/kernel/fdt.h delete mode 100644 include/kernel/mm.h delete mode 100644 include/kernel/pmm.h delete mode 100644 include/kernel/proc.h delete mode 100644 include/kernel/proc_def.h delete mode 100644 include/kernel/riscv.h delete mode 100644 include/kernel/runqueue.h delete mode 100644 include/kernel/sbi.h delete mode 100644 include/kernel/slab.h delete mode 100644 include/kernel/spinlock.h delete mode 100644 include/kernel/sync.h delete mode 100644 include/kernel/syscall.h delete mode 100644 include/kernel/timer.h delete mode 100644 include/kernel/trap.h delete mode 100644 include/lib/errno.h delete mode 100644 include/lib/string.h create mode 100644 include/libc-shim/bits/alltypes.h create mode 100644 include/libc-shim/endian.h create mode 100644 include/libc-shim/features.h create mode 100644 include/libc-shim/limits.h create mode 100644 include/libc-shim/stdint.h create mode 100644 include/libc-shim/string.h create mode 100644 include/libc-shim/strings.h create mode 100644 include/lume/addr.h create mode 100644 include/lume/addr_types.h create mode 100644 include/lume/args.h create mode 100644 include/lume/atomic.h create mode 100644 include/lume/bitops.h create mode 100644 include/lume/chardev.h create mode 100644 include/lume/clone.h create mode 100644 include/lume/compiler.h create mode 100644 include/lume/config.h create mode 100644 include/lume/console.h create mode 100644 include/lume/container_of.h create mode 100644 include/lume/dcache.h create mode 100644 include/lume/device.h create mode 100644 include/lume/device_node.h create mode 100644 include/lume/driver.h create mode 100644 include/lume/elf.h create mode 100644 include/lume/errno.h create mode 100644 include/lume/fdt.h create mode 100644 include/lume/fdt_prop.h create mode 100644 include/lume/fdtable.h create mode 100644 include/lume/features.h create mode 100644 include/lume/file.h create mode 100644 include/lume/fs.h create mode 100644 include/lume/fs_struct.h create mode 100644 include/lume/fs_types.h create mode 100644 include/lume/init.h create mode 100644 include/lume/irq.h create mode 100644 include/lume/kbuild.h create mode 100644 include/lume/kernel_map.h create mode 100644 include/lume/klog.h create mode 100644 include/lume/kobject.h create mode 100644 include/lume/kprintf.h create mode 100644 include/lume/kref.h create mode 100644 include/lume/libfdt.h create mode 100644 include/lume/libfdt_env.h create mode 100644 include/lume/limits.h create mode 100644 include/lume/list.h create mode 100644 include/lume/lumeos.lds.h create mode 100644 include/lume/memblock.h create mode 100644 include/lume/mm_layout.h create mode 100644 include/lume/mman.h create mode 100644 include/lume/mount.h create mode 100644 include/lume/mutex.h create mode 100644 include/lume/namei.h create mode 100644 include/lume/new.h create mode 100644 include/lume/page.h create mode 100644 include/lume/page_cache.h create mode 100644 include/lume/pgrp.h create mode 100644 include/lume/pgtable_types.h create mode 100644 include/lume/pid.h create mode 100644 include/lume/pipe.h create mode 100644 include/lume/platform_device.h create mode 100644 include/lume/pmm.h create mode 100644 include/lume/poll.h create mode 100644 include/lume/ptrace.h create mode 100644 include/lume/random.h create mode 100644 include/lume/rbtree.h create mode 100644 include/lume/rbtree_augmented.h create mode 100644 include/lume/rbtree_types.h create mode 100644 include/lume/sched.h create mode 100644 include/lume/sched_class.h create mode 100644 include/lume/selftest.h create mode 100644 include/lume/setup.h create mode 100644 include/lume/signal.h create mode 100644 include/lume/signal_nr.h create mode 100644 include/lume/signal_types.h create mode 100644 include/lume/slab.h create mode 100644 include/lume/smp.h create mode 100644 include/lume/stdarg.h create mode 100644 include/lume/stringify.h create mode 100644 include/lume/syscall.h create mode 100644 include/lume/syscall_nr.h create mode 100644 include/lume/task.h create mode 100644 include/lume/timer.h create mode 100644 include/lume/tlb.h create mode 100644 include/lume/trap.h create mode 100644 include/lume/tty.h create mode 100644 include/lume/tty_ioctl.h create mode 100644 include/lume/types.h create mode 100644 include/lume/uaccess.h create mode 100644 include/lume/uuid.h create mode 100644 include/lume/vmm.h create mode 100644 include/lume/wait.h create mode 100644 include/lume/waitqueue.h delete mode 100644 include/ulib/assert.h delete mode 100644 include/ulib/cstdlib delete mode 100644 include/ulib/cstring delete mode 100644 include/ulib/ctypes.h delete mode 100644 include/ulib/cxx.cc delete mode 100644 include/ulib/errno.h delete mode 100644 include/ulib/iostream delete mode 100644 include/ulib/limits.h delete mode 100644 include/ulib/stb_sprintf.h delete mode 100644 include/ulib/stdio.h delete mode 100644 include/ulib/stdlib.h delete mode 100644 include/ulib/string.h delete mode 100644 include/ulib/types.h delete mode 100644 include/user/user.h create mode 100644 kernel/exec.cc create mode 100644 kernel/irq.cc create mode 100644 kernel/main.cc create mode 100644 kernel/module.mk create mode 100644 kernel/pgrp.cc create mode 100644 kernel/pid.cc create mode 100644 kernel/poll.cc create mode 100644 kernel/ptrace.cc create mode 100644 kernel/sched_class_o1.cc create mode 100644 kernel/sched_core.cc create mode 100644 kernel/sched_internal.h create mode 100644 kernel/signal.cc create mode 100644 kernel/smp.cc create mode 100644 kernel/sync/cpu_state.h create mode 100644 kernel/sync/mutex.cc create mode 100644 kernel/sync/spinlock.cc create mode 100644 kernel/sync/spinlock.h create mode 100644 kernel/sys_fs.cc create mode 100644 kernel/sys_info.cc create mode 100644 kernel/sys_misc.cc create mode 100644 kernel/sys_mm.cc create mode 100644 kernel/sys_process.cc create mode 100644 kernel/sys_signal.cc create mode 100644 kernel/syscall.cc create mode 100644 kernel/task.cc create mode 100644 kernel/timer.cc create mode 100644 kernel/trap.cc create mode 100644 kernel/uaccess.cc create mode 100644 kernel/waitqueue.cc create mode 100644 lib/cxx_abi.cc create mode 100644 lib/fdt.c create mode 100644 lib/fdt_addresses.c create mode 100644 lib/fdt_empty_tree.c create mode 100644 lib/fdt_ro.c create mode 100644 lib/fdt_rw.c create mode 100644 lib/fdt_strerror.c create mode 100644 lib/fdt_sw.c create mode 100644 lib/fdt_wip.c create mode 100644 lib/klog.cc create mode 100644 lib/kobject.cc create mode 100644 lib/kprintf.cc create mode 100644 lib/module.mk create mode 100644 lib/printf.c create mode 100644 lib/printf.h create mode 100644 lib/random.cc create mode 100644 lib/rbtree.cc create mode 100644 lib/string/COPYRIGHT create mode 100644 lib/string/aarch64/memcpy.S create mode 100644 lib/string/aarch64/memset.S create mode 100644 lib/string/arm/__aeabi_memcpy.s create mode 100644 lib/string/arm/__aeabi_memset.s create mode 100644 lib/string/arm/memcpy.S create mode 100644 lib/string/bcmp.c create mode 100644 lib/string/bcopy.c create mode 100644 lib/string/bzero.c create mode 100644 lib/string/explicit_bzero.c create mode 100644 lib/string/i386/memcpy.s create mode 100644 lib/string/i386/memmove.s create mode 100644 lib/string/i386/memset.s create mode 100644 lib/string/index.c create mode 100644 lib/string/memccpy.c create mode 100644 lib/string/memchr.c create mode 100644 lib/string/memcmp.c create mode 100644 lib/string/memcpy.c create mode 100644 lib/string/memmem.c create mode 100644 lib/string/memmove.c create mode 100644 lib/string/mempcpy.c create mode 100644 lib/string/memrchr.c create mode 100644 lib/string/memset.c create mode 100644 lib/string/rindex.c create mode 100644 lib/string/stpcpy.c create mode 100644 lib/string/stpncpy.c create mode 100644 lib/string/strcat.c create mode 100644 lib/string/strchr.c create mode 100644 lib/string/strchrnul.c create mode 100644 lib/string/strcmp.c create mode 100644 lib/string/strcpy.c create mode 100644 lib/string/strcspn.c create mode 100644 lib/string/strlcat.c create mode 100644 lib/string/strlcpy.c create mode 100644 lib/string/strlen.c create mode 100644 lib/string/strncat.c create mode 100644 lib/string/strncmp.c create mode 100644 lib/string/strncpy.c create mode 100644 lib/string/strnlen.c create mode 100644 lib/string/strpbrk.c create mode 100644 lib/string/strrchr.c create mode 100644 lib/string/strsep.c create mode 100644 lib/string/strspn.c create mode 100644 lib/string/strstr.c create mode 100644 lib/string/strtok.c create mode 100644 lib/string/strtok_r.c create mode 100644 lib/string/x86_64/memcpy.s create mode 100644 lib/string/x86_64/memmove.s create mode 100644 lib/string/x86_64/memset.s delete mode 100644 linker.ld create mode 100644 lume-legacy/Makefile create mode 100644 lume-legacy/include/common/fcntl.h create mode 100644 lume-legacy/include/common/types.h create mode 100644 lume-legacy/include/drivers/plic.h create mode 100644 lume-legacy/include/drivers/uart.h create mode 100644 lume-legacy/include/drivers/uart_color.h create mode 100644 lume-legacy/include/drivers/virtio.h create mode 100644 lume-legacy/include/fs/fat32.h create mode 100644 lume-legacy/include/fs/file.h create mode 100644 lume-legacy/include/fs/fs.h create mode 100644 lume-legacy/include/fs/stat.h create mode 100644 lume-legacy/include/kernel/buf.h create mode 100644 lume-legacy/include/kernel/config.h create mode 100644 lume-legacy/include/kernel/cpu.h create mode 100644 lume-legacy/include/kernel/elf.h create mode 100644 lume-legacy/include/kernel/exec.h create mode 100644 lume-legacy/include/kernel/fdt.h create mode 100644 lume-legacy/include/kernel/mm.h create mode 100644 lume-legacy/include/kernel/pmm.h create mode 100644 lume-legacy/include/kernel/proc.h create mode 100644 lume-legacy/include/kernel/proc_def.h create mode 100644 lume-legacy/include/kernel/riscv.h create mode 100644 lume-legacy/include/kernel/runqueue.h create mode 100644 lume-legacy/include/kernel/sbi.h create mode 100644 lume-legacy/include/kernel/slab.h create mode 100644 lume-legacy/include/kernel/spinlock.h create mode 100644 lume-legacy/include/kernel/sync.h create mode 100644 lume-legacy/include/kernel/syscall.h create mode 100644 lume-legacy/include/kernel/timer.h create mode 100644 lume-legacy/include/kernel/trap.h create mode 100644 lume-legacy/include/lib/errno.h create mode 100644 lume-legacy/include/lib/string.h create mode 100644 lume-legacy/include/ulib/assert.h create mode 100644 lume-legacy/include/ulib/cstdlib create mode 100644 lume-legacy/include/ulib/cstring create mode 100644 lume-legacy/include/ulib/ctypes.h create mode 100644 lume-legacy/include/ulib/cxx.cc create mode 100644 lume-legacy/include/ulib/errno.h create mode 100644 lume-legacy/include/ulib/iostream create mode 100644 lume-legacy/include/ulib/limits.h create mode 100644 lume-legacy/include/ulib/stb_sprintf.h create mode 100644 lume-legacy/include/ulib/stdio.h create mode 100644 lume-legacy/include/ulib/stdlib.h create mode 100644 lume-legacy/include/ulib/string.h create mode 100644 lume-legacy/include/ulib/types.h create mode 100644 lume-legacy/include/user/user.h create mode 100644 lume-legacy/linker.ld create mode 100644 lume-legacy/src/boot/entry.S create mode 100644 lume-legacy/src/drivers/plic.cc create mode 100644 lume-legacy/src/drivers/uart.cc create mode 100644 lume-legacy/src/drivers/virtio_dist.cc create mode 100644 lume-legacy/src/fs/fat32.cc create mode 100644 lume-legacy/src/fs/file.cc create mode 100644 lume-legacy/src/fs/fs.cc create mode 100644 lume-legacy/src/kernel/bio.cc create mode 100644 lume-legacy/src/kernel/config.cc create mode 100644 lume-legacy/src/kernel/cxx.cc create mode 100644 lume-legacy/src/kernel/fdt.cc create mode 100644 lume-legacy/src/kernel/initcode.S create mode 100644 lume-legacy/src/kernel/main.cc create mode 100644 lume-legacy/src/kernel/mm/pmm.cc create mode 100644 lume-legacy/src/kernel/mm/slab.cc create mode 100644 lume-legacy/src/kernel/mm/vm.cc create mode 100644 lume-legacy/src/kernel/proc/exec.cc create mode 100644 lume-legacy/src/kernel/proc/proc.cc create mode 100644 lume-legacy/src/kernel/proc/swtch.S create mode 100644 lume-legacy/src/kernel/sbi.cc create mode 100644 lume-legacy/src/kernel/spinlock.cc create mode 100644 lume-legacy/src/kernel/syscall.cc create mode 100644 lume-legacy/src/kernel/timer.cc create mode 100644 lume-legacy/src/kernel/trap/handler.cc create mode 100644 lume-legacy/src/kernel/trap/trampoline.S create mode 100644 lume-legacy/src/kernel/trap/trap.S create mode 100644 lume-legacy/src/lib/string.cc create mode 100644 lume-legacy/src/ulib/assert.c create mode 100644 lume-legacy/src/ulib/cxx.cc create mode 100644 lume-legacy/src/ulib/iostream.cpp create mode 100644 lume-legacy/src/ulib/stdio.c create mode 100644 lume-legacy/src/ulib/stdlib.c create mode 100644 lume-legacy/src/ulib/string.c create mode 100644 lume-legacy/src/ulib/syscalls.c create mode 100644 lume-legacy/src/user/cat.cc create mode 100644 lume-legacy/src/user/cp.cc create mode 100644 lume-legacy/src/user/echo.cc create mode 100644 lume-legacy/src/user/entry.S create mode 100644 lume-legacy/src/user/grep.cc create mode 100644 lume-legacy/src/user/init.cc create mode 100644 lume-legacy/src/user/kill.cc create mode 100644 lume-legacy/src/user/ln.cc create mode 100644 lume-legacy/src/user/ls.cc create mode 100644 lume-legacy/src/user/mkdir.cc create mode 100644 lume-legacy/src/user/mv.cc create mode 100644 lume-legacy/src/user/rm.cc create mode 100644 lume-legacy/src/user/sh.cc create mode 100644 lume-legacy/src/user/touch.cc create mode 100644 lume-legacy/src/user/user.ld create mode 100644 lume-legacy/src/user/usys.S create mode 100644 lume-legacy/src/user/wc.cc create mode 100644 mm/memblock.cc create mode 100644 mm/module.mk create mode 100644 mm/page_cache.cc create mode 100644 mm/pmm.cc create mode 100644 mm/slab.cc create mode 100644 mm/slab.h create mode 100644 mm/tlb.cc create mode 100644 mm/vma.cc create mode 100644 mm/vmm.cc delete mode 100644 na/.clang-format delete mode 100644 na/.clang-tidy delete mode 100644 na/Makefile delete mode 100644 na/arch/riscv/config.cc delete mode 100644 na/arch/riscv/defconfig delete mode 100644 na/arch/riscv/include/arch/config.h delete mode 100644 na/arch/riscv/include/arch/context.h delete mode 100644 na/arch/riscv/include/arch/cpu.h delete mode 100644 na/arch/riscv/include/arch/extable.h delete mode 100644 na/arch/riscv/include/arch/fpstate.h delete mode 100644 na/arch/riscv/include/arch/mmu.h delete mode 100644 na/arch/riscv/include/arch/pmap.h delete mode 100644 na/arch/riscv/include/arch/random.h delete mode 100644 na/arch/riscv/include/arch/sbi.h delete mode 100644 na/arch/riscv/include/arch/tlb.h delete mode 100644 na/arch/riscv/include/arch/trap.h delete mode 100644 na/arch/riscv/include/arch/trapframe.h delete mode 100644 na/arch/riscv/include/asm/csr.h delete mode 100644 na/arch/riscv/include/asm/page.h delete mode 100644 na/arch/riscv/include/asm/pgtable.h delete mode 100644 na/arch/riscv/include/asm/sections.h delete mode 100644 na/arch/riscv/kernel/asm-offsets.c delete mode 100644 na/arch/riscv/kernel/head.S delete mode 100644 na/arch/riscv/kernel/lumeos.lds.S delete mode 100644 na/arch/riscv/kernel/signal.cc delete mode 100644 na/arch/riscv/lib/random.cc delete mode 100644 na/arch/riscv/mm/init.cc delete mode 100644 na/arch/riscv/mm/pmap.cc delete mode 100644 na/arch/riscv/mm/tlb_md.cc delete mode 100644 na/arch/riscv/module.mk delete mode 100644 na/arch/riscv/syscall/syscall_md.cc delete mode 100644 na/arch/riscv/trap/extable.cc delete mode 100644 na/arch/riscv/trap/fpu.S delete mode 100644 na/arch/riscv/trap/swtch.S delete mode 100644 na/arch/riscv/trap/trap_md.cc delete mode 100644 na/arch/riscv/trap/trap_vector.S delete mode 100644 na/arch/riscv/uaccess/uaccess_memcpy.S delete mode 100644 na/drivers/base/bus.cc delete mode 100644 na/drivers/base/core.cc delete mode 100644 na/drivers/base/driver.cc delete mode 100644 na/drivers/base/module.mk delete mode 100644 na/drivers/base/platform.cc delete mode 100644 na/drivers/console.cc delete mode 100644 na/drivers/module.mk delete mode 100644 na/drivers/plic.cc delete mode 100644 na/drivers/tty/module.mk delete mode 100644 na/drivers/tty/n_tty.cc delete mode 100644 na/drivers/tty/tty_core.cc delete mode 100644 na/drivers/tty/tty_uart.cc delete mode 100644 na/drivers/uart.cc delete mode 100644 na/fdt/device_node.cc delete mode 100644 na/fdt/fdt.cc delete mode 100644 na/fdt/module.mk delete mode 100644 na/fs/chardev.cc delete mode 100644 na/fs/dcache.cc delete mode 100644 na/fs/devfs.cc delete mode 100644 na/fs/fd.cc delete mode 100644 na/fs/file_table.cc delete mode 100644 na/fs/fs_struct.cc delete mode 100644 na/fs/initramfs.cc delete mode 100644 na/fs/inode.cc delete mode 100644 na/fs/module.mk delete mode 100644 na/fs/namei.cc delete mode 100644 na/fs/pipe.cc delete mode 100644 na/fs/ramfs/file.cc delete mode 100644 na/fs/ramfs/inode.cc delete mode 100644 na/fs/ramfs/super.cc delete mode 100644 na/fs/super.cc delete mode 100644 na/fs/vfs_init.cc delete mode 100644 na/include/libc-shim/bits/alltypes.h delete mode 100644 na/include/libc-shim/endian.h delete mode 100644 na/include/libc-shim/features.h delete mode 100644 na/include/libc-shim/limits.h delete mode 100644 na/include/libc-shim/stdint.h delete mode 100644 na/include/libc-shim/string.h delete mode 100644 na/include/libc-shim/strings.h delete mode 100644 na/include/lume/addr.h delete mode 100644 na/include/lume/addr_types.h delete mode 100644 na/include/lume/args.h delete mode 100644 na/include/lume/atomic.h delete mode 100644 na/include/lume/bitops.h delete mode 100644 na/include/lume/chardev.h delete mode 100644 na/include/lume/clone.h delete mode 100644 na/include/lume/compiler.h delete mode 100644 na/include/lume/config.h delete mode 100644 na/include/lume/console.h delete mode 100644 na/include/lume/container_of.h delete mode 100644 na/include/lume/dcache.h delete mode 100644 na/include/lume/device.h delete mode 100644 na/include/lume/device_node.h delete mode 100644 na/include/lume/driver.h delete mode 100644 na/include/lume/elf.h delete mode 100644 na/include/lume/errno.h delete mode 100644 na/include/lume/fdt.h delete mode 100644 na/include/lume/fdt_prop.h delete mode 100644 na/include/lume/fdtable.h delete mode 100644 na/include/lume/features.h delete mode 100644 na/include/lume/file.h delete mode 100644 na/include/lume/fs.h delete mode 100644 na/include/lume/fs_struct.h delete mode 100644 na/include/lume/fs_types.h delete mode 100644 na/include/lume/init.h delete mode 100644 na/include/lume/irq.h delete mode 100644 na/include/lume/kbuild.h delete mode 100644 na/include/lume/kernel_map.h delete mode 100644 na/include/lume/klog.h delete mode 100644 na/include/lume/kobject.h delete mode 100644 na/include/lume/kprintf.h delete mode 100644 na/include/lume/kref.h delete mode 100644 na/include/lume/libfdt.h delete mode 100644 na/include/lume/libfdt_env.h delete mode 100644 na/include/lume/limits.h delete mode 100644 na/include/lume/list.h delete mode 100644 na/include/lume/lumeos.lds.h delete mode 100644 na/include/lume/memblock.h delete mode 100644 na/include/lume/mm_layout.h delete mode 100644 na/include/lume/mman.h delete mode 100644 na/include/lume/mount.h delete mode 100644 na/include/lume/mutex.h delete mode 100644 na/include/lume/namei.h delete mode 100644 na/include/lume/new.h delete mode 100644 na/include/lume/page.h delete mode 100644 na/include/lume/page_cache.h delete mode 100644 na/include/lume/pgrp.h delete mode 100644 na/include/lume/pgtable_types.h delete mode 100644 na/include/lume/pid.h delete mode 100644 na/include/lume/pipe.h delete mode 100644 na/include/lume/platform_device.h delete mode 100644 na/include/lume/pmm.h delete mode 100644 na/include/lume/poll.h delete mode 100644 na/include/lume/ptrace.h delete mode 100644 na/include/lume/random.h delete mode 100644 na/include/lume/rbtree.h delete mode 100644 na/include/lume/rbtree_augmented.h delete mode 100644 na/include/lume/rbtree_types.h delete mode 100644 na/include/lume/sched.h delete mode 100644 na/include/lume/sched_class.h delete mode 100644 na/include/lume/selftest.h delete mode 100644 na/include/lume/setup.h delete mode 100644 na/include/lume/signal.h delete mode 100644 na/include/lume/signal_nr.h delete mode 100644 na/include/lume/signal_types.h delete mode 100644 na/include/lume/slab.h delete mode 100644 na/include/lume/smp.h delete mode 100644 na/include/lume/stdarg.h delete mode 100644 na/include/lume/stringify.h delete mode 100644 na/include/lume/syscall.h delete mode 100644 na/include/lume/syscall_nr.h delete mode 100644 na/include/lume/task.h delete mode 100644 na/include/lume/timer.h delete mode 100644 na/include/lume/tlb.h delete mode 100644 na/include/lume/trap.h delete mode 100644 na/include/lume/tty.h delete mode 100644 na/include/lume/tty_ioctl.h delete mode 100644 na/include/lume/types.h delete mode 100644 na/include/lume/uaccess.h delete mode 100644 na/include/lume/uuid.h delete mode 100644 na/include/lume/vmm.h delete mode 100644 na/include/lume/wait.h delete mode 100644 na/include/lume/waitqueue.h delete mode 100644 na/kernel/exec.cc delete mode 100644 na/kernel/irq.cc delete mode 100644 na/kernel/main.cc delete mode 100644 na/kernel/module.mk delete mode 100644 na/kernel/pgrp.cc delete mode 100644 na/kernel/pid.cc delete mode 100644 na/kernel/poll.cc delete mode 100644 na/kernel/ptrace.cc delete mode 100644 na/kernel/sched_class_o1.cc delete mode 100644 na/kernel/sched_core.cc delete mode 100644 na/kernel/sched_internal.h delete mode 100644 na/kernel/signal.cc delete mode 100644 na/kernel/smp.cc delete mode 100644 na/kernel/sync/cpu_state.h delete mode 100644 na/kernel/sync/mutex.cc delete mode 100644 na/kernel/sync/spinlock.cc delete mode 100644 na/kernel/sync/spinlock.h delete mode 100644 na/kernel/sys_fs.cc delete mode 100644 na/kernel/sys_info.cc delete mode 100644 na/kernel/sys_misc.cc delete mode 100644 na/kernel/sys_mm.cc delete mode 100644 na/kernel/sys_process.cc delete mode 100644 na/kernel/sys_signal.cc delete mode 100644 na/kernel/syscall.cc delete mode 100644 na/kernel/task.cc delete mode 100644 na/kernel/timer.cc delete mode 100644 na/kernel/trap.cc delete mode 100644 na/kernel/uaccess.cc delete mode 100644 na/kernel/waitqueue.cc delete mode 100644 na/lib/cxx_abi.cc delete mode 100644 na/lib/fdt.c delete mode 100644 na/lib/fdt_addresses.c delete mode 100644 na/lib/fdt_empty_tree.c delete mode 100644 na/lib/fdt_ro.c delete mode 100644 na/lib/fdt_rw.c delete mode 100644 na/lib/fdt_strerror.c delete mode 100644 na/lib/fdt_sw.c delete mode 100644 na/lib/fdt_wip.c delete mode 100644 na/lib/klog.cc delete mode 100644 na/lib/kobject.cc delete mode 100644 na/lib/kprintf.cc delete mode 100644 na/lib/module.mk delete mode 100644 na/lib/printf.c delete mode 100644 na/lib/printf.h delete mode 100644 na/lib/random.cc delete mode 100644 na/lib/rbtree.cc delete mode 100644 na/lib/string/COPYRIGHT delete mode 100644 na/lib/string/aarch64/memcpy.S delete mode 100644 na/lib/string/aarch64/memset.S delete mode 100644 na/lib/string/arm/__aeabi_memcpy.s delete mode 100644 na/lib/string/arm/__aeabi_memset.s delete mode 100644 na/lib/string/arm/memcpy.S delete mode 100644 na/lib/string/bcmp.c delete mode 100644 na/lib/string/bcopy.c delete mode 100644 na/lib/string/bzero.c delete mode 100644 na/lib/string/explicit_bzero.c delete mode 100644 na/lib/string/i386/memcpy.s delete mode 100644 na/lib/string/i386/memmove.s delete mode 100644 na/lib/string/i386/memset.s delete mode 100644 na/lib/string/index.c delete mode 100644 na/lib/string/memccpy.c delete mode 100644 na/lib/string/memchr.c delete mode 100644 na/lib/string/memcmp.c delete mode 100644 na/lib/string/memcpy.c delete mode 100644 na/lib/string/memmem.c delete mode 100644 na/lib/string/memmove.c delete mode 100644 na/lib/string/mempcpy.c delete mode 100644 na/lib/string/memrchr.c delete mode 100644 na/lib/string/memset.c delete mode 100644 na/lib/string/rindex.c delete mode 100644 na/lib/string/stpcpy.c delete mode 100644 na/lib/string/stpncpy.c delete mode 100644 na/lib/string/strcat.c delete mode 100644 na/lib/string/strchr.c delete mode 100644 na/lib/string/strchrnul.c delete mode 100644 na/lib/string/strcmp.c delete mode 100644 na/lib/string/strcpy.c delete mode 100644 na/lib/string/strcspn.c delete mode 100644 na/lib/string/strlcat.c delete mode 100644 na/lib/string/strlcpy.c delete mode 100644 na/lib/string/strlen.c delete mode 100644 na/lib/string/strncat.c delete mode 100644 na/lib/string/strncmp.c delete mode 100644 na/lib/string/strncpy.c delete mode 100644 na/lib/string/strnlen.c delete mode 100644 na/lib/string/strpbrk.c delete mode 100644 na/lib/string/strrchr.c delete mode 100644 na/lib/string/strsep.c delete mode 100644 na/lib/string/strspn.c delete mode 100644 na/lib/string/strstr.c delete mode 100644 na/lib/string/strtok.c delete mode 100644 na/lib/string/strtok_r.c delete mode 100644 na/lib/string/x86_64/memcpy.s delete mode 100644 na/lib/string/x86_64/memmove.s delete mode 100644 na/lib/string/x86_64/memset.s delete mode 100644 na/mm/memblock.cc delete mode 100644 na/mm/module.mk delete mode 100644 na/mm/page_cache.cc delete mode 100644 na/mm/pmm.cc delete mode 100644 na/mm/slab.cc delete mode 100644 na/mm/slab.h delete mode 100644 na/mm/tlb.cc delete mode 100644 na/mm/vma.cc delete mode 100644 na/mm/vmm.cc delete mode 100644 na/scripts/Kbuild.include delete mode 100644 na/scripts/Kconfig.mk delete mode 100644 na/scripts/dtc/libfdt/fdt.c delete mode 100644 na/scripts/dtc/libfdt/fdt.h delete mode 100644 na/scripts/dtc/libfdt/fdt_addresses.c delete mode 100644 na/scripts/dtc/libfdt/fdt_empty_tree.c delete mode 100644 na/scripts/dtc/libfdt/fdt_overlay.c delete mode 100644 na/scripts/dtc/libfdt/fdt_ro.c delete mode 100644 na/scripts/dtc/libfdt/fdt_rw.c delete mode 100644 na/scripts/dtc/libfdt/fdt_strerror.c delete mode 100644 na/scripts/dtc/libfdt/fdt_sw.c delete mode 100644 na/scripts/dtc/libfdt/fdt_wip.c delete mode 100644 na/scripts/dtc/libfdt/libfdt.h delete mode 100644 na/scripts/dtc/libfdt/libfdt_env.h delete mode 100644 na/scripts/dtc/libfdt/libfdt_internal.h delete mode 100644 na/scripts/extract-asm-offsets.sed delete mode 100755 na/scripts/gen_initramfs.sh delete mode 100644 na/test/module.mk delete mode 100644 na/test/selftest.cc delete mode 100644 na/test/test_atomic.cc delete mode 100644 na/test/test_dcache.cc delete mode 100644 na/test/test_driver_model.cc delete mode 100644 na/test/test_exec.cc delete mode 100644 na/test/test_fdt.cc delete mode 100644 na/test/test_klog.cc delete mode 100644 na/test/test_mutex.cc delete mode 100644 na/test/test_namei.cc delete mode 100644 na/test/test_page_cache.cc delete mode 100644 na/test/test_pid.cc delete mode 100644 na/test/test_pipe_devfs.cc delete mode 100644 na/test/test_pmm.cc delete mode 100644 na/test/test_random.cc delete mode 100644 na/test/test_rbtree.cc delete mode 100644 na/test/test_sched.cc delete mode 100644 na/test/test_signal.cc delete mode 100644 na/test/test_slab.cc delete mode 100644 na/test/test_smp.cc delete mode 100644 na/test/test_spinlock.cc delete mode 100644 na/test/test_sys_fs_extra.cc delete mode 100644 na/test/test_sys_mm.cc delete mode 100644 na/test/test_sys_process.cc delete mode 100644 na/test/test_trap.cc delete mode 100644 na/test/test_uaccess.cc delete mode 100644 na/test/test_vfs_io.cc delete mode 100644 na/test/test_vma.cc delete mode 100644 na/test/test_vmm.cc delete mode 100644 na/test/test_waitqueue.cc delete mode 100644 na/usr/default_cpio_list delete mode 100644 na/usr/gen_init_cpio.c create mode 100644 scripts/Kbuild.include create mode 100644 scripts/Kconfig.mk create mode 100644 scripts/dtc/libfdt/fdt.c create mode 100644 scripts/dtc/libfdt/fdt.h create mode 100644 scripts/dtc/libfdt/fdt_addresses.c create mode 100644 scripts/dtc/libfdt/fdt_empty_tree.c create mode 100644 scripts/dtc/libfdt/fdt_overlay.c create mode 100644 scripts/dtc/libfdt/fdt_ro.c create mode 100644 scripts/dtc/libfdt/fdt_rw.c create mode 100644 scripts/dtc/libfdt/fdt_strerror.c create mode 100644 scripts/dtc/libfdt/fdt_sw.c create mode 100644 scripts/dtc/libfdt/fdt_wip.c create mode 100644 scripts/dtc/libfdt/libfdt.h create mode 100644 scripts/dtc/libfdt/libfdt_env.h create mode 100644 scripts/dtc/libfdt/libfdt_internal.h create mode 100644 scripts/extract-asm-offsets.sed create mode 100755 scripts/gen_initramfs.sh delete mode 100644 src/boot/entry.S delete mode 100644 src/drivers/plic.cc delete mode 100644 src/drivers/uart.cc delete mode 100644 src/drivers/virtio_dist.cc delete mode 100644 src/fs/fat32.cc delete mode 100644 src/fs/file.cc delete mode 100644 src/fs/fs.cc delete mode 100644 src/kernel/bio.cc delete mode 100644 src/kernel/config.cc delete mode 100644 src/kernel/cxx.cc delete mode 100644 src/kernel/fdt.cc delete mode 100644 src/kernel/initcode.S delete mode 100644 src/kernel/main.cc delete mode 100644 src/kernel/mm/pmm.cc delete mode 100644 src/kernel/mm/slab.cc delete mode 100644 src/kernel/mm/vm.cc delete mode 100644 src/kernel/proc/exec.cc delete mode 100644 src/kernel/proc/proc.cc delete mode 100644 src/kernel/proc/swtch.S delete mode 100644 src/kernel/sbi.cc delete mode 100644 src/kernel/spinlock.cc delete mode 100644 src/kernel/syscall.cc delete mode 100644 src/kernel/timer.cc delete mode 100644 src/kernel/trap/handler.cc delete mode 100644 src/kernel/trap/trampoline.S delete mode 100644 src/kernel/trap/trap.S delete mode 100644 src/lib/string.cc delete mode 100644 src/ulib/assert.c delete mode 100644 src/ulib/cxx.cc delete mode 100644 src/ulib/iostream.cpp delete mode 100644 src/ulib/stdio.c delete mode 100644 src/ulib/stdlib.c delete mode 100644 src/ulib/string.c delete mode 100644 src/ulib/syscalls.c delete mode 100644 src/user/cat.cc delete mode 100644 src/user/cp.cc delete mode 100644 src/user/echo.cc delete mode 100644 src/user/entry.S delete mode 100644 src/user/grep.cc delete mode 100644 src/user/init.cc delete mode 100644 src/user/kill.cc delete mode 100644 src/user/ln.cc delete mode 100644 src/user/ls.cc delete mode 100644 src/user/mkdir.cc delete mode 100644 src/user/mv.cc delete mode 100644 src/user/rm.cc delete mode 100644 src/user/sh.cc delete mode 100644 src/user/touch.cc delete mode 100644 src/user/user.ld delete mode 100644 src/user/usys.S delete mode 100644 src/user/wc.cc create mode 100644 test/module.mk create mode 100644 test/selftest.cc create mode 100644 test/test_atomic.cc create mode 100644 test/test_dcache.cc create mode 100644 test/test_driver_model.cc create mode 100644 test/test_exec.cc create mode 100644 test/test_fdt.cc create mode 100644 test/test_klog.cc create mode 100644 test/test_mutex.cc create mode 100644 test/test_namei.cc create mode 100644 test/test_page_cache.cc create mode 100644 test/test_pid.cc create mode 100644 test/test_pipe_devfs.cc create mode 100644 test/test_pmm.cc create mode 100644 test/test_random.cc create mode 100644 test/test_rbtree.cc create mode 100644 test/test_sched.cc create mode 100644 test/test_signal.cc create mode 100644 test/test_slab.cc create mode 100644 test/test_smp.cc create mode 100644 test/test_spinlock.cc create mode 100644 test/test_sys_fs_extra.cc create mode 100644 test/test_sys_mm.cc create mode 100644 test/test_sys_process.cc create mode 100644 test/test_trap.cc create mode 100644 test/test_uaccess.cc create mode 100644 test/test_vfs_io.cc create mode 100644 test/test_vma.cc create mode 100644 test/test_vmm.cc create mode 100644 test/test_waitqueue.cc create mode 100644 usr/default_cpio_list create mode 100644 usr/gen_init_cpio.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..141e1e2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,35 @@ +Language: Cpp +Standard: c++23 +BasedOnStyle: Google + +# Core Formatting +IndentWidth: 4 +ColumnLimit: 100 +TabWidth: 4 +UseTab: Never + +# Pointers and References +PointerAlignment: Left +ReferenceAlignment: Left + +# Includes +SortIncludes: true + +# Alignment +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true + +# Control Statements +BreakBeforeBraces: Attach +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty + +# Spaces +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceAfterTemplateKeyword: true diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..1af2c5a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,23 @@ +Checks: > + -*, + clang-analyzer-*, + performance-*, + bugprone-*, + readability-*, + misc-*, + modernize-*, + -modernize-use-trailing-return-type, + -modernize-use-nodiscard, + -readability-identifier-length + +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +FormatStyle: 'file' +CheckOptions: + readability-identifier-naming.ClassCase: PascalCase + readability-identifier-naming.StructCase: PascalCase + readability-identifier-naming.VariableCase: lower_case + readability-identifier-naming.FunctionCase: lower_case + readability-identifier-naming.GlobalVariableCase: lower_case + readability-identifier-naming.GlobalVariablePrefix: g_ + readability-identifier-naming.PrivateMemberSuffix: _ diff --git a/.gitignore b/.gitignore index be5dbd4..d0a2443 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ build/ -na/build/ +lume-legacy/build/ *.o *.d *.elf diff --git a/Makefile b/Makefile index 0722f8c..88689f5 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,81 @@ # 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 -# Try to find a suitable RISC-V toolchain prefix +# ============================================================ +# Build Configuration +# ============================================================ +BUILD := build + +# Make sure `make` with no arguments builds the kernel image, not the +# first rule encountered during makefile parsing. scripts/Kconfig.mk +# (included next) defines `defconfig:` at the top of its rule list, +# which would otherwise become the default goal and silently overwrite +# .config from arch/$(ARCH)/defconfig. +.DEFAULT_GOAL := all + +# Load Kbuild helpers (Q, $(call cmd,...), quiet_cmd_* / cmd_* templates, +# kbuild-objs translator). Must come before Kconfig.mk so subsequent +# rules can use $(call cmd,...) immediately. +include scripts/Kbuild.include + +# Load Kconfig infrastructure (reads .config, sets KCPPFLAGS) +include scripts/Kconfig.mk + +ARCH ?= $(CONFIG_ARCH) + +# Two boot products — same pair as Linux RISC-V's arch/riscv/boot/Makefile +# (vmlinux + Image), but named LumeOS-style: +# VMLUME -> build/vmlume.elf : ELF with .symtab/PT_DYNAMIC/.rela.dyn +# IMAGE -> build/vmlume.bin : raw binary (objcopy -O binary) +VMLUME := $(BUILD)/vmlume.elf +IMAGE := $(BUILD)/vmlume.bin + +# Initramfs paths +INITRAMFS_DIR := $(BUILD)/initramfs +INITRAMFS_CPIO := $(BUILD)/initramfs.cpio +INITRAMFS_OBJ := $(BUILD)/initramfs.o +USR_INIT_ELF := $(BUILD)/usr/init +# Relocations: linker emits .rela.dyn under -shared -Bsymbolic; boot +# code in arch/riscv/mm/init.cc::relocate_kernel walks it at startup. +LDS_SRC := arch/$(ARCH)/kernel/lumeos.lds.S +LINKER_SCRIPT := $(BUILD)/lumeos.lds +PYTHON ?= python3 + +# ============================================================ +# Toolchain Detection +# ============================================================ ifndef TOOLCHAIN_PREFIX +ifeq ($(ARCH),riscv) + # Prefer the linux-gnu toolchain — it supports -shared / -pie which + # are required to emit .rela.dyn (Linux RISC-V kernel uses the same). + # The bare-metal `riscv64-unknown-elf-` is kept only as a last-resort + # fallback; with that one, `-shared` fails ("not supported") and the + # kernel can't relocate at runtime. TOOLCHAIN_PREFIX := $(shell \ - if command -v riscv64-linux-gnu-gcc >/dev/null 2>&1; then \ - echo riscv64-linux-gnu-; \ - elif command -v riscv64-unknown-gnu-gcc >/dev/null 2>&1; then \ - echo riscv64-unknown-gnu-; \ - elif command -v riscv64-unknown-linux-gnu-gcc >/dev/null 2>&1; then \ + if command -v riscv64-unknown-linux-gnu-g++ >/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 \ + elif command -v riscv64-linux-gnu-g++ >/dev/null 2>&1; then \ echo riscv64-linux-gnu-; \ + else \ + echo riscv64-unknown-linux-gnu-; \ fi) +else ifeq ($(ARCH),aarch64) + TOOLCHAIN_PREFIX := aarch64-linux-gnu- +else ifeq ($(ARCH),x86_64) + TOOLCHAIN_PREFIX := x86_64-linux-gnu- +endif endif CC := $(TOOLCHAIN_PREFIX)gcc @@ -22,120 +83,337 @@ CXX := $(TOOLCHAIN_PREFIX)g++ LD := $(TOOLCHAIN_PREFIX)ld OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy OBJDUMP := $(TOOLCHAIN_PREFIX)objdump +# Objcopy output formats for binary-to-ELF relocation blob +OBJCOPY_ELF_riscv := elf64-littleriscv +OBJCOPY_ELF_aarch64 := elf64-littleaarch64 +OBJCOPY_ELF_x86_64 := elf64-x86-64 +OBJCOPY_ELF := $(OBJCOPY_ELF_$(ARCH)) -BUILD_DIR := build -SRC_DIR := src +OBJCOPY_BINARCH_riscv := riscv +OBJCOPY_BINARCH_aarch64 := aarch64 +OBJCOPY_BINARCH_x86_64 := i386:x86-64 +OBJCOPY_BINARCH := $(OBJCOPY_BINARCH_$(ARCH)) -CFLAGS := -Wall -Wextra -O2 -g -ffreestanding -nostdlib -mcmodel=medany -Iinclude -CXXFLAGS := $(CFLAGS) -fno-exceptions -fno-rtti -fno-use-cxa-atexit -LINKER_SCRIPT := linker.ld +# Common flags (base, before module includes) +WARNINGS := -Wall -Wextra -Wno-unused-parameter +INCLUDES := -Iinclude -I. -USER_LD := $(SRC_DIR)/user/user.ld -ULIB_OBJS := $(BUILD_DIR)/user/entry.o \ - $(BUILD_DIR)/user/usys.o \ - $(BUILD_DIR)/ulib/stdio.o \ - $(BUILD_DIR)/ulib/stdlib.o \ - $(BUILD_DIR)/ulib/string.o \ - $(BUILD_DIR)/ulib/cxx.o \ - $(BUILD_DIR)/ulib/iostream.o \ - $(BUILD_DIR)/ulib/syscalls.o +# ============================================================ +# Source Collection (Non-Recursive Module Fragments) +# Module .mk files may append to SRCS_CC, SRCS_S, INCLUDES +# ============================================================ +SRCS_CC := +SRCS_C := +SRCS_S := +SRCS_s := -INITCODE_BIN := $(BUILD_DIR)/user/initcode +-include arch/$(ARCH)/module.mk +include kernel/module.mk +include mm/module.mk +include fs/module.mk +include drivers/base/module.mk +include drivers/module.mk +include drivers/tty/module.mk +include fdt/module.mk +include lib/module.mk +include test/module.mk +# Compiler flags (defined AFTER includes so INCLUDES is complete) +CFLAGS := $(WARNINGS) -O2 -g \ + -ffreestanding -nostdlib \ + -mcmodel=medany \ + -fPIE \ + -MMD -MP \ + $(KCPPFLAGS) \ + $(INCLUDES) \ + -I$(BUILD)/generated -USER_SRCS := $(wildcard $(SRC_DIR)/user/*.cc) -UPROGS := $(patsubst $(SRC_DIR)/user/%.cc, $(BUILD_DIR)/user/%, $(USER_SRCS)) +# -fPIE makes the compiler emit position-independent code (PC-relative +# addressing + GOT for globals), which is what `-shared` linking expects. +# Mirrors Linux's arch/riscv/Makefile under CONFIG_RELOCATABLE=y: +# KBUILD_CFLAGS += -fPIE -SRCS_S := $(shell find $(SRC_DIR) -name "*.S" -not -path "$(SRC_DIR)/user/*" -not -path "$(SRC_DIR)/ulib/*") -SRCS_CXX := $(shell find $(SRC_DIR) -name "*.cc" -not -path "$(SRC_DIR)/user/*" -not -path "$(SRC_DIR)/ulib/*") -OBJS := $(SRCS_S:%.S=$(BUILD_DIR)/%.o) $(SRCS_CXX:%.cc=$(BUILD_DIR)/%.o) +# C flags for musl libc sources: add libc-shim headers first +CFLAGS_LIBC := $(CFLAGS) -Iinclude/libc-shim \ + -Wno-parentheses -Wno-sign-compare -TARGET := $(BUILD_DIR)/kernel.elf +CXXFLAGS := $(CFLAGS) \ + -std=c++17 \ + -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ + -fno-threadsafe-statics -QEMUOPTS = -drive file=fs.img,if=none,format=raw,id=x0 -QEMUOPTS += -device virtio-blk-device,drive=x0 -global virtio-mmio.force-legacy=false +# Assembler flags MUST NOT include -fPIE. +# Linux's arch/riscv/Makefile adds -fPIE only to KBUILD_CFLAGS, NOT to +# KBUILD_AFLAGS: with -fPIE the gcc driver puts the assembler in PIC +# mode, where the `la` macro silently expands to a GOT-indirect load +# (`auipc + ld` reading a slot that holds the link-time VA). In our +# pre-MMU boot path that VA is unmapped, so any `la sym, sym` faults +# and reboots into a `_start` loop. Stripping -fPIE makes `la` emit +# direct PC-relative addressing (`auipc + addi`) which works at PA. +ASFLAGS := $(filter-out -fPIE,$(CFLAGS)) -.PHONY: all clean run debug +# Link as a position-independent shared object so the linker emits +# `.rela.dyn` with R_RISCV_RELATIVE entries that the boot stub applies +# at load time. Mirrors Linux's arch/riscv/Makefile §10: +# LDFLAGS_vmlinux += -shared -Bsymbolic -z notext +# `-Bsymbolic` resolves all global refs at link time (no dynamic +# resolution); `-z notext` lets us keep relocations in code pages. +LDFLAGS := -nostdlib -shared -Bsymbolic -z notext -z norelro --no-dynamic-linker -all: $(TARGET) $(INITCODE_BIN) $(UPROGS) fs.img +# ============================================================ +# Object File Computation +# ============================================================ +OBJS := $(patsubst %.cc,$(BUILD)/%.o,$(SRCS_CC)) \ + $(patsubst %.c,$(BUILD)/%.o,$(SRCS_C)) \ + $(patsubst %.S,$(BUILD)/%.o,$(SRCS_S)) \ + $(patsubst %.s,$(BUILD)/%.o,$(SRCS_s)) +DEPS := $(OBJS:.o=.d) -$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.c - @mkdir -p $(dir $@) - @echo " ULIB_C $<" - @$(CC) $(CFLAGS) -c $< -o $@ +# ============================================================ +# Build Rules +# ============================================================ +.PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig -$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.cc - @mkdir -p $(dir $@) - @echo " ULIB_CC $<" - @$(CXX) $(CXXFLAGS) -c $< -o $@ +all: $(VMLUME) $(IMAGE) + +# Generate autoconf.h before any compilation +$(AUTOCONF_H): scripts/Kconfig.mk $(wildcard .config) + $(generate-autoconf) -$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.cpp +# ============================================================ +# Generated header: asm-offsets.h +# +# Linux-style trick (mirrors include/linux/kbuild.h + arch/*/kernel/ +# asm-offsets.c + scripts/Makefile.build): compile asm-offsets.c to +# assembly, scrape the magic `->SYM val` markers produced by the +# DEFINE/OFFSET inline-asm macros, rewrite them into `#define` +# directives via sed. head.S and friends then `#include +# ` to access struct field offsets without +# having to hand-maintain them — re-order a struct field, the +# generated constant follows. +# ============================================================ +ASM_OFFSETS_C := arch/$(ARCH)/kernel/asm-offsets.c +ASM_OFFSETS_S := $(BUILD)/arch/$(ARCH)/kernel/asm-offsets.s +ASM_OFFSETS_H := $(BUILD)/generated/asm-offsets.h +ASM_OFFSETS_SED := scripts/extract-asm-offsets.sed + +# Compile asm-offsets.c to *.s only — we never want the .o linked in. +$(ASM_OFFSETS_S): $(ASM_OFFSETS_C) $(AUTOCONF_H) + @echo " CC -S $<" @mkdir -p $(dir $@) - @echo " ULIB_CPP $<" - @$(CXX) $(CXXFLAGS) -c $< -o $@ + @$(CC) $(CFLAGS_LIBC) -S $< -o $@ -$(BUILD_DIR)/user/%.o: $(SRC_DIR)/user/%.cc +# Rewrite ->NAME markers into #define lines. Header guard hand-rolled +# at the top, sed body in the middle, closing #endif at the bottom. +$(ASM_OFFSETS_H): $(ASM_OFFSETS_S) $(ASM_OFFSETS_SED) + @echo " GEN $@" @mkdir -p $(dir $@) - @echo " USER_CC $<" - @$(CXX) $(CXXFLAGS) -c $< -o $@ + @(echo "/* AUTO-GENERATED — DO NOT EDIT */"; \ + echo "/* Generated from $(ASM_OFFSETS_C) by scripts/extract-asm-offsets.sed */"; \ + echo "#ifndef _LUME_GENERATED_ASM_OFFSETS_H"; \ + echo "#define _LUME_GENERATED_ASM_OFFSETS_H"; \ + echo ""; \ + sed -n -f $(ASM_OFFSETS_SED) $<; \ + echo ""; \ + echo "#endif /* _LUME_GENERATED_ASM_OFFSETS_H */") > $@ + +# All objects depend on autoconf.h and asm-offsets.h +$(OBJS): $(AUTOCONF_H) $(ASM_OFFSETS_H) -$(BUILD_DIR)/user/%.o: $(SRC_DIR)/user/%.S +# ============================================================ +# Generic %.pi.o post-processing rule +# +# Mirrors arch/riscv/kernel/pi/Makefile §26-30 in Linux: +# +# $(obj)/%.pi.o: OBJCOPYFLAGS := --prefix-symbols=__pi_ +# --remove-section=.note.gnu.property +# --prefix-alloc-sections=.init.pi +# $(obj)/%.pi.o: $(obj)/%.o FORCE +# $(call if_changed,objcopy) +# +# Purpose: take a regular .o produced by the normal compile rule and +# rewrite it so the early position-independent code can coexist with +# the rest of the kernel without symbol clashes: +# +# --prefix-symbols=__pi_ Every global symbol becomes __pi_. +# head.S calls them as __pi_fdt_path_offset +# etc., keeping the pre-MMU namespace +# cleanly isolated. +# +# --prefix-alloc-sections=.init.pi +# All SHF_ALLOC sections get re-prefixed +# so they land in .init.pi in vmlinux.lds.S, +# which positions them where the boot +# loader has already mapped memory. +# +# --remove-section=.note.gnu.property +# Discarded because post-rename it can +# confuse the linker about ELF features. +# +# Generic pattern (`$(BUILD)/%.pi.o`) so any subdir's source can opt in +# by listing a `.pi.o` target in its SRCS. The companion compile-side +# CFLAGS overrides (replacing -fPIE with -fpie, adding -Os, etc.) are +# expected to come from the subdir's own .mk fragment via target- +# specific CFLAGS_LIBC / CXXFLAGS overrides. +# ============================================================ +$(BUILD)/%.pi.o: OBJCOPYFLAGS := --prefix-symbols=__pi_ \ + --remove-section=.note.gnu.property \ + --prefix-alloc-sections=.init.pi + +$(BUILD)/%.pi.o: $(BUILD)/%.o + $(call cmd,objcopy) + +# Preprocess linker script template (.lds.S → .lds) +$(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h + @echo " LDS $@" @mkdir -p $(dir $@) - @echo " USER_AS $<" - @$(CC) $(CFLAGS) -c $< -o $@ + @$(CC) -E -P -x c $(INCLUDES) $< -o $@ -$(INITCODE_BIN): $(BUILD_DIR)/user/init.o $(ULIB_OBJS) $(USER_LD) - @echo " U_LD $@" - @$(LD) -T $(USER_LD) -o $@.out $(BUILD_DIR)/user/init.o $(ULIB_OBJS) - @$(OBJCOPY) -S -O binary $@.out $@ - @$(OBJDUMP) -S $@.out > $@.asm +# ============================================================ +# Initramfs: pack a root filesystem into a newc cpio. +# +# Pipeline mirrors Linux's Kbuild: +# usr/gen_init_cpio — host C tool, produces cpio bytes +# scripts/gen_initramfs.sh — wrapper that prepares the listing, +# handles directories vs. listing files, +# applies uid/gid remap, then invokes the +# host tool. +# Sourced from $(CONFIG_INITRAMFS_SOURCE) — see scripts/Kconfig.mk. +# ============================================================ +# Host compiler — distinct from $(CC), which may be the cross compiler. +HOSTCC ?= cc +HOSTCFLAGS ?= -O2 -Wall -$(BUILD_DIR)/user/%: $(BUILD_DIR)/user/%.o $(ULIB_OBJS) $(USER_LD) - @echo " USER_LD $@" - @$(LD) -T $(USER_LD) -o $@ $< $(ULIB_OBJS) - @$(OBJDUMP) -S $@ > $@.asm - @$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $@.sym +# Output path matches Linux convention (usr/gen_init_cpio) so the +# verbatim copy of scripts/gen_initramfs.sh from Linux finds it via its +# hardcoded relative path when invoked from the kernel source root. +GEN_INIT_CPIO := usr/gen_init_cpio +$(GEN_INIT_CPIO): usr/gen_init_cpio.c + @echo " HOSTCC $@" + @$(HOSTCC) $(HOSTCFLAGS) -o $@ $< -$(TARGET): $(OBJS) $(LINKER_SCRIPT) - @echo " K_LD $@" - @$(LD) -T $(LINKER_SCRIPT) -o $@ $(OBJS) - @echo " K_ASM $@.asm" - @$(OBJDUMP) -S $@ > $@.asm - @$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $@.sym +INITRAMFS_SOURCE := $(CONFIG_INITRAMFS_SOURCE) -$(BUILD_DIR)/src/kernel/initcode.o: $(SRC_DIR)/kernel/initcode.S $(INITCODE_BIN) - @mkdir -p $(dir $@) - @echo " AS_INC $<" - @$(CC) $(CFLAGS) -c $< -o $@ +# Record the chosen source so changing CONFIG_INITRAMFS_SOURCE on the +# command line forces a rebuild — without this, `make` only compares +# file mtimes and would happily reuse a cpio packed from a different +# source as long as that other source isn't newer. Evaluated at +# Makefile parse time so the dep edge is correct from the first rule +# check. Mirrors the idea behind Linux's $(call if_changed,...). +INITRAMFS_SRC_STAMP := $(BUILD)/.initramfs_src.stamp +$(shell mkdir -p $(BUILD) 2>/dev/null; \ + prev=$$([ -f "$(INITRAMFS_SRC_STAMP)" ] && cat "$(INITRAMFS_SRC_STAMP)" || echo); \ + if [ "$$prev" != "$(INITRAMFS_SOURCE)" ]; then \ + printf '%s\n' "$(INITRAMFS_SOURCE)" > "$(INITRAMFS_SRC_STAMP)"; \ + fi) -$(BUILD_DIR)/%.o: %.S +$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ + $(GEN_INIT_CPIO) scripts/gen_initramfs.sh + @echo " INITRAMFS $(INITRAMFS_SOURCE)" @mkdir -p $(dir $@) - @echo " K_AS $<" - @$(CC) $(CFLAGS) -c $< -o $@ + @./scripts/gen_initramfs.sh -o $@ \ + -u $(CONFIG_INITRAMFS_ROOT_UID) \ + -g $(CONFIG_INITRAMFS_ROOT_GID) \ + $(INITRAMFS_SOURCE) -$(BUILD_DIR)/%.o: %.cc +$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) + @echo " OBJCOPY $@ (initramfs)" + @$(OBJCOPY) -I binary -O $(OBJCOPY_ELF) -B $(OBJCOPY_BINARCH) \ + --rename-section .data=.init.ramfs,alloc,load,readonly,data,contents \ + $< $@ + +# Single-pass kernel link. With `-shared -Bsymbolic`, the linker +# emits `.rela.dyn` containing R_RISCV_RELATIVE entries directly into +# the output ELF — no separate gen_reloc pass. +$(VMLUME): $(OBJS) $(INITRAMFS_OBJ) $(LINKER_SCRIPT) + @echo " LD $@" @mkdir -p $(dir $@) - @echo " K_CXX $<" - @$(CXX) $(CXXFLAGS) -c $< -o $@ + @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) $(INITRAMFS_OBJ) + @$(TOOLCHAIN_PREFIX)size $@ + +# Image = raw binary, mirrors Linux's `arch/riscv/boot/Image` (objcopy +# vmlinux to raw bytes, no ELF header — what U-Boot or QEMU's `-bios` +# bare-loading would consume). Linux always builds both vmlinux and +# Image; LumeOS follows the same convention. +$(IMAGE): $(VMLUME) + @echo " OBJCOPY $@" + @$(OBJCOPY) -O binary $< $@ + +# Compile C++ sources. Recipe goes through $(call cmd,...) so that +# `make V=1` echoes the full command line. $(Q) prefix on mkdir keeps +# the directory-creation step silent in quiet mode but visible at V=1. +$(BUILD)/%.o: %.cc + $(Q)mkdir -p $(dir $@) + $(call cmd,cxx_o_cc) + +$(BUILD)/%.o: %.c + $(Q)mkdir -p $(dir $@) + $(call cmd,cc_o_c) -fs.img: $(UPROGS) - @echo " IMG Generating fs.img (FAT32)" - @dd if=/dev/zero of=fs.img bs=1M count=32 2>/dev/null - @mkfs.vfat -F 32 fs.img >/dev/null - $(foreach prog,$(UPROGS),mcopy -i fs.img $(prog) ::/$(notdir $(prog));) +$(BUILD)/%.o: %.S + $(Q)mkdir -p $(dir $@) + $(call cmd,as_o_S) -run: $(TARGET) fs.img +$(BUILD)/%.o: %.s + $(Q)mkdir -p $(dir $@) + $(call cmd,as_o_S) + +# Auto-dependency tracking +-include $(DEPS) + +# ============================================================ +# Run & Debug +# ============================================================ +ifeq ($(ARCH),riscv) +QEMU := qemu-system-riscv64 +QEMU_MACHINE := -machine virt -smp 4 -m 128M -nographic +QEMU_BIOS := -bios default +else ifeq ($(ARCH),aarch64) +QEMU := qemu-system-aarch64 +QEMU_MACHINE := -machine virt -cpu cortex-a53 -m 128M -nographic +QEMU_BIOS := +else ifeq ($(ARCH),x86_64) +QEMU := qemu-system-x86_64 +QEMU_MACHINE := -m 128M -nographic +QEMU_BIOS := +endif + +run: $(VMLUME) $(IMAGE) @echo " QEMU Running..." - @qemu-system-riscv64 -machine virt -nographic \ - -kernel $(TARGET) -m 128M $(QEMUOPTS) + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -debug: $(TARGET) fs.img - @echo " QEMU (DEBUG) Waiting for GDB..." - @qemu-system-riscv64 -machine virt -nographic \ - -kernel $(TARGET) -m 128M -s -S $(QEMUOPTS) +debug: $(VMLUME) $(IMAGE) + @echo " QEMU Waiting for GDB on :1234..." + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -s -S +# ============================================================ +# Utilities +# ============================================================ clean: - rm -rf $(BUILD_DIR) fs.img \ No newline at end of file + rm -rf $(BUILD) + +disasm: $(VMLUME) + @echo " OBJDUMP $<" + @$(OBJDUMP) -d -S $(VMLUME) > $(VMLUME).asm + @echo " Written to $(VMLUME).asm" + +fmt: + @echo " FMT Formatting source files..." + @find . -name '*.cc' -o -name '*.h' | xargs clang-format -i 2>/dev/null || true + +lint: + @echo " LINT Generating compile_commands.json via bear..." + @bear -- $(MAKE) all + @echo " LINT Running clang-tidy on C++ sources..." + @clang-tidy -p . $(SRCS_CC) || 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)" + @echo "KCPPFLAGS = $(KCPPFLAGS)" diff --git a/README.md b/README.md index ae2f052..1e0e20a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Lume OS is a 64-bit monolithic operating system kernel written in C++ for the RI ## Note After studying for a period of time, I have roughly understood the design of some modern operating systems such as Windows, Linux, and BSD. I decided to refactor this operating system to make its architecture clear, scalable, and portable. My goal is to make this system compatible with the Linux ABI, so that musl libc and busybox can be easily ported, thereby introducing the ability to depend on more Linux software. Although this goal is very distant, I still decided to try. I have chosen to use C++ as the development language, even though many people think C++ is not a language for developing kernels, I believe that the object-oriented features of C++ can make system code easier to read and understand. Compared to reading various complex macros, reading some object-oriented code might be easier for people unfamiliar with the kernel. Therefore, I decided to sacrifice some performance along with extra handling for the C++ runtime in order to achieve this; after all, you have to give up something to gain something. -The source code of the new system is placed in the `na` (New Architecture) directory, welcome to read it. After learning about the development history of Windows NT, I realized the importance of documentation. The NT team spent a lot of time writing documentation before writing the first line of code, and by the time they wrote code, the system was basically finalized. As a student who is not particularly outstanding, I cannot complete the design of the system before starting to write code, but I can decide on its design before starting each part, and then adjust it according to reality. The documents are in `docs/specs/` directory. +The actively developed kernel now lives at the repository root. The previous implementation has been archived in `lume-legacy/` for reference. After learning about the development history of Windows NT, I realized the importance of documentation. The NT team spent a lot of time writing documentation before writing the first line of code, and by the time they wrote code, the system was basically finalized. As a student who is not particularly outstanding, I cannot complete the design of the system before starting to write code, but I can decide on its design before starting each part, and then adjust it according to reality. The documents are in `docs/specs/` directory. It can basically be concluded that writing an operating system in this era is a thankless task. A personally implemented operating system may never run on machines beyond my own tests, so what is the point of doing this? Perhaps it is just to add color to a monotonous life. However, if you really have ideas, you are welcome to contact me. @@ -102,4 +102,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -See the [LICENSE](LICENSE) file for details. \ No newline at end of file +See the [LICENSE](LICENSE) file for details. diff --git a/arch/riscv/config.cc b/arch/riscv/config.cc new file mode 100644 index 0000000..a18a825 --- /dev/null +++ b/arch/riscv/config.cc @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * RISC-V Architecture Configuration — runtime state and KASLR + * + * Provides the runtime (post-KASLR) values of the higher-half + * virtual address bases. kaslr_init() is called once during + * early boot, before vmm_init(), to randomise the layout and + * fill the unified KaslrLayout structure. + * + * KASLR slide budget: + * The default Direct Map starts at 0xFFFFFFC0_0000_0000 (VPN[2]=256). + * The default Vmemmap starts at 0xFFFFFFD0_0000_0000 (VPN[2]=320). + * A 1GB superpage occupies one VPN[2] slot. We allow a slide of + * 0..63 slots (0..63 GB) upward, which keeps both regions inside + * the upper canonical half and avoids colliding with the kernel + * text region (linked at VPN[2] = 258 + 2 = ~0xFFFFFFC0_8020_0000). + * + * In practice, with 128MB RAM the kernel+direct map only touches + * VPN[2] 258-259, so a slide of up to 63GB is safe. + */ + +#include +#include +#include +#include +#include +#include +#include + +extern "C" char _stext[], _kernel_end[]; + +namespace arch { + +/* Global KASLR Layout Structure*/ +KaslrLayout g_kaslr_layout = {}; + +/* Runtime bases — updated by kaslr_layout_compute() */ +uint64 g_direct_map_base = kDirectMapBaseDefault; +uint64 g_vmemmap_base = kVmemmapBaseDefault; + +/* VA Range for validation */ +struct VaRange { + const char* name; + uint64 start; + uint64 end; // exclusive +}; + +/* ================================================================ + * Utilities + * ================================================================ */ +static inline uint64 align_down_page(uint64 v) +{ + return v & ~(kPageSize - 1); +} + +static inline uint64 align_up_page(uint64 v) +{ + return (v + kPageSize - 1) & ~(kPageSize - 1); +} + +static uint64 checked_add_u64(uint64 a, uint64 b, const char* detail) +{ + if (a > (~0ULL - b)) { + kernel_panic("kaslr: address overflow", detail); + } + return a + b; +} + +/* Splitmix64: non-plaintext hash derived from seed (used for seed_hash) */ +static uint64 splitmix64(uint64 x) +{ + x += 0x9e3779b97f4a7c15ULL; + x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; + x = x ^ (x >> 27); + return x * 0x94d049bb133111ebULL; +} + +/* SV39 canonical VA check: bits [63:39] must equal sign bit 38. */ +static bool is_sv39_canonical(uint64 va) +{ + uint64 sign = (va >> 38) & 1ULL; + uint64 upper = va >> 39; + return sign ? (upper == ((1ULL << 25) - 1ULL)) : (upper == 0); +} + +static bool is_sv39_kernel_half(uint64 va) +{ + return is_sv39_canonical(va) && (((va >> 38) & 1ULL) == 1ULL); +} + +static bool ranges_overlap(const VaRange& a, const VaRange& b) +{ + return (a.start < b.end) && (b.start < a.end); +} + +/* ================================================================ + * KASLR Computation (Phase A: Centralized in single function) + * + * This function: + * 1. Generates or derives random seed + * 2. Selects 1GB-aligned slot [0..63] + * 3. Computes seed_hash via splitmix64 + * 4. Fills KaslrLayout structure completely + * 5. Validates ranges (canonical, overlaps) + * 6. Logs according to CONFIG_KASLR_LOG_LEVEL + * ================================================================ */ +static void kaslr_layout_compute() +{ +#ifdef CONFIG_KASLR + constexpr uint64 k1GB = 1ULL << 30; + constexpr uint64 kMaxSlots = 64; + + /* 1. Get seed and source */ + uint64 seed = random_early_seed_value(); + RandomSeedSource rsrc = random_early_seed_source(); + KaslrSeedSource source; + switch (rsrc) { + case RandomSeedSource::FdtProvided: + source = KaslrSeedSource::kFdt; + break; + case RandomSeedSource::HwRandom: + source = KaslrSeedSource::kHardware; + break; + case RandomSeedSource::CycleFallback: + case RandomSeedSource::Unknown: + default: + source = KaslrSeedSource::kCycle; + break; + } + + /* 2. Select random slot and compute slide */ + uint64 raw = get_random_u64(); + uint64 slot = raw % kMaxSlots; + if (slot == 0) { + slot = 1; /* slot=0 causes direct_map to overlap with kernel image VA */ + } + uint64 slide = slot * k1GB; + + /* 3. Compute non-plaintext hash */ + uint64 seed_hash = splitmix64(seed ^ 0xdeadbeefdeadbeefULL); + + /* 4. Compute randomised bases */ + uint64 direct_map_base = kDirectMapBaseDefault + slide; + uint64 vmemmap_base = kVmemmapBaseDefault + slide; + + /* 5. Fill KaslrLayout */ + g_kaslr_layout.seed = seed; + g_kaslr_layout.seed_hash = seed_hash; + g_kaslr_layout.source = source; + g_kaslr_layout.slot = slot; + g_kaslr_layout.slide_bytes = slide; + g_kaslr_layout.policy_id = kKaslrPolicyId; + g_kaslr_layout.direct_map_base = direct_map_base; + g_kaslr_layout.vmemmap_base = vmemmap_base; + g_kaslr_layout.canonical_ok = false; + g_kaslr_layout.overlap_ok = false; + + /* Update global runtime bases */ + g_direct_map_base = direct_map_base; + g_vmemmap_base = vmemmap_base; + + /* 6. Self-check: canonical VA validation and overlap detection */ + uint64 mem_base = 0, mem_size = 0; + fdt_early_get_mem_info(&mem_base, &mem_size); + if (mem_size == 0) { + kernel_panic("kaslr_layout_compute: invalid memory from FDT"); + } + + /* Construct VA ranges */ + uint64 num_pages = mem_size / kPageSize; + uint64 page_bytes = num_pages * sizeof(Page); + uint64 page_span = align_up_page(page_bytes); + + VaRange direct_map { + "direct-map", + checked_add_u64(g_direct_map_base, mem_base, "direct_map start"), + checked_add_u64(checked_add_u64(g_direct_map_base, mem_base, + "direct_map start"), + mem_size, "direct_map end"), + }; + + VaRange vmemmap { + "vmemmap", + g_vmemmap_base, + checked_add_u64(g_vmemmap_base, page_span, "vmemmap end"), + }; + + VaRange kernel_img { + "kernel-image", + align_down_page(reinterpret_cast(_stext)), + align_up_page(reinterpret_cast(_kernel_end)), + }; + + /* Validate each range is canonical */ + auto check_kernel_half = [](const VaRange& r) { + if (r.start >= r.end) { + kernel_panic("kaslr: invalid VA range", r.name); + } + if (!is_sv39_kernel_half(r.start) || !is_sv39_kernel_half(r.end - 1)) { + kernel_panic("kaslr: non-canonical kernel VA range", r.name); + } + }; + + check_kernel_half(direct_map); + check_kernel_half(vmemmap); + check_kernel_half(kernel_img); + + /* Validate no overlaps */ + if (ranges_overlap(direct_map, kernel_img)) { + kernel_panic("kaslr: direct-map overlaps kernel image"); + } + if (ranges_overlap(vmemmap, kernel_img)) { + kernel_panic("kaslr: vmemmap overlaps kernel image"); + } + if (ranges_overlap(direct_map, vmemmap)) { + kernel_panic("kaslr: direct-map overlaps vmemmap"); + } + + /* Mark validation success */ + g_kaslr_layout.canonical_ok = true; + g_kaslr_layout.overlap_ok = true; + + /* 7. Logging according to CONFIG_KASLR_LOG_LEVEL */ + if (kKaslrLogLevel >= 1) { + /* Minimal logging: just the seed and slot (release mode) */ + kprintf("[boot][kaslr] policy_id=%u source=%s slot=%llu\n", + g_kaslr_layout.policy_id, + random_seed_source_name(rsrc), + slot); + } + + if (kKaslrLogLevel >= 2) { + /* Debug logging: full ranges and hashes */ + kprintf("[boot][kaslr] seed=0x%llx seed_hash=0x%llx\n", + seed, seed_hash); + kprintf("[boot][kaslr] direct-map=[0x%llx,0x%llx)\n", + direct_map.start, direct_map.end); + kprintf("[boot][kaslr] vmemmap=[0x%llx,0x%llx)\n", + vmemmap.start, vmemmap.end); + kprintf("[boot][kaslr] kernel-image=[0x%llx,0x%llx)\n", + kernel_img.start, kernel_img.end); + } + +#else /* CONFIG_KASLR disabled */ + g_kaslr_layout.seed = 0; + g_kaslr_layout.seed_hash = 0; + g_kaslr_layout.source = KaslrSeedSource::kCycle; + g_kaslr_layout.slot = 0; + g_kaslr_layout.slide_bytes = 0; + g_kaslr_layout.policy_id = kKaslrPolicyId; + g_kaslr_layout.direct_map_base = kDirectMapBaseDefault; + g_kaslr_layout.vmemmap_base = kVmemmapBaseDefault; + g_kaslr_layout.canonical_ok = true; + g_kaslr_layout.overlap_ok = true; + + g_direct_map_base = kDirectMapBaseDefault; + g_vmemmap_base = kVmemmapBaseDefault; + + if (kKaslrLogLevel >= 1) { + kprintf("[boot][kaslr] disabled policy_id=%u\n", + g_kaslr_layout.policy_id); + } +#endif +} + +/* ================================================================ + * Public entry point + * ================================================================ */ +void kaslr_init() +{ + kaslr_layout_compute(); +} + +} // namespace arch diff --git a/arch/riscv/defconfig b/arch/riscv/defconfig new file mode 100644 index 0000000..87d3ffd --- /dev/null +++ b/arch/riscv/defconfig @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# LumeOS Default Kernel Configuration — RISC-V (virt) +# +# This is the "golden" default configuration for the RISC-V virt platform. +# To customise, copy this file to the source root as '.config': +# +# cp arch/riscv/defconfig .config +# $EDITOR .config +# make +# +# Any variable not set here inherits its default from scripts/Kconfig.mk. +# ====================================================================== + +# --- Platform ----------------------------------------------------------- +CONFIG_ARCH = riscv + +# --- CPU / SMP ---------------------------------------------------------- +CONFIG_NR_CPUS = 8 + +# --- Scheduler ---------------------------------------------------------- +CONFIG_HZ = 100 +CONFIG_NR_PRIORITIES = 32 + +# --- Memory Management -------------------------------------------------- +CONFIG_PAGE_SIZE = 4096 +CONFIG_MAX_ORDER = 11 +CONFIG_KERNEL_STACK_SIZE = 8192 + +# --- Security / Hardening ----------------------------------------------- +CONFIG_KASLR = y +CONFIG_STACK_CANARY = y + +# --- Debug --------------------------------------------------------------- +CONFIG_DEBUG = y +CONFIG_DEBUG_LOCKS = y +CONFIG_SELFTEST = y +CONFIG_KLOG = y diff --git a/arch/riscv/include/arch/config.h b/arch/riscv/include/arch/config.h new file mode 100644 index 0000000..8ae0502 --- /dev/null +++ b/arch/riscv/include/arch/config.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * RISC-V Architecture Configuration + * + * These constants define the architecture-specific memory layout. + * Each arch/ implementation must provide this header with the same symbols. + * + * KASLR: At boot, kaslr_init() randomises g_direct_map_base and + * g_vmemmap_base. All MI code must read these runtime variables + * (via pa_to_va / vmm helpers) rather than the static defaults. + */ + +#include + +namespace arch { + +/* ================================================================ + * Default (compile-time) virtual addresses + * + * SV39 gives a 512GB virtual address space. + * Higher-half layout (defaults before KASLR): + * + * 0xFFFFFFC0_00000000 (256) Direct map (PA+offset) + * 0xFFFFFFD0_00000000 (320) Vmemmap (Page array) + * + * The 64GB gap between them is the KASLR slide range. + * ================================================================ */ +inline constexpr uint64 kDirectMapBaseDefault = 0xFFFFFFC000000000ULL; +inline constexpr uint64 kVmemmapBaseDefault = 0xFFFFFFD000000000ULL; + +/* Physical memory base (where RAM starts on RISC-V virt platform) */ +inline constexpr uint64 kPhysBase = 0x80000000ULL; + +/* Kernel physical load address (= PHYS_BASE in linker script). + * Everything in [kPhysBase, kPhysStart) is firmware territory (PMP-protected). + * Analogous to Linux's CONFIG_PHYSICAL_START. */ +inline constexpr uint64 kPhysStart = 0x80200000ULL; + +/* ================================================================ + * Runtime (post-KASLR) virtual addresses + * + * These start equal to the defaults above. If CONFIG_KASLR is + * enabled, kaslr_init() adds a random, 1GB-aligned offset + * to each, sliding both regions upward in VA space. + * ================================================================ */ +extern uint64 g_direct_map_base; +extern uint64 g_vmemmap_base; + +/* ================================================================ + * KASLR Layout Structure (Phase A: Unified KASLR State) + * + * This struct holds all KASLR results: the random seed, derived + * hash, selected slot, and the randomised virtual address bases. + * It serves as the single source of truth for all KASLR decisions + * and is filled once during boot and read-only thereafter. + * ================================================================ */ + +enum class KaslrSeedSource : uint8 { + kFdt = 0, /* Loaded from FDT /random node */ + kHardware = 1, /* From hardware RNG (e.g., RDRAND) */ + kCycle = 2, /* Fallback: CPU cycle counter */ +}; + +struct KaslrLayout { + /* Seed and provenance */ + uint64 seed; /* 64-bit random seed value */ + uint64 seed_hash; /* Non-plaintext derived hash (splitmix64) */ + KaslrSeedSource source; /* Seed provenance (fdt/hw/cycle) */ + + /* Randomisation parameters */ + uint64 slot; /* Slot index: [0, 63] */ + uint64 slide_bytes; /* Byte offset: slot * 1GB */ + + /* Build-time policy */ + uint32 policy_id; /* CONFIG_KASLR_POLICY_ID at build time */ + + /* Randomised base addresses (post-KASLR) */ + uint64 direct_map_base; /* Direct map VA (kDirectMapBaseDefault + slide) */ + uint64 vmemmap_base; /* Vmemmap VA (kVmemmapBaseDefault + slide) */ + + /* Validation flags (for debug logging) */ + bool canonical_ok; /* All VA ranges passed canonical check */ + bool overlap_ok; /* No overlaps detected between regions */ +}; + +/* ================================================================ + * KASLR entry point + * + * Must be called AFTER random_early_init() and BEFORE vmm_init(). + * Fills g_kaslr_layout with randomised bases and self-check results. + * When CONFIG_KASLR is disabled this is a no-op. + * ================================================================ */ +extern KaslrLayout g_kaslr_layout; + +void kaslr_init(); + +/* Helper to query the KASLR layout safely */ +static inline const KaslrLayout& kaslr_layout_get() { + return g_kaslr_layout; +} + +} // namespace arch diff --git a/arch/riscv/include/arch/context.h b/arch/riscv/include/arch/context.h new file mode 100644 index 0000000..3d935fa --- /dev/null +++ b/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/arch/riscv/include/arch/cpu.h b/arch/riscv/include/arch/cpu.h new file mode 100644 index 0000000..8c573ec --- /dev/null +++ b/arch/riscv/include/arch/cpu.h @@ -0,0 +1,95 @@ +/* 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) */ + +/* RISC-V sie register bit definitions */ +#define SIE_SSIE (1UL << 1) /* Supervisor Software Interrupt Enable */ +#define SIE_STIE (1UL << 5) /* Supervisor Timer Interrupt Enable */ +#define SIE_SEIE (1UL << 9) /* Supervisor External Interrupt Enable */ + +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; +} + +// Enable timer interrupt in sie (sie.STIE = 1) +static inline void timer_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_STIE)); +} + +// Disable timer interrupt in sie (sie.STIE = 0) +static inline void timer_intr_off() { + __asm__ volatile("csrc sie, %0" :: "r"(SIE_STIE)); +} + +// Enable software interrupt in sie (sie.SSIE = 1) — needed for IPI +static inline void software_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_SSIE)); +} + +// Enable external interrupt in sie (sie.SEIE = 1) — needed for PLIC (UART, etc.) +static inline void external_intr_on() { + __asm__ volatile("csrs sie, %0" :: "r"(SIE_SEIE)); +} + +// Clear S-mode software interrupt pending bit (sip.SSIP = 0) +// Must be called in the IPI handler to acknowledge the interrupt. +static inline void clear_sip_ssip() { + __asm__ volatile("csrc sip, %0" :: "r"(1UL << 1)); +} + +} // namespace arch::cpu diff --git a/arch/riscv/include/arch/extable.h b/arch/riscv/include/arch/extable.h new file mode 100644 index 0000000..eb2daac --- /dev/null +++ b/arch/riscv/include/arch/extable.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#ifndef __ASSEMBLER__ + +#include + +/* + * Exception table entry. + * insn: offset from this struct to the faulting instruction. + * fixup: offset from this struct to the fixup code. + * + * Using relative offsets avoids absolute relocations and saves space. + */ +struct ExceptionTableEntry { + int32 insn; + int32 fixup; +}; + +/* Search the exception table for a given PC. Returns fixup address, or 0 if not found. */ +uint64 search_extable(uint64 pc); + +/* Sorts the exception table at boot for binary search O(log N). */ +void extable_init(); + +#endif /* __ASSEMBLER__ */ + +/* + * EXTABLE macro for assembly usage. + * Appends an entry to the .extable section. + */ +#ifdef __ASSEMBLER__ + +#define EXTABLE(insn, fixup) \ + .pushsection .extable, "a"; \ + .balign 4; \ + .long insn - .; \ + .long fixup - .; \ + .popsection + +#else /* !__ASSEMBLER__ */ + +/* Stringified macro for inline assembly */ +#define _ASM_EXTABLE(insn, fixup) \ + ".pushsection .extable, \"a\"\n" \ + ".balign 4\n" \ + ".long " #insn " - .\n" \ + ".long " #fixup " - .\n" \ + ".popsection\n" + +#define EXTABLE(insn, fixup) _ASM_EXTABLE(insn, fixup) + +#endif /* __ASSEMBLER__ */ diff --git a/arch/riscv/include/arch/fpstate.h b/arch/riscv/include/arch/fpstate.h new file mode 100644 index 0000000..4b42a22 --- /dev/null +++ b/arch/riscv/include/arch/fpstate.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FPU State — Floating-Point Register Context + * + * Pre-reserved structure for RISC-V F/D extension floating-point state. + * MUST be allocated in every TaskControlBlock so that future FPU + * save/restore logic has a stable destination without restructuring TCB. + * + * RISC-V F/D extension state: + * - 32 FP registers (f0-f31), each 64 bits for D extension + * - fcsr (Floating-point Control and Status Register) + * + * sstatus.FS field management: + * - FS=0 (Off): FPU disabled, any FP instruction traps + * - FS=1 (Initial): FPU enabled, registers are initial state + * - FS=2 (Clean): FPU enabled, registers not modified since last save + * - FS=3 (Dirty): FPU enabled, registers modified (need save) + * + * Current status: Structure defined, save/restore NOT implemented. + * When implemented, trap_dispatch and swtch.S will use lazy + * save/restore based on sstatus.FS dirty tracking. + * + * Reference: RISC-V ISA Manual §3.1.6.5 (FS field) + */ + +#include + +namespace arch { + +struct FpState { + uint64 f[32]; // f0-f31 (64-bit each for D extension) + uint32 fcsr; // Floating-point CSR (frm + fflags) + uint32 _pad; // Alignment padding +}; + +static_assert(sizeof(FpState) == 32 * 8 + 8, "FpState must be 264 bytes"); + +/* + * sstatus.FS field constants + */ +inline constexpr uint64 kSstatusFS_Off = 0ULL << 13; +inline constexpr uint64 kSstatusFS_Initial = 1ULL << 13; +inline constexpr uint64 kSstatusFS_Clean = 2ULL << 13; +inline constexpr uint64 kSstatusFS_Dirty = 3ULL << 13; +inline constexpr uint64 kSstatusFS_Mask = 3ULL << 13; + +/* + * Check if sstatus indicates FPU state is dirty (needs save). + */ +inline bool fpu_state_dirty(uint64 sstatus) { + return (sstatus & kSstatusFS_Mask) == kSstatusFS_Dirty; +} + +/* + * Check if FPU is enabled (FS != Off). + */ +inline bool fpu_enabled(uint64 sstatus) { + return (sstatus & kSstatusFS_Mask) != kSstatusFS_Off; +} + +} // namespace arch + +/* Assembly routines for FPU register save/restore (fpu.S) */ +extern "C" void fpu_save(arch::FpState* state); +extern "C" void fpu_restore(arch::FpState* state); + +namespace arch { + +/* + * Read sstatus.FS field (current FPU state) + */ +inline uint64 fpu_get_fs() { + uint64 sstatus; + __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); + return sstatus & kSstatusFS_Mask; +} + +/* + * Set sstatus.FS field. + * @fs: one of kSstatusFS_Off, kSstatusFS_Initial, kSstatusFS_Clean, kSstatusFS_Dirty + */ +inline void fpu_set_fs(uint64 fs) { + __asm__ volatile( + "csrc sstatus, %0\n" + "csrs sstatus, %1\n" + :: "r"(kSstatusFS_Mask), "r"(fs) + ); +} + +} // namespace arch diff --git a/arch/riscv/include/arch/mmu.h b/arch/riscv/include/arch/mmu.h new file mode 100644 index 0000000..f4c9b26 --- /dev/null +++ b/arch/riscv/include/arch/mmu.h @@ -0,0 +1,78 @@ +/* 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; +} + +// Read the raw satp CSR value (mode + PPN). +// Used when the caller needs the exact register value (e.g. for TrapFrame metadata). +static inline uint64 read_satp() { + uint64 val; + __asm__ volatile("csrr %0, satp" : "=r"(val)); + return val; +} + +// 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"); +} + +// Flush the instruction cache. +// Required after loading executable code into memory (e.g. ELF loading). +// sfence.vma only flushes the TLB; fence.i is needed for the I-cache. +static inline void flush_icache() { + __asm__ volatile("fence.i" ::: "memory"); +} + +} // namespace arch::mmu diff --git a/arch/riscv/include/arch/pmap.h b/arch/riscv/include/arch/pmap.h new file mode 100644 index 0000000..0b43b9e --- /dev/null +++ b/arch/riscv/include/arch/pmap.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * pmap — SV39 page table interface (MD layer) + * + * Namespace functions, zero runtime overhead. + * Implementation: arch/riscv/mm/pmap.cc + * + * Reference: docs/specs/vmm.md §3.2 + */ + +#include + +namespace pmap { + +/* PTE permission bits */ +inline constexpr uint64 PTE_V = 1ULL << 0; +inline constexpr uint64 PTE_R = 1ULL << 1; +inline constexpr uint64 PTE_W = 1ULL << 2; +inline constexpr uint64 PTE_X = 1ULL << 3; +inline constexpr uint64 PTE_U = 1ULL << 4; +inline constexpr uint64 PTE_G = 1ULL << 5; +inline constexpr uint64 PTE_A = 1ULL << 6; +inline constexpr uint64 PTE_D = 1ULL << 7; +inline constexpr uint64 PTE_COW = 1ULL << 8; /* Custom RSW bit for Copy-On-Write */ + +uint64 create(); +void destroy(uint64 root_pa); +int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); +int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); +void unmap(uint64 root_pa, uint64 va); +int protect(uint64 root_pa, uint64 va, uint64 perm); +int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm); +bool lookup(uint64 root_pa, uint64 va, uint64* pa_out); +uint64 lookup_pte(uint64 root_pa, uint64 va); +bool is_cow(uint64 root_pa, uint64 va); +int make_cow(uint64 root_pa, uint64 va); +void activate(uint64 root_pa); + +/* + * Translate MI-layer VM_* permission flags to hardware PTE bits. + * Called by vma.cc when faulting in a page. + */ +uint64 vm_perm_to_pte(uint64 vm_perm); + +} // namespace pmap diff --git a/arch/riscv/include/arch/random.h b/arch/riscv/include/arch/random.h new file mode 100644 index 0000000..8c8c051 --- /dev/null +++ b/arch/riscv/include/arch/random.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +namespace arch { + +/* + * Get a random 64-bit value from the hardware TRNG. + * Returns true if supported and successful, false otherwise. + */ +bool get_hw_random(uint64& value); + +/* + * Get the current high-resolution hardware cycle counter. + * Used as an entropy fallback (Jitter/Timer). + */ +inline uint64 get_cycles() +{ + uint64 cycles; + __asm__ volatile("rdcycle %0" : "=r"(cycles)); + return cycles; +} + +} // namespace arch diff --git a/arch/riscv/include/arch/sbi.h b/arch/riscv/include/arch/sbi.h new file mode 100644 index 0000000..80a6d7c --- /dev/null +++ b/arch/riscv/include/arch/sbi.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * sbi.h — RISC-V Supervisor Binary Interface (SBI) wrappers + * + * Provides inline ecall wrappers for SBI calls from S-mode. + * Uses the SBI v1.0+ extension-based calling convention. + * + * Reference: RISC-V SBI Specification v2.0 + */ + +#include + +namespace arch::sbi { + +struct sbiret { + long error; + long value; +}; + +/* SBI error codes */ +inline constexpr long SBI_SUCCESS = 0; +inline constexpr long SBI_ERR_NOT_SUPPORTED = -2; + +/* Extension IDs */ +inline constexpr int SBI_EXT_TIME = 0x54494D45; /* "TIME" */ +inline constexpr int SBI_EXT_IPI = 0x735049; /* "sPI" */ +inline constexpr int SBI_EXT_RFENCE = 0x52464E43; /* "RFNC" */ +inline constexpr int SBI_EXT_HSM = 0x48534D; /* "HSM" */ + +static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0 = 0, + uint64 arg1 = 0, uint64 arg2 = 0, + uint64 arg3 = 0, uint64 arg4 = 0, + uint64 arg5 = 0) { + sbiret ret; + register uint64 a0 asm("a0") = arg0; + register uint64 a1 asm("a1") = arg1; + register uint64 a2 asm("a2") = arg2; + register uint64 a3 asm("a3") = arg3; + register uint64 a4 asm("a4") = arg4; + register uint64 a5 asm("a5") = arg5; + register uint64 a6 asm("a6") = fid; + register uint64 a7 asm("a7") = ext; + __asm__ volatile("ecall" + : "+r"(a0), "+r"(a1) + : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) + : "memory"); + ret.error = static_cast(a0); + ret.value = static_cast(a1); + return ret; +} + +/* ---- Timer Extension (EID 0x54494D45) ---- */ + +static inline void set_timer(uint64 stime_value) { + sbi_ecall(SBI_EXT_TIME, 0, stime_value); +} + +/* ---- IPI Extension (EID 0x735049) ---- */ + +/* + * sbi_send_ipi — Send S-mode software interrupt to target harts + * + * @hart_mask: bitmask of target harts (relative to hart_mask_base) + * @hart_mask_base: base hart ID for the mask (0 = harts 0-63) + */ +static inline sbiret sbi_send_ipi(uint64 hart_mask, + uint64 hart_mask_base = 0) { + return sbi_ecall(SBI_EXT_IPI, 0, hart_mask, hart_mask_base); +} + +/* ---- Remote Fence Extension (EID 0x52464E43) ---- */ + +/* + * sbi_remote_sfence_vma — Execute SFENCE.VMA on target harts + * + * @hart_mask/base: target harts + * @start_addr: start of VA range to invalidate + * @size: size of range (use ~0UL for full flush) + */ +static inline sbiret sbi_remote_sfence_vma(uint64 hart_mask, + uint64 hart_mask_base, + uint64 start_addr, + uint64 size) { + return sbi_ecall(SBI_EXT_RFENCE, 1, + hart_mask, hart_mask_base, + start_addr, size); +} + +/* ---- HSM Extension (EID 0x48534D) ---- */ + +/* + * sbi_hart_start — Start (wake) a hart that is in STOPPED state + * + * @hartid: target hart ID to start + * @start_addr: PHYSICAL address where the hart should begin execution + * @opaque: passed in a1 register to the hart (e.g. FDT pointer) + * + * OpenSBI v1.0+ parks non-boot harts; they must be explicitly started. + */ +static inline sbiret sbi_hart_start(uint64 hartid, + uint64 start_addr, + uint64 opaque) { + return sbi_ecall(SBI_EXT_HSM, 0, hartid, start_addr, opaque); +} + +} // namespace arch::sbi + diff --git a/arch/riscv/include/arch/tlb.h b/arch/riscv/include/arch/tlb.h new file mode 100644 index 0000000..46c6b97 --- /dev/null +++ b/arch/riscv/include/arch/tlb.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +namespace arch::tlb { + +/* + * Perform a cross-CPU TLB flush (shootdown). + * + * @param cpu_mask: Bitmask of CPUs to target. + * @param start: Start virtual address, or 0 for full address space. + * @param size: Size in bytes, or ~0ULL for full address space. + */ +void flush_remote(uint64 cpu_mask, uint64 start, uint64 size); + +} // namespace arch::tlb diff --git a/arch/riscv/include/arch/trap.h b/arch/riscv/include/arch/trap.h new file mode 100644 index 0000000..8e55250 --- /dev/null +++ b/arch/riscv/include/arch/trap.h @@ -0,0 +1,94 @@ +/* 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; +} + +// Enable Supervisor User Memory access (set SUM bit in sstatus). +// When SUM=1, S-mode can read/write pages marked PTE_U. +// This is needed for syscall handlers to access user-space buffers. +static inline void enable_sum() { + __asm__ volatile("csrs sstatus, %0" :: "r"(1UL << 18)); +} + +// Disable Supervisor User Memory access (clear SUM bit in sstatus). +static inline void disable_sum() { + __asm__ volatile("csrc sstatus, %0" :: "r"(1UL << 18)); +} + +} // namespace arch::trap diff --git a/arch/riscv/include/arch/trapframe.h b/arch/riscv/include/arch/trapframe.h new file mode 100644 index 0000000..b198646 --- /dev/null +++ b/arch/riscv/include/arch/trapframe.h @@ -0,0 +1,140 @@ +/* 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 +#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 + +/* Forward declaration — defined in trap_md.cc. + * Must be after TrapFrame definition. */ +extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); + +namespace arch { + +/* + * trapframe_init_user — prepare a TrapFrame for returning to user mode. + * + * Sets arch-specific sstatus: + * SPP = 0 (return to U-mode on sret) + * SPIE = 1 (enable interrupts in U-mode) + * FS = Initial (allow FP instructions) + * + * Called from MI exec.cc instead of setting sstatus directly. + */ +static inline void trapframe_init_user(TrapFrame* tf, uint64 entry, uint64 sp) +{ + /* Clear all registers */ + for (auto& t : tf->t) { t = 0; } + for (auto& s : tf->s) { s = 0; } + for (auto& a : tf->a) { a = 0; } + tf->ra = 0; + tf->gp = 0; + tf->tp = 0; + + tf->epc = entry; + tf->sp = sp; + + /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts), FS=Initial (FP ok) */ + constexpr uint64 kSPIE = (1ULL << 5); + constexpr uint64 kFS_Initial = (1ULL << 13); + tf->sstatus = kSPIE | kFS_Initial; + + /* Kernel metadata used by trap_vector.S on every trap from U-mode. + * These are RISC-V specific — trap_vector reads them from the + * TrapFrame to switch to kernel context. */ + uint64 satp; + asm volatile("csrr %0, satp" : "=r"(satp)); + tf->kernel_satp = satp; + tf->kernel_sp = reinterpret_cast(tf); + tf->kernel_trap = reinterpret_cast(&trap_dispatch); + tf->kernel_cpu_id = cpu::id(); +} + +} // namespace arch + +#endif /* __ASSEMBLER__ */ diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h new file mode 100644 index 0000000..42f1eb4 --- /dev/null +++ b/arch/riscv/include/asm/csr.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_CSR_H +#define _ASM_RISCV_CSR_H + +/* + * RISC-V CSR (Control & Status Register) definitions used by the early + * boot / MMU-switch path. + * + * LumeOS-native subset of Linux arch/riscv/include/asm/csr.h. Only the + * symbols head.S and mm/init.cc actually reference are defined — adding + * the rest is straightforward when new code needs them, and skipping the + * Linux ALTERNATIVE / vendor-extension machinery keeps the cascade flat. + */ + +/* SATP (Supervisor Address Translation and Protection) layout — RV64 */ +#define SATP_PPN_MASK 0x00000FFFFFFFFFFFUL /* bits [43:0] = root PPN */ +#define SATP_ASID_SHIFT 44 +#define SATP_MODE_SHIFT 60 + +/* SATP MODE encodings (RV64) */ +#define SATP_MODE_BARE (0UL << SATP_MODE_SHIFT) +#define SATP_MODE_SV39 (8UL << SATP_MODE_SHIFT) +#define SATP_MODE_SV48 (9UL << SATP_MODE_SHIFT) + +/* Compose a SATP value from a page-table physical address. + * Result: MODE | (root_ppn) where root_ppn = pgd_pa >> PAGE_SHIFT. */ +#define MAKE_SATP(mode, pgd_pa) ((mode) | (((pgd_pa) >> 12) & SATP_PPN_MASK)) + +/* SSTATUS bits we touch at boot — disable FP / vector at entry so that an + * accidental FP / V instruction in early code traps cleanly rather than + * silently using dirty state from before reset. */ +#define SR_FS_OFF (0UL << 13) +#define SR_VS_OFF (0UL << 9) + +#endif /* _ASM_RISCV_CSR_H */ diff --git a/arch/riscv/include/asm/page.h b/arch/riscv/include/asm/page.h new file mode 100644 index 0000000..7b9f155 --- /dev/null +++ b/arch/riscv/include/asm/page.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_PAGE_H +#define _ASM_RISCV_PAGE_H + +/* + * RISC-V page / virtual-memory layout constants. + * + * Mirrors the subset of Linux arch/riscv/include/asm/page.h that LumeOS's + * early boot path needs: + * - PAGE_SIZE / PAGE_SHIFT / PAGE_MASK + * - PAGE_OFFSET (start of the higher-half direct map) + * - KERNEL_LINK_ADDR (link-time VA of _start) + * - SV39 PTE flag bits + * - PTRS_PER_PGD (512 for SV39) + * + * Values match Linux RISC-V SV39 defaults so that struct kernel_mapping + * (filled by setup_vm in arch/riscv/mm/init.cc) carries the same numeric + * relations Linux's mm/init.c maintains. + */ + +#define PAGE_SHIFT 12 +#define PAGE_SIZE (1UL << PAGE_SHIFT) +#define PAGE_MASK (~(PAGE_SIZE - 1)) + +/* Higher-half base of the kernel direct map. Bottom of the SV39 supervisor + * address space minus the unmapped guard region. Identical to Linux's + * CONFIG_PAGE_OFFSET default on RV64. */ +#define PAGE_OFFSET 0xFFFFFFC000000000UL + +/* Where _start was linked. OpenSBI hands control to PHYS_BASE; the kernel + * runs at KERNEL_LINK_ADDR after relocate_enable_mmu jumps to higher-half. */ +#define PHYS_BASE 0x80200000UL +#define KERNEL_LINK_ADDR 0xFFFFFFC080200000UL + +/* SATP mode field — SV39 = 8, shifted to bits [63:60]. */ +#define SATP_MODE_SV39 (8UL << 60) + +#define PTRS_PER_PGD 512 + +/* 1 GB superpage size — entry.S used to build the early page table with + * these; setup_vm in arch/riscv/mm/init.cc now does the same. */ +#define PGDIR_SHIFT 30 +#define PGDIR_SIZE (1UL << PGDIR_SHIFT) +#define PGDIR_MASK (~(PGDIR_SIZE - 1)) + +/* SV39 PTE flag bits (one byte). */ +#define _PAGE_PRESENT (1UL << 0) /* aka VALID — historic Linux name */ +#define _PAGE_READ (1UL << 1) +#define _PAGE_WRITE (1UL << 2) +#define _PAGE_EXEC (1UL << 3) +#define _PAGE_USER (1UL << 4) +#define _PAGE_GLOBAL (1UL << 5) +#define _PAGE_ACCESSED (1UL << 6) +#define _PAGE_DIRTY (1UL << 7) + +#define PAGE_KERNEL_EXEC \ + (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | \ + _PAGE_ACCESSED | _PAGE_DIRTY) + +#endif /* _ASM_RISCV_PAGE_H */ diff --git a/arch/riscv/include/asm/pgtable.h b/arch/riscv/include/asm/pgtable.h new file mode 100644 index 0000000..28fc4c5 --- /dev/null +++ b/arch/riscv/include/asm/pgtable.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_PGTABLE_H +#define _ASM_RISCV_PGTABLE_H + +/* + * Early page-table construction API used by arch/riscv/mm/init.cc::setup_vm. + * + * Mirrors the create_pgd_mapping interface from Linux's + * arch/riscv/mm/init.c — same name, same semantics: + * + * create_pgd_mapping(pgdir, va, pa, sz, prot) + * + * pgdir : root of the page table to populate + * va : virtual address to map + * pa : physical address to map to + * sz : mapping size — must be a supported leaf size (PAGE_SIZE, + * 2 MiB megapage, or 1 GiB gigapage) + * prot : PTE flag bits (PAGE_KERNEL_EXEC etc., from ) + * + * The boot-time variant in this file only supports gigapage (1 GiB) + * mappings — that is all setup_vm needs for the early_pg_dir / trampoline_pg_dir + * built before the slab allocator exists. Finer-grained mappings come + * later via vmm_init(). + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { unsigned long pgd; } pgd_t; + +/* Linker-provided early page-table storage (defined in lumeos.lds.S + * ARCH_BSS_EXTRA, lives in .bss so it's zero-initialized). Allocating + * them in the linker script — not in C — mirrors Linux and keeps the + * symbols visible from head.S without a forward C decl mismatch. */ +extern pgd_t early_pg_dir[PTRS_PER_PGD]; +extern pgd_t trampoline_pg_dir[PTRS_PER_PGD]; +extern pgd_t fixmap_pg_dir[PTRS_PER_PGD]; + +/* Install a single PGD entry mapping `va` → `pa` with size `sz` and PTE + * flags `prot`. Only sz == PGDIR_SIZE (1 GiB) is supported in this early + * variant — vmm_init() takes over with create_pgd_mapping_pmd later. */ +void create_pgd_mapping(pgd_t *pgdir, unsigned long va, unsigned long pa, + unsigned long sz, unsigned long prot); + +#ifdef __cplusplus +} +#endif + +#endif /* _ASM_RISCV_PGTABLE_H */ diff --git a/arch/riscv/include/asm/sections.h b/arch/riscv/include/asm/sections.h new file mode 100644 index 0000000..454d9de --- /dev/null +++ b/arch/riscv/include/asm/sections.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _ASM_RISCV_SECTIONS_H +#define _ASM_RISCV_SECTIONS_H + +/* + * Linker-emitted symbols that bracket each section of the kernel image. + * + * The lumeos.lds.S linker script defines these as the start / end + * addresses of contiguous regions. Code that walks a section + * (relocate_kernel walks .rela.dyn, the C++ ctor loop walks + * .init_array, the VMM mapping code walks .text vs .rodata, ...) + * imports the pair via this header. + * + * Mirrors Linux arch/riscv/include/asm/sections.h / asm-generic/sections.h. + * Each name and pair is identical to Linux so that direct ports of mm/init.c + * relocate_kernel logic compile unchanged against LumeOS. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern char _start[]; +extern char _end[]; + +extern char _stext[]; +extern char _etext[]; +extern char _srodata[]; +extern char _erodata[]; +extern char _sdata[]; +extern char _edata[]; +extern char _sbss[]; +extern char _ebss[]; + +extern char __init_array_start[]; +extern char __init_array_end[]; + +/* .rela.dyn — array of Elf64_Rela entries emitted by the linker under + * `-shared -Bsymbolic`. relocate_kernel walks this range and patches + * each R_RISCV_RELATIVE slot. */ +extern char __rela_dyn_start[]; +extern char __rela_dyn_end[]; + +#ifdef __cplusplus +} +#endif + +#endif /* _ASM_RISCV_SECTIONS_H */ 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 +#include + +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 + +#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\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 + +/* ===== 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 +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* 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(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]; +} diff --git a/arch/riscv/lib/random.cc b/arch/riscv/lib/random.cc new file mode 100644 index 0000000..afc0078 --- /dev/null +++ b/arch/riscv/lib/random.cc @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include + +namespace arch { + +bool get_hw_random(uint64& value) +{ + /* + * Real implementation would check for Zkr extension and execute: + * csrr x, seed + * + * Since QEMU virt doesn't guarantee Zkr, we return false to + * force the MI layer to use the timer fallback. + */ + return false; +} + +} // namespace arch diff --git a/arch/riscv/mm/init.cc b/arch/riscv/mm/init.cc new file mode 100644 index 0000000..3cebcd9 --- /dev/null +++ b/arch/riscv/mm/init.cc @@ -0,0 +1,241 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * RISC-V early memory-management bootstrap. + * + * Mirrors Linux arch/riscv/mm/init.c — same file name, same C function + * names (setup_vm / relocate_enable_mmu / relocate_kernel), same global + * state (kernel_map, early_pg_dir, trampoline_pg_dir). head.S calls + * setup_vm immediately after BSS-clear, then later calls + * relocate_enable_mmu, which switches SATP, jumps to higher-half VA, + * and finally calls relocate_kernel to apply .rela.dyn. + * + * Differences from Linux: + * - KASLR offset is hard-wired to 0 for now (no pi/ subsystem) — the + * code path that would consume kernel_map.virt_offset is in place + * so adding pi/-based seed reading later only requires populating + * that one field. + * - Only 1 GiB superpages supported in create_pgd_mapping; sufficient + * for the early page tables. Finer-grained mappings come from + * vmm_init() once slab is up. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Global kernel mapping descriptor — single source of truth for the + * runtime-vs-link address relationship. Lives in .data so its initial + * zero contents survive the BSS-clear that runs before setup_vm fills + * the fields. Read by VMM, page-fault handler, ptdump, etc. + * + * Linux equivalent: arch/riscv/mm/init.c::kernel_map + */ +struct kernel_mapping kernel_map __attribute__((section(".data"))); + +/* + * create_pgd_mapping — install one PGD entry covering a 1 GiB region. + * + * Only 1 GiB superpages are supported in this early variant; setup_vm + * needs nothing finer. Identical interface to Linux's same-named + * function in arch/riscv/mm/init.c, restricted in arity. + */ +extern "C" void create_pgd_mapping(pgd_t *pgdir, unsigned long va, + unsigned long pa, unsigned long sz, + unsigned long prot) +{ + /* This early variant only supports 1 GiB superpages. */ + if (sz != PGDIR_SIZE) { + return; + } + + /* SV39 PGD index = (va >> 30) & 0x1FF — see Volume II, §4.4.1. + * Each PGD entry is 8 bytes; pgdir[i] is the slot for that VPN[2]. */ + unsigned long idx = (va >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1); + + /* PTE PPN field: physical page number shifted up by 10 (SV39 PTE + * layout: bits [53:10] = PPN, bits [9:0] = flags). */ + unsigned long ppn = pa >> PAGE_SHIFT; + pgdir[idx].pgd = (ppn << 10) | prot; +} + +/* + * setup_vm — fill kernel_map and build early_pg_dir + trampoline_pg_dir. + * + * Called from head.S after BSS is cleared, with: + * load_pa = runtime physical address of _start + * dtb_pa = physical address of the device tree blob (from a1) + * + * Mirrors Linux arch/riscv/mm/init.c::setup_vm. The actual SATP write + * happens later in relocate_enable_mmu; here we only populate page + * tables and the kernel_map descriptor. + */ +extern "C" void __init setup_vm(uintptr_t load_pa, uintptr_t dtb_pa) +{ + (void)dtb_pa; /* KASLR pi/ subsystem will consume this later. */ + + /* --- 1. Fill kernel_map ----------------------------------------- */ + + /* KASLR offset = 0 in this configuration. */ + kernel_map.virt_offset = 0; + kernel_map.virt_addr = KERNEL_LINK_ADDR + kernel_map.virt_offset; + kernel_map.phys_addr = load_pa; + kernel_map.size = (uintptr_t)_end - (uintptr_t)_start; + kernel_map.page_offset = PAGE_OFFSET; + + /* Mirrors Linux: va_pa_offset is the constant added to a physical + * address to obtain its direct-map VA. */ + kernel_map.va_pa_offset = PAGE_OFFSET - load_pa; + + /* va_kernel_pa_offset is the constant added to a load PA to obtain + * the kernel's link VA (used by relocate_enable_mmu to fix sp/ra). */ + kernel_map.va_kernel_pa_offset = kernel_map.virt_addr - load_pa; + + /* --- 2. Build early_pg_dir ------------------------------------- * + * + * SV39 root page table — 4 entries cover identity + higher-half for + * both MMIO (PA 0x0000_0000, 1 GiB) and RAM (PA 0x8000_0000, 1 GiB): + * + * [0] identity PA 0x0000_0000 → VA 0x0000_0000 + * [2] identity PA 0x8000_0000 → VA 0x8000_0000 + * [256] high-half PA 0x0000_0000 → VA 0xFFFFFFC000000000 + * [258] high-half PA 0x8000_0000 → VA 0xFFFFFFC080000000 + * + * Identity mappings keep PC valid right after csrw satp. High-half + * mappings let the post-MMU `jr` to .Lhigher_half resolve. */ + create_pgd_mapping(early_pg_dir, 0x00000000UL, 0x00000000UL, + PGDIR_SIZE, PAGE_KERNEL_EXEC); + create_pgd_mapping(early_pg_dir, 0x80000000UL, 0x80000000UL, + PGDIR_SIZE, PAGE_KERNEL_EXEC); + create_pgd_mapping(early_pg_dir, PAGE_OFFSET + 0x00000000UL, + 0x00000000UL, PGDIR_SIZE, PAGE_KERNEL_EXEC); + create_pgd_mapping(early_pg_dir, PAGE_OFFSET + 0x80000000UL, + 0x80000000UL, PGDIR_SIZE, PAGE_KERNEL_EXEC); + + /* --- 3. Build trampoline_pg_dir -------------------------------- * + * + * The trampoline maps just the gigapage containing _start, both at + * its identity PA and at its higher-half VA. This is the page + * table SATP holds during the single instruction window between + * "MMU off" and "MMU on with early_pg_dir" — the PC must resolve + * under both, so we re-use the same two entries above. */ + create_pgd_mapping(trampoline_pg_dir, load_pa & PGDIR_MASK, + load_pa & PGDIR_MASK, PGDIR_SIZE, PAGE_KERNEL_EXEC); + create_pgd_mapping(trampoline_pg_dir, + (kernel_map.virt_addr) & PGDIR_MASK, + load_pa & PGDIR_MASK, PGDIR_SIZE, PAGE_KERNEL_EXEC); +} + +/* + * relocate_kernel — walk .rela.dyn and apply R_RISCV_RELATIVE entries. + * + * Called from relocate_enable_mmu *after* the SATP switch and the jump + * to higher-half VA — meaning __rela_dyn_start / __rela_dyn_end resolve + * to valid higher-half pointers we can dereference directly. + * + * Mirrors Linux arch/riscv/mm/init.c::relocate_kernel. Key invariant: + * even when kaslr_offset == 0, the slot store MUST still happen — the + * linker leaves R_RISCV_RELATIVE slots zero-filled at link time, and + * only relocate_kernel writes the link-time addend into them. Without + * this pass, .init_array entries (and every other relocated symbol) + * are NULL pointers. + */ +void __init relocate_kernel(void) +{ + auto *rela = (Elf64_Rela *)__rela_dyn_start; + auto *rela_end = (Elf64_Rela *)__rela_dyn_end; + + const uint64 kaslr_offset = kernel_map.virt_offset; + + for (; rela < rela_end; ++rela) { + if (ELF64_R_TYPE(rela->r_info) != R_RISCV_RELATIVE) { + continue; + } + auto *slot = (Elf64_Addr *)(rela->r_offset + kaslr_offset); + *slot = (Elf64_Addr)(rela->r_addend) + kaslr_offset; + } +} + +/* + * relocate_enable_mmu — switch SATP, jump to higher-half, apply relocations. + * + * Called from head.S after setup_vm has filled kernel_map and built the + * page tables. Equivalent to Linux's same-named asmlinkage routine but + * written in C++ so it can share kernel_map / pgtable definitions with + * setup_vm directly. + * + * Three logical steps: + * + * 1. csrw satp ← trampoline_pg_dir. PC continues running at the + * identity PA, which the trampoline still maps; the high-half PA + * mapping is now also live. + * + * 2. la t0, .Lvirt; add t0, t0, va_kernel_pa_offset; jr t0. + * A PC-relative `la` under medany yields the runtime PA of the + * label; adding va_kernel_pa_offset (= link_va - load_pa) converts + * it to the link-time higher-half VA, which the trampoline maps. + * The `jr` then atomically pushes PC into higher-half space. + * + * 3. csrw satp ← early_pg_dir. We're now executing at higher-half + * VA, so the trampoline can be retired in favour of the full + * early page table. sp / ra / gp must be slid to higher-half too, + * otherwise the next ret-or-call uses physical-low addresses + * whose mapping is about to disappear when vmm_init drops the + * identity entries. + * + * 4. Call relocate_kernel — now safe, because all link-time VA + * pointers in __rela_dyn_start[..] dereference correctly. + */ +extern "C" void __init relocate_enable_mmu(pgd_t *pgdir) +{ + (void)pgdir; /* head.S passes early_pg_dir for documentation; we + * read the global symbol directly inside the asm. */ + + const unsigned long va_kernel_pa_offset = kernel_map.va_kernel_pa_offset; + const unsigned long satp_mode = SATP_MODE_SV39; + + asm volatile ( + /* Step 1: csrw satp <- trampoline */ + "la t0, trampoline_pg_dir\n" + "srli t0, t0, 12\n" + "or t0, t0, %[mode]\n" + "csrw satp, t0\n" + "sfence.vma\n" + + /* Step 2: jump to higher-half VA */ + "la t0, 1f\n" + "add t0, t0, %[off]\n" + "jr t0\n" + "1:\n" + + /* Step 3a: csrw satp <- early_pg_dir (now in higher-half) */ + "la t0, early_pg_dir\n" + "srli t0, t0, 12\n" + "or t0, t0, %[mode]\n" + "csrw satp, t0\n" + "sfence.vma\n" + + /* Step 3b: slide sp / ra / gp to higher-half */ + "add sp, sp, %[off]\n" + "add ra, ra, %[off]\n" + ".option push\n" + ".option norelax\n" + "la gp, __global_pointer$\n" + ".option pop\n" + : + : [off] "r"(va_kernel_pa_offset), + [mode] "r"(satp_mode) + : "t0", "memory" + ); + + /* Step 4: apply .rela.dyn now that VA dereferences work. */ + relocate_kernel(); +} diff --git a/arch/riscv/mm/pmap.cc b/arch/riscv/mm/pmap.cc new file mode 100644 index 0000000..f0a6848 --- /dev/null +++ b/arch/riscv/mm/pmap.cc @@ -0,0 +1,361 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * pmap.cc — SV39 page table operations (MD layer) + * + * Implements the pmap namespace interface for RISC-V SV39. + * 3-level page table: VPN[2] (9 bits) -> VPN[1] (9 bits) -> VPN[0] (9 bits) + * Each level is a 4KB page containing 512 PTEs of 8 bytes each. + * + * Address conversion: all page table operations work with physical addresses. + * To read/write page table entries we must convert PA to VA via pa_to_va(). + * + * Reference: docs/specs/vmm.md + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pmap { + +/* + * Before the first pmap::activate(), CPU runs on entry.S bootstrap page table + * (fixed direct-map base). After activation, runtime (KASLR) base is valid. + */ +static bool g_runtime_mapping_ready = false; + +/* PTE bit definitions */ +inline constexpr uint64 PTE_V = 1ULL << 0; +inline constexpr uint64 PTE_R = 1ULL << 1; +inline constexpr uint64 PTE_W = 1ULL << 2; +inline constexpr uint64 PTE_X = 1ULL << 3; +inline constexpr uint64 PTE_U = 1ULL << 4; +inline constexpr uint64 PTE_G = 1ULL << 5; +inline constexpr uint64 PTE_A = 1ULL << 6; +inline constexpr uint64 PTE_D = 1ULL << 7; +inline constexpr uint64 PTE_COW = 1ULL << 8; + +/* SV39: 3 levels, 9 bits per level, 12-bit page offset */ +inline constexpr int kLevels = 3; +inline constexpr int kPtePerPage = 512; + +/* SV39: upper half of VA space (kernel) starts at L2 index 256 */ +inline constexpr int kKernelPgdStart = kPtePerPage / 2; + +/* SATP PPN mask (44 bits) and page shift */ +inline constexpr uint64 kSatpPpnMask = 0xFFFFFFFFFFFULL; +inline constexpr int kPageShift = 12; + +/* Extract VPN[level] from a virtual address */ +static inline uint64 vpn(uint64 va, int level) +{ + return (va >> (12 + 9 * level)) & 0x1FF; +} + +/* Extract PA from a PTE */ +static inline uint64 pte_to_pa(uint64 pte) +{ + return ((pte >> 10) & 0xFFFFFFFFFFFULL) << 12; +} + +/* Create a PTE from a PA and flags */ +static inline uint64 pa_to_pte(uint64 pa, uint64 flags) +{ + return ((pa >> 12) << 10) | flags; +} + +/* Convert PA to kernel VA for accessing page table contents */ +static inline uint64 *pa_to_ptr(uint64 pa) +{ + uint64 va = g_runtime_mapping_ready + ? pa_to_va(phys_addr(pa)).raw + : boot_pa_to_va(phys_addr(pa)).raw; + return reinterpret_cast(va); +} + +/* Allocate a zeroed page for a page table level. + * During early boot (before PMM), uses memblock. + * After PMM init, uses the buddy allocator. + * Returns PA of the new page, or 0 on failure. */ +static uint64 alloc_pt_page() +{ + uint64 pa; + if (memblock_is_active()) { + pa = memblock_alloc(kPageSize, kPageSize); + } else { + Page *page = pmm_alloc_page(); + if (!page) { + return 0; + } + pa = page_to_pa(page).raw; + } + if (!pa) { + return 0; + } + /* Zero the page */ + uint64 *ptr = pa_to_ptr(pa); + for (int i = 0; i < kPtePerPage; i++) { + ptr[i] = 0; + } + return pa; +} + +uint64 create() +{ + uint64 new_root = alloc_pt_page(); + if (!new_root) return 0; + + /* Copy kernel mappings from the current kernel page table. + * In SV39, entries 256..511 (VPN[2] >= 256) cover the upper + * half of the 64-bit VA space where the kernel lives. + * Without these, switching to the new page table would + * immediately fault on any kernel VA access. + * + * Only do this after the runtime page table is active — + * during early boot the bootstrap PT doesn't cover all of + * RAM so pa_to_ptr on the kernel root would fault. */ + if (g_runtime_mapping_ready) { + uint64 kernel_satp; + asm volatile("csrr %0, satp" : "=r"(kernel_satp)); + uint64 kernel_root_pa = (kernel_satp & kSatpPpnMask) << kPageShift; + + uint64 *new_l2 = pa_to_ptr(new_root); + uint64 *kern_l2 = pa_to_ptr(kernel_root_pa); + + for (int i = kKernelPgdStart; i < kPtePerPage; i++) { + new_l2[i] = kern_l2[i]; + } + } + + return new_root; +} + +void destroy(uint64 root_pa) +{ + uint64 *l2 = pa_to_ptr(root_pa); + + /* Only free the user half (entries 0..255). + * Entries 256..511 are shared kernel PGD pointers copied + * from the kernel page table — freeing them would corrupt + * the kernel's own mappings. */ + for (int i = 0; i < kKernelPgdStart; i++) { + if (!(l2[i] & PTE_V)) + continue; + /* Non-leaf L2 entry? (no R/W/X bits = pointer to L1 table) */ + if (!(l2[i] & (PTE_R | PTE_W | PTE_X))) { + uint64 l1_pa = pte_to_pa(l2[i]); + uint64 *l1 = pa_to_ptr(l1_pa); + for (int j = 0; j < kPtePerPage; j++) { + if (!(l1[j] & PTE_V)) + continue; + if (!(l1[j] & (PTE_R | PTE_W | PTE_X))) { + /* L0 page table */ + uint64 l0_pa = pte_to_pa(l1[j]); + pmm_free_page(pa_to_page(phys_addr(l0_pa))); + } + } + pmm_free_page(pa_to_page(phys_addr(l1_pa))); + } + } + pmm_free_page(pa_to_page(phys_addr(root_pa))); +} + +int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) +{ + uint64 *table = pa_to_ptr(root_pa); + + /* Walk levels 2 -> 1, creating intermediate tables as needed */ + for (int level = 2; level > 0; level--) { + uint64 idx = vpn(va, level); + if (!(table[idx] & PTE_V)) { + uint64 child_pa = alloc_pt_page(); + if (!child_pa) + return -ENOMEM; /* -ENOMEM */ + table[idx] = pa_to_pte(child_pa, PTE_V); + } + table = pa_to_ptr(pte_to_pa(table[idx])); + } + + /* Level 0: install the leaf PTE */ + uint64 idx = vpn(va, 0); + table[idx] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); + return 0; +} + +int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) +{ + /* VA and PA must be 2MB-aligned */ + constexpr uint64 k2MB = 2ULL * 1024 * 1024; + if ((va & (k2MB - 1)) != 0 || (pa & (k2MB - 1)) != 0) { + return -EINVAL; /* -EINVAL */ + } + + uint64 *table = pa_to_ptr(root_pa); + + /* Walk L2: create intermediate table if needed */ + uint64 idx2 = vpn(va, 2); + if (!(table[idx2] & PTE_V)) { + uint64 child_pa = alloc_pt_page(); + if (!child_pa) { + return -ENOMEM; /* -ENOMEM */ + } + table[idx2] = pa_to_pte(child_pa, PTE_V); + } + table = pa_to_ptr(pte_to_pa(table[idx2])); + + /* L1: install leaf PTE (2MB mega page) */ + uint64 idx1 = vpn(va, 1); + table[idx1] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); + return 0; +} + +void unmap(uint64 root_pa, uint64 va) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level > 0; level--) { + uint64 idx = vpn(va, level); + if (!(table[idx] & PTE_V)) + return; + table = pa_to_ptr(pte_to_pa(table[idx])); + } + table[vpn(va, 0)] = 0; +} + +int protect(uint64 root_pa, uint64 va, uint64 perm) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level >= 0; level--) { + uint64 idx = vpn(va, level); + uint64 pte = table[idx]; + if (!(pte & PTE_V)) + return -EFAULT; /* -EFAULT */ + if (pte & (PTE_R | PTE_W | PTE_X)) { + uint64 pa = pte_to_pa(pte); + table[idx] = pa_to_pte(pa, perm | PTE_V); + return 0; + } + table = pa_to_ptr(pte_to_pa(pte)); + } + return -EFAULT; +} + +int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm) +{ + if (end <= start) { + return 0; + } + + uint64 va = page_align_down(start); + uint64 va_end = page_align_up(end); + + for (; va < va_end; va += kPageSize) { + int ret = protect(root_pa, va, perm); + if (ret == -14) { + continue; /* unmapped page: skip */ + } + if (ret < 0) { + return ret; + } + } + + arch::mmu::flush_tlb_all(); + return 0; +} + +bool lookup(uint64 root_pa, uint64 va, uint64 *pa_out) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level >= 0; level--) { + uint64 idx = vpn(va, level); + uint64 pte = table[idx]; + if (!(pte & PTE_V)) + return false; + /* Leaf PTE? (has R, W, or X) */ + if (pte & (PTE_R | PTE_W | PTE_X)) { + if (pa_out) + *pa_out = pte_to_pa(pte); + return true; + } + table = pa_to_ptr(pte_to_pa(pte)); + } + return false; +} + +uint64 lookup_pte(uint64 root_pa, uint64 va) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level >= 0; level--) { + uint64 idx = vpn(va, level); + uint64 pte = table[idx]; + if (!(pte & PTE_V)) + return 0; + if (pte & (PTE_R | PTE_W | PTE_X)) + return pte; // leaf PTE raw value + table = pa_to_ptr(pte_to_pa(pte)); + } + return 0; +} + +bool is_cow(uint64 root_pa, uint64 va) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level >= 0; level--) { + uint64 idx = vpn(va, level); + uint64 pte = table[idx]; + if (!(pte & PTE_V)) + return false; + if (pte & (PTE_R | PTE_W | PTE_X)) { + return (pte & PTE_COW) != 0; + } + table = pa_to_ptr(pte_to_pa(pte)); + } + return false; +} + +int make_cow(uint64 root_pa, uint64 va) +{ + uint64 *table = pa_to_ptr(root_pa); + for (int level = 2; level >= 0; level--) { + uint64 idx = vpn(va, level); + uint64 pte = table[idx]; + if (!(pte & PTE_V)) + return -EFAULT; /* -EFAULT */ + if (pte & (PTE_R | PTE_W | PTE_X)) { + /* Clear write bit, set COW bit */ + pte &= ~PTE_W; + pte |= PTE_COW; + table[idx] = pte; + return 0; + } + table = pa_to_ptr(pte_to_pa(pte)); + } + return -EFAULT; +} + +void activate(uint64 root_pa) +{ + arch::mmu::set_page_table(root_pa); + arch::mmu::flush_tlb_all(); + g_runtime_mapping_ready = true; +} + +uint64 vm_perm_to_pte(uint64 vm_perm) +{ + /* Translate MI VM_* flags to SV39 PTE bits. + * The MI flags are defined in . */ + uint64 pte = PTE_V | PTE_A | PTE_D; // Valid + Accessed + Dirty (avoid HW A/D faults) + if (vm_perm & (1ULL << 0)) pte |= PTE_R; // VM_READ + if (vm_perm & (1ULL << 1)) pte |= PTE_W; // VM_WRITE + if (vm_perm & (1ULL << 2)) pte |= PTE_X; // VM_EXEC + if (vm_perm & (1ULL << 3)) pte |= PTE_U; // VM_USER + return pte; +} + +} // namespace pmap diff --git a/arch/riscv/mm/tlb_md.cc b/arch/riscv/mm/tlb_md.cc new file mode 100644 index 0000000..ed9aa29 --- /dev/null +++ b/arch/riscv/mm/tlb_md.cc @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include + +namespace arch::tlb { + +void flush_remote(uint64 cpu_mask, uint64 start, uint64 size) { + if (size == ~0ULL) { + arch::sbi::sbi_remote_sfence_vma(cpu_mask, 0, 0, ~0UL); + } else { + arch::sbi::sbi_remote_sfence_vma(cpu_mask, 0, start, size); + } +} + +} // namespace arch::tlb diff --git a/arch/riscv/module.mk b/arch/riscv/module.mk new file mode 100644 index 0000000..97d22de --- /dev/null +++ b/arch/riscv/module.mk @@ -0,0 +1,18 @@ +# arch/riscv/module.mk +SRCS_CC += arch/riscv/mm/pmap.cc +SRCS_CC += arch/riscv/mm/init.cc +SRCS_CC += arch/riscv/config.cc +SRCS_CC += arch/riscv/trap/trap_md.cc +SRCS_CC += arch/riscv/syscall/syscall_md.cc +SRCS_CC += arch/riscv/lib/random.cc +SRCS_CC += arch/riscv/mm/tlb_md.cc +SRCS_S += arch/riscv/kernel/head.S +SRCS_S += arch/riscv/trap/swtch.S +SRCS_S += arch/riscv/trap/fpu.S +SRCS_S += arch/riscv/trap/trap_vector.S +SRCS_S += arch/riscv/uaccess/uaccess_memcpy.S +SRCS_CC += arch/riscv/trap/extable.cc +SRCS_CC += arch/riscv/kernel/signal.cc + +# Arch-specific headers (added to include path) +INCLUDES += -Iarch/riscv/include diff --git a/arch/riscv/syscall/syscall_md.cc b/arch/riscv/syscall/syscall_md.cc new file mode 100644 index 0000000..26320c8 --- /dev/null +++ b/arch/riscv/syscall/syscall_md.cc @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Syscall MD Layer — RISC-V Register Extraction + * + * Extracts syscall number (a7) and arguments (a0-a5) from the TrapFrame, + * calls the MI dispatcher, and writes the return value back to a0. + * + * Reference: docs/specs/syscall.md §5 + */ + +#include +#include +#include +#include + +void syscall_dispatch_md(arch::TrapFrame* tf) +{ + uint64 nr = tf->a[7]; /* RISC-V ABI: syscall number in a7 */ + + uint64 args[6] = { + tf->a[0], tf->a[1], tf->a[2], + tf->a[3], tf->a[4], tf->a[5], + }; + + /* Enable interrupts so timer and preemption work during syscalls */ + arch::cpu::intr_on(); + + int64 ret = syscall_dispatch(nr, args); + + arch::cpu::intr_off(); + + /* sys_rt_sigreturn restores the entire TrapFrame from the signal + * frame on the user stack. Writing ret into a[0] would clobber + * the restored value, so we must skip the writeback. */ + if (nr == static_cast(SYS_rt_sigreturn)){ + return; + } + tf->a[0] = static_cast(ret); /* Return value in a0 */ +} + diff --git a/arch/riscv/trap/extable.cc b/arch/riscv/trap/extable.cc new file mode 100644 index 0000000..57a3dc1 --- /dev/null +++ b/arch/riscv/trap/extable.cc @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include + +extern struct ExceptionTableEntry __extable_start[]; +extern struct ExceptionTableEntry __extable_end[]; + +static void swap_extable_entries(struct ExceptionTableEntry *a, struct ExceptionTableEntry *b) { + uint64 a_insn = reinterpret_cast(&a->insn) + a->insn; + uint64 a_fixup = reinterpret_cast(&a->fixup) + a->fixup; + uint64 b_insn = reinterpret_cast(&b->insn) + b->insn; + uint64 b_fixup = reinterpret_cast(&b->fixup) + b->fixup; + + a->insn = static_cast(b_insn - reinterpret_cast(&a->insn)); + a->fixup = static_cast(b_fixup - reinterpret_cast(&a->fixup)); + b->insn = static_cast(a_insn - reinterpret_cast(&b->insn)); + b->fixup = static_cast(a_fixup - reinterpret_cast(&b->fixup)); +} + +static void sort_extable(size_t left, size_t right) { + if (left >= right) return; + + size_t mid_idx = left + (right - left) / 2; + struct ExceptionTableEntry *pivot = &__extable_start[mid_idx]; + uint64 pivot_val = reinterpret_cast(&pivot->insn) + pivot->insn; + + size_t i = left; + size_t j = right; + + while (i <= j) { + while ((reinterpret_cast(&__extable_start[i].insn) + __extable_start[i].insn) < pivot_val) { + i++; + } + while ((reinterpret_cast(&__extable_start[j].insn) + __extable_start[j].insn) > pivot_val) { + if (j == 0) { + break; + } + j--; + } + if (i <= j) { + swap_extable_entries(&__extable_start[i], &__extable_start[j]); + i++; + if (j == 0) { + break; + } + j--; + } + } + + if (j > left) { + sort_extable(left, j); + } + if (i < right) { + sort_extable(i, right); + } +} + +void extable_init() { + size_t count = __extable_end - __extable_start; + if (count <= 1) return; + sort_extable(0, count - 1); +} + +uint64 search_extable(uint64 pc) { + size_t count = __extable_end - __extable_start; + if (count == 0) return 0; + + size_t left = 0; + size_t right = count - 1; + + /* Binary search */ + while (left <= right) { + size_t mid = left + (right - left) / 2; + const struct ExceptionTableEntry *entry = &__extable_start[mid]; + uint64 mid_pc = reinterpret_cast(&entry->insn) + entry->insn; + + if (mid_pc == pc) { + return reinterpret_cast(&entry->fixup) + entry->fixup; + } + + if (mid_pc < pc) { + left = mid + 1; + } else { + if (mid == 0) break; + right = mid - 1; + } + } + + return 0; +} diff --git a/arch/riscv/trap/fpu.S b/arch/riscv/trap/fpu.S new file mode 100644 index 0000000..90d96d9 --- /dev/null +++ b/arch/riscv/trap/fpu.S @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * FPU Save / Restore (RISC-V D extension) + * + * void fpu_save(FpState* state); + * Saves f0-f31 and fcsr to the FpState structure. + * + * void fpu_restore(FpState* state); + * Restores f0-f31 and fcsr from the FpState structure. + * + * FpState layout: f[0..31] at offset 0 (each 8 bytes), fcsr at offset 256. + * + * Called from schedule() when switching between tasks that have + * used the FPU (sstatus.FS == Dirty on save path). + */ + + .section .text + +/* ====================================================================== + * void fpu_save(FpState* state); // a0 = state pointer + * ====================================================================== */ + .globl fpu_save + .align 2 +fpu_save: + fsd f0, 0(a0) + fsd f1, 8(a0) + fsd f2, 16(a0) + fsd f3, 24(a0) + fsd f4, 32(a0) + fsd f5, 40(a0) + fsd f6, 48(a0) + fsd f7, 56(a0) + fsd f8, 64(a0) + fsd f9, 72(a0) + fsd f10, 80(a0) + fsd f11, 88(a0) + fsd f12, 96(a0) + fsd f13, 104(a0) + fsd f14, 112(a0) + fsd f15, 120(a0) + fsd f16, 128(a0) + fsd f17, 136(a0) + fsd f18, 144(a0) + fsd f19, 152(a0) + fsd f20, 160(a0) + fsd f21, 168(a0) + fsd f22, 176(a0) + fsd f23, 184(a0) + fsd f24, 192(a0) + fsd f25, 200(a0) + fsd f26, 208(a0) + fsd f27, 216(a0) + fsd f28, 224(a0) + fsd f29, 232(a0) + fsd f30, 240(a0) + fsd f31, 248(a0) + frcsr t0 + sw t0, 256(a0) + ret + +/* ====================================================================== + * void fpu_restore(FpState* state); // a0 = state pointer + * ====================================================================== */ + .globl fpu_restore + .align 2 +fpu_restore: + fld f0, 0(a0) + fld f1, 8(a0) + fld f2, 16(a0) + fld f3, 24(a0) + fld f4, 32(a0) + fld f5, 40(a0) + fld f6, 48(a0) + fld f7, 56(a0) + fld f8, 64(a0) + fld f9, 72(a0) + fld f10, 80(a0) + fld f11, 88(a0) + fld f12, 96(a0) + fld f13, 104(a0) + fld f14, 112(a0) + fld f15, 120(a0) + fld f16, 128(a0) + fld f17, 136(a0) + fld f18, 144(a0) + fld f19, 152(a0) + fld f20, 160(a0) + fld f21, 168(a0) + fld f22, 176(a0) + fld f23, 184(a0) + fld f24, 192(a0) + fld f25, 200(a0) + fld f26, 208(a0) + fld f27, 216(a0) + fld f28, 224(a0) + fld f29, 232(a0) + fld f30, 240(a0) + fld f31, 248(a0) + lw t0, 256(a0) + fscsr t0 + ret diff --git a/arch/riscv/trap/swtch.S b/arch/riscv/trap/swtch.S new file mode 100644 index 0000000..dbae43d --- /dev/null +++ b/arch/riscv/trap/swtch.S @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Context Switch & Kernel Thread Trampoline (RISC-V 64) + * + * context_switch: saves/restores callee-saved registers. + * kthread_entry_trampoline: entry point for newly created kernel threads. + * After context_switch loads the new Context, execution lands here via + * Context.ra = kthread_entry_trampoline. The entry function and argument + * are in s0 and s1 respectively, set by sched_init/create_tcb. + * + * The trampoline calls the MI-layer kthread_entry(entry, arg). + * + * Reference: docs/specs/scheduler.md §6 + */ + + .section .text + +/* ======================================================================== + * void context_switch(Context* old_ctx, Context* next_ctx); + * a0 = old_ctx_ptr, a1 = next_ctx_ptr + * ======================================================================== */ + .globl context_switch + .align 2 +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 + +/* ======================================================================== + * kthread_entry_trampoline + * + * Reached via context_switch when a new kthread is first scheduled. + * Context was set up by create_tcb(): + * s0 = entry function pointer + * s1 = argument pointer + * + * Calls the MI-layer C++ function: + * void kthread_entry(void (*entry)(void*), void* arg); + * ======================================================================== */ + .globl kthread_entry_trampoline + .align 2 +kthread_entry_trampoline: + mv a0, s0 /* a0 = entry function */ + mv a1, s1 /* a1 = argument */ + call kthread_entry /* MI-layer C++ function */ + + /* kthread_entry should never return, but if it does: */ + wfi + j kthread_entry_trampoline + 12 /* loop on wfi */ + +/* ======================================================================== + * ret_from_fork + * + * Trampoline for newly forked processes. + * Context is set up by fork() so that s0 contains the TrapFrame pointer. + * + * Calls the MI-layer C++ function: + * void fork_child_entry(TrapFrame* tf); + * ======================================================================== */ + .globl ret_from_fork + .align 2 +ret_from_fork: + mv a0, s0 /* a0 = TrapFrame pointer */ + call fork_child_entry /* does not return, eventually calls userret */ + wfi + j ret_from_fork + 8 \ No newline at end of file diff --git a/arch/riscv/trap/trap_md.cc b/arch/riscv/trap/trap_md.cc new file mode 100644 index 0000000..e30aea6 --- /dev/null +++ b/arch/riscv/trap/trap_md.cc @@ -0,0 +1,241 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Trap MD Layer (RISC-V 64) + * + * All architecture-specific trap logic lives here: + * - scause/stval reading (CSR access) + * - scause → MI TrapCause translation + * - RISC-V specific fixups (ecall epc += 4, ebreak epc += 2) + * - stvec setup (trap_init / trap_init_ap) + * - kernel_trap_handler (traps from S-mode) + * + * MI layer (kernel/trap.cc) contains ZERO arch-specific code. + * + * Reference: docs/specs/trap.md §7 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PLIC interface (drivers/plic.cc) */ +uint32 plic_claim(); +void plic_complete(uint32 irq); + +/* trap_vector.S: assembly entry point */ +extern "C" void trap_vector(); + +/* ======================================================================== + * scause → TrapCause translation (RISC-V specific) + * ======================================================================== */ + +static TrapCause scause_to_trap_cause(uint64 scause) +{ + if (arch::trap::is_interrupt(scause)) { + switch (arch::trap::cause_code(scause)) { + case IRQ_S_TIMER: return TrapCause::IrqTimer; + case IRQ_S_SOFTWARE: return TrapCause::IrqSoftware; + case IRQ_S_EXTERNAL: return TrapCause::IrqExternal; + default: return TrapCause::Unknown; + } + } + + /* Exception (synchronous) */ + switch (scause) { + case EXC_INST_PAGE_FAULT: return TrapCause::PageFaultExec; + case EXC_LOAD_PAGE_FAULT: return TrapCause::PageFaultLoad; + case EXC_STORE_PAGE_FAULT: return TrapCause::PageFaultStore; + case EXC_ECALL_FROM_U: return TrapCause::Syscall; + case EXC_ILLEGAL_INST: return TrapCause::IllegalInst; + case EXC_BREAKPOINT: return TrapCause::Breakpoint; + + case EXC_INST_ACCESS_FAULT: + case EXC_LOAD_ACCESS_FAULT: + case EXC_STORE_ACCESS_FAULT: + return TrapCause::AccessFault; + + case EXC_INST_MISALIGNED: + case EXC_LOAD_MISALIGNED: + case EXC_STORE_MISALIGNED: + return TrapCause::AlignFault; + + default: + return TrapCause::Unknown; + } +} + +TrapCause trap_translate_scause(uint64 scause) +{ + return scause_to_trap_cause(scause); +} + +static TrapOrigin origin_from_sstatus(uint64 sstatus) +{ + constexpr uint64 kSstatusSPP = (1ULL << 8); + return (sstatus & kSstatusSPP) ? TrapOrigin::FromKernel + : TrapOrigin::FromUser; +} + +static TrapClass classify_trap(uint64 scause, TrapCause cause) +{ + if (arch::trap::is_interrupt(scause)) { + return TrapClass::Interrupt; + } + if (cause == TrapCause::Syscall) { + return TrapClass::Syscall; + } + if (cause == TrapCause::Unknown) { + return TrapClass::Unknown; + } + return TrapClass::Exception; +} + +static TrapInfo build_trap_info(arch::TrapFrame* tf, + uint64 scause, + uint64 stval, + TrapOrigin origin) +{ + TrapInfo info{}; + info.cause = scause_to_trap_cause(scause); + info.cls = classify_trap(scause, info.cause); + info.origin = origin; + info.val = stval; + info.epc = tf ? tf->epc : 0; + info.scause = scause; + return info; +} + +/* ======================================================================== + * trap_dispatch — user-mode trap entry (called from trap_vector.S) + * + * Reads RISC-V CSRs, translates to MI TrapCause, applies arch-specific + * fixups, then calls MI trap_handle(). + * + * After trap_handle returns, we must return to user mode via userret(). + * The assembly jalr t3 that called us has NO return-to-user code after it, + * so trap_dispatch must never return normally. + * ======================================================================== */ + +extern "C" void userret(arch::TrapFrame* tf); + +extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame* tf) +{ + /* Switch stvec to kernelvec immediately so that any traps during + * kernel execution (timer IRQs, page faults from copy_from_user) + * go through the kernel trap entry, not the user trap entry. + * This matches Linux which sets stvec to handle_exception (kernel) + * at the top of the user trap path. */ + extern void kernelvec(); + arch::trap::set_vector(reinterpret_cast(&kernelvec)); + + /* Enable SUM so S-mode can access user pages (PTE_U). + * Without this, any dereference of user-space pointers in syscall + * handlers (sys_write buf, sys_read buf, etc.) would page fault. */ + arch::trap::enable_sum(); + + uint64 scause = arch::trap::get_cause(); + uint64 stval = arch::trap::get_value(); + + TrapOrigin origin = origin_from_sstatus(tf->sstatus); + TrapInfo info = build_trap_info(tf, scause, stval, origin); + + /* Per-trap trace from user mode. Stored at KLOG_DEBUG / KLOG_SUBSYS_TRAP + * so it lives in the ring buffer for postmortem but stays off the console + * by default. No more in-band 20-trap window — flip console_level or + * dump the ring on demand instead. */ + if (origin == TrapOrigin::FromUser) { + pr_debug_s(KLOG_SUBSYS_TRAP, + "trap: cause=%u epc=%llx val=%llx\n", + static_cast(info.cause), info.epc, info.val); + } + + /* Save trapframe in TCB for syscall handlers (like fork) */ + TaskControlBlock* curr = current_task(); + if (curr) { + curr->trapframe = tf; + } + + /* RISC-V ecall fix: ecall does not increment sepc. + * Advance epc so sret returns to the instruction AFTER ecall. */ + if (info.cause == TrapCause::Syscall) { + tf->epc += 4; + info.epc = tf->epc; + } + + /* RISC-V ebreak fix: advance past the C.EBREAK instruction (2 bytes). + * This is arch-specific because instruction size depends on the ISA. */ + if (info.cause == TrapCause::Breakpoint) { + tf->epc += 2; + info.epc = tf->epc; + } + + /* Call MI-layer dispatcher (zero inline assembly from here on) */ + trap_handle(tf, info); + + /* Return to user mode. + * Set stvec back to trap_vector for the next user-mode trap, + * then jump to userret which restores registers and does sret. */ + arch::trap::set_vector(reinterpret_cast(&trap_vector)); + userret(tf); + __builtin_unreachable(); +} + +/* ======================================================================== + * kernel_trap_handler — traps taken while already in S-mode + * + * For Phase 2, only timer and external interrupts are expected. + * Exceptions in S-mode are always fatal. + * + * This belongs in MD because it reads scause directly. + * ======================================================================== */ + +extern "C" void kernel_trap_handler(arch::TrapFrame* tf) +{ + uint64 scause = arch::trap::get_cause(); + uint64 stval = arch::trap::get_value(); + + if (scause == EXC_LOAD_PAGE_FAULT || scause == EXC_STORE_PAGE_FAULT) { + uint64 fixup = search_extable(tf->epc); + if (fixup != 0) { + /* Exception table entry found: redirect execution to fixup code */ + tf->epc = fixup; + return; + } + } + + TrapInfo info = build_trap_info(tf, scause, stval, TrapOrigin::FromKernel); + trap_handle(tf, info); +} + +/* kernelvec assembly is in trap_vector.S */ +extern "C" void kernelvec(); + +/* ======================================================================== + * trap_init() — BSP: set stvec, init IrqManager + * ======================================================================== */ + +void trap_init() +{ + g_irq_manager.init(); + arch::trap::set_vector(reinterpret_cast(&kernelvec)); +} + +/* ======================================================================== + * trap_init_ap() — AP: set this CPU's stvec + * ======================================================================== */ + +void trap_init_ap() +{ + arch::trap::set_vector(reinterpret_cast(&kernelvec)); +} + diff --git a/arch/riscv/trap/trap_vector.S b/arch/riscv/trap/trap_vector.S new file mode 100644 index 0000000..8e45cae --- /dev/null +++ b/arch/riscv/trap/trap_vector.S @@ -0,0 +1,283 @@ +/* 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 + + /* Step 6.5: restore kernel gp (global pointer). + * The compiler uses gp-relative addressing for global variable access + * (e.g. ld reg, offset(gp)). User mode may have set gp to anything, + * so we MUST reload it before entering any C++ code. */ +.option push +.option norelax + la gp, __global_pointer$ +.option pop + + /* Pass TrapFrame* to C++ handler (a0 = tf) */ + mv a0, tp + + /* Set tp = CPU ID (kernel convention) */ + mv tp, t2 + + /* Step 7: jump to C++ trap_handler */ + jalr t3 + + +/* ======================================================================== + * kernelvec — unified S-mode trap entry point for traps FROM S-MODE + * + * Traps from S-mode already have a valid kernel stack pointer in sp. + * We just allocate a TrapFrame on the stack and save registers. + * ======================================================================== */ + .globl kernelvec + .align 4 +kernelvec: + /* Allocate TrapFrame on stack */ + addi sp, sp, -296 + + /* Save all 31 general-purpose registers into TrapFrame */ + sd ra, TF_RA(sp) + /* Save the OLD sp (before we subtracted 296) */ + addi t0, sp, 296 + sd t0, TF_SP(sp) + sd gp, TF_GP(sp) + sd tp, TF_TP(sp) + sd t0, TF_T0(sp) + sd t1, TF_T1(sp) + sd t2, TF_T2(sp) + sd s0, TF_S0(sp) + sd s1, TF_S1(sp) + sd a0, TF_A0(sp) + sd a1, TF_A1(sp) + sd a2, TF_A2(sp) + sd a3, TF_A3(sp) + sd a4, TF_A4(sp) + sd a5, TF_A5(sp) + sd a6, TF_A6(sp) + sd a7, TF_A7(sp) + sd s2, TF_S2(sp) + sd s3, TF_S3(sp) + sd s4, TF_S4(sp) + sd s5, TF_S5(sp) + sd s6, TF_S6(sp) + sd s7, TF_S7(sp) + sd s8, TF_S8(sp) + sd s9, TF_S9(sp) + sd s10, TF_S10(sp) + sd s11, TF_S11(sp) + sd t3, TF_T3(sp) + sd t4, TF_T4(sp) + sd t5, TF_T5(sp) + sd t6, TF_T6(sp) + + /* Save sstatus and sepc */ + csrr t0, sstatus + sd t0, TF_SSTATUS(sp) + csrr t0, sepc + sd t0, TF_EPC(sp) + + /* Call C++ kernel_trap_handler(TrapFrame* tf) */ + mv a0, sp + call kernel_trap_handler + + /* Restore sstatus and sepc */ + ld t0, TF_SSTATUS(sp) + csrw sstatus, t0 + ld t0, TF_EPC(sp) + csrw sepc, t0 + + /* Restore all 31 general-purpose registers */ + ld ra, TF_RA(sp) + ld gp, TF_GP(sp) + ld tp, TF_TP(sp) + ld t0, TF_T0(sp) + ld t1, TF_T1(sp) + ld t2, TF_T2(sp) + ld s0, TF_S0(sp) + ld s1, TF_S1(sp) + ld a0, TF_A0(sp) + ld a1, TF_A1(sp) + ld a2, TF_A2(sp) + ld a3, TF_A3(sp) + ld a4, TF_A4(sp) + ld a5, TF_A5(sp) + ld a6, TF_A6(sp) + ld a7, TF_A7(sp) + ld s2, TF_S2(sp) + ld s3, TF_S3(sp) + ld s4, TF_S4(sp) + ld s5, TF_S5(sp) + ld s6, TF_S6(sp) + ld s7, TF_S7(sp) + ld s8, TF_S8(sp) + ld s9, TF_S9(sp) + ld s10, TF_S10(sp) + ld s11, TF_S11(sp) + ld t3, TF_T3(sp) + ld t4, TF_T4(sp) + ld t5, TF_T5(sp) + ld t6, TF_T6(sp) + + /* Restore sp last */ + ld sp, TF_SP(sp) + + sret + +/* + * 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/arch/riscv/uaccess/uaccess_memcpy.S b/arch/riscv/uaccess/uaccess_memcpy.S new file mode 100644 index 0000000..615680d --- /dev/null +++ b/arch/riscv/uaccess/uaccess_memcpy.S @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include + +/* + * extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len); + * + * a0: dst + * a1: src + * a2: len + * Returns: 0 on success, -EFAULT (-14) on page fault. + */ +.global __uaccess_memcpy +.type __uaccess_memcpy, @function +__uaccess_memcpy: + /* If len == 0, return 0 */ + beqz a2, .Lmemcpy_done + + /* Enable SUM (Supervisor User Memory) bit: sstatus.SUM is bit 18 */ + li t0, (1 << 18) + csrs sstatus, t0 + +.Lmemcpy_loop: + /* Load byte from src */ +1: lb t1, 0(a1) + /* Store byte to dst */ +2: sb t1, 0(a0) + + addi a0, a0, 1 + addi a1, a1, 1 + addi a2, a2, -1 + bnez a2, .Lmemcpy_loop + +.Lmemcpy_done: + /* Disable SUM */ + li t0, (1 << 18) + csrc sstatus, t0 + li a0, 0 + ret + +.Lmemcpy_fault: + /* Fixup routine on fault */ + /* Disable SUM */ + li t0, (1 << 18) + csrc sstatus, t0 + li a0, -14 /* -EFAULT */ + ret + +/* Exception table entries */ +EXTABLE(1b, .Lmemcpy_fault) +EXTABLE(2b, .Lmemcpy_fault) + + +/* + * extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len); + * + * a0: dst + * a1: src + * a2: max_len + * Returns: number of bytes copied (excluding null), or -EFAULT (-14) on page fault. + */ +.global __uaccess_strncpy +.type __uaccess_strncpy, @function +__uaccess_strncpy: + /* If max_len == 0, return 0 */ + beqz a2, .Lstrncpy_done_zero + + /* Enable SUM */ + li t0, (1 << 18) + csrs sstatus, t0 + + /* t2 will store the length copied */ + li t2, 0 + +.Lstrncpy_loop: + /* Load byte from src */ +3: lb t1, 0(a1) + /* Store byte to dst */ +4: sb t1, 0(a0) + + /* If it was the null terminator, we're done */ + beqz t1, .Lstrncpy_done + + addi a0, a0, 1 + addi a1, a1, 1 + addi t2, t2, 1 + addi a2, a2, -1 + bnez a2, .Lstrncpy_loop + + /* Reached max_len without finding null */ + /* We must null-terminate at max_len - 1 as per typical strncpy_from_user semantics */ + addi a0, a0, -1 + sb zero, 0(a0) + addi t2, t2, -1 /* length of string without the forced null terminator */ + +.Lstrncpy_done: + /* Disable SUM */ + li t0, (1 << 18) + csrc sstatus, t0 + mv a0, t2 + ret + +.Lstrncpy_done_zero: + li a0, 0 + ret + +.Lstrncpy_fault: + /* Fixup routine on fault */ + li t0, (1 << 18) + csrc sstatus, t0 + li a0, -14 /* -EFAULT */ + ret + +/* Exception table entries */ +EXTABLE(3b, .Lstrncpy_fault) +EXTABLE(4b, .Lstrncpy_fault) diff --git a/drivers/base/bus.cc b/drivers/base/bus.cc new file mode 100644 index 0000000..975f676 --- /dev/null +++ b/drivers/base/bus.cc @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * drivers/base/bus.cc — bus_type registration. + * + * Mirrors Linux drivers/base/bus.c. Trivial in this iteration: + * initialize the device/driver lists and the per-bus spinlock. + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include +#include +#include + +int bus_register(bus_type* bus) +{ + if (bus == nullptr || bus->name == nullptr) { + return -EINVAL; + } + if (bus->registered) { + return 0; + } + bus->devices.init(); + bus->drivers.init(); + bus->lock.init(bus->name); + bus->registered = true; + kprintf("[bus] '%s' registered\n", bus->name); + return 0; +} diff --git a/drivers/base/core.cc b/drivers/base/core.cc new file mode 100644 index 0000000..f92b2a4 --- /dev/null +++ b/drivers/base/core.cc @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * drivers/base/core.cc — device core. + * + * Mirrors Linux drivers/base/core.c (greatly simplified). Provides + * device_initialize, device_register, device_unregister, and the + * try_bind helper shared with driver registration. + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* device ktype — generic release (no-op; subclasses override) */ +/* ------------------------------------------------------------------ */ + +static void device_release(kobject* kobj) +{ + /* Concrete device flavors (platform_device, virtio_device, ...) are + * expected to allocate via kmalloc and free in their own release + * paths after device_unregister + final kobject_put. The generic + * device kobj_type only fires for stack-allocated devices used in + * tests — nothing to do. */ + (void)kobj; +} + +static const kobj_type device_ktype = { + .release = device_release, +}; + +/* ------------------------------------------------------------------ */ +/* device_initialize / device_register */ +/* ------------------------------------------------------------------ */ + +void device_initialize(device* dev) +{ + if (dev == nullptr) { + return; + } + kobject_init(&dev->kobj, &device_ktype); + dev->parent = nullptr; + dev->driver = nullptr; + dev->driver_data = nullptr; + dev->platform_data = nullptr; + dev->devt = 0; + dev->bus_list.init(); + dev->driver_list.init(); +} + +const char* dev_name(const device* dev) +{ + if (dev == nullptr) { + return "(null)"; + } + if (dev->kobj.name != nullptr) { + return dev->kobj.name; + } + if (dev->init_name != nullptr) { + return dev->init_name; + } + return "(unnamed)"; +} + +/* try_bind: attempt to match `dev` against `drv` and probe. + * Returns 1 on successful bind, 0 otherwise. + * Caller must hold bus->lock. */ +int device_try_bind(device* dev, device_driver* drv); + +int device_try_bind(device* dev, device_driver* drv) +{ + bus_type* bus = dev->bus; + + if (bus->match != nullptr && bus->match(dev, drv) == 0) { + return 0; + } + + dev->driver = drv; + + int rc = 0; + if (bus->probe != nullptr) { + rc = bus->probe(dev); + } else if (drv->probe != nullptr) { + rc = drv->probe(dev); + } + + if (rc == 0) { + list_add_tail(&dev->driver_list, &drv->devices); + return 1; + } + dev->driver = nullptr; + return 0; +} + +int device_register(device* dev) +{ + if (dev == nullptr || dev->bus == nullptr) { + return -EINVAL; + } + if (!dev->bus->registered) { + kprintf("[device] register '%s' fail: bus '%s' not registered\n", + dev_name(dev), + dev->bus->name != nullptr ? dev->bus->name : "?"); + return -ENODEV; + } + + /* device_initialize is idempotent — safe to call again, but most + * paths will already have initialized. Detect via ktype set. */ + if (dev->kobj.ktype == nullptr) { + device_initialize(dev); + } + if (dev->kobj.name == nullptr) { + kobject_add(&dev->kobj, nullptr, dev->init_name); + } + dev->bus_list.init(); + dev->driver_list.init(); + + LockGuard g(dev->bus->lock); + list_add_tail(&dev->bus_list, &dev->bus->devices); + + list_node* cur = dev->bus->drivers.next; + while (cur != &dev->bus->drivers) { + device_driver* drv = + list_entry(cur); + cur = cur->next; + if (device_try_bind(dev, drv) != 0) { + break; + } + } + return 0; +} + +void device_unregister(device* dev) +{ + if (dev == nullptr || dev->bus == nullptr) { + return; + } + + LockGuard g(dev->bus->lock); + + if (dev->driver != nullptr) { + if (dev->driver->remove != nullptr) { + dev->driver->remove(dev); + } + list_del_init(&dev->driver_list); + dev->driver = nullptr; + } + list_del_init(&dev->bus_list); +} diff --git a/drivers/base/driver.cc b/drivers/base/driver.cc new file mode 100644 index 0000000..ce15b6d --- /dev/null +++ b/drivers/base/driver.cc @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * drivers/base/driver.cc — driver_register / driver_unregister. + * + * Mirrors Linux drivers/base/driver.c. Registers a device_driver on + * its bus and tries to bind any unbound matching devices. + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include +#include +#include +#include + +/* Implemented in core.cc — bind a device to a driver on its bus. + * Returns 1 on success, 0 on no-match-or-probe-fail. */ +int device_try_bind(device* dev, device_driver* drv); + +int driver_register(device_driver* drv) +{ + if (drv == nullptr || drv->bus == nullptr) { + return -EINVAL; + } + if (!drv->bus->registered) { + kprintf("[driver] register '%s' fail: bus '%s' not registered\n", + drv->name != nullptr ? drv->name : "?", + drv->bus->name != nullptr ? drv->bus->name : "?"); + return -ENODEV; + } + + drv->bus_node.init(); + drv->devices.init(); + + LockGuard g(drv->bus->lock); + list_add_tail(&drv->bus_node, &drv->bus->drivers); + + /* Try to bind any unbound devices already on the bus. */ + list_node* cur = drv->bus->devices.next; + while (cur != &drv->bus->devices) { + device* dev = list_entry(cur); + cur = cur->next; + if (dev->driver == nullptr) { + device_try_bind(dev, drv); + } + } + return 0; +} + +void driver_unregister(device_driver* drv) +{ + if (drv == nullptr || drv->bus == nullptr) { + return; + } + + LockGuard g(drv->bus->lock); + + /* Unbind every device bound to this driver. */ + while (!drv->devices.is_empty()) { + list_node* first = drv->devices.next; + device* dev = list_entry(first); + if (drv->remove != nullptr) { + drv->remove(dev); + } + list_del_init(&dev->driver_list); + dev->driver = nullptr; + } + list_del_init(&drv->bus_node); +} diff --git a/drivers/base/module.mk b/drivers/base/module.mk new file mode 100644 index 0000000..1d8fb05 --- /dev/null +++ b/drivers/base/module.mk @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# Driver model core (mirrors Linux drivers/base/) + +SRCS_CC += drivers/base/bus.cc +SRCS_CC += drivers/base/core.cc +SRCS_CC += drivers/base/driver.cc +SRCS_CC += drivers/base/platform.cc diff --git a/drivers/base/platform.cc b/drivers/base/platform.cc new file mode 100644 index 0000000..65ec21e --- /dev/null +++ b/drivers/base/platform.cc @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * drivers/base/platform.cc — platform bus + FDT-driven device creation. + * + * Mirrors Linux drivers/base/platform.c. Provides: + * - platform_bus — the bus_type instance + * - platform_match — compatible-string matching via of_match_table + * - platform_dispatch — bus->probe forwarder that down-casts and calls + * platform_driver::probe(platform_device*) + * - driver_probe_all() — boot entry: register bus, register linker-set + * drivers, walk FDT, register platform_devices + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include +#include +#include +#include + +/* kmalloc is declared extern "C" in , but mm/slab.cc defines + * it WITHOUT extern "C" (i.e. C++-mangled). Declare locally with C++ + * linkage so the symbol matches what mm/slab.o exports. */ +void* kmalloc(uint32 size); +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Linker-section registry */ +/* ------------------------------------------------------------------ */ + +extern "C" { + extern platform_driver* __drivers_start[]; + extern platform_driver* __drivers_end[]; +} + +/* ------------------------------------------------------------------ */ +/* platform_bus_type */ +/* ------------------------------------------------------------------ */ + +static int platform_match(device* dev, device_driver* drv) +{ + platform_device* pdev = to_platform_device(dev); + platform_driver* pdrv = to_platform_driver(drv); + + if (pdrv->of_match_table == nullptr || pdev->of_node == nullptr) { + return 0; + } + const char* node_compat = pdev->of_node->get_prop_string("compatible"); + if (node_compat == nullptr) { + return 0; + } + for (const of_device_id* id = pdrv->of_match_table; + id->compatible != nullptr; ++id) { + if (strcmp(id->compatible, node_compat) == 0) { + return 1; + } + } + return 0; +} + +static int platform_dispatch(device* dev) +{ + platform_device* pdev = to_platform_device(dev); + platform_driver* pdrv = to_platform_driver(dev->driver); + if (pdrv->probe == nullptr) { + return 0; + } + return pdrv->probe(pdev); +} + +bus_type platform_bus = { + .name = "platform", + .match = platform_match, + .probe = platform_dispatch, + .devices = {}, + .drivers = {}, + .lock = {}, + .registered = false, +}; + +void platform_bus_init() +{ + bus_register(&platform_bus); +} + +/* ------------------------------------------------------------------ */ +/* platform_driver_register / platform_device_register */ +/* ------------------------------------------------------------------ */ + +int platform_driver_register(platform_driver* pdrv) +{ + if (pdrv == nullptr) { + return -EINVAL; + } + pdrv->driver.bus = &platform_bus; + /* driver.probe is unused — we route through bus->probe. Set name + * from the first of_match if not already provided. */ + if (pdrv->driver.name == nullptr && pdrv->of_match_table != nullptr) { + pdrv->driver.name = pdrv->of_match_table[0].compatible; + } + return driver_register(&pdrv->driver); +} + +int platform_device_register(platform_device* pdev) +{ + if (pdev == nullptr) { + return -EINVAL; + } + pdev->dev.bus = &platform_bus; + if (pdev->dev.init_name == nullptr && pdev->of_node != nullptr) { + pdev->dev.init_name = pdev->of_node->get_prop_string("compatible"); + } + device_initialize(&pdev->dev); + return device_register(&pdev->dev); +} + +/* ------------------------------------------------------------------ */ +/* FDT-driven device instantiation */ +/* ------------------------------------------------------------------ */ + +static platform_device* alloc_platform_device_from_node(DeviceNode* node, + const char* compat) +{ + auto* pdev = static_cast( + kmalloc(static_cast(sizeof(platform_device)))); + if (pdev == nullptr) { + return nullptr; + } + memset(pdev, 0, sizeof(*pdev)); + pdev->of_node = node; + pdev->mmio_base = node->get_prop_u64("reg", 0); + /* "reg" second cell is size — for now we don't bother parsing it + * separately; drivers that need it pull from of_node directly. */ + pdev->mmio_size = 0; + pdev->irq = -1; + pdev->dev.init_name = compat; + return pdev; +} + +/* For each registered platform_driver, walk its of_match_table and + * spawn a platform_device for each FDT compatible match found. + * + * NOTE: This is a simplified FDT walker — it only finds the FIRST FDT + * node matching each compatible string (delegating to + * fdt_find_compatible). Devices with multiple FDT instances of the + * same compatible will only see one bound here. A full FDT tree walk + * is left for when virtio-mmio or multi-instance UART arrives. + */ +static void instantiate_devices_for_driver(platform_driver* pdrv) +{ + if (pdrv == nullptr || pdrv->of_match_table == nullptr) { + return; + } + for (const of_device_id* id = pdrv->of_match_table; + id->compatible != nullptr; ++id) { + DeviceNode* node = fdt_find_compatible(id->compatible); + if (node == nullptr) { + continue; + } + platform_device* pdev = + alloc_platform_device_from_node(node, id->compatible); + if (pdev == nullptr) { + kprintf("[platform] OOM allocating pdev for %s\n", id->compatible); + continue; + } + kprintf("[platform] instantiating '%s' -> pdev mmio=0x%llx\n", + id->compatible, pdev->mmio_base); + platform_device_register(pdev); + } +} + +/* ------------------------------------------------------------------ */ +/* Boot entry — replaces the old driver_probe_all() */ +/* ------------------------------------------------------------------ */ + +void driver_probe_all() +{ + kprintf("[dpa] step 1: &platform_bus=%p (link 0xffffffc08033a0a8)\n", + &platform_bus); + kprintf("[dpa] step 1: pre-touch platform_bus.name slot...\n"); + const char** name_slot = &platform_bus.name; + kprintf("[dpa] step 1: name_slot=%p\n", name_slot); + kprintf("[dpa] step 1: about to deref *name_slot\n"); + const char* nm = *name_slot; + kprintf("[dpa] step 1: *name_slot=%p\n", nm); + kprintf("[dpa] step 1: about to read first byte of nm\n"); + char first = nm != nullptr ? nm[0] : '!'; + kprintf("[dpa] step 1: nm[0]='%c'\n", first); + + kprintf("[dpa] step 1: platform_bus_init enter\n"); + platform_bus_init(); + kprintf("[dpa] step 1: platform_bus_init done\n"); + + platform_driver** start = __drivers_start; + platform_driver** end = __drivers_end; + kprintf("[dpa] step 2: __drivers_start=%p __drivers_end=%p count=%lld\n", + start, end, (long long)(end - start)); + + for (platform_driver** slot = start; slot < end; ++slot) { + kprintf("[dpa] step 2: slot=%p *slot=%p\n", slot, *slot); + if (*slot != nullptr) { + platform_driver* pdrv = *slot; + kprintf("[dpa] step 2: pdrv->of_match_table=%p\n", + pdrv->of_match_table); + if (pdrv->of_match_table != nullptr) { + kprintf("[dpa] step 2: pdrv->of_match_table[0].compatible=%p\n", + pdrv->of_match_table[0].compatible); + if (pdrv->of_match_table[0].compatible != nullptr) { + kprintf("[dpa] step 2: compatible='%s'\n", + pdrv->of_match_table[0].compatible); + } + } + platform_driver_register(pdrv); + kprintf("[dpa] step 2: registered\n"); + } + } + + kprintf("[dpa] step 3: instantiate devices\n"); + for (platform_driver** slot = start; slot < end; ++slot) { + if (*slot != nullptr) { + instantiate_devices_for_driver(*slot); + } + } + kprintf("[dpa] done\n"); +} diff --git a/drivers/console.cc b/drivers/console.cc new file mode 100644 index 0000000..f741dd2 --- /dev/null +++ b/drivers/console.cc @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * console.cc — Console character device (/dev/console) + * + * Wraps the existing early_putc() UART driver as a file_operations + * chardev. read() is a stub for now (returns 0 = EOF). + * write() sends each byte through early_putc(). + */ + +#include +#include +#include + +extern "C" void early_putc(char c); + +/* ================================================================== */ +/* console_write */ +/* ================================================================== */ + +static int64 console_write(file* fp, const char* buf, uint64 count, int64* pos) +{ + (void)fp; + (void)pos; + + for (uint64 i = 0; i < count; i++) { + early_putc(buf[i]); + } + + return static_cast(count); +} + +/* ================================================================== */ +/* console_read — stub (returns 0 = EOF for now) */ +/* ================================================================== */ + +static int64 console_read(file* fp, char* buf, uint64 count, int64* pos) +{ + (void)fp; + (void)buf; + (void)count; + (void)pos; + /* TODO: implement UART RX polling/IRQ-based blocking read */ + return 0; +} + +/* ================================================================== */ +/* null device — /dev/null */ +/* ================================================================== */ + +static int64 null_read(file* /*fp*/, char* /*buf*/, uint64 /*count*/, int64* /*pos*/) +{ + return 0; /* EOF */ +} + +static int64 null_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/) +{ + return static_cast(count); /* discard */ +} + +/* ================================================================== */ +/* zero device — /dev/zero */ +/* ================================================================== */ + +static int64 zero_read(file* /*fp*/, char* buf, uint64 count, int64* /*pos*/) +{ + for (uint64 i = 0; i < count; i++) { + buf[i] = 0; + } + return static_cast(count); +} + +static int64 zero_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/) +{ + return static_cast(count); /* discard */ +} + +/* ================================================================== */ +/* Registration — runtime init (no static initializers!) */ +/* ================================================================== */ + +static file_operations console_fops; +static file_operations null_fops; +static file_operations zero_fops; + +static cdev console_cdev; +static cdev null_cdev; +static cdev zero_cdev; + +void console_cdev_init() +{ + /* Console: major=5, minor=1 */ + console_fops.read = console_read; + console_fops.write = console_write; + + console_cdev.name = "console"; + console_cdev.dev = MKDEV(5, 1); + console_cdev.fops = &console_fops; + cdev_register(&console_cdev); + + /* /dev/null: major=1, minor=3 */ + null_fops.read = null_read; + null_fops.write = null_write; + + null_cdev.name = "null"; + null_cdev.dev = MKDEV(1, 3); + null_cdev.fops = &null_fops; + cdev_register(&null_cdev); + + /* /dev/zero: major=1, minor=5 */ + zero_fops.read = zero_read; + zero_fops.write = zero_write; + + zero_cdev.name = "zero"; + zero_cdev.dev = MKDEV(1, 5); + zero_cdev.fops = &zero_fops; + cdev_register(&zero_cdev); +} diff --git a/drivers/module.mk b/drivers/module.mk new file mode 100644 index 0000000..9326f30 --- /dev/null +++ b/drivers/module.mk @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# Drivers module + +SRCS_CC += drivers/uart.cc +SRCS_CC += drivers/console.cc + +ifeq ($(ARCH),riscv) +SRCS_CC += drivers/plic.cc +endif diff --git a/drivers/plic.cc b/drivers/plic.cc new file mode 100644 index 0000000..2818700 --- /dev/null +++ b/drivers/plic.cc @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * plic.cc — Platform-Level Interrupt Controller (RISC-V) + * + * QEMU virt platform PLIC: + * Base PA: 0x0C000000 + * Size: 0x04000000 (64MB) + * + * Register layout (per RISC-V PLIC spec): + * 0x000000: Priority registers (source 0..1023, 4 bytes each) + * 0x001000: Pending bits (source 0..1023, bit per source) + * 0x002000: Enable bits per context + * 0x200000: Threshold + claim/complete per context + * + * Context mapping for QEMU virt: + * Context 2*hartid + 1 = S-mode context for hart N + * + * Reference: docs/specs/trap.md §5.1 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PLIC virtual base (after MMIO mapping) */ +static uint64 plic_va; + +extern "C" uint32 bsp_hart_id; + +/* PLIC register offsets */ +static constexpr uint64 PLIC_PRIORITY_BASE = 0x000000; +static constexpr uint64 PLIC_PENDING_BASE = 0x001000; +static constexpr uint64 PLIC_ENABLE_BASE = 0x002000; +static constexpr uint64 PLIC_THRESHOLD_BASE = 0x200000; +static constexpr uint64 PLIC_CLAIM_BASE = 0x200004; + +/* Each context's enable/threshold/claim region is 0x80 bytes apart for enable, + * and 0x1000 bytes apart for threshold/claim. */ +static constexpr uint64 PLIC_ENABLE_STRIDE = 0x80; +static constexpr uint64 PLIC_CONTEXT_STRIDE = 0x1000; + +/* Dynamic S-mode context mapping */ +static uint32 g_s_context[kMaxCpus]; + +static inline uint64 s_context(uint64 hart_id) { + if (hart_id < kMaxCpus) + return g_s_context[hart_id]; + return 2 * hart_id + 1; /* safe fallback */ +} + +static bool str_equals_plic(const char *a, const char *b) { + if (!a || !b) + return false; + while (*a && *a == *b) { + a++; + b++; + } + return *a == *b; +} + +/* Read a 32-bit MMIO register */ +static inline uint32 plic_read32(uint64 offset) { + return *reinterpret_cast(plic_va + offset); +} + +/* Write a 32-bit MMIO register */ +static inline void plic_write32(uint64 offset, uint32 val) { + *reinterpret_cast(plic_va + offset) = val; +} + +/* ======================================================================== + * Public API + * ======================================================================== */ + +static void plic_probe(DeviceNode* node) { + uint64 plic_pa = 0; + + /* Initialize default fallback mapping */ + for (uint32 i = 0; i < kMaxCpus; i++) { + g_s_context[i] = 2 * i + 1; + } + + /* Dynamically discover PLIC base address and context mapping from FDT */ + if (node) { + plic_pa = node->get_prop_u64("reg", plic_pa); + + /* Build phandle to hart_id map */ + struct PhandleMap { + uint32 phandle; + uint32 hart_id; + } pmap[kMaxCpus]; + int num_cpus = 0; + + DeviceNode *cpus_node = fdt_get_node_by_path("/cpus"); + if (cpus_node) { + list_node *cur = cpus_node->children_list_head(); + list_node *end = cpus_node->children_list_sentinel(); + while (cur != end && num_cpus < kMaxCpus) { + DeviceNode *cpu = + list_entry(cur); + cur = cur->next; + + const char *type = cpu->get_prop_string("device_type"); + if (type && str_equals_plic(type, "cpu")) { + uint32 hart_id = cpu->get_prop_u32("reg"); + DeviceNode *intc = cpu->find_child("interrupt-controller"); + if (intc) { + uint32 phandle = intc->get_prop_u32("phandle"); + if (phandle == 0) + phandle = intc->get_prop_u32("linux,phandle"); + if (phandle != 0) { + pmap[num_cpus].phandle = phandle; + pmap[num_cpus].hart_id = hart_id; + num_cpus++; + } + } + } + } + } + + /* Parse interrupts-extended */ + const FdtProperty *ie_prop = node->get_prop("interrupts-extended"); + if (ie_prop && ie_prop->len > 0) { + uint32 len = ie_prop->len; + const uint8 *val = static_cast(ie_prop->value); + int context_idx = 0; + /* Each entry is 2 cells (8 bytes): [phandle, interrupt_type] */ + for (uint32 i = 0; i + 7 < len; i += 8) { + uint32 phandle = (uint32(val[i]) << 24) | (uint32(val[i + 1]) << 16) | + (uint32(val[i + 2]) << 8) | val[i + 3]; + uint32 irq_type = (uint32(val[i + 4]) << 24) | + (uint32(val[i + 5]) << 16) | + (uint32(val[i + 6]) << 8) | val[i + 7]; + + if (irq_type == 9) { /* IRQ_S_EXTERNAL */ + for (int h = 0; h < num_cpus; h++) { + if (pmap[h].phandle == phandle) { + if (pmap[h].hart_id < kMaxCpus) { + g_s_context[pmap[h].hart_id] = context_idx; + } + break; + } + } + } + context_idx++; + } + } + } + + if (plic_pa == 0) return; + + /* Map PLIC MMIO */ + vmm_map_kernel_mmio(plic_pa, 0x4000000); + plic_va = pa_to_va(phys_addr(plic_pa)).raw; + + /* Use the actual BSP hart ID (it may not be hart 0) */ + uint64 ctx = s_context(bsp_hart_id); + + /* Set priority for all sources to 1 (minimum non-zero = enabled) */ + for (uint32 irq = 1; irq < IrqManager::kMaxIrqs; irq++) { + plic_write32(PLIC_PRIORITY_BASE + irq * 4, 1); + } + + /* Set threshold to 0 for BSP context (accept all priorities > 0) */ + plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); +} + +static void plic_init_ap() { + uint64 ctx = s_context(arch::cpu::id()); + + /* Set threshold to 0 for this AP's context */ + plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); +} + +static void plic_enable(uint32 irq) { + if (irq == 0 || irq >= IrqManager::kMaxIrqs) { + return; + } + + uint64 ctx = s_context(arch::cpu::id()); + uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; + uint32 word_index = irq / 32; + uint32 bit_index = irq % 32; + + uint32 val = plic_read32(offset + word_index * 4); + val |= (1U << bit_index); + plic_write32(offset + word_index * 4, val); +} + +static void plic_disable(uint32 irq) { + if (irq == 0 || irq >= IrqManager::kMaxIrqs) { + return; + } + + uint64 ctx = s_context(arch::cpu::id()); + uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; + uint32 word_index = irq / 32; + uint32 bit_index = irq % 32; + + uint32 val = plic_read32(offset + word_index * 4); + val &= ~(1U << bit_index); + plic_write32(offset + word_index * 4, val); +} + +static void plic_set_priority(uint32 irq, uint32 priority) { + if (irq == 0 || irq >= IrqManager::kMaxIrqs) + return; + plic_write32(PLIC_PRIORITY_BASE + (irq * 4), priority); +} + +static uint32 plic_claim() { + uint64 ctx = s_context(arch::cpu::id()); + return plic_read32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE); +} + +static void plic_complete(uint32 irq) { + uint64 ctx = s_context(arch::cpu::id()); + plic_write32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE, irq); +} + +static const IrqChip plic_chip = { + "riscv,plic0", + plic_claim, + plic_complete, + plic_init_ap, + plic_enable, + plic_disable, + plic_set_priority +}; + +static int plic_platform_probe(platform_device* pdev) { + plic_probe(pdev->of_node); + g_irq_manager.set_chip(&plic_chip); + return 0; +} + +static const of_device_id plic_of_match[] = { + { .compatible = "riscv,plic0", .data = nullptr }, + { .compatible = nullptr, .data = nullptr }, /* sentinel */ +}; + +platform_driver plic_driver = { + .driver = { .name = "plic", .bus = nullptr, + .probe = nullptr, .remove = nullptr, + .bus_node = {}, .devices = {} }, + .probe = plic_platform_probe, + .remove = nullptr, + .of_match_table = plic_of_match, +}; + +PLATFORM_DRIVER(plic_driver); diff --git a/drivers/tty/module.mk b/drivers/tty/module.mk new file mode 100644 index 0000000..49190d4 --- /dev/null +++ b/drivers/tty/module.mk @@ -0,0 +1,5 @@ +# drivers/tty/module.mk — TTY subsystem build rules + +SRCS_CC += drivers/tty/tty_core.cc +SRCS_CC += drivers/tty/n_tty.cc +SRCS_CC += drivers/tty/tty_uart.cc diff --git a/drivers/tty/n_tty.cc b/drivers/tty/n_tty.cc new file mode 100644 index 0000000..0e312b0 --- /dev/null +++ b/drivers/tty/n_tty.cc @@ -0,0 +1,539 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * n_tty.cc — N_TTY line discipline + * + * Implements the standard terminal line discipline: + * + * Canonical mode (ICANON): + * Input is buffered until newline/EOF. Backspace (VERASE) and + * kill-line (VKILL) editing are supported. + * + * Raw mode (!ICANON): + * Characters are available immediately (subject to VMIN/VTIME). + * + * Signal generation (ISIG): + * VINTR → SIGINT, VQUIT → SIGQUIT, VSUSP → SIGTSTP + * + * Echo (ECHO): + * Typed characters are reflected to output. + * + * Input processing (c_iflag): + * ICRNL: CR → NL mapping + * INLCR: NL → CR mapping + * IGNCR: ignore CR + * ISTRIP: strip high bit + * + * Output processing (c_oflag): + * OPOST + ONLCR: NL → CR+NL on output + * + * Reference: Linux drivers/tty/n_tty.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENOIOCTLCMD +#define ENOIOCTLCMD 515 +#endif + +extern void schedule(); + +/* ================================================================== */ +/* N_TTY internal data */ +/* ================================================================== */ + +inline constexpr int N_TTY_BUF_SIZE = 4096; + +struct n_tty_data { + char read_buf[N_TTY_BUF_SIZE]; + int read_head; /* producer writes here (from receive_buf) */ + int read_tail; /* consumer reads from here (from read()) */ + int canon_head; /* in canonical mode: end of last complete line */ + bool eof_pending; /* Ctrl+D was pressed */ + Spinlock lock; +}; + +static inline int buf_count(const n_tty_data* d) +{ + return (d->read_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; +} + +static inline int canon_count(const n_tty_data* d) +{ + return (d->canon_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; +} + +static inline bool buf_full(const n_tty_data* d) +{ + return buf_count(d) >= N_TTY_BUF_SIZE - 1; +} + +static inline void buf_put(n_tty_data* d, char c) +{ + d->read_buf[d->read_head] = c; + d->read_head = (d->read_head + 1) % N_TTY_BUF_SIZE; +} + +static inline char buf_get(n_tty_data* d) +{ + char c = d->read_buf[d->read_tail]; + d->read_tail = (d->read_tail + 1) % N_TTY_BUF_SIZE; + return c; +} + +/* ================================================================== */ +/* Output helpers */ +/* ================================================================== */ + +static void tty_put_char(tty_struct* tty, char c) +{ + if (tty->driver && tty->driver->ops && tty->driver->ops->put_char) { + tty->driver->ops->put_char(tty, c); + } +} + +/* + * Process and output a single character, applying c_oflag transformations. + */ +static void tty_output_char(tty_struct* tty, char c) +{ + if (tty->termios_data.c_oflag & OPOST) { + if (c == '\n' && (tty->termios_data.c_oflag & ONLCR)) { + tty_put_char(tty, '\r'); + tty_put_char(tty, '\n'); + return; + } + if (c == '\r' && (tty->termios_data.c_oflag & OCRNL)) { + tty_put_char(tty, '\n'); + return; + } + } + tty_put_char(tty, c); +} + +/* + * Echo a character back to the terminal. + * Control characters are displayed as ^X. + */ +static void echo_char(tty_struct* tty, char c) +{ + if (!(tty->termios_data.c_lflag & ECHO)) + return; + + if (c == '\n') { + tty_output_char(tty, '\n'); + return; + } + + if (c == '\t') { + tty_put_char(tty, '\t'); + return; + } + + /* Control characters: echo as ^X */ + if ((tty->termios_data.c_lflag & ECHOCTL) && c < 0x20 && c != '\t' && c != '\n') { + tty_put_char(tty, '^'); + tty_put_char(tty, c + '@'); + return; + } + + if (c == 0x7f) { /* DEL */ + tty_put_char(tty, '^'); + tty_put_char(tty, '?'); + return; + } + + tty_output_char(tty, c); +} + +/* + * Erase one character visually (backspace + space + backspace). + */ +static void erase_char(tty_struct* tty, char erased) +{ + if (!(tty->termios_data.c_lflag & ECHO)) + return; + + if ((tty->termios_data.c_lflag & ECHOE)) { + /* If the erased char was a control char displayed as ^X, erase 2 cols */ + if ((erased < 0x20 && erased != '\t' && erased != '\n') || erased == 0x7f) { + tty_put_char(tty, '\b'); + tty_put_char(tty, ' '); + tty_put_char(tty, '\b'); + tty_put_char(tty, '\b'); + tty_put_char(tty, ' '); + tty_put_char(tty, '\b'); + } else { + tty_put_char(tty, '\b'); + tty_put_char(tty, ' '); + tty_put_char(tty, '\b'); + } + } +} + +/* ================================================================== */ +/* Input processing — receive_buf (called from IRQ or driver) */ +/* ================================================================== */ + +static void n_tty_receive_buf(tty_struct* tty, const char* cp, int count) +{ + auto* d = static_cast(tty->ldisc.data); + if (!d) return; + + const termios& t = tty->termios_data; + + LockGuard guard(d->lock); + + for (int i = 0; i < count; i++) { + char c = cp[i]; + + /* Strip high bit if requested */ + if (t.c_iflag & ISTRIP) + c &= 0x7f; + + /* Input CR/NL processing */ + if (c == '\r') { + if (t.c_iflag & IGNCR) + continue; + if (t.c_iflag & ICRNL) + c = '\n'; + } else if (c == '\n') { + if (t.c_iflag & INLCR) + c = '\r'; + } + + /* Signal generation */ + if (t.c_lflag & ISIG) { + if (c == t.c_cc[VINTR]) { + tty_signal_pgrp(tty, SIGINT); + echo_char(tty, c); + if (!(t.c_lflag & NOFLSH)) { + /* Flush input buffer */ + d->read_head = d->read_tail; + d->canon_head = d->read_tail; + } + continue; + } + if (c == t.c_cc[VQUIT]) { + tty_signal_pgrp(tty, SIGQUIT); + echo_char(tty, c); + if (!(t.c_lflag & NOFLSH)) { + d->read_head = d->read_tail; + d->canon_head = d->read_tail; + } + continue; + } + if (c == t.c_cc[VSUSP]) { + tty_signal_pgrp(tty, SIGTSTP); + echo_char(tty, c); + continue; + } + } + + /* Canonical mode processing */ + if (t.c_lflag & ICANON) { + /* EOF (Ctrl+D) */ + if (c == t.c_cc[VEOF]) { + d->eof_pending = true; + d->canon_head = d->read_head; + tty->read_wait.wake_all(); + continue; + } + + /* ERASE (backspace/DEL) */ + if (c == t.c_cc[VERASE]) { + if (d->read_head != d->canon_head && + d->read_head != d->read_tail) { + int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; + char erased = d->read_buf[prev]; + d->read_head = prev; + erase_char(tty, erased); + } + continue; + } + + /* KILL (Ctrl+U — erase entire line) */ + if (c == t.c_cc[VKILL]) { + while (d->read_head != d->canon_head && + d->read_head != d->read_tail) { + int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; + char erased = d->read_buf[prev]; + d->read_head = prev; + erase_char(tty, erased); + } + if (t.c_lflag & ECHOK) { + tty_output_char(tty, '\n'); + } + continue; + } + + /* WERASE (Ctrl+W — erase word) */ + if ((t.c_lflag & IEXTEN) && c == t.c_cc[VWERASE]) { + /* Skip trailing spaces */ + while (d->read_head != d->canon_head) { + int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; + if (d->read_buf[prev] != ' ' && d->read_buf[prev] != '\t') + break; + erase_char(tty, d->read_buf[prev]); + d->read_head = prev; + } + /* Erase word */ + while (d->read_head != d->canon_head) { + int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; + if (d->read_buf[prev] == ' ' || d->read_buf[prev] == '\t') + break; + erase_char(tty, d->read_buf[prev]); + d->read_head = prev; + } + continue; + } + + /* REPRINT (Ctrl+R) */ + if ((t.c_lflag & IEXTEN) && c == t.c_cc[VREPRINT]) { + tty_output_char(tty, '\n'); + int pos = d->canon_head; + while (pos != d->read_head) { + echo_char(tty, d->read_buf[pos]); + pos = (pos + 1) % N_TTY_BUF_SIZE; + } + continue; + } + + /* Regular character — buffer it */ + if (!buf_full(d)) { + buf_put(d, c); + echo_char(tty, c); + } + + /* Line complete on newline or EOL */ + if (c == '\n' || c == t.c_cc[VEOL] || + (t.c_cc[VEOL2] && c == t.c_cc[VEOL2])) { + d->canon_head = d->read_head; + tty->read_wait.wake_all(); + } + } else { + /* Raw / non-canonical mode */ + if (!buf_full(d)) { + buf_put(d, c); + echo_char(tty, c); + tty->read_wait.wake_all(); + } + } + } +} + +/* ================================================================== */ +/* N_TTY read — blocking read from the input buffer */ +/* ================================================================== */ + +static int n_tty_read(tty_struct* tty, file* fp, char* buf, uint64 count) +{ + auto* d = static_cast(tty->ldisc.data); + if (!d) return -EIO; + + const termios& t = tty->termios_data; + TaskControlBlock* curr = current_task(); + int copied = 0; + + if (t.c_lflag & ICANON) { + /* Canonical mode: block until a complete line is available */ + while (canon_count(d) == 0 && !d->eof_pending) { + if (tty->flags & TTY_IO_ERROR) + return -EIO; + + /* Check for pending signals — return -EINTR so the + * trap return path can deliver them via do_signal() */ + if (signal_pending(curr)) + return -EINTR; + + tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping); + if (canon_count(d) == 0 && !d->eof_pending) + schedule(); + tty->read_wait.finish_wait(curr); + } + + if (d->eof_pending && canon_count(d) == 0) { + d->eof_pending = false; + return 0; /* EOF */ + } + + /* Copy up to one line */ + LockGuard guard(d->lock); + while (copied < static_cast(count) && d->read_tail != d->canon_head) { + buf[copied++] = buf_get(d); + } + } else { + /* Non-canonical mode */ + int vmin = t.c_cc[VMIN]; + /* VTIME not implemented (would need timers) */ + + if (vmin == 0) vmin = 1; + + while (buf_count(d) < vmin) { + if (tty->flags & TTY_IO_ERROR) + return -EIO; + + if (signal_pending(curr)) + return -EINTR; + + tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping); + if (buf_count(d) < vmin) + schedule(); + tty->read_wait.finish_wait(curr); + } + + LockGuard guard(d->lock); + while (copied < static_cast(count) && buf_count(d) > 0) { + buf[copied++] = buf_get(d); + } + } + + return copied; +} + +/* ================================================================== */ +/* N_TTY write — output through the driver with post-processing */ +/* ================================================================== */ + +static int n_tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count) +{ + if (!tty->driver || !tty->driver->ops || !tty->driver->ops->write) + return -EIO; + + /* Apply output processing (OPOST) */ + if (tty->termios_data.c_oflag & OPOST) { + for (uint64 i = 0; i < count; i++) { + tty_output_char(tty, buf[i]); + } + return static_cast(count); + } + + /* Raw output: pass directly to driver */ + return tty->driver->ops->write(tty, buf, static_cast(count)); +} + +/* ================================================================== */ +/* N_TTY ioctl */ +/* ================================================================== */ + +static int n_tty_ioctl(tty_struct* tty, unsigned int cmd, uint64 arg) +{ + auto* d = static_cast(tty->ldisc.data); + + switch (cmd) { + case FIONREAD: { + int count = buf_count(d); + auto* p = reinterpret_cast(arg); + *p = count; + return 0; + } + default: + return -ENOIOCTLCMD; + } +} + +/* ================================================================== */ +/* N_TTY open / close */ +/* ================================================================== */ + +static int n_tty_open(tty_struct* tty) +{ + auto* d = new n_tty_data{}; + if (!d) return -ENOMEM; + + d->read_head = 0; + d->read_tail = 0; + d->canon_head = 0; + d->eof_pending = false; + d->lock.init("n_tty"); + + tty->ldisc.data = d; + return 0; +} + +static void n_tty_close(tty_struct* tty) +{ + auto* d = static_cast(tty->ldisc.data); + if (d) { + delete d; + tty->ldisc.data = nullptr; + } +} + +static void n_tty_flush_buffer(tty_struct* tty) +{ + auto* d = static_cast(tty->ldisc.data); + if (!d) return; + LockGuard guard(d->lock); + d->read_head = 0; + d->read_tail = 0; + d->canon_head = 0; + d->eof_pending = false; +} + +static int n_tty_chars_in_buffer(tty_struct* tty) +{ + auto* d = static_cast(tty->ldisc.data); + if (!d) return 0; + return buf_count(d); +} + +/* + * Probe input readiness for poll(). In canonical mode the read syscall + * blocks until a whole line is queued, so we mirror that: POLLIN only + * when canon_count() shows a finalized line (or pending EOF). In raw + * mode any character satisfies VMIN==1 immediately. Without this + * mirror, ppoll() would falsely signal POLLIN on partial canonical + * input, then read() would block — exactly the busy-loop the shell's + * ppoll-then-read pattern is trying to avoid. + */ +bool n_tty_input_ready(tty_struct* tty) +{ + auto* d = static_cast(tty->ldisc.data); + if (!d) return false; + const termios& t = tty->termios_data; + if (t.c_lflag & ICANON) + return canon_count(d) != 0 || d->eof_pending; + int vmin = t.c_cc[VMIN]; + if (vmin == 0) vmin = 1; + return buf_count(d) >= vmin; +} + +static void n_tty_set_termios(tty_struct* /*tty*/, const termios* /*old*/) +{ + /* Nothing special needed for now */ +} + +static void n_tty_hangup(tty_struct* tty) +{ + n_tty_flush_buffer(tty); + tty->read_wait.wake_all(); +} + +/* ================================================================== */ +/* N_TTY ops table */ +/* ================================================================== */ + +const tty_ldisc_ops n_tty_ops = { + .name = "n_tty", + .num = 0, /* N_TTY */ + .open = n_tty_open, + .close = n_tty_close, + .read = n_tty_read, + .write = n_tty_write, + .receive_buf = n_tty_receive_buf, + .ioctl = n_tty_ioctl, + .flush_buffer = n_tty_flush_buffer, + .chars_in_buffer = n_tty_chars_in_buffer, + .set_termios = n_tty_set_termios, + .hangup = n_tty_hangup, +}; diff --git a/drivers/tty/tty_core.cc b/drivers/tty/tty_core.cc new file mode 100644 index 0000000..5985bd9 --- /dev/null +++ b/drivers/tty/tty_core.cc @@ -0,0 +1,536 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * tty_core.cc — TTY subsystem core + * + * Provides the tty_struct lifecycle, file_operations dispatch, + * and ioctl routing between the line discipline and driver. + * + * Call chain: + * sys_read → tty_fops.read → tty_read → ldisc.read + * sys_write → tty_fops.write → tty_write → ldisc.write + * sys_ioctl → tty_fops.ioctl → tty_ioctl → {core / ldisc / driver} + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENOIOCTLCMD +#define ENOIOCTLCMD 515 +#endif + +/* ================================================================== */ +/* Global console TTY */ +/* ================================================================== */ + +tty_struct* g_console_tty = nullptr; + +/* ================================================================== */ +/* tty_struct allocation */ +/* ================================================================== */ + +tty_struct* tty_alloc(const tty_driver* driver, int index) +{ + auto* tty = new tty_struct{}; + if (!tty) return nullptr; + + tty->index = index; + tty->driver = driver; + tty->link = nullptr; + tty->pgrp = 0; + tty->session = 0; + tty->write_cnt = 0; + tty->flags = 0; + tty->open_count = 0; + tty->driver_data = nullptr; + + tty_default_termios(&tty->termios_data); + + tty->ws.ws_row = 24; + tty->ws.ws_col = 80; + tty->ws.ws_xpixel = 0; + tty->ws.ws_ypixel = 0; + + tty->lock.init("tty"); + tty->read_wait.init("tty_rd"); + tty->write_wait.init("tty_wr"); + + /* Attach default line discipline (N_TTY) */ + tty->ldisc.ops = &n_tty_ops; + tty->ldisc.data = nullptr; + + return tty; +} + +/* ================================================================== */ +/* tty_open / tty_close */ +/* ================================================================== */ + +int tty_open(tty_struct* tty) +{ + if (!tty) return -EINVAL; + + if (tty->open_count == 0) { + /* Initialize line discipline */ + if (tty->ldisc.ops && tty->ldisc.ops->open) { + int ret = tty->ldisc.ops->open(tty); + if (ret < 0) return ret; + } + + /* Initialize driver */ + if (tty->driver && tty->driver->ops && tty->driver->ops->open) { + int ret = tty->driver->ops->open(tty); + if (ret < 0) { + if (tty->ldisc.ops && tty->ldisc.ops->close) + tty->ldisc.ops->close(tty); + return ret; + } + } + } + + tty->open_count++; + return 0; +} + +void tty_close(tty_struct* tty) +{ + if (!tty) return; + + tty->open_count--; + if (tty->open_count <= 0) { + tty->open_count = 0; + + if (tty->driver && tty->driver->ops && tty->driver->ops->close) + tty->driver->ops->close(tty); + + if (tty->ldisc.ops && tty->ldisc.ops->close) + tty->ldisc.ops->close(tty); + } +} + +/* ================================================================== */ +/* Core I/O */ +/* ================================================================== */ + +int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count) +{ + if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->read) + return -EIO; + + return tty->ldisc.ops->read(tty, fp, buf, count); +} + +int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count) +{ + if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->write) + return -EIO; + + return tty->ldisc.ops->write(tty, fp, buf, count); +} + +/* ================================================================== */ +/* tty_receive_buf — called by driver on RX */ +/* ================================================================== */ + +void tty_receive_buf(tty_struct* tty, const char* buf, int count) +{ + if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->receive_buf) + return; + + tty->ldisc.ops->receive_buf(tty, buf, count); +} + +/* ================================================================== */ +/* tty_signal_pgrp — send signal to foreground process group */ +/* ================================================================== */ + +extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); + +void tty_signal_pgrp(tty_struct* tty, int sig) +{ + if (!tty) return; + + int32 pgid; + { + LockGuard guard(tty->lock); + pgid = tty->pgrp; + } + if (pgid <= 0) return; + + /* Deliver to the leader of every thread group in the foreground + * process group. ldisc paths call this for SIGINT/SIGQUIT/SIGWINCH + * etc.; previously we only signalled the current task, which was + * incorrect when the fg pgrp had multiple processes. */ + kernel_siginfo info{}; + info.si_signo = sig; + info.si_code = 0; /* SI_KERNEL would be 0x80; user rarely checks */ + for_each_pgrp_member(pgid, [&](signal_struct* s) { + if (s->leader) + send_signal(sig, &info, s->leader); + }); +} + +/* ================================================================== */ +/* tty_ioctl — ioctl dispatch */ +/* ================================================================== */ + +long tty_ioctl(tty_struct* tty, file* /*fp*/, unsigned int cmd, uint64 arg) +{ + if (!tty) return -EINVAL; + + switch (cmd) { + case TCGETS: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + *p = tty->termios_data; + return 0; + } + + case TCSETS: + case TCSETSW: + case TCSETSF: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + termios old = tty->termios_data; + tty->termios_data = *p; + + /* Notify line discipline of termios change */ + if (tty->ldisc.ops && tty->ldisc.ops->set_termios) + tty->ldisc.ops->set_termios(tty, &old); + + /* If TCSETSF, flush input buffer */ + if (cmd == TCSETSF) { + if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer) + tty->ldisc.ops->flush_buffer(tty); + } + return 0; + } + + case TIOCGWINSZ: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + *p = tty->ws; + return 0; + } + + case TIOCSWINSZ: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + winsize old = tty->ws; + tty->ws = *p; + if (old.ws_row != tty->ws.ws_row || old.ws_col != tty->ws.ws_col) { + tty_signal_pgrp(tty, SIGWINCH); + } + return 0; + } + + case TIOCGPGRP: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ENOTTY; + if (curr->signal->tty != tty) return -ENOTTY; + + int32 pgid; + { + LockGuard guard(tty->lock); + pgid = tty->pgrp != 0 ? tty->pgrp : curr->signal->pgid; + } + *p = pgid; + return 0; + } + + case TIOCSPGRP: { + auto* p = reinterpret_cast(arg); + if (!p) return -EINVAL; + + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ENOTTY; + if (curr->signal->tty != tty) return -ENOTTY; + + int32 new_pgid = *p; + if (new_pgid <= 0) return -EINVAL; + + /* new_pgid must be in the caller's session. */ + bool ok = false; + for_each_pgrp_member(new_pgid, [&](signal_struct* s) { + if (s->sid == curr->signal->sid) ok = true; + }); + if (!ok) return -EPERM; + + LockGuard guard(tty->lock); + tty->pgrp = new_pgid; + return 0; + } + + case TIOCSCTTY: { + /* Become the controlling terminal of the caller's session. + * Linux: only session leader without ctty may do this; with + * arg==1, CAP_SYS_ADMIN may steal a ctty from another session. + * We support the basic path; "steal" is left unimplemented. */ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -EPERM; + signal_struct* sig = curr->signal; + + if (sig->sid != curr->pid) return -EPERM; /* not leader */ + + LockGuard sg(sig->lock); + if (sig->tty != nullptr) return -EPERM; /* already have one */ + + LockGuard tg(tty->lock); + if (tty->session != 0 && tty->session != sig->sid) { + return -EPERM; /* owned elsewhere */ + } + tty->session = sig->sid; + tty->pgrp = sig->pgid; + sig->tty = tty; + return 0; + } + + case TIOCNOTTY: { + /* Detach from controlling terminal. */ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ENOTTY; + signal_struct* sig = curr->signal; + if (sig->tty != tty) return -ENOTTY; + + /* Session leader: HUP everyone left in the old fg pgrp. + * Job-control conformance — without this, daemons spawned by + * a relinquishing leader keep blocking on a dead terminal. */ + bool was_leader = (sig->sid == curr->pid); + int32 hup_pgrp = 0; + { + LockGuard tg(tty->lock); + hup_pgrp = tty->pgrp; + if (was_leader) { + tty->session = 0; + tty->pgrp = 0; + } + } + { + LockGuard sg(sig->lock); + sig->tty = nullptr; + } + if (was_leader && hup_pgrp > 0) { + tty_signal_pgrp(tty, SIGHUP); + tty_signal_pgrp(tty, SIGCONT); + (void)hup_pgrp; + } + return 0; + } + + case FIONREAD: { + /* Delegate to line discipline */ + if (tty->ldisc.ops && tty->ldisc.ops->ioctl) { + int ret = tty->ldisc.ops->ioctl(tty, cmd, arg); + if (ret != -ENOIOCTLCMD) + return ret; + } + return 0; + } + + case TCFLSH: { + int queue = static_cast(arg); + if (queue == 0 || queue == 2) { + /* Flush input */ + if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer) + tty->ldisc.ops->flush_buffer(tty); + } + if (queue == 1 || queue == 2) { + /* Flush output */ + if (tty->driver && tty->driver->ops && tty->driver->ops->flush_buffer) + tty->driver->ops->flush_buffer(tty); + } + return 0; + } + + default: + break; + } + + /* Try line discipline ioctl */ + if (tty->ldisc.ops && tty->ldisc.ops->ioctl) { + int ret = tty->ldisc.ops->ioctl(tty, cmd, arg); + if (ret != -ENOIOCTLCMD) + return ret; + } + + /* Try driver ioctl */ + if (tty->driver && tty->driver->ops && tty->driver->ops->ioctl) { + int ret = tty->driver->ops->ioctl(tty, cmd, arg); + if (ret != -ENOIOCTLCMD) + return ret; + } + + return -ENOTTY; +} + +/* ================================================================== */ +/* file_operations for TTY devices */ +/* ================================================================== */ + +static int64 tty_fops_read(file* fp, char* buf, uint64 count, int64* /*pos*/) +{ + auto* tty = static_cast(fp->private_data); + return tty_read(tty, fp, buf, count); +} + +static int64 tty_fops_write(file* fp, const char* buf, uint64 count, int64* /*pos*/) +{ + auto* tty = static_cast(fp->private_data); + return tty_write(tty, fp, buf, count); +} + +static long tty_fops_ioctl(file* fp, unsigned int cmd, uint64 arg) +{ + auto* tty = static_cast(fp->private_data); + return tty_ioctl(tty, fp, cmd, arg); +} + +/* + * tty poll callback — Linux drivers/tty/tty_io.c:tty_poll() does the + * same thing: register tty->read_wait so the poller is woken on input, + * then ask the ldisc whether data is currently available. Write side + * is always considered ready since our writes don't block. + */ +extern bool n_tty_input_ready(tty_struct* tty); +extern void poll_wait(file*, WaitQueue*, poll_table*); + +static uint32 tty_fops_poll(file* fp, void* pt) +{ + auto* tty = static_cast(fp->private_data); + if (!tty) return POLLNVAL; + + poll_wait(fp, &tty->read_wait, static_cast(pt)); + + uint32 mask = POLLOUT | POLLWRNORM; /* always writable */ + if (n_tty_input_ready(tty)) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +/* + * Auto-acquire a controlling terminal on open. + * + * POSIX rule: when a session leader without a controlling tty opens a + * terminal that itself has no controlling session, and the caller did + * not pass O_NOCTTY, that terminal becomes the controlling terminal of + * the caller's session. tty->session and tty->pgrp are seeded from the + * caller's sid/pgid so that the very first tcgetpgrp() call returns a + * value matching the caller's pgid — which is precisely what BusyBox + * ash:setjobctl() needs to exit its setup loop. + * + * Reference: Linux drivers/tty/tty_jobctrl.c:tty_open_proc_set_tty() + */ +static void tty_acquire_ctty(tty_struct* tty, file* fp) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return; + if (fp->f_flags & O_NOCTTY) return; + + signal_struct* sig = curr->signal; + + /* Must be session leader (sid == pid). */ + if (sig->sid != curr->pid) return; + + LockGuard sig_guard(sig->lock); + if (sig->tty != nullptr) return; /* already have one */ + + LockGuard tty_guard(tty->lock); + if (tty->session != 0) return; /* tty owned by another sess */ + + tty->session = sig->sid; + tty->pgrp = sig->pgid; + sig->tty = tty; +} + +static int tty_fops_open(inode* /*ip*/, file* fp) +{ + /* The console TTY is pre-allocated; just attach it */ + if (!g_console_tty) return -ENXIO; + fp->private_data = g_console_tty; + int err = tty_open(g_console_tty); + if (err == 0) { + tty_acquire_ctty(g_console_tty, fp); + } + return err; +} + +static int tty_fops_release(inode* /*ip*/, file* fp) +{ + auto* tty = static_cast(fp->private_data); + tty_close(tty); + return 0; +} + +file_operations tty_fops; + +static void init_tty_fops() +{ + tty_fops.read = tty_fops_read; + tty_fops.write = tty_fops_write; + tty_fops.unlocked_ioctl = tty_fops_ioctl; + tty_fops.poll = tty_fops_poll; + tty_fops.open = tty_fops_open; + tty_fops.release = tty_fops_release; +} + +/* ================================================================== */ +/* TTY chardev registration */ +/* ================================================================== */ + +static cdev tty_cdev; + +void tty_init() +{ + init_tty_fops(); + + /* Allocate the console TTY */ + g_console_tty = tty_alloc(&uart_tty_driver, 0); + if (!g_console_tty) { + return; + } + + /* Open the TTY (initializes ldisc + driver) */ + tty_open(g_console_tty); + + /* Leave tty->session and tty->pgrp at 0. The first session-leader + * to open /dev/console without O_NOCTTY claims it as its controlling + * terminal via tty_acquire_ctty(), which fills both fields. Pre- + * seeding them here (as we used to with the hardcoded pid==1) would + * make every subsequent acquire path see "owned by another session" + * and bail out, leaving sh's tcgetpgrp() returning -ENOTTY and + * disabling job control. */ + + /* Initialize UART RX interrupts */ + uart_tty_init(g_console_tty); + + /* Register UART as a struct console driver. + * After this, printk/klog output goes through console_write_all() + * → uart_console_write() instead of raw early_putc(). */ + extern void uart_register_console(); + uart_register_console(); + + /* Register as /dev/console chardev — replace the old console_cdev. + * We use the same major/minor (5,1) so that existing opens + * to /dev/console will get the TTY fops. */ + tty_cdev.name = "console"; + tty_cdev.dev = MKDEV(5, 1); + tty_cdev.fops = &tty_fops; + cdev_register(&tty_cdev); +} diff --git a/drivers/tty/tty_uart.cc b/drivers/tty/tty_uart.cc new file mode 100644 index 0000000..973a6e9 --- /dev/null +++ b/drivers/tty/tty_uart.cc @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * tty_uart.cc — UART → tty_driver adapter + * + * Bridges the 16550A UART hardware to the TTY subsystem. + * + * TX: Calls early_putc() for each character (same as before). + * RX: Registers a PLIC IRQ handler. On UART RX interrupt, + * reads available bytes and feeds them to the line discipline + * via tty_receive_buf(). + * + * QEMU virt UART: + * - IRQ 10 on the PLIC + * - 16550A register layout at the base address discovered from FDT + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENOIOCTLCMD +#define ENOIOCTLCMD 515 +#endif + +extern "C" void early_putc(char c); + +/* 16550A register offsets */ +static constexpr int UART_RBR = 0; /* Receive Buffer Register */ +static constexpr int UART_THR = 0; /* Transmit Holding Register */ +static constexpr int UART_IER = 1; /* Interrupt Enable Register */ +static constexpr int UART_IIR = 2; /* Interrupt Identification Register */ +static constexpr int UART_FCR = 2; /* FIFO Control Register */ +static constexpr int UART_LCR = 3; /* Line Control Register */ +static constexpr int UART_MCR = 4; /* Modem Control Register */ +static constexpr int UART_LSR = 5; /* Line Status Register */ + +/* LSR bits */ +static constexpr uint8 UART_LSR_DR = 0x01; /* Data Ready */ +static constexpr uint8 UART_LSR_THRE = 0x20; /* TX Holding Register Empty */ + +/* IER bits */ +static constexpr uint8 UART_IER_RDI = 0x01; /* Received Data Available */ + +/* MCR bits */ +static constexpr uint8 UART_MCR_DTR = 0x01; /* Data Terminal Ready */ +static constexpr uint8 UART_MCR_RTS = 0x02; /* Request To Send */ +static constexpr uint8 UART_MCR_OUT2 = 0x08; /* Auxiliary output 2 — gates the + IRQ pin on PC-style 16550A. + Without this the UART + internally asserts but does + NOT propagate to the PLIC. */ + +/* QEMU virt default UART IRQ */ +static constexpr uint32 UART_IRQ = 10; + +/* UART MMIO base (virtual address) */ +static volatile uint8* g_uart_base; +static tty_struct* g_uart_tty; + +/* ================================================================== */ +/* UART MMIO helpers */ +/* ================================================================== */ + +static inline uint8 uart_read(int reg) +{ + return g_uart_base[reg]; +} + +static inline void uart_write(int reg, uint8 val) +{ + g_uart_base[reg] = val; +} + +static inline bool uart_rx_ready() +{ + return (uart_read(UART_LSR) & UART_LSR_DR) != 0; +} + +static inline char uart_rx_char() +{ + return static_cast(uart_read(UART_RBR)); +} + +/* ================================================================== */ +/* UART RX interrupt handler */ +/* ================================================================== */ + +static void uart_irq_handler(uint32 /*irq*/, void* arg) +{ + auto* tty = static_cast(arg); + + /* Read all available bytes from the FIFO */ + char buf[16]; + int count = 0; + + while (uart_rx_ready() && count < 16) { + buf[count++] = uart_rx_char(); + } + + if (count > 0) { + tty_receive_buf(tty, buf, count); + } +} + +/* ================================================================== */ +/* tty_driver_ops for UART */ +/* ================================================================== */ + +static int uart_tty_open_op(tty_struct* /*tty*/) +{ + return 0; +} + +static void uart_tty_close_op(tty_struct* /*tty*/) +{ + /* Nothing to do */ +} + +static int uart_tty_write_op(tty_struct* /*tty*/, const char* buf, int count) +{ + for (int i = 0; i < count; i++) { + early_putc(buf[i]); + } + return count; +} + +static int uart_tty_write_room_op(tty_struct* /*tty*/) +{ + return TTY_BUF_SIZE; /* Always room (blocking putc) */ +} + +static void uart_tty_put_char_op(tty_struct* /*tty*/, char ch) +{ + early_putc(ch); +} + +static void uart_tty_flush_chars_op(tty_struct* /*tty*/) +{ + /* No buffering in early_putc, nothing to flush */ +} + +static int uart_tty_chars_in_buffer_op(tty_struct* /*tty*/) +{ + return 0; /* No TX buffer */ +} + +static void uart_tty_flush_buffer_op(tty_struct* /*tty*/) +{ + /* No TX buffer to flush */ +} + +static int uart_tty_ioctl_op(tty_struct* /*tty*/, unsigned int /*cmd*/, uint64 /*arg*/) +{ + return -ENOIOCTLCMD; +} + +static void uart_tty_hangup_op(tty_struct* /*tty*/) +{ + /* Disable UART RX interrupts */ + if (g_uart_base) { + uart_write(UART_IER, 0); + } +} + +static const tty_driver_ops uart_ops = { + .open = uart_tty_open_op, + .close = uart_tty_close_op, + .write = uart_tty_write_op, + .write_room = uart_tty_write_room_op, + .put_char = uart_tty_put_char_op, + .flush_chars = uart_tty_flush_chars_op, + .chars_in_buffer = uart_tty_chars_in_buffer_op, + .flush_buffer = uart_tty_flush_buffer_op, + .ioctl = uart_tty_ioctl_op, + .throttle = nullptr, + .unthrottle = nullptr, + .hangup = uart_tty_hangup_op, +}; + +const tty_driver uart_tty_driver = { + .name = "ttyS", + .major = 4, + .minor_start = 64, + .num = 1, + .ops = &uart_ops, +}; + +/* ================================================================== */ +/* UART TTY init — discover base address, enable RX IRQ */ +/* ================================================================== */ + +void uart_tty_init(tty_struct* tty) +{ + g_uart_tty = tty; + + /* Discover UART base from FDT */ + DeviceNode* node = fdt_find_compatible("ns16550a"); + if (!node) { + /* Try alternative compatible string */ + node = fdt_find_compatible("snps,dw-apb-uart"); + } + + uint64 uart_pa = 0; + if (node) { + uart_pa = node->get_prop_u64("reg", 0); + } + + if (uart_pa == 0) { + /* Fallback: QEMU virt default */ + uart_pa = 0x10000000; + } + + g_uart_base = reinterpret_cast( + pa_to_va(phys_addr(uart_pa)).raw); + + /* Set FIFO control: enable FIFOs, clear RX/TX FIFOs, 1-byte trigger */ + uart_write(UART_FCR, 0x07); + + /* MCR: assert OUT2 (gates the IRQ pin on PC-style 16550A), plus + * DTR/RTS for completeness. Mirrors Linux drivers/tty/serial/8250/ + * 8250_port.c: serial8250_do_startup() does the same. */ + uart_write(UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); + + /* Enable UART receive-data-available interrupt */ + uart_write(UART_IER, UART_IER_RDI); + + /* Register IRQ handler with PLIC */ + uint32 irq = UART_IRQ; + if (node) { + /* Try to get IRQ number from FDT */ + const FdtProperty* irq_prop = node->get_prop("interrupts"); + if (irq_prop && irq_prop->len >= 4) { + const uint8* val = static_cast(irq_prop->value); + irq = (uint32(val[0]) << 24) | (uint32(val[1]) << 16) | + (uint32(val[2]) << 8) | val[3]; + } + } + + g_irq_manager.register_handler(irq, uart_irq_handler, tty); + g_irq_manager.enable(irq); + + printk(KERN_INFO "uart_tty_init: base=0x%llx irq=%u IER=0x%x LSR=0x%x\n", + (unsigned long long)uart_pa, irq, + uart_read(UART_IER), uart_read(UART_LSR)); +} diff --git a/drivers/uart.cc b/drivers/uart.cc new file mode 100644 index 0000000..467b496 --- /dev/null +++ b/drivers/uart.cc @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * uart.cc — 16550a UART driver + console registry + * + * Two roles: + * 1. Early console: early_putc() for pre-init output (panic, kprintf) + * 2. Runtime console: struct console registered with register_console(). + * klog/printk route through console_write_all() → uart_console_write(). + * + * The console registry is a simple linked list of struct console. + * console_write_all() iterates the list and calls each console's write(). + * If no console is registered, it falls back to early_putc(). + * + * Reference: Linux kernel/printk/printk.c, drivers/tty/serial/8250/ + */ + +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* UART state */ +/* ================================================================== */ + +static uint64 g_uart_pa = 0; +static uint64 g_uart_size = 0; +static bool g_runtime_mapping_ready = false; +static bool g_mmu_enabled = false; + +/* ================================================================== */ +/* Early console (Phase 1) */ +/* ================================================================== */ + +extern "C" void console_set_mmu_enabled() { + g_mmu_enabled = true; +} + +extern "C" void early_putc(char c) { + if (!g_uart_pa) return; + + /* + * Before vmm_init() activates the final page table, UART MMIO is only + * reachable via the bootstrap fixed direct map from entry.S. + * After activation, switch to the runtime (possibly KASLR-slid) base. + */ + uint64 uart_va = 0; + if (g_runtime_mapping_ready) { + uart_va = pa_to_va(phys_addr(g_uart_pa)).raw; + } else if (!g_mmu_enabled) { + uart_va = g_uart_pa; + } else { + uart_va = boot_pa_to_va(phys_addr(g_uart_pa)).raw; + } + + volatile auto* uart = reinterpret_cast(uart_va); + *uart = static_cast(c); +} + +extern "C" void early_puts(const char* s) { + while (*s) early_putc(*s++); +} + +void console_early_init(uint64 fdt_paddr) { + uint64 pa = 0; + uint64 size = 0; + FdtManager::early_scan_uart(fdt_paddr, &pa, &size); + if (pa != 0) { + g_uart_pa = pa; + if (size != 0) g_uart_size = size; + } +} + +void console_init() { + /* + * Once vmm_init() activates the new page table, the 1GB identity map is dropped. + * The UART must be explicitly registered into the active page table. + */ + if (g_uart_pa != 0 && g_uart_size != 0) { + vmm_map_kernel_mmio(g_uart_pa, g_uart_size); + } +} + +void console_use_runtime_mapping() { + g_runtime_mapping_ready = true; +} + +/* ================================================================== */ +/* Console registry (Phase 2) — linked list of struct console */ +/* ================================================================== */ + +static list_node g_console_list; +static bool g_console_list_init = false; + +void register_console(console* con) +{ + if (!con) return; + + if (!g_console_list_init) { + g_console_list.init(); + g_console_list_init = true; + } + + con->flags |= CON_ENABLED; + con->list.init(); + + /* Insert at tail */ + list_add_tail(&con->list, &g_console_list); +} + +void unregister_console(console* con) +{ + if (!con) return; + + list_del_init(&con->list); + con->flags &= ~CON_ENABLED; +} + +bool console_drivers_registered() +{ + if (!g_console_list_init) return false; + return !list_empty(&g_console_list); +} + +void console_write_all(const char* s, int count) +{ + if (g_console_list_init && !list_empty(&g_console_list)) { + list_node* pos; + list_for_each(pos, &g_console_list) { + auto* con = container_of(pos, console, list); + if ((con->flags & CON_ENABLED) && con->write) { + con->write(con, s, static_cast(count)); + } + } + } else { + /* Fallback: direct early_putc (pre-registration) */ + for (int i = 0; i < count; i++) { + early_putc(s[i]); + } + } +} + +/* ================================================================== */ +/* UART console driver */ +/* ================================================================== */ + +static void uart_console_write(console* /*con*/, const char* s, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) { + if (s[i] == '\n') { + early_putc('\r'); + } + early_putc(s[i]); + } +} + +/* + * uart_console must be initialized at RUNTIME, not with static + * aggregate initializers. Reason: KASLR self-relocation processes + * .rela.dyn entries for simple pointer variables, but may not cover + * function pointer fields inside static aggregate initializers. + * A static `{ .write = uart_console_write }` would store the + * pre-relocation address, causing a crash when called. + */ +static console uart_console; + +void uart_register_console() +{ + uart_console.name = "ttyS"; + uart_console.write = uart_console_write; + uart_console.setup = nullptr; + uart_console.flags = CON_ENABLED | CON_BOOT; + uart_console.index = 0; + uart_console.data = nullptr; + uart_console.list.init(); + register_console(&uart_console); +} + diff --git a/fdt/device_node.cc b/fdt/device_node.cc new file mode 100644 index 0000000..0c8ad46 --- /dev/null +++ b/fdt/device_node.cc @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * device_node.cc — DeviceNode implementation + * + * Provides property lookup and child traversal for the unflattened + * device tree. After construction, the tree is globally read-only. + * + * Reference: docs/specs/fdt.md §3.2 + */ + +#include + +/* Minimal strcmp — no lib dependency */ +static bool str_eq(const char *a, const char *b) +{ + while (*a && *a == *b) { a++; b++; } + return *a == *b; +} + +/* ------------------------------------------------------------------ */ +/* Construction / Destruction */ +/* ------------------------------------------------------------------ */ + +DeviceNode::DeviceNode(DeviceNode *parent, const char *name) + : name_(name), parent_(parent) +{ + children_list_.init(); + props_list_.init(); + sibling_link_.init(); +} + +DeviceNode::~DeviceNode() +{ + /* Recursively delete children */ + list_node *cur = children_list_.next; + while (cur != &children_list_) { + list_node *next = cur->next; + /* Recover DeviceNode* from sibling_link_ offset */ + DeviceNode *child = list_entry(cur); + delete child; + cur = next; + } + + /* Delete properties */ + list_node *pc = props_list_.next; + while (pc != &props_list_) { + list_node *pn = pc->next; + FdtProperty *prop = reinterpret_cast( + reinterpret_cast(pc) - + __builtin_offsetof(FdtProperty, link)); + delete prop; + pc = pn; + } +} + +/* ------------------------------------------------------------------ */ +/* Tree construction (used by FdtManager::unflatten) */ +/* ------------------------------------------------------------------ */ + +void DeviceNode::add_child(DeviceNode *child) +{ + /* Append to end of children list */ + list_node *node = &child->sibling_link_; + node->prev = children_list_.prev; + node->next = &children_list_; + children_list_.prev->next = node; + children_list_.prev = node; +} + +void DeviceNode::add_prop(FdtProperty *prop) +{ + list_node *node = &prop->link; + node->prev = props_list_.prev; + node->next = &props_list_; + props_list_.prev->next = node; + props_list_.prev = node; +} + +/* ------------------------------------------------------------------ */ +/* Property access */ +/* ------------------------------------------------------------------ */ + +const FdtProperty *DeviceNode::get_prop(const char *name) const +{ + const list_node *cur = props_list_.next; + while (cur != &props_list_) { + const FdtProperty *prop = reinterpret_cast( + reinterpret_cast(cur) - + __builtin_offsetof(FdtProperty, link)); + if (str_eq(prop->name, name)) + return prop; + cur = cur->next; + } + return nullptr; +} + +/* Big-endian helpers for decoding property values */ +static inline uint32 be32(const void *p) +{ + const uint8 *b = static_cast(p); + return (uint32(b[0]) << 24) | (uint32(b[1]) << 16) | + (uint32(b[2]) << 8) | uint32(b[3]); +} + +static inline uint64 be64(const void *p) +{ + const uint8 *b = static_cast(p); + return (uint64(be32(b)) << 32) | uint64(be32(b + 4)); +} + +uint32 DeviceNode::get_prop_u32(const char *name, uint32 default_val) const +{ + const FdtProperty *p = get_prop(name); + if (!p || p->len < 4) + return default_val; + return be32(p->value); +} + +uint64 DeviceNode::get_prop_u64(const char *name, uint64 default_val) const +{ + const FdtProperty *p = get_prop(name); + if (!p) + return default_val; + if (p->len >= 8) + return be64(p->value); + if (p->len >= 4) + return be32(p->value); + return default_val; +} + +const char *DeviceNode::get_prop_string(const char *name) const +{ + const FdtProperty *p = get_prop(name); + if (!p || p->len == 0) + return nullptr; + return static_cast(p->value); +} + +/* ------------------------------------------------------------------ */ +/* Child traversal */ +/* ------------------------------------------------------------------ */ + +DeviceNode *DeviceNode::find_child(const char *name) +{ + list_node *cur = children_list_.next; + while (cur != &children_list_) { + DeviceNode *child = list_entry(cur); + if (str_eq(child->name_, name)) + return child; + cur = cur->next; + } + return nullptr; +} + +void DeviceNode::for_each_child(void (*callback)(DeviceNode *)) +{ + list_node *cur = children_list_.next; + while (cur != &children_list_) { + list_node *next = cur->next; + DeviceNode *child = list_entry(cur); + callback(child); + cur = next; + } +} diff --git a/fdt/fdt.cc b/fdt/fdt.cc new file mode 100644 index 0000000..d44ebda --- /dev/null +++ b/fdt/fdt.cc @@ -0,0 +1,625 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * fdt.cc — Flattened Device Tree parser implementation + * + * Implements the two-phase FDT parsing strategy described in fdt.md: + * + * Phase 1 (Early Scan): + * Static methods that linearly walk the raw FDT blob in-place, + * performing zero memory allocation. Called before PMM/Slab exist. + * + * Phase 2 (Unflatten): + * Builds a persistent DeviceNode C++ object tree using Slab. + * Stub implementation — will be completed when Slab is available. + * + * Reference: docs/specs/fdt.md + */ + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Global singleton (Placement New pattern, see global_init.md §3.1) */ +/* ------------------------------------------------------------------ */ + +alignas(FdtManager) uint8 g_fdt_buf[sizeof(FdtManager)]; +FdtManager *g_fdt = nullptr; + +/* ------------------------------------------------------------------ */ +/* FDT binary format constants (DTSpec v0.4) */ +/* ------------------------------------------------------------------ */ + +struct fdt_header { + uint32 magic; + uint32 totalsize; + uint32 off_dt_struct; + uint32 off_dt_strings; + uint32 off_mem_rsvmap; + uint32 version; + uint32 last_comp_version; + uint32 boot_cpuid_phys; + uint32 size_dt_strings; + uint32 size_dt_struct; +}; + +#define FDT_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 1 +#define FDT_END_NODE 2 +#define FDT_PROP 3 +#define FDT_NOP 4 +#define FDT_END 9 + +/* ------------------------------------------------------------------ */ +/* Minimal string helpers (no lib/ dependency for early boot) */ +/* ------------------------------------------------------------------ */ + +static inline bool str_starts_with(const char *str, const char *prefix) { + while (*prefix) { + if (*str++ != *prefix++) + return false; + } + return true; +} + +static inline bool str_equals(const char *s1, const char *s2) { + while (*s1 && *s1 == *s2) { + s1++; + s2++; + } + return *s1 == *s2; +} + +/* + * Check whether a null-separated string list (e.g. the "compatible" + * property) contains @target. + */ +static bool prop_contains_string(const char *prop_val, uint32 len, + const char *target) { + uint32 i = 0; + while (i < len) { + const char *s = prop_val + i; + if (str_equals(s, target)) + return true; + while (i < len && prop_val[i] != '\0') + i++; + i++; // skip '\0' separator + } + return false; +} + +/* ------------------------------------------------------------------ */ +/* Byte-swap helpers */ +/* ------------------------------------------------------------------ */ + +/* + * Manual 32-bit big-endian to little-endian conversion. + * We cannot use __builtin_bswap32 because on RISC-V without the Zbb + * extension the compiler emits a call to libgcc's __bswapsi2, which + * is unavailable in a freestanding (-nostdlib) environment. + */ +static inline uint32 bswap32(uint32 x) { + return ((x & 0xFF000000u) >> 24) | ((x & 0x00FF0000u) >> 8) | + ((x & 0x0000FF00u) << 8) | ((x & 0x000000FFu) << 24); +} + +/* + * Read a big-endian cell value from the FDT struct block. + * @cells: number of 32-bit cells (1 or 2). + * 2-cell values are common for 64-bit addresses/sizes. + */ +static inline uint64 read_cells(const uint8 *data, uint32 cells) { + const uint32 *p = reinterpret_cast(data); + if (cells == 2) { + uint64 high = bswap32(p[0]); + uint64 low = bswap32(p[1]); + return (high << 32) | low; + } + return bswap32(p[0]); +} + +/* ------------------------------------------------------------------ */ +/* FdtManager construction */ +/* ------------------------------------------------------------------ */ + +FdtManager::FdtManager(uint64 fdt_paddr) + : fdt_paddr_(fdt_paddr) {} + +/* ------------------------------------------------------------------ */ +/* Phase 1: Early scan — memory layout */ +/* ------------------------------------------------------------------ */ + +/* + * Walk the raw FDT struct block to locate the /memory node and + * extract its "reg" property (base address + size). + * + * Cell sizes (#address-cells, #size-cells) are tracked per depth + * level to correctly decode the "reg" value under any bus topology. + * + * Panics on invalid magic or missing /memory node — there is no + * recovery path this early in boot. + */ +void FdtManager::early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size) { + const fdt_header *hdr = reinterpret_cast(fdt_paddr); + if (bswap32(hdr->magic) != FDT_MAGIC) + kernel_panic("FDT early_scan_mem: invalid magic number", nullptr); + + const uint8 *dt_struct = + reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); + const char *dt_strings = + reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); + + uint32 offset = 0; + int depth = -1; + + // Per-depth cell sizes (inherit from parent on entry) + uint32 address_cells[16] = {2}; + uint32 size_cells[16] = {1}; + bool in_memory_node = false; + + while (true) { + uint32 token = + bswap32(*reinterpret_cast(dt_struct + offset)); + offset += 4; + + if (token == FDT_BEGIN_NODE) { + const char *name = reinterpret_cast(dt_struct + offset); + uint32 name_len = 0; + while (name[name_len] != '\0') + name_len++; + offset = (offset + name_len + 1 + 3) & ~3u; // align to 4 bytes + + depth++; + + // Inherit parent's cell sizes + if (depth > 0 && depth < 16) { + address_cells[depth] = address_cells[depth - 1]; + size_cells[depth] = size_cells[depth - 1]; + } + + // Match top-level "memory" or "memory@..." nodes + in_memory_node = (depth == 1 && str_starts_with(name, "memory")); + + } else if (token == FDT_END_NODE) { + if (depth >= 0) + depth--; + in_memory_node = false; + + } else if (token == FDT_PROP) { + uint32 len = + bswap32(*reinterpret_cast(dt_struct + offset)); + uint32 nameoff = + bswap32(*reinterpret_cast(dt_struct + offset + 4)); + offset += 8; + + const char *prop_name = dt_strings + nameoff; + const uint8 *prop_val = dt_struct + offset; + + if (depth >= 0 && depth < 16) { + // Track cell size overrides at the current depth + if (str_equals(prop_name, "#address-cells")) { + address_cells[depth] = + bswap32(*reinterpret_cast(prop_val)); + } else if (str_equals(prop_name, "#size-cells")) { + size_cells[depth] = + bswap32(*reinterpret_cast(prop_val)); + } + // Decode the "reg" property using the parent's cell sizes + else if (in_memory_node && str_equals(prop_name, "reg")) { + uint32 p_depth = (depth > 0) ? depth - 1 : 0; + *base = read_cells(prop_val, address_cells[p_depth]); + *size = read_cells(prop_val + address_cells[p_depth] * 4, + size_cells[p_depth]); + return; // Success + } + } + + offset = (offset + len + 3) & ~3u; // align to 4 bytes + + } else if (token == FDT_NOP) { + continue; + } else if (token == FDT_END) { + break; + } else { + kernel_panic("FDT early_scan_mem: unknown token", nullptr); + } + } + + kernel_panic("FDT early_scan_mem: /memory node not found", nullptr); +} + +/* ------------------------------------------------------------------ */ +/* Phase 1: Early scan — UART address */ +/* ------------------------------------------------------------------ */ + +/* + * Locate a UART node by matching compatible = "ns16550a" and return + * its base address from the "reg" property. + * + * Unlike early_scan_mem, failure here is non-fatal: we fall back to + * the platform's default UART address (0x10000000 for QEMU virt). + */ +void FdtManager::early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, + uint64 *uart_size) { + if (!uart_addr || !uart_size) + return; + + auto set_default_uart = [uart_addr, uart_size]() { + *uart_addr = 0x10000000ULL; + *uart_size = 0x100; + }; + + const fdt_header *hdr = reinterpret_cast(fdt_paddr); + if (bswap32(hdr->magic) != FDT_MAGIC) { + set_default_uart(); + return; + } + + const uint8 *dt_struct = + reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); + const char *dt_strings = + reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); + + uint32 offset = 0; + int depth = -1; + uint32 address_cells[16] = {2}; + uint32 size_cells[16] = {1}; + bool in_uart_node = false; + bool has_current_node_reg = false; + uint64 current_node_reg = 0; + uint64 current_node_size = 0; + + while (true) { + uint32 token = + bswap32(*reinterpret_cast(dt_struct + offset)); + offset += 4; + + if (token == FDT_BEGIN_NODE) { + const char *name = reinterpret_cast(dt_struct + offset); + uint32 name_len = 0; + while (name[name_len] != '\0') + name_len++; + offset = (offset + name_len + 1 + 3) & ~3u; + + depth++; + if (depth > 0 && depth < 16) { + address_cells[depth] = address_cells[depth - 1]; + size_cells[depth] = size_cells[depth - 1]; + } + in_uart_node = false; + has_current_node_reg = false; + + } else if (token == FDT_END_NODE) { + if (depth >= 0) + depth--; + in_uart_node = false; + has_current_node_reg = false; + + } else if (token == FDT_PROP) { + uint32 len = + bswap32(*reinterpret_cast(dt_struct + offset)); + uint32 nameoff = + bswap32(*reinterpret_cast(dt_struct + offset + 4)); + offset += 8; + + const char *prop_name = dt_strings + nameoff; + const uint8 *prop_val = dt_struct + offset; + + if (depth >= 0 && depth < 16) { + if (str_equals(prop_name, "#address-cells")) { + address_cells[depth] = + bswap32(*reinterpret_cast(prop_val)); + } else if (str_equals(prop_name, "#size-cells")) { + size_cells[depth] = + bswap32(*reinterpret_cast(prop_val)); + } else if (str_equals(prop_name, "compatible")) { + if (prop_contains_string(reinterpret_cast(prop_val), + len, "ns16550a")) { + in_uart_node = true; + if (has_current_node_reg) { + *uart_addr = current_node_reg; + *uart_size = current_node_size; + return; + } + } + } else if (str_equals(prop_name, "reg")) { + uint32 p_depth = (depth > 0) ? depth - 1 : 0; + current_node_reg = read_cells(prop_val, address_cells[p_depth]); + current_node_size = read_cells(prop_val + address_cells[p_depth] * 4, + size_cells[p_depth]); + has_current_node_reg = true; + if (in_uart_node) { + *uart_addr = current_node_reg; + *uart_size = current_node_size; + return; // Found it + } + } + } + + offset = (offset + len + 3) & ~3u; + + } else if (token == FDT_NOP) { + continue; + } else if (token == FDT_END) { + break; + } else { + break; // Unknown token — give up silently + } + } + + set_default_uart(); +} + +/* ------------------------------------------------------------------ */ +/* Phase 2: Unflatten (stub — requires Slab) */ +/* ------------------------------------------------------------------ */ + +void FdtManager::unflatten() { + const uint64 fdt_va = pa_to_va(phys_addr(fdt_paddr_)).raw; + const fdt_header *hdr = reinterpret_cast(fdt_va); + if (bswap32(hdr->magic) != FDT_MAGIC) + kernel_panic("fdt unflatten: invalid magic", nullptr); + + const uint8 *dt_struct = + reinterpret_cast(fdt_va) + bswap32(hdr->off_dt_struct); + const char *dt_strings = + reinterpret_cast(fdt_va) + bswap32(hdr->off_dt_strings); + + uint32 offset = 0; + + /* Stack to track current node at each depth (max 16 levels) */ + DeviceNode *stack[16] = {}; + int depth = -1; + + while (true) { + uint32 token = + bswap32(*reinterpret_cast(dt_struct + offset)); + offset += 4; + + if (token == FDT_BEGIN_NODE) { + const char *name = reinterpret_cast(dt_struct + offset); + uint32 name_len = 0; + while (name[name_len] != '\0') + name_len++; + offset = (offset + name_len + 1 + 3) & ~3u; + + depth++; + if (depth >= 16) + kernel_panic("fdt unflatten: tree too deep", nullptr); + + DeviceNode *parent = (depth > 0) ? stack[depth - 1] : nullptr; + DeviceNode *node = new DeviceNode(parent, name); + if (!node) + kernel_panic("fdt unflatten: OOM creating DeviceNode", nullptr); + + stack[depth] = node; + + if (parent) + parent->add_child(node); + else + root_ = node; /* Root node */ + + } else if (token == FDT_END_NODE) { + if (depth >= 0) + depth--; + + } else if (token == FDT_PROP) { + uint32 len = + bswap32(*reinterpret_cast(dt_struct + offset)); + uint32 nameoff = + bswap32(*reinterpret_cast(dt_struct + offset + 4)); + offset += 8; + + const char *prop_name = dt_strings + nameoff; + const void *prop_val = dt_struct + offset; + + if (depth >= 0 && depth < 16 && stack[depth]) { + FdtProperty *prop = new FdtProperty; + if (!prop) + kernel_panic("fdt unflatten: OOM creating FdtProperty", nullptr); + prop->name = prop_name; + prop->value = prop_val; + prop->len = static_cast(len); + prop->link.init(); + stack[depth]->add_prop(prop); + } + + offset = (offset + len + 3) & ~3u; + + } else if (token == FDT_NOP) { + continue; + } else if (token == FDT_END) { + break; + } else { + kernel_panic("fdt unflatten: unknown token", nullptr); + } + } + + is_unflattened_ = true; +} + +/* Recursive helper: find first node with matching compatible string */ +static DeviceNode *find_compat_recursive(DeviceNode *node, const char *compat) { + const FdtProperty *prop = node->get_prop("compatible"); + if (prop) { + const char *val = static_cast(prop->value); + uint32 i = 0; + while (i < static_cast(prop->len)) { + const char *s = val + i; + if (str_equals(s, compat)) + return node; + while (i < static_cast(prop->len) && val[i] != '\0') + i++; + i++; + } + } + + /* Search children */ + list_node *cur = node->children_list_head(); + list_node *end = node->children_list_sentinel(); + while (cur != end) { + DeviceNode *child = list_entry(cur); + DeviceNode *found = find_compat_recursive(child, compat); + if (found) + return found; + cur = cur->next; + } + return nullptr; +} + +DeviceNode *FdtManager::find_compatible(const char *compat_string) { + if (!is_unflattened_) + kernel_panic("fdt: tree not unflattened yet", nullptr); + if (!root_) + return nullptr; + return find_compat_recursive(root_, compat_string); +} + +DeviceNode *FdtManager::get_node_by_path(const char *path) { + if (!is_unflattened_) + kernel_panic("fdt: tree not unflattened yet", nullptr); + if (!root_ || !path) + return nullptr; + + /* Skip leading '/' */ + if (*path == '/') + path++; + if (*path == '\0') + return root_; + + DeviceNode *cur = root_; + while (*path && cur) { + /* Extract next path component */ + const char *end = path; + while (*end && *end != '/') + end++; + + /* Search children for a name matching this component */ + bool found = false; + list_node *lc = cur->children_list_head(); + list_node *le = cur->children_list_sentinel(); + while (lc != le) { + DeviceNode *child = + list_entry(lc); + /* Compare name up to the component length */ + const char *cn = child->get_name(); + const char *p = path; + const char *e = end; + bool match = true; + while (p < e) { + if (*cn != *p) { + match = false; + break; + } + cn++; + p++; + } + if (match && *cn == '\0') { + cur = child; + found = true; + break; + } + lc = lc->next; + } + + if (!found) + return nullptr; + + path = (*end == '/') ? end + 1 : end; + } + return cur; +} + +/* ------------------------------------------------------------------ */ +/* Public free-function API */ +/* ------------------------------------------------------------------ */ + +void fdt_init(uint64 fdt_paddr) { + const uint32 *magic_ptr = reinterpret_cast(fdt_paddr); + if (bswap32(*magic_ptr) != FDT_MAGIC) + kernel_panic("fdt_init: invalid FDT magic number", nullptr); + + g_fdt = new (g_fdt_buf) FdtManager(fdt_paddr); +} + +void fdt_unflatten() { g_fdt->unflatten(); } + +DeviceNode *fdt_find_compatible(const char *compat_string) { + return g_fdt->find_compatible(compat_string); +} + +DeviceNode *fdt_get_node_by_path(const char *path) { + return g_fdt->get_node_by_path(path); +} + +void fdt_early_get_mem_info(uint64 *base, uint64 *size) { + FdtManager::early_scan_mem(g_fdt->get_paddr(), base, size); +} + +void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size) { + FdtManager::early_scan_uart(g_fdt->get_paddr(), uart_addr, uart_size); +} + +void FdtManager::early_scan_kaslr(uint64 fdt_paddr, uint64 *seed) +{ + if (!seed) return; + *seed = 0; + + const fdt_header *hdr = reinterpret_cast(fdt_paddr); + if (bswap32(hdr->magic) != FDT_MAGIC) return; + + const uint8 *dt_struct = reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); + const char *dt_strings = reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); + + uint32 offset = 0; + int depth = -1; + bool in_chosen = false; + + while (true) { + uint32 token = bswap32(*reinterpret_cast(dt_struct + offset)); + offset += 4; + + if (token == FDT_BEGIN_NODE) { + const char *name = reinterpret_cast(dt_struct + offset); + uint32 name_len = 0; + while (name[name_len] != '\0') name_len++; + offset = (offset + name_len + 1 + 3) & ~3u; + + depth++; + if (depth == 1 && str_starts_with(name, "chosen")) { + in_chosen = true; + } else if (depth == 1) { + in_chosen = false; + } + } else if (token == FDT_END_NODE) { + if (depth >= 0) depth--; + if (depth < 1) in_chosen = false; + } else if (token == FDT_PROP) { + uint32 len = bswap32(*reinterpret_cast(dt_struct + offset)); + uint32 nameoff = bswap32(*reinterpret_cast(dt_struct + offset + 4)); + offset += 8; + + if (in_chosen) { + const char *prop_name = dt_strings + nameoff; + if (str_starts_with(prop_name, "kaslr-seed") && len == 8) { + *seed = read_cells(dt_struct + offset, 2); + return; + } + } + offset = (offset + len + 3) & ~3u; + } else if (token == FDT_NOP) { + continue; + } else if (token == FDT_END) { + break; + } else { + break; + } + } +} + +void fdt_early_get_kaslr_seed(uint64 *seed) { + FdtManager::early_scan_kaslr(g_fdt->get_paddr(), seed); +} diff --git a/fdt/module.mk b/fdt/module.mk new file mode 100644 index 0000000..3e490ca --- /dev/null +++ b/fdt/module.mk @@ -0,0 +1,3 @@ +# fdt/module.mk +SRCS_CC += fdt/fdt.cc +SRCS_CC += fdt/device_node.cc diff --git a/fs/chardev.cc b/fs/chardev.cc new file mode 100644 index 0000000..fa96276 --- /dev/null +++ b/fs/chardev.cc @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * chardev.cc — Character device registry + * + * Simple linked list of registered cdevs, searched by dev number. + */ + +#include +#include +#include +#include + +static list_node g_cdev_list; +static Spinlock g_cdev_lock; + +void cdev_init() +{ + list_init(&g_cdev_list); +} + +void cdev_register(cdev* dev) +{ + LockGuard guard(g_cdev_lock); + list_init(&dev->list); + list_add_tail(&dev->list, &g_cdev_list); + printk("[cdev] registered '%s' (%d,%d)\n", + dev->name, MAJOR(dev->dev), MINOR(dev->dev)); +} + +cdev* cdev_find(uint32 dev_nr) +{ + LockGuard guard(g_cdev_lock); + list_node* pos; + for (pos = g_cdev_list.prev; pos != &g_cdev_list; pos = pos->prev) { + cdev* cd = container_of(pos, cdev, list); + if (cd->dev == dev_nr) { + return cd; + } + } + return nullptr; +} diff --git a/fs/dcache.cc b/fs/dcache.cc new file mode 100644 index 0000000..94ab287 --- /dev/null +++ b/fs/dcache.cc @@ -0,0 +1,287 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * dcache.cc — Directory entry cache + * + * Provides dentry allocation, lookup, instantiation, and d_path. + */ + +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* Hash function */ +/* ================================================================== */ + +uint32 full_name_hash(const char* name, uint32 len) +{ + uint32 hash = 0; + for (uint32 i = 0; i < len; i++) + hash = hash * 31 + static_cast(name[i]); + return hash; +} + +/* ================================================================== */ +/* d_set_type — set DCACHE_*_TYPE bits from inode mode */ +/* ================================================================== */ + +void d_set_type(dentry* de, uint32 i_mode) +{ + de->d_flags &= ~DCACHE_ENTRY_TYPE; + + if (S_ISDIR(i_mode)) + de->d_flags |= DCACHE_DIRECTORY_TYPE; + else if (S_ISLNK(i_mode)) + de->d_flags |= DCACHE_SYMLINK_TYPE; + else if (S_ISREG(i_mode)) + de->d_flags |= DCACHE_REGULAR_TYPE; + else + de->d_flags |= DCACHE_SPECIAL_TYPE; +} + +/* ================================================================== */ +/* d_alloc — allocate a dentry under a parent */ +/* ================================================================== */ + +static void dentry_init(dentry* de, dentry* parent, const qstr* name) +{ + de->d_flags = DCACHE_MISS_TYPE; /* negative until instantiated */ + de->d_parent = parent ? parent : de; + de->d_inode = nullptr; + de->d_op = nullptr; + de->d_sb = parent ? parent->d_sb : nullptr; + de->d_time = 0; + de->d_fsdata = nullptr; + de->d_lockref.store(1); + + list_init(&de->d_lru); + list_init(&de->d_child); + list_init(&de->d_children); + list_init(&de->d_alias); + + /* Copy name */ + if (name && name->name && name->len > 0) { + uint32 len = name->len; + if (len < DNAME_INLINE_LEN) { + for (uint32 i = 0; i < len; i++) + de->d_iname[i] = name->name[i]; + de->d_iname[len] = '\0'; + de->d_name.name = reinterpret_cast(de->d_iname); + } else { + /* Heap-allocate for long names */ + char* buf = new char[len + 1]; + for (uint32 i = 0; i < len; i++) + buf[i] = name->name[i]; + buf[len] = '\0'; + de->d_name.name = buf; + } + de->d_name.len = len; + de->d_name.hash = name->hash ? name->hash : full_name_hash(name->name, len); + } else { + de->d_iname[0] = '\0'; + de->d_name.name = reinterpret_cast(de->d_iname); + de->d_name.len = 0; + de->d_name.hash = 0; + } +} + +dentry* d_alloc(dentry* parent, const qstr* name) +{ + auto* de = new dentry{}; + if (!de) return nullptr; + + dentry_init(de, parent, name); + de->d_sb = parent->d_sb; + + /* Use sb default d_op if set */ + if (parent->d_sb->s_d_op) + de->d_op = parent->d_sb->s_d_op; + + /* Link into parent's children list */ + if (parent) { + LockGuard guard(parent->d_lock); + list_add_tail(&de->d_child, &parent->d_children); + dget(parent); /* child holds ref on parent */ + } + + return de; +} + +dentry* d_alloc_anon(super_block* sb) +{ + auto* de = new dentry{}; + if (!de) return nullptr; + + dentry_init(de, nullptr, nullptr); + de->d_sb = sb; + de->d_parent = de; /* root: parent is self */ + + return de; +} + +/* ================================================================== */ +/* d_instantiate — bind an inode to a dentry */ +/* ================================================================== */ + +void d_instantiate(dentry* de, inode* ip) +{ + if (!ip) return; + + LockGuard guard(de->d_lock); + de->d_inode = ip; + d_set_type(de, ip->i_mode); + + /* Add to inode's alias list */ + list_add_tail(&de->d_alias, &ip->i_dentry); +} + +/* ================================================================== */ +/* d_make_root — create root dentry for a superblock */ +/* ================================================================== */ + +dentry* d_make_root(inode* root_inode) +{ + if (!root_inode) return nullptr; + + dentry* de = d_alloc_anon(root_inode->i_sb); + if (!de) { + iput(root_inode); + return nullptr; + } + + d_instantiate(de, root_inode); + return de; +} + +/* ================================================================== */ +/* d_add — combined d_instantiate + d_rehash */ +/* ================================================================== */ + +void d_add(dentry* de, inode* ip) +{ + d_instantiate(de, ip); + /* d_rehash would add to dcache hash table; simplified for now */ +} + +/* ================================================================== */ +/* d_lookup — search children of parent for a name match */ +/* ================================================================== */ + +dentry* d_lookup(const dentry* parent, const qstr* name) +{ + LockGuard guard(const_cast(parent->d_lock)); + + list_node* pos; + list_for_each(pos, &parent->d_children) { + dentry* child = list_entry(pos, dentry, d_child); + + if (child->d_name.len != name->len) + continue; + + /* Compare names */ + const char* a = child->d_name.name; + const char* b = name->name; + uint32 len = name->len; + bool match = true; + for (uint32 i = 0; i < len; i++) { + if (a[i] != b[i]) { match = false; break; } + } + if (match) { + dget(child); + return child; + } + } + + return nullptr; +} + +/* ================================================================== */ +/* dget / dput — dentry reference counting */ +/* ================================================================== */ + +dentry* dget(dentry* de) +{ + if (de) + de->d_lockref.fetch_add(1); + return de; +} + +void dput(dentry* de) +{ + if (!de) return; + + int old = de->d_lockref.fetch_sub(1); + if (old > 1) return; + + /* Last reference — could free, but for ramfs we keep dentries alive. + * A real implementation would check d_op->d_delete and possibly + * add to LRU for later reclaim. For now, just leak (Phase 1). */ +} + +/* ================================================================== */ +/* d_drop / d_delete */ +/* ================================================================== */ + +void d_drop(dentry* de) +{ + /* Remove from parent's children list */ + LockGuard guard(de->d_lock); + if (!list_empty(&de->d_child)) + list_del_init(&de->d_child); +} + +void d_delete(dentry* de) +{ + d_drop(de); +} + +/* ================================================================== */ +/* d_path — build absolute pathname from a path */ +/* ================================================================== */ + +char* d_path(const path* p, char* buf, int buflen) +{ + if (!p || !p->de || buflen <= 0) + return nullptr; + + /* Build path by walking up d_parent chain, writing backwards */ + char* end = buf + buflen - 1; + *end = '\0'; + + dentry* de = p->de; + + if (d_is_root(de)) { + if (end <= buf) return nullptr; + *(--end) = '/'; + return end; + } + + while (!d_is_root(de)) { + uint32 len = de->d_name.len; + end -= len; + if (end <= buf) return nullptr; + + const char* name = de->d_name.name; + for (uint32 i = 0; i < len; i++) + end[i] = name[i]; + + *(--end) = '/'; + de = de->d_parent; + } + + return end; +} + +/* ================================================================== */ +/* dcache_init */ +/* ================================================================== */ + +void dcache_init() +{ + /* Future: init dcache hash table */ +} diff --git a/fs/devfs.cc b/fs/devfs.cc new file mode 100644 index 0000000..b4b2cf3 --- /dev/null +++ b/fs/devfs.cc @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * devfs.cc — /dev population and chardev-aware open + * + * Creates /dev as a subdirectory under the ramfs root, then + * populates device nodes. On open(), if the inode is S_IFCHR, + * we look up the cdev by i_rdev and use its fops. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* devfs_create_node — create a char device inode under parent */ +/* ================================================================== */ + +static int devfs_create_node(dentry* parent, const char* name, + uint32 major, uint32 minor) +{ + inode* dir_ip = parent->d_inode; + if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mknod) { + return -EPERM; + } + + qstr q{}; + q.name = name; + q.len = 0; + for (const char* s = name; *s; s++) { + q.len++; + } + + dentry* de = d_alloc(parent, &q); + if (!de) { + return -ENOMEM; + } + + int err = dir_ip->i_op->mknod(dir_ip, de, S_IFCHR | 0666, + MKDEV(major, minor)); + if (err) { + return err; + } + + return 0; +} + +/* ================================================================== */ +/* devfs_open — chardev-aware open hook */ +/* ================================================================== */ + +/* + * Called from sys_openat when the inode is S_IFCHR. + * Replaces f_op with the chardev's file_operations. + */ +int devfs_chrdev_open(inode* ip, file* fp) +{ + if (!S_ISCHR(ip->i_mode)) { + return 0; + } + + cdev* cd = cdev_find(ip->i_rdev); + if (!cd || !cd->fops) { + return -ENXIO; /* No such device */ + } + + fp->f_op = cd->fops; + + /* Call the device's own open if it has one */ + if (fp->f_op->open) { + return fp->f_op->open(ip, fp); + } + + return 0; +} + +/* ================================================================== */ +/* devfs_init — create /dev and populate device nodes */ +/* ================================================================== */ + +void devfs_init() +{ + if (!g_root_dentry || !g_root_dentry->d_inode) { + printk(KERN_ERR "[devfs] no root filesystem!\n"); + return; + } + + /* Create /dev directory — or reuse if initramfs already created it. + * Linux: devtmpfs similarly handles pre-existing directories. */ + inode* root_ip = g_root_dentry->d_inode; + + qstr dev_name{}; + dev_name.name = "dev"; + dev_name.len = 3; + dev_name.hash = full_name_hash("dev", 3); + + dentry* dev_de = d_lookup(g_root_dentry, &dev_name); + if (dev_de) { + /* /dev already exists (from initramfs) — reuse it */ + printk("[devfs] reusing existing /dev directory\n"); + } else { + /* Create /dev from scratch */ + if (!root_ip->i_op || !root_ip->i_op->mkdir) { + printk(KERN_ERR "[devfs] root has no mkdir!\n"); + return; + } + + dev_de = d_alloc(g_root_dentry, &dev_name); + if (!dev_de) { + printk(KERN_ERR "[devfs] failed to alloc /dev dentry\n"); + return; + } + + int err = root_ip->i_op->mkdir(root_ip, dev_de, S_IFDIR | 0755); + if (err) { + printk(KERN_ERR "[devfs] mkdir /dev failed: %d\n", err); + return; + } + } + + /* Populate: /dev/console (5,1) */ + devfs_create_node(dev_de, "console", 5, 1); + + /* Populate: /dev/null (1,3) */ + devfs_create_node(dev_de, "null", 1, 3); + + /* Populate: /dev/zero (1,5) */ + devfs_create_node(dev_de, "zero", 1, 5); + + printk("[devfs] /dev populated (console, null, zero)\n"); +} diff --git a/fs/fd.cc b/fs/fd.cc new file mode 100644 index 0000000..5fc8ab0 --- /dev/null +++ b/fs/fd.cc @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * fd.cc — File descriptor table management + */ + +#include +#include +#include +#include + +/* ================================================================== */ +/* Bitmap helpers */ +/* ================================================================== */ + +static inline bool bit_test(const uint64* bmap, int bit) { + return (bmap[bit / 64] >> (bit % 64)) & 1; +} +static inline void bit_set(uint64* bmap, int bit) { + bmap[bit / 64] |= (1ULL << (bit % 64)); +} +static inline void bit_clear(uint64* bmap, int bit) { + bmap[bit / 64] &= ~(1ULL << (bit % 64)); +} + +/* ================================================================== */ +/* alloc_files_struct */ +/* ================================================================== */ + +files_struct* alloc_files_struct() +{ + auto* files = new files_struct{}; + if (!files) return nullptr; + + files->count.store(1); + files->next_fd = 0; + files->max_fds = NR_OPEN_DEFAULT; + + for (int i = 0; i < NR_OPEN_DEFAULT; i++) + files->fd_array[i] = nullptr; + + int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; + for (int i = 0; i < bmap_words; i++) { + files->close_on_exec_init[i] = 0; + files->open_fds_init[i] = 0; + } + + return files; +} + +/* ================================================================== */ +/* put_files_struct — drop reference, free on last */ +/* ================================================================== */ + +void put_files_struct(files_struct* files) +{ + if (!files) return; + + int old = files->count.fetch_sub(1); + if (old > 1) return; + + /* Close all open fds */ + for (int i = 0; i < files->max_fds; i++) { + if (files->fd_array[i]) { + fput(files->fd_array[i]); + files->fd_array[i] = nullptr; + } + } + + delete files; +} + +/* ================================================================== */ +/* dup_fd — duplicate entire fd table (for fork) */ +/* ================================================================== */ + +files_struct* dup_fd(files_struct* old_files) +{ + auto* new_files = alloc_files_struct(); + if (!new_files) return nullptr; + + LockGuard guard(old_files->file_lock); + + for (int i = 0; i < old_files->max_fds; i++) { + file* fp = old_files->fd_array[i]; + if (fp) { + get_file(fp); + new_files->fd_array[i] = fp; + } + } + + /* Copy bitmaps */ + int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; + for (int i = 0; i < bmap_words; i++) { + new_files->open_fds_init[i] = old_files->open_fds_init[i]; + new_files->close_on_exec_init[i] = old_files->close_on_exec_init[i]; + } + + new_files->next_fd = old_files->next_fd; + + return new_files; +} + +/* ================================================================== */ +/* alloc_fd — find lowest free fd >= start */ +/* ================================================================== */ + +int alloc_fd(files_struct* files, unsigned int start) +{ + LockGuard guard(files->file_lock); + + for (int fd = static_cast(start); fd < files->max_fds; fd++) { + if (!bit_test(files->open_fds_init, fd)) { + bit_set(files->open_fds_init, fd); + files->fd_array[fd] = nullptr; /* placeholder, fd_install sets real file */ + if (fd >= files->next_fd) + files->next_fd = fd + 1; + return fd; + } + } + + return -EMFILE; +} + +/* ================================================================== */ +/* fd_install — place a file* at a given fd */ +/* ================================================================== */ + +void fd_install(files_struct* files, int fd, file* fp) +{ + LockGuard guard(files->file_lock); + files->fd_array[fd] = fp; +} + +/* ================================================================== */ +/* fcheck — look up file by fd (no refcount bump) */ +/* ================================================================== */ + +file* fcheck(files_struct* files, int fd) +{ + if (fd < 0 || fd >= files->max_fds) return nullptr; + return files->fd_array[fd]; +} + +/* ================================================================== */ +/* close_fd */ +/* ================================================================== */ + +int close_fd(files_struct* files, int fd) +{ + file* fp; + { + LockGuard guard(files->file_lock); + if (fd < 0 || fd >= files->max_fds) return -EBADF; + + fp = files->fd_array[fd]; + if (!fp) return -EBADF; + + files->fd_array[fd] = nullptr; + bit_clear(files->open_fds_init, fd); + bit_clear(files->close_on_exec_init, fd); + } + + fput(fp); + return 0; +} + +/* ================================================================== */ +/* do_dup2 — duplicate oldfd to newfd */ +/* ================================================================== */ + +int do_dup2(files_struct* files, int oldfd, int newfd, int flags) +{ + if (oldfd == newfd) return -EINVAL; + + file* existing = nullptr; + { + LockGuard guard(files->file_lock); + + if (oldfd < 0 || oldfd >= files->max_fds) return -EBADF; + if (newfd < 0 || newfd >= files->max_fds) return -EBADF; + + file* old_fp = files->fd_array[oldfd]; + if (!old_fp) return -EBADF; + + existing = files->fd_array[newfd]; + + get_file(old_fp); + files->fd_array[newfd] = old_fp; + bit_set(files->open_fds_init, newfd); + + if (flags & O_CLOEXEC) + bit_set(files->close_on_exec_init, newfd); + else + bit_clear(files->close_on_exec_init, newfd); + } + + if (existing) + fput(existing); + + return newfd; +} + +/* ================================================================== */ +/* do_close_on_exec — close all fds with CLOEXEC set */ +/* ================================================================== */ + +void do_close_on_exec(files_struct* files) +{ + for (int fd = 0; fd < files->max_fds; fd++) { + if (bit_test(files->close_on_exec_init, fd)) { + close_fd(files, fd); + } + } +} diff --git a/fs/file_table.cc b/fs/file_table.cc new file mode 100644 index 0000000..06095c1 --- /dev/null +++ b/fs/file_table.cc @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * file_table.cc — Open file allocation and reference counting + */ + +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* alloc_empty_file */ +/* ================================================================== */ + +file* alloc_empty_file(int flags) +{ + auto* fp = new file{}; + if (!fp) return nullptr; + + fp->f_count.store(1); + fp->f_flags = flags; + fp->f_mode = 0; + fp->f_op = nullptr; + fp->f_mapping = nullptr; + fp->f_inode = nullptr; + fp->f_pos = 0; + fp->private_data = nullptr; + fp->f_path.mnt = nullptr; + fp->f_path.de = nullptr; + + /* Derive f_mode from O_* flags */ + int acc = flags & O_ACCMODE; + if (acc == O_RDONLY) + fp->f_mode = FMODE_READ | FMODE_CAN_READ; + else if (acc == O_WRONLY) + fp->f_mode = FMODE_WRITE | FMODE_CAN_WRITE; + else if (acc == O_RDWR) + fp->f_mode = FMODE_READ | FMODE_WRITE | FMODE_CAN_READ | FMODE_CAN_WRITE; + + fp->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; + + return fp; +} + +/* ================================================================== */ +/* get_file / fput */ +/* ================================================================== */ + +file* get_file(file* f) +{ + f->f_count.fetch_add(1); + return f; +} + +void fput(file* fp) +{ + if (!fp) return; + + int old = fp->f_count.fetch_sub(1); + if (old > 1) return; + + /* Last reference — release */ + if (fp->f_op && fp->f_op->release) + fp->f_op->release(fp->f_inode, fp); + + if (fp->f_path.de) + dput(fp->f_path.de); + + /* Future: mntput(fp->f_path.mnt) */ + + delete fp; +} + +/* ================================================================== */ +/* vfs_read / vfs_write / vfs_llseek / vfs_fstat */ +/* ================================================================== */ + +int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos) +{ + if (!(fp->f_mode & FMODE_CAN_READ)) + return -EBADF; + if (!fp->f_op || !fp->f_op->read) + return -EINVAL; + + return fp->f_op->read(fp, buf, count, pos); +} + +int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos) +{ + if (!(fp->f_mode & FMODE_CAN_WRITE)) + return -EBADF; + if (!fp->f_op || !fp->f_op->write) + return -EINVAL; + + return fp->f_op->write(fp, buf, count, pos); +} + +int64 vfs_llseek(file* fp, int64 offset, int whence) +{ + if (fp->f_op && fp->f_op->llseek) + return fp->f_op->llseek(fp, offset, whence); + return generic_file_llseek(fp, offset, whence); +} + +int64 generic_file_llseek(file* fp, int64 offset, int whence) +{ + int64 new_pos; + switch (whence) { + case SEEK_SET: + new_pos = offset; + break; + case SEEK_CUR: + new_pos = fp->f_pos + offset; + break; + case SEEK_END: + new_pos = fp->f_inode->i_size + offset; + break; + default: + return -EINVAL; + } + + if (new_pos < 0) return -EINVAL; + fp->f_pos = new_pos; + return new_pos; +} + +int vfs_fstat(file* fp, kstat* stat) +{ + inode* ip = file_inode(fp); + if (!ip) return -EBADF; + + if (ip->i_op && ip->i_op->getattr) { + return ip->i_op->getattr(&fp->f_path, stat, 0, 0); + } + + generic_fillattr(ip, stat); + return 0; +} + +/* ================================================================== */ +/* path_get / path_put (simplified) */ +/* ================================================================== */ + +void path_get(const path* p) +{ + if (p->de) dget(p->de); + /* Future: mntget(p->mnt) */ +} + +void path_put(const path* p) +{ + if (p->de) dput(p->de); + /* Future: mntput(p->mnt) */ +} + +/* ================================================================== */ +/* mntget / mntput (stubs for Phase 1) */ +/* ================================================================== */ + +vfsmount* mntget(vfsmount* mnt) { return mnt; } +void mntput(vfsmount*) {} diff --git a/fs/fs_struct.cc b/fs/fs_struct.cc new file mode 100644 index 0000000..090819f --- /dev/null +++ b/fs/fs_struct.cc @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * fs_struct.cc — Per-task filesystem context (cwd, root) + */ + +#include +#include + +/* ================================================================== */ +/* alloc_fs_struct */ +/* ================================================================== */ + +fs_struct* alloc_fs_struct() +{ + auto* fs = new fs_struct{}; + if (!fs) return nullptr; + + fs->count.store(1); + fs->umask = 022; + fs->root = {nullptr, nullptr}; + fs->pwd = {nullptr, nullptr}; + + return fs; +} + +/* ================================================================== */ +/* put_fs_struct */ +/* ================================================================== */ + +void put_fs_struct(fs_struct* fs) +{ + if (!fs) return; + + int old = fs->count.fetch_sub(1); + if (old > 1) return; + + path_put(&fs->root); + path_put(&fs->pwd); + delete fs; +} + +/* ================================================================== */ +/* copy_fs_struct — for fork without CLONE_FS */ +/* ================================================================== */ + +fs_struct* copy_fs_struct(const fs_struct* old_fs) +{ + auto* fs = alloc_fs_struct(); + if (!fs) return nullptr; + + fs->umask = old_fs->umask; + + fs->root = old_fs->root; + path_get(&fs->root); + + fs->pwd = old_fs->pwd; + path_get(&fs->pwd); + + return fs; +} + +/* ================================================================== */ +/* set_fs_root / set_fs_pwd */ +/* ================================================================== */ + +void set_fs_root(fs_struct* fs, const path* p) +{ + path old_root; + { + LockGuard guard(fs->lock); + old_root = fs->root; + fs->root = *p; + path_get(p); + } + path_put(&old_root); +} + +void set_fs_pwd(fs_struct* fs, const path* p) +{ + path old_pwd; + { + LockGuard guard(fs->lock); + old_pwd = fs->pwd; + fs->pwd = *p; + path_get(p); + } + path_put(&old_pwd); +} diff --git a/fs/initramfs.cc b/fs/initramfs.cc new file mode 100644 index 0000000..ba10a3e --- /dev/null +++ b/fs/initramfs.cc @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * initramfs.cc — Unpack embedded CPIO archive into ramfs + * + * The build system embeds a newc-format CPIO archive into the kernel + * ELF as the section .init.ramfs. The linker script exports + * __initramfs_start / __initramfs_end symbols. + * + * At boot, populate_rootfs() walks the CPIO entries and creates + * directories and files in the already-mounted ramfs root. + * + * Reference: Linux init/initramfs.c + * + * CPIO "newc" header format (110 bytes, ASCII hex fields): + * magic[6] "070701" + * ino[8] mode[8] uid[8] gid[8] nlink[8] mtime[8] + * filesize[8] devmajor[8] devminor[8] rdevmajor[8] rdevminor[8] + * namesize[8] check[8] + * filename (namesize bytes, NUL terminated, padded to 4-byte boundary) + * file data (filesize bytes, padded to 4-byte boundary) + */ + +#include +#include +#include +#include +#include +#include + +/* Linker-provided symbols for the embedded CPIO archive */ +extern "C" const char __initramfs_start[]; +extern "C" const char __initramfs_end[]; + +/* ramfs inode creation (from fs/ramfs/inode.cc) */ +extern inode* ramfs_get_inode(super_block* sb, const inode* dir, + uint32 mode, uint32 rdev); + +/* Global root dentry (from fs/vfs_init.cc) */ +extern dentry* g_root_dentry; + +/* ================================================================== */ +/* CPIO newc header parsing */ +/* ================================================================== */ + +struct cpio_header { + char magic[6]; + char ino[8]; + char mode[8]; + char uid[8]; + char gid[8]; + char nlink[8]; + char mtime[8]; + char filesize[8]; + char devmajor[8]; + char devminor[8]; + char rdevmajor[8]; + char rdevminor[8]; + char namesize[8]; + char check[8]; +}; + +static constexpr int kCpioHeaderSize = 110; + +static uint32 parse_hex8(const char* s) +{ + uint32 val = 0; + for (int i = 0; i < 8; i++) { + char c = s[i]; + uint32 nibble; + if (c >= '0' && c <= '9') nibble = c - '0'; + else if (c >= 'a' && c <= 'f') nibble = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') nibble = c - 'A' + 10; + else return val; + val = (val << 4) | nibble; + } + return val; +} + +static inline uint32 align4(uint32 v) +{ + return (v + 3) & ~3U; +} + +/* ================================================================== */ +/* Path resolution helpers */ +/* ================================================================== */ + +static dentry* cpio_mkdir_p(dentry* root, const char* path, + const char** out_name) +{ + dentry* cur = root; + + while (*path == '/') path++; + + while (true) { + const char* slash = path; + while (*slash && *slash != '/') slash++; + + if (*slash == '\0') { + *out_name = path; + return cur; + } + + uint32 len = static_cast(slash - path); + + qstr name; + name.name = path; + name.len = len; + name.hash = full_name_hash(path, len); + + dentry* child = d_lookup(cur, &name); + if (!child) { + child = d_alloc(cur, &name); + if (!child) return nullptr; + + inode* ip = ramfs_get_inode(cur->d_inode->i_sb, cur->d_inode, + S_IFDIR | 0755, 0); + if (!ip) return nullptr; + + d_instantiate(child, ip); + dget(child); + inc_nlink(cur->d_inode); + } + + cur = child; + path = slash + 1; + while (*path == '/') path++; + } +} + +/* ================================================================== */ +/* populate_rootfs — the main entry point */ +/* ================================================================== */ + +void populate_rootfs() +{ + const char* p = __initramfs_start; + const char* end = __initramfs_end; + uint64 archive_size = static_cast(end - p); + + if (archive_size == 0) { + printk("[initramfs] no embedded archive found\n"); + return; + } + + printk("[initramfs] unpacking %llu bytes...\n", archive_size); + + int file_count = 0; + int dir_count = 0; + + while (p + kCpioHeaderSize <= end) { + auto* hdr = reinterpret_cast(p); + + /* Verify magic */ + if (hdr->magic[0] != '0' || hdr->magic[1] != '7' || + hdr->magic[2] != '0' || hdr->magic[3] != '7' || + hdr->magic[4] != '0' || hdr->magic[5] != '1') { + printk("[initramfs] bad cpio magic at offset %llu\n", + static_cast(p - __initramfs_start)); + break; + } + + uint32 mode = parse_hex8(hdr->mode); + uint32 filesize = parse_hex8(hdr->filesize); + uint32 namesize = parse_hex8(hdr->namesize); + + const char* name = p + kCpioHeaderSize; + uint32 name_padded = align4(kCpioHeaderSize + namesize); + const char* data = p + name_padded; + uint32 data_padded = align4(filesize); + + const char* next = p + name_padded + data_padded; + + /* Check for TRAILER!!! end-of-archive marker */ + if (namesize == 11 && name[0] == 'T' && name[1] == 'R' && + name[2] == 'A' && name[3] == 'I' && name[4] == 'L' && + name[5] == 'E' && name[6] == 'R' && name[7] == '!' && + name[8] == '!' && name[9] == '!') { + break; + } + + /* Skip "." entry */ + if (namesize == 2 && name[0] == '.' && name[1] == '\0') { + p = next; + continue; + } + + /* Resolve parent directory, creating intermediate dirs */ + const char* basename = nullptr; + dentry* parent = cpio_mkdir_p(g_root_dentry, name, &basename); + if (!parent || !basename || basename[0] == '\0') { + p = next; + continue; + } + + /* Build qstr for the final component */ + uint32 blen = 0; + while (basename[blen] && basename[blen] != '/') blen++; + + qstr bname; + bname.name = basename; + bname.len = blen; + bname.hash = full_name_hash(basename, blen); + + if (S_ISDIR(mode)) { + dentry* existing = d_lookup(parent, &bname); + if (!existing) { + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFDIR | (mode & 0777), 0); + if (ip) { + d_instantiate(de, ip); + dget(de); + inc_nlink(parent->d_inode); + dir_count++; + } + } + } + } else if (S_ISREG(mode)) { + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFREG | (mode & 0777), 0); + if (ip) { + d_instantiate(de, ip); + dget(de); + + /* Write file content via ramfs write path */ + if (filesize > 0 && ip->i_fop && ip->i_fop->write) { + file tmp_fp = {}; + tmp_fp.f_inode = ip; + tmp_fp.f_path.de = de; + tmp_fp.f_pos = 0; + tmp_fp.f_flags = O_WRONLY; + tmp_fp.f_count.store(1); + + int64 pos = 0; + ip->i_fop->write(&tmp_fp, data, + filesize, &pos); + } + + file_count++; + printk("[initramfs] %s (%u bytes, mode %03o)\n", + name, filesize, mode & 0777); + } + } + } else if (S_ISLNK(mode)) { + /* Symlink: file data is the target path (not NUL-terminated). + * Linux: init/initramfs.c do_symlink() */ + dentry* de = d_alloc(parent, &bname); + if (de) { + inode* ip = ramfs_get_inode(parent->d_inode->i_sb, + parent->d_inode, + S_IFLNK | 0777, 0); + if (ip) { + /* Store symlink target in inode. + * Allocate a kernel buffer and copy the target. */ + char* target = new char[filesize + 1]; + if (target) { + for (uint32 i = 0; i < filesize; i++) + target[i] = data[i]; + target[filesize] = '\0'; + ip->i_link = target; + } + + d_instantiate(de, ip); + dget(de); + file_count++; + printk("[initramfs] %s -> %s (symlink)\n", + name, target ? target : "?"); + } + } + } + + p = next; + } + + printk("[initramfs] done: %d files, %d dirs\n", + file_count, dir_count); +} diff --git a/fs/inode.cc b/fs/inode.cc new file mode 100644 index 0000000..1d3d006 --- /dev/null +++ b/fs/inode.cc @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * inode.cc — Inode allocation, lifecycle, and generic helpers + */ + +#include +#include +#include +#include + +namespace { +lume::atomic g_next_ino{1}; +} // namespace + +/* ================================================================== */ +/* new_inode — allocate a fresh inode on a superblock */ +/* ================================================================== */ + +inode* new_inode(super_block* sb) +{ + inode* ip = nullptr; + + if (sb->s_op && sb->s_op->alloc_inode) + ip = sb->s_op->alloc_inode(sb); + else + ip = new inode{}; + + if (!ip) return nullptr; + + /* Zero-init key fields */ + ip->i_sb = sb; + ip->i_ino = g_next_ino.fetch_add(1); + ip->i_mode = 0; + ip->i_flags = 0; + ip->i_uid = 0; + ip->i_gid = 0; + ip->i_nlink = 1; + ip->i_rdev = 0; + ip->i_size = 0; + ip->i_blocks = 0; + ip->i_bytes = 0; + ip->i_blkbits = sb->s_blocksize_bits; + ip->i_generation = 0; + ip->i_state = I_NEW; + ip->i_count.store(1); + ip->i_writecount.store(0); + + ip->i_atime_sec = ip->i_mtime_sec = ip->i_ctime_sec = 0; + ip->i_atime_nsec = ip->i_mtime_nsec = ip->i_ctime_nsec = 0; + + ip->i_op = nullptr; + ip->i_fop = nullptr; + ip->i_mapping = &ip->i_data; + ip->i_private = nullptr; + ip->i_pipe = nullptr; + + /* Init embedded address_space */ + ip->i_data.host = ip; + ip->i_data.a_ops = nullptr; + ip->i_data.nrpages = 0; + ip->i_data.flags = 0; + + /* Init lists */ + list_init(&ip->i_sb_list); + list_init(&ip->i_dentry); + + /* Add to superblock's inode list */ + { + LockGuard guard(sb->s_inode_list_lock); + list_add_tail(&ip->i_sb_list, &sb->s_inodes); + } + + return ip; +} + +/* ================================================================== */ +/* ihold / iput — reference counting */ +/* ================================================================== */ + +void ihold(inode* ip) +{ + ip->i_count.fetch_add(1); +} + +void iput(inode* ip) +{ + if (!ip) return; + + int old = ip->i_count.fetch_sub(1); + if (old > 1) return; + + /* Last reference dropped — evict */ + if (ip->i_sb->s_op && ip->i_sb->s_op->evict_inode) + ip->i_sb->s_op->evict_inode(ip); + + /* Remove from sb list */ + { + LockGuard guard(ip->i_sb->s_inode_list_lock); + list_del(&ip->i_sb_list); + } + + if (ip->i_sb->s_op && ip->i_sb->s_op->destroy_inode) + ip->i_sb->s_op->destroy_inode(ip); + else + delete ip; +} + +/* ================================================================== */ +/* nlink helpers */ +/* ================================================================== */ + +void set_nlink(inode* ip, uint32 nlink) { ip->i_nlink = nlink; } +void inc_nlink(inode* ip) { ip->i_nlink++; } +void drop_nlink(inode* ip) { ip->i_nlink--; } +void clear_nlink(inode* ip) { ip->i_nlink = 0; } + +/* ================================================================== */ +/* inode_init_owner */ +/* ================================================================== */ + +void inode_init_owner(inode* ip, const inode* dir, uint32 mode) +{ + ip->i_uid = 0; /* Phase 1: always root */ + ip->i_gid = 0; + ip->i_mode = mode; +} + +/* ================================================================== */ +/* generic_fillattr — fill kstat from inode (used by getattr) */ +/* ================================================================== */ + +void generic_fillattr(inode* ip, kstat* stat) +{ + stat->st_dev = ip->i_sb->s_dev; + stat->st_ino = ip->i_ino; + stat->st_mode = ip->i_mode; + stat->st_nlink = ip->i_nlink; + stat->st_uid = ip->i_uid; + stat->st_gid = ip->i_gid; + stat->st_rdev = ip->i_rdev; + stat->__pad = 0; + stat->st_size = ip->i_size; + stat->st_blksize = 1 << ip->i_blkbits; + stat->__pad2 = 0; + stat->st_blocks = ip->i_blocks; + stat->st_atime_sec = ip->i_atime_sec; + stat->st_atime_nsec = ip->i_atime_nsec; + stat->st_mtime_sec = ip->i_mtime_sec; + stat->st_mtime_nsec = ip->i_mtime_nsec; + stat->st_ctime_sec = ip->i_ctime_sec; + stat->st_ctime_nsec = ip->i_ctime_nsec; + stat->__unused[0] = 0; + stat->__unused[1] = 0; +} + +/* ================================================================== */ +/* mark_inode_dirty (stub) */ +/* ================================================================== */ + +void mark_inode_dirty(inode* ip) +{ + ip->i_state |= I_DIRTY_SYNC; + /* Phase 1: no writeback, ramfs is memory-only */ +} diff --git a/fs/module.mk b/fs/module.mk new file mode 100644 index 0000000..f1be157 --- /dev/null +++ b/fs/module.mk @@ -0,0 +1,17 @@ +# fs/module.mk — VFS subsystem build rules + +SRCS_CC += fs/super.cc +SRCS_CC += fs/inode.cc +SRCS_CC += fs/dcache.cc +SRCS_CC += fs/file_table.cc +SRCS_CC += fs/fd.cc +SRCS_CC += fs/fs_struct.cc +SRCS_CC += fs/namei.cc +SRCS_CC += fs/ramfs/inode.cc +SRCS_CC += fs/ramfs/file.cc +SRCS_CC += fs/ramfs/super.cc +SRCS_CC += fs/vfs_init.cc +SRCS_CC += fs/chardev.cc +SRCS_CC += fs/devfs.cc +SRCS_CC += fs/pipe.cc +SRCS_CC += fs/initramfs.cc diff --git a/fs/namei.cc b/fs/namei.cc new file mode 100644 index 0000000..9124ea9 --- /dev/null +++ b/fs/namei.cc @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * namei.cc — Pathname resolution (path lookup) + * + * Simplified Phase 1: no symlink, no RCU, no mount traversal. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static const char* next_component(const char* path, qstr* result) +{ + while (*path == '/') path++; + if (*path == '\0') return nullptr; + + const char* start = path; + while (*path != '\0' && *path != '/') path++; + + uint32 len = static_cast(path - start); + result->name = start; + result->len = len; + result->hash = full_name_hash(start, len); + + return path; +} + +static bool is_dot(const qstr* name) { + return name->len == 1 && name->name[0] == '.'; +} +static bool is_dotdot(const qstr* name) { + return name->len == 2 && name->name[0] == '.' && name->name[1] == '.'; +} + +/* ================================================================== */ +/* walk_component */ +/* ================================================================== */ + +static dentry* walk_component(dentry* parent, const qstr* name) +{ + if (!parent->d_inode || !S_ISDIR(parent->d_inode->i_mode)) + return reinterpret_cast(-ENOTDIR); + + if (is_dot(name)) + return dget(parent); + + if (is_dotdot(name)) + return dget(parent->d_parent); + + /* Try dcache */ + dentry* child = d_lookup(parent, name); + if (child) + return child; + + /* Cache miss — call filesystem lookup */ + inode* dir = parent->d_inode; + if (!dir->i_op || !dir->i_op->lookup) + return reinterpret_cast(-ENOENT); + + child = d_alloc(parent, name); + if (!child) + return reinterpret_cast(-ENOMEM); + + dentry* result = dir->i_op->lookup(dir, child, 0); + if (result) { + dput(child); + return result; + } + + /* If lookup didn't set an inode, this is a negative dentry + * (file doesn't exist). Remove from dcache and return error. */ + if (!child->d_inode) { + d_drop(child); + dput(child); + return reinterpret_cast(-ENOENT); + } + + return child; +} + +/* ================================================================== */ +/* filename_lookup */ +/* ================================================================== */ + +int filename_lookup(int dfd, const char* name, unsigned int flags, path* result) +{ + if (!name || !result) + return -EINVAL; + + TaskControlBlock* task = current_task(); + dentry* start; + + /* Determine starting dentry */ + if (name[0] == '/') { + start = task->fs->root.de; + if (!start) start = g_root_dentry; + name++; + } else if (dfd == AT_FDCWD) { + start = task->fs->pwd.de; + if (!start) start = g_root_dentry; + } else { + file* dir_fp = fcheck(task->files, dfd); + if (!dir_fp) return -EBADF; + start = dir_fp->f_path.de; + if (!start || !start->d_inode || !S_ISDIR(start->d_inode->i_mode)) + return -ENOTDIR; + } + + dentry* current_de = dget(start); + const char* p = name; + + while (p && *p) { + qstr component; + const char* next = next_component(p, &component); + if (!next && component.len == 0) + break; + + const char* after = next; + while (after && *after == '/') after++; + bool is_last = (!after || *after == '\0'); + + if (is_last && (flags & LOOKUP_PARENT)) { + result->de = current_de; + result->mnt = task->fs->root.mnt; + return 0; + } + + dentry* child = walk_component(current_de, &component); + + int64 err = reinterpret_cast(child); + if (err < 0 && err >= -4095) { + dput(current_de); + return static_cast(err); + } + + /* ---- Symlink following (Linux: step_into → pick_link) ---- + * If the resolved component is a symlink, follow it. + * Linux limits this to MAXSYMLINKS (40); we use 8. */ + constexpr int kMaxSymlinks = 8; + int symlink_depth = 0; + while (child->d_inode && S_ISLNK(child->d_inode->i_mode) && + child->d_inode->i_link && symlink_depth < kMaxSymlinks) { + symlink_depth++; + const char* target = child->d_inode->i_link; + dput(child); + + /* Absolute symlink: restart from root. + * Relative symlink: resolve from current parent. */ + dentry* link_start; + if (target[0] == '/') { + link_start = dget(task->fs->root.de ? task->fs->root.de : g_root_dentry); + target++; + } else { + link_start = dget(current_de); + } + + /* Walk each component of the symlink target */ + dentry* link_cur = link_start; + const char* lp = target; + qstr lcomp; + while (lp && *lp) { + const char* lnext = next_component(lp, &lcomp); + if (!lnext && lcomp.len == 0) break; + + dentry* lchild = walk_component(link_cur, &lcomp); + int64 lerr = reinterpret_cast(lchild); + if (lerr < 0 && lerr >= -4095) { + dput(link_cur); + dput(current_de); + return static_cast(lerr); + } + + dput(link_cur); + link_cur = lchild; + lp = lnext; + } + + child = link_cur; + } + + if (symlink_depth >= kMaxSymlinks) { + dput(child); + dput(current_de); + return -ELOOP; + } + + if (!is_last && child->d_inode && !S_ISDIR(child->d_inode->i_mode)) { + dput(child); + dput(current_de); + return -ENOTDIR; + } + + if (!is_last && !child->d_inode) { + dput(child); + dput(current_de); + return -ENOENT; + } + + dput(current_de); + current_de = child; + p = next; + } + + if ((flags & LOOKUP_DIRECTORY) && current_de->d_inode && + !S_ISDIR(current_de->d_inode->i_mode)) { + dput(current_de); + return -ENOTDIR; + } + + /* Negative dentry: the path was walked, but the final component + * does not exist (d_inode == NULL). Return -ENOENT. */ + if (!current_de->d_inode) { + dput(current_de); + return -ENOENT; + } + + result->de = current_de; + result->mnt = task->fs->root.mnt; + return 0; +} + +int kern_path(const char* name, unsigned int flags, path* result) +{ + return filename_lookup(AT_FDCWD, name, flags, result); +} + +dentry* lookup_one_len(const char* name, dentry* parent, int len) +{ + qstr q; + q.name = name; + q.len = static_cast(len); + q.hash = full_name_hash(name, q.len); + + return walk_component(parent, &q); +} + +void namei_init() {} diff --git a/fs/pipe.cc b/fs/pipe.cc new file mode 100644 index 0000000..f7708f5 --- /dev/null +++ b/fs/pipe.cc @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * pipe.cc — Pipe implementation + * + * Implements unidirectional byte-stream pipes with blocking I/O. + * Each pipe has a 4KB circular buffer. Reads block when empty, + * writes block when full (unless O_NONBLOCK). + * + * When all writers close, read returns 0 (EOF). + * When all readers close, write returns -EPIPE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* pipe_inode_info lifecycle */ +/* ================================================================== */ + +pipe_inode_info* alloc_pipe_info() +{ + auto* pi = new pipe_inode_info{}; + if (!pi) return nullptr; + + pi->rd_pos = 0; + pi->wr_pos = 0; + pi->len = 0; + pi->rd_wait.init("pipe_rd"); + pi->wr_wait.init("pipe_wr"); + pi->readers.store(0); + pi->writers.store(0); + pi->refcount.store(1); + return pi; +} + +void free_pipe_info(pipe_inode_info* pipe) +{ + delete pipe; +} + +static void pipe_get(pipe_inode_info* pi) +{ + pi->refcount.fetch_add(1); +} + +static void pipe_put(pipe_inode_info* pi) +{ + if (pi->refcount.fetch_sub(1) == 1) { + free_pipe_info(pi); + } +} + +/* ================================================================== */ +/* pipe_read */ +/* ================================================================== */ + +static int64 pipe_read(file* fp, char* buf, uint64 count, int64* /*pos*/) +{ + pipe_inode_info* pi = static_cast(fp->private_data); + if (!pi) return -EBADF; + + while (true) { + pi->lock.acquire(); + + if (pi->len > 0) { + /* Data available — copy out */ + int64 to_read = static_cast(count); + if (to_read > pi->len) to_read = pi->len; + + for (int64 i = 0; i < to_read; i++) { + buf[i] = pi->buf[pi->rd_pos]; + pi->rd_pos = (pi->rd_pos + 1) % PIPE_BUF_SIZE; + } + pi->len -= static_cast(to_read); + + pi->lock.release(); + + /* Wake any blocked writers */ + pi->wr_wait.wake_all(); + return to_read; + } + + /* Buffer empty */ + if (pi->writers.load() == 0) { + /* No writers left — EOF */ + pi->lock.release(); + return 0; + } + + if (fp->f_flags & O_NONBLOCK) { + pi->lock.release(); + return -EAGAIN; + } + + pi->lock.release(); + + /* Sleep until data or writer closes */ + pi->rd_wait.sleep(); + } +} + +/* ================================================================== */ +/* pipe_write */ +/* ================================================================== */ + +static int64 pipe_write(file* fp, const char* buf, uint64 count, int64* /*pos*/) +{ + pipe_inode_info* pi = static_cast(fp->private_data); + if (!pi) return -EBADF; + + if (pi->readers.load() == 0) { + return -EPIPE; /* Broken pipe */ + } + + int64 written = 0; + + while (static_cast(written) < count) { + pi->lock.acquire(); + + if (pi->len < PIPE_BUF_SIZE) { + /* Space available — copy in */ + int avail = PIPE_BUF_SIZE - pi->len; + int64 remaining = static_cast(count) - written; + int to_write = avail < static_cast(remaining) + ? avail + : static_cast(remaining); + + for (int i = 0; i < to_write; i++) { + pi->buf[pi->wr_pos] = buf[written + i]; + pi->wr_pos = (pi->wr_pos + 1) % PIPE_BUF_SIZE; + } + pi->len += to_write; + written += to_write; + + pi->lock.release(); + + /* Wake any blocked readers */ + pi->rd_wait.wake_all(); + continue; + } + + /* Buffer full */ + if (pi->readers.load() == 0) { + pi->lock.release(); + return written > 0 ? written : -EPIPE; + } + + if (fp->f_flags & O_NONBLOCK) { + pi->lock.release(); + return written > 0 ? written : -EAGAIN; + } + + pi->lock.release(); + + /* Sleep until space or reader closes */ + pi->wr_wait.sleep(); + } + + return written; +} + +/* ================================================================== */ +/* pipe_release — called on last fput of a pipe fd */ +/* ================================================================== */ + +static int pipe_read_release(inode* /*ip*/, file* fp) +{ + pipe_inode_info* pi = static_cast(fp->private_data); + if (pi) { + pi->readers.fetch_sub(1); + /* Wake writers so they see readers==0 and get EPIPE */ + pi->wr_wait.wake_all(); + pipe_put(pi); + } + return 0; +} + +static int pipe_write_release(inode* /*ip*/, file* fp) +{ + pipe_inode_info* pi = static_cast(fp->private_data); + if (pi) { + pi->writers.fetch_sub(1); + /* Wake readers so they see writers==0 and get EOF */ + pi->rd_wait.wake_all(); + pipe_put(pi); + } + return 0; +} + +/* ================================================================== */ +/* file_operations tables */ +/* ================================================================== */ + +static file_operations pipe_read_fops; +static file_operations pipe_write_fops; +static bool pipe_fops_initialized = false; + +static void pipe_fops_init() +{ + if (pipe_fops_initialized) return; + + pipe_read_fops = {}; + pipe_read_fops.read = pipe_read; + pipe_read_fops.release = pipe_read_release; + + pipe_write_fops = {}; + pipe_write_fops.write = pipe_write; + pipe_write_fops.release = pipe_write_release; + + pipe_fops_initialized = true; +} + +/* ================================================================== */ +/* do_pipe2 — create a pipe, install two fds */ +/* ================================================================== */ + +int do_pipe2(files_struct* files, int fds[2], int flags) +{ + pipe_fops_init(); + + pipe_inode_info* pi = alloc_pipe_info(); + if (!pi) return -ENOMEM; + + /* Create read-end file */ + file* rd_fp = alloc_empty_file(O_RDONLY | (flags & O_NONBLOCK)); + if (!rd_fp) { + free_pipe_info(pi); + return -ENFILE; + } + rd_fp->f_op = &pipe_read_fops; + rd_fp->private_data = pi; + + /* Create write-end file */ + file* wr_fp = alloc_empty_file(O_WRONLY | (flags & O_NONBLOCK)); + if (!wr_fp) { + fput(rd_fp); + free_pipe_info(pi); + return -ENFILE; + } + wr_fp->f_op = &pipe_write_fops; + wr_fp->private_data = pi; + + /* Bump pipe refcount for the two files */ + pipe_get(pi); /* wr_fp gets second ref */ + pi->readers.store(1); + pi->writers.store(1); + + /* Allocate fds */ + int rd_fd = alloc_fd(files, 0); + if (rd_fd < 0) { + fput(rd_fp); + fput(wr_fp); + return rd_fd; + } + + int wr_fd = alloc_fd(files, 0); + if (wr_fd < 0) { + close_fd(files, rd_fd); + fput(wr_fp); + return wr_fd; + } + + fd_install(files, rd_fd, rd_fp); + fd_install(files, wr_fd, wr_fp); + + fds[0] = rd_fd; + fds[1] = wr_fd; + + return 0; +} diff --git a/fs/ramfs/file.cc b/fs/ramfs/file.cc new file mode 100644 index 0000000..6d9a070 --- /dev/null +++ b/fs/ramfs/file.cc @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/file.cc — RamFS file operations (read/write via in-memory pages) + * + * Phase 1: simple flat buffer per-inode via i_private. + * Will migrate to page_cache integration later. + */ + +#include +#include +#include +#include +#include +#include + +/* + * Simplest possible in-memory file storage: + * i_private points to a dynamically grown buffer. + */ +struct ramfs_file_data { + char* buf; + uint64 capacity; +}; + +static ramfs_file_data* get_or_create_data(inode* ip) +{ + if (!ip->i_private) { + auto* data = new ramfs_file_data{}; + data->buf = nullptr; + data->capacity = 0; + ip->i_private = data; + } + return static_cast(ip->i_private); +} + +static int ensure_capacity(ramfs_file_data* data, uint64 needed) +{ + if (needed <= data->capacity) return 0; + + /* Round up to page size */ + uint64 new_cap = (needed + 4095) & ~4095ULL; + char* new_buf = new char[new_cap]; + if (!new_buf) return -ENOMEM; + + /* Copy old data */ + if (data->buf && data->capacity > 0) { + for (uint64 i = 0; i < data->capacity; i++) + new_buf[i] = data->buf[i]; + } + /* Zero new region */ + for (uint64 i = data->capacity; i < new_cap; i++) + new_buf[i] = 0; + + delete[] data->buf; + data->buf = new_buf; + data->capacity = new_cap; + return 0; +} + +/* ================================================================== */ +/* ramfs_read */ +/* ================================================================== */ + +static int64 ramfs_read(file* fp, char* buf, uint64 count, int64* pos) +{ + inode* ip = file_inode(fp); + int64 offset = *pos; + + if (offset < 0) return -EINVAL; + if (offset >= ip->i_size) return 0; + + int64 avail = ip->i_size - offset; + if (static_cast(count) > avail) + count = static_cast(avail); + + auto* data = static_cast(ip->i_private); + if (!data || !data->buf) return 0; + + /* Copy to user buffer. + * TODO: use copy_to_user when called from syscall context */ + for (uint64 i = 0; i < count; i++) + buf[i] = data->buf[offset + i]; + + *pos += count; + return static_cast(count); +} + +/* ================================================================== */ +/* ramfs_write */ +/* ================================================================== */ + +static int64 ramfs_write(file* fp, const char* buf, uint64 count, int64* pos) +{ + inode* ip = file_inode(fp); + int64 offset = *pos; + + if (fp->f_flags & O_APPEND) + offset = ip->i_size; + + if (offset < 0) return -EINVAL; + + auto* data = get_or_create_data(ip); + uint64 end = static_cast(offset) + count; + + int err = ensure_capacity(data, end); + if (err) return err; + + /* Copy from user buffer */ + for (uint64 i = 0; i < count; i++) + data->buf[offset + i] = buf[i]; + + if (static_cast(end) > ip->i_size) + ip->i_size = end; + + *pos = end; + mark_inode_dirty(ip); + return static_cast(count); +} + +/* ================================================================== */ +/* ramfs_dir_iterate — getdents64 */ +/* ================================================================== */ + +static int ramfs_dir_iterate(file* fp, dir_context* ctx) +{ + dentry* dir_de = file_dentry(fp); + inode* dir_ip = file_inode(fp); + int64 pos = ctx->pos; + int count = 0; + + /* pos 0: emit "." */ + if (pos == 0) { + if (!ctx->actor(ctx, ".", 1, pos, dir_ip->i_ino, DT_DIR)) + return 0; + ctx->pos = 1; + pos = 1; + count++; + } + + /* pos 1: emit ".." */ + if (pos == 1) { + uint64 parent_ino = dir_de->d_parent->d_inode + ? dir_de->d_parent->d_inode->i_ino : dir_ip->i_ino; + if (!ctx->actor(ctx, "..", 2, pos, parent_ino, DT_DIR)) + return 0; + ctx->pos = 2; + pos = 2; + count++; + } + + /* pos >= 2: iterate children */ + int64 child_idx = 2; + list_node* node; + list_for_each(node, &dir_de->d_children) { + dentry* child = list_entry(node, dentry, d_child); + + if (!child->d_inode) + continue; /* skip negative dentries */ + + if (child_idx >= pos) { + uint8 d_type = DT_UNKNOWN; + if (S_ISDIR(child->d_inode->i_mode)) d_type = DT_DIR; + else if (S_ISREG(child->d_inode->i_mode)) d_type = DT_REG; + else if (S_ISCHR(child->d_inode->i_mode)) d_type = DT_CHR; + else if (S_ISBLK(child->d_inode->i_mode)) d_type = DT_BLK; + else if (S_ISLNK(child->d_inode->i_mode)) d_type = DT_LNK; + else if (S_ISFIFO(child->d_inode->i_mode)) d_type = DT_FIFO; + + if (!ctx->actor(ctx, child->d_name.name, + static_cast(child->d_name.len), + child_idx, child->d_inode->i_ino, d_type)) + return 0; + + ctx->pos = child_idx + 1; + count++; + } + child_idx++; + } + + return count; +} + +extern file_operations ramfs_file_operations; +extern file_operations ramfs_dir_operations; + +file_operations ramfs_file_operations; +file_operations ramfs_dir_operations; + +void ramfs_file_ops_init() +{ + ramfs_file_operations.llseek = nullptr; + ramfs_file_operations.read = ramfs_read; + ramfs_file_operations.write = ramfs_write; + ramfs_file_operations.iterate_shared = nullptr; + ramfs_file_operations.poll = nullptr; + ramfs_file_operations.unlocked_ioctl = nullptr; + ramfs_file_operations.mmap = nullptr; + ramfs_file_operations.open = nullptr; + ramfs_file_operations.flush = nullptr; + ramfs_file_operations.release = nullptr; + ramfs_file_operations.fsync = nullptr; + + ramfs_dir_operations.llseek = nullptr; + ramfs_dir_operations.read = nullptr; + ramfs_dir_operations.write = nullptr; + ramfs_dir_operations.iterate_shared = ramfs_dir_iterate; + ramfs_dir_operations.poll = nullptr; + ramfs_dir_operations.unlocked_ioctl = nullptr; + ramfs_dir_operations.mmap = nullptr; + ramfs_dir_operations.open = nullptr; + ramfs_dir_operations.flush = nullptr; + ramfs_dir_operations.release = nullptr; + ramfs_dir_operations.fsync = nullptr; +} diff --git a/fs/ramfs/inode.cc b/fs/ramfs/inode.cc new file mode 100644 index 0000000..c5e49bf --- /dev/null +++ b/fs/ramfs/inode.cc @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/inode.cc — RamFS inode operations + * + * A simple in-memory filesystem (like Linux tmpfs without swap). + * All data lives in memory via the page cache. + */ + +#include +#include +#include +#include +#include +#include + +/* Forward declarations */ +extern file_operations ramfs_file_operations; +extern file_operations ramfs_dir_operations; +extern inode_operations ramfs_dir_inode_operations; + +/* ================================================================== */ +/* ramfs_get_inode — allocate + init an inode for ramfs */ +/* ================================================================== */ + +inode* ramfs_get_inode(super_block* sb, const inode* dir, uint32 mode, uint32 rdev) +{ + inode* ip = new_inode(sb); + if (!ip) return nullptr; + + inode_init_owner(ip, dir, mode); + /* TODO: set timestamps to current time when we have a time source */ + + switch (mode & S_IFMT) { + case S_IFREG: + ip->i_op = nullptr; /* regular files don't need inode_ops for Phase 1 */ + ip->i_fop = &ramfs_file_operations; + break; + case S_IFDIR: + ip->i_op = &ramfs_dir_inode_operations; + ip->i_fop = &ramfs_dir_operations; + inc_nlink(ip); /* directories start with nlink=2 (. and parent's link) */ + break; + case S_IFLNK: + /* Symlink: stub for Phase 1 */ + break; + default: + /* Character/block devices: i_rdev */ + ip->i_rdev = rdev; + break; + } + + return ip; +} + +/* ================================================================== */ +/* Directory inode operations */ +/* ================================================================== */ + +static dentry* ramfs_lookup(inode* dir, dentry* de, unsigned int flags) +{ + (void)dir; + (void)flags; + /* For ramfs, everything is in dcache. If we get here, it means + * d_lookup() already failed, so this is a negative dentry. + * Just return nullptr to let the caller see de->d_inode == NULL. */ + return nullptr; +} + +static int ramfs_create(inode* dir, dentry* de, uint32 mode, bool excl) +{ + (void)excl; + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFREG, 0); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_mkdir(inode* dir, dentry* de, uint32 mode) +{ + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFDIR, 0); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + inc_nlink(dir); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_rmdir(inode* dir, dentry* de) +{ + /* Check if directory is empty */ + if (!list_empty(&de->d_children)) + return -ENOTEMPTY; + + drop_nlink(de->d_inode); /* remove . link */ + drop_nlink(de->d_inode); /* remove parent's link */ + drop_nlink(dir); + d_delete(de); + dir->i_size -= de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_unlink(inode* dir, dentry* de) +{ + drop_nlink(de->d_inode); + d_delete(de); + dir->i_size -= de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_link(dentry* old_de, inode* dir, dentry* new_de) +{ + inode* ip = old_de->d_inode; + if (!ip) return -ENOENT; + + inc_nlink(ip); + ihold(ip); + d_instantiate(new_de, ip); + dget(new_de); + dir->i_size += new_de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +static int ramfs_mknod(inode* dir, dentry* de, uint32 mode, uint32 rdev) +{ + inode* ip = ramfs_get_inode(dir->i_sb, dir, mode, rdev); + if (!ip) return -ENOMEM; + + d_instantiate(de, ip); + dget(de); + dir->i_size += de->d_name.len; + mark_inode_dirty(dir); + return 0; +} + +inode_operations ramfs_dir_inode_operations; + +void ramfs_dir_inode_ops_init() +{ + ramfs_dir_inode_operations.lookup = ramfs_lookup; + ramfs_dir_inode_operations.create = ramfs_create; + ramfs_dir_inode_operations.link = ramfs_link; + ramfs_dir_inode_operations.unlink = ramfs_unlink; + ramfs_dir_inode_operations.symlink = nullptr; + ramfs_dir_inode_operations.mkdir = ramfs_mkdir; + ramfs_dir_inode_operations.rmdir = ramfs_rmdir; + ramfs_dir_inode_operations.mknod = ramfs_mknod; + ramfs_dir_inode_operations.rename = nullptr; + ramfs_dir_inode_operations.setattr = nullptr; + ramfs_dir_inode_operations.getattr = nullptr; + ramfs_dir_inode_operations.get_link = nullptr; + ramfs_dir_inode_operations.permission = nullptr; +} diff --git a/fs/ramfs/super.cc b/fs/ramfs/super.cc new file mode 100644 index 0000000..6f929f2 --- /dev/null +++ b/fs/ramfs/super.cc @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * ramfs/super.cc — RamFS superblock: mount, fill_super, and init + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern inode* ramfs_get_inode(super_block* sb, const inode* dir, + uint32 mode, uint32 rdev); + +/* Superblock allocation helper (defined in super.cc) */ +extern super_block* alloc_super(file_system_type* type); + +/* ================================================================== */ +/* ramfs super_operations */ +/* ================================================================== */ + +static inode* ramfs_alloc_inode(super_block* sb) +{ + return new inode{}; +} + +static void ramfs_destroy_inode(inode* ip) +{ + /* Free ramfs_file_data if any */ + if (ip->i_private) { + struct ramfs_file_data { + char* buf; + uint64 capacity; + }; + auto* data = static_cast(ip->i_private); + delete[] data->buf; + delete data; + } + delete ip; +} + +static int ramfs_statfs(dentry* de, kstatfs* buf) +{ + /* Simple fake values */ + buf->f_type = 0x858458f6; /* RAMFS_MAGIC */ + buf->f_bsize = 4096; + buf->f_blocks = 0; + buf->f_bfree = 0; + buf->f_bavail = 0; + buf->f_files = 0; + buf->f_ffree = 0; + buf->f_namelen = 255; + buf->f_frsize = 4096; + buf->f_flags = 0; + return 0; +} + +static super_operations ramfs_sops; + +static void ramfs_sops_init() +{ + ramfs_sops.alloc_inode = ramfs_alloc_inode; + ramfs_sops.destroy_inode = ramfs_destroy_inode; + ramfs_sops.free_inode = nullptr; + ramfs_sops.dirty_inode = nullptr; + ramfs_sops.write_inode = nullptr; + ramfs_sops.drop_inode = nullptr; + ramfs_sops.evict_inode = nullptr; + ramfs_sops.put_super = nullptr; + ramfs_sops.sync_fs = nullptr; + ramfs_sops.statfs = ramfs_statfs; + ramfs_sops.umount_begin = nullptr; +} + +/* ================================================================== */ +/* ramfs_fill_super */ +/* ================================================================== */ + +static int ramfs_fill_super(super_block* sb, void* data, int silent) +{ + sb->s_op = &ramfs_sops; + sb->s_magic = 0x858458f6; + sb->s_blocksize = 4096; + sb->s_blocksize_bits = 12; + sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; + sb->s_time_gran = 1; + + /* Create root inode */ + inode* root_ip = ramfs_get_inode(sb, nullptr, S_IFDIR | 0755, 0); + if (!root_ip) return -ENOMEM; + + /* Create root dentry */ + sb->s_root = d_make_root(root_ip); + if (!sb->s_root) + return -ENOMEM; + + return 0; +} + +/* ================================================================== */ +/* ramfs_mount */ +/* ================================================================== */ + +static super_block* ramfs_mount(file_system_type* type, int flags, + const char* dev_name, void* data) +{ + super_block* sb = alloc_super(type); + if (!sb) return nullptr; + + int err = ramfs_fill_super(sb, data, flags & SB_SILENT); + if (err) { + delete sb; + return nullptr; + } + + return sb; +} + +static void ramfs_kill_sb(super_block* sb) +{ + /* Phase 1: no cleanup needed for ramfs */ + (void)sb; +} + +/* ================================================================== */ +/* Filesystem type registration */ +/* ================================================================== */ + +static file_system_type ramfs_fs_type; + +extern void ramfs_file_ops_init(); +extern void ramfs_dir_inode_ops_init(); + +void ramfs_init() +{ + /* Runtime init to avoid un-relocated absolute pointers in .data */ + ramfs_sops_init(); + ramfs_file_ops_init(); + ramfs_dir_inode_ops_init(); + + ramfs_fs_type.name = "ramfs"; + ramfs_fs_type.fs_flags = FS_NO_DEVICE; + ramfs_fs_type.mount = ramfs_mount; + ramfs_fs_type.kill_sb = ramfs_kill_sb; + list_init(&ramfs_fs_type.fs_list); + + register_filesystem(&ramfs_fs_type); +} + diff --git a/fs/super.cc b/fs/super.cc new file mode 100644 index 0000000..231b57c --- /dev/null +++ b/fs/super.cc @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * super.cc — Superblock lifecycle + filesystem type registry + */ + +#include +#include +#include +#include +#include +#include + +/* Global root filesystem entry points */ +vfsmount* g_root_mnt = nullptr; +dentry* g_root_dentry = nullptr; + +/* ================================================================== */ +/* Filesystem type registry */ +/* ================================================================== */ + +namespace { + +list_head g_fs_types; +Spinlock g_fs_type_lock; + +} // namespace + +int register_filesystem(file_system_type* fs) +{ + LockGuard guard(g_fs_type_lock); + list_add_tail(&fs->fs_list, &g_fs_types); + printk("[vfs] registered filesystem: %s\n", fs->name); + return 0; +} + +file_system_type* get_fs_type(const char* name) +{ + LockGuard guard(g_fs_type_lock); + list_node* pos; + list_for_each(pos, &g_fs_types) { + auto* fs = list_entry(pos, file_system_type, fs_list); + /* Simple strcmp */ + const char* a = fs->name; + const char* b = name; + while (*a && *a == *b) { a++; b++; } + if (*a == '\0' && *b == '\0') + return fs; + } + return nullptr; +} + +/* ================================================================== */ +/* Superblock allocation */ +/* ================================================================== */ + +super_block* alloc_super(file_system_type* type) +{ + auto* sb = new super_block{}; + if (!sb) return nullptr; + + list_init(&sb->s_list); + list_init(&sb->s_inodes); + sb->s_type = type; + sb->s_op = nullptr; + sb->s_root = nullptr; + sb->s_fs_info = nullptr; + sb->s_blocksize = 4096; + sb->s_blocksize_bits = 12; + sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; + sb->s_time_gran = 1; + sb->s_time_min = 0; + sb->s_time_max = 0x7FFFFFFFFFFFFFFFLL; + sb->s_flags = 0; + sb->s_magic = 0; + sb->s_active.store(1); + sb->s_count = 1; + sb->s_max_links = 0; + sb->s_d_op = nullptr; + sb->s_mounts = nullptr; + sb->s_dev = 0; + for (int i = 0; i < 32; i++) sb->s_id[i] = 0; + + return sb; +} + +/* ================================================================== */ +/* Init */ +/* ================================================================== */ + +void super_init() +{ + list_init(&g_fs_types); +} diff --git a/fs/vfs_init.cc b/fs/vfs_init.cc new file mode 100644 index 0000000..ec56e88 --- /dev/null +++ b/fs/vfs_init.cc @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vfs_init.cc — Bootstrap the VFS: mount ramfs root, set global root + * + * Called from kernel_main() after slab_init() and ramfs_init(). + * This is the moral equivalent of Linux's vfs_caches_init() + + * mnt_init() + init_rootfs(). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* vfs_init — mount ramfs as root filesystem */ +/* ================================================================== */ + +/* + * ramfs_mount is called internally via the file_system_type::mount + * callback registered by ramfs_init(). We look it up by name. + */ +extern file_system_type* get_fs_type(const char* name); +extern super_block* alloc_super(file_system_type* type); + +void vfs_init() +{ + /* 1. Look up the ramfs filesystem type */ + file_system_type* ramfs_type = get_fs_type("ramfs"); + if (!ramfs_type || !ramfs_type->mount) { + printk(KERN_ERR "vfs_init: ramfs not registered!\n"); + return; + } + + /* 2. Mount ramfs */ + super_block* sb = ramfs_type->mount(ramfs_type, 0, "ramfs", nullptr); + if (!sb || !sb->s_root) { + printk(KERN_ERR "vfs_init: failed to mount ramfs!\n"); + return; + } + + /* 3. Set global root */ + g_root_dentry = sb->s_root; + g_root_mnt = nullptr; /* TODO: allocate a proper vfsmount when mount.cc lands */ + + dget(g_root_dentry); /* Hold a reference for the global root */ + + printk(KERN_INFO "[vfs] root filesystem mounted (ramfs)\n"); + + /* 4. Set up VFS state for the current (BSP idle) task, + * which will be inherited by all subsequently created tasks. */ + TaskControlBlock* bsp = current_task(); + if (bsp) { + if (!bsp->files) { + bsp->files = alloc_files_struct(); + } + if (!bsp->fs) { + bsp->fs = alloc_fs_struct(); + if (bsp->fs) { + bsp->fs->root.de = g_root_dentry; + bsp->fs->root.mnt = g_root_mnt; + bsp->fs->pwd.de = g_root_dentry; + bsp->fs->pwd.mnt = g_root_mnt; + dget(g_root_dentry); /* root ref */ + dget(g_root_dentry); /* pwd ref */ + } + } + printk(KERN_INFO "[vfs] BSP task VFS context initialized\n"); + } +} diff --git a/include/common/fcntl.h b/include/common/fcntl.h deleted file mode 100644 index 6059538..0000000 --- a/include/common/fcntl.h +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -#define O_RDONLY 0x000 -#define O_WRONLY 0x001 -#define O_RDWR 0x002 -#define O_CREATE 0x200 -#define O_TRUNC 0x400 - -#define NOFILE 16 \ No newline at end of file diff --git a/include/common/types.h b/include/common/types.h deleted file mode 100644 index aa2cb08..0000000 --- a/include/common/types.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef unsigned long long uint64_t; - -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef signed long long int64_t; - -typedef uint64_t size_t; -typedef uint64_t uintptr_t; - -typedef uint64_t uint64; -typedef uint32_t uint32; -typedef uint16_t uint16; -typedef uint8_t uint8; -typedef unsigned int uint; -typedef unsigned short ushort; -typedef unsigned char uchar; - -typedef int64_t int64; -typedef int32_t int32; -typedef int16_t int16; -typedef int8_t int8; \ No newline at end of file diff --git a/include/drivers/plic.h b/include/drivers/plic.h deleted file mode 100644 index cdcd0ce..0000000 --- a/include/drivers/plic.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace PLIC -{ - // Global initialization (executed only once by hart 0) - void init(); - - // Per-core initialization (executed when each CPU starts) - void inithart(); - - // Claim interrupt: Get the currently occurring interrupt number (IRQ) - int claim(); - - // End of interrupt: notify the PLIC that this IRQ has been handled - void complete(int irq); -} // namespace PILC diff --git a/include/drivers/uart.h b/include/drivers/uart.h deleted file mode 100644 index a56624c..0000000 --- a/include/drivers/uart.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "drivers/uart_color.h" -#include "kernel/fdt.h" - -// UART register offsets (NS16550A) -#define RHR 0 // Receive Holding Register (read mode) -#define THR 0 // Transmit Holding Register (write mode) -#define IER 1 // Interrupt Enable Register -#define FCR 2 // FIFO Control Register -#define ISR 2 // Interrupt Status Register -#define LCR 3 // Line Control Register -#define LSR 5 // Line Status Register - -#define LSR_RX_READY 0x01 // input is waiting to be read from RHR -#define LSR_TX_IDLE 0x20 // THR can accept another character to send - -#define UART_RX_BUF_SIZE 128 - -// UART base address from FDT (use inline function to avoid macro issues) -static inline uint64 uart_base() { return g_devices.uart.base_addr; } - -namespace Drivers -{ - void uart_init(); - void uart_intr(); - int console_read(uint64 target, int len); - - void uart_putc(char c); - void uart_puts(const char *s); - void uart_put_int(int value); - void print_hex(uint64 value); -} \ No newline at end of file diff --git a/include/drivers/uart_color.h b/include/drivers/uart_color.h deleted file mode 100644 index 7ecc28b..0000000 --- a/include/drivers/uart_color.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -// ANSI Escape Codes for Modern Terminal Colors -#define ANSI_RESET "\033[0m" -#define ANSI_RED "\033[31m" -#define ANSI_GREEN "\033[32m" -#define ANSI_YELLOW "\033[33m" -#define ANSI_BLUE "\033[34m" -#define ANSI_CLEAR "\033[2J\033[1;1H" \ No newline at end of file diff --git a/include/drivers/virtio.h b/include/drivers/virtio.h deleted file mode 100644 index 9fda8cf..0000000 --- a/include/drivers/virtio.h +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -// VirtIO MMIO Base Address -#define VIRTIO0 0x10001000 - -// MMIO Registers (Modern / Version 2) -#define VIRTIO_MMIO_MAGIC_VALUE 0x000 -#define VIRTIO_MMIO_VERSION 0x004 -#define VIRTIO_MMIO_DEVICE_ID 0x008 -#define VIRTIO_MMIO_VENDOR_ID 0x00c -#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 -#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 -#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 -#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 -#define VIRTIO_MMIO_QUEUE_SEL 0x030 -#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 -#define VIRTIO_MMIO_QUEUE_NUM 0x038 -#define VIRTIO_MMIO_QUEUE_READY 0x044 -#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 -#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 -#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 -#define VIRTIO_MMIO_STATUS 0x070 - -// 64-bit Queue Address Registers (Modern) -#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 -#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 -#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 -#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 -#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0a0 -#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0a4 - -// Status Bits -#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 -#define VIRTIO_CONFIG_S_DRIVER 2 -#define VIRTIO_CONFIG_S_DRIVER_OK 4 -#define VIRTIO_CONFIG_S_FEATURES_OK 8 - -// Feature Bits -#define VIRTIO_BLK_F_RO 5 -#define VIRTIO_BLK_F_SCSI 7 -#define VIRTIO_BLK_F_CONFIG_WCE 11 -#define VIRTIO_BLK_F_MQ 12 -#define VIRTIO_F_ANY_LAYOUT 27 -#define VIRTIO_RING_F_INDIRECT_DESC 28 -#define VIRTIO_RING_F_EVENT_IDX 29 -#define VIRTIO_F_VERSION_1 32 - -// Descriptor Flags -#define VRING_DESC_F_NEXT 1 -#define VRING_DESC_F_WRITE 2 - -#define NUM_DESCS 8 - -#define VIRTIO_BLK_T_IN 0 -#define VIRTIO_BLK_T_OUT 1 - -// Device Configuration Layout (Offset 0x100) -#define VIRTIO_MMIO_CONFIG 0x100 - -namespace VirtIO -{ - struct VRingDesc - { - uint64 addr; - uint32 len; - uint16 flags; - uint16 next; - }; - - struct VRingAvail - { - uint16 flags; - uint16 idx; - uint16 ring[NUM_DESCS]; - }; - - struct VRingUsedElem - { - uint32 id; - uint32 len; - }; - - struct VRingUsed - { - uint16 flags; - uint16 idx; - struct VRingUsedElem ring[NUM_DESCS]; - }; - - struct __attribute__((packed)) BlockRequest - { - uint32 type; // 0=Read, 1=Write - uint32 reserved; - uint64 sector; - }; - - void init(); - void intr(); - void rw(int write, uint64 blockno, char *buf); - void test_rw(); - void test_bio(); -} \ No newline at end of file diff --git a/include/fs/fat32.h b/include/fs/fat32.h deleted file mode 100644 index 63d62ae..0000000 --- a/include/fs/fat32.h +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "fs/fs.h" -#include "kernel/buf.h" - -// Disk Layout Structures (On-Disk) - -// FAT32 Boot Sector (BPB) -struct FAT32_BPB -{ - uint8 jmpBoot[3]; - char oemName[8]; - uint16 bytesPerSector; - uint8 secPerClus; - uint16 reservedSecCnt; - uint8 numFATs; - uint16 rootEntCnt; - uint16 totSec16; - uint8 media; - uint16 FATSz16; - uint16 secPerTrk; - uint16 numHeads; - uint32 hiddSec; - uint32 totSec32; - uint32 FATSz32; - uint16 extFlags; - uint16 FSVer; - uint32 rootClus; - uint16 FSInfo; - uint16 bkBootSec; - uint8 reserved[12]; - uint8 drvNum; - uint8 reserved1; - uint8 bootSig; - uint32 volID; - char volLab[11]; - char filSysType[8]; -} __attribute__((packed)); - -// FAT32 Directory Entry (32 bytes) -struct FAT32_DirEnt -{ - char name[11]; // 8.3 filename - uint8 attr; // File attributes - uint8 ntRes; - uint8 crtTimeTenth; - uint16 crtTime; - uint16 crtDate; - uint16 lstAccDate; - uint16 fstClusHI; // High 16 bits of first cluster - uint16 wrtTime; - uint16 wrtDate; - uint16 fstClusLO; // Low 16 bits of first cluster - uint32 fileSize; -} __attribute__((packed)); - -#define ATTR_READ_ONLY 0x01 -#define ATTR_HIDDEN 0x02 -#define ATTR_SYSTEM 0x04 -#define ATTR_VOLUME_ID 0x08 -#define ATTR_DIRECTORY 0x10 -#define ATTR_ARCHIVE 0x20 -#define ATTR_LONG_NAME 0x0F - -// In-Memory Classes - -class FAT32FileSystem; - -class FAT32Inode : public Inode -{ -public: - FAT32FileSystem *fs; - - // FAT32 Private Data - uint32 first_clus; // The starting cluster number of the file - uint32 parent_clus; // Starting cluster number of the parent directory - uint32 dir_offset; // The byte offset of this directory entry in the parent directory - uint32 ent_sec; // Sector number of the directory entry - uint32 ent_off; // Offset of the directory entry within the sector - - FAT32Inode() : fs(nullptr), first_clus(0) {} - // Implement VFS Interface - void update() override; - int read(char *dst, uint32 off, uint32 n, int isUser) override; - int write(const char *src, uint32 off, uint32 n, int isUser) override; - Inode *lookup(const char *name) override; - Inode *create(const char *name, short type, short major, short minor) override; - void truncate() override; - int unlink(const char *name) override; -}; - -class FAT32FileSystem : public FileSystem -{ -public: - uint32 fat_start_sector; - uint32 data_start_sector; - uint32 root_cluster; - uint32 sectors_per_cluster; - uint32 bytes_per_cluster; - uint32 fat_sz; // Size of the FAT table (number of sectors) - - void init() override; - Inode* root() override; - - // Helper functions - uint32 next_cluster(uint32 cluster); // Check the FAT table for the next cluster - uint32 alloc_cluster(); // Assign a new cluster - uint32 cluster_to_sector(uint32 cluster); - - // directory entry parsing - void read_dir_ent(struct FAT32_DirEnt *de, char *filename); - // Release the entire cluster chain starting from the cluster - void free_cluster_chain(uint32 cluster); -}; - -extern FAT32FileSystem fat32_fs; - -void fat32_test(); \ No newline at end of file diff --git a/include/fs/file.h b/include/fs/file.h deleted file mode 100644 index 9aff1af..0000000 --- a/include/fs/file.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "fs/fs.h" - -enum FileType -{ - FD_NONE, - FD_INODE, - FD_DEVICE, - FD_PIPE -}; - -class Pipe; - -struct file -{ - enum FileType type; - int ref; - char readable; - char writable; - - Pipe *pipe; // pipeline pointer - Inode *ip; // VFS Inode - uint32 off; // Current Offset -}; - -class Pipe -{ -public: - static constexpr uint32_t SIZE = 512; - - Pipe() : nread(0), nwrite(0), read_open(true), write_open(true) - { - lock.init("pipe"); - } - - // forbid copy - Pipe(const Pipe &) = delete; - Pipe &operator=(const Pipe &) = delete; - - ~Pipe() - { - // - } - - // creates a pair of connected file objects - static int create_pair(struct file **f0, struct file **f1); - - int read(uint64_t addr, int n); - int write(uint64_t addr, int n); - void close(bool is_writer); - -private: - Spinlock lock; - char data[SIZE]; - uint32_t nread; // read - uint32_t nwrite; // write - bool read_open; - bool write_open; -}; - -namespace FileTable -{ - void init(); - struct file *alloc(); // Allocate a free file handle - struct file *dup(struct file *f); // Duplicate handle (sys_dup) - void close(struct file *f); // Close handle - - int stat(struct file *f, uint64 addr); - int read(struct file *f, uint64 addr, int n); - int write(struct file *f, uint64 addr, int n); - int lseek(struct file *f, int offset, int whence); -} // namespace FileTable diff --git a/include/fs/fs.h b/include/fs/fs.h deleted file mode 100644 index b511a84..0000000 --- a/include/fs/fs.h +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "fs/stat.h" - -class FileSystem; - -// VFS Inode (Abstract Base Class) - -class Inode -{ -private: - ; -public: - // VFS Common Fields - uint32 dev; // Device number (VirtIO Block ID) - uint32 inum; // Inode number - int ref_cnt; // Memory reference count (number of struct file or processes referencing it) - int valid; // Whether the data is valid (whether metadata has been read from disk) - - Mutex lock; // Sleep lock: protects internal Inode data and atomicity of read/write operations - - // Metadata Cache (for speeding up getattr) - short type; // T_DIR, T_FILE, T_DEVICE - short major; // Major device number (only for T_DEVICE) - short minor; // Minor device number (only for T_DEVICE) - short nlink; // Number of hard links - uint32 size; // File size - - Inode() : ref_cnt(0), valid(0) - { - lock.init("inode"); - } - virtual ~Inode() {} - - /* Core Operation Interface (Subclass Must Implement) */ - // Update metadata - // Sync size, type, links, etc. from memory back to disk - virtual void update() = 0; - // Read content - // Read n bytes from the file at offset into dst - // Returns the actual number of bytes read, returns -1 on error - virtual int read(char *dst, uint32 off, uint32 n, int isUser) = 0; - // 3. Write content - // Write n bytes from src to the file at the given offset - // Need to handle file growth - virtual int write(const char *src, uint32 off, uint32 n, int isUser) = 0; - // Directory Lookup (only valid when type == T_DIR) - // Search for a file named 'name' in the current directory and return its Inode pointer (locked) - // Return nullptr if not found - virtual Inode *lookup(const char *name) = 0; - // Create file/directory (only effective when type == T_DIR) - // Create a new entry in the current directory - virtual Inode *create(const char *name, short type, short major, short minor) = 0; - // Get file status (populate the stat structure) - // Default implementation, subclasses can override to provide more detailed information - virtual void getattr(struct kstat *st); - // Truncate file (set size to 0, release all data blocks) - virtual void truncate() = 0; - // Delete the file named 'name' from the directory (Unlink) - virtual int unlink(const char *name) = 0; -}; - -// File System Abstraction (SuperBlock) -class FileSystem -{ -private: - ; -public: - virtual void init() = 0; - virtual Inode* root() = 0; - virtual ~FileSystem() {}; -}; - -// Inode Cache Interface (Global Function) -namespace VFS -{ - void init(); - - // Decrease the reference count, and if it reaches 0, free the memory - void iput(Inode *ip); - // Increase reference count - Inode *idup(Inode *ip); - // lock and load data - void ilock(Inode *ip); - // unlock - void iunlock(Inode *ip); - void iunlockput(Inode *ip); - // Mount the root filesystem - void mount_root(Inode *root); - // Get system root node - Inode *get_root(); - // Path Analysis - Inode *namei(const char *path); - // Find the parent directory inode of the path, and copy the last part of the path (the file name) into the name buffer - Inode *nameiparent(const char *path, char *name); - // Register a file system - void register_fs(FileSystem *fs); - -} // namespace VFS diff --git a/include/fs/stat.h b/include/fs/stat.h deleted file mode 100644 index e60ae42..0000000 --- a/include/fs/stat.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -#define T_DIR 1 // Directory -#define T_FILE 2 // File -#define T_DEVICE 3 // Device - -struct kstat -{ - int dev; // File System's disk device - uint32 ino; // Inode number - short type; //Type of file - short nlink; // Number of links to file - uint64 size; // Size of file in bytes - - uint32 mode; // Protection: rwx - uint32 uid; // User ID - uint32 gid; // Group ID - uint32 atime; // Access time - uint32 mtime; // Modified time - uint32 ctime; // Change time -}; diff --git a/include/kernel/buf.h b/include/kernel/buf.h deleted file mode 100644 index d0d849c..0000000 --- a/include/kernel/buf.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" - -#define BSIZE 1024 -#define NBUF 30 - -struct Buf -{ - int valid; // Whether the data has been read from the disk (1=valid, 0=invalid) - int disk; // Whether it has been held by the driver (dirty bit/write-back flag is usually extended here) - uint32 dev; // Device Number - uint32 blockno; // Block number - uint32 refcnt; // Reference Counting - struct Buf *prev; // LRU linked list - struct Buf *next; - uchar data[BSIZE]; - - Mutex lock; // Sleep Lock -}; - -namespace BufferCache -{ - void init(); - struct Buf *bread(uint32 dev, uint32 blockno); - void bwrite(struct Buf *b); - void brelse(struct Buf *b); - void bpin(struct Buf *b); - void bunpin(struct Buf *b); -} // namespace BufferCache diff --git a/include/kernel/config.h b/include/kernel/config.h deleted file mode 100644 index 49df2ff..0000000 --- a/include/kernel/config.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "kernel/fdt.h" - -// Legacy compat — these are still referenced in some places -extern uint64 g_uart_base; -extern uint64 g_dtb_addr; \ No newline at end of file diff --git a/include/kernel/cpu.h b/include/kernel/cpu.h deleted file mode 100644 index 62bdc17..0000000 --- a/include/kernel/cpu.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/proc_def.h" - -struct RunQueue; - -struct cpu -{ - struct Proc *proc; - struct Context context; - int noff; - int intena; - - struct RunQueue *runqueue; -}; - - -extern struct cpu cpus[]; -struct cpu *mycpu(); \ No newline at end of file diff --git a/include/kernel/elf.h b/include/kernel/elf.h deleted file mode 100644 index 30193d2..0000000 --- a/include/kernel/elf.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian - -// ELF File Header -struct elfhdr -{ - uint32 magic; // Must equal ELF_MAGIC - uint8 elf[12]; - uint16 type; - uint16 machine; - uint32 version; - uint64 entry; // Program Entry Point (Virtual Address) - uint64 phoff; // File offset of the Program Header Table - uint64 shoff; - uint32 flags; - uint16 ehsize; - uint16 phentsize; - uint16 phnum; // Number of Program Headers - uint16 shentsize; - uint16 shnum; - uint16 shstrndx; -}; - -// Program Header -// Describes how to load segments from a file into memory -struct proghdr -{ - uint32 type; - uint32 flags; - uint64 off; // Offset of the section in the file - uint64 vaddr; // Virtual address of the segment in memory - uint64 paddr; - uint64 filesz; // Size of the section in the file - uint64 memsz; // The size of the segment in memory (memsz >= filesz) - uint64 align; -}; - -// Values for Proghdr type -#define ELF_PROG_LOAD 1 - -// Flag bits for Proghdr flags -#define ELF_PROG_FLAG_EXEC 1 -#define ELF_PROG_FLAG_WRITE 2 -#define ELF_PROG_FLAG_READ 4 \ No newline at end of file diff --git a/include/kernel/exec.h b/include/kernel/exec.h deleted file mode 100644 index 304e03f..0000000 --- a/include/kernel/exec.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -namespace Exec -{ - int exec(char *path, char **argv); -} \ No newline at end of file diff --git a/include/kernel/fdt.h b/include/kernel/fdt.h deleted file mode 100644 index 89cbae6..0000000 --- a/include/kernel/fdt.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * LumeOS - FDT Device Discovery - */ - -#pragma once -#include "common/types.h" - -// Single device descriptor (populated from FDT) -struct FdtDevice { - uint64 base_addr; // MMIO base address - uint64 size; // MMIO region size - uint32 irq; // Interrupt number (0 = none) -}; - -// Device registry — populated by fdt_parse(), consumed by drivers -struct DeviceRegistry { - // Memory - uint64 mem_base; - uint64 mem_size; - - // Core platform devices - FdtDevice uart; - FdtDevice plic; - FdtDevice clint; - - // VirtIO devices (multiple possible) - static constexpr int MAX_VIRTIO = 8; - FdtDevice virtio[MAX_VIRTIO]; - int virtio_count; - - // MMIO ranges for kvminit() to map - static constexpr int MAX_MMIO = 16; - struct MmioRegion { - uint64 base; - uint64 size; - } mmio_regions[MAX_MMIO]; - int mmio_count; -}; - -extern DeviceRegistry g_devices; - -// Parse FDT and populate g_devices -void fdt_parse(uint64 dtb); diff --git a/include/kernel/mm.h b/include/kernel/mm.h deleted file mode 100644 index e3de09a..0000000 --- a/include/kernel/mm.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace VM -{ - extern uint64 *kernel_pagetable; - - void kvminit(); - void kvminithart(); - - uint64 *uvmcreate(); - void uvminit(uint64 *pagetable, uchar *src, uint64 sz); - uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm); - uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz); - void uvmfree(uint64 *pagetable, uint64 sz); - int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz); - void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free); - - int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len); - int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len); - int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max); - - int handle_cow_fault(uint64 *pagetable, uint64 va); - int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm); - - uint64 walkaddr(uint64 *pagetable, uint64 va); -} \ No newline at end of file diff --git a/include/kernel/pmm.h b/include/kernel/pmm.h deleted file mode 100644 index 261786d..0000000 --- a/include/kernel/pmm.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "kernel/fdt.h" // For g_devices - -// Physical Memory Layout Definition -#define KERNBASE 0x80000000 - -// Dynamic memory size from FDT -// Default to 128MB if FDT not present/parsed -extern uint64 g_phys_size; - -#define PHYSIZE (g_phys_size) -#define PHYSTOP (KERNBASE + PHYSIZE) - -// Buddy System Max Order -// Order 10 => 2^10 pages = 1024 * 4KB = 4MB contiguous block -// Order 11 => 2^11 pages = 8MB -#define MAX_ORDER 11 - -// Page flag -#define PG_used 0x01 // This page is occupied -#define PG_reserved 0x02 // This page is reserved (kernel code/data, DTB, MemMap) - -// Physical Page Metadata Structure -struct Page -{ - uint8 flags; // Page Status (PG_used, PG_reserved) - uint8 order; // If it is the first page of a free block, record the order of the current block - uint16 refcnt; // Reference Counting - struct Page *next; // Free list pointer (points to the next free block of the same size) - struct Page *prev; // Doubly linked list (easy to remove) -}; - -namespace PMM -{ - // Initialize physical memory management - void init(); - - // Core Allocation Interface (Buddy System) - // order: 0 ~ MAX_ORDER-1 - void *alloc_pages(int order); - void free_pages(void *pa, int order); - - // Compatible with old interface (allocate single page with order=0 and automatically reset to zero) - void *alloc_page(); - void free_page(void *pa); - - // Helper function - struct Page *pa_to_page(uint64 pa); - uint64 page_to_pa(struct Page *page); - - // Debug information - void print_free_lists(); -} \ No newline at end of file diff --git a/include/kernel/proc.h b/include/kernel/proc.h deleted file mode 100644 index e11dc47..0000000 --- a/include/kernel/proc.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/proc_def.h" -#include "kernel/spinlock.h" - -namespace ProcManager -{ - void init(); - void scheduler(); - void yield(); - - void create_kernel_thread(void (*func)(), const char *name); - - void user_init(); - void exit(int status); - int wait(uint64 addr); - - void sleep(void *chan, Spinlock *lk); - void wakeup(void *chan); - - int growproc(int n); - - int kill(int pid); -} - -void forkret(); -struct Proc *myproc(); -void usertrapret(); -int fork(); \ No newline at end of file diff --git a/include/kernel/proc_def.h b/include/kernel/proc_def.h deleted file mode 100644 index 50f27d7..0000000 --- a/include/kernel/proc_def.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "common/fcntl.h" - -struct cpu; -struct Trapframe; -class Inode; -struct file; - -enum ProcState -{ - UNUSED, - USED, - SLEEPING, - RUNNABLE, - RUNNING, - ZOMBIE, -}; - -#define PRIO_MAX 0 -#define PRIO_MIN 3 -#define N_PRIO 4 - -// Context structure -struct Context -{ - uint64 ra; - uint64 sp; - uint64 s0; - uint64 s1; - uint64 s2; - uint64 s3; - uint64 s4; - uint64 s5; - uint64 s6; - uint64 s7; - uint64 s8; - uint64 s9; - uint64 s10; - uint64 s11; -}; - -struct Proc -{ - struct Context context; - struct Trapframe *tf; - uint64 *pagetable; - enum ProcState state; - int pid; - void *kstack; - char name[16]; - uint64 sz; - struct Proc *parent; - void *chan; - int xstate; - int killed; - - Inode *cwd; - struct file *ofile[NOFILE]; - - struct Proc *next; - struct Proc *prev; - - struct Proc *all_next; - struct Proc *all_prev; - - int priority; - int ticks; - struct cpu *cpu; -}; \ No newline at end of file diff --git a/include/kernel/riscv.h b/include/kernel/riscv.h deleted file mode 100644 index 0c586a9..0000000 --- a/include/kernel/riscv.h +++ /dev/null @@ -1,404 +0,0 @@ -#ifndef __ASSEMBLER__ - -// which hart (core) is this? -static inline uint64 -r_mhartid() -{ - uint64 x; - asm volatile("csrr %0, mhartid" : "=r" (x) ); - return x; -} - -// Machine Status Register, mstatus - -#define MSTATUS_MPP_MASK (3L << 11) // previous mode. -#define MSTATUS_MPP_M (3L << 11) -#define MSTATUS_MPP_S (1L << 11) -#define MSTATUS_MPP_U (0L << 11) - -static inline uint64 -r_mstatus() -{ - uint64 x; - asm volatile("csrr %0, mstatus" : "=r" (x) ); - return x; -} - -static inline void -w_mstatus(uint64 x) -{ - asm volatile("csrw mstatus, %0" : : "r" (x)); -} - -// machine exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void -w_mepc(uint64 x) -{ - asm volatile("csrw mepc, %0" : : "r" (x)); -} - -// Supervisor Status Register, sstatus - -#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User -#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable -#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable -#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable -#define SSTATUS_UIE (1L << 0) // User Interrupt Enable - -static inline uint64 -r_sstatus() -{ - uint64 x; - asm volatile("csrr %0, sstatus" : "=r" (x) ); - return x; -} - -static inline void -w_sstatus(uint64 x) -{ - asm volatile("csrw sstatus, %0" : : "r" (x)); -} - -// Supervisor Interrupt Pending -static inline uint64 -r_sip() -{ - uint64 x; - asm volatile("csrr %0, sip" : "=r" (x) ); - return x; -} - -static inline void -w_sip(uint64 x) -{ - asm volatile("csrw sip, %0" : : "r" (x)); -} - -// Supervisor Interrupt Enable -#define SIE_SEIE (1L << 9) // external -#define SIE_STIE (1L << 5) // timer -#define SIE_SSIE (1L << 1) // software -static inline uint64 -r_sie() -{ - uint64 x; - asm volatile("csrr %0, sie" : "=r" (x) ); - return x; -} - -static inline void -w_sie(uint64 x) -{ - asm volatile("csrw sie, %0" : : "r" (x)); -} - -// Machine-mode Interrupt Enable -#define MIE_STIE (1L << 5) // supervisor timer -static inline uint64 -r_mie() -{ - uint64 x; - asm volatile("csrr %0, mie" : "=r" (x) ); - return x; -} - -static inline void -w_mie(uint64 x) -{ - asm volatile("csrw mie, %0" : : "r" (x)); -} - -// supervisor exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void -w_sepc(uint64 x) -{ - asm volatile("csrw sepc, %0" : : "r" (x)); -} - -static inline uint64 -r_sepc() -{ - uint64 x; - asm volatile("csrr %0, sepc" : "=r" (x) ); - return x; -} - -// Machine Exception Delegation -static inline uint64 -r_medeleg() -{ - uint64 x; - asm volatile("csrr %0, medeleg" : "=r" (x) ); - return x; -} - -static inline void -w_medeleg(uint64 x) -{ - asm volatile("csrw medeleg, %0" : : "r" (x)); -} - -// Machine Interrupt Delegation -static inline uint64 -r_mideleg() -{ - uint64 x; - asm volatile("csrr %0, mideleg" : "=r" (x) ); - return x; -} - -static inline void -w_mideleg(uint64 x) -{ - asm volatile("csrw mideleg, %0" : : "r" (x)); -} - -// Supervisor Trap-Vector Base Address -// low two bits are mode. -static inline void -w_stvec(uint64 x) -{ - asm volatile("csrw stvec, %0" : : "r" (x)); -} - -static inline uint64 -r_stvec() -{ - uint64 x; - asm volatile("csrr %0, stvec" : "=r" (x) ); - return x; -} - -// Supervisor Timer Comparison Register -static inline uint64 -r_stimecmp() -{ - uint64 x; - // asm volatile("csrr %0, stimecmp" : "=r" (x) ); - asm volatile("csrr %0, 0x14d" : "=r" (x) ); - return x; -} - -static inline void -w_stimecmp(uint64 x) -{ - // asm volatile("csrw stimecmp, %0" : : "r" (x)); - asm volatile("csrw 0x14d, %0" : : "r" (x)); -} - -// Machine Environment Configuration Register -static inline uint64 -r_menvcfg() -{ - uint64 x; - // asm volatile("csrr %0, menvcfg" : "=r" (x) ); - asm volatile("csrr %0, 0x30a" : "=r" (x) ); - return x; -} - -static inline void -w_menvcfg(uint64 x) -{ - //asm volatile("csrw menvcfg, %0" : : "r" (x)); - asm volatile("csrw 0x30a, %0" : : "r" (x)); -} - -// Physical Memory Protection -static inline void -w_pmpcfg0(uint64 x) -{ - asm volatile("csrw pmpcfg0, %0" : : "r" (x)); -} - -static inline void -w_pmpaddr0(uint64 x) -{ - asm volatile("csrw pmpaddr0, %0" : : "r" (x)); -} - -// use riscv's sv39 page table scheme. -#define SATP_SV39 (8L << 60) - -#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) - -// supervisor address translation and protection; -// holds the address of the page table. -static inline void -w_satp(uint64 x) -{ - asm volatile("csrw satp, %0" : : "r" (x)); -} - -static inline uint64 -r_satp() -{ - uint64 x; - asm volatile("csrr %0, satp" : "=r" (x) ); - return x; -} - -// Supervisor Trap Cause -static inline uint64 -r_scause() -{ - uint64 x; - asm volatile("csrr %0, scause" : "=r" (x) ); - return x; -} - -// Supervisor Trap Value -static inline uint64 -r_stval() -{ - uint64 x; - asm volatile("csrr %0, stval" : "=r" (x) ); - return x; -} - -// Machine-mode Counter-Enable -static inline void -w_mcounteren(uint64 x) -{ - asm volatile("csrw mcounteren, %0" : : "r" (x)); -} - -static inline uint64 -r_mcounteren() -{ - uint64 x; - asm volatile("csrr %0, mcounteren" : "=r" (x) ); - return x; -} - -// machine-mode cycle counter -static inline uint64 -r_time() -{ - uint64 x; - asm volatile("csrr %0, time" : "=r" (x) ); - return x; -} - -// enable device interrupts -static inline void -intr_on() -{ - w_sstatus(r_sstatus() | SSTATUS_SIE); -} - -// disable device interrupts -static inline void -intr_off() -{ - w_sstatus(r_sstatus() & ~SSTATUS_SIE); -} - -// are device interrupts enabled? -static inline int -intr_get() -{ - uint64 x = r_sstatus(); - return (x & SSTATUS_SIE) != 0; -} - -static inline uint64 -r_sp() -{ - uint64 x; - asm volatile("mv %0, sp" : "=r" (x) ); - return x; -} - -static inline uint64 -r_fp() -{ - uint64 x; - asm volatile("mv %0, s0" : "=r" (x) ); - return x; -} - -// read and write tp, the thread pointer, which xv6 uses to hold -// this core's hartid (core number), the index into cpus[]. -static inline uint64 -r_tp() -{ - uint64 x; - asm volatile("mv %0, tp" : "=r" (x) ); - return x; -} - -static inline void -w_tp(uint64 x) -{ - asm volatile("mv tp, %0" : : "r" (x)); -} - -static inline uint64 -r_ra() -{ - uint64 x; - asm volatile("mv %0, ra" : "=r" (x) ); - return x; -} - -// flush the TLB. -static inline void -sfence_vma() -{ - // the zero, zero means flush all TLB entries. - asm volatile("sfence.vma zero, zero"); -} - -typedef uint64 pte_t; -typedef uint64 *pagetable_t; // 512 PTEs - -#endif // __ASSEMBLER__ - -#define PGSIZE 4096 // bytes per page -#define PGSHIFT 12 // bits of offset within a page - -#ifdef LAB_PGTBL -#define SUPERPGSIZE (2 * (1 << 20)) // bytes per page -#define SUPERPGROUNDUP(sz) (((sz)+SUPERPGSIZE-1) & ~(SUPERPGSIZE-1)) -#define SUPERPGROUNDDOWN(sz) (SUPERPGROUNDUP(sz)-SUPERPGSIZE) -#endif - -#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1)) -#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) - -#define PTE_V (1L << 0) // valid -#define PTE_R (1L << 1) -#define PTE_W (1L << 2) -#define PTE_X (1L << 3) -#define PTE_U (1L << 4) // user can access - -// COW Flag (PTE bit 8) -#define PTE_COW (1L << 8) - -#if defined(LAB_MMAP) || defined(LAB_PGTBL) || defined(LAB_COW) -#define PTE_LEAF(pte) (((pte) & PTE_R) | ((pte) & PTE_W) | ((pte) & PTE_X)) -#endif - -// shift a physical address to the right place for a PTE. -#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) - -#define PTE2PA(pte) (((pte) >> 10) << 12) - -#define PTE_FLAGS(pte) ((pte) & 0x3FF) - -// extract the three 9-bit page table indices from a virtual address. -#define PXMASK 0x1FF // 9 bits -#define PXSHIFT(level) (PGSHIFT+(9*(level))) -#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) - -// one beyond the highest possible virtual address. -// MAXVA is actually one bit less than the max allowed by -// Sv39, to avoid having to sign-extend virtual addresses -// that have the high bit set. -#define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) -#define TRAMPOLINE (MAXVA - PGSIZE) -#define TRAPFRAME (TRAMPOLINE - PGSIZE) \ No newline at end of file diff --git a/include/kernel/runqueue.h b/include/kernel/runqueue.h deleted file mode 100644 index 14704ce..0000000 --- a/include/kernel/runqueue.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "kernel/spinlock.h" -#include "kernel/proc_def.h" - -struct RunQueue -{ - Spinlock lock; - struct Proc *heads[N_PRIO]; - struct Proc *tails[N_PRIO]; - uint64 count; - - void init(const char *name); - void enqueue(struct Proc *p); - struct Proc *dequeue(); - struct Proc *steal(); -}; \ No newline at end of file diff --git a/include/kernel/sbi.h b/include/kernel/sbi.h deleted file mode 100644 index a6be5b3..0000000 --- a/include/kernel/sbi.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -// SBI Extension IDs -#define SBI_EID_SRST 0x53525354 - -// SBI Function IDs for SRST -#define SBI_FID_SRST_RESET 0 - -// Reset Types -#define SBI_SRST_TYPE_SHUTDOWN 0 -#define SBI_SRST_TYPE_COLD_REBOOT 1 -#define SBI_SRST_TYPE_WARM_REBOOT 2 - -// Reset Reasons -#define SBI_SRST_REASON_NONE 0 -#define SBI_SRST_REASON_SYSTEM_FAILURE 1 - -namespace SBI -{ - void set_timer(uint64 stime_value); - void sbi_shutdown(); - void sbi_reboot(); -} // namespace SBI diff --git a/include/kernel/slab.h b/include/kernel/slab.h deleted file mode 100644 index bb7b699..0000000 --- a/include/kernel/slab.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace Slab -{ - void init(); - - void *kmalloc(uint64 size); - void kfree(void *ptr); -} \ No newline at end of file diff --git a/include/kernel/spinlock.h b/include/kernel/spinlock.h deleted file mode 100644 index 1511199..0000000 --- a/include/kernel/spinlock.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -struct cpu; - -// Spinlock -// Features: Non-interruptible, busy-wait loop, extremely short hold time. -// Scenario: Protecting core data such as scheduler, interrupt handling, PMM, etc. -class Spinlock -{ -public: - volatile uint32 locked; // Whether it is locked (0: unlocked, 1: locked) - const char *name; // Lock Name (for debugging) - struct cpu *cpu; // The CPU holding the lock - - // Constructor - Spinlock() : locked(0), name("uninit"), cpu(nullptr) {} - - // Initialize lock name - void init(const char *name); - - // Acquire lock - void acquire(); - - // Release lock - void release(); - - // Check if the current CPU holds the lock - bool holding(); -}; - -// Mutex -// Features: Allows interruption, allows sleeping (yield CPU), can hold for a long time. -// Scenarios: File system IO, device driver waiting, long-term buffering operations. -class Mutex -{ -public: - uint32 locked; // Whether it is locked - const char *name; // Lock Name - int pid; // PID of the process holding the lock (for debugging) - - Spinlock lk; // Internal spin lock, protects the state of the Mutex itself - - void init(const char *name); - void acquire(); - void release(); - bool holding(); -}; \ No newline at end of file diff --git a/include/kernel/sync.h b/include/kernel/sync.h deleted file mode 100644 index 7f7c208..0000000 --- a/include/kernel/sync.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "kernel/proc_def.h" - - -struct WaitQueue -{ - Spinlock lock; - struct Proc *head; - struct Proc *tail; - - void init(const char *name); - void sleep(); - void wakeup(); - void wakeup_all(); -}; - -struct Semaphore -{ - int value; - WaitQueue wq; - Spinlock lock; - - void init(int val, const char *name); - void wait(); - void post(); -}; - -struct CondVar -{ - WaitQueue wq; - - void init(const char *name); - void wait(Spinlock *mutex_lock); - void signal(); - void broadcast(); -}; \ No newline at end of file diff --git a/include/kernel/syscall.h b/include/kernel/syscall.h deleted file mode 100644 index 3594c08..0000000 --- a/include/kernel/syscall.h +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * Lume OS - Linux Compatibility Layer - * - * Strictly aligned with Linux RISC-V 64 ABI (asm-generic) - * Reference: linux/usr/include/asm-generic/unistd.h - */ - -#pragma once - -// File System -#define SYS_getcwd 17 -#define SYS_dup 23 -#define SYS_dup3 24 -#define SYS_fcntl 25 -#define SYS_ioctl 29 -#define SYS_mknodat 33 -#define SYS_mkdirat 34 -#define SYS_unlinkat 35 -#define SYS_linkat 37 -#define SYS_umount2 39 -#define SYS_mount 40 -#define SYS_statfs 43 -#define SYS_ftruncate 46 -#define SYS_faccessat 48 -#define SYS_chdir 49 -#define SYS_fchmod 52 -#define SYS_fchmodat 53 -#define SYS_fchownat 54 -#define SYS_openat 56 -#define SYS_close 57 -#define SYS_vhangup 58 -#define SYS_pipe2 59 -#define SYS_getdents64 61 -#define SYS_lseek 62 -#define SYS_read 63 -#define SYS_write 64 -#define SYS_readv 65 -#define SYS_writev 66 -#define SYS_pread64 67 -#define SYS_pwrite64 68 -#define SYS_pselect6 72 -#define SYS_ppoll 73 -#define SYS_readlinkat 78 -#define SYS_fstatat 79 // newfstatat -#define SYS_fstat 80 - -// Process Management -#define SYS_exit 93 -#define SYS_exit_group 94 -#define SYS_set_tid_address 96 -#define SYS_nanosleep 101 -#define SYS_kill 129 -#define SYS_rt_sigaction 134 -#define SYS_times 153 -#define SYS_uname 160 -#define SYS_gettimeofday 169 -#define SYS_getpid 172 -#define SYS_getppid 173 -#define SYS_getuid 174 -#define SYS_geteuid 175 -#define SYS_getgid 176 -#define SYS_getegid 177 -#define SYS_sysinfo 179 -#define SYS_brk 214 -#define SYS_munmap 215 -#define SYS_mremap 216 -#define SYS_clone 220 -#define SYS_execve 221 -#define SYS_mmap 222 -#define SYS_wait4 260 -#define SYS_prlimit64 261 - -// System -#define SYS_reboot 142 // Used for shutdown - -// === Custom / Debug (Non-Standard) === -// Placed above 1000 to avoid conflicts with Linux ABI -#define SYS_putc 1000 -#define SYS_disk_test 1001 - -#ifndef __ASSEMBLER__ -void syscall(); -#endif \ No newline at end of file diff --git a/include/kernel/timer.h b/include/kernel/timer.h deleted file mode 100644 index 8b97bbe..0000000 --- a/include/kernel/timer.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#include "common/types.h" - -namespace Timer{ - uint64 get_time(); - void set_next_trigger(); - void init(); - - void tick(); - uint64 get_ticks(); - Spinlock *get_lock(); - void *get_tick_chan(); -} \ No newline at end of file diff --git a/include/kernel/trap.h b/include/kernel/trap.h deleted file mode 100644 index ddc1d12..0000000 --- a/include/kernel/trap.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -struct Trapframe -{ - /* 0 */ uint64 kernel_satp; - /* 8 */ uint64 kernel_sp; - /* 16 */ uint64 kernel_trap; - /* 24 */ uint64 epc; - /* 32 */ uint64 kernel_hartid; - - /* 40 */ uint64 ra; // x1 - /* 48 */ uint64 sp; // x2 - /* 56 */ uint64 gp; // x3 - /* 64 */ uint64 tp; // x4 - /* 72 */ uint64 t0; // x5 - /* 80 */ uint64 t1; // x6 - /* 88 */ uint64 t2; // x7 - /* 96 */ uint64 s0; // x8 (Frame Pointer) - /* 104 */ uint64 s1; // x9 - /* 112 */ uint64 a0; // x10 - /* 120 */ uint64 a1; // x11 - /* 128 */ uint64 a2; // x12 - /* 136 */ uint64 a3; // x13 - /* 144 */ uint64 a4; // x14 - /* 152 */ uint64 a5; // x15 - /* 160 */ uint64 a6; // x16 - /* 168 */ uint64 a7; // x17 - /* 176 */ uint64 s2; // x18 - /* 184 */ uint64 s3; // x19 - /* 192 */ uint64 s4; // x20 - /* 200 */ uint64 s5; // x21 - /* 208 */ uint64 s6; // x22 - /* 216 */ uint64 s7; // x23 - /* 224 */ uint64 s8; // x24 - /* 232 */ uint64 s9; // x25 - /* 240 */ uint64 s10; // x26 - /* 248 */ uint64 s11; // x27 - /* 256 */ uint64 t3; // x28 - /* 264 */ uint64 t4; // x29 - /* 272 */ uint64 t5; // x30 - /* 280 */ uint64 t6; // x31 -}; - -namespace Trap -{ - void init(); - void inithart(); - int devintr(); -} \ No newline at end of file diff --git a/include/lib/errno.h b/include/lib/errno.h deleted file mode 100644 index f0c1436..0000000 --- a/include/lib/errno.h +++ /dev/null @@ -1,168 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Lume OS - Linux Compatibility Layer - * - * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * 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 -#define _ERRNO_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* 1-34: 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 */ - -/* * CRITICAL FIX: Missing definitions (35-37) from asm-generic - * Without these, your file system and locking code will fail compliance. - */ -#define EDEADLK 35 /* Resource deadlock would occur */ -#define ENAMETOOLONG 36 /* File name too long */ -#define ENOLCK 37 /* No record locks available */ - -#define ENOSYS 38 /* Invalid system call number */ -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ - -#define EDEADLOCK EDEADLK - -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Can not access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ -#define EOWNERDEAD 130 /* Owner died */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ -#define ERFKILL 132 /* Operation not possible due to RF-kill */ -#define EHWPOISON 133 /* Memory page has hardware error */ - -extern __thread int errno; - - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/include/lib/string.h b/include/lib/string.h deleted file mode 100644 index 6a74942..0000000 --- a/include/lib/string.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - - -void *memset(void *dst, int c, uint n); -int memcmp(const void *v1, const void *v2, uint n); -void *memmove(void *dst, const void *src, uint n); -void *memcpy(void *dst, const void *src, uint n); - - -void strcpy(char *dst, const char *src); -int strlen(const char *s); -int strcmp(const char *s1, const char *s2); -int strcasecmp(const char *s1, const char *s2); \ No newline at end of file diff --git a/include/libc-shim/bits/alltypes.h b/include/libc-shim/bits/alltypes.h new file mode 100644 index 0000000..cf21a5e --- /dev/null +++ b/include/libc-shim/bits/alltypes.h @@ -0,0 +1,15 @@ +/* Shim: provide types musl string functions need */ +#ifndef _BITS_ALLTYPES_H +#define _BITS_ALLTYPES_H + +#ifndef __cplusplus +typedef unsigned long size_t; +#endif + +typedef long ssize_t; +typedef unsigned long uintptr_t; + +/* locale_t stub — unused in kernel, but declared by some headers */ +typedef void *locale_t; + +#endif diff --git a/include/libc-shim/endian.h b/include/libc-shim/endian.h new file mode 100644 index 0000000..8346386 --- /dev/null +++ b/include/libc-shim/endian.h @@ -0,0 +1,9 @@ +/* Shim: endianness for musl string functions */ +#ifndef _ENDIAN_H +#define _ENDIAN_H + +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN /* RISC-V is LE */ + +#endif diff --git a/include/libc-shim/features.h b/include/libc-shim/features.h new file mode 100644 index 0000000..a3466c8 --- /dev/null +++ b/include/libc-shim/features.h @@ -0,0 +1,12 @@ +/* Shim: musl expects this header */ +#ifndef _FEATURES_H +#define _FEATURES_H + +/* weak_alias: musl uses this for symbol aliasing (e.g. __memrchr -> memrchr) */ +#define weak_alias(old, new) \ + extern __typeof(old) new __attribute__((__weak__, __alias__(#old))) + +/* hidden attribute used by some musl sources */ +#define hidden __attribute__((__visibility__("hidden"))) + +#endif diff --git a/include/libc-shim/limits.h b/include/libc-shim/limits.h new file mode 100644 index 0000000..a1d546e --- /dev/null +++ b/include/libc-shim/limits.h @@ -0,0 +1,17 @@ +/* Shim: limits for musl string functions (memmem uses SIZE_MAX) */ +#ifndef _LIMITS_H +#define _LIMITS_H + +#define CHAR_BIT 8 +#define UCHAR_MAX 255 +#define SCHAR_MAX 127 +#define SCHAR_MIN (-128) +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) +#define UINT_MAX 4294967295U +#define LONG_MAX 9223372036854775807L +#define LONG_MIN (-LONG_MAX - 1L) +#define ULONG_MAX 18446744073709551615UL +#define SIZE_MAX ULONG_MAX + +#endif diff --git a/include/libc-shim/stdint.h b/include/libc-shim/stdint.h new file mode 100644 index 0000000..650bdcf --- /dev/null +++ b/include/libc-shim/stdint.h @@ -0,0 +1,21 @@ +/* Shim: stdint types for musl string functions */ +#ifndef _STDINT_H +#define _STDINT_H + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long uint64_t; +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef signed long int64_t; +typedef unsigned long uintptr_t; +typedef long intptr_t; + +#define UINT8_MAX 0xFF +#define UINT16_MAX 0xFFFF +#define UINT32_MAX 0xFFFFFFFFU +#define UINT64_MAX 0xFFFFFFFFFFFFFFFFUL + +#endif diff --git a/include/libc-shim/string.h b/include/libc-shim/string.h new file mode 100644 index 0000000..8a86304 --- /dev/null +++ b/include/libc-shim/string.h @@ -0,0 +1,110 @@ +#ifndef _STRING_H +#define _STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if __cplusplus >= 201103L +#define NULL nullptr +#elif defined(__cplusplus) +#define NULL 0L +#else +#define NULL ((void*)0) +#endif + +#define __NEED_size_t +#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ + || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +#define __NEED_locale_t +#endif + +#include + +void *memcpy (void *__restrict, const void *__restrict, size_t); +void *memmove (void *, const void *, size_t); +void *memset (void *, int, size_t); +int memcmp (const void *, const void *, size_t); +void *memchr (const void *, int, size_t); + +char *strcpy (char *__restrict, const char *__restrict); +char *strncpy (char *__restrict, const char *__restrict, size_t); + +char *strcat (char *__restrict, const char *__restrict); +char *strncat (char *__restrict, const char *__restrict, size_t); + +int strcmp (const char *, const char *); +int strncmp (const char *, const char *, size_t); + +int strcoll (const char *, const char *); +size_t strxfrm (char *__restrict, const char *__restrict, size_t); + +char *strchr (const char *, int); +char *strrchr (const char *, int); + +size_t strcspn (const char *, const char *); +size_t strspn (const char *, const char *); +char *strpbrk (const char *, const char *); +char *strstr (const char *, const char *); +char *strtok (char *__restrict, const char *__restrict); + +size_t strlen (const char *); + +char *strerror (int); + +#if defined(_BSD_SOURCE) || defined(_GNU_SOURCE) +#include +#endif + +#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ + || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +char *strtok_r (char *__restrict, const char *__restrict, char **__restrict); +int strerror_r (int, char *, size_t); +char *stpcpy(char *__restrict, const char *__restrict); +char *stpncpy(char *__restrict, const char *__restrict, size_t); +size_t strnlen (const char *, size_t); +char *strdup (const char *); +char *strndup (const char *, size_t); +char *strsignal(int); +char *strerror_l (int, locale_t); +int strcoll_l (const char *, const char *, locale_t); +size_t strxfrm_l (char *__restrict, const char *__restrict, size_t, locale_t); +void *memmem(const void *, size_t, const void *, size_t); +#endif + +#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +void *memccpy (void *__restrict, const void *__restrict, int, size_t); +#endif + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +char *strsep(char **, const char *); +size_t strlcat (char *, const char *, size_t); +size_t strlcpy (char *, const char *, size_t); +void explicit_bzero (void *, size_t); +#endif + +#ifdef _GNU_SOURCE +#define strdupa(x) strcpy(alloca(strlen(x)+1),x) +int strverscmp (const char *, const char *); +char *strchrnul(const char *, int); +char *strcasestr(const char *, const char *); +void *memrchr(const void *, int, size_t); +void *mempcpy(void *, const void *, size_t); +#endif + +/* musl internal symbols used across translation units */ +char *__strchrnul(const char *, int); +void *__memrchr(const void *, int, size_t); +char *__stpcpy(char *__restrict, const char *__restrict); +char *__stpncpy(char *__restrict, const char *__restrict, size_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libc-shim/strings.h b/include/libc-shim/strings.h new file mode 100644 index 0000000..b7a5ea0 --- /dev/null +++ b/include/libc-shim/strings.h @@ -0,0 +1,40 @@ +#ifndef _STRINGS_H +#define _STRINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define __NEED_size_t +#define __NEED_locale_t +#include + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) || defined(_POSIX_SOURCE) \ + || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE+0 < 200809L) \ + || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE+0 < 700) +int bcmp (const void *, const void *, size_t); +void bcopy (const void *, void *, size_t); +void bzero (void *, size_t); +char *index (const char *, int); +char *rindex (const char *, int); +#endif + +#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +int ffs (int); +int ffsl (long); +int ffsll (long long); +#endif + +int strcasecmp (const char *, const char *); +int strncasecmp (const char *, const char *, size_t); + +int strcasecmp_l (const char *, const char *, locale_t); +int strncasecmp_l (const char *, const char *, size_t, locale_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/lume/addr.h b/include/lume/addr.h new file mode 100644 index 0000000..572b6fe --- /dev/null +++ b/include/lume/addr.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Machine-Independent Address Conversion Helpers + * + * All PA↔VA conversions in MI code (mm/, kernel/, etc.) must go through + * these functions. The actual offset is provided by . + * + * Reference: docs/specs/00_conventions.md §1.1 (MI/MD separation) + */ + +#include +#include +#include +#include + +/* Convert physical address to kernel virtual address (direct map). */ +inline VirtAddr pa_to_va(PhysAddr pa) +{ + return VirtAddr{pa.raw + arch::g_direct_map_base}; +} + +/* Convert kernel virtual address to physical address (direct map). */ +inline PhysAddr va_to_pa(VirtAddr va) +{ + return PhysAddr{va.raw - arch::g_direct_map_base}; +} + +/* + * Early-boot conversions pinned to the bootstrap direct-map base. + * + * Use these only before vmm_init() activates the final kernel page table. + * They are required when KASLR has already randomized g_direct_map_base, + * but the CPU is still running on the bootstrap mapping built in entry.S. + */ +inline VirtAddr boot_pa_to_va(PhysAddr pa) +{ + return VirtAddr{pa.raw + arch::kDirectMapBaseDefault}; +} + +inline PhysAddr boot_va_to_pa(VirtAddr va) +{ + return PhysAddr{va.raw - arch::kDirectMapBaseDefault}; +} + +/* Align up to page boundary */ +inline uint64 page_align_up(uint64 addr) +{ + return (addr + kPageSize - 1) & ~(kPageSize - 1); +} + +/* Align down to page boundary */ +inline uint64 page_align_down(uint64 addr) +{ + return addr & ~(kPageSize - 1); +} + +inline PhysAddr page_align_up(PhysAddr addr) +{ + return PhysAddr{page_align_up(addr.raw)}; +} + +inline PhysAddr page_align_down(PhysAddr addr) +{ + return PhysAddr{page_align_down(addr.raw)}; +} + +inline VirtAddr page_align_up(VirtAddr addr) +{ + return VirtAddr{page_align_up(addr.raw)}; +} + +inline VirtAddr page_align_down(VirtAddr addr) +{ + return VirtAddr{page_align_down(addr.raw)}; +} + +/* Convert physical address to page frame number (PFN) */ +inline Pfn pa_to_pfn(PhysAddr pa) +{ + return Pfn{pa.raw / kPageSize}; +} + +/* Convert page frame number (PFN) to physical address */ +inline PhysAddr pfn_to_pa(Pfn pfn) +{ + return PhysAddr{pfn.raw * kPageSize}; +} diff --git a/include/lume/addr_types.h b/include/lume/addr_types.h new file mode 100644 index 0000000..91a0779 --- /dev/null +++ b/include/lume/addr_types.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +struct PhysAddr { + uint64 raw; +}; + +struct VirtAddr { + uint64 raw; +}; + +struct Pfn { + uint64 raw; +}; + +inline constexpr PhysAddr phys_addr(uint64 raw) { return PhysAddr{raw}; } +inline constexpr VirtAddr virt_addr(uint64 raw) { return VirtAddr{raw}; } +inline constexpr Pfn pfn(uint64 raw) { return Pfn{raw}; } + +inline constexpr bool operator==(PhysAddr a, PhysAddr b) { return a.raw == b.raw; } +inline constexpr bool operator!=(PhysAddr a, PhysAddr b) { return a.raw != b.raw; } +inline constexpr bool operator==(VirtAddr a, VirtAddr b) { return a.raw == b.raw; } +inline constexpr bool operator!=(VirtAddr a, VirtAddr b) { return a.raw != b.raw; } +inline constexpr bool operator==(Pfn a, Pfn b) { return a.raw == b.raw; } +inline constexpr bool operator!=(Pfn a, Pfn b) { return a.raw != b.raw; } diff --git a/include/lume/args.h b/include/lume/args.h new file mode 100644 index 0000000..0562dc5 --- /dev/null +++ b/include/lume/args.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_ARGS_H +#define _LINUX_ARGS_H + +/* + * How do these macros work? + * + * In __COUNT_ARGS() _0 to _15 are just placeholders from the start + * in order to make sure _n is positioned over the correct number + * from 15 to 0 (depending on X, which is a variadic argument list). + * They serve no purpose other than occupying a position. Since each + * macro parameter must have a distinct identifier, those identifiers + * are as good as any. + * + * In COUNT_ARGS() we use actual integers, so __COUNT_ARGS() returns + * that as _n. + */ + +/* This counts to 15. Any more, it will return 16th argument. */ +#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n +#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +/* Concatenate two parameters, but allow them to be expanded beforehand. */ +#define __CONCAT(a, b) a ## b +#define CONCATENATE(a, b) __CONCAT(a, b) + +#endif /* _LINUX_ARGS_H */ diff --git a/include/lume/atomic.h b/include/lume/atomic.h new file mode 100644 index 0000000..6e31129 --- /dev/null +++ b/include/lume/atomic.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * lume::atomic — Thin wrapper over __atomic builtins. + * + * All multi-core shared data that does NOT use a spinlock MUST use + * this wrapper instead of raw __atomic_* builtins. + * + * Reference: docs/specs/00_conventions.md §5.3 + */ + +#include + +namespace lume { + +template +class atomic { +public: + atomic() = default; + explicit atomic(T val) : val_(val) {} + + /* Prevent copies — an atomic value has identity */ + atomic(const atomic&) = delete; + atomic& operator=(const atomic&) = delete; + + /* Load with specified memory ordering */ + T load(int order = __ATOMIC_SEQ_CST) const { + return __atomic_load_n(&val_, order); + } + + /* Store with specified memory ordering */ + void store(T desired, int order = __ATOMIC_SEQ_CST) { + __atomic_store_n(&val_, desired, order); + } + + /* Atomic exchange, returns old value */ + T exchange(T desired, int order = __ATOMIC_SEQ_CST) { + return __atomic_exchange_n(&val_, desired, order); + } + + /* Fetch-and-add, returns old value */ + T fetch_add(T delta, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_add(&val_, delta, order); + } + + /* Fetch-and-subtract, returns old value */ + T fetch_sub(T delta, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_sub(&val_, delta, order); + } + + /* Fetch-and-or, returns old value */ + T fetch_or(T mask, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_or(&val_, mask, order); + } + + /* Fetch-and-and, returns old value */ + T fetch_and(T mask, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_and(&val_, mask, order); + } + + /* Compare-and-swap. Returns true on success. */ + bool compare_exchange(T& expected, T desired, + int success_order = __ATOMIC_ACQ_REL, + int failure_order = __ATOMIC_ACQUIRE) { + return __atomic_compare_exchange_n(&val_, &expected, desired, + false, success_order, failure_order); + } + +private: + T val_ = 0; +}; + +} // namespace lume diff --git a/include/lume/bitops.h b/include/lume/bitops.h new file mode 100644 index 0000000..4227ca3 --- /dev/null +++ b/include/lume/bitops.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Bit Operations — portable kernel bit manipulation utilities + * + * Provides ctz, clz, ffs, fls and related operations. + * All implementations are manual (no __builtin_ctz) to avoid + * generating libgcc __ctzdi2/__clzdi2 calls in freestanding. + */ + +#include + +namespace lume { + +/* ------------------------------------------------------------------ */ +/* Count Trailing Zeros (ctz) */ +/* Returns the number of trailing 0-bits in val. */ +/* Precondition: val != 0 (undefined if val == 0). */ +/* ------------------------------------------------------------------ */ + +static inline int ctz32(uint32 val) +{ + int n = 0; + if (!(val & 0x0000FFFFU)) { n += 16; val >>= 16; } + if (!(val & 0x000000FFU)) { n += 8; val >>= 8; } + if (!(val & 0x0000000FU)) { n += 4; val >>= 4; } + if (!(val & 0x00000003U)) { n += 2; val >>= 2; } + if (!(val & 0x00000001U)) { n += 1; } + return n; +} + +static inline int ctz64(uint64 val) +{ + if (static_cast(val) != 0) { + return ctz32(static_cast(val)); + } + return 32 + ctz32(static_cast(val >> 32)); +} + +/* ------------------------------------------------------------------ */ +/* Count Leading Zeros (clz) */ +/* Returns the number of leading 0-bits in val. */ +/* Precondition: val != 0 (undefined if val == 0). */ +/* ------------------------------------------------------------------ */ + +static inline int clz32(uint32 val) +{ + int n = 0; + if (!(val & 0xFFFF0000U)) { n += 16; val <<= 16; } + if (!(val & 0xFF000000U)) { n += 8; val <<= 8; } + if (!(val & 0xF0000000U)) { n += 4; val <<= 4; } + if (!(val & 0xC0000000U)) { n += 2; val <<= 2; } + if (!(val & 0x80000000U)) { n += 1; } + return n; +} + +static inline int clz64(uint64 val) +{ + if (static_cast(val >> 32) != 0) { + return clz32(static_cast(val >> 32)); + } + return 32 + clz32(static_cast(val)); +} + +/* ------------------------------------------------------------------ */ +/* Find First Set (ffs) — 1-indexed, returns 0 if val == 0 */ +/* ------------------------------------------------------------------ */ + +static inline int ffs32(uint32 val) +{ + return val ? ctz32(val) + 1 : 0; +} + +static inline int ffs64(uint64 val) +{ + return val ? ctz64(val) + 1 : 0; +} + +/* ------------------------------------------------------------------ */ +/* Find Last Set (fls) — 1-indexed, returns 0 if val == 0 */ +/* Returns the position of the highest set bit. */ +/* ------------------------------------------------------------------ */ + +static inline int fls32(uint32 val) +{ + return val ? 32 - clz32(val) : 0; +} + +static inline int fls64(uint64 val) +{ + return val ? 64 - clz64(val) : 0; +} + +/* ------------------------------------------------------------------ */ +/* Order (log2) — returns floor(log2(val)), val must be > 0 */ +/* ------------------------------------------------------------------ */ + +static inline int ilog2_32(uint32 val) +{ + return fls32(val) - 1; +} + +static inline int ilog2_64(uint64 val) +{ + return fls64(val) - 1; +} + +/* ------------------------------------------------------------------ */ +/* Power-of-two test */ +/* ------------------------------------------------------------------ */ + +static inline bool is_power_of_2(uint64 val) +{ + return val != 0 && (val & (val - 1)) == 0; +} + +} /* namespace lume */ diff --git a/include/lume/chardev.h b/include/lume/chardev.h new file mode 100644 index 0000000..be81ab9 --- /dev/null +++ b/include/lume/chardev.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * chardev.h — Character device abstraction + * + * Minimal Linux-style cdev registry: each character device has a + * major/minor number and a file_operations table. + */ + +#include +#include + +struct file_operations; + +/* ================================================================== */ +/* MKDEV / MAJOR / MINOR */ +/* ================================================================== */ + +inline constexpr uint32 MKDEV(uint32 ma, uint32 mi) { + return (ma << 8) | (mi & 0xFF); +} +inline constexpr uint32 MAJOR(uint32 dev) { return dev >> 8; } +inline constexpr uint32 MINOR(uint32 dev) { return dev & 0xFF; } + +/* ================================================================== */ +/* cdev — character device descriptor */ +/* ================================================================== */ + +struct cdev { + const char* name; + uint32 dev; /* MKDEV(major, minor) */ + const file_operations* fops; + list_node list; /* global cdev list */ +}; + +/* ================================================================== */ +/* API */ +/* ================================================================== */ + +void cdev_init(); +void cdev_register(cdev* dev); +cdev* cdev_find(uint32 dev); diff --git a/include/lume/clone.h b/include/lume/clone.h new file mode 100644 index 0000000..156bae4 --- /dev/null +++ b/include/lume/clone.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Linux-compatible clone(2) flags. + * + * Only the flags relevant to current and near-future use are defined. + * Reference: include/uapi/linux/sched.h + */ + +#include + +/* Signal mask stored in low byte of clone flags */ +inline constexpr uint64 CSIGNAL = 0x000000ffULL; + +/* Sharing flags */ +inline constexpr uint64 CLONE_VM = 0x00000100ULL; // Share address space +inline constexpr uint64 CLONE_FS = 0x00000200ULL; // Share fs info +inline constexpr uint64 CLONE_FILES = 0x00000400ULL; // Share file descriptors +inline constexpr uint64 CLONE_SIGHAND = 0x00000800ULL; // Share signal handlers +inline constexpr uint64 CLONE_PIDFD = 0x00001000ULL; // pidfd +inline constexpr uint64 CLONE_PTRACE = 0x00002000ULL; // Trace child +inline constexpr uint64 CLONE_VFORK = 0x00004000ULL; // Parent blocks until child exits/execs +inline constexpr uint64 CLONE_PARENT = 0x00008000ULL; // Same parent as cloner +inline constexpr uint64 CLONE_THREAD = 0x00010000ULL; // Same thread group +inline constexpr uint64 CLONE_NEWNS = 0x00020000ULL; // New mount namespace +inline constexpr uint64 CLONE_SYSVSEM = 0x00040000ULL; // Share SysV semaphore undo +inline constexpr uint64 CLONE_SETTLS = 0x00080000ULL; // Set TLS +inline constexpr uint64 CLONE_PARENT_SETTID = 0x00100000ULL; // Write TID to parent +inline constexpr uint64 CLONE_CHILD_CLEARTID = 0x00200000ULL; // Clear TID on child exit +inline constexpr uint64 CLONE_DETACHED = 0x00400000ULL; // Unused, historical +inline constexpr uint64 CLONE_CHILD_SETTID = 0x01000000ULL; // Write TID to child diff --git a/include/lume/compiler.h b/include/lume/compiler.h new file mode 100644 index 0000000..243f0de --- /dev/null +++ b/include/lume/compiler.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_COMPILER_H +#define _LUME_COMPILER_H + +/* + * Minimal compiler primitives used across LumeOS. + * + * Provides only what current code actually references: + * - READ_ONCE / WRITE_ONCE memory access opacity for concurrent code + * (rbtree.h, rbtree_augmented.h) + * - likely / unlikely branch hints for hot/cold paths + * + * Implementation mirrors Linux's semantics — same surface, same effect — + * without pulling in Linux's compiler_types.h cascade (KASAN/KCSAN/KMSAN + * sanitizer attribute machinery, BTF type tags, hardened-usercopy hooks). + * LumeOS doesn't build with those sanitizers, so the simpler form is + * sufficient and keeps the dependency graph flat. + */ + +/* Branch prediction hints */ +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +/* Compile-time barrier — prevent the compiler from reordering memory + * accesses across this point. Not a CPU fence; for CPU ordering use + * the architecture-specific fence intrinsics. */ +#define barrier() __asm__ __volatile__("" ::: "memory") + +/* + * __always_inline — force the compiler to actually inline a function, + * overriding -fno-inline / -Os / size heuristics. Used by rbtree helper + * templates whose performance depends on the call being inlined into + * the iterating loop. + */ +#define __always_inline inline __attribute__((__always_inline__)) + +/* + * READ_ONCE / WRITE_ONCE — opaque single-access memory operations. + * + * The volatile cast prevents the compiler from: + * - merging adjacent reads/writes to the same location, + * - splitting a load into multiple narrower loads, + * - hoisting reads out of a loop (or sinking writes past one). + * + * Used in lock-free / cross-CPU shared data paths (rbtree color writes, + * future RCU pointers, atomic flag polls). Does NOT imply any CPU-side + * memory ordering — pair with smp_mb()/acquire/release for that. + */ +#define READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x)) +#define WRITE_ONCE(x, val) (*(volatile __typeof__(x) *)&(x) = (val)) + +#endif /* _LUME_COMPILER_H */ diff --git a/include/lume/config.h b/include/lume/config.h new file mode 100644 index 0000000..bde717a --- /dev/null +++ b/include/lume/config.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * LumeOS Global Compile-Time Configuration + * + * All kernel-wide tuning constants live here. + * Architecture-specific constants live in . + * + * This header is safe to include from both C++ and assembly files. + * Assembly-visible constants use #define; C++ code gets typed constexpr. + * + * Values are injected by the build system via -DCONFIG_xxx flags + * (see scripts/Kconfig.mk). The #ifndef guards below provide + * safe defaults so the header is self-contained for IDE analysis. + * + * Reference: docs/specs/00_conventions.md §1.3 + */ + +/* ================================================================ + * Defaults — only used when CONFIG_xxx is NOT supplied by -D flag. + * In a normal build, scripts/Kconfig.mk always provides them. + * ================================================================ */ + +#ifndef CONFIG_NR_CPUS +#define CONFIG_NR_CPUS 8 +#endif + +#ifndef CONFIG_PAGE_SIZE +#define CONFIG_PAGE_SIZE 4096 +#endif + +#ifndef CONFIG_MAX_ORDER +#define CONFIG_MAX_ORDER 11 +#endif + +#ifndef CONFIG_KERNEL_STACK_SIZE +#define CONFIG_KERNEL_STACK_SIZE 16384 +#endif + +#ifndef CONFIG_HZ +#define CONFIG_HZ 100 +#endif + +#ifndef CONFIG_NR_PRIORITIES +#define CONFIG_NR_PRIORITIES 32 +#endif + +#ifndef CONFIG_DEFAULT_PRIORITY +#define CONFIG_DEFAULT_PRIORITY (CONFIG_NR_PRIORITIES / 2) +#endif + +#ifndef CONFIG_IDLE_PRIORITY +#define CONFIG_IDLE_PRIORITY (CONFIG_NR_PRIORITIES - 1) +#endif + +#ifndef CONFIG_DEFAULT_TIMESLICE +#define CONFIG_DEFAULT_TIMESLICE (CONFIG_HZ / 10) +#endif + +#ifndef CONFIG_KASLR_POLICY_ID +#define CONFIG_KASLR_POLICY_ID 0 /* Default: basic KASLR policy */ +#endif + +#ifndef CONFIG_KASLR_LOG_LEVEL +#define CONFIG_KASLR_LOG_LEVEL 1 /* 0=off, 1=minimal, 2=debug */ +#endif + +/* ================================================================ + * Legacy compatibility aliases (shared with assembly) + * ================================================================ */ +#define LUME_MAX_CPUS CONFIG_NR_CPUS +#define LUME_PAGE_SIZE CONFIG_PAGE_SIZE + +#ifndef __ASSEMBLER__ + +#include + +/* Maximum number of CPUs supported by the kernel. + * Compile-time limit for static arrays (boot stacks, per-cpu data). + * Actual CPU count is detected at runtime via FDT. */ +inline constexpr int kMaxCpus = CONFIG_NR_CPUS; + +/* Virtual memory page size (4KB, matching SV39 base page). */ +inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE; + +/* Page shift: log2(kPageSize). Used for PFN ↔ PA conversions. */ +inline constexpr int kPageShift = 12; // 4096 = 1 << 12 + +/* KASLR policy ID and logging level (build-time configurable) */ +inline constexpr uint32 kKaslrPolicyId = CONFIG_KASLR_POLICY_ID; +inline constexpr int kKaslrLogLevel = CONFIG_KASLR_LOG_LEVEL; + +#include + +using arch::kPhysStart; + +#endif /* __ASSEMBLER__ */ \ No newline at end of file diff --git a/include/lume/console.h b/include/lume/console.h new file mode 100644 index 0000000..af187a6 --- /dev/null +++ b/include/lume/console.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * console.h — Kernel console abstraction + * + * Two-phase design following Linux: + * + * Phase 1 (early boot): + * early_putc() → direct UART MMIO write. Used before MMU and + * before the console registry is ready. kprintf() and panic + * use this path. + * + * Phase 2 (runtime): + * struct console registered via register_console(). printk/klog + * emit through the console driver chain. Multiple consoles can + * be registered (e.g., UART + framebuffer). + * + * Reference: Linux include/linux/console.h + */ + +#include +#include + +/* ================================================================== */ +/* Early console (Phase 1 — direct UART access) */ +/* ================================================================== */ + +extern "C" void early_putc(char c); +extern "C" void early_puts(const char* s); +extern "C" void console_set_mmu_enabled(); + +/* Early UART discovery via raw FDT parsing. */ +void console_early_init(uint64 fdt_paddr); + +/* Full console initialization. Maps MMIO. */ +void console_init(); + +/* + * Notify console that the runtime kernel page table is active. + * After this point early_putc() must use runtime direct-map base + * instead of the bootstrap fixed base. + */ +void console_use_runtime_mapping(); + +/* ================================================================== */ +/* struct console (Phase 2 — registered console drivers) */ +/* ================================================================== */ + +inline constexpr int CON_PRINTBUFFER = (1 << 0); /* Replay klog on register */ +inline constexpr int CON_ENABLED = (1 << 1); +inline constexpr int CON_BOOT = (1 << 2); /* Early boot console */ + +struct console { + const char* name; + void (*write)(console* con, const char* s, unsigned int count); + int (*setup)(console* con, char* options); + int flags; + int index; + void* data; /* driver private data */ + list_node list; +}; + +/* Register / unregister a console driver */ +void register_console(console* con); +void unregister_console(console* con); + +/* + * Write to all registered consoles. + * Falls back to early_putc() if no console is registered. + */ +void console_write_all(const char* s, int count); + +/* Check if any console is registered */ +bool console_drivers_registered(); diff --git a/include/lume/container_of.h b/include/lume/container_of.h new file mode 100644 index 0000000..a6889eb --- /dev/null +++ b/include/lume/container_of.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * container_of.h — Recover containing struct from member pointer + * + * Equivalent to Linux include/linux/container_of.h. + */ + +#include + +#define container_of(ptr, type, member) \ + reinterpret_cast( \ + reinterpret_cast(ptr) - \ + reinterpret_cast(&(reinterpret_cast(0)->member))) diff --git a/include/lume/dcache.h b/include/lume/dcache.h new file mode 100644 index 0000000..a31402e --- /dev/null +++ b/include/lume/dcache.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * dcache.h — Directory entry cache + * + * Modeled after Linux include/linux/dcache.h. + * Reference: docs/vfs/03_dentry.md + */ + +#include +#include +#include +#include +#include + +struct inode; +struct super_block; +struct dentry; +struct dentry_operations; +struct path; + +/* ================================================================== */ +/* qstr — quick string (name + precomputed hash + length) */ +/* ================================================================== */ + +struct qstr { + union { + struct { + uint32 hash; + uint32 len; + }; + uint64 hash_len; + }; + const char* name; +}; + +uint32 full_name_hash(const char* name, uint32 len); + +/* ================================================================== */ +/* dentry */ +/* ================================================================== */ + +inline constexpr int DNAME_INLINE_LEN = 40; + +struct dentry { + /* ---- RCU / lookup hot fields ---- */ + unsigned int d_flags; + dentry* d_parent; + qstr d_name; + inode* d_inode; + unsigned char d_iname[DNAME_INLINE_LEN]; + + /* ---- Ref lookup ---- */ + const dentry_operations* d_op; + super_block* d_sb; + unsigned long d_time; + void* d_fsdata; + + /* ---- Refcount + lock ---- */ + lume::atomic d_lockref; + Spinlock d_lock; + + /* ---- Tree linkage ---- */ + list_node d_lru; + list_node d_child; /* Linkage in parent->d_children */ + list_head d_children; /* Children of this dentry */ + + /* ---- Inode alias ---- */ + list_node d_alias; /* Linkage in inode->i_dentry */ +}; + +/* ================================================================== */ +/* dentry_operations */ +/* ================================================================== */ + +struct dentry_operations { + int (*d_revalidate)(dentry* de, unsigned int flags); + int (*d_hash)(const dentry* de, qstr* name); + int (*d_compare)(const dentry* parent, unsigned int len, + const char* str, const qstr* name); + int (*d_delete)(const dentry* de); + int (*d_init)(dentry* de); + void (*d_release)(dentry* de); + void (*d_iput)(dentry* de, inode* ip); +}; + +/* ================================================================== */ +/* Dentry helpers */ +/* ================================================================== */ + +inline bool d_is_negative(const dentry* de) { + return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_MISS_TYPE; +} +inline bool d_is_positive(const dentry* de) { + return !d_is_negative(de); +} +inline bool d_is_dir(const dentry* de) { + return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_DIRECTORY_TYPE; +} +inline bool d_is_reg(const dentry* de) { + return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_REGULAR_TYPE; +} +inline bool d_is_symlink(const dentry* de) { + return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_SYMLINK_TYPE; +} +inline bool d_mountpoint(const dentry* de) { + return (de->d_flags & DCACHE_MOUNTED) != 0; +} +inline bool d_is_root(const dentry* de) { + return de == de->d_parent; +} + +inline inode* d_inode_ptr(const dentry* de) { + return de->d_inode; +} + +/* ================================================================== */ +/* Dcache API */ +/* ================================================================== */ + +dentry* d_alloc(dentry* parent, const qstr* name); +dentry* d_alloc_anon(super_block* sb); +void d_instantiate(dentry* de, inode* ip); +dentry* d_make_root(inode* root_inode); +void d_add(dentry* de, inode* ip); +dentry* d_lookup(const dentry* parent, const qstr* name); +dentry* dget(dentry* de); +void dput(dentry* de); +void d_drop(dentry* de); +void d_delete(dentry* de); +char* d_path(const path* p, char* buf, int buflen); + +/* Set dentry type flags based on inode mode */ +void d_set_type(dentry* de, uint32 i_mode); + +/* ================================================================== */ +/* Dcache init */ +/* ================================================================== */ + +void dcache_init(); diff --git a/include/lume/device.h b/include/lume/device.h new file mode 100644 index 0000000..46e22e4 --- /dev/null +++ b/include/lume/device.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Driver model: device, device_driver, bus_type. + * + * Mirrors Linux include/linux/device.h. Every hardware unit embeds a + * `device` and binds to a `device_driver` via a `bus_type` that knows + * how to match the two. Buses we will have: + * - platform: FDT-described memory-mapped devices (this commit) + * - virtio: virtio-mmio devices (P5 of storage_stack.md) + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include +#include +#include + +struct bus_type; +struct device_driver; + +struct device { + kobject kobj; // embedded; first member by convention + device* parent; + const char* init_name; // used until kobj.name is set + bus_type* bus; + device_driver* driver; + void* driver_data; + void* platform_data; + uint32 devt; // for char/block dev_t allocation + list_node bus_list; // node in bus->devices + list_node driver_list; // node in driver->devices +}; + +struct device_driver { + const char* name; + bus_type* bus; + int (*probe)(device*); + void (*remove)(device*); + list_node bus_node; // node in bus->drivers + list_node devices; // head of devices bound to this driver +}; + +struct bus_type { + const char* name; + int (*match)(device*, device_driver*); + /* If non-null, this is the bus-level probe dispatcher. It is called + * before drv->probe and is responsible for downcasting device/driver + * to the concrete bus type and forwarding (e.g. platform_dispatch). */ + int (*probe)(device*); + list_node devices; + list_node drivers; + Spinlock lock; + bool registered; +}; + +int bus_register (bus_type* bus); + +/* device_initialize: kobject_init the embedded kobj with the generic + * device_ktype. device_register calls this automatically if the caller + * hasn't already. Separate entry point for callers that want a half- + * initialized device (so they can take refs before adding). */ +void device_initialize(device* dev); +int device_register (device* dev); +void device_unregister(device* dev); + +int driver_register (device_driver* drv); +void driver_unregister(device_driver* drv); + +/* Convenience accessor — name from kobject if set, else init_name. */ +const char* dev_name(const device* dev); diff --git a/include/lume/device_node.h b/include/lume/device_node.h new file mode 100644 index 0000000..e35f22d --- /dev/null +++ b/include/lume/device_node.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * DeviceNode — FDT object tree node + * + * Each DeviceNode represents a single node in the unflattened device tree. + * Nodes form a parent-children hierarchy via intrusive linked lists. + * After unflattening, the tree is globally read-only and requires no locks. + * + * Created when Slab is available. + * + * Reference: docs/specs/fdt.md §3.2 + */ + +#include +#include +#include + +class DeviceNode { +public: + // Construction / destruction (used by FdtManager::unflatten) + DeviceNode(DeviceNode *parent, const char *name); + ~DeviceNode(); + + // --- Property access --- + + // Look up a property by name. Returns nullptr if not found. + const FdtProperty *get_prop(const char *name) const; + + // Convenience helpers that decode the property value. + // Return default_val when the property does not exist. + uint32 get_prop_u32(const char *name, uint32 default_val = 0) const; + uint64 get_prop_u64(const char *name, uint64 default_val = 0) const; + const char *get_prop_string(const char *name) const; + + // --- Child traversal --- + + // Find a direct child by name. Returns nullptr if not found. + DeviceNode *find_child(const char *name); + + // Invoke callback for every direct child. + void for_each_child(void (*callback)(DeviceNode *)); + + // --- Internal (used by FdtManager to build the tree) --- + void add_child(DeviceNode *child); + void add_prop(FdtProperty *prop); + + // --- Accessors for tree traversal --- + const char *get_name() const { return name_; } + list_node *children_list_head() { return children_list_.next; } + list_node *children_list_sentinel() { return &children_list_; } + + // sibling_link_ offset needed by container_of in fdt.cc + list_node sibling_link_; // Link in parent's children_list_ + +private: + const char *name_; // Node name (e.g. "memory@80000000") + DeviceNode *parent_; // Parent node (nullptr for root) + list_node children_list_; // Head of the children linked list + list_node props_list_; // Head of the property linked list +}; diff --git a/include/lume/driver.h b/include/lume/driver.h new file mode 100644 index 0000000..a8bd787 --- /dev/null +++ b/include/lume/driver.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * driver.h — boot-time driver probing entry point. + * + * The actual driver-model primitives (kobject, device, bus_type, + * platform_device, platform_driver) live in their own headers: + * , , . + * + * This header just exposes the single boot entry point that + * kernel_main() calls after the slab allocator + FDT unflatten are + * ready. The implementation lives in drivers/base/platform.cc. + * + * Reference: docs/specs/storage_stack.md §4 + */ + +/* Register the platform bus, register every linker-set platform_driver, + * walk the FDT, and instantiate a platform_device for each compatible + * match. Each device is bound to its driver via the platform bus + * match/probe path. Returns after all probes have run. */ +void driver_probe_all(); diff --git a/include/lume/elf.h b/include/lume/elf.h new file mode 100644 index 0000000..772ad97 --- /dev/null +++ b/include/lume/elf.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * ELF64 Type Definitions — Minimal set for loading executables + * AND for the kernel's own .rela.dyn relocation walk at boot. + * + * Follows the ELF specification (Tool Interface Standard, TIS). + * Only includes what's needed for static ELF64 executable loading. + * + * Reference: https://refspecs.linuxfoundation.org/elf/elf.pdf + */ + +#include + +/* ================================================================== */ +/* Basic ELF types */ +/* ================================================================== */ + +using Elf64_Addr = uint64; +using Elf64_Off = uint64; +using Elf64_Half = uint16; +using Elf64_Word = uint32; +using Elf64_Sword = int32; +using Elf64_Xword = uint64; +using Elf64_Sxword = int64; + +/* ================================================================== */ +/* ELF Header */ +/* ================================================================== */ + +inline constexpr int EI_NIDENT = 16; + +struct Elf64_Ehdr { + uint8 e_ident[EI_NIDENT]; /* ELF identification */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Machine type */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point address */ + Elf64_Off e_phoff; /* Program header table offset */ + Elf64_Off e_shoff; /* Section header table offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size */ + Elf64_Half e_phentsize; /* Program header entry size */ + Elf64_Half e_phnum; /* Number of program headers */ + Elf64_Half e_shentsize; /* Section header entry size */ + Elf64_Half e_shnum; /* Number of section headers */ + Elf64_Half e_shstrndx; /* Section name string table index */ +}; + +/* e_ident[] indices */ +inline constexpr int EI_MAG0 = 0; +inline constexpr int EI_MAG1 = 1; +inline constexpr int EI_MAG2 = 2; +inline constexpr int EI_MAG3 = 3; +inline constexpr int EI_CLASS = 4; +inline constexpr int EI_DATA = 5; +inline constexpr int EI_VERSION = 6; +inline constexpr int EI_OSABI = 7; + +/* e_ident[] magic */ +inline constexpr uint8 ELFMAG0 = 0x7f; +inline constexpr uint8 ELFMAG1 = 'E'; +inline constexpr uint8 ELFMAG2 = 'L'; +inline constexpr uint8 ELFMAG3 = 'F'; + +/* e_ident[EI_CLASS] */ +inline constexpr uint8 ELFCLASS64 = 2; + +/* e_ident[EI_DATA] */ +inline constexpr uint8 ELFDATA2LSB = 1; /* Little-endian */ + +/* e_type */ +inline constexpr Elf64_Half ET_EXEC = 2; /* Executable */ +inline constexpr Elf64_Half ET_DYN = 3; /* Shared object (PIE) */ + +/* e_machine */ +inline constexpr Elf64_Half EM_RISCV = 243; + +/* ================================================================== */ +/* Program Header */ +/* ================================================================== */ + +struct Elf64_Phdr { + Elf64_Word p_type; /* Segment type */ + Elf64_Word p_flags; /* Segment flags */ + Elf64_Off p_offset; /* File offset */ + Elf64_Addr p_vaddr; /* Virtual address */ + Elf64_Addr p_paddr; /* Physical address (unused) */ + Elf64_Xword p_filesz; /* Size in file */ + Elf64_Xword p_memsz; /* Size in memory (>= p_filesz, diff is BSS) */ + Elf64_Xword p_align; /* Alignment */ +}; + +/* p_type values */ +inline constexpr Elf64_Word PT_NULL = 0; +inline constexpr Elf64_Word PT_LOAD = 1; +inline constexpr Elf64_Word PT_DYNAMIC = 2; +inline constexpr Elf64_Word PT_INTERP = 3; +inline constexpr Elf64_Word PT_NOTE = 4; +inline constexpr Elf64_Word PT_PHDR = 6; +inline constexpr Elf64_Word PT_GNU_STACK = 0x6474e551; + +/* p_flags values */ +inline constexpr Elf64_Word PF_X = 0x1; /* Execute */ +inline constexpr Elf64_Word PF_W = 0x2; /* Write */ +inline constexpr Elf64_Word PF_R = 0x4; /* Read */ + +/* ================================================================== */ +/* Auxiliary Vector types */ +/* ================================================================== */ + +struct Elf64_auxv_t { + uint64 a_type; + uint64 a_val; +}; + +inline constexpr uint64 AT_NULL = 0; +inline constexpr uint64 AT_PHDR = 3; +inline constexpr uint64 AT_PHENT = 4; +inline constexpr uint64 AT_PHNUM = 5; +inline constexpr uint64 AT_PAGESZ = 6; +inline constexpr uint64 AT_ENTRY = 9; +inline constexpr uint64 AT_UID = 11; +inline constexpr uint64 AT_EUID = 12; +inline constexpr uint64 AT_GID = 13; +inline constexpr uint64 AT_EGID = 14; +inline constexpr uint64 AT_RANDOM = 25; +inline constexpr uint64 AT_BASE = 7; /* PIE load base address */ + +/* ================================================================== */ +/* RELA relocation entry — used by the kernel's own .rela.dyn walk */ +/* at boot (relocate_kernel in arch/riscv/mm/init.cc). */ +/* ================================================================== */ + +struct Elf64_Rela { + Elf64_Addr r_offset; /* link-time VA of the slot to patch */ + Elf64_Xword r_info; /* r_sym << 32 | r_type */ + Elf64_Sxword r_addend; /* link-time value of the target */ +}; + +#define ELF64_R_TYPE(info) ((info) & 0xFFFFFFFFULL) + +/* RISC-V relocation types — only the ones a PIE-linked kernel emits + * into .rela.dyn under `-shared -Bsymbolic`. */ +inline constexpr uint32 R_RISCV_NONE = 0; +inline constexpr uint32 R_RISCV_64 = 2; +inline constexpr uint32 R_RISCV_RELATIVE = 3; diff --git a/include/lume/errno.h b/include/lume/errno.h new file mode 100644 index 0000000..f3bf09f --- /dev/null +++ b/include/lume/errno.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_ERRNO_H +#define _LUME_ERRNO_H + +/* + * Error codes — numeric values aligned with Linux's asm-generic/errno-base.h, + * asm-generic/errno.h, and linux/errno.h (kernel-internal codes 512+). + * + * Userspace-visible codes (1..133) match POSIX / Linux exactly so a LumeOS + * binary returning -EINVAL to a user program receives the same number Linux + * would. Kernel-internal codes (>= 512) follow Linux's convention of being + * out of POSIX range; they are translated to a user-visible code (typically + * EINTR) before crossing into user mode. + * + * Defined as plain #define so this header is usable from C, C++, and + * assembly (the macros expand to integer literals, no types). + */ + +/* ===== Base errors (asm-generic/errno-base.h equivalent) ===== */ +#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 */ +#define ERANGE 34 /* Math result not representable */ + +/* ===== Extended errors (asm-generic/errno.h equivalent) ===== */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ +#define EDEADLOCK EDEADLK +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Cannot access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Linking in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define ENOTSUP EOPNOTSUPP +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died (for robust mutexes) */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + +/* ===== Kernel-internal errors (linux/errno.h equivalent, >= 512) ===== + * These should never be seen by user programs. signal/syscall layer + * translates them to a user-visible code (typically EINTR) before + * returning to userspace. + */ +#define ERESTARTSYS 512 +#define ERESTARTNOINTR 513 +#define ERESTARTNOHAND 514 /* restart if no signal handler */ +#define ENOIOCTLCMD 515 /* No ioctl command */ +#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */ +#define EPROBE_DEFER 517 /* Driver requests probe retry */ +#define EOPENSTALE 518 /* open() found a stale dentry */ +#define ENOPARAM 519 /* Parameter not supported */ + +/* NFSv3 reserved range (521..531) */ +#define EBADHANDLE 521 /* Illegal NFS file handle */ +#define ENOTSYNC 522 /* Update synchronization mismatch */ +#define EBADCOOKIE 523 /* Cookie is stale */ +#define ENOTSUPP 524 /* Operation is not supported */ +#define ETOOSMALL 525 /* Buffer or request is too small */ +#define ESERVERFAULT 526 /* An untranslatable error occurred */ +#define EBADTYPE 527 /* Type not supported by server */ +#define EJUKEBOX 528 /* Request initiated, will not complete before timeout */ +#define EIOCBQUEUED 529 /* iocb queued, will get completion event */ +#define ERECALLCONFLICT 530 /* conflict with recalled state */ +#define ENOGRACE 531 /* NFS file lock reclaim refused */ + +#endif /* _LUME_ERRNO_H */ diff --git a/include/lume/fdt.h b/include/lume/fdt.h new file mode 100644 index 0000000..626cc48 --- /dev/null +++ b/include/lume/fdt.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FdtManager — Flattened Device Tree parser + * + * Two-phase design: + * Phase 1 (Early Scan): Static methods that walk the raw FDT blob + * without any memory allocation. Used before PMM is available. + * Phase 2 (Unflatten): Builds a persistent C++ DeviceNode object + * tree once Slab is ready. The tree is then globally read-only. + * + * Concurrency: all init is done by BSP in the single-core window. + * After unflattening the tree is immutable — no locks required. + * + * Reference: docs/specs/fdt.md + */ + +#include +#include + +class FdtManager { +public: + // Construct with the physical address of the FDT blob. + // Called via Placement New in fdt_init(). + FdtManager(uint64 fdt_paddr); + + // --- Phase 1: Early scan (static, allocation-free) --- + + // Extract physical memory base and size from the /memory node. + // Panics if the FDT is invalid or /memory is missing. + static void early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size); + + // Extract the UART base address and size by matching compatible = "ns16550a". + // Falls back to platform default on failure. + static void early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, + uint64 *uart_size); + + static void early_scan_kaslr(uint64 fdt_paddr, uint64 *seed); + + // --- Phase 2: Object tree construction (requires Slab) --- + + // Walk the FDT blob and build the DeviceNode tree. + void unflatten(); + + // --- Query interface (read-only after unflatten) --- + + // Find the first node whose "compatible" property contains the string. + // Returns nullptr if no match. + DeviceNode *find_compatible(const char *compat_string); + + // Look up a node by its full path (e.g. "/soc/uart@10000000"). + // Returns nullptr if not found. + DeviceNode *get_node_by_path(const char *path); + + // Return the stored FDT physical address. + uint64 get_paddr() const { return fdt_paddr_; } + +private: + uint64 fdt_paddr_; // Physical address of the FDT blob + bool is_unflattened_ = false; // True after unflatten() completes + DeviceNode *root_ = nullptr; // Root of the unflattened tree +}; + +/* Global FDT manager singleton (Placement New, see global_init.md §3.1) */ +extern FdtManager *g_fdt; + +// --- Public free-function API --- + +// Save FDT address and validate magic. Called early by BSP. +void fdt_init(uint64 fdt_paddr); + +// Build the DeviceNode object tree. Requires Slab. +void fdt_unflatten(); + +// Driver probing helpers (delegate to g_fdt). +DeviceNode *fdt_find_compatible(const char *compat_string); +DeviceNode *fdt_get_node_by_path(const char *path); + +// Early scan wrappers (delegate to FdtManager static methods). +void fdt_early_get_mem_info(uint64 *base, uint64 *size); +void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size); +void fdt_early_get_kaslr_seed(uint64 *seed); diff --git a/include/lume/fdt_prop.h b/include/lume/fdt_prop.h new file mode 100644 index 0000000..f8a263c --- /dev/null +++ b/include/lume/fdt_prop.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FDT Property + * + * Represents a single property within a DeviceNode (e.g. "reg", + * "compatible"). The value pointer points directly into the + * original FDT blob — no copies are made. + * + * Reference: docs/specs/fdt.md §3.1 + */ + +#include +#include + +struct FdtProperty { + const char *name; // Property name (points into FDT strings block) + const void *value; // Raw property data (points into FDT struct block) + int len; // Length of value in bytes + list_node link; // Intrusive link into DeviceNode's property list +}; diff --git a/include/lume/fdtable.h b/include/lume/fdtable.h new file mode 100644 index 0000000..6159519 --- /dev/null +++ b/include/lume/fdtable.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fdtable.h — Per-task file descriptor table + * + * Modeled after Linux include/linux/fdtable.h. + * Reference: docs/vfs/05_fdtable.md + */ + +#include +#include +#include + +struct file; + +/* ================================================================== */ +/* files_struct — per-task open file table */ +/* ================================================================== */ + +inline constexpr int NR_OPEN_DEFAULT = 256; + +struct files_struct { + lume::atomic count; /* Reference count (CLONE_FILES) */ + Spinlock file_lock; + + int next_fd; /* Hint for next free fd */ + int max_fds; /* Current max (= NR_OPEN_DEFAULT) */ + file* fd_array[NR_OPEN_DEFAULT]; + uint64 close_on_exec_init[(NR_OPEN_DEFAULT + 63) / 64]; + uint64 open_fds_init[(NR_OPEN_DEFAULT + 63) / 64]; +}; + +/* ================================================================== */ +/* API */ +/* ================================================================== */ + +files_struct* alloc_files_struct(); +void put_files_struct(files_struct* files); +files_struct* dup_fd(files_struct* old_files); + +int alloc_fd(files_struct* files, unsigned int start); +void fd_install(files_struct* files, int fd, file* fp); +file* fcheck(files_struct* files, int fd); +int close_fd(files_struct* files, int fd); +int do_dup2(files_struct* files, int oldfd, int newfd, int flags); +void do_close_on_exec(files_struct* files); diff --git a/include/lume/features.h b/include/lume/features.h new file mode 100644 index 0000000..85cfb72 --- /dev/null +++ b/include/lume/features.h @@ -0,0 +1,40 @@ +#ifndef _FEATURES_H +#define _FEATURES_H + +#if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE 1 +#endif + +#if defined(_DEFAULT_SOURCE) && !defined(_BSD_SOURCE) +#define _BSD_SOURCE 1 +#endif + +#if !defined(_POSIX_SOURCE) && !defined(_POSIX_C_SOURCE) \ + && !defined(_XOPEN_SOURCE) && !defined(_GNU_SOURCE) \ + && !defined(_BSD_SOURCE) && !defined(__STRICT_ANSI__) +#define _BSD_SOURCE 1 +#define _XOPEN_SOURCE 700 +#endif + +#if __STDC_VERSION__ >= 199901L +#define __restrict restrict +#elif !defined(__GNUC__) +#define __restrict +#endif + +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +#define __inline inline +#elif !defined(__GNUC__) +#define __inline +#endif + +#if __STDC_VERSION__ >= 201112L +#elif defined(__GNUC__) +#define _Noreturn __attribute__((__noreturn__)) +#else +#define _Noreturn +#endif + +#define __REDIR(x,y) __typeof__(x) x __asm__(#y) + +#endif diff --git a/include/lume/file.h b/include/lume/file.h new file mode 100644 index 0000000..349059b --- /dev/null +++ b/include/lume/file.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * file.h — Open file description + file_operations + path + vfsmount + * + * Modeled after Linux include/linux/fs.h struct file / file_operations. + * Reference: docs/vfs/04_file.md + */ + +#include +#include +#include +#include + +struct inode; +struct dentry; +struct super_block; +struct address_space; +struct dir_context; +struct VmAreaStruct; +struct kstat; + +/* ================================================================== */ +/* vfsmount (public part) */ +/* ================================================================== */ + +struct vfsmount { + dentry* mnt_root; + super_block* mnt_sb; + int mnt_flags; +}; + +vfsmount* mntget(vfsmount* mnt); +void mntput(vfsmount* mnt); + +/* ================================================================== */ +/* path */ +/* ================================================================== */ + +struct path { + vfsmount* mnt; + dentry* de; /* Linux calls this 'dentry' but that conflicts in C++ */ +}; + +void path_get(const path* p); +void path_put(const path* p); + +/* ================================================================== */ +/* file */ +/* ================================================================== */ + +struct file_operations; + +struct file { + Spinlock f_lock; + uint32 f_mode; /* FMODE_* */ + const file_operations* f_op; + address_space* f_mapping; + void* private_data; + inode* f_inode; + unsigned int f_flags; /* O_* */ + + path f_path; + + int64 f_pos; + lume::atomic f_count; +}; + +/* ================================================================== */ +/* Accessors */ +/* ================================================================== */ + +inline inode* file_inode(const file* f) { return f->f_inode; } +inline dentry* file_dentry(const file* f) { return f->f_path.de; } + +file* get_file(file* f); +file* alloc_empty_file(int flags); +void fput(file* fp); + +/* ================================================================== */ +/* dir_context (for iterate_shared / getdents64) */ +/* ================================================================== */ + +struct dir_context; +using filldir_t = bool (*)(dir_context* ctx, const char* name, int namelen, + int64 offset, uint64 ino, unsigned int d_type); + +struct dir_context { + filldir_t actor; + int64 pos; +}; + +/* ================================================================== */ +/* file_operations */ +/* ================================================================== */ + +struct file_operations { + int64 (*llseek)(file* fp, int64 offset, int whence); + int64 (*read)(file* fp, char* buf, uint64 count, int64* pos); + int64 (*write)(file* fp, const char* buf, uint64 count, int64* pos); + int (*iterate_shared)(file* fp, dir_context* ctx); + uint32 (*poll)(file* fp, void* pt); + long (*unlocked_ioctl)(file* fp, unsigned int cmd, uint64 arg); + int (*mmap)(file* fp, VmAreaStruct* vma); + int (*open)(inode* ip, file* fp); + int (*flush)(file* fp); + int (*release)(inode* ip, file* fp); + int (*fsync)(file* fp, int64 start, int64 end, int datasync); +}; + +/* ================================================================== */ +/* VFS read/write/seek */ +/* ================================================================== */ + +int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos); +int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos); +int64 vfs_llseek(file* fp, int64 offset, int whence); +int vfs_fstat(file* fp, kstat* stat); + +/* Generic helpers for simple fs */ +int64 generic_file_llseek(file* fp, int64 offset, int whence); + +/* ================================================================== */ +/* path helpers */ +/* ================================================================== */ + +inline bool path_equal(const path* a, const path* b) { + return a->mnt == b->mnt && a->de == b->de; +} diff --git a/include/lume/fs.h b/include/lume/fs.h new file mode 100644 index 0000000..8dac552 --- /dev/null +++ b/include/lume/fs.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs.h — Core VFS data structures + * + * Defines super_block, inode, address_space, and their operations. + * Modeled after Linux include/linux/fs.h + include/linux/fs/super_types.h. + * + * Reference: docs/vfs/01_super_block.md, docs/vfs/02_inode.md + */ + +#include +#include +#include +#include +#include + +/* Forward declarations */ +struct super_block; +struct inode; +struct dentry; +struct file; +struct vfsmount; +struct path; +struct kstat; +struct iattr; +struct file_operations; +struct inode_operations; +struct dentry_operations; +struct dir_context; +struct pipe_inode_info; +struct VmAreaStruct; +struct Page; + +/* ================================================================== */ +/* address_space_operations */ +/* ================================================================== */ + +struct address_space_operations { + int (*read_folio)(file* fp, Page* page); + int (*write_begin)(struct address_space* mapping, int64 pos, + unsigned len, Page** pagep, void** fsdata); + int (*write_end)(struct address_space* mapping, int64 pos, + unsigned len, unsigned copied, + Page* page, void* fsdata); +}; + +/* ================================================================== */ +/* address_space */ +/* ================================================================== */ + +struct address_space { + inode* host; + const address_space_operations* a_ops; + uint64 nrpages; + uint64 flags; + Spinlock i_private_lock; +}; + +/* ================================================================== */ +/* super_operations */ +/* ================================================================== */ + +struct super_operations { + inode* (*alloc_inode)(super_block* sb); + void (*destroy_inode)(inode* ip); + void (*free_inode)(inode* ip); + + void (*dirty_inode)(inode* ip, int flags); + int (*write_inode)(inode* ip, bool sync); + int (*drop_inode)(inode* ip); + void (*evict_inode)(inode* ip); + void (*put_super)(super_block* sb); + int (*sync_fs)(super_block* sb, int wait); + int (*statfs)(dentry* de, struct kstatfs* buf); + void (*umount_begin)(super_block* sb); +}; + +/* ================================================================== */ +/* file_system_type */ +/* ================================================================== */ + +struct file_system_type { + const char* name; + int fs_flags; + super_block* (*mount)(file_system_type* type, int flags, + const char* dev_name, void* data); + void (*kill_sb)(super_block* sb); + list_node fs_list; +}; + +int register_filesystem(file_system_type* fs); +file_system_type* get_fs_type(const char* name); + +/* ================================================================== */ +/* super_block */ +/* ================================================================== */ + +struct mount; + +struct super_block { + list_node s_list; + uint64 s_dev; + uint8 s_blocksize_bits; + uint64 s_blocksize; + int64 s_maxbytes; + file_system_type* s_type; + const super_operations* s_op; + + uint64 s_flags; + uint64 s_magic; + dentry* s_root; + + char s_id[32]; + void* s_fs_info; + + uint32 s_time_gran; + int64 s_time_min; + int64 s_time_max; + + Spinlock s_inode_list_lock; + list_head s_inodes; + + lume::atomic s_active; + int s_count; + + mount* s_mounts; + uint32 s_max_links; + const dentry_operations* s_d_op; +}; + +/* ================================================================== */ +/* inode */ +/* ================================================================== */ + +struct inode { + /* ---- Path-walk hot fields ---- */ + uint32 i_mode; + uint32 i_flags; + uint32 i_uid; + uint32 i_gid; + + const inode_operations* i_op; + super_block* i_sb; + address_space* i_mapping; + + /* ---- Stat data ---- */ + uint64 i_ino; + uint32 i_nlink; /* Use set_nlink/inc_nlink/drop_nlink */ + uint32 i_rdev; + int64 i_size; + + int64 i_atime_sec; + int64 i_mtime_sec; + int64 i_ctime_sec; + uint32 i_atime_nsec; + uint32 i_mtime_nsec; + uint32 i_ctime_nsec; + uint32 i_generation; + + Spinlock i_lock; + unsigned short i_bytes; + uint8 i_blkbits; + int64 i_blocks; + + /* ---- State ---- */ + uint32 i_state; + + /* ---- Lists ---- */ + list_node i_sb_list; + list_head i_dentry; + + /* ---- Counters ---- */ + lume::atomic i_count; + lume::atomic i_writecount; + + /* ---- File ops ---- */ + const file_operations* i_fop; + + /* ---- Embedded address_space ---- */ + address_space i_data; + + /* ---- Type-specific ---- */ + union { + pipe_inode_info* i_pipe; + char* i_link; + uint32 i_dir_seq; + }; + + void* i_private; +}; + +/* ================================================================== */ +/* inode_operations */ +/* ================================================================== */ + +struct inode_operations { + dentry* (*lookup)(inode* dir, dentry* de, unsigned int flags); + int (*create)(inode* dir, dentry* de, uint32 mode, bool excl); + int (*link)(dentry* old_de, inode* dir, dentry* new_de); + int (*unlink)(inode* dir, dentry* de); + int (*symlink)(inode* dir, dentry* de, const char* target); + int (*mkdir)(inode* dir, dentry* de, uint32 mode); + int (*rmdir)(inode* dir, dentry* de); + int (*mknod)(inode* dir, dentry* de, uint32 mode, uint32 rdev); + int (*rename)(inode* old_dir, dentry* old_de, + inode* new_dir, dentry* new_de, unsigned int flags); + int (*setattr)(dentry* de, iattr* attr); + int (*getattr)(const path* p, kstat* stat, uint32 request_mask, + unsigned int query_flags); + const char* (*get_link)(dentry* de, inode* ip); + int (*permission)(inode* ip, int mask); +}; + +/* ================================================================== */ +/* Inode API */ +/* ================================================================== */ + +inode* new_inode(super_block* sb); +inode* iget(super_block* sb, uint64 ino); +void iput(inode* ip); +void ihold(inode* ip); +void mark_inode_dirty(inode* ip); + +void set_nlink(inode* ip, uint32 nlink); +void inc_nlink(inode* ip); +void drop_nlink(inode* ip); +void clear_nlink(inode* ip); + +void inode_init_owner(inode* ip, const inode* dir, uint32 mode); + +/* Generic helpers used by simple filesystems */ +void generic_fillattr(inode* ip, kstat* stat); + +/* ================================================================== */ +/* kstat (matches oscomp ABI layout exactly) */ +/* ================================================================== */ + +struct kstat { + uint64 st_dev; + uint64 st_ino; + uint32 st_mode; + uint32 st_nlink; + uint32 st_uid; + uint32 st_gid; + uint64 st_rdev; + uint64 __pad; + int64 st_size; + int32 st_blksize; + int32 __pad2; + int64 st_blocks; + int64 st_atime_sec; + int64 st_atime_nsec; + int64 st_mtime_sec; + int64 st_mtime_nsec; + int64 st_ctime_sec; + int64 st_ctime_nsec; + uint32 __unused[2]; +}; + +struct kstatfs { + int64 f_type; + int64 f_bsize; + uint64 f_blocks; + uint64 f_bfree; + uint64 f_bavail; + uint64 f_files; + uint64 f_ffree; + int32 f_fsid[2]; + int64 f_namelen; + int64 f_frsize; + int64 f_flags; + int64 f_spare[4]; +}; + +/* ================================================================== */ +/* iattr (for setattr) */ +/* ================================================================== */ + +struct iattr { + unsigned int ia_valid; /* ATTR_* flags */ + uint32 ia_mode; + uint32 ia_uid; + uint32 ia_gid; + int64 ia_size; + int64 ia_atime_sec; + int64 ia_atime_nsec; + int64 ia_mtime_sec; + int64 ia_mtime_nsec; + int64 ia_ctime_sec; + int64 ia_ctime_nsec; +}; + +/* ATTR_* flags */ +inline constexpr uint32 ATTR_MODE = (1U << 0); +inline constexpr uint32 ATTR_UID = (1U << 1); +inline constexpr uint32 ATTR_GID = (1U << 2); +inline constexpr uint32 ATTR_SIZE = (1U << 3); +inline constexpr uint32 ATTR_ATIME = (1U << 4); +inline constexpr uint32 ATTR_MTIME = (1U << 5); +inline constexpr uint32 ATTR_CTIME = (1U << 6); + +/* ================================================================== */ +/* VFS init */ +/* ================================================================== */ + +void vfs_init(); diff --git a/include/lume/fs_struct.h b/include/lume/fs_struct.h new file mode 100644 index 0000000..ba643b5 --- /dev/null +++ b/include/lume/fs_struct.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs_struct.h — Per-task filesystem context (cwd, root) + * + * Modeled after Linux include/linux/fs_struct.h. + * Reference: docs/vfs/05_fdtable.md + */ + +#include +#include +#include /* struct path */ +#include + +/* ================================================================== */ +/* fs_struct */ +/* ================================================================== */ + +struct fs_struct { + lume::atomic count; /* Reference count (CLONE_FS) */ + Spinlock lock; + int umask; /* File creation mask */ + path root; /* Root directory */ + path pwd; /* Current working directory */ +}; + +/* ================================================================== */ +/* API */ +/* ================================================================== */ + +fs_struct* alloc_fs_struct(); +void put_fs_struct(fs_struct* fs); +fs_struct* copy_fs_struct(const fs_struct* old_fs); + +void set_fs_root(fs_struct* fs, const path* p); +void set_fs_pwd(fs_struct* fs, const path* p); diff --git a/include/lume/fs_types.h b/include/lume/fs_types.h new file mode 100644 index 0000000..767f37c --- /dev/null +++ b/include/lume/fs_types.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs_types.h — File mode constants, open flags, seek constants + * + * All values match Linux asm-generic so musl/busybox binaries work. + * Reference: docs/vfs/02_inode.md, docs/vfs/04_file.md + */ + +#include + +/* ================================================================== */ +/* S_IF* — file type in inode::i_mode upper bits */ +/* ================================================================== */ + +inline constexpr uint32 S_IFMT = 0170000; +inline constexpr uint32 S_IFSOCK = 0140000; +inline constexpr uint32 S_IFLNK = 0120000; +inline constexpr uint32 S_IFREG = 0100000; +inline constexpr uint32 S_IFBLK = 0060000; +inline constexpr uint32 S_IFDIR = 0040000; +inline constexpr uint32 S_IFCHR = 0020000; +inline constexpr uint32 S_IFIFO = 0010000; + +inline constexpr uint32 S_ISUID = 04000; +inline constexpr uint32 S_ISGID = 02000; +inline constexpr uint32 S_ISVTX = 01000; + +inline constexpr uint32 S_IRWXU = 00700; +inline constexpr uint32 S_IRUSR = 00400; +inline constexpr uint32 S_IWUSR = 00200; +inline constexpr uint32 S_IXUSR = 00100; +inline constexpr uint32 S_IRWXG = 00070; +inline constexpr uint32 S_IRGRP = 00040; +inline constexpr uint32 S_IWGRP = 00020; +inline constexpr uint32 S_IXGRP = 00010; +inline constexpr uint32 S_IRWXO = 00007; +inline constexpr uint32 S_IROTH = 00004; +inline constexpr uint32 S_IWOTH = 00002; +inline constexpr uint32 S_IXOTH = 00001; + +inline bool S_ISREG(uint32 m) { return (m & S_IFMT) == S_IFREG; } +inline bool S_ISDIR(uint32 m) { return (m & S_IFMT) == S_IFDIR; } +inline bool S_ISCHR(uint32 m) { return (m & S_IFMT) == S_IFCHR; } +inline bool S_ISBLK(uint32 m) { return (m & S_IFMT) == S_IFBLK; } +inline bool S_ISFIFO(uint32 m) { return (m & S_IFMT) == S_IFIFO; } +inline bool S_ISLNK(uint32 m) { return (m & S_IFMT) == S_IFLNK; } +inline bool S_ISSOCK(uint32 m) { return (m & S_IFMT) == S_IFSOCK; } + +/* ================================================================== */ +/* O_* — open(2) flags */ +/* ================================================================== */ + +inline constexpr int O_RDONLY = 00; +inline constexpr int O_WRONLY = 01; +inline constexpr int O_RDWR = 02; +inline constexpr int O_ACCMODE = 03; +inline constexpr int O_CREAT = 0100; +inline constexpr int O_EXCL = 0200; +inline constexpr int O_NOCTTY = 0400; +inline constexpr int O_TRUNC = 01000; +inline constexpr int O_APPEND = 02000; +inline constexpr int O_NONBLOCK = 04000; +inline constexpr int O_DSYNC = 010000; +inline constexpr int O_DIRECTORY = 0200000; +inline constexpr int O_NOFOLLOW = 0400000; +inline constexpr int O_CLOEXEC = 02000000; + +/* ================================================================== */ +/* AT_* — *at() syscall dirfd constants */ +/* ================================================================== */ + +inline constexpr int AT_FDCWD = -100; +inline constexpr int AT_REMOVEDIR = 0x200; +inline constexpr int AT_SYMLINK_NOFOLLOW = 0x100; +inline constexpr int AT_EMPTY_PATH = 0x1000; + +/* ================================================================== */ +/* SEEK_* — lseek(2) whence */ +/* ================================================================== */ + +inline constexpr int SEEK_SET = 0; +inline constexpr int SEEK_CUR = 1; +inline constexpr int SEEK_END = 2; + +/* ================================================================== */ +/* FMODE_* — internal file mode flags */ +/* ================================================================== */ + +inline constexpr uint32 FMODE_READ = (1U << 0); +inline constexpr uint32 FMODE_WRITE = (1U << 1); +inline constexpr uint32 FMODE_LSEEK = (1U << 2); +inline constexpr uint32 FMODE_PREAD = (1U << 3); +inline constexpr uint32 FMODE_PWRITE = (1U << 4); +inline constexpr uint32 FMODE_EXEC = (1U << 5); +inline constexpr uint32 FMODE_CAN_READ = (1U << 17); +inline constexpr uint32 FMODE_CAN_WRITE = (1U << 18); +inline constexpr uint32 FMODE_OPENED = (1U << 19); +inline constexpr uint32 FMODE_CREATED = (1U << 20); +inline constexpr uint32 FMODE_STREAM = (1U << 21); + +/* ================================================================== */ +/* MAY_* — permission check flags */ +/* ================================================================== */ + +inline constexpr int MAY_EXEC = 0x00000001; +inline constexpr int MAY_WRITE = 0x00000002; +inline constexpr int MAY_READ = 0x00000004; +inline constexpr int MAY_OPEN = 0x00000020; +inline constexpr int MAY_CHDIR = 0x00000040; + +/* ================================================================== */ +/* DT_* — directory entry types (for getdents64) */ +/* ================================================================== */ + +inline constexpr uint8 DT_UNKNOWN = 0; +inline constexpr uint8 DT_FIFO = 1; +inline constexpr uint8 DT_CHR = 2; +inline constexpr uint8 DT_DIR = 4; +inline constexpr uint8 DT_BLK = 6; +inline constexpr uint8 DT_REG = 8; +inline constexpr uint8 DT_LNK = 10; +inline constexpr uint8 DT_SOCK = 12; + +/* ================================================================== */ +/* SB_* — super_block flags */ +/* ================================================================== */ + +inline constexpr uint64 SB_RDONLY = (1ULL << 0); +inline constexpr uint64 SB_NOSUID = (1ULL << 1); +inline constexpr uint64 SB_NODEV = (1ULL << 2); +inline constexpr uint64 SB_NOEXEC = (1ULL << 3); +inline constexpr uint64 SB_SYNCHRONOUS = (1ULL << 4); +inline constexpr uint64 SB_NOATIME = (1ULL << 10); +inline constexpr uint64 SB_NODIRATIME = (1ULL << 11); +inline constexpr uint64 SB_SILENT = (1ULL << 15); +inline constexpr uint64 SB_KERNMOUNT = (1ULL << 22); +inline constexpr uint64 SB_LAZYTIME = (1ULL << 25); +inline constexpr uint64 SB_ACTIVE = (1ULL << 30); + +/* ================================================================== */ +/* MNT_* — mount flags */ +/* ================================================================== */ + +inline constexpr int MNT_NOSUID = 0x01; +inline constexpr int MNT_NODEV = 0x02; +inline constexpr int MNT_NOEXEC = 0x04; +inline constexpr int MNT_NOATIME = 0x08; +inline constexpr int MNT_RELATIME = 0x20; +inline constexpr int MNT_READONLY = 0x40; +inline constexpr int MNT_INTERNAL = 0x4000; + +/* ================================================================== */ +/* Inode state flags */ +/* ================================================================== */ + +inline constexpr uint32 I_NEW = (1U << 0); +inline constexpr uint32 I_DIRTY_SYNC = (1U << 4); +inline constexpr uint32 I_DIRTY_DATASYNC = (1U << 5); +inline constexpr uint32 I_DIRTY_PAGES = (1U << 6); +inline constexpr uint32 I_WILL_FREE = (1U << 7); +inline constexpr uint32 I_FREEING = (1U << 8); +inline constexpr uint32 I_CLEAR = (1U << 9); +inline constexpr uint32 I_REFERENCED = (1U << 10); +inline constexpr uint32 I_DIRTY_TIME = (1U << 12); +inline constexpr uint32 I_CREATING = (1U << 15); + +inline constexpr uint32 I_DIRTY_INODE = I_DIRTY_SYNC | I_DIRTY_DATASYNC; +inline constexpr uint32 I_DIRTY = I_DIRTY_INODE | I_DIRTY_PAGES; + +/* ================================================================== */ +/* DCACHE_* — dentry flags */ +/* ================================================================== */ + +inline constexpr uint32 DCACHE_OP_HASH = (1U << 0); +inline constexpr uint32 DCACHE_OP_COMPARE = (1U << 1); +inline constexpr uint32 DCACHE_OP_REVALIDATE = (1U << 2); +inline constexpr uint32 DCACHE_DISCONNECTED = (1U << 5); +inline constexpr uint32 DCACHE_REFERENCED = (1U << 6); +inline constexpr uint32 DCACHE_CANT_MOUNT = (1U << 8); +inline constexpr uint32 DCACHE_MOUNTED = (1U << 15); +inline constexpr uint32 DCACHE_ENTRY_TYPE = (7U << 19); +inline constexpr uint32 DCACHE_MISS_TYPE = (0U << 19); +inline constexpr uint32 DCACHE_DIRECTORY_TYPE = (2U << 19); +inline constexpr uint32 DCACHE_REGULAR_TYPE = (4U << 19); +inline constexpr uint32 DCACHE_SPECIAL_TYPE = (5U << 19); +inline constexpr uint32 DCACHE_SYMLINK_TYPE = (6U << 19); + +/* ================================================================== */ +/* FS_* — file_system_type flags */ +/* ================================================================== */ + +inline constexpr int FS_REQUIRES_DEV = 1; +inline constexpr int FS_NO_DEVICE = 2; diff --git a/include/lume/init.h b/include/lume/init.h new file mode 100644 index 0000000..541ab96 --- /dev/null +++ b/include/lume/init.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_INIT_H +#define _LUME_INIT_H + +/* + * Boot-time section markers. + * + * Mirrors the subset of Linux include/linux/init.h that LumeOS uses: + * + * __init mark a function as boot-only — emitted into .init.text, + * which the kernel can later free once setup_arch / + * start_kernel have finished (LumeOS doesn't yet reclaim + * this memory, but the segment lives so future versions + * can without source changes). + * __initdata same for data, emitted into .init.data. + * asmlinkage ABI marker for C functions called from assembly. On + * RISC-V the default C ABI matches the assembly calling + * convention, so this is empty (matches Linux RISC-V). + * + * No attempt is made to mirror Linux's COMMAND_LINE_SIZE / saved_command_line + * machinery — that belongs in a future . + */ + +#define __init __attribute__((section(".init.text"))) +#define __initdata __attribute__((section(".init.data"))) + +#ifndef asmlinkage +#define asmlinkage /* RISC-V: empty, ABI already matches */ +#endif + +#endif /* _LUME_INIT_H */ diff --git a/include/lume/irq.h b/include/lume/irq.h new file mode 100644 index 0000000..f5f34e4 --- /dev/null +++ b/include/lume/irq.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * IRQ Manager — Dynamic Interrupt Dispatch + * + * Drivers register ISR callbacks at init time via register_handler(). + * The MI trap layer calls dispatch() when an external interrupt arrives. + * dispatch() runs in interrupt context — handlers must be short and + * non-blocking. + * + * Lock contract: + * - lock_ protects register/unregister (cold path). + * - dispatch() is lock-free: handler pointers are published with RELEASE + * semantics and read with ACQUIRE in dispatch(). + * - Handlers must not acquire VmSpace::lock or sleep. + * + * Reference: docs/specs/trap.md §3.3 + */ + +#include +#include + +/* IRQ handler callback signature */ +using IrqHandler = void (*)(uint32 irq, void *arg); + +/* Abstract Interrupt Controller Interface */ +struct IrqChip { + const char* name; + uint32 (*claim)(); + void (*complete)(uint32 irq); + void (*init_ap)(); + void (*enable)(uint32 irq); + void (*disable)(uint32 irq); + void (*set_priority)(uint32 irq, uint32 priority); +}; + +class IrqManager { +public: + void init(); + + /* Register an ISR for the given IRQ number. + * Returns 0 on success, -EINVAL if irq out of range, -EBUSY if already + * registered. */ + int register_handler(uint32 irq, IrqHandler handler, void *arg); + + /* Unregister the ISR for the given IRQ number. */ + void unregister_handler(uint32 irq); + + /* Dispatch an interrupt to the registered handler. + * Called from MI trap layer in interrupt context. Lock-free. */ + void dispatch(uint32 irq); + + /* Set the active interrupt controller chip */ + void set_chip(const IrqChip* chip); + const IrqChip* get_chip() const { return chip_; } + + /* Architecture-independent claim and complete for the active IRQ chip */ + uint32 claim() const; + void complete(uint32 irq) const; + void init_ap() const; + void enable(uint32 irq) const; + void disable(uint32 irq) const; + void set_priority(uint32 irq, uint32 priority) const; + + /* Check if a handler is registered for the given IRQ. */ + bool has_handler(uint32 irq) const; + + static constexpr uint32 kMaxIrqs = 64; + +private: + struct IrqEntry { + lume::atomic handler_ptr; // Stored as uint64 for atomic access + void *arg = nullptr; + }; + + const IrqChip* chip_ = nullptr; + IrqEntry entries_[kMaxIrqs]; +}; + +/* Global IRQ manager singleton */ +extern IrqManager g_irq_manager; diff --git a/include/lume/kbuild.h b/include/lume/kbuild.h new file mode 100644 index 0000000..4c3e12a --- /dev/null +++ b/include/lume/kbuild.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_KBUILD_H +#define _LUME_KBUILD_H + +/* + * Helpers for arch//kernel/asm-offsets.c — 1:1 with Linux + * include/linux/kbuild.h. + * + * Trick: each DEFINE/OFFSET emits an inline-asm ".ascii" directive + * containing a literal "->" prefix. When the file is compiled with + * + * $(CC) -S asm-offsets.c -o asm-offsets.s + * + * the resulting assembly contains lines like + * + * ->KERNEL_MAP_VIRT_ADDR $0 offsetof(struct kernel_mapping, virt_addr) + * + * scripts/extract-asm-offsets.sed then rewrites those into proper + * `#define KERNEL_MAP_VIRT_ADDR 0` lines for inclusion from head.S. + * + * The benefit over hand-written #defines is that struct layout changes + * are caught at build time: rearrange a field and the asm offset + * changes automatically. + */ + +#define DEFINE(sym, val) \ + asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val)) + +#define BLANK() \ + asm volatile("\n.ascii \"->\"" : : ) + +#define OFFSET(sym, str, mem) \ + DEFINE(sym, offsetof(struct str, mem)) + +#define COMMENT(x) \ + asm volatile("\n.ascii \"->#" x "\"") + +#endif /* _LUME_KBUILD_H */ diff --git a/include/lume/kernel_map.h b/include/lume/kernel_map.h new file mode 100644 index 0000000..7eafe88 --- /dev/null +++ b/include/lume/kernel_map.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_KERNEL_MAP_H +#define _LUME_KERNEL_MAP_H + +/* + * struct kernel_mapping — where the kernel image lives in physical and + * virtual memory. Field names and order match Linux's + * arch/riscv/include/asm/page.h struct kernel_mapping so that: + * + * 1. arch/riscv/kernel/head.S can do `la a1, kernel_map` then + * `REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)` and read the virtual + * address. The KERNEL_MAP_* asm constants come from + * , produced by arch/riscv/kernel/ + * asm-offsets.c at build time — re-order the struct and the + * asm offsets follow automatically. + * + * 2. arch/riscv/mm/init.c (Linux origin) populates the same fields + * with the same semantics so no source edits are needed. + */ + +#ifndef __ASSEMBLY__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct kernel_mapping { + unsigned long virt_addr; + unsigned long virt_offset; + uintptr_t phys_addr; + uintptr_t size; + unsigned long va_pa_offset; + unsigned long page_offset; + unsigned long va_kernel_pa_offset; +}; + +extern struct kernel_mapping kernel_map; + +#ifdef __cplusplus +} +#endif + +#else /* __ASSEMBLY__ */ + +/* Assembly sees only the field offsets, picked up from the generated + * header. Including the C struct above would be a parse error. */ +#include + +#endif /* __ASSEMBLY__ */ + +#endif /* _LUME_KERNEL_MAP_H */ diff --git a/include/lume/klog.h b/include/lume/klog.h new file mode 100644 index 0000000..69ab269 --- /dev/null +++ b/include/lume/klog.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Kernel Logging Subsystem + * + * All kernel output goes through klog(), which formats messages into + * a fixed-size ring buffer and optionally emits them to the console. + * + * Architecture: + * vsnprintf_() ← pure formatting (mpaland/printf) + * ↓ + * KlogBuffer (ring) ← all messages stored here + * ↓ + * early_putc() (sync) ← current: synchronous UART output + * klogd thread (async) ← future: background flush thread + * /proc/kmsg ← future: userspace dmesg interface + * + * Reference: docs/specs/klog.md + */ + +#include + +/* ======================================================================== + * Log Levels (matches Linux kern_levels.h ordering) + * ======================================================================== */ + +enum KlogLevel : uint8 { + KLOG_EMERG = 0, /* System is unusable */ + KLOG_ALERT = 1, /* Action must be taken immediately */ + KLOG_CRIT = 2, /* Critical conditions */ + KLOG_ERR = 3, /* Error conditions */ + KLOG_WARN = 4, /* Warning conditions */ + KLOG_NOTICE = 5, /* Normal but significant condition */ + KLOG_INFO = 6, /* Informational */ + KLOG_DEBUG = 7, /* Debug-level messages */ +}; + +/* Log subsystems (bitmask-friendly) */ +enum KlogSubsys : uint8 { + KLOG_SUBSYS_GENERIC = 0, + KLOG_SUBSYS_SCHED = 1, + KLOG_SUBSYS_TRAP = 2, + KLOG_SUBSYS_IRQ = 3, + KLOG_SUBSYS_TIMER = 4, + KLOG_SUBSYS_VMM = 5, + KLOG_SUBSYS_PMM = 6, + KLOG_SUBSYS_SLAB = 7, + KLOG_SUBSYS_FS = 8, + KLOG_SUBSYS_DRIVER = 9, + KLOG_SUBSYS_TEST = 10, + KLOG_SUBSYS_SYSCALL = 11, /* per-syscall entry trace */ + KLOG_SUBSYS_EXEC = 12, /* ELF load / exec internals */ + KLOG_SUBSYS_SIGNAL = 13, /* signal delivery trace */ + KLOG_SUBSYS_TTY = 14, /* tty / job-control trace */ + KLOG_SUBSYS_MAX = 15, +}; + +/* Default: show everything up to INFO. DEBUG is compile-gated. */ +#ifndef KLOG_DEFAULT_LEVEL +#define KLOG_DEFAULT_LEVEL KLOG_INFO +#endif + +/* Ring buffer size: 2^KLOG_BUF_SHIFT bytes (default 64KB) */ +#ifndef KLOG_BUF_SHIFT +#define KLOG_BUF_SHIFT 16 +#endif + +#define KLOG_BUF_SIZE (1U << KLOG_BUF_SHIFT) + +/* Maximum length of a single log message (stack-allocated) */ +#define KLOG_LINE_MAX 256 + +struct KlogStats { + uint32 head; + uint32 tail; + uint32 console_tail; + uint32 dropped; + uint32 seq; +}; + +struct KlogRateLimit { + uint64 last_ts; + uint32 interval; + uint32 burst; + uint32 printed; + uint32 suppressed; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ======================================================================== + * Initialization + * ======================================================================== */ + +/* Initialize the ring buffer and console backend. Call once from BSP. */ +void klog_init(void); + +/* ======================================================================== + * Core API + * ======================================================================== */ + +/* Write a leveled log message. Formats into ring buffer, then + * synchronously flushes to console if level <= current threshold. */ +__attribute__((format(printf, 2, 3))) +void klog(KlogLevel level, const char* fmt, ...); + +__attribute__((format(printf, 3, 4))) +void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...); + +/* Linux-style printk interface */ +__attribute__((format(printf, 1, 2))) +int printk(const char* fmt, ...); +int vprintk(const char* fmt, __builtin_va_list va); + +/* Set runtime log level filter. Messages above this level are + * still stored in the ring buffer but not printed to console. */ +void klog_set_level(KlogLevel level); + +void klog_set_subsys_mask(uint64 mask); +void klog_enable_subsys(KlogSubsys subsys); +void klog_disable_subsys(KlogSubsys subsys); + +/* ======================================================================== + * Ring Buffer Read (for future dmesg / /proc/kmsg) + * ======================================================================== */ + +/* Read up to 'size' bytes from the ring buffer into 'buf'. + * Returns number of bytes read. Non-destructive peek. */ +int klog_read(char* buf, int size); + +/* Read and consume entries (advances tail). */ +int klog_read_consume(char* buf, int size); + +/* Flush pending entries to console (advances console tail). */ +void klog_flush(void); + +bool klog_ratelimit(KlogRateLimit* rl, uint64 now); + +void klog_stats(KlogStats* out); + +/* ======================================================================== + * Convenience Macros + * ======================================================================== */ + +#define pr_emerg(fmt, ...) klog_ex(KLOG_EMERG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_alert(fmt, ...) klog_ex(KLOG_ALERT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_crit(fmt, ...) klog_ex(KLOG_CRIT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_err(fmt, ...) klog_ex(KLOG_ERR, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_warn(fmt, ...) klog_ex(KLOG_WARN, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_notice(fmt, ...) klog_ex(KLOG_NOTICE, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_info(fmt, ...) klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) + +#define pr_emerg_s(subsys, fmt, ...) klog_ex(KLOG_EMERG, subsys, fmt, ##__VA_ARGS__) +#define pr_alert_s(subsys, fmt, ...) klog_ex(KLOG_ALERT, subsys, fmt, ##__VA_ARGS__) +#define pr_crit_s(subsys, fmt, ...) klog_ex(KLOG_CRIT, subsys, fmt, ##__VA_ARGS__) +#define pr_err_s(subsys, fmt, ...) klog_ex(KLOG_ERR, subsys, fmt, ##__VA_ARGS__) +#define pr_warn_s(subsys, fmt, ...) klog_ex(KLOG_WARN, subsys, fmt, ##__VA_ARGS__) +#define pr_notice_s(subsys, fmt, ...) klog_ex(KLOG_NOTICE, subsys, fmt, ##__VA_ARGS__) +#define pr_info_s(subsys, fmt, ...) klog_ex(KLOG_INFO, subsys, fmt, ##__VA_ARGS__) + +#ifdef KLOG_ENABLE_DEBUG +#define pr_debug(fmt, ...) klog_ex(KLOG_DEBUG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#else +#define pr_debug(fmt, ...) do {} while (0) +#endif + +/* pr_debug_s: subsystem-tagged debug. Always compiled in (subsys is the gate); + * level KLOG_DEBUG ensures messages stay in the ring buffer but are filtered + * out of the console by default (console_level = KLOG_INFO). Toggle a subsys + * with klog_enable_subsys()/klog_disable_subsys() at runtime — or, to make + * the messages also reach the console for debugging, raise console_level + * via klog_set_level(KLOG_DEBUG). */ +#define pr_debug_s(subsys, fmt, ...) \ + klog_ex(KLOG_DEBUG, subsys, fmt, ##__VA_ARGS__) + +/* Linux kernel loglevel prefixes */ +#define KERN_SOH "\001" +#define KERN_EMERG KERN_SOH "0" +#define KERN_ALERT KERN_SOH "1" +#define KERN_CRIT KERN_SOH "2" +#define KERN_ERR KERN_SOH "3" +#define KERN_WARNING KERN_SOH "4" +#define KERN_NOTICE KERN_SOH "5" +#define KERN_INFO KERN_SOH "6" +#define KERN_DEBUG KERN_SOH "7" + +/* Boot stage log (thin wrapper over printk) */ +#define BOOT_STAGE(n, desc) \ + printk(KERN_INFO "STAGE%u %s\n", (unsigned)(n), (desc)) + +extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/kobject.h b/include/lume/kobject.h new file mode 100644 index 0000000..8581d6e --- /dev/null +++ b/include/lume/kobject.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * kobject — generic refcounted object with parent/child relationship. + * + * Mirrors Linux include/linux/kobject.h. Embedded into device, gendisk, + * request_queue, block_device, etc. Each kobject has a kobj_type that + * provides the release callback called when the last reference drops. + * + * sysfs integration is not yet implemented — the sd/sysfs_ops fields are + * reserved for future use. + * + * Reference: docs/specs/storage_stack.md §4.2 + */ + +#include +#include +#include +#include + +struct kobj_type; + +struct kobject { + const char* name; + kobject* parent; + kref refcount; + const kobj_type* ktype; + list_node entry; // node in parent->children + list_node children; // head of children list + Spinlock lock; + bool state_in_sysfs; // reserved, always false for now +}; + +struct kobj_type { + void (*release)(kobject*); + /* sysfs_ops + default_attrs: reserved for future sysfs implementation. */ +}; + +/* Initialize a kobject in place; refcount starts at 1. + * Caller must provide a non-null ktype with a valid release(). */ +void kobject_init(kobject* k, const kobj_type* type); + +/* Link `k` under `parent` (which may be null for a detached root). + * Caller passes a stable `name` pointer (e.g. a static string or an + * already-strdup'd buffer); kobject does not own the name string. */ +int kobject_add(kobject* k, kobject* parent, const char* name); + +/* Unlink from parent. Does NOT drop the final ref — caller decides + * when to kobject_put(). */ +void kobject_del(kobject* k); + +/* Bump / drop reference. */ +void kobject_get(kobject* k); +void kobject_put(kobject* k); diff --git a/include/lume/kprintf.h b/include/lume/kprintf.h new file mode 100644 index 0000000..b158602 --- /dev/null +++ b/include/lume/kprintf.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Kernel Formatted Output — kprintf + * + * Provides printf-style formatted output for the kernel. + * Built on top of stb_sprintf for full format string support + * without any libc dependency. + * + * Output is synchronous and directed to the console (UART). + * Safe to call from any context (interrupt or process). + * + * Usage: + * kprintf("Hello %s, addr=0x%lx\n", name, addr); + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Formatted print to console (like printf). */ +__attribute__((format(printf, 1, 2))) +int kprintf(const char* fmt, ...); + +/* Formatted print with va_list. */ +int kvprintf(const char* fmt, va_list va); + +/* Formatted snprintf into buffer. */ +__attribute__((format(printf, 3, 4))) +int ksnprintf(char* buf, int count, const char* fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/kref.h b/include/lume/kref.h new file mode 100644 index 0000000..13d53f3 --- /dev/null +++ b/include/lume/kref.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * kref — atomic reference counting. + * + * Mirrors Linux include/linux/kref.h. Use kref_init() to set count=1, + * kref_get() / kref_put() to add/drop refs. kref_put() invokes the + * release callback when the final ref is dropped and returns 1 in that + * case. + * + * Reference: docs/specs/storage_stack.md §4.1 + */ + +#include +#include + +struct kref { + lume::atomic refcount; +}; + +inline void kref_init(kref* k) +{ + k->refcount.store(1, __ATOMIC_RELEASE); +} + +inline void kref_get(kref* k) +{ + k->refcount.fetch_add(1, __ATOMIC_RELAXED); +} + +inline int kref_put(kref* k, void (*release)(kref*)) +{ + if (k->refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) { + release(k); + return 1; + } + return 0; +} + +inline int32 kref_read(const kref* k) +{ + return k->refcount.load(__ATOMIC_RELAXED); +} diff --git a/include/lume/libfdt.h b/include/lume/libfdt.h new file mode 100644 index 0000000..e3496e9 --- /dev/null +++ b/include/lume/libfdt.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INCLUDE_LIBFDT_H_ +#define _INCLUDE_LIBFDT_H_ + +#include +#include "../../scripts/dtc/libfdt/libfdt.h" + +#endif /* _INCLUDE_LIBFDT_H_ */ diff --git a/include/lume/libfdt_env.h b/include/lume/libfdt_env.h new file mode 100644 index 0000000..a95fb1d --- /dev/null +++ b/include/lume/libfdt_env.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef LIBFDT_ENV_H +#define LIBFDT_ENV_H + +/* + * Kernel-side adapter for the standalone libfdt sources in + * scripts/dtc/libfdt/. libfdt's *.c files start with + * #include "libfdt_env.h" + * and expect this header to provide: + * - byte-swap helpers (cpu_to_fdt16/32/64, fdt16/32/64_to_cpu) + * - fdt16/32/64_t typedefs (just plain uint types — endianness is + * handled in the helpers above) + * - string / mem helpers (memcpy, memmove, memcmp, strlen, strcmp, + * strchr, strnlen, ...) + * - INT_MAX, UINT32_MAX (libfdt bounds-checks props with these) + * - size_t / uintptr_t (everywhere) + * + * LumeOS-native: no Linux includes. Provides the surface + * libfdt needs and nothing else. + */ + +#include /* uint16_t / uint32_t / uint64_t / size_t */ +#include /* INT_MAX */ +#include /* libc-shim memcpy / memcmp / strlen ... */ + +/* libfdt uses these names; both come from . */ +#define INT32_MAX 2147483647 +#define UINT32_MAX 4294967295U + +/* libfdt's "FDT word" types — opaque big-endian-stored u16/u32/u64. + * The byte-swap helpers below are the only access path. */ +typedef uint16_t fdt16_t; +typedef uint32_t fdt32_t; +typedef uint64_t fdt64_t; + +/* + * Endianness helpers. FDT (Device Tree Blob) is big-endian by spec. + * LumeOS runs on RISC-V (little-endian) and x86 (little-endian); we + * unconditionally byte-swap. + * + * Open-coded shifts rather than __builtin_bswapN — on RISC-V without + * the Zbb extension, gcc lowers __builtin_bswap32 to a libgcc call + * (__bswapsi2 / __bswapdi2) which isn't present in a freestanding + * kernel link. Mirrors Linux's scripts/dtc/libfdt/libfdt_env.h + * which uses ___constant_swab32-style explicit shifts. */ +static inline uint16_t __lumefdt_bswap16(uint16_t x) +{ + return (uint16_t)((x << 8) | (x >> 8)); +} +static inline uint32_t __lumefdt_bswap32(uint32_t x) +{ + return ((x & 0x000000FFu) << 24) | + ((x & 0x0000FF00u) << 8) | + ((x & 0x00FF0000u) >> 8) | + ((x & 0xFF000000u) >> 24); +} +static inline uint64_t __lumefdt_bswap64(uint64_t x) +{ + return ((x & 0x00000000000000FFull) << 56) | + ((x & 0x000000000000FF00ull) << 40) | + ((x & 0x0000000000FF0000ull) << 24) | + ((x & 0x00000000FF000000ull) << 8) | + ((x & 0x000000FF00000000ull) >> 8) | + ((x & 0x0000FF0000000000ull) >> 24) | + ((x & 0x00FF000000000000ull) >> 40) | + ((x & 0xFF00000000000000ull) >> 56); +} + +#define fdt16_to_cpu(x) __lumefdt_bswap16((uint16_t)(x)) +#define cpu_to_fdt16(x) ((fdt16_t)__lumefdt_bswap16((uint16_t)(x))) +#define fdt32_to_cpu(x) __lumefdt_bswap32((uint32_t)(x)) +#define cpu_to_fdt32(x) ((fdt32_t)__lumefdt_bswap32((uint32_t)(x))) +#define fdt64_to_cpu(x) __lumefdt_bswap64((uint64_t)(x)) +#define cpu_to_fdt64(x) ((fdt64_t)__lumefdt_bswap64((uint64_t)(x))) + +#endif /* LIBFDT_ENV_H */ diff --git a/include/lume/limits.h b/include/lume/limits.h new file mode 100644 index 0000000..a275a71 --- /dev/null +++ b/include/lume/limits.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_LIMITS_H +#define _LUME_LIMITS_H + +/* + * Numeric limit constants — minimal subset needed by LumeOS kernel code. + * + * Values match the standard on all 64-bit LP64 targets, which + * is the only ABI LumeOS supports. Defined as plain integer literals so + * this header is usable from C, C++, and assembly without dragging in + * the toolchain's (which on hosted builds references libc + * internals we don't link against). + */ + +/* Signed/unsigned char */ +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 +#define UCHAR_MAX 255 +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX + +/* Short */ +#define SHRT_MIN (-32768) +#define SHRT_MAX 32767 +#define USHRT_MAX 65535 + +/* Int */ +#define INT_MIN (-INT_MAX - 1) +#define INT_MAX 2147483647 +#define UINT_MAX 4294967295U + +/* Long (LP64: 64-bit) */ +#define LONG_MIN (-LONG_MAX - 1L) +#define LONG_MAX 9223372036854775807L +#define ULONG_MAX 18446744073709551615UL + +/* Long long */ +#define LLONG_MIN (-LLONG_MAX - 1LL) +#define LLONG_MAX 9223372036854775807LL +#define ULLONG_MAX 18446744073709551615ULL + +/* size_t (LP64) */ +#define SIZE_MAX ULONG_MAX +#define SSIZE_MAX LONG_MAX + +#endif /* _LUME_LIMITS_H */ diff --git a/include/lume/list.h b/include/lume/list.h new file mode 100644 index 0000000..ed1202a --- /dev/null +++ b/include/lume/list.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include +#include + +/** + * @brief The node to be embedded in a struct to make it linkable. + * + * An object can be on multiple lists simultaneously by embedding multiple + * list_node members with different names. + */ +struct list_node { + list_node* prev; + list_node* next; + + /** + * @brief Initializes the node to point to itself, forming an empty list head. + */ + void init() { + prev = this; + next = this; + } + + /** + * @brief Checks if the list this node is the head of is empty. + * @return True if the list is empty, false otherwise. + */ + bool is_empty() const { + return next == this; + } +}; + +/* + * Type-safe container_of: given a pointer to a list_node member, + * recover a pointer to the enclosing object. + * Uses pointer-to-member arithmetic — no __builtin_offsetof needed. + */ +template +static inline T *list_entry(list_node *node) +{ + const size_t off = (size_t)(uintptr) & (((T *)nullptr)->*Member); + return reinterpret_cast(reinterpret_cast(node) - off); +} + +/** + * @brief A type-safe, intrusive, doubly-linked list. + * + * This class manages objects of type T that have a `list_node` member. + * It has zero runtime overhead compared to a manual C implementation and + * provides compile-time type safety. + * + * @tparam T The type of the object in the list. + * @tparam NodeMember A pointer-to-member specifying which `list_node` in T to use. + */ +template +class List { +private: + static list_node* get_node(T* obj) { + return &(obj->*NodeMember); + } + + // NOTE: `((T*)nullptr)->*NodeMember` is technically undefined behavior in + // standard C++. However, it's a common idiom in low-level programming + // (similar to `offsetof`) that compilers typically handle correctly, + // especially with flags like `-fno-delete-null-pointer-checks`. + // The alternative (using `offsetof`) would sacrifice the compile-time + // type safety of the pointer-to-member template parameter. + static T* get_obj(list_node* node) { + constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); + return reinterpret_cast(reinterpret_cast(node) - offset); + } + + static const T* get_obj(const list_node* node) { + constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); + return reinterpret_cast(reinterpret_cast(node) - offset); + } + +public: + class iterator; + class const_iterator; + + class iterator { + friend class List; + friend class const_iterator; + + public: + iterator(list_node* node) : node_(node) {} + + T& operator*() const { return *get_obj(node_); } + T* operator->() const { return get_obj(node_); } + + iterator& operator++() { + node_ = node_->next; + return *this; + } + iterator operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; + } + + iterator& operator--() { + node_ = node_->prev; + return *this; + } + iterator operator--(int) { + iterator tmp = *this; + --(*this); + return tmp; + } + + bool operator==(const iterator& other) const { return node_ == other.node_; } + bool operator!=(const iterator& other) const { return !(*this == other); } + + private: + list_node* node_; + }; + + class const_iterator { + public: + const_iterator(const list_node* node) : node_(node) {} + const_iterator(const iterator& other) : node_(other.node_) {} + + const T& operator*() const { return *get_obj(node_); } + const T* operator->() const { return get_obj(node_); } + + const_iterator& operator++() { + node_ = node_->next; + return *this; + } + const_iterator operator++(int) { + const_iterator tmp = *this; + ++(*this); + return tmp; + } + + const_iterator& operator--() { + node_ = node_->prev; + return *this; + } + const_iterator operator--(int) { + const_iterator tmp = *this; + --(*this); + return tmp; + } + + bool operator==(const const_iterator& other) const { return node_ == other.node_; } + bool operator!=(const const_iterator& other) const { return !(*this == other); } + + private: + const list_node* node_; + }; + + List() { head_.init(); } + ~List() = default; + + // This container is intrusive and does not own the objects. + // Copying or moving it is a logical error. + List(const List&) = delete; + List& operator=(const List&) = delete; + + bool empty() const { return head_.is_empty(); } + + void push_front(T* obj) { + list_node* node = get_node(obj); + node->next = head_.next; + node->prev = &head_; + head_.next->prev = node; + head_.next = node; + } + + void push_back(T* obj) { + list_node* node = get_node(obj); + node->prev = head_.prev; + node->next = &head_; + head_.prev->next = node; + head_.prev = node; + } + + /** + * @brief Splices all elements from another list to the front of this list. + * + * Moves all elements from `other` to the beginning of `this` list in O(1) + * time. The `other` list becomes empty after the operation. + * + * @param other The list to splice from. + */ + void splice_front(List& other) { + if (other.empty()) { + return; + } + list_node* this_first = head_.next; + list_node* other_first = other.head_.next; + list_node* other_last = other.head_.prev; + + // Link other's last to this's original first + other_last->next = this_first; + this_first->prev = other_last; + + // Link this's head to other's first + head_.next = other_first; + other_first->prev = &head_; + + // Reset the other list + other.head_.init(); + } + + /** + * @brief Splices all elements from another list to the back of this list. + * + * Moves all elements from `other` to the end of `this` list in O(1) + * time. The `other` list becomes empty after the operation. + * + * @param other The list to splice from. + */ + void splice_tail(List& other) { + if (other.empty()) { + return; + } + list_node* this_last = head_.prev; + list_node* other_first = other.head_.next; + list_node* other_last = other.head_.prev; + + // Link this's last to other's first + this_last->next = other_first; + other_first->prev = this_last; + + // Link other's last to this's head + other_last->next = &head_; + head_.prev = other_last; + + // Reset the other list + other.head_.init(); + } + + T* pop_front() { + if (empty()) { + return nullptr; + } + list_node* node = head_.next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + return get_obj(node); + } + + T* pop_back() { + if (empty()) { + return nullptr; + } + list_node* node = head_.prev; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + return get_obj(node); + } + + void remove(T* obj) { + list_node* node = get_node(obj); + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + } + + T* front() { + return empty() ? nullptr : get_obj(head_.next); + } + + const T* front() const { + return empty() ? nullptr : get_obj(head_.next); + } + + T* back() { + return empty() ? nullptr : get_obj(head_.prev); + } + + const T* back() const { + return empty() ? nullptr : get_obj(head_.prev); + } + + iterator begin() { return iterator(head_.next); } + iterator end() { return iterator(&head_); } + const_iterator begin() const { return const_iterator(head_.next); } + const_iterator end() const { return const_iterator(&head_); } + const_iterator cbegin() const { return const_iterator(head_.next); } + const_iterator cend() const { return const_iterator(&head_); } + +private: + list_node head_; +}; + +/* ======================================================================== + * Linux-compatible C-style list macros (for VFS subsystem) + * ======================================================================== */ + +using list_head = list_node; + +inline void list_init(list_node* head) { + head->prev = head; + head->next = head; +} + +inline bool list_empty(const list_node* head) { + return head->next == head; +} + +inline void __list_add(list_node* node, list_node* p, list_node* n) { + n->prev = node; + node->next = n; + node->prev = p; + p->next = node; +} + +inline void list_add(list_node* node, list_node* head) { + __list_add(node, head, head->next); +} + +inline void list_add_tail(list_node* node, list_node* head) { + __list_add(node, head->prev, head); +} + +inline void list_del(list_node* node) { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = nullptr; + node->prev = nullptr; +} + +inline void list_del_init(list_node* node) { + node->prev->next = node->next; + node->next->prev = node->prev; + list_init(node); +} + +#define list_entry(ptr, type, member) container_of(ptr, type, member) + +#define list_for_each(pos, head) \ + for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) + +#define list_for_each_safe(pos, tmp, head) \ + for ((pos) = (head)->next, (tmp) = (pos)->next; \ + (pos) != (head); \ + (pos) = (tmp), (tmp) = (pos)->next) diff --git a/include/lume/lumeos.lds.h b/include/lume/lumeos.lds.h new file mode 100644 index 0000000..58d2f72 --- /dev/null +++ b/include/lume/lumeos.lds.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Common Linker Script Macros (Machine-Independent) + * + * Shared section definitions that every architecture must emit. + * Architecture-specific lumeos.lds.S files #include this header + * and use these macros to avoid duplicating common boilerplate. + * + * Modeled after Linux include/asm-generic/vmlinux.lds.h + * Reference: docs/specs/linker_and_entry.md + */ + +#ifndef _LUME_LUMEOS_LDS_H +#define _LUME_LUMEOS_LDS_H + +/* ===== Text Section: executable code ===== + * + * .init.text is merged into the main .text segment so PC-relative `la` + * from regular code can call __init helpers without a thunk. Linux + * keeps a separate .init.text only to reclaim the pages after boot — + * LumeOS doesn't yet reclaim, so the merge is harmless and avoids an + * extra PT_LOAD entry. */ +#define TEXT_SECTION \ + .text : AT(ADDR(.text) - PAGE_OFFSET) { \ + _stext = .; \ + *(.text .text.*) \ + *(.init.text .init.text.*) \ + *(.fixup) \ + _etext = .; \ + } = 0 + +/* ===== Read-Only Data + C++ init_array + initramfs ===== + * + * Layout invariants: + * - extable / init_array / driver_array / initramfs all live INSIDE + * .rodata between _srodata and _erodata so the VMM has one R-only + * mapping to manage. + * - The relocation table (.lume.reloc) is NOT in here — it lives in + * its own padded output section (see RELOC_SECTION) so that its + * two-pass-build size variance can't shift downstream offsets. + * (Putting it in .rodata caused a heisenbug where pre-link offsets + * used by gen_reloc.py didn't match post-link offsets after the + * reloc.o blob was inserted.) + */ +#define RODATA_SECTION \ + .rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { \ + _srodata = .; \ + *(.rodata .rodata.*) \ + *(.srodata .srodata.* .srodata.cst*) \ + . = ALIGN(8); \ + __extable_start = .; \ + KEEP(*(.extable)) \ + __extable_end = .; \ + INIT_ARRAY \ + DRIVER_ARRAY \ + /* Embedded initramfs CPIO archive */ \ + . = ALIGN(8); \ + __initramfs_start = .; \ + KEEP(*(.init.ramfs)) \ + __initramfs_end = .; \ + . = ALIGN(4096); \ + _erodata = .; \ + } + +/* ===== .rel.dyn / .rela.dyn — Linker-emitted RELA relocations ===== + * + * Mirrors arch/riscv/kernel/vmlinux.lds.S §101-109: the .rela.dyn + * section sits BETWEEN init data and .rodata so it lands in a + * read-only PT_LOAD segment alongside .text/.rodata. The companion + * dynamic-linker bookkeeping (.dynamic, .dynsym, .dynstr, .hash, + * .gnu.hash) lives in the RW segment AFTER .data — Linux does the + * same under #ifdef CONFIG_RELOCATABLE (§139-147). */ +#define RELA_DYN_SECTION \ + .rel.dyn : AT(ADDR(.rel.dyn) - PAGE_OFFSET) { \ + *(.rel.dyn*) \ + } \ + .rela.dyn : AT(ADDR(.rela.dyn) - PAGE_OFFSET) ALIGN(8) {\ + __rela_dyn_start = .; \ + *(.rela .rela*) \ + __rela_dyn_end = .; \ + } + +/* ===== GOT — kept; everything else dynamic-linker is discarded ===== + * + * Strict mirror of arch/riscv/kernel/vmlinux.lds.S in Linux: the only + * dynamic-linker artefact the kernel keeps is `.got` (it's referenced + * by every -fPIE compiled global access). `.plt .dynamic .dynsym + * .dynstr .hash .gnu.hash .interp .dynbss` are all sent to /DISCARD/ + * via the DISCARDS macro below — the kernel never invokes a dynamic + * linker (head.S walks .rela.dyn directly). + * + * Keeping .plt was the cause of two distinct bugs: + * 1. PLT trampoline target for an undefined symbol (selftest_reloc + * after we removed test_reloc.cc) was unresolved → jumps to a + * slot that re-jumps to itself → instruction page fault. + * 2. .plt carries X permission; living in the same PT_LOAD as .data + * forces the load segment to RWX, which `ld` warns about. + * + * Sit in the same PT_LOAD as .data because .got is RW. */ +#define GOT_SECTION \ + .got : AT(ADDR(.got) - PAGE_OFFSET) { *(.got*) } + +/* ===== Embedded initramfs (CPIO archive) ===== */ +#define INITRAMFS_SECTION \ + .init.ramfs : AT(ADDR(.init.ramfs) - PAGE_OFFSET) { \ + . = ALIGN(8); \ + __initramfs_start = .; \ + KEEP(*(.init.ramfs)) \ + __initramfs_end = .; \ + } + +/* ===== C++ Global Constructor Table ===== */ +#define INIT_ARRAY \ + . = ALIGN(8); \ + __init_array_start = .; \ + KEEP(*(.init_array .init_array.*)) \ + __init_array_end = .; + +/* ===== Driver Registration Table ===== */ +#define DRIVER_ARRAY \ + . = ALIGN(8); \ + PROVIDE(__drivers_start = .); \ + KEEP(*(.drivers)) \ + PROVIDE(__drivers_end = .); + +/* ===== Initialized Read-Write Data ===== + * + * .init.data is merged into .data for the same reason TEXT_SECTION + * absorbs .init.text — LumeOS doesn't yet reclaim __init pages, so + * splitting them gains nothing while costing a PT_LOAD entry. */ +#define DATA_SECTION \ + .data : AT(ADDR(.data) - PAGE_OFFSET) { \ + _sdata = .; \ + *(.data .data.*) \ + *(.init.data .init.data.*) \ + ARCH_DATA_EXTRA \ + _edata = .; \ + } + +/* ===== BSS: Uninitialized Data ===== */ +#define BSS_SECTION \ + .bss : AT(ADDR(.bss) - PAGE_OFFSET) { \ + _sbss = .; \ + *(.bss .bss.*) \ + *(.sbss .sbss.*) \ + ARCH_BSS_EXTRA \ + _ebss = .; \ + } + +/* ===== Discard Unwanted Sections ===== + * + * Mirrors arch/riscv/kernel/vmlinux.lds.S in Linux: + * /DISCARD/ : { + * *(.dynbss) + * *(.dynstr*) + * *(.dynamic*) + * *(.plt*) + * *(.interp*) + * *(.eh_frame) + * *(.note.GNU-stack) + * } + * + * Why each is safe to discard: + * .plt — -Bsymbolic resolves all internal refs at link time; + * any remaining PLT entry is an undefined symbol that + * the kernel would have crashed on anyway. + * .dynamic etc. — kernel doesn't invoke a dynamic linker; head.S + * walks __rela_dyn_start..__rela_dyn_end manually. + * .interp — no PT_INTERP at runtime. + * .eh_frame* — no C++ exceptions in kernel (-fno-exceptions). + * .note.* — boot-time identification metadata, unused. */ +#define DISCARDS \ + /DISCARD/ : { \ + *(.dynbss) \ + *(.dynstr*) \ + *(.dynamic*) \ + *(.dynsym*) \ + *(.hash*) *(.gnu.hash*) \ + *(.plt*) \ + *(.interp*) \ + *(.eh_frame .eh_frame_hdr) \ + *(.note .note.*) \ + *(.comment) \ + } + +/* ===== Kernel Image Boundary ===== */ +#define KERNEL_END \ + . = ALIGN(4096); \ + _kernel_end = .; + +#endif /* _LUME_LUMEOS_LDS_H */ diff --git a/include/lume/memblock.h b/include/lume/memblock.h new file mode 100644 index 0000000..cb9a76b --- /dev/null +++ b/include/lume/memblock.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * memblock — Boot-time physical memory allocator + * + * A simple region-based allocator active from early boot until + * the buddy system (PMM) takes over. Inspired by Linux's memblock. + * + * Lifetime: + * 1. memblock_init() — discovers RAM via FDT, reserves kernel image + * 2. vmm_init() — uses memblock_alloc for page table pages & page_map + * 3. pmm_init() — absorbs remaining free regions into buddy + * 4. memblock_retire() — marks memblock inactive; all alloc via PMM + * + * This eliminates the need for post-init pointer relocation when + * rebinding page_map to a VMEMMAP virtual address. + */ + +#include + +inline constexpr int kMemblockMaxRegions = 128; + +/* Initialize memblock: scan FDT for RAM, reserve kernel image + firmware. */ +void memblock_init(uint64 fdt_paddr); + +/* Mark a physical range as reserved (will not be handed to buddy). */ +void memblock_reserve(uint64 base, uint64 size); + +/* Allocate `size` bytes aligned to `align` from top of free memory. + * Returns PA, or 0 on failure. Used for page_map. */ +uint64 memblock_alloc_top(uint64 size, uint64 align); + +/* Allocate `size` bytes aligned to `align` from bottom of free memory. + * Returns PA, or 0 on failure. Used for page table pages. */ +uint64 memblock_alloc(uint64 size, uint64 align); + +/* Query memory layout (same info as FDT). */ +uint64 memblock_get_mem_base(); +uint64 memblock_get_mem_size(); + +/* Is memblock still the active allocator? */ +bool memblock_is_active(); + +/* Mark memblock as retired (PMM takes over). */ +void memblock_retire(); + +/* Iterate all free (unreserved) regions in ascending order. + * Used by pmm_init() to feed free pages into the buddy system. */ +void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), + void* ctx); diff --git a/include/lume/mm_layout.h b/include/lume/mm_layout.h new file mode 100644 index 0000000..7744de0 --- /dev/null +++ b/include/lume/mm_layout.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * mm_layout.h — User/Kernel address space boundary definitions + * + * Single source of truth for the user/kernel VA split. + * All modules (VMM, uaccess, page fault, tests) MUST include this + * header instead of defining their own constants. + * + * Layout (SV39, RISC-V): + * [0x0000_0000_0000, kUserVaLimit) — User space + * [kUserVaLimit, 0xFFFF_FFFF_FFFF) — Kernel space (direct map, vmemmap, etc.) + * + * Reference: docs/specs/linux_abi_mm_plan.md §5 + */ + +#include +#include + +/* User VA range: [kUserVaBase, kUserVaLimit) + * kUserVaBase avoids mapping the null page (catch NULL derefs). + * + * SV39: user space is the lower half [0, 2^38). The upper half + * [0xFFFFFFC000000000, 0xFFFFFFFFFFFFFFFF] is kernel space. + * Addresses between 2^38 and 0xFFFFFFC000000000 are non-canonical + * and will always fault — they must never be used. + * + * This value MUST match the page table mode selected by the MD layer. + * If we ever support SV48/SV57, update this accordingly. */ +inline constexpr uint64 kUserVaBase = kPageSize; +inline constexpr uint64 kUserVaLimit = 0x4000000000ULL; /* 2^38 = 256 GB */ + +/* Kernel VA starts at the sign-extended upper half */ +inline constexpr uint64 kKernelVaBase = 0xFFFFFFC000000000ULL; + +/* User stack: sits at the top of the user VA range. + * Matches Linux STACK_TOP = TASK_SIZE = kUserVaLimit. + * One guard page below the top, 8 MB stack. */ +inline constexpr uint64 kUserStackSize = 8ULL * 1024 * 1024; +inline constexpr uint64 kUserStackTop = kUserVaLimit - kPageSize; /* guard page */ +inline constexpr uint64 kUserStackBase = kUserStackTop - kUserStackSize; + +/* + * Check whether an address range [addr, addr+len) is entirely + * within user space. Returns true if valid. + */ +inline bool is_user_range(uint64 addr, uint64 len) +{ + if (addr < kUserVaBase) { + return false; + } + if (addr >= kUserVaLimit) { + return false; + } + if (addr + len < addr) { + return false; // Overflow + } + if (addr + len > kUserVaLimit) { + return false; + } + return true; +} + +/* + * Check whether a single address is in user space. + */ +inline bool is_user_address(uint64 addr) +{ + return addr >= kUserVaBase && addr < kUserVaLimit; +} diff --git a/include/lume/mman.h b/include/lume/mman.h new file mode 100644 index 0000000..d952419 --- /dev/null +++ b/include/lume/mman.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * POSIX Memory Management Constants + * Reference: docs/specs/syscall.md §9 + */ + +/* mmap / mprotect protection flags */ +inline constexpr int PROT_NONE = 0x0; +inline constexpr int PROT_READ = 0x1; +inline constexpr int PROT_WRITE = 0x2; +inline constexpr int PROT_EXEC = 0x4; + +/* mmap flags */ +inline constexpr int MAP_SHARED = 0x01; +inline constexpr int MAP_PRIVATE = 0x02; +inline constexpr int MAP_FIXED = 0x10; +inline constexpr int MAP_ANONYMOUS = 0x20; diff --git a/include/lume/mount.h b/include/lume/mount.h new file mode 100644 index 0000000..13f8e62 --- /dev/null +++ b/include/lume/mount.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * mount.h — Internal mount structure + */ + +#include +#include +#include +#include + +struct dentry; +struct super_block; +struct file_system_type; + +/* ================================================================== */ +/* mount (VFS-internal) */ +/* ================================================================== */ + +struct mount { + vfsmount mnt; + mount* mnt_parent; + dentry* mnt_mountpoint; + list_node mnt_child; + list_head mnt_mounts; + list_node mnt_list; + lume::atomic mnt_count; +}; + +inline mount* real_mount(vfsmount* m) { + return reinterpret_cast(m); +} + +/* ================================================================== */ +/* Mount API */ +/* ================================================================== */ + +int do_mount(const char* dev, const char* dir, + const char* fstype, uint64 flags, void* data); +int do_umount(vfsmount* mnt, int flags); + +vfsmount* vfs_kern_mount(file_system_type* type, int flags, + const char* name, void* data); + +/* Global root */ +extern vfsmount* g_root_mnt; +extern dentry* g_root_dentry; + +void mount_init(); diff --git a/include/lume/mutex.h b/include/lume/mutex.h new file mode 100644 index 0000000..9719aa6 --- /dev/null +++ b/include/lume/mutex.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Mutex — Linux-style sleeping mutex (no priority inheritance). + * + * Semantics mirror Linux kernel/locking/mutex.c: + * - Single owner (nullptr when free). + * - FIFO wait list — first waiter is the next owner. + * - Recursive acquire by the same task is FORBIDDEN (panic). + * - Owner MUST be the one to unlock. + * - lock() may sleep; MUST NOT be called from IRQ context, with + * preemption disabled, or while holding a spinlock. + * - try_lock() never sleeps; may be called from any context. + * + * Priority inheritance lives in a separate primitive (RtMutex, future) + * just like Linux splits struct mutex / struct rt_mutex. + * + * Reference: docs/specs/mutex.md + */ + +#include +#include +#include + +struct TaskControlBlock; + +class Mutex { +public: + void init(const char* name); + + /* Blocking acquire (uninterruptible). */ + void lock(); + + /* Non-blocking attempt. Returns true if acquired. */ + bool try_lock(); + + /* Release; only the owner may call (else panic). */ + void unlock(); + + bool is_locked() const { return owner_ != nullptr; } + bool is_owned_by_current() const; + TaskControlBlock* owner() const { return owner_; } + +private: + TaskControlBlock* owner_ = nullptr; + Spinlock wait_lock_; + list_node wait_list_{}; + const char* name_ = nullptr; +}; + +/* RAII guard. */ +class MutexGuard { +public: + explicit MutexGuard(Mutex& m) : m_(m) { m_.lock(); } + ~MutexGuard() { m_.unlock(); } + MutexGuard(const MutexGuard&) = delete; + MutexGuard& operator=(const MutexGuard&) = delete; +private: + Mutex& m_; +}; diff --git a/include/lume/namei.h b/include/lume/namei.h new file mode 100644 index 0000000..1764693 --- /dev/null +++ b/include/lume/namei.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * namei.h — Pathname lookup (path resolution) + */ + +#include +#include /* struct path */ +#include /* struct qstr */ + +/* ================================================================== */ +/* LOOKUP_* flags */ +/* ================================================================== */ + +inline constexpr uint32 LOOKUP_FOLLOW = 0x0001; +inline constexpr uint32 LOOKUP_DIRECTORY = 0x0002; +inline constexpr uint32 LOOKUP_CREATE = 0x0200; +inline constexpr uint32 LOOKUP_EXCL = 0x0400; +inline constexpr uint32 LOOKUP_PARENT = 0x0010; + +/* ================================================================== */ +/* Path lookup API */ +/* ================================================================== */ + +int kern_path(const char* name, unsigned int flags, path* result); +int filename_lookup(int dfd, const char* name, unsigned int flags, path* result); +dentry* lookup_one_len(const char* name, dentry* parent, int len); + +void namei_init(); diff --git a/include/lume/new.h b/include/lume/new.h new file mode 100644 index 0000000..cdcacb8 --- /dev/null +++ b/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/include/lume/page.h b/include/lume/page.h new file mode 100644 index 0000000..61bff95 --- /dev/null +++ b/include/lume/page.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Page — Physical page descriptor + * + * Each Page describes one 4KB physical page. A flat array of Page + * structs (page_map) covers all physical memory discovered via FDT. + * + * Memory layout is compressed using a union: only one of free/slab + * metadata is valid at a time, determined by `type`. + * + * Fields: + * refcount — lifecycle reference count (atomic). Page cannot be + * freed while refcount > 0. + * mapcount — PTE mapping count (atomic). Number of page table + * entries pointing to this page. Used by COW and + * future reclaim. + * flags — page state bits (PG_*). Also serves as a bit-lock + * via PG_LOCKED for short critical sections. + * type — discriminant for the union (Free/Slab/Anon/File). + * mapping — pointer to reverse-mapping context. For anonymous + * pages, points to AnonVma (low bit set). For file + * pages, points to FileMapping (low bit clear). + * index — page offset within the mapping object. + * + * Reference: docs/specs/pmm.md §3.1 + */ + +#include +#include +#include + +/* Forward declarations */ +class KmemCache; + +/* ================================================================== */ +/* Page type discriminant */ +/* ================================================================== */ + +enum class PageType : uint8 { + Free, // In Buddy free list + PcpCached, // In Per-CPU cache (invisible to buddy coalescing) + Allocated, // Handed out but not yet typed (PMM default) + Slab, // Backing a Slab cache + Anon, // Anonymous user page + File, // File-backed page (page cache) +}; + +/* ================================================================== */ +/* Page flags (stored as atomic uint32) */ +/* ================================================================== */ + +inline constexpr uint32 kPgLocked = (1U << 0); // Bit-lock for short critical sections +inline constexpr uint32 kPgReferenced = (1U << 1); // Accessed since last check +inline constexpr uint32 kPgDirty = (1U << 2); // Modified, needs writeback +inline constexpr uint32 kPgWriteback = (1U << 3); // IO writeback in progress +inline constexpr uint32 kPgLru = (1U << 4); // On an LRU list +inline constexpr uint32 kPgMapped = (1U << 5); // Has at least one PTE mapping +inline constexpr uint32 kPgSwapcache = (1U << 6); // In swap cache (future) +inline constexpr uint32 kPgCow = (1U << 7); // Involved in COW sharing + +/* ================================================================== */ +/* Page descriptor */ +/* ================================================================== */ + +struct Page { + /* --- Counts (atomic, lock-free) --- */ + lume::atomic refcount; // Lifecycle ref: 0 = reclaimable + lume::atomic mapcount; // PTE mapping count: 0 = no user mapping + + /* --- State --- */ + lume::atomic flags; // PG_* bits (also serves as bit-lock) + PageType type; // Discriminant for union + uint8 order; // Buddy order (0–10, i.e. 4KB–4MB) + + /* --- Reverse mapping --- */ + void* mapping; // AnonVma* (low bit set) or FileMapping* + uint64 index; // Page offset within mapping object + + /* --- LRU (future) --- */ + list_node lru; // LRU list linkage (for reclaim) + + /* --- Subsystem-specific metadata --- */ + union { + /* Valid when type == Free */ + struct { + list_node free_link; // Link into Buddy or PCP free list + } free; + + /* Valid when type == Slab */ + struct { + KmemCache* cache; // Owning slab cache + uint32 obj_count; // Number of allocated objects on this page + void* freelist; // Head of in-page free object linked list + } slab; + + /* type == Anon / File: no extra union metadata (rmap via mapping) */ + }; +}; + +/* ================================================================== */ +/* Page bit-lock (lightweight, no sleep, used for rmap critical */ +/* sections and short metadata updates) */ +/* ================================================================== */ + +inline bool page_trylock(Page* page) +{ + uint32 old = page->flags.fetch_or(kPgLocked); + return (old & kPgLocked) == 0; +} + +inline void page_lock(Page* page) +{ + while (!page_trylock(page)) { + /* spin — acceptable for short critical sections only */ + } +} + +inline void page_unlock(Page* page) +{ + page->flags.fetch_and(~kPgLocked); +} + +/* ================================================================== */ +/* Mapping pointer helpers (Linux convention: low bit = anon) */ +/* ================================================================== */ + +inline bool page_is_anon(const Page* page) +{ + return (reinterpret_cast(page->mapping) & 1ULL) != 0; +} + +inline void* page_mapping_raw(const Page* page) +{ + return reinterpret_cast( + reinterpret_cast(page->mapping) & ~1ULL); +} + +inline void page_set_anon_mapping(Page* page, void* anon_vma) +{ + page->mapping = reinterpret_cast( + reinterpret_cast(anon_vma) | 1ULL); +} + +inline void page_set_file_mapping(Page* page, void* file_mapping) +{ + page->mapping = file_mapping; +} diff --git a/include/lume/page_cache.h b/include/lume/page_cache.h new file mode 100644 index 0000000..abdc535 --- /dev/null +++ b/include/lume/page_cache.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * page_cache.h — Page Cache interface + * + * Provides a stable abstraction for caching physical pages indexed by + * (mapping, index) pairs. All file-backed fault paths should go + * through this interface so that upgrading the backing implementation + * (e.g. LRU eviction, writeback, radix tree) does not require + * changing any caller. + * + * Current implementation: simple open-addressing hash table. + * Future: radix tree + LRU lists + writeback integration. + * + * Terminology: + * mapping — opaque pointer identifying the backing object + * (FileMapping*, or any future cache-able object). + * index — page-granularity offset within the mapping. + * + * Reference: docs/specs/linux_abi_mm_plan.md §2.9, §3 + */ + +#include + +struct Page; + +/* + * Look up a cached page by (mapping, index). + * Returns the Page* if found (with refcount incremented), or nullptr. + * Caller must call page_cache_put() when done. + */ +Page* page_cache_get(void* mapping, uint64 index); + +/* + * Insert a page into the cache, associated with (mapping, index). + * The page's refcount is incremented by the cache. + * If a page already exists at (mapping, index), returns -EEXIST. + * Returns 0 on success, negative errno on failure. + */ +int page_cache_insert(void* mapping, uint64 index, Page* page); + +/* + * Release a reference obtained via page_cache_get(). + * This decrements the page's refcount. If the page was removed + * from the cache and refcount drops to 0, the page is freed. + */ +void page_cache_put(Page* page); + +/* + * Remove a page from the cache by (mapping, index). + * Decrements the cache's reference on the page. + * Returns the removed Page* (caller does NOT own a new ref), + * or nullptr if not found. + */ +Page* page_cache_remove(void* mapping, uint64 index); + +/* + * Remove all pages associated with a given mapping. + * Used during file close / inode eviction / truncate. + * Each removed page has its cache reference decremented. + */ +void page_cache_invalidate(void* mapping); + +/* + * Look up a page; if not found, allocate one, insert it, and return it. + * The returned page has its refcount incremented (caller must put). + * Returns nullptr on OOM. + * + * This is the primary interface for file-backed fault paths: + * the caller gets a page and then fills it with file data if kPgUptodate + * is not set. + */ +Page* page_cache_get_or_alloc(void* mapping, uint64 index); + +/* + * Initialize the page cache subsystem. + * Called once during boot after slab_init(). + */ +void page_cache_init(); diff --git a/include/lume/pgrp.h b/include/lume/pgrp.h new file mode 100644 index 0000000..127f3f8 --- /dev/null +++ b/include/lume/pgrp.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Process Group / Session infrastructure. + * + * Linux models pgrp/session via struct pid + PIDTYPE_PGID/PIDTYPE_SID hlists. + * We use a simpler design that supports the same operations cheaply: + * + * - Each thread group owns one signal_struct (refcount-shared with + * CLONE_THREAD siblings). + * - signal_struct carries pgid/sid and is the linked-list node on a + * fixed-size pgrp hash table. + * - kill(pid<=0), tty_signal_pgrp, orphan checks all walk one bucket. + * + * Concurrency: g_pgrp_lock protects every bucket and the pgrp_link of + * every signal_struct on a bucket. signal_struct::lock is a finer-grain + * lock for fields that change without re-hashing (e.g. tty pointer). + * + * Reference: Linux include/linux/pid.h, kernel/pid.c + */ + +#include +#include +#include + +struct signal_struct; +struct TaskControlBlock; + +/* ================================================================== */ +/* Subsystem init */ +/* ================================================================== */ + +void pgrp_init(); + +/* ================================================================== */ +/* signal_struct lifecycle */ +/* ================================================================== */ + +/* + * Allocate a new signal_struct for `leader`, with sid=pgid=leader_pid, + * refcount=1, attached to pgrp `leader_pid`. Returns nullptr on OOM. + */ +signal_struct* alloc_signal_struct(int32 leader_pid, + TaskControlBlock* leader); + +/* + * Allocate a child signal_struct inheriting pgid/sid/tty from `parent`. + * The new struct is attached to the parent's pgrp and starts with + * refcount=1. Used by fork (non-CLONE_THREAD). + */ +signal_struct* fork_signal_struct(signal_struct* parent, + TaskControlBlock* child_leader); + +/* Increment refcount (CLONE_THREAD share). */ +void get_signal_struct(signal_struct* sig); + +/* + * Decrement refcount; on last drop, detach from pgrp hash and free. + * Pass the task that's dropping the reference so we can manage + * leader pointers if the leader exits while sub-threads remain. + */ +void put_signal_struct(signal_struct* sig); + +/* ================================================================== */ +/* Pgrp operations (assume signal_struct is allocated) */ +/* ================================================================== */ + +/* + * Move a signal_struct from its current pgrp to `new_pgid`. + * Used by setpgid / setsid. Takes g_pgrp_lock internally. + */ +void change_pgid(signal_struct* sig, int32 new_pgid); + +/* + * Invoke `fn(sig)` for every signal_struct whose pgid == target. + * Holds g_pgrp_lock for the entire walk — fn must not block, sleep, + * or take locks that order before g_pgrp_lock. + * + * Template-y but kept in header to avoid a function-ptr indirection; + * keep callsites lean. + */ +template +void for_each_pgrp_member(int32 target_pgid, F&& fn); + +/* + * Count the number of signal_structs in `pgid`. Used by setsid + * preflight (can't create a session if you're already a pgrp leader + * with members) and orphan checks. + */ +int pgrp_member_count(int32 pgid); + +/* ================================================================== */ +/* Implementation details exposed for the template above */ +/* ================================================================== */ + +constexpr int kPgrpHashShift = 6; +constexpr int kPgrpHashSize = 1 << kPgrpHashShift; /* 64 buckets */ +constexpr int kPgrpHashMask = kPgrpHashSize - 1; + +extern list_node g_pgrp_hash[kPgrpHashSize]; +extern Spinlock g_pgrp_lock; + +inline int pgrp_hash_idx(int32 pgid) { + return static_cast(static_cast(pgid)) & kPgrpHashMask; +} + +#include + +template +void for_each_pgrp_member(int32 target_pgid, F&& fn) +{ + LockGuard guard(g_pgrp_lock); + list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(target_pgid)]; + list_node* node = bucket->next; + while (node != bucket) { + list_node* nxt = node->next; /* fn may not unlink, but be safe */ + auto* sig = list_entry(node); + if (sig->pgid == target_pgid) + fn(sig); + node = nxt; + } +} diff --git a/include/lume/pgtable_types.h b/include/lume/pgtable_types.h new file mode 100644 index 0000000..e309a82 --- /dev/null +++ b/include/lume/pgtable_types.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Page Table Entry Types — strongly typed wrappers. + * + * Layout and macro names mirror Linux's arch/riscv/include/asm/page.h + * and pgtable-64.h so that arch/riscv/mm/init.c can be copied from + * Linux without source edits. The wrapped struct prevents accidental + * mixing of a pte_t with a pgd_t or a raw physical address, which the + * previous bare-uint64 design in pmap.cc could not catch. + * + * Underlying width on RV64 LP64 is `unsigned long` = 64 bits, matching + * Linux exactly — assembly references (e.g. REG_S/REG_L on a pte_t*) + * see the same byte layout. + * + * SV39 uses 3 levels (pgd -> pmd -> pte); SV48 uses 4 (pgd -> pud -> + * pmd -> pte). p4d_t/pud_t are emitted unconditionally so that the + * higher-level abstractions in asm-generic/pgtable-nopud.h and friends + * (which Linux uses to collapse unused levels) work without #ifdef. + */ + +#include + +typedef struct { + unsigned long pte; +} pte_t; + +typedef struct { + unsigned long pmd; +} pmd_t; + +typedef struct { + unsigned long pud; +} pud_t; + +typedef struct { + unsigned long p4d; +} p4d_t; + +typedef struct { + unsigned long pgd; +} pgd_t; + +typedef struct { + unsigned long pgprot; +} pgprot_t; + +typedef struct page *pgtable_t; + +/* Field accessors */ +#define pte_val(x) ((x).pte) +#define pmd_val(x) ((x).pmd) +#define pud_val(x) ((x).pud) +#define p4d_val(x) ((x).p4d) +#define pgd_val(x) ((x).pgd) +#define pgprot_val(x) ((x).pgprot) + +/* Constructors (cast raw uint64 to wrapped type) */ +#define __pte(x) ((pte_t) { (x) }) +#define __pmd(x) ((pmd_t) { (x) }) +#define __pud(x) ((pud_t) { (x) }) +#define __p4d(x) ((p4d_t) { (x) }) +#define __pgd(x) ((pgd_t) { (x) }) +#define __pgprot(x) ((pgprot_t) { (x) }) + +#define PTE_FMT "%016lx" diff --git a/include/lume/pid.h b/include/lume/pid.h new file mode 100644 index 0000000..8503750 --- /dev/null +++ b/include/lume/pid.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * PID Allocator — Bitmap-based O(1) block search + * Reference: docs/specs/task.md §3.2 + */ + +#include +#include + +inline constexpr int32 kMaxPid = 32768; + +class PidAllocator { +public: + void init(); + int32 alloc(); + void free(int32 pid); + +private: + static constexpr int32 kBitmapWords = kMaxPid / 64; + Spinlock lock_; + uint64 bitmap_[kBitmapWords]; + int32 next_hint_ = 2; +}; + +extern PidAllocator g_pid_alloc; diff --git a/include/lume/pipe.h b/include/lume/pipe.h new file mode 100644 index 0000000..c905c9a --- /dev/null +++ b/include/lume/pipe.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * pipe.h — Pipe data structure and API + * + * A pipe is a unidirectional byte stream backed by a fixed-size + * circular buffer. Two file structs (reader + writer) share a + * single pipe_inode_info. + */ + +#include +#include +#include +#include + +struct file; +struct files_struct; + +/* ================================================================== */ +/* pipe_inode_info */ +/* ================================================================== */ + +inline constexpr int PIPE_BUF_SIZE = 4096; + +struct pipe_inode_info { + char buf[PIPE_BUF_SIZE]; + int rd_pos; /* Read position in circular buffer */ + int wr_pos; /* Write position in circular buffer */ + int len; /* Bytes currently in buffer */ + Spinlock lock; + WaitQueue rd_wait; /* Readers sleep here when empty */ + WaitQueue wr_wait; /* Writers sleep here when full */ + + lume::atomic readers; /* Number of open read ends */ + lume::atomic writers; /* Number of open write ends */ + lume::atomic refcount; /* Total references */ +}; + +/* ================================================================== */ +/* API */ +/* ================================================================== */ + +/* Create a pipe, install read/write fds into the given files_struct. + * Returns 0 on success, negative errno on failure. */ +int do_pipe2(files_struct* files, int fds[2], int flags); + +/* Allocate a fresh pipe_inode_info */ +pipe_inode_info* alloc_pipe_info(); +void free_pipe_info(pipe_inode_info* pipe); diff --git a/include/lume/platform_device.h b/include/lume/platform_device.h new file mode 100644 index 0000000..ac3e527 --- /dev/null +++ b/include/lume/platform_device.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * platform_device / platform_driver — FDT-described memory-mapped devices. + * + * Mirrors Linux include/linux/platform_device.h. The platform bus is + * the catch-all bus for devices that don't live on a real bus like PCI + * or USB — every device discovered via the FDT lands here. + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include +#include + +class DeviceNode; + +/* Compatible-string -> driver-private data table (Linux's of_device_id). */ +struct of_device_id { + const char* compatible; + const void* data; +}; + +struct platform_device { + device dev; + DeviceNode* of_node; // FDT node this device came from + uint64 mmio_base; // first `reg` cell from FDT (0 if none) + uint64 mmio_size; // second `reg` cell (0 if none) + int irq; // first interrupt (-1 if none / not parsed) +}; + +struct platform_driver { + device_driver driver; + int (*probe)(platform_device*); + void (*remove)(platform_device*); + const of_device_id* of_match_table; // NULL-terminated array +}; + +extern bus_type platform_bus; + +void platform_bus_init(); +int platform_driver_register(platform_driver* drv); +int platform_device_register(platform_device* pdev); + +/* Helper: pull current driver out of a probed device. */ +inline platform_driver* to_platform_driver(device_driver* drv) +{ + return container_of(drv, platform_driver, driver); +} +inline platform_device* to_platform_device(device* dev) +{ + return container_of(dev, platform_device, dev); +} + +/* Linker-set registration macro — drop a pointer to your platform_driver + * into the .drivers section. driver_probe_all() iterates this set at + * boot. Use a unique token in CONCAT to avoid name collisions. */ +#define PLATFORM_DRIVER(_drv) \ + extern platform_driver _drv; \ + static platform_driver* const __pdrv_ptr_##_drv \ + __attribute__((used, section(".drivers"))) = &_drv diff --git a/include/lume/pmm.h b/include/lume/pmm.h new file mode 100644 index 0000000..3e4b819 --- /dev/null +++ b/include/lume/pmm.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Physical Memory Manager (PMM) — Buddy System + Per-CPU Cache + * + * Manages all physical pages discovered via FDT. Built on a + * power-of-two buddy allocator for multi-page requests, with a + * Per-CPU single-page cache (PCP) to reduce lock contention on the + * hot single-page alloc/free path. + * + * Concurrency model: + * - PCP operations run with interrupts disabled (same-CPU safety). + * - Global buddy operations are protected by a single Spinlock. + * - Reference counts use __atomic builtins (lock-free). + * + * Reference: docs/specs/pmm.md + */ + +#include +#include +#include +#include + +/* Buddy system constants */ +inline constexpr int kMaxOrder = CONFIG_MAX_ORDER; // Orders 0..(N-1) + +/* Per-CPU cache dynamic parameters (calculated in pmm_init) */ +extern uint32 g_pcp_high_watermark; +extern uint32 g_pcp_batch_size; + +/* ================================================================== */ +/* Public free-function API */ +/* ================================================================== */ + +/* + * Initialize the PMM. Called by BSP after vmm_init() + memblock. + * page_map is already mapped at VMEMMAP VA by vmm_init. + * Populates buddy lists from memblock's free regions, then retires memblock. + * Panics on failure. + */ +void pmm_init(); + +/* --- Single-page fast path (uses PCP) --- */ + +// Allocate one 4KB page. Returns nullptr on OOM. +Page* pmm_alloc_page(); + +// Free one 4KB page. +void pmm_free_page(Page* page); + +/* --- Multi-page slow path (direct buddy) --- */ + +// Allocate 2^order contiguous pages. Returns nullptr on OOM. +Page* pmm_alloc_pages(uint8 order); + +// Free 2^order contiguous pages. +void pmm_free_pages(Page* page, uint8 order); + +/* --- Reference counting (atomic, lock-free) --- */ + +void page_incref(Page* page); +void page_decref(Page* page); + +/* --- Map counting (atomic, lock-free) --- */ + +inline void page_inc_mapcount(Page* page) { + page->mapcount.fetch_add(1); +} + +inline uint32 page_dec_mapcount(Page* page) { + return page->mapcount.fetch_sub(1) - 1; // Returns new value +} + +inline uint32 page_mapcount(const Page* page) { + return page->mapcount.load(__ATOMIC_RELAXED); +} + +/* --- Address conversion --- */ + +// Convert a Page* to its physical address. +PhysAddr page_to_pa(const Page* page); + +// Convert a physical address to its Page*. +Page* pa_to_page(PhysAddr pa); + +// Convert a Page* to a kernel virtual address. +VirtAddr page_to_va(const Page* page); + +/* --- PFN (Page Frame Number) abstraction --- */ +/* + * PFN is the fundamental unit for PA↔Page conversion. + * PA↔PFN converters already exist in addr.h (pa_to_pfn / pfn_to_pa). + * When sparse vmemmap lands, only pfn_to_page/page_to_pfn + * need to change — no callers are affected. + */ + +// Convert PFN to Page*. PFN = pa / PAGE_SIZE. +Page* pfn_to_page(uint64 pfn); + +// Convert Page* to PFN. +uint64 page_to_pfn(const Page* page); + +// Query PMM memory range (for VMM kernel mapping) +PhysAddr pmm_get_mem_base(); +uint64 pmm_get_mem_size(); diff --git a/include/lume/poll.h b/include/lume/poll.h new file mode 100644 index 0000000..7d5bca2 --- /dev/null +++ b/include/lume/poll.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * poll.h — POSIX poll() / ppoll() infrastructure. + * + * Linux pattern (include/linux/poll.h): + * - file_operations->poll(file, poll_table) returns a bitmask of + * POLL* events that are currently true on this file. + * - As a side effect, the poll callback calls poll_wait(file, wq, pt) + * to register the caller on every wait queue that might fire when + * state changes. sys_ppoll() then sleeps on those queues until + * one of them fires or a signal/timeout arrives. + * + * Our minimal implementation supports a SINGLE wait queue per poll call + * (sufficient for nfds==1, which is the BusyBox interactive shell case). + * Multi-fd polls work but fall back to busy re-checking; a proper + * per-fd waiter-list upgrade can come later. + */ + +#include + +struct file; +class WaitQueue; + +/* ================================================================== */ +/* UAPI: pollfd struct + POLL* event bits (Linux asm-generic) */ +/* ================================================================== */ + +struct pollfd { + int32 fd; + int16 events; + int16 revents; +}; + +inline constexpr int16 POLLIN = 0x0001; +inline constexpr int16 POLLPRI = 0x0002; +inline constexpr int16 POLLOUT = 0x0004; +inline constexpr int16 POLLERR = 0x0008; +inline constexpr int16 POLLHUP = 0x0010; +inline constexpr int16 POLLNVAL = 0x0020; +inline constexpr int16 POLLRDNORM = 0x0040; +inline constexpr int16 POLLRDBAND = 0x0080; +inline constexpr int16 POLLWRNORM = 0x0100; +inline constexpr int16 POLLWRBAND = 0x0200; + +/* ================================================================== */ +/* Kernel-side poll_table */ +/* ================================================================== */ + +/* + * Carried into file_operations->poll(). Drivers call poll_wait() with + * the wait queue they'd like the poller to sleep on; we record the + * first one. When `wq == nullptr` on entry, no real registration is + * needed (the caller is just probing readiness). + */ +struct poll_table { + WaitQueue* wq; /* first wait queue registered (single-wq design) */ +}; + +/* + * Called from f_op->poll() implementations to register a wait queue. + * If `pt` is null we're in a probe-only pass — caller doesn't want to + * sleep, just to read current revents. + */ +void poll_wait(file* fp, WaitQueue* wq, poll_table* pt); diff --git a/include/lume/ptrace.h b/include/lume/ptrace.h new file mode 100644 index 0000000..687d103 --- /dev/null +++ b/include/lume/ptrace.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * PtRegs — Linux-compatible user register context + * + * This structure MUST match the Linux RISC-V pt_regs layout exactly. + * It is the canonical representation for: + * - Signal frame construction (ucontext / sigcontext) + * - ptrace(GETREGS/SETREGS) + * - /proc//regs + * + * The internal TrapFrame (arch/trapframe.h) has a DIFFERENT layout + * optimized for fast trap entry/exit. Conversion between the two + * is done via trapframe_to_pt_regs() / pt_regs_to_trapframe(). + * + * Reference: Linux arch/riscv/include/asm/ptrace.h + */ + +#include + +struct PtRegs { + uint64 epc; // Program counter at trap + uint64 ra; // x1 + uint64 sp; // x2 + uint64 gp; // x3 + uint64 tp; // x4 + uint64 t0; // x5 + uint64 t1; // x6 + uint64 t2; // x7 + uint64 s0; // x8 + uint64 s1; // x9 + uint64 a0; // x10 + uint64 a1; // x11 + uint64 a2; // x12 + uint64 a3; // x13 + uint64 a4; // x14 + uint64 a5; // x15 + uint64 a6; // x16 + uint64 a7; // x17 + uint64 s2; // x18 + uint64 s3; // x19 + uint64 s4; // x20 + uint64 s5; // x21 + uint64 s6; // x22 + uint64 s7; // x23 + uint64 s8; // x24 + uint64 s9; // x25 + uint64 s10; // x26 + uint64 s11; // x27 + uint64 t3; // x28 + uint64 t4; // x29 + uint64 t5; // x30 + uint64 t6; // x31 + /* CSRs */ + uint64 status; // sstatus + uint64 badaddr; // stval + uint64 cause; // scause + /* Syscall bookkeeping */ + uint64 orig_a0; // a0 value before syscall (for restart) +}; + +static_assert(sizeof(PtRegs) == 36 * 8, "PtRegs must be 288 bytes (Linux ABI)"); + +/* + * Convert between internal TrapFrame and Linux-ABI PtRegs. + * These are the ONLY places that know both layouts. + * Future TrapFrame restructuring only needs to update these two functions. + */ +namespace arch { struct TrapFrame; } + +void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src, + uint64 scause, uint64 stval); +void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src); diff --git a/include/lume/random.h b/include/lume/random.h new file mode 100644 index 0000000..740b060 --- /dev/null +++ b/include/lume/random.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +enum class RandomSeedSource : uint8 { + Unknown = 0, + FdtProvided, + HwRandom, + CycleFallback, +}; + +/* + * MI: Machine-Independent Random Number Generator API + * + * Provides early PRNG and eventually a full CSPRNG. + */ + +/* + * Initialize early PRNG with a seed (e.g., from FDT). + * If seed is 0, attempts to use hardware TRNG or falls back to timer jitter. + */ +void random_early_init(uint64 seed); + +/* Consume random bits */ +uint64 get_random_u64(); +uint32 get_random_u32(); +void get_random_bytes(void* buf, size_t len); + +/* Inject entropy (for future interrupt/device mixing) */ +void add_device_randomness(const void* buf, size_t len); + +/* Early-seed metadata (for boot diagnostics/KASLR logs). */ +RandomSeedSource random_early_seed_source(); +uint64 random_early_seed_value(); +const char* random_seed_source_name(RandomSeedSource source); diff --git a/include/lume/rbtree.h b/include/lume/rbtree.h new file mode 100644 index 0000000..80b0c6c --- /dev/null +++ b/include/lume/rbtree.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Trees — public API + * Adapted from Linux kernel include/linux/rbtree.h + * + * (C) 1999 Andrea Arcangeli + */ + +#pragma once + +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Core macros */ +/* ------------------------------------------------------------------ */ + +#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3UL)) + +#define rb_entry(ptr, type, member) container_of(ptr, type, member) + +#define RB_EMPTY_ROOT(root) (READ_ONCE((root)->rb_node) == nullptr) + +#define RB_EMPTY_NODE(node) \ + ((node)->__rb_parent_color == (unsigned long)(node)) +#define RB_CLEAR_NODE(node) \ + ((node)->__rb_parent_color = (unsigned long)(node)) + +/* ------------------------------------------------------------------ */ +/* Core API (implemented in lib/rbtree.cc) */ +/* ------------------------------------------------------------------ */ + +#ifdef __cplusplus +extern "C" { +#endif + +void rb_insert_color(struct rb_node *, struct rb_root *); +void rb_erase(struct rb_node *, struct rb_root *); + +struct rb_node *rb_next(const struct rb_node *); +struct rb_node *rb_prev(const struct rb_node *); +struct rb_node *rb_first(const struct rb_root *); +struct rb_node *rb_last(const struct rb_root *); + +struct rb_node *rb_first_postorder(const struct rb_root *); +struct rb_node *rb_next_postorder(const struct rb_node *); + +void rb_replace_node(struct rb_node *victim, struct rb_node *_new, + struct rb_root *root); + +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------ */ +/* Inline helpers */ +/* ------------------------------------------------------------------ */ + +static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, + struct rb_node **rb_link) +{ + node->__rb_parent_color = (unsigned long)parent; + node->rb_left = node->rb_right = nullptr; + *rb_link = node; +} + +#define rb_entry_safe(ptr, type, member) \ + ({ __typeof__(ptr) ____ptr = (ptr); \ + ____ptr ? rb_entry(____ptr, type, member) : nullptr; \ + }) + +/* ------------------------------------------------------------------ */ +/* Cached (leftmost) tree helpers */ +/* ------------------------------------------------------------------ */ + +#define rb_first_cached(root) (root)->rb_leftmost + +static inline void rb_insert_color_cached(struct rb_node *node, + struct rb_root_cached *root, + bool leftmost) +{ + if (leftmost) + root->rb_leftmost = node; + rb_insert_color(node, &root->rb_root); +} + +static inline void rb_erase_cached(struct rb_node *node, + struct rb_root_cached *root) +{ + if (root->rb_leftmost == node) + root->rb_leftmost = rb_next(node); + rb_erase(node, &root->rb_root); +} + +static inline void rb_replace_node_cached(struct rb_node *victim, + struct rb_node *_new, + struct rb_root_cached *root) +{ + if (root->rb_leftmost == victim) + root->rb_leftmost = _new; + rb_replace_node(victim, _new, &root->rb_root); +} + +static inline void rb_erase_init(struct rb_node *n, struct rb_root *root) +{ + rb_erase(n, root); + RB_CLEAR_NODE(n); +} + +/* ------------------------------------------------------------------ */ +/* Generic insert / find helpers (inline, using function pointers) */ +/* ------------------------------------------------------------------ */ + +static inline void +rb_add(struct rb_node *node, struct rb_root *tree, + bool (*less)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_node; + struct rb_node *parent = nullptr; + + while (*link) { + parent = *link; + if (less(node, parent)) + link = &parent->rb_left; + else + link = &parent->rb_right; + } + + rb_link_node(node, parent, link); + rb_insert_color(node, tree); +} + +static inline void +rb_add_cached(struct rb_node *node, struct rb_root_cached *tree, + bool (*less)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_root.rb_node; + struct rb_node *parent = nullptr; + bool leftmost = true; + + while (*link) { + parent = *link; + if (less(node, parent)) { + link = &parent->rb_left; + } else { + link = &parent->rb_right; + leftmost = false; + } + } + + rb_link_node(node, parent, link); + rb_insert_color_cached(node, tree, leftmost); +} + +static inline struct rb_node * +rb_find(const void *key, const struct rb_root *tree, + int (*cmp)(const void *key, const struct rb_node *)) +{ + struct rb_node *node = tree->rb_node; + + while (node) { + int c = cmp(key, node); + if (c < 0) + node = node->rb_left; + else if (c > 0) + node = node->rb_right; + else + return node; + } + return nullptr; +} + +static inline struct rb_node * +rb_find_add(struct rb_node *node, struct rb_root *tree, + int (*cmp)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_node; + struct rb_node *parent = nullptr; + int c; + + while (*link) { + parent = *link; + c = cmp(node, parent); + if (c < 0) + link = &parent->rb_left; + else if (c > 0) + link = &parent->rb_right; + else + return parent; + } + + rb_link_node(node, parent, link); + rb_insert_color(node, tree); + return nullptr; +} + +/* ------------------------------------------------------------------ */ +/* Iteration macros */ +/* ------------------------------------------------------------------ */ + +#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ + for (pos = rb_entry_safe(rb_first_postorder(root), __typeof__(*pos), field); \ + pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ + __typeof__(*pos), field); 1; }); \ + pos = n) + diff --git a/include/lume/rbtree_augmented.h b/include/lume/rbtree_augmented.h new file mode 100644 index 0000000..d4559c4 --- /dev/null +++ b/include/lume/rbtree_augmented.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Trees — augmented operations & internal helpers + * Adapted from Linux kernel include/linux/rbtree_augmented.h + * + * (C) 1999 Andrea Arcangeli + * (C) 2002 David Woodhouse + * (C) 2012 Michel Lespinasse + */ + +#pragma once + +#include + +/* ------------------------------------------------------------------ */ +/* Augmented callback structure */ +/* ------------------------------------------------------------------ */ + +struct rb_augment_callbacks { + void (*propagate)(struct rb_node *node, struct rb_node *stop); + void (*copy)(struct rb_node *old, struct rb_node *_new); + void (*rotate)(struct rb_node *old, struct rb_node *_new); +}; + +extern "C" void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); + +extern "C" void __rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); + +static inline void +rb_insert_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + __rb_insert_augmented(node, root, augment->rotate); +} + +static inline void +rb_insert_augmented_cached(struct rb_node *node, + struct rb_root_cached *root, bool newleft, + const struct rb_augment_callbacks *augment) +{ + if (newleft) + root->rb_leftmost = node; + rb_insert_augmented(node, &root->rb_root, augment); +} + +/* ------------------------------------------------------------------ */ +/* Color / parent encoding helpers */ +/* ------------------------------------------------------------------ */ + +#define RB_RED 0 +#define RB_BLACK 1 + +#define __rb_parent(pc) ((struct rb_node *)(pc & ~3UL)) + +#define __rb_color(pc) ((pc) & 1) +#define __rb_is_black(pc) __rb_color(pc) +#define __rb_is_red(pc) (!__rb_color(pc)) +#define rb_color(rb) __rb_color((rb)->__rb_parent_color) +#define rb_is_red(rb) __rb_is_red((rb)->__rb_parent_color) +#define rb_is_black(rb) __rb_is_black((rb)->__rb_parent_color) + +static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) +{ + rb->__rb_parent_color = rb_color(rb) + (unsigned long)p; +} + +static inline void rb_set_parent_color(struct rb_node *rb, + struct rb_node *p, int color) +{ + rb->__rb_parent_color = (unsigned long)p + color; +} + +static inline void +__rb_change_child(struct rb_node *old, struct rb_node *_new, + struct rb_node *parent, struct rb_root *root) +{ + if (parent) { + if (parent->rb_left == old) + WRITE_ONCE(parent->rb_left, _new); + else + WRITE_ONCE(parent->rb_right, _new); + } else + WRITE_ONCE(root->rb_node, _new); +} + +/* ------------------------------------------------------------------ */ +/* Augmented erase (inline) */ +/* ------------------------------------------------------------------ */ + +static __always_inline struct rb_node * +__rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *child = node->rb_right; + struct rb_node *tmp = node->rb_left; + struct rb_node *parent, *rebalance; + unsigned long pc; + + if (!tmp) { + /* + * Case 1: node to erase has no more than 1 child (easy!) + */ + pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, child, parent, root); + if (child) { + child->__rb_parent_color = pc; + rebalance = nullptr; + } else + rebalance = __rb_is_black(pc) ? parent : nullptr; + tmp = parent; + } else if (!child) { + /* Still case 1, but this time the child is node->rb_left */ + tmp->__rb_parent_color = pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, tmp, parent, root); + rebalance = nullptr; + tmp = parent; + } else { + struct rb_node *successor = child, *child2; + + tmp = child->rb_left; + if (!tmp) { + /* + * Case 2: node's successor is its right child + */ + parent = successor; + child2 = successor->rb_right; + + augment->copy(node, successor); + } else { + /* + * Case 3: node's successor is leftmost under + * node's right child subtree + */ + do { + parent = successor; + successor = tmp; + tmp = tmp->rb_left; + } while (tmp); + child2 = successor->rb_right; + WRITE_ONCE(parent->rb_left, child2); + WRITE_ONCE(successor->rb_right, child); + rb_set_parent(child, successor); + + augment->copy(node, successor); + augment->propagate(parent, successor); + } + + tmp = node->rb_left; + WRITE_ONCE(successor->rb_left, tmp); + rb_set_parent(tmp, successor); + + pc = node->__rb_parent_color; + tmp = __rb_parent(pc); + __rb_change_child(node, successor, tmp, root); + + if (child2) { + rb_set_parent_color(child2, parent, RB_BLACK); + rebalance = nullptr; + } else { + rebalance = rb_is_black(successor) ? parent : nullptr; + } + successor->__rb_parent_color = pc; + tmp = successor; + } + + augment->propagate(tmp, nullptr); + return rebalance; +} + +static __always_inline void +rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *rebalance = __rb_erase_augmented(node, root, augment); + if (rebalance) + __rb_erase_color(rebalance, root, augment->rotate); +} + +static __always_inline void +rb_erase_augmented_cached(struct rb_node *node, struct rb_root_cached *root, + const struct rb_augment_callbacks *augment) +{ + if (root->rb_leftmost == node) + root->rb_leftmost = rb_next(node); + rb_erase_augmented(node, &root->rb_root, augment); +} + +/* ------------------------------------------------------------------ */ +/* Augmented callback declaration macros */ +/* ------------------------------------------------------------------ */ + +#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +static inline void \ +RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ +{ \ + while (rb != stop) { \ + RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ + if (RBCOMPUTE(node, true)) \ + break; \ + rb = rb_parent(&node->RBFIELD); \ + } \ +} \ +static inline void \ +RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ + RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ + new_->RBAUGMENTED = old_->RBAUGMENTED; \ +} \ +static void \ +RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ + RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ + new_->RBAUGMENTED = old_->RBAUGMENTED; \ + RBCOMPUTE(old_, false); \ +} \ +RBSTATIC const struct rb_augment_callbacks RBNAME = { \ + RBNAME ## _propagate, \ + RBNAME ## _copy, \ + RBNAME ## _rotate \ +}; + +#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ + RBTYPE, RBAUGMENTED, RBCOMPUTE) \ +static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit_early) \ +{ \ + RBSTRUCT *child; \ + RBTYPE max = RBCOMPUTE(node); \ + if (node->RBFIELD.rb_left) { \ + child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD); \ + if (child->RBAUGMENTED > max) \ + max = child->RBAUGMENTED; \ + } \ + if (node->RBFIELD.rb_right) { \ + child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD); \ + if (child->RBAUGMENTED > max) \ + max = child->RBAUGMENTED; \ + } \ + if (exit_early && node->RBAUGMENTED == max) \ + return true; \ + node->RBAUGMENTED = max; \ + return false; \ +} \ +RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max) + diff --git a/include/lume/rbtree_types.h b/include/lume/rbtree_types.h new file mode 100644 index 0000000..688ade4 --- /dev/null +++ b/include/lume/rbtree_types.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Tree type definitions + * Adapted from Linux kernel include/linux/rbtree_types.h + */ + +#pragma once + +#include + +struct rb_node { + unsigned long __rb_parent_color; + struct rb_node *rb_right; + struct rb_node *rb_left; +} __attribute__((aligned(sizeof(long)))); + +struct rb_root { + struct rb_node *rb_node; +}; + +struct rb_root_cached { + struct rb_root rb_root; + struct rb_node *rb_leftmost; +}; + +#define RB_ROOT (struct rb_root) { nullptr, } +#define RB_ROOT_CACHED (struct rb_root_cached) { {nullptr, }, nullptr } + diff --git a/include/lume/sched.h b/include/lume/sched.h new file mode 100644 index 0000000..ab26bac --- /dev/null +++ b/include/lume/sched.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Scheduler Public Interface + * + * Linux-style sched_class core with per-CPU runqueues. + * Phase 2: O(1) bitmap class is the default scheduling class. + * + * Reference: docs/specs/scheduler.md + */ + +#include +#include + +struct TaskControlBlock; + +/* ======================================================================== + * Scheduler Constants — driven by CONFIG_xxx (see scripts/Kconfig.mk) + * ======================================================================== */ + +inline constexpr int kNumPriorities = CONFIG_NR_PRIORITIES; +inline constexpr int kDefaultPriority = CONFIG_DEFAULT_PRIORITY; +inline constexpr int kDefaultTimeSlice = CONFIG_DEFAULT_TIMESLICE; +inline constexpr int kIdlePriority = CONFIG_IDLE_PRIORITY; + +/* ======================================================================== + * Initialization + * ======================================================================== */ + +/* BSP: initialize all RunQueues, create BSP idle thread and boot TCB. */ +void sched_init(); + +/* AP: create per-CPU idle thread and boot TCB. */ +void sched_init_ap(); + +/* ======================================================================== + * Core Scheduling + * ======================================================================== */ + +/* Main scheduling entry point. Picks the next task and context-switches. + * Called voluntarily (yield, sleep, exit) or from trap return path. */ +void schedule(); + +/* Timer tick handler. Delegates to current task's sched_class. + * Called from MI trap layer in interrupt context. */ +void sched_tick(); + +/* Check if rescheduling is needed (called before returning to user mode). */ +void sched_check_preempt(); + +/* Voluntarily yield the current time slice. */ +void sched_yield(); + +/* ======================================================================== + * Task Queue Management (used by task.cc, waitqueue.cc) + * ======================================================================== */ + +/* Make a task runnable: add it to its CPU's RunQueue. */ +void sched_enqueue(TaskControlBlock* task); + +/* Mark a task as sleeping: remove it from the RunQueue. */ +void sched_dequeue(TaskControlBlock* task); + +/* ======================================================================== + * Time Slice Policy + * ======================================================================== */ + +/* Convert priority level to time slice length. + * Higher priority (lower number) → longer time slice. */ +int32 priority_to_timeslice(uint8 priority); + +/* ======================================================================== + * Task Migration & Load Balancing + * ======================================================================== */ + +/* + * sched_migrate_task — Move a task from its current CPU to target_cpu. + * + * The task must NOT be the currently running task. It is dequeued from + * its old runqueue and enqueued onto the target. Both runqueue locks + * are acquired in cpu_id order to prevent deadlock. + * + * Returns 0 on success, -EINVAL if task is current or target is invalid. + */ +int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu); + +/* + * sched_balance — Periodic load balancer. + * + * Called from sched_tick(). Checks if any CPU has significantly more + * runnable tasks and pulls work to the local runqueue. This is a + * simplified version of Linux's load_balance(). + */ +void sched_balance(); + +/* + * sched_nr_running — Get the number of runnable tasks on a CPU. + */ +uint32 sched_nr_running(uint32 cpu_id); diff --git a/include/lume/sched_class.h b/include/lume/sched_class.h new file mode 100644 index 0000000..08686c1 --- /dev/null +++ b/include/lume/sched_class.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Scheduler Class Interface (Linux-style) + * + * Each scheduling algorithm implements a sched_class and is chained + * in priority order. The scheduler core walks the chain to select + * the next runnable task. + */ + +#include + +struct RunQueue; +struct TaskControlBlock; + +struct SchedClass { + const char* name; + const SchedClass* next; // Linked list order: higher priority first + + void (*init_rq)(RunQueue* rq); + + void (*enqueue_task)(RunQueue* rq, TaskControlBlock* task); + void (*dequeue_task)(RunQueue* rq, TaskControlBlock* task); + TaskControlBlock* (*pick_next_task)(RunQueue* rq); + void (*put_prev_task)(RunQueue* rq, TaskControlBlock* task); + + void (*task_tick)(RunQueue* rq, TaskControlBlock* curr); + void (*yield_task)(RunQueue* rq, TaskControlBlock* curr); +}; + +extern const SchedClass g_sched_class_o1; +extern const SchedClass g_sched_class_idle; diff --git a/include/lume/selftest.h b/include/lume/selftest.h new file mode 100644 index 0000000..7040986 --- /dev/null +++ b/include/lume/selftest.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Boot Self-Test Framework + * + * Minimal assertion-based test harness that runs during kernel_main() + * after all subsystems are initialized. Each test is a void function + * that calls ST_ASSERT / ST_ASSERT_EQ macros. On failure, prints + * the failing expression and panics. + * + * Usage: + * void selftest_pmm(); // defined in test/test_pmm.cc + * void selftest_slab(); // defined in test/test_slab.cc + * + * All self-tests run only on BSP in single-core mode. + */ + +#include +#include +#include + +#define ST_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + kprintf("[FAIL] %s:%d: assertion failed: %s\n", \ + __FILE__, __LINE__, #cond); \ + kernel_panic("selftest failed", #cond); \ + } \ + } while (0) + +#define ST_ASSERT_EQ(a, b) \ + do { \ + auto _a = (a); auto _b = (b); \ + if (_a != _b) { \ + kprintf("[FAIL] %s:%d: %s != %s (0x%llx != 0x%llx)\n", \ + __FILE__, __LINE__, #a, #b, \ + (uint64)(uintptr)(_a), (uint64)(uintptr)(_b)); \ + kernel_panic("selftest failed", #a " != " #b); \ + } \ + } while (0) + +#define ST_ASSERT_NE(a, b) \ + do { \ + auto _a = (a); auto _b = (b); \ + if (_a == _b) { \ + kprintf("[FAIL] %s:%d: %s == %s (both 0x%llx)\n", \ + __FILE__, __LINE__, #a, #b, \ + (uint64)(uintptr)(_a)); \ + kernel_panic("selftest failed", #a " == " #b); \ + } \ + } while (0) + +/* Announce a test suite beginning/end */ +inline void st_begin(const char* name) +{ + kprintf("[test] %s ... ", name); +} + +inline void st_pass() +{ + kprintf("PASS\n"); +} diff --git a/include/lume/setup.h b/include/lume/setup.h new file mode 100644 index 0000000..f8e6cb6 --- /dev/null +++ b/include/lume/setup.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_SETUP_H +#define _LUME_SETUP_H + +/* + * Boot-time interfaces called between assembly and C. + * + * Mirrors the subset of Linux include/linux/setup.h relevant to LumeOS's + * Linux-aligned RISC-V boot flow: + * + * setup_vm Build early_pg_dir / trampoline_pg_dir before + * the MMU is enabled. Filled kernel_map for + * all subsequent code to consult. + * relocate_enable_mmu Switch SATP, jump to higher-half, apply + * .rela.dyn via relocate_kernel. + * relocate_kernel Walk .rela.dyn, patch R_RISCV_RELATIVE + * slots with addend + kaslr_offset. + * + * kernel_main (LumeOS's equivalent of Linux's start_kernel) is declared + * in kernel/main.cc and re-declared in arch-side glue — kept local to + * that file to avoid creating an artificial public API for the lone + * call site in head.S. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void setup_vm(uintptr_t load_pa, uintptr_t dtb_pa); +void relocate_enable_mmu(pgd_t *pgdir); +void relocate_kernel(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _LUME_SETUP_H */ diff --git a/include/lume/signal.h b/include/lume/signal.h new file mode 100644 index 0000000..05b7542 --- /dev/null +++ b/include/lume/signal.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Signal Infrastructure — Core signal delivery API + * + * Provides the interface between trap handling, signal delivery, + * and user-space signal handler execution. + * + * Reference: Linux include/linux/sched/signal.h + */ + +#include + +struct TaskControlBlock; +struct sighand_struct; +namespace arch { struct TrapFrame; } + +/* ------------------------------------------------------------------ + * sighand_struct lifecycle (Linux kernel/fork.c:copy_sighand pattern) + * ------------------------------------------------------------------ */ + +sighand_struct* alloc_sighand(); +sighand_struct* copy_sighand(sighand_struct* parent); +void get_sighand(sighand_struct* sh); +void put_sighand(sighand_struct* sh); + +/* + * force_sig_fault — Deliver a fault-induced signal to the current task. + * + * Called from trap handler when a user-mode exception cannot be resolved. + * Bypasses the blocked mask and enqueues the signal. + * + * @signo: Signal number (SIGSEGV, SIGBUS, SIGILL, SIGFPE, etc.) + * @code: si_code for siginfo_t (e.g., SEGV_MAPERR, SEGV_ACCERR) + * @addr: Fault address (stval / badaddr) + */ +void force_sig_fault(int signo, int code, uint64 addr); + +/* + * do_signal — Process pending signals before returning to user mode. + * + * Called at the end of trap_handle() when origin == FromUser. + * If a signal is pending and a handler is registered, constructs + * a signal frame on the user stack and redirects epc. + * + * @tf: TrapFrame to modify if signal delivery is needed. + */ +void do_signal(arch::TrapFrame* tf); + +/* + * signal_pending — Check if the task has unblocked pending signals. + * + * Used by blocking syscalls (e.g., n_tty_read) to detect whether + * they should return -EINTR. + */ +bool signal_pending(TaskControlBlock* task); + diff --git a/include/lume/signal_nr.h b/include/lume/signal_nr.h new file mode 100644 index 0000000..8e595d7 --- /dev/null +++ b/include/lume/signal_nr.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Linux-compatible signal numbers and related ABI constants. + * + * This mirrors the common Linux UAPI numbering used by asm-generic/signal.h. + * Keep the values stable so user-space binaries see the same numbers. + */ + +inline constexpr int _NSIG = 64; +inline constexpr int _NSIG_BPW = sizeof(unsigned long) * 8; +inline constexpr int _NSIG_WORDS = _NSIG / _NSIG_BPW; + +inline constexpr int SIGHUP = 1; +inline constexpr int SIGINT = 2; +inline constexpr int SIGQUIT = 3; +inline constexpr int SIGILL = 4; +inline constexpr int SIGTRAP = 5; +inline constexpr int SIGABRT = 6; +inline constexpr int SIGIOT = SIGABRT; +inline constexpr int SIGBUS = 7; +inline constexpr int SIGFPE = 8; +inline constexpr int SIGKILL = 9; +inline constexpr int SIGUSR1 = 10; +inline constexpr int SIGSEGV = 11; +inline constexpr int SIGUSR2 = 12; +inline constexpr int SIGPIPE = 13; +inline constexpr int SIGALRM = 14; +inline constexpr int SIGTERM = 15; +inline constexpr int SIGSTKFLT = 16; +inline constexpr int SIGCHLD = 17; +inline constexpr int SIGCONT = 18; +inline constexpr int SIGSTOP = 19; +inline constexpr int SIGTSTP = 20; +inline constexpr int SIGTTIN = 21; +inline constexpr int SIGTTOU = 22; +inline constexpr int SIGURG = 23; +inline constexpr int SIGXCPU = 24; +inline constexpr int SIGXFSZ = 25; +inline constexpr int SIGVTALRM = 26; +inline constexpr int SIGPROF = 27; +inline constexpr int SIGWINCH = 28; +inline constexpr int SIGIO = 29; +inline constexpr int SIGPOLL = SIGIO; +inline constexpr int SIGPWR = 30; +inline constexpr int SIGSYS = 31; +inline constexpr int SIGUNUSED = 31; + +/* Real-time signals start here in Linux UAPI. */ +inline constexpr int SIGRTMIN = 32; +inline constexpr int SIGRTMAX = _NSIG; diff --git a/include/lume/signal_types.h b/include/lume/signal_types.h new file mode 100644 index 0000000..f66231b --- /dev/null +++ b/include/lume/signal_types.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Signal Types — Linux-compatible structures for signal delivery + * + * Follows the RISC-V LP64 ABI layout so that user-space programs + * compiled with musl / glibc see the correct struct sizes. + * + * Reference: Linux include/uapi/asm-generic/signal.h + * Linux include/uapi/asm-generic/siginfo.h + */ + +#include +#include +#include +#include + +/* ================================================================== */ +/* Signal set (64-bit bitmap) */ +/* ================================================================== */ + +struct sigset_t { + uint64 sig[_NSIG_WORDS]; /* _NSIG_WORDS = 1 on LP64 */ + + void clear() { for (auto& s : sig) s = 0; } + void fill() { for (auto& s : sig) s = ~0ULL; } + void add(int signo) { sig[(signo-1)/64] |= (1ULL << ((signo-1)%64)); } + void del(int signo) { sig[(signo-1)/64] &= ~(1ULL << ((signo-1)%64)); } + bool has(int signo) const { return (sig[(signo-1)/64] >> ((signo-1)%64)) & 1; } + bool is_empty() const { for (auto s : sig) if (s) return false; return true; } + + sigset_t operator&(const sigset_t& o) const { + sigset_t r; + for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] & o.sig[i]; + return r; + } + sigset_t operator|(const sigset_t& o) const { + sigset_t r; + for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] | o.sig[i]; + return r; + } + sigset_t operator~() const { + sigset_t r; + for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = ~sig[i]; + return r; + } +}; + +/* ================================================================== */ +/* siginfo_t (simplified, Linux UAPI compatible layout) */ +/* ================================================================== */ + +struct kernel_siginfo { + int si_signo; + int si_errno; + int si_code; + int _pad0; /* alignment */ + + union { + /* kill() */ + struct { + int32 _pid; + int32 _uid; + } _kill; + + /* SIGCHLD */ + struct { + int32 _pid; + int32 _uid; + int _status; + long _utime; + long _stime; + } _sigchld; + + /* SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP */ + struct { + uint64 _addr; + } _sigfault; + + /* Pad to 128 bytes total (Linux SI_MAX_SIZE) */ + char _pad[128 - 4*sizeof(int)]; + } _sifields; +}; + +/* ================================================================== */ +/* Signal action (kernel representation) */ +/* ================================================================== */ + +/* sa_flags bits (Linux UAPI) */ +inline constexpr uint64 SA_NOCLDSTOP = 0x00000001; +inline constexpr uint64 SA_NOCLDWAIT = 0x00000002; +inline constexpr uint64 SA_SIGINFO = 0x00000004; +inline constexpr uint64 SA_RESTORER = 0x04000000; +inline constexpr uint64 SA_ONSTACK = 0x08000000; +inline constexpr uint64 SA_RESTART = 0x10000000; +inline constexpr uint64 SA_NODEFER = 0x40000000; +inline constexpr uint64 SA_RESETHAND = 0x80000000; + +/* Special handler values */ +inline constexpr uint64 SIG_DFL = 0; +inline constexpr uint64 SIG_IGN = 1; +inline constexpr uint64 SIG_ERR = static_cast(-1); + +struct kernel_sigaction { + uint64 sa_handler; /* handler address or SIG_DFL/SIG_IGN */ + uint64 sa_flags; + uint64 sa_restorer; /* sigreturn trampoline (SA_RESTORER) */ + sigset_t sa_mask; /* signals blocked during handler */ +}; + +/* ================================================================== */ +/* Per-task signal state */ +/* ================================================================== */ + +/* Queued signal entry */ +struct sigqueue { + list_node list; + kernel_siginfo info; +}; + +/* Pending signal set + queue */ +struct sigpending { + list_node list; /* list of sigqueue entries */ + sigset_t signal; /* bitmap of pending signals */ + + void init() { + list.init(); + signal.clear(); + } +}; + +/* Signal handler table (shared between threads via CLONE_SIGHAND). + * + * Linux pattern (kernel/fork.c:copy_sighand): one sighand_struct per + * thread group by default, shared across CLONE_SIGHAND siblings, + * refcounted, freed when the last referencing task exits. Its + * `siglock` guards BOTH the action table and every per-task `pending` + * list of every task that references this sighand. That single-lock + * model is what lets send/dequeue/discard race-free without per-list + * locks. */ +struct sighand_struct { + Spinlock siglock; + uint32 refcount; /* # tasks sharing this sighand (≥1) */ + kernel_sigaction action[_NSIG]; + + void init() { + siglock.init("sighand"); + refcount = 1; + for (auto& a : action) { + a.sa_handler = SIG_DFL; + a.sa_flags = 0; + a.sa_restorer = 0; + a.sa_mask.clear(); + } + } +}; + +/* ================================================================== */ +/* signal_struct — per-thread-group shared state */ +/* ================================================================== */ + +struct TaskControlBlock; +struct tty_struct; + +/* + * Linux's signal_struct is the per-thread-group container holding: + * - Process group / session identity (pgid, sid) + * - Controlling terminal (tty) + * - Misc state shared across CLONE_THREAD siblings + * + * Refcount tracks the number of tasks in the thread group; when the last + * task in the group exits, the signal_struct is freed. Detachment from + * the pgrp hash also happens at that point. + * + * `pgrp_link` makes this signal_struct a member of one pgrp bucket; the + * pgrp subsystem walks the bucket to enumerate all thread groups whose + * pgid hashes there. + */ +struct signal_struct { + Spinlock lock; + uint32 refcount; /* # tasks sharing this (≥1) */ + int32 pgid; /* process group id */ + int32 sid; /* session id */ + tty_struct* tty; /* controlling tty (or nullptr) */ + int32 tty_old_pgrp; /* SIGHUP target on TIOCNOTTY */ + TaskControlBlock* leader; /* thread-group leader (pid==tgid) */ + list_node pgrp_link; /* on pgrp hash bucket */ + + void init(int32 leader_pid, TaskControlBlock* tg_leader) { + lock.init("signal"); + refcount = 1; + pgid = leader_pid; + sid = leader_pid; + tty = nullptr; + tty_old_pgrp = 0; + leader = tg_leader; + pgrp_link.init(); + } +}; + +/* ================================================================== */ +/* Default signal dispositions */ +/* ================================================================== */ + +enum SigDefaultAction { + SIG_ACT_TERM, /* Terminate */ + SIG_ACT_IGN, /* Ignore */ + SIG_ACT_CORE, /* Terminate + core dump */ + SIG_ACT_STOP, /* Stop process */ + SIG_ACT_CONT, /* Continue if stopped */ +}; + +/* Returns the default action for a standard signal */ +inline SigDefaultAction sig_default_action(int sig) +{ + switch (sig) { + case SIGCHLD: + case SIGURG: + case SIGWINCH: + return SIG_ACT_IGN; + + case SIGCONT: + return SIG_ACT_CONT; + + case SIGSTOP: + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + return SIG_ACT_STOP; + + case SIGQUIT: + case SIGILL: + case SIGTRAP: + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGSEGV: + case SIGXCPU: + case SIGXFSZ: + case SIGSYS: + return SIG_ACT_CORE; + + default: + return SIG_ACT_TERM; + } +} + +/* SIG_KERNEL_ONLY: cannot be caught or ignored */ +inline bool sig_kernel_only(int sig) +{ + return sig == SIGKILL || sig == SIGSTOP; +} + +/* ================================================================== */ +/* SigprocMask how values (Linux UAPI) */ +/* ================================================================== */ + +inline constexpr int SIG_BLOCK = 0; +inline constexpr int SIG_UNBLOCK = 1; +inline constexpr int SIG_SETMASK = 2; diff --git a/include/lume/slab.h b/include/lume/slab.h new file mode 100644 index 0000000..4ebade1 --- /dev/null +++ b/include/lume/slab.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * SLUB public API + * + * This header exposes the allocator entry points to the rest of the kernel. + * Implementation details stay in mm/slab.h and mm/slab.cc. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize global slab caches. */ +void slab_init(); + +/* Allocate/free kernel memory. */ +void* kmalloc(uint32 size); +void kfree(void* ptr); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/smp.h b/include/lume/smp.h new file mode 100644 index 0000000..a5292b6 --- /dev/null +++ b/include/lume/smp.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * smp.h — Machine-Independent SMP primitives + * + * Provides cross-CPU communication (IPI) and TLB coordination. + * The MI interface calls through to arch-specific backends + * (e.g. SBI on RISC-V, APIC on x86). + * + * Reference: docs/specs/linux_abi_mm_plan.md §SMP + */ + +#include +#include +#include + +/* + * IPI reasons — each bit in the per-CPU pending mask + * corresponds to one reason. Multiple reasons can be + * pending simultaneously. + */ +enum IpiReason : uint32 { + IPI_RESCHEDULE = (1U << 0), /* Target should call schedule() */ + IPI_TLB_SHOOTDOWN = (1U << 1), /* Target should flush TLB */ + IPI_CALL_FUNCTION = (1U << 2), /* Target should run a function */ + IPI_HALT = (1U << 3), /* Target should halt */ +}; + +/* + * Per-CPU IPI pending state. + * Written by sender (atomically), consumed by receiver in IRQ context. + */ +struct IpiData { + lume::atomic pending; /* Bitmask of IpiReason */ + + /* TLB shootdown payload */ + uint64 tlb_start; + uint64 tlb_end; +}; + +/* Per-CPU IPI data array (BSS zero-initialized) */ +extern IpiData g_ipi_data[kMaxCpus]; + +/* + * smp_send_ipi — Send an IPI to a set of CPUs + * + * @cpu_mask: bitmask of target CPU IDs (bit N = CPU N) + * @reason: reason bits to set in the target's pending mask + * + * Safe to call from any context (interrupt or process). + */ +void smp_send_ipi(uint64 cpu_mask, uint32 reason); + +/* + * smp_send_reschedule — Convenience: send reschedule IPI to one CPU + */ +inline void smp_send_reschedule(uint8 cpu_id) +{ + smp_send_ipi(1ULL << cpu_id, IPI_RESCHEDULE); +} + +/* + * smp_init — Initialize SMP subsystem (called from BSP after trap_init) + */ +void smp_init(); + +/* + * smp_handle_ipi — Called from trap handler on receiving S-mode + * software interrupt. Processes all pending IPI reasons. + */ +void smp_handle_ipi(); diff --git a/include/lume/stdarg.h b/include/lume/stdarg.h new file mode 100644 index 0000000..c8dc7f4 --- /dev/null +++ b/include/lume/stdarg.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef _LINUX_STDARG_H +#define _LINUX_STDARG_H + +typedef __builtin_va_list va_list; +#define va_start(v, l) __builtin_va_start(v, l) +#define va_end(v) __builtin_va_end(v) +#define va_arg(v, T) __builtin_va_arg(v, T) +#define va_copy(d, s) __builtin_va_copy(d, s) + +#endif diff --git a/include/lume/stringify.h b/include/lume/stringify.h new file mode 100644 index 0000000..0e84cbe --- /dev/null +++ b/include/lume/stringify.h @@ -0,0 +1,14 @@ +#ifndef __LINUX_STRINGIFY_H +#define __LINUX_STRINGIFY_H + +/* Indirect stringification. Doing two levels allows the parameter to be a + * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) + * converts to "bar". + */ + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#define FILE_LINE __FILE__ ":" __stringify(__LINE__) + +#endif /* !__LINUX_STRINGIFY_H */ diff --git a/include/lume/syscall.h b/include/lume/syscall.h new file mode 100644 index 0000000..8185096 --- /dev/null +++ b/include/lume/syscall.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * System Call Interface — Machine-Independent Layer + * + * Defines the unified syscall handler signature and the MI dispatch function. + * MD layer (arch/riscv/syscall/syscall_md.cc) extracts registers into args[6] + * and calls syscall_dispatch(). + * + * Reference: docs/specs/syscall.md §4 + */ + +#include + +/* Unified syscall handler signature. + * All arguments and return value are register-width (uint64). + * Individual sys_* functions cast internally. */ +using SyscallHandler = int64 (*)(uint64, uint64, uint64, + uint64, uint64, uint64); + +/* MI-layer syscall dispatcher. + * Called by MD layer after extracting syscall number and arguments. + * Returns the syscall result (negative errno on error). */ +int64 syscall_dispatch(uint64 nr, uint64 args[6]); + +/* Register all implemented syscall handlers into the jump table. + * Called from kernel_main() after sched_init(). */ +void syscall_init(); + +/* MD-layer entry point — called from trap handler on TrapCause::Syscall. + * Extracts registers from TrapFrame, calls syscall_dispatch, writes result. */ +namespace arch { +struct TrapFrame; +} +void syscall_dispatch_md(arch::TrapFrame* tf); diff --git a/include/lume/syscall_nr.h b/include/lume/syscall_nr.h new file mode 100644 index 0000000..29e3904 --- /dev/null +++ b/include/lume/syscall_nr.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * System Call Numbers — RISC-V LP64 (asm-generic/unistd.h compatible) + * + * Numbering strictly follows Linux asm-generic so that musl libc / Busybox + * binaries can be run without modification. + * + * Reference: docs/specs/syscall.md §4.3 + */ + +inline constexpr int SYS_exit = 93; +inline constexpr int SYS_exit_group = 94; +inline constexpr int SYS_set_tid_address = 96; +inline constexpr int SYS_sched_yield = 124; +inline constexpr int SYS_uname = 160; +inline constexpr int SYS_getpid = 172; +inline constexpr int SYS_getppid = 173; +inline constexpr int SYS_getuid = 174; +inline constexpr int SYS_getgid = 176; +inline constexpr int SYS_gettid = 178; +inline constexpr int SYS_brk = 214; +inline constexpr int SYS_munmap = 215; +inline constexpr int SYS_clone = 220; +inline constexpr int SYS_execve = 221; +inline constexpr int SYS_mmap = 222; +inline constexpr int SYS_mprotect = 226; +inline constexpr int SYS_wait4 = 260; + +inline constexpr int SYS_getcwd = 17; +inline constexpr int SYS_dup = 23; +inline constexpr int SYS_dup3 = 24; +inline constexpr int SYS_ioctl = 29; +inline constexpr int SYS_mkdirat = 34; +inline constexpr int SYS_unlinkat = 35; +inline constexpr int SYS_chdir = 49; +inline constexpr int SYS_openat = 56; +inline constexpr int SYS_close = 57; +inline constexpr int SYS_pipe2 = 59; +inline constexpr int SYS_lseek = 62; +inline constexpr int SYS_read = 63; +inline constexpr int SYS_write = 64; +inline constexpr int SYS_writev = 66; +inline constexpr int SYS_ppoll = 73; +inline constexpr int SYS_fstatat = 79; +inline constexpr int SYS_fstat = 80; +inline constexpr int SYS_getdents64 = 61; + +inline constexpr int SYS_fcntl = 25; +inline constexpr int SYS_linkat = 37; +inline constexpr int SYS_mount = 40; +inline constexpr int SYS_umount2 = 39; + +inline constexpr int SYS_kill = 129; +inline constexpr int SYS_tkill = 130; +inline constexpr int SYS_tgkill = 131; +inline constexpr int SYS_sigaltstack = 132; +inline constexpr int SYS_rt_sigaction = 134; +inline constexpr int SYS_rt_sigprocmask = 135; +inline constexpr int SYS_rt_sigreturn = 139; +inline constexpr int SYS_reboot = 142; +inline constexpr int SYS_setpgid = 154; +inline constexpr int SYS_getpgid = 155; +inline constexpr int SYS_getsid = 156; +inline constexpr int SYS_setsid = 157; +inline constexpr int SYS_prctl = 167; +inline constexpr int SYS_geteuid = 175; +inline constexpr int SYS_getegid = 177; + +inline constexpr int SYS_futex = 98; +inline constexpr int SYS_nanosleep = 101; +inline constexpr int SYS_clock_gettime = 113; +inline constexpr int SYS_epoll_create1 = 20; +inline constexpr int SYS_epoll_ctl = 21; +inline constexpr int SYS_epoll_pwait = 22; + +inline constexpr int SYS_rt_sigtimedwait = 137; +inline constexpr int SYS_socket = 198; +inline constexpr int SYS_sendto = 206; diff --git a/include/lume/task.h b/include/lume/task.h new file mode 100644 index 0000000..e959147 --- /dev/null +++ b/include/lume/task.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Task Control Block & Task State + * + * Central representation of every execution context in LumeOS, + * whether kernel thread or (future) user process. + * + * Reference: docs/specs/task.md §3.1, docs/specs/scheduler.md §3.1 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Forward declarations */ +struct VmSpace; +struct SchedClass; + +/* ======================================================================== + * Task State Machine + * ======================================================================== */ + +enum class TaskState : uint8 { + Runnable = 0, // In a Per-CPU ready queue + Running = 1, // Currently executing on a CPU + Sleeping = 2, // Blocked on a WaitQueue + Zombie = 3, // Exited, waiting for parent wait() + Dead = 4, // Fully reclaimed, TCB can be freed + Stopped = 5, // Job control: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU +}; + +/* ======================================================================== + * Task Flags + * ======================================================================== */ + +inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user mode) +inline constexpr uint8 TF_IDLE = 0x02; // Idle thread +inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1) + +/* ======================================================================== + * Kernel Stack + * ======================================================================== */ + +inline constexpr uint64 kKernelStackSize = CONFIG_KERNEL_STACK_SIZE; +inline constexpr int kKernelStackOrder = (kKernelStackSize <= 4096) ? 0 + : (kKernelStackSize <= 8192) ? 1 + : (kKernelStackSize <= 16384) ? 2 + : (kKernelStackSize <= 32768) ? 3 : 4; +inline constexpr uint64 kStackCanary = 0xDEAD'BEEF'CAFE'BABEULL; + +/* ======================================================================== + * Task Control Block + * ======================================================================== */ + +struct TaskControlBlock { + /* ===== Scheduler hot fields (cache-line optimized) ===== */ + TaskState state; // Current state + uint8 priority; // Static priority [0, 31] + uint8 cpu_id; // Pinned CPU (Phase 2: no migration) + uint8 flags; // TF_KTHREAD | TF_IDLE | TF_INIT + + int32 time_slice; // Remaining time slice (ticks) + int32 pid; // Unique task ID (Linux: tid) + int32 tgid; // Thread group ID (Linux: pid) + int32 preempt_count; // >0: preemption disabled (holding locks etc.) + + list_node run_link; // RunQueue list node + list_node all_link; // Global task list node + + /* ===== Context ===== */ + arch::Context context; // Callee-saved registers (scheduler switch) + arch::TrapFrame* trapframe; // Trap frame (user-mode entry/exit) + arch::FpState fpstate; // FPU register state (f0-f31 + fcsr) + + /* ===== Kernel Stack ===== */ + uint64 kstack_pa; // Physical address + uint64 kstack_va; // Virtual address (stack bottom) + + /* ===== Address Space ===== */ + VmSpace* mm; // nullptr = kernel thread + + /* ===== Per-task lock ===== + * Protects: parent, children, sibling, exit_code, state transitions. + * Lock ordering: task->lock -> g_task_list_lock (never reverse). */ + Spinlock lock; + + /* ===== Task Hierarchy ===== */ + TaskControlBlock* parent; + list_node children; // Children list head + list_node sibling; // Sibling list node + WaitQueue wait_chld; // Parent waits on this for children to exit + + /* ===== Exit ===== */ + int32 exit_code; + int32 exit_signal; // Linux: signal sent to parent on exit. + // SIGCHLD = notify parent, become zombie + // -1 = auto-reap (kthreads) + uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: write 0 here on exit + + /* ===== Job control reporting ===== + * stopped_code: 0 means "no pending stop notification"; non-zero is the + * Linux wstatus value to hand back from wait4(WUNTRACED). Cleared + * after the parent consumes it via wait4 (or task termination). + * notify_continued: set when SIGCONT resumes us; consumed by + * wait4(WCONTINUED). */ + int32 stopped_code; + bool notify_continued; + + /* ===== Scheduler class metadata ===== */ + const SchedClass* sched_class; + void* sched_entity; + + /* ===== VFS ===== */ + struct files_struct* files; // Per-task fd table (shared via CLONE_FILES) + struct fs_struct* fs; // cwd/root (shared via CLONE_FS) + + /* ===== Signals ===== */ + struct sigpending pending; // Per-task pending signal queue + sigset_t blocked; // Blocked signal mask + struct sighand_struct* sighand; // Signal handler table (shared via CLONE_SIGHAND) + struct signal_struct* signal; // Thread-group identity: pgid/sid/ctty (shared via CLONE_THREAD) + + /* ===== Debug ===== */ + const char* name; +}; + +/* ======================================================================== + * current_task() — fast access to the running task on this CPU + * + * Must be called with interrupts disabled or while holding a spinlock. + * ======================================================================== */ +TaskControlBlock* current_task(); + +/* Terminate the current task with a signal. */ +void task_kill_current(int sig); diff --git a/include/lume/timer.h b/include/lume/timer.h new file mode 100644 index 0000000..9bdddd5 --- /dev/null +++ b/include/lume/timer.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include + +/* + * MI Timer Subsystem + */ + +#include + +struct TimerList { + list_node entry; + uint64 expires; + void (*function)(TimerList*); + void* data; + + void init(void (*cb)(TimerList*)) { + entry.init(); + function = cb; + data = nullptr; + expires = 0; + } +}; + +extern uint64 g_jiffies; + +void timer_init(); + +// AP per-CPU initialization +void timer_init_ap(); + +// Handle timer interrupt tick (called from TrapHandler) +void timer_tick(); + +// Get configured timebase frequency +uint64 timer_get_freq(); + +void add_timer(TimerList* timer); +int del_timer(TimerList* timer); +int mod_timer(TimerList* timer, uint64 expires); diff --git a/include/lume/tlb.h b/include/lume/tlb.h new file mode 100644 index 0000000..b4d51ea --- /dev/null +++ b/include/lume/tlb.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tlb.h — Machine-Independent TLB management interface + * + * All TLB invalidation in the kernel MUST go through these functions. + * Direct use of arch::mmu::flush_tlb_*() is prohibited outside of + * this file's implementation. + * + * Current implementation: local CPU sfence.vma only (single-core). + * When SMP IPI infrastructure is available, tlb_shootdown_range() + * and tlb_shootdown_all() will broadcast invalidation to remote + * CPUs via IPI — no caller changes required. + * + * Reference: docs/specs/linux_abi_mm_plan.md §4 + */ + +#include + +struct VmSpace; + +/* + * Invalidate TLB entries for a VA range [start, end) on ALL CPUs + * that may have cached translations for the given address space. + * + * @mm: address space (used to determine which CPUs to IPI; + * nullptr means kernel address space = all CPUs). + * @start: first VA to invalidate (page-aligned). + * @end: one-past-last VA to invalidate (page-aligned). + * + * If (end - start) exceeds a threshold, falls back to full flush. + */ +void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end); + +/* + * Invalidate a single page's TLB entry on ALL CPUs. + * Convenience wrapper around tlb_shootdown_range(). + */ +void tlb_shootdown_page(VmSpace* mm, uint64 va); + +/* + * Full TLB flush on ALL CPUs. + * Used after page table switch, large-scale munmap, etc. + */ +void tlb_shootdown_all(); diff --git a/include/lume/trap.h b/include/lume/trap.h new file mode 100644 index 0000000..61a5054 --- /dev/null +++ b/include/lume/trap.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Trap Subsystem — Machine-Independent Interface + * + * Defines TrapCause enum and MI-layer declarations. + * MD layer (arch/riscv/trap/trap_md.cc) translates scause → TrapCause. + * MI layer (kernel/trap.cc) dispatches based on TrapCause alone. + * + * Reference: docs/specs/trap.md §3.2 + */ + +#include + +/* MI-layer trap cause classification. + * MD layer is responsible for translating architecture-specific cause codes + * (e.g., RISC-V scause) into these semantic values. MI layer never reads CSRs. */ +enum class TrapCause : uint32 { + /* --- Exceptions (Synchronous) --- */ + PageFaultLoad = 0, // Load page fault + PageFaultStore = 1, // Store/AMO page fault + PageFaultExec = 2, // Instruction page fault + Syscall = 3, // User-mode ecall + IllegalInst = 4, // Illegal instruction + Breakpoint = 5, // Breakpoint (ebreak) + AccessFault = 6, // Bus access fault (not page fault) + AlignFault = 7, // Address misalignment + + /* --- Interrupts (Asynchronous) --- */ + IrqTimer = 16, // S-mode timer interrupt + IrqSoftware = 17, // S-mode software interrupt (IPI) + IrqExternal = 18, // S-mode external interrupt (PLIC) + + /* --- Unknown --- */ + Unknown = 255, +}; + +/* High-level trap class for routing and stats. */ +enum class TrapClass : uint8 { + Exception = 0, + Interrupt = 1, + Syscall = 2, + Unknown = 3, +}; + +/* Origin privilege of the trap. */ +enum class TrapOrigin : uint8 { + FromUser = 0, + FromKernel = 1, +}; + +/* Unified trap context passed from MD to MI. */ +struct TrapInfo { + TrapClass cls; + TrapCause cause; + TrapOrigin origin; + uint64 val; // stval (faulting address or instruction) + uint64 epc; // sepc at trap entry (may be adjusted by MD) + uint64 scause; // raw scause for diagnostics +}; + +/* Trap statistics for debugging/testing. */ +inline constexpr uint32 kTrapCauseCount = 12; + +struct TrapStats { + uint64 total; + uint64 by_cause[kTrapCauseCount]; + TrapCause last_cause; + TrapClass last_class; + TrapOrigin last_origin; + uint64 last_val; + uint64 last_epc; + uint64 last_scause; +}; + +namespace arch { +struct TrapFrame; // Forward declaration (defined in arch/trapframe.h) +} + +/* + * trap_init() — BSP initialization + * Sets stvec to kernel_trap_entry (handles traps while in S-mode). + * Initializes the global IrqManager. + * Called from kernel_main() after VMM and before PLIC. + */ +void trap_init(); + +/* + * trap_init_ap() — AP per-CPU initialization + * Sets this CPU's stvec. + */ +void trap_init_ap(); + +/* + * trap_handle() — MI-layer semantic dispatcher + * Called by MD layer after scause → TrapCause translation. + * Routes to page fault handler, syscall dispatcher, IRQ manager, etc. + * + * tf: pointer to the current TrapFrame + * info: translated trap context from MD layer + */ +void trap_handle(arch::TrapFrame* tf, const TrapInfo& info); + +/* Trap statistics helpers (MI layer). */ +void trap_stats_get(TrapStats* out); +void trap_stats_reset(); diff --git a/include/lume/tty.h b/include/lume/tty.h new file mode 100644 index 0000000..34cf7bb --- /dev/null +++ b/include/lume/tty.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tty.h — TTY subsystem core structures + * + * tty_struct is the central object representing an open terminal. + * It connects a hardware driver (tty_driver) with a line discipline + * (tty_ldisc) to implement the full terminal abstraction. + * + * Architecture: + * user read/write → tty_core → tty_ldisc (n_tty) → tty_driver (uart/pty) + * + * Reference: Linux include/linux/tty.h, include/linux/tty_ldisc.h + */ + +#include +#include +#include +#include + +struct tty_struct; +struct file; +struct file_operations; +struct inode; + +/* ================================================================== */ +/* tty_driver — hardware/pseudo-terminal driver interface */ +/* ================================================================== */ + +struct tty_driver_ops { + int (*open)(tty_struct* tty); + void (*close)(tty_struct* tty); + int (*write)(tty_struct* tty, const char* buf, int count); + int (*write_room)(tty_struct* tty); + void (*put_char)(tty_struct* tty, char ch); + void (*flush_chars)(tty_struct* tty); + int (*chars_in_buffer)(tty_struct* tty); + void (*flush_buffer)(tty_struct* tty); + int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg); + void (*throttle)(tty_struct* tty); + void (*unthrottle)(tty_struct* tty); + void (*hangup)(tty_struct* tty); +}; + +struct tty_driver { + const char* name; /* "ttyS", "ttySIF", "pts" */ + int major; + int minor_start; + int num; /* number of devices */ + const tty_driver_ops* ops; +}; + +/* ================================================================== */ +/* tty_ldisc — line discipline interface */ +/* ================================================================== */ + +struct tty_ldisc_ops { + const char* name; + int num; /* N_TTY = 0 */ + + int (*open)(tty_struct* tty); + void (*close)(tty_struct* tty); + int (*read)(tty_struct* tty, file* fp, char* buf, uint64 count); + int (*write)(tty_struct* tty, file* fp, const char* buf, uint64 count); + void (*receive_buf)(tty_struct* tty, const char* buf, int count); + int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg); + void (*flush_buffer)(tty_struct* tty); + int (*chars_in_buffer)(tty_struct* tty); + void (*set_termios)(tty_struct* tty, const termios* old); + void (*hangup)(tty_struct* tty); +}; + +struct tty_ldisc { + const tty_ldisc_ops* ops; + void* data; /* ldisc private data (n_tty_data*) */ +}; + +/* ================================================================== */ +/* tty_struct — central TTY object */ +/* ================================================================== */ + +inline constexpr int TTY_BUF_SIZE = 4096; + +/* TTY flags */ +inline constexpr uint32 TTY_THROTTLED = (1 << 0); +inline constexpr uint32 TTY_IO_ERROR = (1 << 1); +inline constexpr uint32 TTY_CLOSING = (1 << 2); +inline constexpr uint32 TTY_HUPPING = (1 << 3); + +struct tty_struct { + int index; /* device index */ + const tty_driver* driver; /* hardware driver */ + tty_ldisc ldisc; /* current line discipline */ + tty_struct* link; /* PTY peer (master ↔ slave) */ + + termios termios_data; /* terminal settings */ + winsize ws; /* window size */ + + int32 pgrp; /* foreground process group */ + int32 session; /* controlling session */ + + /* Output buffer (tty_core → driver) */ + char write_buf[TTY_BUF_SIZE]; + int write_cnt; + + /* Synchronization */ + WaitQueue read_wait; + WaitQueue write_wait; + Spinlock lock; + + uint32 flags; + int open_count; + + /* Private driver data */ + void* driver_data; +}; + +/* ================================================================== */ +/* TTY core API */ +/* ================================================================== */ + +/* Initialize the TTY subsystem (creates /dev/console TTY) */ +void tty_init(); + +/* Allocate and initialize a new tty_struct */ +tty_struct* tty_alloc(const tty_driver* driver, int index); + +/* Open/close a TTY (called from chardev open/close) */ +int tty_open(tty_struct* tty); +void tty_close(tty_struct* tty); + +/* Core I/O (called from file_operations) */ +int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count); +int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count); +long tty_ioctl(tty_struct* tty, file* fp, unsigned int cmd, uint64 arg); + +/* Called by hardware driver when bytes arrive */ +void tty_receive_buf(tty_struct* tty, const char* buf, int count); + +/* Signal helpers */ +void tty_signal_pgrp(tty_struct* tty, int sig); + +/* file_operations for TTY devices */ +extern file_operations tty_fops; + +/* Global console TTY */ +extern tty_struct* g_console_tty; + +/* ================================================================== */ +/* N_TTY line discipline */ +/* ================================================================== */ + +extern const tty_ldisc_ops n_tty_ops; + +/* ================================================================== */ +/* UART TTY driver */ +/* ================================================================== */ + +extern const tty_driver uart_tty_driver; + +/* Initialize UART as a TTY driver (registers RX IRQ) */ +void uart_tty_init(tty_struct* tty); diff --git a/include/lume/tty_ioctl.h b/include/lume/tty_ioctl.h new file mode 100644 index 0000000..87ba59b --- /dev/null +++ b/include/lume/tty_ioctl.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tty_ioctl.h — Terminal I/O control constants and structures + * + * Linux-compatible termios, winsize, and TIOC* ioctl definitions. + * Values match the RISC-V/generic Linux ABI. + * + * Reference: include/uapi/asm-generic/termbits.h, + * include/uapi/asm-generic/ioctls.h + */ + +#include + +/* ================================================================== */ +/* termios c_cc indices */ +/* ================================================================== */ + +inline constexpr int VINTR = 0; +inline constexpr int VQUIT = 1; +inline constexpr int VERASE = 2; +inline constexpr int VKILL = 3; +inline constexpr int VEOF = 4; +inline constexpr int VTIME = 5; +inline constexpr int VMIN = 6; +inline constexpr int VSWTC = 7; +inline constexpr int VSTART = 8; +inline constexpr int VSTOP = 9; +inline constexpr int VSUSP = 10; +inline constexpr int VEOL = 11; +inline constexpr int VREPRINT = 12; +inline constexpr int VDISCARD = 13; +inline constexpr int VWERASE = 14; +inline constexpr int VLNEXT = 15; +inline constexpr int VEOL2 = 16; +inline constexpr int NCCS = 19; + +/* ================================================================== */ +/* c_iflag — input mode flags */ +/* ================================================================== */ + +inline constexpr uint32 IGNBRK = 0000001; +inline constexpr uint32 BRKINT = 0000002; +inline constexpr uint32 IGNPAR = 0000004; +inline constexpr uint32 PARMRK = 0000010; +inline constexpr uint32 INPCK = 0000020; +inline constexpr uint32 ISTRIP = 0000040; +inline constexpr uint32 INLCR = 0000100; +inline constexpr uint32 IGNCR = 0000200; +inline constexpr uint32 ICRNL = 0000400; +inline constexpr uint32 IUCLC = 0001000; +inline constexpr uint32 IXON = 0002000; +inline constexpr uint32 IXANY = 0004000; +inline constexpr uint32 IXOFF = 0010000; +inline constexpr uint32 IMAXBEL = 0020000; +inline constexpr uint32 IUTF8 = 0040000; + +/* ================================================================== */ +/* c_oflag — output mode flags */ +/* ================================================================== */ + +inline constexpr uint32 OPOST = 0000001; +inline constexpr uint32 OLCUC = 0000002; +inline constexpr uint32 ONLCR = 0000004; +inline constexpr uint32 OCRNL = 0000010; +inline constexpr uint32 ONOCR = 0000020; +inline constexpr uint32 ONLRET = 0000040; +inline constexpr uint32 OFILL = 0000100; +inline constexpr uint32 OFDEL = 0000200; + +/* ================================================================== */ +/* c_cflag — control mode flags */ +/* ================================================================== */ + +inline constexpr uint32 CSIZE = 0000060; +inline constexpr uint32 CS5 = 0000000; +inline constexpr uint32 CS6 = 0000020; +inline constexpr uint32 CS7 = 0000040; +inline constexpr uint32 CS8 = 0000060; +inline constexpr uint32 CSTOPB = 0000100; +inline constexpr uint32 CREAD = 0000200; +inline constexpr uint32 PARENB = 0000400; +inline constexpr uint32 PARODD = 0001000; +inline constexpr uint32 HUPCL = 0002000; +inline constexpr uint32 CLOCAL = 0004000; + +/* Baud rates (stored in c_cflag masked with CBAUD) */ +inline constexpr uint32 CBAUD = 0010017; +inline constexpr uint32 B0 = 0000000; +inline constexpr uint32 B9600 = 0000015; +inline constexpr uint32 B19200 = 0000016; +inline constexpr uint32 B38400 = 0000017; +inline constexpr uint32 B115200 = 0010002; + +/* ================================================================== */ +/* c_lflag — local mode flags */ +/* ================================================================== */ + +inline constexpr uint32 ISIG = 0000001; +inline constexpr uint32 ICANON = 0000002; +inline constexpr uint32 XCASE = 0000004; +inline constexpr uint32 LECHO = 0000010; /* Named LECHO to avoid macro conflict */ +inline constexpr uint32 ECHOE = 0000020; +inline constexpr uint32 ECHOK = 0000040; +inline constexpr uint32 ECHONL = 0000100; +inline constexpr uint32 NOFLSH = 0000200; +inline constexpr uint32 TOSTOP = 0000400; +inline constexpr uint32 ECHOCTL = 0001000; +inline constexpr uint32 ECHOPRT = 0002000; +inline constexpr uint32 ECHOKE = 0004000; +inline constexpr uint32 IEXTEN = 0100000; + +/* Linux uses ECHO = 0x0008, we use LECHO to avoid reserved-name issues. + * Provide the standard name as well for ioctl compatibility. */ +inline constexpr uint32 ECHO = LECHO; + +/* ================================================================== */ +/* termios structure */ +/* ================================================================== */ + +struct termios { + uint32 c_iflag; /* input mode flags */ + uint32 c_oflag; /* output mode flags */ + uint32 c_cflag; /* control mode flags */ + uint32 c_lflag; /* local mode flags */ + uint8 c_line; /* line discipline */ + uint8 c_cc[NCCS]; /* control characters */ +}; + +/* ================================================================== */ +/* winsize structure */ +/* ================================================================== */ + +struct winsize { + uint16 ws_row; + uint16 ws_col; + uint16 ws_xpixel; + uint16 ws_ypixel; +}; + +/* ================================================================== */ +/* ioctl command numbers (Linux asm-generic/ioctls.h) */ +/* ================================================================== */ + +inline constexpr uint32 TCGETS = 0x5401; +inline constexpr uint32 TCSETS = 0x5402; +inline constexpr uint32 TCSETSW = 0x5403; +inline constexpr uint32 TCSETSF = 0x5404; +inline constexpr uint32 TCGETA = 0x5405; +inline constexpr uint32 TCSETA = 0x5406; +inline constexpr uint32 TCSETAW = 0x5407; +inline constexpr uint32 TCSETAF = 0x5408; +inline constexpr uint32 TCSBRK = 0x5409; +inline constexpr uint32 TCXONC = 0x540A; +inline constexpr uint32 TCFLSH = 0x540B; +inline constexpr uint32 TIOCEXCL = 0x540C; +inline constexpr uint32 TIOCNXCL = 0x540D; +inline constexpr uint32 TIOCSCTTY = 0x540E; +inline constexpr uint32 TIOCGPGRP = 0x540F; +inline constexpr uint32 TIOCSPGRP = 0x5410; +inline constexpr uint32 TIOCOUTQ = 0x5411; +inline constexpr uint32 TIOCSTI = 0x5412; +inline constexpr uint32 TIOCGWINSZ = 0x5413; +inline constexpr uint32 TIOCSWINSZ = 0x5414; +inline constexpr uint32 TIOCMGET = 0x5415; +inline constexpr uint32 TIOCMSET = 0x5418; +inline constexpr uint32 FIONREAD = 0x541B; +inline constexpr uint32 TIOCNOTTY = 0x5422; +inline constexpr uint32 TIOCGPTLCK = 0x80045430; +inline constexpr uint32 TIOCSPTLCK = 0x40045431; + +/* Default termios for a new TTY */ +inline void tty_default_termios(termios* t) +{ + t->c_iflag = ICRNL | IXON | IUTF8; + t->c_oflag = OPOST | ONLCR; + t->c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; + t->c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; + t->c_line = 0; /* N_TTY */ + + for (int i = 0; i < NCCS; i++) + t->c_cc[i] = 0; + + t->c_cc[VINTR] = 003; /* Ctrl+C */ + t->c_cc[VQUIT] = 034; /* Ctrl+\ */ + t->c_cc[VERASE] = 0177; /* DEL */ + t->c_cc[VKILL] = 025; /* Ctrl+U */ + t->c_cc[VEOF] = 004; /* Ctrl+D */ + t->c_cc[VSTART] = 021; /* Ctrl+Q */ + t->c_cc[VSTOP] = 023; /* Ctrl+S */ + t->c_cc[VSUSP] = 032; /* Ctrl+Z */ + t->c_cc[VREPRINT] = 022; /* Ctrl+R */ + t->c_cc[VWERASE] = 027; /* Ctrl+W */ + t->c_cc[VLNEXT] = 026; /* Ctrl+V */ + t->c_cc[VEOL] = 0; + t->c_cc[VEOL2] = 0; + t->c_cc[VMIN] = 1; + t->c_cc[VTIME] = 0; +} diff --git a/include/lume/types.h b/include/lume/types.h new file mode 100644 index 0000000..2342359 --- /dev/null +++ b/include/lume/types.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_TYPES_H +#define _LUME_TYPES_H + +/* + * LumeOS Base Type Definitions + * + * Three naming layers coexist so that source files imported from + * upstream projects (Linux kernel, libfdt) compile unchanged: + * + * LumeOS native uint8 / int8 / ... / uint64 / int64 + * Linux kernel short names u8 / s8 / ... / u64 / s64 + * POSIX / C99 (with _t) uint8_t / ... / uint64_t / intptr_t + * + * All three resolve to the same underlying compiler builtin, so they + * are binary-compatible. + * + * Declared with C `typedef` (not C++ `using`) so this header may be + * included from .c sources — required for libfdt shims and the + * arch/riscv/kernel/pi/ subsystem, which are pure C imports from + * Linux. C++ accepts `typedef` syntax identically. + */ + +/* ===== LumeOS native names ===== */ +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef signed long long int64; + +typedef __SIZE_TYPE__ size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef int64 ssize_t; +typedef uint64 uintptr; + +/* ===== Linux kernel short names ===== */ +typedef uint8 u8; typedef int8 s8; +typedef uint16 u16; typedef int16 s16; +typedef uint32 u32; typedef int32 s32; +typedef uint64 u64; typedef int64 s64; + +/* ===== POSIX / C99 aliases ===== + * Guarded so a later #include from libc-shim during musl + * userspace builds is a no-op rather than a redefinition error. */ +#ifndef _STDINT_H +# define _STDINT_H +typedef uint8 uint8_t; +typedef uint16 uint16_t; +typedef uint32 uint32_t; +typedef uint64 uint64_t; +typedef int8 int8_t; +typedef int16 int16_t; +typedef int32 int32_t; +typedef int64 int64_t; +typedef uintptr uintptr_t; +typedef int64 intptr_t; +#endif + +/* ===== Linux domain typedefs needed by RISC-V boot path ===== */ +typedef uint64 phys_addr_t; + +/* ===== libfdt on-wire types (big-endian). Real byte-swap helpers + * live in . ===== */ +typedef uint16 fdt16_t; +typedef uint32 fdt32_t; +typedef uint64 fdt64_t; + +#ifndef offsetof +# define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER) +#endif + +#endif /* _LUME_TYPES_H */ diff --git a/include/lume/uaccess.h b/include/lume/uaccess.h new file mode 100644 index 0000000..5a9ed71 --- /dev/null +++ b/include/lume/uaccess.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * User-space memory access — safe copy routines. + * + * Phase 2: Software page-table validation. + * Phase 3+: Exception Table (zero-cost happy path). + * + * Reference: docs/specs/syscall.md §8 + */ + +#include + +/* Copy len bytes from user address to kernel buffer. + * Returns 0 on success, -EFAULT if user address is invalid. */ +int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len); + +/* Copy len bytes from kernel buffer to user address. + * Returns 0 on success, -EFAULT if user address is invalid. */ +int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len); + +/* Copy NUL-terminated string from user space. + * Returns string length (excluding NUL) or -EFAULT. */ +int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len); diff --git a/include/lume/uuid.h b/include/lume/uuid.h new file mode 100644 index 0000000..fcbd4a3 --- /dev/null +++ b/include/lume/uuid.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * UUID/GUID definition + * + * Copyright (C) 2010, 2016 Intel Corp. + * Huang Ying + */ + +#pragma once + +#include +#include + +constexpr uint32 UUID_SIZE = 16; +constexpr uint32 UUID_STRING_LEN = 36; + +static inline int uuid_hex_value(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +static inline bool uuid_parse_raw(const char* uuid, uint8 out[UUID_SIZE]) +{ + if (!uuid) { + return false; + } + if (strlen(uuid) != UUID_STRING_LEN) { + return false; + } + if (uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || uuid[23] != '-') { + return false; + } + + int out_index = 0; + for (int i = 0; i < static_cast(UUID_STRING_LEN); ) { + if (uuid[i] == '-') { + ++i; + continue; + } + + int hi = uuid_hex_value(uuid[i]); + int lo = uuid_hex_value(uuid[i + 1]); + if (hi < 0 || lo < 0) { + return false; + } + + out[out_index++] = static_cast((hi << 4) | lo); + i += 2; + } + + return out_index == static_cast(UUID_SIZE); +} + +struct uuid_base { + uint8 b[UUID_SIZE]; + + bool is_null() const + { + for (uint32 i = 0; i < UUID_SIZE; ++i) { + if (b[i] != 0) { + return false; + } + } + return true; + } + + bool operator==(const uuid_base& other) const + { + return memcmp(b, other.b, UUID_SIZE) == 0; + } + + bool operator!=(const uuid_base& other) const + { + return !(*this == other); + } +}; + +struct uuid_t : public uuid_base { + explicit uuid_t(const uint8 in_b[UUID_SIZE]) + { + memcpy(b, in_b, UUID_SIZE); + } + + explicit constexpr uuid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1, + uint8 d2, uint8 d3, uint8 d4, uint8 d5, + uint8 d6, uint8 d7) + : uuid_base{{ + static_cast((a >> 24) & 0xff), + static_cast((a >> 16) & 0xff), + static_cast((a >> 8) & 0xff), + static_cast(a & 0xff), + static_cast((b >> 8) & 0xff), + static_cast(b & 0xff), + static_cast((c >> 8) & 0xff), + static_cast(c & 0xff), + d0, d1, d2, d3, d4, d5, d6, d7 + }} + { + } + + constexpr uuid_t() : uuid_base{{0}} {} + + static bool parse(const char* uuid, uuid_t& out) + { + uint8 tmp[UUID_SIZE]; + if (!uuid_parse_raw(uuid, tmp)) { + return false; + } + + memcpy(out.b, tmp, UUID_SIZE); + return true; + } + + static bool is_valid(const char* uuid) + { + uint8 tmp[UUID_SIZE]; + return uuid_parse_raw(uuid, tmp); + } + + static constexpr uuid_t null() + { + return uuid_t(); + } + + void import_bytes(const uint8* src) + { + memcpy(b, src, UUID_SIZE); + } + + void export_bytes(uint8* dst) const + { + memcpy(dst, b, UUID_SIZE); + } +}; + +struct guid_t : public uuid_base { + explicit guid_t(const uint8 in_b[UUID_SIZE]) + { + memcpy(b, in_b, UUID_SIZE); + } + + explicit constexpr guid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1, + uint8 d2, uint8 d3, uint8 d4, uint8 d5, + uint8 d6, uint8 d7) + : uuid_base{{ + static_cast(a & 0xff), + static_cast((a >> 8) & 0xff), + static_cast((a >> 16) & 0xff), + static_cast((a >> 24) & 0xff), + static_cast(b & 0xff), + static_cast((b >> 8) & 0xff), + static_cast(c & 0xff), + static_cast((c >> 8) & 0xff), + d0, d1, d2, d3, d4, d5, d6, d7 + }} + { + } + + constexpr guid_t() : uuid_base{{0}} {} + static bool parse(const char* uuid, guid_t& out) + { + uint8 tmp[UUID_SIZE]; + if (!uuid_parse_raw(uuid, tmp)) { + return false; + } + + static constexpr uint8 guid_index[UUID_SIZE] = { + 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15, + }; + + for (int i = 0; i < static_cast(UUID_SIZE); ++i) { + out.b[guid_index[i]] = tmp[i]; + } + return true; + } + + static bool is_valid(const char* uuid) + { + uint8 tmp[UUID_SIZE]; + return uuid_parse_raw(uuid, tmp); + } + + static constexpr guid_t null() + { + return guid_t(); + } + + void import_bytes(const uint8* src) + { + memcpy(b, src, UUID_SIZE); + } + + void export_bytes(uint8* dst) const + { + memcpy(dst, b, UUID_SIZE); + } +}; + diff --git a/include/lume/vmm.h b/include/lume/vmm.h new file mode 100644 index 0000000..9cbebf1 --- /dev/null +++ b/include/lume/vmm.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * vmm.h — Virtual Memory Manager + * + * Managing kernel and user virtual address spaces. + * + * Reference: docs/specs/vmm.md + */ + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* MI-layer permission flags (architecture-independent) */ +/* */ +/* These are used by VMA and vmm_map_user / vmm_handle_page_fault. */ +/* The pmap MD layer translates them to hardware PTE bits. */ +/* ------------------------------------------------------------------ */ +inline constexpr uint64 VM_READ = (1ULL << 0); +inline constexpr uint64 VM_WRITE = (1ULL << 1); +inline constexpr uint64 VM_EXEC = (1ULL << 2); +inline constexpr uint64 VM_USER = (1ULL << 3); + +enum class VmFaultCause : uint8 { + Read = 0, + Write = 1, + Exec = 2, +}; + +/* Forward declarations */ +struct VmSpace; +struct VmAreaStruct; + +/* + * Virtual Memory Area (VMA) + * Represents a contiguous range [start, end) of uniform permissions. + */ +struct VmAreaStruct { + uint64 start; // Start VA (page aligned) + uint64 end; // End VA (exclusive, page aligned) + uint64 perm; // VM_READ | VM_WRITE | VM_EXEC | VM_USER + struct rb_node rb; // Linkage in VmSpace::vma_tree + VmSpace* mm; // Back pointer to the parent address space +}; + +/* + * Address Space (VmSpace) + * Represents a user process's complete virtual memory mapping. + * + * Lock contract: + * Holds: lock (internal spinlock) + * May acquire: PMM global lock (via pmm_alloc_page inside page fault) + * Callers must NOT hold: PMM lock (lock ordering: VmSpace -> PMM) + */ +struct VmSpace { + uint64 root_pa; // SV39 root page table physical address + struct rb_root vma_tree; // Red-black tree of VmAreaStructs + Spinlock lock; // Protects vma_tree mutations + lume::atomic refcount; // Reference count (for CLONE_VM sharing) + lume::atomic cpu_mask; // Bitmask of CPUs with this mm in satp + + uint64 brk_start; // Start of heap (page-aligned) + uint64 brk_end; // Current heap end (page-aligned) + uint64 mmap_base; // Base for mmap (Linux: TASK_UNMAPPED_BASE = TASK_SIZE/3) + + // Create a brand new VmSpace (allocates root pagetable, refcount=1) + static VmSpace* create(); + + // Increment reference count (for CLONE_VM) + void incref(); + + // Decrement reference count; destroys VmSpace when it reaches 0. + // Call this instead of destroy() directly. + void put(); + + // Destroy VmSpace, unmapping all VMAs and freeing pagetables. + // Internal — called by put() when refcount drops to 0. + void destroy(); +}; + +/* Initialize kernel page table (Phase 1) */ +void vmm_init(); + +/* Switch AP to the kernel page table built by BSP (Phase 1) */ +void vmm_init_ap(); + +/* Map MMIO device region to the active kernel page table */ +void vmm_map_kernel_mmio(uint64 pa, uint64 size); + +/* Query page_map allocation (set by vmm_init, read by pmm_init). + * Returns the PA, byte size, and page count of the vmemmap region. */ +uint64 vmm_get_page_map_pa(); +uint64 vmm_get_page_map_size(); +uint64 vmm_get_num_pages(); + +/* User space memory management (VMA & Demand Paging) */ +int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); +int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len); +int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); + +/* VMA helpers for mmap/mprotect implementations */ +uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align); +bool vma_range_covered(VmSpace* space, uint64 start, uint64 end); + +/* Handle user page fault (lookup VMA -> allocate page -> map) */ +int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause); + +/* Clone an address space for fork() */ +VmSpace* vmm_clone(VmSpace* parent_space); diff --git a/include/lume/wait.h b/include/lume/wait.h new file mode 100644 index 0000000..865c1f2 --- /dev/null +++ b/include/lume/wait.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * POSIX waitpid / wait4 Constants + * Reference: docs/specs/syscall.md §9 + */ + +/* waitpid options */ +inline constexpr int WNOHANG = 1; +inline constexpr int WUNTRACED = 2; +inline constexpr int WCONTINUED = 8; +inline constexpr int WSTOPPED = WUNTRACED; /* POSIX alias */ +inline constexpr int WEXITED = 4; /* POSIX */ +inline constexpr int WNOWAIT = 0x01000000; /* POSIX: peek, don't reap */ + +/* Exit status encoding (Linux-compatible) */ +static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; } +static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; } +static inline bool WIFSIGNALED(int s) { return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; } +static inline int WTERMSIG(int s) { return s & 0x7F; } +static inline bool WIFSTOPPED(int s) { return (s & 0xFF) == 0x7F; } +static inline int WSTOPSIG(int s) { return (s >> 8) & 0xFF; } +static inline bool WIFCONTINUED(int s){ return s == 0xFFFF; } + +/* Encode exit code into wstatus (normal exit) */ +static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; } +static inline int W_STOPCODE(int sig) { return ((sig & 0xFF) << 8) | 0x7F; } +static inline int W_CONTINUED() { return 0xFFFF; } diff --git a/include/lume/waitqueue.h b/include/lume/waitqueue.h new file mode 100644 index 0000000..3720cb7 --- /dev/null +++ b/include/lume/waitqueue.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * WaitQueue — blocking synchronization primitive + * + * Supports two usage patterns: + * + * 1. Simple (legacy): wq.sleep() — combines prepare/schedule/finish. + * Suitable for simple cases where no external condition is checked. + * + * 2. Linux-style two-phase (recommended for correctness): + * + * wq.prepare_to_wait(curr, TaskState::Sleeping); + * if (!condition) { + * schedule(); + * } + * wq.finish_wait(curr); + * + * This pattern prevents the "lost wakeup" race: the task is already + * on the wait list (and marked Sleeping) BEFORE checking the condition. + * If a wake_one/wake_all fires between the check and schedule(), the + * task's state will already be set back to Runnable, and schedule() + * will see state == Runnable and simply re-run the task. + * + * Reference: docs/specs/scheduler.md, docs/specs/task.md §5 + */ + +#include +#include +#include + +struct TaskControlBlock; +enum class TaskState : uint8; + +class WaitQueue { +public: + void init(const char* name = "wq") { + head_.init(); + lock_.init(name); + } + + /* + * Phase 1: Add the task to the wait queue and set its state. + * The task is NOT yet descheduled — the caller must check the + * condition and call schedule() if the condition is still false. + * + * Must be called with interrupts disabled or preemption disabled. + */ + void prepare_to_wait(TaskControlBlock* task, TaskState new_state); + + /* + * Phase 3: Remove the task from the wait queue (if still on it) + * and ensure its state is Runnable. Called after schedule() returns + * (i.e. after the task has been woken up, or if the condition was + * true and schedule() was skipped). + */ + void finish_wait(TaskControlBlock* task); + + /* Legacy convenience: combines prepare_to_wait + schedule + finish_wait. + * If external_lock is provided, it is released AFTER the task is + * added to the wait queue but BEFORE schedule() is called. */ + void sleep(Spinlock* external_lock = nullptr); + + /* Wake the first (oldest) sleeping task. Returns true if a task was woken. */ + bool wake_one(); + + /* Wake all sleeping tasks. Returns the number of tasks woken. */ + int wake_all(); + + /* Check if any tasks are waiting. */ + bool is_empty() const { return head_.is_empty(); } + +private: + list_node head_; + Spinlock lock_; +}; diff --git a/include/ulib/assert.h b/include/ulib/assert.h deleted file mode 100644 index 014be9b..0000000 --- a/include/ulib/assert.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _ASSERT_H -#define _ASSERT_H - -#ifdef NDEBUG -#define assert(ignore) ((void)0) -#else -#ifdef __cplusplus -extern "C" -{ -#endif - void __assert_fail(const char *expr, const char *file, int line, const char *func); -#ifdef __cplusplus -} -#endif - -#define assert(expr) \ - ((expr) ? (void)0 : __assert_fail(#expr, __FILE__, __LINE__, __func__)) -#endif - -#endif \ No newline at end of file diff --git a/include/ulib/cstdlib b/include/ulib/cstdlib deleted file mode 100644 index 8b07338..0000000 --- a/include/ulib/cstdlib +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CPP_CSTDLIB -#define _CPP_CSTDLIB - -#include "stdlib.h" - -namespace std -{ - using :: malloc; - using :: free; - using :: calloc; - using :: realloc; - - using :: exit; - using :: abort; - using :: atexit; - using :: getenv; - using :: system; - - using :: atoi; - using :: atol; - using :: atoll; - using :: atof; - - using :: strtol; - using :: strtoul; - using :: strtoll; - using :: strtoull; - - using :: strtod; - using :: strtof; - - using :: bsearch; - using :: qsort; - - using :: abs; - using :: labs; - using :: llabs; - using :: div; - using :: ldiv; - using :: lldiv; - - using :: rand; - using :: srand; - - using :: mblen; - using :: mbtowc; - using :: wctomb; - using :: mbstowcs; - using :: wcstombs; -} - -#endif \ No newline at end of file diff --git a/include/ulib/cstring b/include/ulib/cstring deleted file mode 100644 index d65dc8d..0000000 --- a/include/ulib/cstring +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CPP_CSTRING -#define _CPP_CSTRING - -#include "string.h" - -namespace std -{ - using::memchr; - using::memcmp; - using::memcpy; - using::memmove; - using::memset; - using::strcat; - using::strchr; - using::strcmp; - using::strcpy; - using::strcspn; - using::strdup; - using::strerror; - using::strlcat; - using::strlcpy; - using::strlen; - using::strncat; - using::strncmp; - using::strpbrk; - using::strrchr; - using::strspn; - using::strstr; - using::strtok; -} // namespace std - -#endif \ No newline at end of file diff --git a/include/ulib/ctypes.h b/include/ulib/ctypes.h deleted file mode 100644 index 1ec204e..0000000 --- a/include/ulib/ctypes.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CTYPE_H -#define _CTYPE_H - -#ifdef __cplusplus -extern "C" -{ -#endif - - static inline int isdigit(int c) { return c >= '0' && c <= '9'; } - static inline int islower(int c) { return c >= 'a' && c <= 'z'; } - static inline int isupper(int c) { return c >= 'A' && c <= 'Z'; } - static inline int isalpha(int c) { return islower(c) || isupper(c); } - static inline int isalnum(int c) { return isalpha(c) || isdigit(c); } - static inline int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } - static inline int isprint(int c) { return c >= 32 && c <= 126; } - static inline int iscntrl(int c) { return c < 32 || c == 127; } - static inline int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } - - static inline int tolower(int c) { return isupper(c) ? c + 32 : c; } - static inline int toupper(int c) { return islower(c) ? c - 32 : c; } - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/include/ulib/cxx.cc b/include/ulib/cxx.cc deleted file mode 100644 index f454fea..0000000 --- a/include/ulib/cxx.cc +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#include "ulib/stdlib.h" - - -void *operator new(unsigned long n) -{ - return malloc(n); -} - -void *operator new[](unsigned long n) -{ - return malloc(n); -} - -void operator delete(void *p) -{ - free(p); -} - -void operator delete[](void *p) -{ - free(p); -} - -void operator delete(void *p, unsigned long) -{ - free(p); -} - -void operator delete[](void *p, unsigned long) -{ - free(p); -} \ No newline at end of file diff --git a/include/ulib/errno.h b/include/ulib/errno.h deleted file mode 100644 index dd51598..0000000 --- a/include/ulib/errno.h +++ /dev/null @@ -1,170 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Lume OS - Linux Compatibility Layer - * - * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * 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 -#define _ERRNO_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -#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 */ -/* - * This error code is special: arch syscall entry code will return - * -ENOSYS if users try to call a syscall that doesn't exist. To keep - * failures of syscalls that really do exist distinguishable from - * failures due to attempts to use a nonexistent syscall, syscall - * implementations should refrain from returning -ENOSYS. - */ -#define ENOSYS 38 /* Invalid system call number */ - -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ - -#define EDEADLOCK EDEADLK - -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Can not access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ - -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ - -/* for robust mutexes */ -#define EOWNERDEAD 130 /* Owner died */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ - -#define ERFKILL 132 /* Operation not possible due to RF-kill */ - -#define EHWPOISON 133 /* Memory page has hardware error */ - -extern int errno; -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/include/ulib/iostream b/include/ulib/iostream deleted file mode 100644 index b05f603..0000000 --- a/include/ulib/iostream +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _IOSTREAM -#define _IOSTREAM - -#include "stdio.h" - -namespace std -{ - class ostream - { - private: - FILE* _stream; - public: - ostream(FILE* f) : _stream(f) {} - - ostream& operator<<(const char* str) - { - fprintf(_stream, "%s", str); - return *this; - } - - ostream& operator<<(int n) - { - fprintf(_stream, "%d", n); - return *this; - } - - ostream &operator<<(long n) - { - fprintf(_stream, "%ld", n); - return *this; - } - - ostream &operator<<(long long n) - { - fprintf(_stream, "%lld", n); - return *this; - } - - ostream &operator<<(double f) - { - fprintf(_stream, "%f", f); - return *this; - } - - ostream &operator<<(char c) - { - fputc(c, _stream); - return *this; - } - - ostream &operator<<(void *p) - { - fprintf(_stream, "%p", p); - return *this; - } - - ostream &operator<<(ostream &(*func)(ostream &)) - { - return func(*this); - } - - ostream &flush() - { - fflush(_stream); - return *this; - } - }; - - class istream{ - private: - FILE* _stream; - public: - istream(FILE *f) : _stream(f) {} - - istream& operator>>(int n) - { - fscanf(_stream, "%d", n); - return *this; - } - - istream &operator>>(double &f) - { - fscanf(_stream, "%lf", &f); - return *this; - } - - istream &operator>>(char *str) - { - fscanf(_stream, "%s", str); - return *this; - } - - istream &operator>>(char &c) - { - char tmp[2]; - fscanf(_stream, " %c", tmp); - c = tmp[0]; - return *this; - } - }; - - inline ostream &endl(ostream &os) - { - os << '\n'; - os.flush(); - return os; - } -} - -#endif \ No newline at end of file diff --git a/include/ulib/limits.h b/include/ulib/limits.h deleted file mode 100644 index b317e6a..0000000 --- a/include/ulib/limits.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#pragma once - -#define CHAR_BIT 8 -#define SCHAR_MIN (-128) -#define SCHAR_MAX 127 -#define UCHAR_MAX 255 - -#define SHRT_MIN (-32768) -#define SHRT_MAX 32767 -#define USHRT_MAX 65535 - -#define INT_MIN (-2147483648) -#define INT_MAX 2147483647 -#define UINT_MAX 4294967295U - -#define LONG_MAX 9223372036854775807L -#define LONG_MIN (-LONG_MAX - 1L) -#define ULONG_MAX 18446744073709551615UL - -#define LLONG_MAX 9223372036854775807LL -#define LLONG_MIN (-LLONG_MAX - 1LL) -#define ULLONG_MAX 18446744073709551615ULL \ No newline at end of file diff --git a/include/ulib/stb_sprintf.h b/include/ulib/stb_sprintf.h deleted file mode 100644 index 00dc123..0000000 --- a/include/ulib/stb_sprintf.h +++ /dev/null @@ -1,1916 +0,0 @@ -// stb_sprintf - v1.10 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// github:aganm (attribute format) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -==== -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -=============== -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -============ -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -======= -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) - #if defined(__has_feature) && defined(__has_attribute) - #if __has_feature(address_sanitizer) - #if __has_attribute(__no_sanitize__) - #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) - #elif __has_attribute(__no_sanitize_address__) - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #elif __has_attribute(__no_address_safety_analysis__) - #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) - #endif - #endif - #endif -#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#if defined(__has_attribute) - #if __has_attribute(format) - #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) - #endif -#endif - -#ifndef STBSP__ATTRIBUTE_FORMAT -#define STBSP__ATTRIBUTE_FORMAT(fmt,va) -#endif - -#ifdef _MSC_VER -#define STBSP__NOTUSED(v) (void)(v) -#else -#define STBSP__NOTUSED(v) (void)sizeof(v) -#endif - -#include // for va_arg(), va_list() -#include // size_t, ptrdiff_t - -#ifndef stbsp__uintptr -#if defined(__riscv) && (__riscv_xlen == 64) -#define stbsp__uintptr unsigned long long -#elif defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr unsigned long long -#else -#define stbsp__uintptr unsigned int -#endif -#endif - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); - -#endif // STB_SPRINTF_H_INCLUDE - -#ifdef STB_SPRINTF_IMPLEMENTATION - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) -{ - char const * sn = s; - - // get up to 4-byte alignment - for (;;) { - if (((stbsp__uintptr)sn & 3) == 0) - break; - - if (!limit || *sn == 0) - return (stbsp__uint32)(sn - s); - - ++sn; - --limit; - } - - // scan over 4 bytes at a time to find terminating 0 - // this will intentionally scan up to 3 bytes past the end of buffers, - // but becase it works 4B aligned, it will never cross page boundaries - // (hence the STBSP__ASAN markup; the over-read here is intentional - // and harmless) - while (limit >= 4) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - // bit hack to find if there's a 0 byte in there - if ((v - 0x01010101) & (~v) & 0x80808080UL) - break; - - sn += 4; - limit -= 4; - } - - // handle the last few characters to find actual size - while (limit && *sn) { - ++sn; - --limit; - } - - return (stbsp__uint32)(sn - s); -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff - #define stbsp__chk_cb_bufL(bytes) \ - { \ - int len = (int)(bf - buf); \ - if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ - tlen += len; \ - if (0 == (bf = buf = callback(buf, user, len))) \ - goto done; \ - } \ - } - #define stbsp__chk_cb_buf(bytes) \ - { \ - if (callback) { \ - stbsp__chk_cb_bufL(bytes); \ - } \ - } - #define stbsp__flush_cb() \ - { \ - stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ - } // flush if there is even one byte in the buffer - #define stbsp__cb_buf_clamp(cl, v) \ - cl = v; \ - if (callback) { \ - int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ - if (cl > lg) \ - cl = lg; \ - } - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; - #ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else - #endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { - #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length, limited to desired precision - // always limit to ~0u chars since our counts are 32b - l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - cs = 0; - STBSP__NOTUSED(dp); - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); -// add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = 0; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } -endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - -done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ - { \ - int cn; \ - for (cn = 0; cn < 8; cn++) \ - ((char *)&dest)[cn] = ((char *)&src)[cn]; \ - } - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ - { \ - double ahi = 0, alo, bhi = 0, blo; \ - stbsp__int64 bt; \ - oh = xh * yh; \ - STBSP__COPYFP(bt, xh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(ahi, bt); \ - alo = xh - ahi; \ - STBSP__COPYFP(bt, yh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(bhi, bt); \ - blo = yh - bhi; \ - ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ - } - -#define stbsp__ddtoS64(ob, xh, xl) \ - { \ - double ahi = 0, alo, vh, t; \ - ob = (stbsp__int64)xh; \ - vh = (double)ob; \ - ahi = (xh - vh); \ - t = (ahi - xh); \ - alo = (xh - (ahi - t)) - (vh + t); \ - ob += (stbsp__int64)(ahi + alo + xl); \ - } - -#define stbsp__ddrenorm(oh, ol) \ - { \ - double s; \ - s = oh + ol; \ - ol = ol - (s - oh); \ - oh = s; \ - } - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/include/ulib/stdio.h b/include/ulib/stdio.h deleted file mode 100644 index a6df21d..0000000 --- a/include/ulib/stdio.h +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -/* Standard I/O Library for Lume OS */ - -#ifndef _STDIO_H -#define _STDIO_H - -#include -#include "ulib/types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/*Type Definition (Adjusted According to Architecture)*/ -#ifndef NULL -#define NULL ((void *)0) -#endif - -/*Macro definition*/ -#define EOF (-1) - -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 - -#define BUFSIZ 1024 - - -/* FILE structure */ -typedef struct -{ - int fd; - int flags; - int error; - int eof; - - char buffer[BUFSIZ]; - - int buf_write_pos; - - int buf_read_pos; - int buf_read_cnt; - - int buf_mode; -} FILE; - -/* standard stream object */ -extern FILE *stdin; -extern FILE *stdout; -extern FILE *stderr; - -/* Function Declaration */ - -/* File Operations */ -FILE *fopen(const char *filename, const char *mode); -int fclose(FILE *stream); -int fflush(FILE *stream); // Not implement - -/* Formatted input and output */ -int printf(const char *format, ...); -int scanf(const char *format, ...); -int sprintf(char *str, const char *format, ...); -int snprintf(char *str, uint64 size, const char *format, ...); -int vsnprintf(char *str, uint64 size, const char *format, va_list ap); -int sscanf(const char *str, const char *format, ...); - -int fprintf(FILE *stream, const char *format, ...); -int fscanf(FILE *stream, const char *format, ...); - -/* Character/String Read and Write */ -int fputc(int c, FILE *stream); -int fputs(const char *s, FILE *stream); -int fgetc(FILE *stream); -char *fgets(char *str, int n, FILE *stream); -int ungetc(int c, FILE *stream); - -/* Common Macros/Wrapper Functions */ -#define getchar() fgetc(stdin) -#define putchar(c) fputc((c), stdout) - -/* Binary read and write */ -uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream); -uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream); - -/* File location */ -int fseek(FILE *stream, long offset, int whence); -long ftell(FILE *stream); -void rewind(FILE *stream); - -/* Status Inquiry */ -int feof(FILE *stream); -int ferror(FILE *stream); -void clearerr(FILE *stream); - -#ifdef __cplusplus -} -#endif -#endif /* _STDIO_H */ \ No newline at end of file diff --git a/include/ulib/stdlib.h b/include/ulib/stdlib.h deleted file mode 100644 index 6d8f75d..0000000 --- a/include/ulib/stdlib.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _STDLIB_H -#define _STDLIB_H - -#include "ulib/types.h" -#include "ulib/limits.h" -#include "ulib/errno.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -#ifndef NULL -#define NULL ((void *)0) -#endif - -#define ERANGE 34 /* Math result not representable */ -#define ENOMEM 12 /* Out of memory */ - -#define EXIT_SUCCESS 0 -#define EXIT_FAILURE 1 -#define RAND_MAX 32767 -#define MB_CUR_MAX 1 - -typedef struct -{ - int quot; - int rem; -} div_t; -typedef struct -{ - long quot; - long rem; -} ldiv_t; -typedef struct -{ - long long quot; - long long rem; -} lldiv_t; - -typedef int (*__compar_fn_t)(const void *, const void *); - -/* Memory Management */ -void *malloc(size_t size); -void free(void *ptr); -void *calloc(size_t nmemb, size_t size); -void *realloc(void *ptr, size_t size); - -/* Process Control */ -int exit(int status); -void abort(void); -int atexit(void (*function)(void)); -char *getenv(const char *name); -int system(const char *command); - -/* String Conversion */ -int atoi(const char *nptr); -long atol(const char *nptr); -long long atoll(const char *nptr); -double atof(const char *nptr); - -long strtol(const char *nptr, char **endptr, int base); -unsigned long strtoul(const char *nptr, char **endptr, int base); -long long strtoll(const char *nptr, char **endptr, int base); -unsigned long long strtoull(const char *nptr, char **endptr, int base); - -double strtod(const char *nptr, char **endptr); -float strtof(const char *nptr, char **endptr); - -/* Search & Sort*/ -void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); -void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); - -/* Math & Mics */ -int abs(int j); -long labs(long j); -long long llabs(long long j); -div_t div(int numer, int denom); -ldiv_t ldiv(long numer, long denom); -lldiv_t lldiv(long long numer, long long denom); - -int rand(void); -void srand(unsigned int seed); - -int mblen(const char *s, size_t n); -int mbtowc(wchar_t *pwc, const char *s, size_t n); -int wctomb(char *s, wchar_t wchar); -size_t mbstowcs(wchar_t *dest, const char *src, size_t n); -size_t wcstombs(char *dest, const wchar_t *src, size_t n); - -#ifdef __cplusplus -} -#endif - -#endif /* _STDLIB_H */ \ No newline at end of file diff --git a/include/ulib/string.h b/include/ulib/string.h deleted file mode 100644 index c7519ea..0000000 --- a/include/ulib/string.h +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#ifndef _STRING_H -#define _STRING_H - -#include "ulib/types.h" -#include "ulib/errno.h" - -#ifndef _SIZE_T -#define _SIZE_T -typedef unsigned long long size_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - - /* Memory Manipulation*/ - void *memcpy(void *dest, const void *src, size_t n); - - void *memmove(void *dest, const void *src, size_t n); - - void *memset(void *dest, int c, size_t n); - - int memcmp(const void *s1, const void *s2, size_t n); - - void *memchr(const void *s, int c, size_t n); - - /* String Manipulation */ - - size_t strlen(const char *s); - - char *strcpy(char *dest, const char *src); - - char *strncpy(char *dest, const char *src, size_t n); - - char *strcat(char *dest, const char *src); - - char *strncat(char *dest, const char *src, size_t n); - - int strcmp(const char *s1, const char *s2); - - int strncmp(const char *s1, const char *s2, size_t n); - - char *strchr(const char *s, int c); - - char *strrchr(const char *s, int c); - - char *strstr(const char *haystack, const char *needle); - - char *strdup(const char *s); - - size_t strlcpy(char *dest, const char *src, size_t size); - - size_t strlcat(char *dest, const char *src, size_t size); - - size_t strspn(const char *s, const char *accept); - - size_t strcspn(const char *s, const char *reject); - - char *strpbrk(const char *s, const char *accept); - - char *strtok(char *str, const char *delim); - - char *strerror(int errnum); - -#ifdef __cplusplus -} -#endif - -#endif /* _STRING_H */ \ No newline at end of file diff --git a/include/ulib/types.h b/include/ulib/types.h deleted file mode 100644 index 69ee233..0000000 --- a/include/ulib/types.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#ifndef _ULIB_TYPES_H -#define _ULIB_TYPES_H - -#include "common/types.h" - -#ifndef _ULIB_ALIASES -#define _ULIB_ALIASES -typedef unsigned int uint; -typedef unsigned short ushort; -typedef unsigned char uchar; -#endif - -#ifndef NULL -#define NULL ((void *)0) -#endif - -#ifndef __cplusplus -typedef int wchar_t; -#endif - -#endif \ No newline at end of file diff --git a/include/user/user.h b/include/user/user.h deleted file mode 100644 index 7af8bdd..0000000 --- a/include/user/user.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ -#pragma once - -typedef unsigned long long uint64; - -#define AT_FDCWD -100 - -#ifdef __cplusplus -extern "C" -{ -#endif - - // Raw Linux System Calls - int sys_openat(int dfd, const char *filename, int flags, int mode); - int sys_mknodat(int dfd, const char *filename, int mode, int dev); - int sys_mkdirat(int dfd, const char *path, int mode); - int sys_unlinkat(int dfd, const char *path, int flags); - int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); - int sys_chdir(const char *path); - int sys_dup(int oldfd); - int sys_fstat(int fd, void *statbuf); - int sys_read(int fd, void *buf, int count); - int sys_write(int fd, const void *buf, int count); - int sys_close(int fd); - int sys_pipe2(int pipefd[2], int flags); - int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); - int sys_execve(const char *path, char *const argv[], char *const envp[]); - int sys_wait4(int pid, int *status, int options, void *rusage); - int sys_exit(int status); - int sys_getpid(); - int sys_brk(int n); - int sys_kill(int pid, int sig); - int sys_nanosleep(void *req, void *rem); - int sys_reboot(int magic1, int magic2, int cmd, void *arg); - int sys_lseek(int fd, int offset, int whence); - - // Custom - void sys_putc(char c); - int sys_disk_test(); - - // POSIX Wrappers - int fork(); - int exit(int status); - int wait(uint64 status_addr); - int pipe(int p[]); - int write(int fd, const void *buf, int n); - int read(int fd, void *buf, int n); - int close(int fd); - int kill(int pid); - int exec(char *, char **); - int open(const char *, int); - int mknod(const char *, short, short); - int unlink(const char *); - int link(const char *, const char *); - int fstat(int fd, void *st); - int mkdir(const char *); - int chdir(const char *); - int dup(int fd); - int getpid(); - char *sbrk(int n); - int sleep(int ticks); - int uptime(); - int shutdown(); - int lseek(int fd, int offset, int whence); - - // Debug - void putc(char c); - int disk_test(); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/kernel/exec.cc b/kernel/exec.cc new file mode 100644 index 0000000..616c0da --- /dev/null +++ b/kernel/exec.cc @@ -0,0 +1,659 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * exec.cc — ELF64 binary loader and execve implementation + * + * Loads a static ELF64 executable into a fresh address space, + * constructs the user stack (argc/argv/envp/auxv), and redirects + * the current task's trapframe to begin executing at the ELF entry. + * + * Reference: Linux fs/binfmt_elf.c, fs/exec.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Assembly entry points (trap_vector.S) */ +extern "C" void trap_vector(); +extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); +extern "C" void userret(arch::TrapFrame* tf); + +/* ================================================================== */ +/* Constants */ +/* ================================================================== */ + +/* User stack layout comes from mm_layout.h (MI layer). + * kUserStackTop, kUserStackBase, kUserStackSize are defined there, + * derived from kUserVaLimit so they automatically adapt to the + * page table mode (SV39/SV48). */ + +/* ================================================================== */ +/* Helper: kernel_read — read from a file into a kernel buffer */ +/* ================================================================== */ + +static int64 kernel_read(file* fp, void* buf, uint64 count, int64 offset) +{ + int64 pos = offset; + if (!fp->f_op || !fp->f_op->read) return -EINVAL; + return fp->f_op->read(fp, static_cast(buf), count, &pos); +} + +/* ================================================================== */ +/* Helper: copy data to a user page via kernel direct map */ +/* ================================================================== */ + +static void memcpy_kernel(void* dst, const void* src, uint64 n) +{ + auto* d = static_cast(dst); + auto* s = static_cast(src); + for (uint64 i = 0; i < n; i++) + d[i] = s[i]; +} + +static void memset_kernel(void* dst, uint8 val, uint64 n) +{ + auto* d = static_cast(dst); + for (uint64 i = 0; i < n; i++) + d[i] = val; +} + +static uint64 strlen_kernel(const char* s) +{ + uint64 n = 0; + while (s[n]) n++; + return n; +} + +/* ================================================================== */ +/* Helper: allocate and map a page at a specific user VA */ +/* ================================================================== */ + +static int map_page_at(VmSpace* mm, uint64 va, uint64 pte_perm) +{ + Page* pg = pmm_alloc_page(); + if (!pg) return -ENOMEM; + + uint64 pa = page_to_pa(pg).raw; + + /* Zero the page */ + void* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + memset_kernel(kva, 0, kPageSize); + + int ret = pmap::map(mm->root_pa, va, pa, pte_perm); + if (ret != 0) { + pmm_free_page(pg); + return -ENOMEM; + } + page_inc_mapcount(pg); + return 0; +} + +/* ================================================================== */ +/* load_elf_binary — parse ELF and map segments */ +/* ================================================================== */ + +struct elf_load_info { + uint64 entry; /* ELF entry point (after base offset) */ + uint64 phdr_addr; /* User VA where phdr was loaded */ + uint16 phnum; /* Number of program headers */ + uint16 phentsize; /* Size of each phdr */ + uint64 brk_start; /* End of highest PT_LOAD segment (for brk) */ + uint64 load_bias; /* PIE load bias (0 for ET_EXEC) */ +}; + +static int load_elf_binary(file* fp, VmSpace* mm, elf_load_info* info) +{ + /* Read ELF header */ + Elf64_Ehdr ehdr{}; + int64 n = kernel_read(fp, &ehdr, sizeof(ehdr), 0); + if (n < static_cast(sizeof(ehdr))) { + printk(KERN_ERR "exec: failed to read ELF header (got %lld)\n", n); + return -ENOEXEC; + } + + /* Validate magic */ + if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || + ehdr.e_ident[EI_MAG1] != ELFMAG1 || + ehdr.e_ident[EI_MAG2] != ELFMAG2 || + ehdr.e_ident[EI_MAG3] != ELFMAG3) { + printk(KERN_ERR "exec: bad ELF magic\n"); + return -ENOEXEC; + } + + /* Validate class (64-bit) and endianness (little-endian) */ + if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) return -ENOEXEC; + if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) return -ENOEXEC; + + /* Validate type and machine */ + if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) return -ENOEXEC; + if (ehdr.e_machine != EM_RISCV) return -ENOEXEC; + + if (ehdr.e_phnum == 0 || ehdr.e_phentsize < sizeof(Elf64_Phdr)) + return -ENOEXEC; + + /* Read program headers */ + uint64 phdr_total = static_cast(ehdr.e_phnum) * ehdr.e_phentsize; + if (phdr_total > kPageSize) return -ENOEXEC; /* Sanity limit */ + + /* Allocate kernel buffer for program headers */ + uint8 phdr_buf[512]; /* Max ~8 phdrs * 56 bytes each */ + if (phdr_total > sizeof(phdr_buf)) return -ENOEXEC; + + n = kernel_read(fp, phdr_buf, phdr_total, static_cast(ehdr.e_phoff)); + if (n < static_cast(phdr_total)) return -ENOEXEC; + + info->phnum = ehdr.e_phnum; + info->phentsize = ehdr.e_phentsize; + info->phdr_addr = 0; + info->brk_start = 0; + info->load_bias = 0; + + /* + * PIE (ET_DYN): segments have p_vaddr starting near 0. + * We must choose a load base and add it to all addresses. + * Linux uses ELF_ET_DYN_BASE (~2/3 of TASK_SIZE); we use 0x400000. + */ + constexpr uint64 kPieLoadBase = 0x400000; + uint64 load_bias = (ehdr.e_type == ET_DYN) ? kPieLoadBase : 0; + info->load_bias = load_bias; + info->entry = ehdr.e_entry + load_bias; + + /* Process each PT_LOAD segment */ + for (uint16 i = 0; i < ehdr.e_phnum; i++) { + auto* phdr = reinterpret_cast( + phdr_buf + i * ehdr.e_phentsize); + + if (phdr->p_type == PT_PHDR) { + /* Remember where program headers are mapped */ + info->phdr_addr = phdr->p_vaddr + load_bias; + continue; + } + + if (phdr->p_type != PT_LOAD) continue; + if (phdr->p_memsz == 0) continue; + + /* Compute permissions */ + uint64 perm = pmap::PTE_V | pmap::PTE_U; + if (phdr->p_flags & PF_R) perm |= pmap::PTE_R; + if (phdr->p_flags & PF_W) perm |= pmap::PTE_W; + if (phdr->p_flags & PF_X) perm |= pmap::PTE_X; + + /* Page-align the segment boundaries (apply load bias for PIE) */ + uint64 seg_start = page_align_down(phdr->p_vaddr + load_bias); + uint64 seg_end = page_align_up(phdr->p_vaddr + phdr->p_memsz + load_bias); + + /* Create VMA for this segment */ + uint64 vm_perm = VM_USER; + if (phdr->p_flags & PF_R) vm_perm |= VM_READ; + if (phdr->p_flags & PF_W) vm_perm |= VM_WRITE; + if (phdr->p_flags & PF_X) vm_perm |= VM_EXEC; + + int err = vmm_map_user(mm, seg_start, seg_end - seg_start, vm_perm); + if (err) { + printk(KERN_ERR "exec: vmm_map_user failed for segment %d: %d\n", + i, err); + return err; + } + + /* Map and fill pages */ + for (uint64 va = seg_start; va < seg_end; va += kPageSize) { + err = map_page_at(mm, va, perm); + if (err) return err; + + /* Determine how much file data goes into this page */ + uint64 file_seg_start = phdr->p_vaddr + load_bias; + uint64 file_seg_end = phdr->p_vaddr + phdr->p_filesz + load_bias; + + uint64 page_start = va; + uint64 page_end = va + kPageSize; + + /* Overlap between [page_start, page_end) and [file_seg_start, file_seg_end) */ + if (page_start < file_seg_end && page_end > file_seg_start) { + uint64 copy_start = (page_start > file_seg_start) ? page_start : file_seg_start; + uint64 copy_end = (page_end < file_seg_end) ? page_end : file_seg_end; + uint64 copy_len = copy_end - copy_start; + + /* File offset for this data */ + uint64 file_off = phdr->p_offset + (copy_start - phdr->p_vaddr - load_bias); + + /* Get kernel VA of the mapped page */ + uint64 pa; + if (!pmap::lookup(mm->root_pa, va, &pa)) return -EFAULT; + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + + /* Read file data into the page */ + uint64 page_offset = copy_start - va; + n = kernel_read(fp, kva + page_offset, copy_len, + static_cast(file_off)); + if (n < static_cast(copy_len)) { + printk(KERN_ERR "exec: short read at offset %llu\n", file_off); + return -EIO; + } + } + /* BSS region (memsz > filesz) is already zeroed by map_page_at */ + } + + /* Track highest loaded address for brk */ + if (seg_end > info->brk_start) + info->brk_start = seg_end; + + /* Detect which PT_LOAD contains the program header table. + * Linux: fs/binfmt_elf.c line ~1209 */ + if (info->phdr_addr == 0 && + phdr->p_offset <= ehdr.e_phoff && + ehdr.e_phoff < phdr->p_offset + phdr->p_filesz) { + info->phdr_addr = ehdr.e_phoff - phdr->p_offset + + phdr->p_vaddr + load_bias; + } + } + + /* phdr_addr should have been set by the PT_LOAD loop above + * or by a PT_PHDR entry. If still zero, something is wrong. */ + if (info->phdr_addr == 0) { + printk(KERN_ERR "exec: unable to determine AT_PHDR address\n"); + return -ENOEXEC; + } + + return 0; +} + +/* ================================================================== */ +/* setup_user_stack — build argc/argv/envp/auxv on the user stack */ +/* ================================================================== */ + +static int setup_user_stack(VmSpace* mm, const char* const* argv, + const char* const* envp, + const elf_load_info* info, + uint64* out_sp) +{ + /* Create stack VMA */ + uint64 vm_perm = VM_USER | VM_READ | VM_WRITE; + int err = vmm_map_user(mm, kUserStackBase, kUserStackSize, vm_perm); + if (err) return err; + + /* We only need the top few pages for initial stack content. + * Map 2 pages at the top for argc/argv/envp/auxv. */ + uint64 perm = pmap::PTE_V | pmap::PTE_U | pmap::PTE_R | pmap::PTE_W; + for (uint64 va = kUserStackTop - 2 * kPageSize; va < kUserStackTop; va += kPageSize) { + err = map_page_at(mm, va, perm); + if (err) return err; + } + + /* Count arguments and environment */ + int argc = 0; + int envc = 0; + if (argv) { while (argv[argc]) argc++; } + if (envp) { while (envp[envc]) envc++; } + + /* + * Build the stack from top down using kernel VA access. + * Layout (top to bottom): + * 1. String data (argv strings, envp strings) + * 2. Padding (16-byte align) + * 3. auxv[] + * 4. NULL (end of envp) + * 5. envp[] pointers + * 6. NULL (end of argv) + * 7. argv[] pointers + * 8. argc + */ + + /* First pass: calculate total string space */ + uint64 str_total = 0; + for (int i = 0; i < argc; i++) + str_total += strlen_kernel(argv[i]) + 1; + for (int i = 0; i < envc; i++) + str_total += strlen_kernel(envp[i]) + 1; + + /* String area starts at top of stack */ + uint64 str_pos = kUserStackTop - str_total; + str_pos &= ~15ULL; /* 16-byte align */ + + /* Copy strings and record user addresses */ + uint64 argv_addrs[32]; /* Max 32 args */ + uint64 envp_addrs[32]; + if (argc > 32) argc = 32; + if (envc > 32) envc = 32; + + uint64 cur = str_pos; + for (int i = 0; i < argc; i++) { + uint64 len = strlen_kernel(argv[i]) + 1; + argv_addrs[i] = cur; + + /* Copy string bytes to user stack via kernel VA */ + uint64 pa; + if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa)) + return -EFAULT; + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + uint64 off = cur - page_align_down(cur); + memcpy_kernel(kva + off, argv[i], len); + cur += len; + } + for (int i = 0; i < envc; i++) { + uint64 len = strlen_kernel(envp[i]) + 1; + envp_addrs[i] = cur; + + uint64 pa; + if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa)) + return -EFAULT; + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + uint64 off = cur - page_align_down(cur); + memcpy_kernel(kva + off, envp[i], len); + cur += len; + } + + /* Build auxv, envp[], argv[], argc below the strings */ + /* Count entries: argc + argv_ptrs + NULL + envp_ptrs + NULL + auxv (6 pairs + terminator) */ + int auxv_count = 8; /* AT_PHDR, AT_PHENT, AT_PHNUM, AT_PAGESZ, AT_BASE, AT_ENTRY, AT_RANDOM, AT_NULL */ + uint64 table_size = sizeof(uint64) * (1 + argc + 1 + envc + 1 + auxv_count * 2); + uint64 table_start = str_pos - table_size; + table_start &= ~15ULL; + + /* Write the table entries. + * We need to handle page boundaries, but for simplicity we assume + * the table fits in the already-mapped pages. */ + uint64 sp = table_start; + + /* Helper: write a uint64 to user stack */ + auto write_u64 = [&](uint64 addr, uint64 val) -> int { + uint64 pa; + if (!pmap::lookup(mm->root_pa, page_align_down(addr), &pa)) + return -EFAULT; + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + uint64 off = addr - page_align_down(addr); + *reinterpret_cast(kva + off) = val; + return 0; + }; + + /* argc */ + err = write_u64(sp, static_cast(argc)); + if (err) return err; + sp += 8; + + /* argv[] */ + for (int i = 0; i < argc; i++) { + err = write_u64(sp, argv_addrs[i]); + if (err) return err; + sp += 8; + } + err = write_u64(sp, 0); /* NULL terminator */ + if (err) return err; + sp += 8; + + /* envp[] */ + for (int i = 0; i < envc; i++) { + err = write_u64(sp, envp_addrs[i]); + if (err) return err; + sp += 8; + } + err = write_u64(sp, 0); /* NULL terminator */ + if (err) return err; + sp += 8; + + /* auxv[] */ + auto write_auxv = [&](uint64 type, uint64 val) -> int { + int r = write_u64(sp, type); + if (r) return r; + sp += 8; + r = write_u64(sp, val); + if (r) return r; + sp += 8; + return 0; + }; + + write_auxv(AT_PHDR, info->phdr_addr); + write_auxv(AT_PHENT, info->phentsize); + write_auxv(AT_PHNUM, info->phnum); + write_auxv(AT_PAGESZ, kPageSize); + write_auxv(AT_BASE, info->load_bias); + write_auxv(AT_ENTRY, info->entry); + write_auxv(AT_RANDOM, str_pos - 16); /* Point to 16 bytes of zeros for now */ + write_auxv(AT_NULL, 0); + + *out_sp = table_start; + return 0; +} + +/* ================================================================== */ +/* do_execve — main entry point */ +/* ================================================================== */ + +int do_execve(const char* pathname, const char* const* argv, + const char* const* envp) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + /* 1. Look up the file */ + path p{}; + int err = filename_lookup(AT_FDCWD, pathname, 0, &p); + if (err) { + printk(KERN_ERR "exec: file not found: %s (err=%d)\n", pathname, err); + return err; + } + + inode* ip = p.de->d_inode; + if (!ip || !S_ISREG(ip->i_mode)) { + printk(KERN_ERR "exec: not a regular file: %s\n", pathname); + return -EACCES; + } + + /* Open a kernel file handle */ + file* fp = alloc_empty_file(O_RDONLY); + if (!fp) { + return -ENOMEM; + } + fp->f_inode = ip; + fp->f_path = p; + fp->f_op = ip->i_fop; + fp->f_pos = 0; + + if (fp->f_op && fp->f_op->open) { + err = fp->f_op->open(ip, fp); + if (err) { + fput(fp); + return err; + } + } + + /* 2. Create new address space */ + VmSpace* new_mm = VmSpace::create(); + if (!new_mm) { + fput(fp); + return -ENOMEM; + } + + /* 3. Load ELF segments */ + elf_load_info info{}; + err = load_elf_binary(fp, new_mm, &info); + if (err) { + printk(KERN_ERR "exec: load_elf_binary failed: %d\n", err); + new_mm->put(); + fput(fp); + dput(p.de); + return err; + } + + /* 4. Set up user stack */ + uint64 user_sp = 0; + err = setup_user_stack(new_mm, argv, envp, &info, &user_sp); + if (err) { + printk(KERN_ERR "exec: setup_user_stack failed: %d\n", err); + new_mm->put(); + fput(fp); + return err; + } + + /* Set brk */ + new_mm->brk_start = page_align_up(info.brk_start); + new_mm->brk_end = new_mm->brk_start; + + /* Set mmap base — Linux: arch_pick_mmap_layout() sets mm->mmap_base + * to TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3). + * This ensures mmap(NULL) returns addresses well above the ELF + * text/data segments. */ + new_mm->mmap_base = page_align_up(kUserVaLimit / 3); + + fput(fp); + + /* =============================================================== */ + /* Point of no return — replace the current process */ + /* =============================================================== */ + + /* 5a. Replace address space */ + VmSpace* old_mm = curr->mm; + curr->mm = new_mm; + pmap::activate(new_mm->root_pa); + if (old_mm) old_mm->put(); + + /* 5a'. Transition from kernel thread to user process. + * Linux: begin_new_exec() in fs/exec.c: + * current->flags &= ~PF_KTHREAD; (line 1173) + * tsk->exit_signal = SIGCHLD; (line 1021) + * Without this, a kernel_execve'd task stays marked as a kthread + * and would be auto-reaped (exit_signal=-1) instead of becoming + * a proper zombie for parent wait(). */ + curr->flags &= ~TF_KTHREAD; + curr->exit_signal = SIGCHLD; + + /* 5b. Reset signal handlers to SIG_DFL (POSIX requirement). + * + * Linux fs/exec.c:flush_signal_handlers() runs under sighand->siglock. + * Even though after fork our sighand is no longer shared with the + * parent (we copy in task_clone), Linux still locks here — and so + * must we, because the same sighand may be shared with CLONE_SIGHAND + * sibling threads of the execing task. Holding siglock here also + * keeps the per-task blocked mask reset atomic w.r.t. concurrent + * sigprocmask / signal delivery on this very task. */ + if (curr->sighand) { + LockGuard guard(curr->sighand->siglock); + for (int i = 0; i < _NSIG; i++) { + if (curr->sighand->action[i].sa_handler != SIG_IGN) + curr->sighand->action[i].sa_handler = SIG_DFL; + curr->sighand->action[i].sa_flags = 0; + curr->sighand->action[i].sa_mask.clear(); + } + curr->blocked.clear(); + } else { + curr->blocked.clear(); + } + + /* 5c. Close O_CLOEXEC file descriptors */ + /* TODO: implement close-on-exec */ + + /* 6. Set up trapframe to enter user space */ + arch::TrapFrame* tf = curr->trapframe; + if (!tf) { + printk(KERN_ERR "exec: no trapframe!\n"); + task_kill_current(SIGKILL); + return -EINVAL; /* not reached */ + } + + /* Initialize trapframe for user-mode return. + * This is the MI equivalent of Linux's start_thread() + kernel + * metadata setup. All arch-specific details (sstatus, FP state, + * kernel_satp, kernel_sp, trap handler address, etc.) are handled + * by the MD layer. MI exec never touches arch registers. */ + arch::trapframe_init_user(tf, info.entry, user_sp); + + pr_debug_s(KLOG_SUBSYS_EXEC, + "exec: pid %d entry=%llx sp=%llx phdr=%llx\n", + curr->pid, info.entry, user_sp, info.phdr_addr); + + /* Verify loaded data at entry point — debug-level, ring buffer only. */ + { + uint64 pa; + if (pmap::lookup(curr->mm->root_pa, page_align_down(info.entry), &pa)) { + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + uint64 off = info.entry - page_align_down(info.entry); + pr_debug_s(KLOG_SUBSYS_EXEC, + "exec: entry bytes @%llx: %02x %02x %02x %02x %02x %02x %02x %02x\n", + info.entry, + kva[off+0], kva[off+1], kva[off+2], kva[off+3], + kva[off+4], kva[off+5], kva[off+6], kva[off+7]); + } + for (uint64 va = 0x10000; va < 0x14000; va += kPageSize) { + if (pmap::lookup(curr->mm->root_pa, va, &pa)) { + uint8* kva2 = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + pr_debug_s(KLOG_SUBSYS_EXEC, + "exec: page %llx: %02x%02x%02x%02x %02x%02x%02x%02x\n", + va, kva2[0], kva2[1], kva2[2], kva2[3], + kva2[4], kva2[5], kva2[6], kva2[7]); + } else { + pr_debug_s(KLOG_SUBSYS_EXEC, + "exec: page %llx: NOT MAPPED\n", va); + } + } + } + + /* Flush instruction cache after loading executable code into memory. + * Without this, the I-cache may contain stale data for the pages + * we just loaded, causing the CPU to execute garbage. */ + arch::mmu::flush_icache(); + + return 0; /* Will return to user mode via trap return */ +} + +/* ================================================================== */ +/* sys_execve_wrapper — syscall entry point */ +/* ================================================================== */ + +int64 sys_execve_wrapper(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + const char* pathname = reinterpret_cast(a0); + const char* const* argv = reinterpret_cast(a1); + const char* const* envp = reinterpret_cast(a2); + + return do_execve(pathname, argv, envp); +} + +/* ================================================================== */ +/* kernel_execve — call execve from kernel context (e.g. init) */ +/* ================================================================== */ + +int kernel_execve(const char* pathname, const char* const* argv, + const char* const* envp) +{ + TaskControlBlock* curr = current_task(); + + int ret = do_execve(pathname, argv, envp); + if (ret != 0) + return ret; + + /* + * do_execve succeeded: the trapframe is set up with entry point, + * user stack, and sstatus (SPP=0 for U-mode). We must NOT return + * normally — that would go back through the kthread return path + * and jump to address 0. Instead, jump directly to user mode via + * the same userret() path that fork_child_entry uses. + * + * Switch stvec from kernelvec to trap_vector so that traps from + * U-mode are handled by the user trap entry path (which loads + * kernel context from the trapframe via sscratch). + */ + arch::trap::set_vector(reinterpret_cast(trap_vector)); + userret(curr->trapframe); + __builtin_unreachable(); +} diff --git a/kernel/irq.cc b/kernel/irq.cc new file mode 100644 index 0000000..4de468a --- /dev/null +++ b/kernel/irq.cc @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * IRQ Manager Implementation + * + * Lock-free dispatch path for interrupt context safety. + * Registration/unregistration is protected by atomic publish. + * + * Reference: docs/specs/trap.md §3.3 + */ + +#include +#include +#include + +/* Global singleton */ +IrqManager g_irq_manager; + +void IrqManager::init() { + for (uint32 i = 0; i < kMaxIrqs; i++) { + entries_[i].handler_ptr.store(0, __ATOMIC_RELAXED); + entries_[i].arg = nullptr; + } +} + +int IrqManager::register_handler(uint32 irq, IrqHandler handler, void *arg) { + if (irq >= kMaxIrqs) + return -EINVAL; + + if (!handler) + return -EINVAL; + + /* Check if already registered (CAS from 0 to handler) */ + uint64 expected = 0; + uint64 desired = reinterpret_cast(handler); + + /* Set arg first (before publishing handler) so dispatch sees valid arg */ + entries_[irq].arg = arg; + + if (!entries_[irq].handler_ptr.compare_exchange(expected, desired, + __ATOMIC_ACQ_REL)) { + /* Already registered by someone else */ + return -EBUSY; + } + + return 0; +} + +void IrqManager::unregister_handler(uint32 irq) { + if (irq >= kMaxIrqs) + return; + + /* Clear handler with release semantics */ + entries_[irq].handler_ptr.store(0, __ATOMIC_RELEASE); + entries_[irq].arg = nullptr; +} + +void IrqManager::dispatch(uint32 irq) { + if (irq >= kMaxIrqs) + return; + + /* Load handler with acquire semantics — pairs with register's release */ + uint64 h = entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE); + if (h == 0) { + /* Spurious or unregistered IRQ — silently ignore */ + return; + } + + auto handler = reinterpret_cast(h); + handler(irq, entries_[irq].arg); +} + +bool IrqManager::has_handler(uint32 irq) const { + if (irq >= kMaxIrqs) + return false; + return entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE) != 0; +} + +void IrqManager::set_chip(const IrqChip* chip) { + chip_ = chip; +} + +uint32 IrqManager::claim() const { + return chip_ ? chip_->claim() : 0; +} + +void IrqManager::complete(uint32 irq) const { + if (chip_) chip_->complete(irq); +} + +void IrqManager::init_ap() const { + if (chip_ && chip_->init_ap) chip_->init_ap(); +} + +void IrqManager::enable(uint32 irq) const { + if (chip_ && chip_->enable) chip_->enable(irq); +} + +void IrqManager::disable(uint32 irq) const { + if (chip_ && chip_->disable) chip_->disable(irq); +} + +void IrqManager::set_priority(uint32 irq, uint32 priority) const { + if (chip_ && chip_->set_priority) chip_->set_priority(irq, priority); +} diff --git a/kernel/main.cc b/kernel/main.cc new file mode 100644 index 0000000..db5843c --- /dev/null +++ b/kernel/main.cc @@ -0,0 +1,468 @@ +/* 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. + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 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. +// 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 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(); + +// AP per-CPU init +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(-100), /* AT_FDCWD */ + reinterpret_cast("/dev/console"), O_RDWR, 0, 0, 0); + if (fd0 < 0) { + printk(KERN_ERR "[init] open /dev/console failed: %d\n", + static_cast(fd0)); + } else { + sys_dup(static_cast(fd0), 0, 0, 0, 0, 0); /* fd 1 */ + sys_dup(static_cast(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. +// ============================================================ +static lume::atomic ap_boot_sync; + +// ============================================================ +// kernel_main — C++ entry point for all CPUs +// ============================================================ +extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { + + if (cpu_id == bsp_hart_id) { + // ======================================================== + // BSP: Absolute single-core period — no locks needed. + // APs are spinning on ap_boot_sync. + // ======================================================== + + uint64 fdt_pa = fdt_paddr; + + // ---- Phase 1: Console + Exception Safety Net ---- + + // 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=%llu\n", cpu_id); + printk(KERN_INFO "boot: higher-half kernel active\n"); + + /* 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(kernel_map.phys_addr), + static_cast(kernel_map.virt_addr), + static_cast(kernel_map.size)); + + // --- Subsystem init DAG (strict top-down order) --- + + // 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"); + + // 4. KASLR — seed PRNG and randomise VA layout. + // MUST happen before vmm_init() builds the kernel page table. + { + uint64 kaslr_seed = 0; + fdt_early_get_kaslr_seed(&kaslr_seed); + random_early_init(kaslr_seed); + arch::kaslr_init(); + } + + // Sort exception table before setting RO permissions in vmm_init() + extable_init(); + + // ---- 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"); + + // 6. Virtual memory manager — builds page table with KASLR'd bases + vmm_init(); + printk(KERN_INFO "init: vmm_init done\n"); + + // 7. Physical memory manager (Buddy) + pmm_init(); + printk(KERN_INFO "init: pmm_init done\n"); + + // 8. Slab allocator — operator new usable after this + slab_init(); + printk(KERN_INFO "init: slab_init done\n"); + + // 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"); + + // ---- Phase 3: Task & Scheduler (software state before hardware) ---- + + // 11. Task management (PID allocator + global task list) + task_init(); + printk(KERN_INFO "init: task_init done\n"); + + 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"); + + // 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"); + + // 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) + + // ---- 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(); + + // 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"); + + // Release APs with RELEASE semantics: + // 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(_start))).raw; + } + uint32 num_harts = 4; // TODO: read from FDT /cpus + for (uint32 h = 0; h < num_harts; h++) { + if (h == static_cast(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, which will pick up PID 1. + arch::cpu::intr_on(); + + printk(KERN_INFO "boot: BSP entering idle loop\n"); + + } else { + // ======================================================== + // 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); + + 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 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) + } + + // 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(); + } +} diff --git a/kernel/module.mk b/kernel/module.mk new file mode 100644 index 0000000..754a14c --- /dev/null +++ b/kernel/module.mk @@ -0,0 +1,26 @@ +# kernel/module.mk +SRCS_CC += kernel/main.cc +SRCS_CC += kernel/sync/spinlock.cc +SRCS_CC += kernel/sync/mutex.cc +SRCS_CC += kernel/trap.cc +SRCS_CC += kernel/irq.cc +SRCS_CC += kernel/sched_core.cc +SRCS_CC += kernel/sched_class_o1.cc +SRCS_CC += kernel/timer.cc +SRCS_CC += kernel/pid.cc +SRCS_CC += kernel/pgrp.cc +SRCS_CC += kernel/task.cc +SRCS_CC += kernel/syscall.cc +SRCS_CC += kernel/sys_process.cc +SRCS_CC += kernel/sys_mm.cc +SRCS_CC += kernel/sys_info.cc +SRCS_CC += kernel/sys_fs.cc +SRCS_CC += kernel/uaccess.cc +SRCS_CC += kernel/waitqueue.cc +SRCS_CC += kernel/signal.cc +SRCS_CC += kernel/sys_signal.cc +SRCS_CC += kernel/sys_misc.cc +SRCS_CC += kernel/exec.cc +SRCS_CC += kernel/poll.cc +SRCS_CC += kernel/ptrace.cc +SRCS_CC += kernel/smp.cc diff --git a/kernel/pgrp.cc b/kernel/pgrp.cc new file mode 100644 index 0000000..e5b382c --- /dev/null +++ b/kernel/pgrp.cc @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Process Group / Session Implementation + * + * Hash table of signal_struct objects keyed by pgid. Lifecycle of + * signal_struct mirrors Linux: one per thread group, refcounted across + * CLONE_THREAD siblings, freed when the last task in the group exits. + * + * Reference: Linux kernel/pid.c, kernel/exit.c + */ + +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* Hash table state */ +/* ================================================================== */ + +list_node g_pgrp_hash[kPgrpHashSize]; +Spinlock g_pgrp_lock; + +void pgrp_init() +{ + for (auto& bucket : g_pgrp_hash) bucket.init(); + g_pgrp_lock.init("pgrp"); +} + +/* ================================================================== */ +/* Internal helpers (must be called with g_pgrp_lock held) */ +/* ================================================================== */ + +static void pgrp_attach_locked(signal_struct* sig, int32 pgid) +{ + sig->pgid = pgid; + list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)]; + sig->pgrp_link.prev = bucket->prev; + sig->pgrp_link.next = bucket; + bucket->prev->next = &sig->pgrp_link; + bucket->prev = &sig->pgrp_link; +} + +static void pgrp_detach_locked(signal_struct* sig) +{ + /* Unlink only if currently on a list — init() makes prev/next point + * to self, which we treat as "not linked". */ + if (sig->pgrp_link.next == &sig->pgrp_link) return; + sig->pgrp_link.prev->next = sig->pgrp_link.next; + sig->pgrp_link.next->prev = sig->pgrp_link.prev; + sig->pgrp_link.init(); +} + +/* ================================================================== */ +/* signal_struct allocation / refcount */ +/* ================================================================== */ + +signal_struct* alloc_signal_struct(int32 leader_pid, TaskControlBlock* leader) +{ + auto* sig = new signal_struct; + if (!sig) return nullptr; + sig->init(leader_pid, leader); + + LockGuard guard(g_pgrp_lock); + pgrp_attach_locked(sig, leader_pid); + return sig; +} + +signal_struct* fork_signal_struct(signal_struct* parent, + TaskControlBlock* child_leader) +{ + if (!parent) return nullptr; + + auto* sig = new signal_struct; + if (!sig) return nullptr; + + /* Copy identity from parent, then override leader and attach. */ + sig->lock.init("signal"); + sig->refcount = 1; + sig->leader = child_leader; + sig->tty_old_pgrp = 0; + sig->pgrp_link.init(); + + { + LockGuard pg(parent->lock); + sig->pgid = parent->pgid; + sig->sid = parent->sid; + sig->tty = parent->tty; + } + + LockGuard guard(g_pgrp_lock); + pgrp_attach_locked(sig, sig->pgid); + return sig; +} + +void get_signal_struct(signal_struct* sig) +{ + if (!sig) return; + LockGuard guard(sig->lock); + sig->refcount++; +} + +void put_signal_struct(signal_struct* sig) +{ + if (!sig) return; + + uint32 remaining; + { + LockGuard guard(sig->lock); + remaining = --sig->refcount; + } + if (remaining != 0) return; + + /* Last reference — detach from pgrp hash and free. */ + { + LockGuard guard(g_pgrp_lock); + pgrp_detach_locked(sig); + } + delete sig; +} + +/* ================================================================== */ +/* Pgrp membership change */ +/* ================================================================== */ + +void change_pgid(signal_struct* sig, int32 new_pgid) +{ + if (!sig) return; + LockGuard guard(g_pgrp_lock); + if (sig->pgid == new_pgid) return; + pgrp_detach_locked(sig); + pgrp_attach_locked(sig, new_pgid); +} + +int pgrp_member_count(int32 pgid) +{ + LockGuard guard(g_pgrp_lock); + int n = 0; + list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)]; + for (list_node* node = bucket->next; node != bucket; node = node->next) { + auto* sig = list_entry(node); + if (sig->pgid == pgid) n++; + } + return n; +} diff --git a/kernel/pid.cc b/kernel/pid.cc new file mode 100644 index 0000000..04b846b --- /dev/null +++ b/kernel/pid.cc @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * PID Allocator — Bitmap-based O(1) block search + * Reference: docs/specs/task.md §3.2 + */ + +#include + +void PidAllocator::init() +{ + for (int i = 0; i < kBitmapWords; i++) + bitmap_[i] = 0; + next_hint_ = 1; + bitmap_[0] |= 0x1ULL; /* Reserve PID 0 (idle/swapper) only. + * PID 1 (init) is allocated normally — the first + * kthread_create() call gets it, matching Linux. */ +} + +int32 PidAllocator::alloc() +{ + LockGuard guard(lock_); + + int32 word_start = next_hint_ / 64; + + for (int32 passes = 0; passes < kBitmapWords; passes++) { + int32 w = (word_start + passes) % kBitmapWords; + uint64 free_bits = ~bitmap_[w]; + if (free_bits == 0) + continue; + + int bit = 0; + uint64 v = free_bits; + while ((v & 1ULL) == 0) { v >>= 1; bit++; } + int32 pid = w * 64 + bit; + + if (pid >= kMaxPid) + continue; + + bitmap_[w] |= (1ULL << bit); + next_hint_ = pid + 1; + if (next_hint_ >= kMaxPid) + next_hint_ = 2; + + return pid; + } + + return -1; +} + +void PidAllocator::free(int32 pid) +{ + if (pid < 0 || pid >= kMaxPid) + return; + + LockGuard guard(lock_); + int32 w = pid / 64; + int32 bit = pid % 64; + bitmap_[w] &= ~(1ULL << bit); +} + +PidAllocator g_pid_alloc; diff --git a/kernel/poll.cc b/kernel/poll.cc new file mode 100644 index 0000000..3674491 --- /dev/null +++ b/kernel/poll.cc @@ -0,0 +1,250 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * sys_ppoll — POSIX ppoll() implementation. + * + * Linux fs/select.c:do_sys_poll() pattern, simplified for our needs: + * - Two-phase loop: "register-then-check". First pass calls every + * f_op->poll() with a real poll_table so drivers can register their + * wait queues; if any fd is already ready we return immediately. + * Otherwise schedule() until a wq fires (or timeout/signal), then + * re-check with poll_table=nullptr (probe-only). + * + * Limitations vs. Linux: + * - Only a single wait queue per ppoll call is tracked (the first one + * a driver hands to poll_wait). Multi-fd polls work, but only the + * first wq's wake actually re-runs the loop; other fds becoming + * ready won't be noticed until the timer fires or another wake. + * BusyBox interactive shell polls one fd (stdin), so this matches. + * + * Reference: Linux fs/select.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* poll_wait — called by file_operations->poll() implementations */ +/* ================================================================== */ + +void poll_wait(file* /*fp*/, WaitQueue* wq, poll_table* pt) +{ + /* Probe-only pass: pt is null, drivers skip registration. */ + if (!pt || !wq) return; + /* Single-wq design: keep the first one we see. A more complete + * implementation would chain entries onto a list. */ + if (!pt->wq) pt->wq = wq; +} + +/* ================================================================== */ +/* Linux-compatible timespec */ +/* ================================================================== */ + +struct linux_timespec_pp { + int64 tv_sec; + int64 tv_nsec; +}; + +/* + * Convert a timespec into a deadline in jiffies (g_jiffies clock). + * Returns 0 if the input is null (= infinite wait), or a deadline + * jiffy value otherwise. Caller treats 0 deadline as "no timeout". + */ +static uint64 timespec_to_deadline(const linux_timespec_pp* ts) +{ + if (!ts) return 0; + /* g_jiffies advances at 100 Hz (HZ=100) — 1 jiffy = 10 ms */ + uint64 ticks = static_cast(ts->tv_sec) * 100ULL; + ticks += ts->tv_nsec / 10'000'000; /* 10 ms granularity */ + /* Round up sub-jiffy nsec so a tiny timeout doesn't truncate to 0. */ + if (ts->tv_nsec % 10'000'000 != 0) ticks++; + return g_jiffies + ticks; +} + +/* ================================================================== */ +/* do_poll — one scan over all fds */ +/* ================================================================== */ + +/* + * For each fd: look up the file, call its poll() callback. If `pt` is + * non-null, drivers register their wait queues onto it. Returns the + * number of fds that have any non-zero revents. + */ +static int do_poll_scan(pollfd* fds, uint32 nfds, poll_table* pt) +{ + TaskControlBlock* c = current_task(); + files_struct* files = c ? c->files : nullptr; + if (!files) return 0; + int ready = 0; + + for (uint32 i = 0; i < nfds; i++) { + if (fds[i].fd < 0) { + fds[i].revents = 0; + continue; + } + file* fp = fcheck(files, fds[i].fd); + if (!fp) { + fds[i].revents = POLLNVAL; + ready++; + continue; + } + uint32 mask = 0; + if (fp->f_op && fp->f_op->poll) { + mask = fp->f_op->poll(fp, pt); + } else { + /* No poll handler — treat as always readable/writable so + * a missing implementation doesn't permanently block sh. */ + mask = POLLIN | POLLOUT; + } + /* Filter: revents always include error/hup; userland event bits + * only set if requested in events. */ + uint32 always = POLLERR | POLLHUP | POLLNVAL; + uint32 want = static_cast(fds[i].events) | always; + fds[i].revents = static_cast(mask & want); + if (fds[i].revents) ready++; + } + return ready; +} + +/* ================================================================== */ +/* sys_ppoll */ +/* ================================================================== */ + +/* + * a0 = struct pollfd __user *ufds + * a1 = nfds_t nfds + * a2 = const struct __kernel_timespec __user *tsp (nullable = infinite) + * a3 = const sigset_t __user *sigmask (nullable = no override) + * a4 = size_t sigsetsize + */ +int64 sys_ppoll(uint64 a0, uint64 a1, uint64 a2, uint64 a3, + uint64 a4, uint64 /*a5*/) +{ + uint64 ufds = a0; + uint32 nfds = static_cast(a1); + uint64 utsp = a2; + uint64 usigmask = a3; + uint64 sigsetsize = a4; + + if (nfds > 1024) return -EINVAL; /* Linux RLIMIT_NOFILE-ish cap */ + if (usigmask && sigsetsize != sizeof(sigset_t)) return -EINVAL; + + /* Copy timespec from user. Null pointer means "wait forever". */ + bool have_timeout = (utsp != 0); + linux_timespec_pp ts{}; + if (have_timeout) { + if (copy_from_user(&ts, utsp, sizeof(ts)) < 0) return -EFAULT; + if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= 1'000'000'000) + return -EINVAL; + } + uint64 deadline = have_timeout ? timespec_to_deadline(&ts) : 0; + + /* Apply temporary sigmask if requested. Linux semantics: the new + * mask is in effect for the duration of the call; on return we + * restore the old mask. If a signal interrupts us mid-wait, the + * original mask is restored before returning -EINTR. */ + TaskControlBlock* curr = current_task(); + sigset_t saved_mask{}; + bool mask_swapped = false; + if (usigmask && curr && curr->sighand) { + sigset_t newmask{}; + if (copy_from_user(&newmask, usigmask, sizeof(newmask)) < 0) + return -EFAULT; + newmask.del(SIGKILL); + newmask.del(SIGSTOP); + LockGuard guard(curr->sighand->siglock); + saved_mask = curr->blocked; + curr->blocked = newmask; + mask_swapped = true; + } + + /* Copy pollfd array from user. Allocate a kernel buffer; for the + * common nfds<=8 case this is one slab object. */ + pollfd* kfds = nullptr; + uint64 fds_bytes = static_cast(nfds) * sizeof(pollfd); + int64 ret = 0; + + if (nfds > 0) { + kfds = static_cast(operator new[](fds_bytes)); + if (!kfds) { ret = -ENOMEM; goto out_restore_mask; } + if (copy_from_user(kfds, ufds, fds_bytes) < 0) { + ret = -EFAULT; goto out_free; + } + } + + /* The main poll loop. */ + { + poll_table pt{}; + pt.wq = nullptr; + int n_ready = do_poll_scan(kfds, nfds, &pt); + + while (n_ready == 0) { + /* Timeout==0 means non-blocking probe. */ + if (have_timeout && deadline <= g_jiffies) break; + + /* No registered wait queue? Nothing to sleep on. This + * happens when no fd has a poll handler that calls + * poll_wait — fall back to a short reschedule loop so we + * don't burn the CPU. */ + if (!pt.wq) { + schedule(); + if (signal_pending(curr)) { ret = -EINTR; goto out_free; } + pt.wq = nullptr; + n_ready = do_poll_scan(kfds, nfds, &pt); + continue; + } + + /* Standard prepare-check-schedule pattern. */ + pt.wq->prepare_to_wait(curr, TaskState::Sleeping); + + /* Re-probe with pt=nullptr (no extra registration) so we + * see any state change that arrived between the previous + * scan and prepare_to_wait. */ + n_ready = do_poll_scan(kfds, nfds, nullptr); + if (n_ready > 0) { + pt.wq->finish_wait(curr); + break; + } + if (signal_pending(curr)) { + pt.wq->finish_wait(curr); + ret = -EINTR; goto out_free; + } + + schedule(); + pt.wq->finish_wait(curr); + + /* Reset the registration and rescan — drivers may register + * fresh wait queues this pass. */ + pt.wq = nullptr; + n_ready = do_poll_scan(kfds, nfds, &pt); + } + ret = n_ready; + } + + /* Copy revents back to user (events bits stay as user wrote them). */ + if (nfds > 0) { + if (copy_to_user(ufds, kfds, fds_bytes) < 0) ret = -EFAULT; + } + +out_free: + if (kfds) operator delete[](kfds); + +out_restore_mask: + if (mask_swapped && curr && curr->sighand) { + LockGuard guard(curr->sighand->siglock); + curr->blocked = saved_mask; + } + return ret; +} diff --git a/kernel/ptrace.cc b/kernel/ptrace.cc new file mode 100644 index 0000000..3280c07 --- /dev/null +++ b/kernel/ptrace.cc @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * PtRegs ↔ TrapFrame conversion + * + * This is the ONLY file that knows both the internal TrapFrame layout + * and the Linux-ABI PtRegs layout. When TrapFrame is restructured + * (e.g., to embed pt_regs directly), only this file needs to change. + */ + +#include +#include + +void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src, + uint64 scause, uint64 stval) +{ + dst->epc = src->epc; + dst->ra = src->ra; + dst->sp = src->sp; + dst->gp = src->gp; + dst->tp = src->tp; + dst->t0 = src->t[0]; + dst->t1 = src->t[1]; + dst->t2 = src->t[2]; + dst->s0 = src->s[0]; + dst->s1 = src->s[1]; + dst->a0 = src->a[0]; + dst->a1 = src->a[1]; + dst->a2 = src->a[2]; + dst->a3 = src->a[3]; + dst->a4 = src->a[4]; + dst->a5 = src->a[5]; + dst->a6 = src->a[6]; + dst->a7 = src->a[7]; + dst->s2 = src->s[2]; + dst->s3 = src->s[3]; + dst->s4 = src->s[4]; + dst->s5 = src->s[5]; + dst->s6 = src->s[6]; + dst->s7 = src->s[7]; + dst->s8 = src->s[8]; + dst->s9 = src->s[9]; + dst->s10 = src->s[10]; + dst->s11 = src->s[11]; + dst->t3 = src->t[3]; + dst->t4 = src->t[4]; + dst->t5 = src->t[5]; + dst->t6 = src->t[6]; + dst->status = src->sstatus; + dst->badaddr = stval; + dst->cause = scause; + dst->orig_a0 = src->a[0]; /* Caller should set this before syscall dispatch */ +} + +void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src) +{ + dst->epc = src->epc; + dst->ra = src->ra; + dst->sp = src->sp; + dst->gp = src->gp; + dst->tp = src->tp; + dst->t[0] = src->t0; + dst->t[1] = src->t1; + dst->t[2] = src->t2; + dst->s[0] = src->s0; + dst->s[1] = src->s1; + dst->a[0] = src->a0; + dst->a[1] = src->a1; + dst->a[2] = src->a2; + dst->a[3] = src->a3; + dst->a[4] = src->a4; + dst->a[5] = src->a5; + dst->a[6] = src->a6; + dst->a[7] = src->a7; + dst->s[2] = src->s2; + dst->s[3] = src->s3; + dst->s[4] = src->s4; + dst->s[5] = src->s5; + dst->s[6] = src->s6; + dst->s[7] = src->s7; + dst->s[8] = src->s8; + dst->s[9] = src->s9; + dst->s[10] = src->s10; + dst->s[11] = src->s11; + dst->t[3] = src->t3; + dst->t[4] = src->t4; + dst->t[5] = src->t5; + dst->t[6] = src->t6; + dst->sstatus = src->status; + /* badaddr and cause are CSR values, not written back to TrapFrame */ +} diff --git a/kernel/sched_class_o1.cc b/kernel/sched_class_o1.cc new file mode 100644 index 0000000..28c693c --- /dev/null +++ b/kernel/sched_class_o1.cc @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * O(1) Priority Bitmap Scheduling Class + */ + +#include +#include +#include +#include +#include + +static void o1_init_rq(RunQueue* rq) +{ + rq->o1().init(); +} + +static void o1_enqueue_task(RunQueue* rq, TaskControlBlock* task) +{ + rq->o1().enqueue(task); + rq->inc_running(); +} + +static void o1_dequeue_task(RunQueue* rq, TaskControlBlock* task) +{ + rq->o1().dequeue(task); + rq->dec_running(); +} + +static TaskControlBlock* o1_pick_next_task(RunQueue* rq) +{ + return rq->o1().pick_next(); +} + +static void o1_put_prev_task(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} + +int32 priority_to_timeslice(uint8 priority) +{ + int32 ts = 20 - static_cast(priority) * 19 / 31; + if (ts < 1) ts = 1; + return ts; +} + +static void o1_task_tick(RunQueue* rq, TaskControlBlock* curr) +{ + if (curr == rq->idle()) { + /* Idle task: if anything is runnable, yield immediately */ + if (rq->nr_running() > 0) { + rq->set_need_resched(true); + } + return; + } + + curr->time_slice--; + if (curr->time_slice <= 0) { + curr->time_slice = priority_to_timeslice(curr->priority); + rq->set_need_resched(true); + } +} + +static void o1_yield_task(RunQueue* rq, TaskControlBlock* curr) +{ + if (curr == rq->idle()) + return; + + curr->time_slice = 0; + rq->set_need_resched(true); +} + +const SchedClass g_sched_class_o1 = { + .name = "o1", + .next = &g_sched_class_idle, + .init_rq = o1_init_rq, + .enqueue_task = o1_enqueue_task, + .dequeue_task = o1_dequeue_task, + .pick_next_task = o1_pick_next_task, + .put_prev_task = o1_put_prev_task, + .task_tick = o1_task_tick, + .yield_task = o1_yield_task, +}; diff --git a/kernel/sched_core.cc b/kernel/sched_core.cc new file mode 100644 index 0000000..c0e068e --- /dev/null +++ b/kernel/sched_core.cc @@ -0,0 +1,571 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Scheduler Core + * + * The core walks sched_class chain to pick the next task. Each class + * owns its own runqueue data and policy. This file owns the per-CPU + * runqueue array and the scheduling state machine. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Assembly trampoline symbol (MD layer) */ +extern "C" void kthread_entry_trampoline(); + +/* Per-CPU runqueue array */ +static RunQueue g_rq[kMaxCpus]; + +/* sched_class chain head (highest priority first) */ +static const SchedClass* g_sched_class_head = &g_sched_class_o1; + +/* ======================================================================== + * Idle thread + * ======================================================================== */ + +static void idle_thread_func(void* /*arg*/) +{ + while (true) { + if (g_rq[arch::cpu::id()].need_resched()) { + schedule(); + } + arch::cpu::intr_on(); + arch::cpu::halt_until_interrupt(); + } +} + + +/* ======================================================================== + * Idle class (always returns idle task) + * ======================================================================== */ + +static void idle_init_rq(RunQueue* /*rq*/) {} +static void idle_enqueue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} +static void idle_dequeue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} +static TaskControlBlock* idle_pick_next(RunQueue* rq) { return rq->idle(); } +static void idle_put_prev(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} +static void idle_task_tick(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} +static void idle_yield(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} + +const SchedClass g_sched_class_idle = { + .name = "idle", + .next = nullptr, + .init_rq = idle_init_rq, + .enqueue_task = idle_enqueue, + .dequeue_task = idle_dequeue, + .pick_next_task = idle_pick_next, + .put_prev_task = idle_put_prev, + .task_tick = idle_task_tick, + .yield_task = idle_yield, +}; + +/* ======================================================================== + * Internal helpers + * ======================================================================== */ + +static RunQueue* rq_for_cpu(uint32 cpu_id) +{ + return &g_rq[cpu_id]; +} + +static RunQueue* this_rq() +{ + return rq_for_cpu(arch::cpu::id()); +} + +static TaskControlBlock* pick_next_task(RunQueue* rq) +{ + for (const SchedClass* c = g_sched_class_head; c; c = c->next) { + TaskControlBlock* t = c->pick_next_task(rq); + if (t) { + return t; + } + } + return rq->idle(); +} + +static const char* idle_name_for_cpu(uint32 cpu) +{ + static char names[kMaxCpus][16]; + if (cpu >= kMaxCpus) { + kernel_panic("sched: cpu_id out of range"); + } + + if (names[cpu][0] == '\0') { + ksnprintf(names[cpu], sizeof(names[cpu]), "idle/%u", cpu); + } + return names[cpu]; +} + +/* ======================================================================== + * Kernel thread entry — MI-layer function called by MD trampoline + * ======================================================================== */ + +/* Forward declaration — C++ linkage (task.cc) */ +void task_exit(int code); + +extern "C" void kthread_entry(void (*entry)(void*), void* arg) +{ + RunQueue* rq = this_rq(); + rq->lock().release(); + + arch::cpu::intr_on(); + + entry(arg); + + /* Entry function returned — clean exit (release PID, stack, TCB) */ + task_exit(0); + __builtin_unreachable(); +} + +/* ======================================================================== + * fork_child_entry — called from ret_from_fork assembly + * ======================================================================== */ +extern "C" void userret(arch::TrapFrame* tf); +extern "C" void trap_vector(); + +extern "C" void fork_child_entry(arch::TrapFrame* tf) +{ + RunQueue* rq = this_rq(); + rq->lock().release(); + + /* Set stvec to trap_vector for user-mode traps, then return to user */ + arch::trap::set_vector(reinterpret_cast(trap_vector)); + userret(tf); + + __builtin_unreachable(); +} + +/* ======================================================================== + * alloc_task — canonical TCB allocator (defined in task.cc) + * + * Single point of initialization for TaskControlBlock. Ensures all + * fields are properly zeroed when the struct gains new members. + * ======================================================================== */ + +extern TaskControlBlock* alloc_task(const char* name, uint8 priority, + uint8 flags, uint8 cpu); + +/* ======================================================================== + * Public API: current_task() + * ======================================================================== */ + +TaskControlBlock* current_task() +{ + return this_rq()->current(); +} + +/* ======================================================================== + * Public API: sched_enqueue / sched_dequeue + * ======================================================================== */ + +void sched_enqueue(TaskControlBlock* task) +{ + RunQueue* rq = rq_for_cpu(task->cpu_id); + LockGuard guard(rq->lock()); + + if (!task->sched_class) { + task->sched_class = &g_sched_class_o1; + } + if (!task->sched_entity) { + task->sched_entity = &task->run_link; + } + + task->sched_class->enqueue_task(rq, task); + + if (rq->current() == rq->idle() || task->priority < rq->current()->priority) { + rq->set_need_resched(true); + } +} + +void sched_dequeue(TaskControlBlock* task) +{ + RunQueue* rq = rq_for_cpu(task->cpu_id); + LockGuard guard(rq->lock()); + + if (!task->sched_class) { + task->sched_class = &g_sched_class_o1; + } + task->sched_class->dequeue_task(rq, task); +} + +/* ======================================================================== + * schedule() — main scheduling entry point + * ======================================================================== */ + +void schedule() +{ + RunQueue* rq = this_rq(); + rq->lock().acquire(); + + TaskControlBlock* prev = rq->current(); + + if (prev && prev->state == TaskState::Running) { + prev->state = TaskState::Runnable; + prev->sched_class->enqueue_task(rq, prev); + } else if (prev) { + prev->sched_class->put_prev_task(rq, prev); + } + + TaskControlBlock* next = pick_next_task(rq); + + if (prev == next) { + /* We picked the same task. It must be Running again. */ + next->state = TaskState::Running; + next->sched_class->dequeue_task(rq, next); + rq->clear_need_resched(); + rq->lock().release(); + return; + } + + if (next != rq->idle()) { + next->sched_class->dequeue_task(rq, next); + } + + next->state = TaskState::Running; + rq->set_current(next); + rq->clear_need_resched(); + + /* Switch address space (satp) if the new task has a different mm. + * Kernel threads (mm == nullptr) borrow the previous task's mm, + * so no satp switch is needed — this matches Linux's lazy TLB behavior. */ + { + VmSpace* prev_mm = prev ? prev->mm : nullptr; + VmSpace* next_mm = next->mm; + uint32 cpu = static_cast(arch::cpu::id()); + + if (next_mm && next_mm != prev_mm) { + /* Switching TO a user process with a different address space. + * Linux: switch_mm_irqs_off() in arch/riscv/mm/context.c + * sets satp and flushes TLB. */ + arch::mmu::set_page_table(next_mm->root_pa); + arch::mmu::flush_tlb_all(); + next_mm->cpu_mask.fetch_or(1ULL << cpu, __ATOMIC_RELAXED); + + /* Critical: update TrapFrame's kernel_satp so that trap_vector.S + * restores the correct page table on next trap entry. + * Without this, a process migrated to a new CPU would have its + * trap entry use a stale satp from when trapframe_init_user() + * was called, pointing to whatever page table was active then + * on the ORIGINAL CPU. */ + if (next->trapframe) { + next->trapframe->kernel_satp = arch::mmu::read_satp(); + } + } + + if (prev_mm && prev_mm != next_mm) { + /* Leaving prev's address space — clear our bit */ + prev_mm->cpu_mask.fetch_and(~(1ULL << cpu), __ATOMIC_RELAXED); + } + } + + /* FPU lazy save/restore. + * Save prev's FP registers only if they were modified (FS=Dirty). + * Restore next's FP state for user tasks; disable FPU for kthreads. */ + if (arch::fpu_state_dirty(arch::fpu_get_fs())) { + fpu_save(&prev->fpstate); + } + if (next->mm != nullptr) { + arch::fpu_set_fs(arch::kSstatusFS_Clean); + fpu_restore(&next->fpstate); + } else { + arch::fpu_set_fs(arch::kSstatusFS_Off); + } + + arch::context_switch(&prev->context, &next->context); + + rq->lock().release(); +} + +/* ======================================================================== + * sched_tick() — called from timer interrupt (MI trap layer) + * ======================================================================== */ + +void sched_tick() +{ + RunQueue* rq = this_rq(); + { + LockGuard guard(rq->lock()); + TaskControlBlock* curr = rq->current(); + if (!curr) return; + curr->sched_class->task_tick(rq, curr); + } + + /* Periodic load balancing — runs without holding rq lock */ + sched_balance(); +} + +/* ======================================================================== + * sched_check_preempt() — called from trap return path + * + * Only preempts if: + * 1. need_resched flag is set (a higher-priority task is ready) + * 2. preempt_count == 0 (no spinlocks held, safe to context switch) + * ======================================================================== */ + +void sched_check_preempt() +{ + RunQueue* rq = this_rq(); + if (!rq->need_resched()) return; + + TaskControlBlock* curr = current_task(); + if (curr && curr->preempt_count > 0) { + /* Cannot preempt — caller is in a critical section. + * The preemption will fire when the last lock is released. */ + return; + } + schedule(); +} + +/* ======================================================================== + * sched_yield() — voluntary yield + * ======================================================================== */ + +void sched_yield() +{ + RunQueue* rq = this_rq(); + { + LockGuard guard(rq->lock()); + TaskControlBlock* curr = rq->current(); + if (curr && curr != rq->idle()) { + curr->sched_class->yield_task(rq, curr); + rq->set_need_resched(true); + } + } + schedule(); +} + +/* ======================================================================== + * sched_init() — BSP initialization + * ======================================================================== */ + +void sched_init() +{ + for (int i = 0; i < kMaxCpus; i++) { + RunQueue* rq = &g_rq[i]; + rq->init(static_cast(i)); + + g_sched_class_o1.init_rq(rq); + g_sched_class_idle.init_rq(rq); + } + + g_sched_class_head = &g_sched_class_o1; + + uint64 bsp_cpu = arch::cpu::id(); + uint8 bsp_cpu8 = static_cast(bsp_cpu); + + TaskControlBlock* idle0 = alloc_task(idle_name_for_cpu(static_cast(bsp_cpu)), + kIdlePriority, + TF_KTHREAD | TF_IDLE, bsp_cpu8); + if (!idle0) kernel_panic("sched_init: failed to create idle/bsp"); + + idle0->pid = 0; + idle0->tgid = 0; + idle0->sched_class = &g_sched_class_idle; + idle0->sched_entity = nullptr; + + uint64 stack_top = idle0->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); + idle0->context = {}; + idle0->context.ra = reinterpret_cast(kthread_entry_trampoline); + idle0->context.sp = stack_top; + idle0->context.s[0] = reinterpret_cast(idle_thread_func); + idle0->context.s[1] = 0; + + g_rq[bsp_cpu].set_idle(idle0); + + TaskControlBlock* bsp_task = alloc_task("kernel_main", 0, TF_KTHREAD, bsp_cpu8); + if (!bsp_task) kernel_panic("sched_init: failed to create bsp_task"); + + bsp_task->pid = -1; + bsp_task->tgid = -1; + bsp_task->state = TaskState::Running; + + g_rq[bsp_cpu].set_current(bsp_task); +} + +/* ======================================================================== + * sched_init_ap() — AP per-CPU initialization + * ======================================================================== */ + +void sched_init_ap() +{ + uint64 cpu = arch::cpu::id(); + + const char* name = idle_name_for_cpu(static_cast(cpu)); + + TaskControlBlock* idle = alloc_task(name, kIdlePriority, + TF_KTHREAD | TF_IDLE, + static_cast(cpu)); + if (!idle) kernel_panic("sched_init_ap: failed to create idle"); + + idle->pid = 0; + idle->tgid = 0; + idle->sched_class = &g_sched_class_idle; + idle->sched_entity = nullptr; + + uint64 stack_top = idle->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); + idle->context = {}; + idle->context.ra = reinterpret_cast(kthread_entry_trampoline); + idle->context.sp = stack_top; + idle->context.s[0] = reinterpret_cast(idle_thread_func); + idle->context.s[1] = 0; + + g_rq[cpu].set_idle(idle); + + TaskControlBlock* ap_task = alloc_task("ap_boot", 0, TF_KTHREAD, + static_cast(cpu)); + if (!ap_task) kernel_panic("sched_init_ap: failed to create ap_task"); + + ap_task->pid = -1; + ap_task->tgid = -1; + ap_task->state = TaskState::Running; + + g_rq[cpu].set_current(ap_task); +} + +/* ======================================================================== + * sched_nr_running() — runnable task count for a CPU + * ======================================================================== */ + +uint32 sched_nr_running(uint32 cpu_id) +{ + if (cpu_id >= kMaxCpus) return 0; + return g_rq[cpu_id].o1().nr_running(); +} + +/* ======================================================================== + * sched_migrate_task() — move a task between CPUs + * + * Locks are acquired in cpu_id order to prevent ABBA deadlock. + * The task must be Runnable (in a runqueue) and not currently running. + * ======================================================================== */ + +int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu) +{ + if (!task || target_cpu >= kMaxCpus) return -22; /* -EINVAL */ + + uint8 src_cpu = task->cpu_id; + if (src_cpu == target_cpu) return 0; /* already there */ + + /* Cannot migrate idle or currently-running tasks */ + if (task->flags & TF_IDLE) return -22; + + RunQueue* src_rq = rq_for_cpu(src_cpu); + RunQueue* dst_rq = rq_for_cpu(target_cpu); + + /* Ordered lock acquisition: always lock lower cpu_id first */ + if (src_cpu < target_cpu) { + src_rq->lock().acquire(); + dst_rq->lock().acquire(); + } else { + dst_rq->lock().acquire(); + src_rq->lock().acquire(); + } + + int ret = -22; + + /* Double-check: task must not be current on either CPU */ + if (task != src_rq->current() && task->state == TaskState::Runnable) { + /* Dequeue from source */ + task->sched_class->dequeue_task(src_rq, task); + + /* Update cpu_id and enqueue on destination */ + task->cpu_id = target_cpu; + task->sched_class->enqueue_task(dst_rq, task); + + /* Signal target CPU to reschedule */ + dst_rq->set_need_resched(true); + ret = 0; + } + + /* Release in reverse order */ + if (src_cpu < target_cpu) { + dst_rq->lock().release(); + src_rq->lock().release(); + } else { + src_rq->lock().release(); + dst_rq->lock().release(); + } + + /* If migration succeeded, send reschedule IPI to target */ + if (ret == 0) { + smp_send_reschedule(target_cpu); + } + + return ret; +} + +/* ======================================================================== + * sched_balance() — periodic load balancer (pull model) + * + * Called from sched_tick() context. Looks at all other CPUs and if any + * has 2+ more runnable tasks than the local CPU, pulls one task. + * This is a simplified Linux-style load_balance(). + * ======================================================================== */ + +void sched_balance() +{ + uint32 this_cpu = static_cast(arch::cpu::id()); + uint32 local_load = g_rq[this_cpu].o1().nr_running(); + + /* Only balance if we're idle or lightly loaded */ + if (local_load > 1) return; + + uint32 busiest_cpu = this_cpu; + uint32 busiest_load = local_load; + + /* Find the busiest CPU */ + for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) { + if (cpu == this_cpu) continue; + uint32 load = g_rq[cpu].o1().nr_running(); + if (load > busiest_load) { + busiest_load = load; + busiest_cpu = cpu; + } + } + + /* Only pull if imbalance is >= 2 */ + if (busiest_cpu == this_cpu || busiest_load < local_load + 2) return; + + /* Try to pull one task from the busiest CPU */ + RunQueue* busy_rq = rq_for_cpu(busiest_cpu); + + /* Lock ordering: lower cpu_id first */ + RunQueue* first = (this_cpu < busiest_cpu) + ? &g_rq[this_cpu] : busy_rq; + RunQueue* second = (this_cpu < busiest_cpu) + ? busy_rq : &g_rq[this_cpu]; + + first->lock().acquire(); + second->lock().acquire(); + + /* Re-check after acquiring locks */ + TaskControlBlock* victim = busy_rq->o1().pick_next(); + if (victim && victim != busy_rq->current() + && !(victim->flags & TF_IDLE)) { + victim->sched_class->dequeue_task(busy_rq, victim); + victim->cpu_id = static_cast(this_cpu); + victim->sched_class->enqueue_task(&g_rq[this_cpu], victim); + g_rq[this_cpu].set_need_resched(true); + } + + second->lock().release(); + first->lock().release(); +} diff --git a/kernel/sched_internal.h b/kernel/sched_internal.h new file mode 100644 index 0000000..514b613 --- /dev/null +++ b/kernel/sched_internal.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class O1RunQueue { +public: + void init() { + bitmap_ = 0; + nr_running_ = 0; + for (int p = 0; p < kNumPriorities; p++) { + queues_[p].init(); + } + } + + void enqueue(TaskControlBlock* task) { + int prio = task->priority; + + list_node* head = &queues_[prio]; + task->run_link.prev = head->prev; + task->run_link.next = head; + head->prev->next = &task->run_link; + head->prev = &task->run_link; + + bitmap_ |= (1U << prio); + nr_running_++; + + task->state = TaskState::Runnable; + } + + void dequeue(TaskControlBlock* task) { + int prio = task->priority; + + task->run_link.prev->next = task->run_link.next; + task->run_link.next->prev = task->run_link.prev; + task->run_link.init(); + + if (queues_[prio].is_empty()) { + bitmap_ &= ~(1U << prio); + } + + if (nr_running_ > 0) { + nr_running_--; + } + } + + TaskControlBlock* pick_next() const { + if (bitmap_ == 0) { + return nullptr; + } + + int prio = lume::ctz32(bitmap_); + list_node* node = queues_[prio].next; + return list_entry(node); + } + + uint32 nr_running() const { return nr_running_; } + +private: + uint32 bitmap_ = 0; + list_node queues_[kNumPriorities]; + uint32 nr_running_ = 0; +}; + +class RunQueue { +public: + void init(uint32 cpu_id) { + lock_.init("RunQueue"); + cpu_id_ = cpu_id; + nr_running_ = 0; + need_resched_ = false; + current_ = nullptr; + idle_ = nullptr; + } + + Spinlock& lock() { return lock_; } + + uint32 cpu_id() const { return cpu_id_; } + + uint32 nr_running() const { return nr_running_; } + void inc_running() { nr_running_++; } + void dec_running() { if (nr_running_ > 0) nr_running_--; } + + bool need_resched() const { return need_resched_; } + void set_need_resched(bool v) { need_resched_ = v; } + void clear_need_resched() { need_resched_ = false; } + + TaskControlBlock* current() const { return current_; } + void set_current(TaskControlBlock* task) { current_ = task; } + + TaskControlBlock* idle() const { return idle_; } + void set_idle(TaskControlBlock* task) { idle_ = task; } + + O1RunQueue& o1() { return o1_; } + const O1RunQueue& o1() const { return o1_; } + +private: + Spinlock lock_; + uint32 cpu_id_ = 0; + uint32 nr_running_ = 0; + bool need_resched_ = false; + + TaskControlBlock* current_ = nullptr; + TaskControlBlock* idle_ = nullptr; + + O1RunQueue o1_; +}; diff --git a/kernel/signal.cc b/kernel/signal.cc new file mode 100644 index 0000000..53d26da --- /dev/null +++ b/kernel/signal.cc @@ -0,0 +1,442 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Signal Delivery — Queue, Dequeue, Default Actions, and do_signal + * + * Implements the core signal infrastructure: + * - send_signal(): enqueue a signal to a task's pending set + * - force_sig_fault(): fault-induced signal (trap handler API) + * - do_signal(): called on return-to-user, delivers pending signals + * - next_signal(): dequeue the highest-priority deliverable signal + * + * Signal frame construction (arch-specific) is in arch/riscv/signal.cc. + * + * Reference: Linux kernel/signal.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Forward declaration: arch-specific signal frame setup */ +int setup_signal_frame(arch::TrapFrame* tf, int signo, + kernel_sigaction* ka, kernel_siginfo* info); + +/* ================================================================== */ +/* sighand_struct lifecycle (refcount-shared, Linux copy_sighand) */ +/* ================================================================== */ + +sighand_struct* alloc_sighand() +{ + auto* sh = new sighand_struct; + if (sh) sh->init(); + return sh; +} + +sighand_struct* copy_sighand(sighand_struct* parent) +{ + /* Linux: kernel/fork.c:copy_sighand() when !CLONE_SIGHAND copies + * the action table verbatim so the child inherits dispositions + * without sharing future modifications. */ + if (!parent) return alloc_sighand(); + auto* sh = new sighand_struct; + if (!sh) return nullptr; + sh->init(); + LockGuard guard(parent->siglock); + for (int i = 0; i < _NSIG; i++) sh->action[i] = parent->action[i]; + return sh; +} + +void get_sighand(sighand_struct* sh) +{ + if (!sh) return; + LockGuard guard(sh->siglock); + sh->refcount++; +} + +void put_sighand(sighand_struct* sh) +{ + if (!sh) return; + uint32 remaining; + { + LockGuard guard(sh->siglock); + remaining = --sh->refcount; + } + if (remaining == 0) delete sh; +} + +/* ================================================================== */ +/* next_signal — find lowest-numbered deliverable signal */ +/* ================================================================== */ + +/* Manual count-trailing-zeros (no libgcc __ctzdi2 dependency) */ +static inline int sig_ctz64(uint64 x) +{ + if (x == 0) return 64; + int n = 0; + if ((x & 0xFFFFFFFF) == 0) { n += 32; x >>= 32; } + if ((x & 0x0000FFFF) == 0) { n += 16; x >>= 16; } + if ((x & 0x000000FF) == 0) { n += 8; x >>= 8; } + if ((x & 0x0000000F) == 0) { n += 4; x >>= 4; } + if ((x & 0x00000003) == 0) { n += 2; x >>= 2; } + if ((x & 0x00000001) == 0) { n += 1; } + return n; +} + +static int next_signal(sigpending* pending, const sigset_t* mask) +{ + /* Standard signals: check bitmap & ~blocked */ + for (int i = 0; i < _NSIG_WORDS; i++) { + uint64 bits = pending->signal.sig[i] & ~mask->sig[i]; + if (bits != 0) { + int sig = i * 64 + sig_ctz64(bits) + 1; + return sig; + } + } + return 0; +} + +/* ================================================================== */ +/* dequeue_signal — remove a signal from the pending set */ +/* ================================================================== */ + +/* + * Dequeue the next deliverable signal. MUST be called with + * task->sighand->siglock held. Mirrors Linux's __dequeue_signal() + * (kernel/signal.c) which requires the same lock. + */ +static int dequeue_signal_locked(TaskControlBlock* task, kernel_siginfo* info) +{ + int sig = next_signal(&task->pending, &task->blocked); + if (sig == 0) return 0; + + /* Clear the bitmap bit */ + task->pending.signal.del(sig); + + /* Search the queue for a matching sigqueue entry */ + list_node* node = task->pending.list.next; + while (node != &task->pending.list) { + auto* q = list_entry(node); + if (q->info.si_signo == sig) { + /* Remove from queue */ + node->prev->next = node->next; + node->next->prev = node->prev; + if (info) *info = q->info; + delete q; + return sig; + } + node = node->next; + } + + /* No queued entry — fill in minimal info */ + if (info) { + info->si_signo = sig; + info->si_errno = 0; + info->si_code = 0; /* SI_USER */ + } + return sig; +} + +/* ================================================================== */ +/* send_signal — enqueue a signal to a task */ +/* ================================================================== */ + +/* + * Drop a queued sigqueue entry with the given signo from `task`. + * + * MUST be called with task->sighand->siglock held — Linux's + * flush_sigqueue_mask() has the same precondition (kernel/signal.c). + */ +static bool discard_pending_locked(TaskControlBlock* task, int signo) +{ + bool any = false; + list_node* node = task->pending.list.next; + while (node != &task->pending.list) { + list_node* nxt = node->next; + auto* q = list_entry(node); + if (q->info.si_signo == signo) { + node->prev->next = node->next; + node->next->prev = node->prev; + delete q; + any = true; + } + node = nxt; + } + task->pending.signal.del(signo); + return any; +} + +static inline bool is_stop_sig(int sig) +{ + return sig == SIGSTOP || sig == SIGTSTP || + sig == SIGTTIN || sig == SIGTTOU; +} + +/* + * send_signal — queue a signal to a task. + * + * Acquires task->sighand->siglock for the full update (bitmap, queue, + * stop/cont side-effects, scheduler wake). Mirrors Linux's + * __send_signal_locked() which also requires this lock (see + * kernel/signal.c:1048 lockdep_assert_held). + * + * Returns 0 on success or a negative errno. + */ +int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task) +{ + if (sig < 1 || sig > _NSIG) return -EINVAL; + if (!task) return -ESRCH; + if (!task->sighand) return -ESRCH; /* eager-alloc invariant; tasks + * without sighand can't receive + * signals (idle / early kthreads) */ + + LockGuard guard(task->sighand->siglock); + + /* + * Linux's prepare_signal(): a stop signal cancels any pending + * SIGCONT and vice versa, so the most recent decision wins. + */ + if (is_stop_sig(sig)) { + discard_pending_locked(task, SIGCONT); + } else if (sig == SIGCONT) { + discard_pending_locked(task, SIGSTOP); + discard_pending_locked(task, SIGTSTP); + discard_pending_locked(task, SIGTTIN); + discard_pending_locked(task, SIGTTOU); + + /* SIGCONT's resume effect happens atomically with delivery. */ + if (task->state == TaskState::Stopped) { + task->state = TaskState::Runnable; + task->notify_continued = true; + task->stopped_code = 0; + sched_enqueue(task); + if (task->parent) task->parent->wait_chld.wake_all(); + } + } + + /* Already pending? For standard signals, just mark the bitmap */ + if (sig < SIGRTMIN && task->pending.signal.has(sig)) + return 0; + + /* Enqueue a sigqueue entry */ + auto* q = new sigqueue; + if (!q) { + /* Can't allocate — just set the bitmap (signal still delivered) */ + task->pending.signal.add(sig); + return 0; + } + + q->list.init(); + if (info) { + q->info = *info; + } else { + q->info.si_signo = sig; + q->info.si_errno = 0; + q->info.si_code = 0; /* SI_USER */ + } + q->info.si_signo = sig; /* ensure consistent */ + + /* Add to tail of queue */ + list_node* head = &task->pending.list; + q->list.prev = head->prev; + q->list.next = head; + head->prev->next = &q->list; + head->prev = &q->list; + + /* Set bitmap */ + task->pending.signal.add(sig); + + /* Wake the task if it's sleeping in an interruptible wait. */ + if (task->state == TaskState::Sleeping) { + task->state = TaskState::Runnable; + sched_enqueue(task); + } + + /* SIGKILL must run even if the target is Stopped. */ + if (sig == SIGKILL && task->state == TaskState::Stopped) { + task->state = TaskState::Runnable; + task->stopped_code = 0; + sched_enqueue(task); + } + + return 0; +} + +/* ================================================================== */ +/* force_sig_fault — trap handler API */ +/* ================================================================== */ + +void force_sig_fault(int signo, int code, uint64 addr) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return; + + /* Kernel threads cannot handle signals — kill immediately */ + if (curr->flags & TF_KTHREAD) { + pr_err("signal: kernel thread %s received signal %d\n", + curr->name ? curr->name : "(noname)", signo); + task_kill_current(signo); + return; + } + + /* Sighand must already exist for user-mode tasks (allocated at + * fork / kernel_execve time). If it's somehow missing, the only + * safe action is to terminate. */ + if (!curr->sighand) { + pr_err("signal: pid %d has no sighand, killing\n", curr->pid); + task_kill_current(signo); + return; + } + + kernel_siginfo info{}; + info.si_signo = signo; + info.si_code = code; + info._sifields._sigfault._addr = addr; + + /* Force: bypass blocked mask for fatal signals (lock to keep + * blocked-mask updates consistent with sigprocmask). */ + { + LockGuard guard(curr->sighand->siglock); + curr->blocked.del(signo); + } + + send_signal(signo, &info, curr); +} + +/* ================================================================== */ +/* signal_pending — check if unblocked signals are pending */ +/* ================================================================== */ + +bool signal_pending(TaskControlBlock* task) +{ + /* Advisory check (matches Linux's recalc_sigpending() usage): + * callers re-validate under siglock before acting. The bitmap is + * a uint64 word; a torn read can only produce false-positives or + * false-negatives transiently, never a corrupt pointer. */ + if (!task) return false; + sigset_t unblocked = task->pending.signal & ~task->blocked; + return !unblocked.is_empty(); +} + +/* ================================================================== */ +/* do_signal — called before returning to user mode */ +/* ================================================================== */ + +void do_signal(arch::TrapFrame* tf) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return; + + /* Kernel threads never deliver signals via frames */ + if (curr->flags & TF_KTHREAD) return; + + /* No sighand means no signal infrastructure for this task */ + if (!curr->sighand) return; + + while (true) { + kernel_siginfo info{}; + kernel_sigaction ka_copy; + int sig; + + /* + * Take siglock for dequeue + action snapshot. Action processing + * (default actions like SIG_ACT_TERM, or constructing a signal + * frame for a user handler) is done OUTSIDE the lock — it can + * touch user memory and even call schedule(). This matches + * Linux's get_signal() loop in kernel/signal.c: it takes + * siglock around dequeue_signal+ka_copy, drops it before + * dispatching, and re-acquires next iteration. + */ + { + LockGuard guard(curr->sighand->siglock); + sig = dequeue_signal_locked(curr, &info); + if (sig == 0) break; + ka_copy = curr->sighand->action[sig - 1]; + } + kernel_sigaction* ka = &ka_copy; + + /* SIG_IGN: discard */ + if (ka->sa_handler == SIG_IGN && !sig_kernel_only(sig)) + continue; + + /* SIG_DFL: apply default action */ + if (ka->sa_handler == SIG_DFL) { + SigDefaultAction act = sig_default_action(sig); + switch (act) { + case SIG_ACT_IGN: + continue; + case SIG_ACT_TERM: + case SIG_ACT_CORE: + pr_debug_s(KLOG_SUBSYS_SIGNAL, + "signal: pid %d killed by signal %d\n", + curr->pid, sig); + task_kill_current(sig); + return; /* never reached */ + case SIG_ACT_STOP: { + /* + * Enter Stopped state. Record the encoded wstatus so + * the parent's wait4(WUNTRACED) can observe and report. + * The schedule() below does NOT re-enqueue Stopped + * tasks — when SIGCONT eventually arrives, send_signal + * flips us back to Runnable and re-enqueues. + */ + curr->stopped_code = W_STOPCODE(sig); + curr->state = TaskState::Stopped; + if (curr->parent) + curr->parent->wait_chld.wake_all(); + pr_debug_s(KLOG_SUBSYS_SIGNAL, + "signal: pid %d stopped by sig %d\n", + curr->pid, sig); + schedule(); + /* + * Resumed (either by SIGCONT or SIGKILL). Loop back to + * deliver any remaining pending signals before returning + * to user mode. + */ + continue; + } + case SIG_ACT_CONT: + /* SIGCONT's resume side-effect already happened in + * send_signal(). The only thing left at handler time + * is to consume the signal (no-op for SIG_DFL). */ + continue; + } + } + + /* User handler: construct signal frame */ + pr_debug_s(KLOG_SUBSYS_SIGNAL, + "signal: pid %d delivering sig %d handler=%llx\n", + curr->pid, sig, reinterpret_cast(ka->sa_handler)); + int ret = setup_signal_frame(tf, sig, ka, &info); + if (ret < 0) { + pr_err("signal: failed to set up frame for sig %d\n", sig); + task_kill_current(sig); + return; + } + + /* Post-delivery bookkeeping under siglock — both the per-task + * blocked mask and the shared action table (SA_RESETHAND) are + * touched, so we must hold the same lock the senders use. */ + { + LockGuard guard(curr->sighand->siglock); + if (!(ka->sa_flags & SA_NODEFER)) curr->blocked.add(sig); + curr->blocked = curr->blocked | ka->sa_mask; + if (ka->sa_flags & SA_RESETHAND) { + curr->sighand->action[sig - 1].sa_handler = SIG_DFL; + } + } + + /* Only deliver one signal per return-to-user */ + break; + } +} diff --git a/kernel/smp.cc b/kernel/smp.cc new file mode 100644 index 0000000..1a79ac3 --- /dev/null +++ b/kernel/smp.cc @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * smp.cc — SMP IPI implementation + * + * Sending side: atomically set reason bits in target CPU's IpiData, + * then invoke arch-specific IPI delivery (SBI ecall on RISC-V). + * + * Receiving side: smp_handle_ipi() is called from the trap handler + * when an S-mode software interrupt arrives. It atomically consumes + * all pending reason bits and dispatches to the appropriate handler. + * + * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.2 + */ + +#include +#include +#include +#include +#include +#include + +/* Per-CPU IPI data — BSS zero-initialized */ +IpiData g_ipi_data[kMaxCpus]; + +/* ------------------------------------------------------------------ */ +/* smp_init */ +/* ------------------------------------------------------------------ */ + +void smp_init() +{ + for (uint32 i = 0; i < kMaxCpus; i++) { + g_ipi_data[i].pending.store(0, __ATOMIC_RELAXED); + g_ipi_data[i].tlb_start = 0; + g_ipi_data[i].tlb_end = 0; + } + + printk(KERN_INFO "smp: IPI framework initialized (%u CPUs max)\n", + static_cast(kMaxCpus)); +} + +/* ------------------------------------------------------------------ */ +/* smp_send_ipi — send IPI to target CPUs */ +/* ------------------------------------------------------------------ */ + +void smp_send_ipi(uint64 cpu_mask, uint32 reason) +{ + if (cpu_mask == 0) return; + + /* Set pending bits on each target CPU's IpiData */ + for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) { + if (cpu_mask & (1ULL << cpu)) { + g_ipi_data[cpu].pending.fetch_or(reason, __ATOMIC_RELEASE); + } + } + + /* Deliver hardware interrupt via SBI. + * RISC-V SBI IPI extension sends S-mode software interrupt + * to the specified harts. hart_mask maps 1:1 to cpu_mask + * (assuming hartid == cpu_id, which holds for QEMU virt). */ + arch::sbi::sbi_send_ipi(cpu_mask, 0); +} + +/* ------------------------------------------------------------------ */ +/* smp_handle_ipi — called from trap handler on software interrupt */ +/* ------------------------------------------------------------------ */ + +void smp_handle_ipi() +{ + uint32 cpu = static_cast(arch::cpu::id()); + + /* Clear the S-mode software interrupt pending bit (sip.SSIP). + * On RISC-V, we must clear this in software, otherwise the + * interrupt will keep firing. */ + arch::cpu::clear_sip_ssip(); + + /* Atomically consume all pending reasons */ + uint32 reasons = g_ipi_data[cpu].pending.exchange(0, __ATOMIC_ACQUIRE); + + if (reasons == 0) return; /* Spurious */ + + /* Dispatch each reason */ + + if (reasons & IPI_RESCHEDULE) { + /* Just need to return from interrupt — schedule() will be + * called on the return-from-trap path if need_resched is set. + * For now, set the flag by yielding on next opportunity. */ + /* TODO: set need_resched flag when preemption tracking is added */ + } + + if (reasons & IPI_TLB_SHOOTDOWN) { + /* Execute local TLB flush with the parameters the sender stored */ + uint64 start = g_ipi_data[cpu].tlb_start; + uint64 end = g_ipi_data[cpu].tlb_end; + if (start == 0 && end == 0) { + arch::mmu::flush_tlb_all(); + } else { + for (uint64 va = start; va < end; va += kPageSize) { + arch::mmu::flush_tlb_page(va); + } + } + } + + if (reasons & IPI_HALT) { + printk(KERN_INFO "smp: CPU %u halting by IPI\n", cpu); + arch::cpu::intr_off(); + while (true) { + arch::cpu::halt_until_interrupt(); + } + } +} diff --git a/kernel/sync/cpu_state.h b/kernel/sync/cpu_state.h new file mode 100644 index 0000000..1f7dde0 --- /dev/null +++ b/kernel/sync/cpu_state.h @@ -0,0 +1,35 @@ +/* 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 +#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/kernel/sync/mutex.cc b/kernel/sync/mutex.cc new file mode 100644 index 0000000..c4d3e9d --- /dev/null +++ b/kernel/sync/mutex.cc @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Mutex — Linux-style sleeping mutex (no PI). + * + * Implementation notes (mirroring kernel/locking/mutex.c, simplified): + * + * - Fast path (uncontended): wait_lock_ -> see owner_==null -> claim -> unlock. + * - Slow path (contended): enqueue MutexWaiter on stack, sleep, race for + * ownership after each wake. unlock() wakes only the head waiter. + * - No optimistic spin, no handoff anti-starvation, no interruptible wait. + * These belong to follow-up work (see mutex.md §5). + * + * Reference: docs/specs/mutex.md + */ + +#include +#include +#include +#include +#include + +namespace { + +struct MutexWaiter { + list_node link; + TaskControlBlock* task; +}; + +} // namespace + +/* ------------------------------------------------------------------ */ +/* init */ +/* ------------------------------------------------------------------ */ + +void Mutex::init(const char* name) +{ + owner_ = nullptr; + name_ = name; + wait_lock_.init(name != nullptr ? name : "mutex"); + wait_list_.init(); +} + +/* ------------------------------------------------------------------ */ +/* try_lock — non-blocking, safe from any context */ +/* ------------------------------------------------------------------ */ + +bool Mutex::try_lock() +{ + LockGuard g(wait_lock_); + if (owner_ != nullptr) { + return false; + } + owner_ = current_task(); + return true; +} + +/* ------------------------------------------------------------------ */ +/* lock — blocking, must be called in schedulable context */ +/* ------------------------------------------------------------------ */ + +void Mutex::lock() +{ + TaskControlBlock* curr = current_task(); + + if (curr == nullptr) { + kernel_panic("Mutex::lock: no current task"); + } + if (curr->preempt_count > 0) { + kernel_panic("Mutex::lock: preemption disabled (holding spinlock?)"); + } + + wait_lock_.acquire(); + + if (owner_ == nullptr) { + owner_ = curr; + wait_lock_.release(); + return; + } + + if (owner_ == curr) { + wait_lock_.release(); + kernel_panic("Mutex::lock: recursive acquire by same task"); + } + + /* Slow path: enqueue ourselves and sleep until we are head + lock free. */ + MutexWaiter waiter; + waiter.link.init(); + waiter.task = curr; + list_add_tail(&waiter.link, &wait_list_); + + for (;;) { + /* Set state inside wait_lock_ so unlock-side sees a coherent view: + * either we are on the list AND Sleeping (will be woken), or we + * already took ownership above (not in this loop). */ + curr->state = TaskState::Sleeping; + wait_lock_.release(); + + schedule(); + + wait_lock_.acquire(); + + if (owner_ == nullptr && wait_list_.next == &waiter.link) { + owner_ = curr; + list_del_init(&waiter.link); + wait_lock_.release(); + return; + } + /* Spurious wake / lost race — keep waiting. */ + } +} + +/* ------------------------------------------------------------------ */ +/* unlock */ +/* ------------------------------------------------------------------ */ + +void Mutex::unlock() +{ + TaskControlBlock* curr = current_task(); + + wait_lock_.acquire(); + + if (owner_ != curr) { + wait_lock_.release(); + kernel_panic("Mutex::unlock: not the owner"); + } + + owner_ = nullptr; + + if (!wait_list_.is_empty()) { + list_node* first = wait_list_.next; + MutexWaiter* w = list_entry(first); + TaskControlBlock* victim = w->task; + + /* Race-tolerant wake: only enqueue if still sleeping. If the + * waiter is already Runnable (e.g. it was already woken by a + * timer-driven schedule reentry and didn't yet retry the lock), + * sched_enqueue would double-link it. */ + if (victim->state == TaskState::Sleeping) { + victim->state = TaskState::Runnable; + sched_enqueue(victim); + } + } + + wait_lock_.release(); +} + +/* ------------------------------------------------------------------ */ +/* Debug accessors */ +/* ------------------------------------------------------------------ */ + +bool Mutex::is_owned_by_current() const +{ + return owner_ == current_task(); +} diff --git a/kernel/sync/spinlock.cc b/kernel/sync/spinlock.cc new file mode 100644 index 0000000..f649fe7 --- /dev/null +++ b/kernel/sync/spinlock.cc @@ -0,0 +1,120 @@ +/* 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 +#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_.store(0, __ATOMIC_RELAXED); + 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 (locked_.load(__ATOMIC_RELAXED)) + ; + // Inner: attempt atomic exchange with acquire semantics + if (locked_.exchange(1, __ATOMIC_ACQUIRE) == 0) + break; + } + + // Step 4: record owner + cpu_id_ = arch::cpu::id(); + + // Step 5: bump preempt_count — prevents schedule() from preempting + TaskControlBlock* curr = current_task(); + if (curr) curr->preempt_count++; +} + +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 + locked_.store(0, __ATOMIC_RELEASE); + + // Step 4: drop preempt_count + TaskControlBlock* curr = current_task(); + if (curr) curr->preempt_count--; + + // Step 5: 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/kernel/sync/spinlock.h b/kernel/sync/spinlock.h new file mode 100644 index 0000000..a1369d1 --- /dev/null +++ b/kernel/sync/spinlock.h @@ -0,0 +1,61 @@ +/* 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 +#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: + lume::atomic locked_; // 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/kernel/sys_fs.cc b/kernel/sys_fs.cc new file mode 100644 index 0000000..1d26fdd --- /dev/null +++ b/kernel/sys_fs.cc @@ -0,0 +1,679 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * sys_fs.cc — Filesystem-related syscall handlers + * + * Implements: openat, close, read, write, lseek, fstat, getdents64, + * dup, dup3, getcwd, chdir, mkdirat, unlinkat + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static files_struct* current_files() +{ + TaskControlBlock* t = current_task(); + return t ? t->files : nullptr; +} + +static fs_struct* current_fs() +{ + TaskControlBlock* t = current_task(); + return t ? t->fs : nullptr; +} + +/* ================================================================== */ +/* sys_openat(dirfd, pathname, flags, mode) */ +/* ================================================================== */ + +int64 sys_openat(uint64 a0, uint64 a1, uint64 a2, + uint64 a3, uint64 /*a4*/, uint64 /*a5*/) +{ + int dirfd = static_cast(a0); + const char* pathname = reinterpret_cast(a1); + int flags = static_cast(a2); + uint32 mode = static_cast(a3); + + files_struct* files = current_files(); + if (!files) return -EMFILE; + + + /* Pathname lookup */ + path p{}; + int err = filename_lookup(dirfd, pathname, 0, &p); + + if (err == -ENOENT && (flags & O_CREAT)) { + /* Create: look up the parent directory */ + /* Find last '/' to split parent path / filename */ + const char* last_slash = nullptr; + for (const char* s = pathname; *s; s++) { + if (*s == '/') last_slash = s; + } + + if (!last_slash || last_slash == pathname) { + /* File in root or current directory */ + path parent{}; + if (last_slash == pathname) { + /* Absolute: parent is root */ + parent.de = g_root_dentry; + parent.mnt = g_root_mnt; + } else { + /* Relative: parent is cwd */ + fs_struct* fs = current_fs(); + if (!fs) return -ENOENT; + parent = fs->pwd; + } + + const char* name = last_slash ? last_slash + 1 : pathname; + inode* dir_ip = parent.de->d_inode; + if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->create) { + return -EACCES; + } + + /* Create the inode via the directory's i_op->create */ + qstr q{}; + q.name = name; + q.len = 0; + for (const char* s = name; *s; s++) q.len++; + dentry* new_de = d_alloc(parent.de, &q); + if (!new_de) return -ENOMEM; + + + err = dir_ip->i_op->create(dir_ip, new_de, mode & ~S_IFMT, false); + if (err) { + /* TODO: free new_de */ + return err; + } + + p.de = new_de; + p.mnt = parent.mnt; + } else { + /* TODO: multi-component parent lookup */ + return -ENOENT; + } + } else if (err) { + return err; + } + + /* Allocate fd */ + int fd = alloc_fd(files, 0); + if (fd < 0) { + return fd; + } + + /* Allocate file object */ + file* fp = alloc_empty_file(flags); + if (!fp) { + /* TODO: release fd */ + return -ENFILE; + } + + fp->f_inode = p.de->d_inode; + fp->f_path = p; + fp->f_op = p.de->d_inode->i_fop; + fp->f_pos = 0; + + /* Chardev: look up cdev by i_rdev and replace f_op */ + if (S_ISCHR(fp->f_inode->i_mode)) { + extern int devfs_chrdev_open(inode* ip, file* fp); + err = devfs_chrdev_open(fp->f_inode, fp); + if (err) { + fput(fp); + return err; + } + } else if (fp->f_op && fp->f_op->open) { + err = fp->f_op->open(fp->f_inode, fp); + if (err) { + fput(fp); + return err; + } + } + + /* O_TRUNC for regular files */ + if ((flags & O_TRUNC) && S_ISREG(fp->f_inode->i_mode)) { + fp->f_inode->i_size = 0; + } + + fd_install(files, fd, fp); + return fd; +} + +/* ================================================================== */ +/* sys_close(fd) */ +/* ================================================================== */ + +int64 sys_close(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + files_struct* files = current_files(); + if (!files) return -EBADF; + return close_fd(files, fd); +} + +/* ================================================================== */ +/* sys_read(fd, buf, count) */ +/* ================================================================== */ + +int64 sys_read(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + char* buf = reinterpret_cast(a1); + uint64 count = a2; + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + if (!(fp->f_mode & FMODE_READ)) return -EBADF; + + return vfs_read(fp, buf, count, &fp->f_pos); +} + +/* ================================================================== */ +/* sys_write(fd, buf, count) */ +/* ================================================================== */ + +int64 sys_write(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + const char* buf = reinterpret_cast(a1); + uint64 count = a2; + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + if (!(fp->f_mode & FMODE_WRITE)) return -EBADF; + + return vfs_write(fp, buf, count, &fp->f_pos); +} + +/* ================================================================== */ +/* sys_lseek(fd, offset, whence) */ +/* ================================================================== */ + +int64 sys_lseek(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + int64 offset = static_cast(a1); + int whence = static_cast(a2); + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + return vfs_llseek(fp, offset, whence); +} + +/* ================================================================== */ +/* sys_fstat(fd, statbuf) */ +/* ================================================================== */ + +int64 sys_fstat_wrapper(uint64 a0, uint64 a1, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + auto* statbuf = reinterpret_cast(a1); + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + return vfs_fstat(fp, statbuf); +} + +/* ================================================================== */ +/* sys_getdents64(fd, dirp, count) */ +/* ================================================================== */ + +struct linux_dirent64 { + uint64 d_ino; + int64 d_off; + uint16 d_reclen; + uint8 d_type; + char d_name[]; +}; + +struct getdents_ctx { + dir_context ctx; + char* buf; + int count; + int written; + int error; +}; + +static bool filldir64(dir_context* ctx, const char* name, int namelen, + int64 offset, uint64 ino, unsigned int d_type) +{ + auto* gctx = container_of(ctx, getdents_ctx, ctx); + + /* Calculate record length (rounded up to 8 bytes) */ + int reclen = static_cast( + (sizeof(linux_dirent64) + namelen + 1 + 7) & ~7ULL); + + if (gctx->written + reclen > gctx->count) { + return false; /* Buffer full */ + } + + auto* dirent = reinterpret_cast( + gctx->buf + gctx->written); + + dirent->d_ino = ino; + dirent->d_off = offset + 1; + dirent->d_reclen = static_cast(reclen); + dirent->d_type = static_cast(d_type); + + /* Copy name */ + for (int i = 0; i < namelen; i++) { + dirent->d_name[i] = name[i]; + } + dirent->d_name[namelen] = '\0'; + + gctx->written += reclen; + return true; +} + +int64 sys_getdents64(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + char* buf = reinterpret_cast(a1); + int count = static_cast(a2); + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + if (!fp->f_op || !fp->f_op->iterate_shared) { + return -ENOTDIR; + } + + getdents_ctx gctx{}; + gctx.ctx.actor = filldir64; + gctx.ctx.pos = fp->f_pos; + gctx.buf = buf; + gctx.count = count; + gctx.written = 0; + gctx.error = 0; + + fp->f_op->iterate_shared(fp, &gctx.ctx); + fp->f_pos = gctx.ctx.pos; + + return gctx.written; +} + +/* ================================================================== */ +/* sys_dup(oldfd) */ +/* ================================================================== */ + +int64 sys_dup(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int oldfd = static_cast(a0); + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, oldfd); + if (!fp) return -EBADF; + + int newfd = alloc_fd(files, 0); + if (newfd < 0) return newfd; + + get_file(fp); + fd_install(files, newfd, fp); + return newfd; +} + +/* ================================================================== */ +/* sys_dup3(oldfd, newfd, flags) */ +/* ================================================================== */ + +int64 sys_dup3(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int oldfd = static_cast(a0); + int newfd = static_cast(a1); + int flags = static_cast(a2); + + files_struct* files = current_files(); + if (!files) return -EBADF; + + return do_dup2(files, oldfd, newfd, flags); +} + +/* ================================================================== */ +/* sys_getcwd(buf, size) */ +/* ================================================================== */ + +int64 sys_getcwd(uint64 a0, uint64 a1, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + char* buf = reinterpret_cast(a0); + uint64 size = a1; + + if (!buf || size == 0) return -EINVAL; + + fs_struct* fs = current_fs(); + if (!fs) return -ENOENT; + + char tmp[256]; + char* result = d_path(&fs->pwd, tmp, sizeof(tmp)); + if (!result) return -ERANGE; + + /* Calculate length */ + int len = 0; + for (const char* s = result; *s; s++) len++; + len++; /* Include NUL */ + + if (static_cast(len) > size) return -ERANGE; + + for (int i = 0; i < len; i++) { + buf[i] = result[i]; + } + + return len; +} + +/* ================================================================== */ +/* sys_chdir(pathname) */ +/* ================================================================== */ + +int64 sys_chdir(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + const char* pathname = reinterpret_cast(a0); + + path p{}; + int err = filename_lookup(AT_FDCWD, pathname, LOOKUP_DIRECTORY, &p); + if (err) return err; + + if (!S_ISDIR(p.de->d_inode->i_mode)) { + path_put(&p); + return -ENOTDIR; + } + + fs_struct* fs = current_fs(); + if (!fs) { + path_put(&p); + return -ENOENT; + } + + set_fs_pwd(fs, &p); + path_put(&p); + return 0; +} + +/* ================================================================== */ +/* sys_mkdirat(dirfd, pathname, mode) */ +/* ================================================================== */ + +int64 sys_mkdirat(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int dirfd = static_cast(a0); + const char* pathname = reinterpret_cast(a1); + uint32 mode = static_cast(a2); + + /* Walk to the parent directory */ + /* For simplicity, support only "single component" paths for now */ + path parent{}; + int err; + + if (pathname[0] == '/') { + parent.de = g_root_dentry; + parent.mnt = g_root_mnt; + pathname++; /* skip leading / */ + } else { + fs_struct* fs = current_fs(); + if (!fs) return -ENOENT; + parent = fs->pwd; + (void)dirfd; /* TODO: respect dirfd for AT_FDCWD check */ + } + + inode* dir_ip = parent.de->d_inode; + if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mkdir) { + return -EPERM; + } + + qstr q{}; + q.name = pathname; + q.len = 0; + for (const char* s = pathname; *s; s++) q.len++; + dentry* new_de = d_alloc(parent.de, &q); + if (!new_de) return -ENOMEM; + + err = dir_ip->i_op->mkdir(dir_ip, new_de, mode | S_IFDIR); + if (err) { + return err; + } + + return 0; +} + +/* ================================================================== */ +/* sys_unlinkat(dirfd, pathname, flags) */ +/* ================================================================== */ + +int64 sys_unlinkat(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + (void)a0; (void)a1; (void)a2; + /* TODO: implement unlink/rmdir */ + return -ENOSYS; +} + +/* ================================================================== */ +/* sys_writev(fd, iov, iovcnt) */ +/* ================================================================== */ + +struct iovec { + void* iov_base; + uint64 iov_len; +}; + +int64 sys_writev(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + const iovec* iov = reinterpret_cast(a1); + int iovcnt = static_cast(a2); + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + if (!(fp->f_mode & FMODE_WRITE)) return -EBADF; + + int64 total = 0; + for (int i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) continue; + int64 ret = vfs_write(fp, static_cast(iov[i].iov_base), + iov[i].iov_len, &fp->f_pos); + if (ret < 0) { + return total > 0 ? total : ret; + } + total += ret; + if (static_cast(ret) < iov[i].iov_len) break; + } + return total; +} + +/* ================================================================== */ +/* sys_ioctl — dispatch to file_operations */ +/* ================================================================== */ + +int64 sys_ioctl(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + unsigned int cmd = static_cast(a1); + uint64 arg = a2; + + TaskControlBlock* curr = current_task(); + if (!curr || !curr->files) return -EBADF; + + file* fp = fcheck(curr->files, fd); + if (!fp) return -EBADF; + + if (fp->f_op && fp->f_op->unlocked_ioctl) + return fp->f_op->unlocked_ioctl(fp, cmd, arg); + + return -ENOTTY; +} + +/* ================================================================== */ +/* sys_pipe2(fds, flags) */ +/* ================================================================== */ + +#include + +int64 sys_pipe2(uint64 a0, uint64 a1, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int* user_fds = reinterpret_cast(a0); + int flags = static_cast(a1); + + files_struct* files = current_files(); + if (!files) return -EMFILE; + + int fds[2]; + int err = do_pipe2(files, fds, flags); + if (err) return err; + + user_fds[0] = fds[0]; + user_fds[1] = fds[1]; + return 0; +} + +/* ================================================================== */ +/* sys_fcntl(fd, cmd, arg) */ +/* ================================================================== */ + +/* fcntl commands — Linux ABI values */ +inline constexpr int F_DUPFD = 0; +inline constexpr int F_GETFD = 1; +inline constexpr int F_SETFD = 2; +inline constexpr int F_GETFL = 3; +inline constexpr int F_SETFL = 4; +inline constexpr int F_DUPFD_CLOEXEC = 1030; + +inline constexpr int FD_CLOEXEC = 1; + +/* Bitmap helpers (duplicated from fd.cc — TODO: move to shared header) */ +static inline bool fcntl_bit_test(const uint64* bmap, int bit) { + return (bmap[bit / 64] >> (bit % 64)) & 1; +} +static inline void fcntl_bit_set(uint64* bmap, int bit) { + bmap[bit / 64] |= (1ULL << (bit % 64)); +} +static inline void fcntl_bit_clear(uint64* bmap, int bit) { + bmap[bit / 64] &= ~(1ULL << (bit % 64)); +} + +int64 sys_fcntl(uint64 a0, uint64 a1, uint64 a2, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int fd = static_cast(a0); + int cmd = static_cast(a1); + uint64 arg = a2; + + files_struct* files = current_files(); + if (!files) return -EBADF; + + file* fp = fcheck(files, fd); + if (!fp) return -EBADF; + + switch (cmd) { + case F_DUPFD: + case F_DUPFD_CLOEXEC: { + int new_fd = alloc_fd(files, static_cast(arg)); + if (new_fd < 0) return new_fd; + get_file(fp); + fd_install(files, new_fd, fp); + if (cmd == F_DUPFD_CLOEXEC) { + LockGuard guard(files->file_lock); + fcntl_bit_set(files->close_on_exec_init, new_fd); + } + return new_fd; + } + + case F_GETFD: { + LockGuard guard(files->file_lock); + return fcntl_bit_test(files->close_on_exec_init, fd) ? FD_CLOEXEC : 0; + } + + case F_SETFD: { + LockGuard guard(files->file_lock); + if (arg & FD_CLOEXEC) + fcntl_bit_set(files->close_on_exec_init, fd); + else + fcntl_bit_clear(files->close_on_exec_init, fd); + return 0; + } + + case F_GETFL: + return fp->f_flags; + + case F_SETFL: + /* Only allow changing O_APPEND, O_NONBLOCK, O_ASYNC */ + fp->f_flags = (fp->f_flags & ~(O_APPEND | O_NONBLOCK)) | + (static_cast(arg) & (O_APPEND | O_NONBLOCK)); + return 0; + + default: + return -EINVAL; + } +} + +/* ================================================================== */ +/* sys_socket (SYS_socket = 198) & sys_sendto (SYS_sendto = 206) */ +/* ================================================================== */ + +int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64) +{ + /* + * COMPATIBILITY STUB: + * LumeOS currently lacks a networking stack and Unix Domain Sockets (AF_UNIX). + * We return -EAFNOSUPPORT (Address family not supported by protocol) as expected + * by Linux applications (like BusyBox syslogd connecting to /dev/log). + */ + return -EAFNOSUPPORT; +} + +int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64) +{ + /* COMPATIBILITY STUB */ + return -EAFNOSUPPORT; +} diff --git a/kernel/sys_info.cc b/kernel/sys_info.cc new file mode 100644 index 0000000..79baab0 --- /dev/null +++ b/kernel/sys_info.cc @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * System Information Calls + * + * Implements: uname + * + * Reference: docs/specs/syscall.md §7.3 + */ + +#include +#include +#include + +/* Linux-compatible utsname structure (65-byte fields) */ +struct utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; + char domainname[65]; +}; + +/* Simple safe string copy */ +static void safe_strncpy(char* dst, const char* src, uint64 n) +{ + uint64 i = 0; + for (; i < n - 1 && src[i] != '\0'; i++) + dst[i] = src[i]; + dst[i] = '\0'; +} + +int64 sys_uname(uint64 buf_ptr, uint64, uint64, uint64, uint64, uint64) +{ + utsname info = {}; + safe_strncpy(info.sysname, "LumeOS", sizeof(info.sysname)); + safe_strncpy(info.nodename, "lume", sizeof(info.nodename)); + safe_strncpy(info.release, "0.1.0", sizeof(info.release)); + safe_strncpy(info.version, "Phase 2", sizeof(info.version)); + safe_strncpy(info.machine, "riscv64", sizeof(info.machine)); + safe_strncpy(info.domainname, "(none)", sizeof(info.domainname)); + + if (copy_to_user(buf_ptr, &info, sizeof(info)) < 0) + return -EFAULT; + + return 0; +} diff --git a/kernel/sys_misc.cc b/kernel/sys_misc.cc new file mode 100644 index 0000000..48eb47b --- /dev/null +++ b/kernel/sys_misc.cc @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Miscellaneous System Calls — busybox bootstrap set + * + * Each syscall follows the Linux kernel semantics exactly. + * Reference: Linux kernel/sys.c, kernel/signal.c, kernel/time/posix-timers.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ====================================================================== + * sys_reboot — Linux kernel/reboot.c + * + * Validates magic numbers and performs the requested action. + * For now, we only support LINUX_REBOOT_CMD_CAD_ON/OFF (ignore) + * and reject everything else with -EPERM (no CAP_SYS_BOOT). + * ====================================================================== */ + +static constexpr uint32 LINUX_REBOOT_MAGIC1 = 0xfee1dead; +static constexpr uint32 LINUX_REBOOT_MAGIC2 = 672274793; /* 0x28121969 */ +static constexpr uint32 LINUX_REBOOT_MAGIC2A = 85072278; +static constexpr uint32 LINUX_REBOOT_MAGIC2B = 369367448; +static constexpr uint32 LINUX_REBOOT_MAGIC2C = 537993216; + +int64 sys_reboot(uint64 magic1, uint64 magic2, uint64 cmd, + uint64, uint64, uint64) +{ + /* Linux: kernel/reboot.c — validate magic numbers first */ + if (static_cast(magic1) != LINUX_REBOOT_MAGIC1) + return -EINVAL; + if (static_cast(magic2) != LINUX_REBOOT_MAGIC2 && + static_cast(magic2) != LINUX_REBOOT_MAGIC2A && + static_cast(magic2) != LINUX_REBOOT_MAGIC2B && + static_cast(magic2) != LINUX_REBOOT_MAGIC2C) + return -EINVAL; + + /* We don't have CAP_SYS_BOOT yet — reject with -EPERM. + * This matches Linux behavior for unprivileged callers. + * Busybox handles this gracefully and continues. */ + (void)cmd; + printk(KERN_INFO "reboot: cmd=%llu (not implemented, returning -EPERM)\n", cmd); + return -EPERM; +} + +/* ====================================================================== + * sys_prctl — Linux kernel/sys.c + * + * Process control. Most options are optional for early boot. + * We implement the minimum set busybox needs. + * ====================================================================== */ + +/* prctl option constants (from ) */ +static constexpr int PR_SET_NAME = 15; +static constexpr int PR_GET_NAME = 16; +static constexpr int PR_SET_PDEATHSIG = 1; +static constexpr int PR_GET_PDEATHSIG = 2; + +int64 sys_prctl(uint64 option, uint64 arg2, uint64 arg3, + uint64 arg4, uint64 arg5, uint64) +{ + switch (static_cast(option)) { + case PR_SET_PDEATHSIG: + /* Linux: set signal delivered when parent dies. + * Silently accept — we don't track pdeath_signal yet. */ + return 0; + case PR_GET_PDEATHSIG: { + /* Linux: return current pdeath_signal (0 = none) */ + int32 zero = 0; + if (copy_to_user(arg2, &zero, sizeof(zero)) != 0) + return -EFAULT; + return 0; + } + case PR_SET_NAME: { + /* Linux: set comm (task name), max 16 chars */ + /* TODO: copy_from_user the string */ + return 0; + } + case PR_GET_NAME: { + /* Linux: get comm (task name) */ + TaskControlBlock* curr = current_task(); + if (!curr) return -ESRCH; + const char* name = curr->name ? curr->name : ""; + uint64 len = 0; + while (name[len] && len < 15) len++; + char buf[16] = {}; + for (uint64 i = 0; i < len; i++) buf[i] = name[i]; + if (copy_to_user(arg2, buf, 16) != 0) + return -EFAULT; + return 0; + } + default: + /* Unknown prctl — Linux returns -EINVAL for unrecognized options */ + return -EINVAL; + } +} + +/* ====================================================================== + * Session / Process-Group syscalls + * + * All four operate on the caller's signal_struct (or a target task's). + * Identity lives in signal_struct, so these become straightforward reads + * and pgrp-hash updates. We mirror Linux kernel/sys.c semantics closely. + * ====================================================================== */ + +static TaskControlBlock* find_task_by_pid(int32 pid) +{ + extern list_node g_all_tasks; + extern Spinlock g_task_list_lock; + LockGuard guard(g_task_list_lock); + list_node* node = g_all_tasks.next; + while (node != &g_all_tasks) { + auto* task = list_entry(node); + if (task->pid == pid) return task; + node = node->next; + } + return nullptr; +} + +/* + * sys_setsid — Linux kernel/sys.c:ksys_setsid() + * + * Caller becomes leader of a new session+pgrp. Refused if a pgrp with + * id == caller pid already exists with members other than the caller — + * that's Linux's "process group id already exists" check, which prevents + * collisions when a non-leader tries to setsid. + */ +int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ESRCH; + + /* Detect collision: another pgrp already uses our pid as pgid. */ + bool conflict = false; + for_each_pgrp_member(curr->pid, [&](signal_struct* sig) { + if (sig != curr->signal) conflict = true; + }); + if (conflict) return -EPERM; + + /* Move to new pgrp (creates one if missing), update sid, drop ctty. */ + change_pgid(curr->signal, curr->pid); + + /* Detach the controlling terminal. If this task is the session + * leader that owns the tty (tty->session == caller's old sid), + * also clear the tty's session/pgrp so the next session-leader + * that opens the device can claim it. Without this, tty->session + * permanently sticks to the abandoned sid and every subsequent + * tty_acquire_ctty() bails with "owned by another session" — + * which is exactly what kept BusyBox's child sh from picking the + * console back up after init's setsid(). + * + * Linux's behavior is similar in spirit (disassociate_ctty when + * the leader exits), but it triggers from exit, not setsid. Our + * simpler "clear-on-give-up" matches the way our acquisition path + * is written. */ + tty_struct* old_tty = nullptr; + int32 old_sid = 0; + { + LockGuard guard(curr->signal->lock); + old_tty = curr->signal->tty; + old_sid = curr->signal->sid; + curr->signal->sid = curr->pid; + curr->signal->tty = nullptr; + curr->signal->tty_old_pgrp = 0; + } + if (old_tty) { + LockGuard guard(old_tty->lock); + if (old_tty->session == old_sid) { + old_tty->session = 0; + old_tty->pgrp = 0; + } + } + return curr->pid; +} + +/* + * sys_setpgid — Linux kernel/sys.c:ksys_setpgid() + * + * Move a process between groups within the same session. Allowed: + * - caller targeting self or a child + * - target is not a session leader + * - target stays in caller's session + * - target hasn't exec()'d (we don't track did_exec yet, so skip) + * - new pgid is either == target's pid (new group) or names a pgrp + * that already exists in the same session + */ +int64 sys_setpgid(uint64 pid, uint64 pgid, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ESRCH; + + int32 target_pid = static_cast(pid); + int32 target_pgid = static_cast(pgid); + + if (target_pid == 0) target_pid = curr->pid; + if (target_pgid == 0) target_pgid = target_pid; + if (target_pgid < 0) return -EINVAL; + + TaskControlBlock* target = (target_pid == curr->pid) + ? curr + : find_task_by_pid(target_pid); + if (!target || !target->signal) return -ESRCH; + + /* Target must be caller or a child of caller. */ + if (target != curr && target->parent != curr) return -ESRCH; + + /* Same session as caller. */ + if (target->signal->sid != curr->signal->sid) return -EPERM; + + /* Cannot move a session leader into a different pgrp. */ + if (target->signal->sid == target->pid) return -EPERM; + + /* If target_pgid != target_pid, the pgrp must already exist in + * the same session. pgrp existence == any signal_struct with + * pgid == target_pgid AND sid == caller's sid. */ + if (target_pgid != target->pid) { + bool found = false; + for_each_pgrp_member(target_pgid, [&](signal_struct* sig) { + if (sig->sid == curr->signal->sid) found = true; + }); + if (!found) return -EPERM; + } + + change_pgid(target->signal, target_pgid); + return 0; +} + +/* + * sys_getpgid — return process group ID of `pid` (0 = self). + */ +int64 sys_getpgid(uint64 pid, uint64, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ESRCH; + + if (pid == 0) return curr->signal->pgid; + + TaskControlBlock* target = find_task_by_pid(static_cast(pid)); + if (!target || !target->signal) return -ESRCH; + return target->signal->pgid; +} + +/* + * sys_getsid — return session ID of `pid` (0 = self). + */ +int64 sys_getsid(uint64 pid, uint64, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->signal) return -ESRCH; + + if (pid == 0) return curr->signal->sid; + + TaskControlBlock* target = find_task_by_pid(static_cast(pid)); + if (!target || !target->signal) return -ESRCH; + + /* Linux: -EPERM if target's session differs from caller's and we're + * in a different namespace. We have one namespace, so always allow. */ + return target->signal->sid; +} + +/* ====================================================================== + * sys_geteuid / sys_getegid — Linux kernel/sys.c + * + * Return effective UID/GID. We run everything as root (0). + * ====================================================================== */ + +int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + /* Linux: return current_euid(). We are always root. */ + return 0; +} + +int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + /* Linux: return current_egid(). We are always root. */ + return 0; +} + +/* ====================================================================== + * sys_sigaltstack — Linux kernel/signal.c + * + * Set/get alternate signal stack. For now, accept and ignore + * (busybox doesn't critically need it for shell operation). + * ====================================================================== */ + +int64 sys_sigaltstack(uint64 uss_ptr, uint64 uoss_ptr, + uint64, uint64, uint64, uint64) +{ + /* Linux: if uoss != NULL, return current alt stack info. + * We don't have alt stacks yet — report SS_DISABLE. */ + if (uoss_ptr != 0) { + /* stack_t { void* ss_sp; int ss_flags; size_t ss_size; } + * On RISC-V LP64: 8 + 4 + padding + 8 = 24 bytes typically, + * but the ABI packs it as {uint64, int32, pad, uint64}. */ + struct { + uint64 ss_sp; + int32 ss_flags; + int32 _pad; + uint64 ss_size; + } oss = { 0, 2 /* SS_DISABLE */, 0, 0 }; + if (copy_to_user(uoss_ptr, &oss, sizeof(oss)) != 0) + return -EFAULT; + } + + /* uss: silently accept (we'll need it for proper signal handling later) */ + (void)uss_ptr; + return 0; +} + +/* ====================================================================== + * sys_clock_gettime — Linux kernel/time/posix-timers.c + * + * Get the current time for a given clock. + * We use the RISC-V cycle counter as a monotonic source. + * ====================================================================== */ + +static constexpr int CLOCK_REALTIME = 0; +static constexpr int CLOCK_MONOTONIC = 1; + +int64 sys_clock_gettime(uint64 clk_id, uint64 tp_ptr, + uint64, uint64, uint64, uint64) +{ + if (tp_ptr == 0) return -EFAULT; + + /* Read RISC-V time CSR (mtime mapped to SBI) */ + uint64 ticks = arch::cpu::read_time(); + + /* Convert to seconds + nanoseconds. + * QEMU virt timebase-frequency is 10MHz. */ + constexpr uint64 kTimebaseHz = 10'000'000ULL; + uint64 sec = ticks / kTimebaseHz; + uint64 nsec = (ticks % kTimebaseHz) * (1'000'000'000ULL / kTimebaseHz); + + struct timespec64 { + int64 tv_sec; + int64 tv_nsec; + }; + + timespec64 ts; + ts.tv_sec = static_cast(sec); + ts.tv_nsec = static_cast(nsec); + + (void)clk_id; /* We treat all clocks as monotonic for now */ + + if (copy_to_user(tp_ptr, &ts, sizeof(ts)) != 0) + return -EFAULT; + + return 0; +} diff --git a/kernel/sys_mm.cc b/kernel/sys_mm.cc new file mode 100644 index 0000000..bfa067c --- /dev/null +++ b/kernel/sys_mm.cc @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Memory Management System Calls + * + * Implements: brk, mmap (anonymous only), munmap, mprotect. + * + * Reference: docs/specs/syscall.md §7.2 + */ + +#include +#include +#include +#include +#include +#include +#include + + +/* Page alignment helpers */ +static inline uint64 page_align_up(uint64 x) +{ + return (x + kPageSize - 1) & ~(kPageSize - 1); +} + +static inline uint64 page_align_down(uint64 x) +{ + return x & ~(kPageSize - 1); +} + +/* ====================================================================== + * sys_brk + * ====================================================================== */ + +int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64) +{ + VmSpace* mm = current_task()->mm; + if (!mm) + return -ENOMEM; /* kernel threads have no user address space */ + + if (new_brk == 0) { + return static_cast(mm->brk_end); + } + + if (mm->brk_start == 0) { + uint64 init_brk = page_align_up(new_brk); + mm->brk_start = init_brk; + mm->brk_end = init_brk; + } + + if (new_brk < mm->brk_start) { + return -EINVAL; + } + + uint64 new_end = page_align_up(new_brk); + if (new_end > mm->brk_end) { + uint64 grow = new_end - mm->brk_end; + int ret = vmm_map_user(mm, mm->brk_end, grow, VM_READ | VM_WRITE); + if (ret < 0) { + return ret; + } + } else if (new_end < mm->brk_end) { + uint64 shrink = mm->brk_end - new_end; + int ret = vmm_unmap_user(mm, new_end, shrink); + if (ret < 0) { + return ret; + } + } + + mm->brk_end = new_end; + return static_cast(new_brk); +} + +/* ====================================================================== + * sys_mmap + * ====================================================================== */ + +int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, + uint64 flags, uint64 fd, uint64 offset) +{ + printk("[mmap] addr=%llx len=%llx prot=%llx flags=%llx fd=%lld off=%llx\n", + addr, length, prot, flags, static_cast(fd), offset); + + if (length == 0) return -EINVAL; + length = page_align_up(length); + if (offset % kPageSize != 0) return -EINVAL; + + if ((flags & MAP_PRIVATE) && (flags & MAP_SHARED)) return -EINVAL; + if (!(flags & MAP_PRIVATE) && !(flags & MAP_SHARED)) return -EINVAL; + + VmSpace* mm = current_task()->mm; + if (!mm) return -ENOMEM; + + /* Phase 2: only anonymous private mappings */ + if (!(flags & MAP_ANONYMOUS)) { + printk("[mmap] REJECTED: file-backed mmap not implemented\n"); + return -ENOSYS; /* File-backed mmap is Phase 3 */ + } + if (flags & MAP_SHARED) + return -ENOSYS; /* Shared mappings are Phase 3 */ + + (void)fd; + + /* Convert POSIX prot → MI permission flags */ + uint64 vm_perm = 0; + if (prot & PROT_READ) vm_perm |= VM_READ; + if (prot & PROT_WRITE) vm_perm |= VM_WRITE; + if (prot & PROT_EXEC) vm_perm |= VM_EXEC; + vm_perm |= VM_USER; + + uint64 va = addr; + if (flags & MAP_FIXED) { + if (va % kPageSize != 0) return -EINVAL; + } else if (va != 0) { + va = page_align_down(va); + } + + if (va == 0) { + va = mm_find_free_va(mm, length, kPageSize); + if (!va) return -ENOMEM; + } + + if (flags & MAP_FIXED) { + int ret = vmm_unmap_user(mm, va, length); + if (ret < 0) return ret; + } + + int ret = vmm_map_user(mm, va, length, vm_perm); + if (ret < 0 && !(flags & MAP_FIXED) && addr != 0) { + uint64 fallback = mm_find_free_va(mm, length, kPageSize); + if (!fallback) return ret; + ret = vmm_map_user(mm, fallback, length, vm_perm); + if (ret < 0) return ret; + va = fallback; + } else if (ret < 0) { + return ret; + } + + return static_cast(va); +} + +/* ====================================================================== + * sys_munmap + * ====================================================================== */ + +int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64) +{ + if (length == 0) return -EINVAL; + if (addr % kPageSize != 0) return -EINVAL; + length = page_align_up(length); + + VmSpace* mm = current_task()->mm; + if (!mm) return -EINVAL; + + return vmm_unmap_user(mm, addr, length); +} + +/* ====================================================================== + * sys_mprotect — Phase 2 stub + * ====================================================================== */ + +int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, + uint64, uint64, uint64) +{ + if (len == 0) return -EINVAL; + if (addr % kPageSize != 0) return -EINVAL; + + VmSpace* mm = current_task()->mm; + if (!mm) return -ENOMEM; + + uint64 vm_perm = 0; + if (prot & PROT_READ) vm_perm |= VM_READ; + if (prot & PROT_WRITE) vm_perm |= VM_WRITE; + if (prot & PROT_EXEC) vm_perm |= VM_EXEC; + + return vmm_protect_user(mm, addr, len, vm_perm); +} diff --git a/kernel/sys_process.cc b/kernel/sys_process.cc new file mode 100644 index 0000000..438ad1b --- /dev/null +++ b/kernel/sys_process.cc @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Process Control System Calls + * + * Implements: exit, exit_group, getpid, gettid, getppid, + * sched_yield, clone (stub), wait4 (stub), + * set_tid_address, getuid, getgid. + * + * Reference: docs/specs/syscall.md §7.1 + */ + +#include "lume/klog.h" +#include +#include +#include +#include +#include +#include + +/* External: task lifecycle (kernel/task.cc) */ +extern void task_exit(int code); +extern int32 do_wait4(int32 wait_pid, int options, int32* exit_code); +extern int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, + uint64 child_stack, uint64 parent_tidptr, + uint64 child_tidptr, uint64 tls); +extern int32 task_fork(arch::TrapFrame* tf); + +/* ====================================================================== + * sys_exit / sys_exit_group + * ====================================================================== */ + +int64 sys_exit(uint64 code, uint64, uint64, uint64, uint64, uint64) +{ + task_exit(static_cast(code & 0xFF)); + __builtin_unreachable(); +} + +int64 sys_exit_group(uint64 code, uint64, uint64, uint64, uint64, uint64) +{ + /* Phase 2: single-threaded, equivalent to sys_exit. + * Phase 3: kill all threads in tgid then exit. */ + task_exit(static_cast(code & 0xFF)); + __builtin_unreachable(); +} + +/* ====================================================================== + * sys_getpid / sys_gettid / sys_getppid + * ====================================================================== */ + +int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + return current_task()->tgid; /* Linux: getpid() returns tgid */ +} + +int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + return current_task()->pid; /* Linux: gettid() returns real tid */ +} + +int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* parent = current_task()->parent; + return parent ? parent->tgid : 0; +} + +/* ====================================================================== + * sys_sched_yield + * ====================================================================== */ + +int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64) +{ + sched_yield(); + return 0; +} + +int64 sys_clone_wrapper(uint64 flags, uint64 child_stack, + uint64 ptid, uint64 ctid, uint64 tls, uint64) +{ + arch::TrapFrame* tf = current_task()->trapframe; + if (!tf) return -EINVAL; + + return task_clone(tf, flags, child_stack, ptid, ctid, tls); +} + +/* ======================================================================== + * sys_wait4 — Wait for child to exit + * ======================================================================== */ + +int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr, + uint64 options, uint64, uint64, uint64) +{ + int32 exit_code = 0; + int32 child_pid = do_wait4(static_cast(pid), + static_cast(options), + &exit_code); + + if (child_pid < 0) return child_pid; + + /* exit_code is already in Linux wstatus format */ + if (wstatus_ptr != 0) { + if (copy_to_user(wstatus_ptr, &exit_code, sizeof(int)) < 0) + return -EFAULT; + } + + return child_pid; +} + +/* ====================================================================== + * sys_set_tid_address — musl libc calls this very early + * ====================================================================== */ + +int64 sys_set_tid_address(uint64 tidptr, uint64, uint64, + uint64, uint64, uint64) +{ + /* Store clear_child_tid pointer — on exit, kernel writes 0 here + * and does futex_wake (enables pthread_join). */ + current_task()->clear_child_tid = tidptr; + return current_task()->pid; +} + +/* ====================================================================== + * sys_getuid / sys_getgid — Phase 2: always root + * ====================================================================== */ + +int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + return 0; /* root */ +} + +int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64) +{ + return 0; /* root */ +} + +/* ====================================================================== + * sys_nanosleep (SYS_nanosleep = 101) + * ====================================================================== */ +#include +#include +#include + +struct linux_timespec { + int64 tv_sec; + int64 tv_nsec; +}; + +static void nanosleep_wakeup(TimerList* t) { + TaskControlBlock* task = static_cast(t->data); + task->state = TaskState::Runnable; + sched_enqueue(task); +} + +int64 sys_nanosleep(uint64 req_ptr, uint64 rem_ptr, uint64, uint64, uint64, uint64) { + if (req_ptr == 0) return -EINVAL; + + linux_timespec req; + if (copy_from_user(&req, req_ptr, sizeof(req)) != 0) { + return -EFAULT; + } + + uint64 target = *(volatile uint64*)&g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000); + + TaskControlBlock* curr = current_task(); + curr->state = TaskState::Sleeping; + + TimerList t; + t.init(nanosleep_wakeup); + t.expires = target; + t.data = curr; + + add_timer(&t); + + if (*(volatile uint64*)&g_jiffies < target) { + schedule(); + } else { + curr->state = TaskState::Running; + } + + + del_timer(&t); + + return 0; +} diff --git a/kernel/sys_signal.cc b/kernel/sys_signal.cc new file mode 100644 index 0000000..8b459d5 --- /dev/null +++ b/kernel/sys_signal.cc @@ -0,0 +1,351 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * sys_signal.cc — Signal-related system calls + * + * Implements: + * sys_kill (SYS_kill = 129) + * sys_rt_sigaction (SYS_rt_sigaction = 134) + * sys_rt_sigprocmask (SYS_rt_sigprocmask = 135) + * sys_rt_sigreturn (SYS_rt_sigreturn = 139) + * + * Reference: Linux kernel/signal.c, arch/riscv/kernel/signal.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* From signal.cc */ +extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); + +/* From task.cc */ +extern list_node g_all_tasks; +extern Spinlock g_task_list_lock; + +/* ================================================================== */ +/* Helper: find task by PID */ +/* ================================================================== */ + +static TaskControlBlock* find_task_by_pid(int32 pid) +{ + LockGuard guard(g_task_list_lock); + list_node* node = g_all_tasks.next; + while (node != &g_all_tasks) { + auto* task = list_entry(node); + if (task->pid == pid) return task; + node = node->next; + } + return nullptr; +} + +/* ================================================================== */ +/* sys_kill — send a signal to a process */ +/* ================================================================== */ + +/* + * Deliver `sig` to the thread-group leader represented by `sig_st`. + * sighand is allocated eagerly at fork/exec time, so we just trust + * that the leader has one; send_signal returns -ESRCH if not. + */ +static int deliver_to_group(signal_struct* sig_st, int sig, + kernel_siginfo* info) +{ + if (!sig_st || !sig_st->leader) return 0; + if (send_signal(sig, info, sig_st->leader) == 0) return 1; + return 0; +} + +int64 sys_kill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64) +{ + int32 pid = static_cast(a0); + int sig = static_cast(a1); + + if (sig < 0 || sig > _NSIG) return -EINVAL; + + TaskControlBlock* curr = current_task(); + if (!curr) return -ESRCH; + + kernel_siginfo info{}; + info.si_signo = sig; + info.si_code = 0; /* SI_USER */ + info._sifields._kill._pid = curr->pid; + info._sifields._kill._uid = 0; + + /* pid > 0: deliver to that thread group's leader. */ + if (pid > 0) { + TaskControlBlock* target = find_task_by_pid(pid); + if (!target) return -ESRCH; + /* Group signal: prefer the thread-group leader if available. */ + if (target->signal && target->signal->leader) + target = target->signal->leader; + if (sig == 0) return 0; + return send_signal(sig, &info, target); + } + + /* pid == 0: every member of caller's process group. */ + if (pid == 0) { + if (!curr->signal) return -ESRCH; + if (sig == 0) return 0; + int delivered = 0; + int32 target_pgid = curr->signal->pgid; + for_each_pgrp_member(target_pgid, [&](signal_struct* s) { + delivered += deliver_to_group(s, sig, &info); + }); + return delivered > 0 ? 0 : -ESRCH; + } + + /* pid == -1: every process the caller may signal, except init. + * We approximate "every" with "every thread-group leader on every + * pgrp bucket". This is rare in practice (BusyBox doesn't use it + * during normal shell operation). */ + if (pid == -1) { + if (sig == 0) return 0; + int delivered = 0; + LockGuard guard(g_pgrp_lock); + for (auto& bucket : g_pgrp_hash) { + list_node* node = bucket.next; + while (node != &bucket) { + list_node* next = node->next; + auto* s = list_entry(node); + if (s->leader && s->leader->pid != 1) + delivered += deliver_to_group(s, sig, &info); + node = next; + } + } + return delivered > 0 ? 0 : -ESRCH; + } + + /* pid < -1: every member of pgrp |pid|. */ + int32 target_pgid = -pid; + if (sig == 0) { + bool any = false; + for_each_pgrp_member(target_pgid, [&](signal_struct*) { any = true; }); + return any ? 0 : -ESRCH; + } + int delivered = 0; + for_each_pgrp_member(target_pgid, [&](signal_struct* s) { + delivered += deliver_to_group(s, sig, &info); + }); + return delivered > 0 ? 0 : -ESRCH; +} + +/* ================================================================== */ +/* sys_tkill / sys_tgkill — send a signal to a specific thread */ +/* ================================================================== */ + +static int64 do_tkill(int32 tgid, int32 tid, int sig) +{ + if (sig < 0 || sig > _NSIG) return -EINVAL; + if (tid <= 0) return -EINVAL; + TaskControlBlock* target = find_task_by_pid(tid); + if (!target) return -ESRCH; + /* tgid == -1 from tkill means "any tgid"; otherwise enforce match. */ + if (tgid != -1 && target->tgid != tgid) return -ESRCH; + + if (sig == 0) return 0; + + kernel_siginfo info{}; + info.si_signo = sig; + info.si_code = 0; /* SI_TKILL would be -6; userland rarely checks */ + info._sifields._kill._pid = current_task() ? current_task()->pid : 0; + info._sifields._kill._uid = 0; + return send_signal(sig, &info, target); +} + +int64 sys_tkill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64) +{ + return do_tkill(-1, + static_cast(a0), + static_cast(a1)); +} + +int64 sys_tgkill(uint64 a0, uint64 a1, uint64 a2, uint64, uint64, uint64) +{ + return do_tkill(static_cast(a0), + static_cast(a1), + static_cast(a2)); +} + +/* ================================================================== */ +/* sys_rt_sigaction — get/set signal disposition */ +/* ================================================================== */ + +/* + * User-space sigaction struct layout (RISC-V LP64): + * uint64 sa_handler; + * uint64 sa_flags; + * uint64 sa_restorer; (SA_RESTORER) + * sigset_t sa_mask; (8 bytes on LP64) + */ +struct user_sigaction { + uint64 sa_handler; + uint64 sa_flags; + uint64 sa_restorer; + sigset_t sa_mask; +}; + +int64 sys_rt_sigaction(uint64 a0, uint64 a1, uint64 a2, uint64 a3, + uint64, uint64) +{ + int sig = static_cast(a0); + uint64 act_uaddr = a1; /* const struct sigaction __user * */ + uint64 oact_uaddr = a2; /* struct sigaction __user * */ + uint64 sigsetsize = a3; + + if (sig < 1 || sig > _NSIG) return -EINVAL; + if (sig_kernel_only(sig)) return -EINVAL; + if (sigsetsize != sizeof(sigset_t)) return -EINVAL; + + TaskControlBlock* curr = current_task(); + if (!curr || !curr->sighand) return -EINVAL; + + /* All reads/writes of the action table happen under siglock — + * Linux's do_sigaction() also takes the same lock (kernel/signal.c). */ + user_sigaction old{}; + user_sigaction act{}; + bool have_new = (act_uaddr != 0); + + if (have_new) { + int err = copy_from_user(&act, act_uaddr, sizeof(act)); + if (err < 0) return -EFAULT; + } + + { + LockGuard guard(curr->sighand->siglock); + kernel_sigaction* ksa = &curr->sighand->action[sig - 1]; + old.sa_handler = ksa->sa_handler; + old.sa_flags = ksa->sa_flags; + old.sa_restorer = ksa->sa_restorer; + old.sa_mask = ksa->sa_mask; + + if (have_new) { + ksa->sa_handler = act.sa_handler; + ksa->sa_flags = act.sa_flags; + ksa->sa_restorer = act.sa_restorer; + ksa->sa_mask = act.sa_mask; + ksa->sa_mask.del(SIGKILL); + ksa->sa_mask.del(SIGSTOP); + } + } + + if (oact_uaddr != 0) { + int err = copy_to_user(oact_uaddr, &old, sizeof(old)); + if (err < 0) return -EFAULT; + } + return 0; +} + +/* ================================================================== */ +/* sys_rt_sigprocmask — change signal blocked mask */ +/* ================================================================== */ + +int64 sys_rt_sigprocmask(uint64 a0, uint64 a1, uint64 a2, uint64 a3, + uint64, uint64) +{ + int how = static_cast(a0); + uint64 set_uaddr = a1; + uint64 oset_uaddr = a2; + uint64 sigsetsize = a3; + + if (sigsetsize != sizeof(sigset_t)) return -EINVAL; + + TaskControlBlock* curr = current_task(); + if (!curr || !curr->sighand) return -EINVAL; + + sigset_t newset{}; + bool have_new = (set_uaddr != 0); + if (have_new) { + int err = copy_from_user(&newset, set_uaddr, sizeof(sigset_t)); + if (err < 0) return -EFAULT; + newset.del(SIGKILL); + newset.del(SIGSTOP); + } + + sigset_t old{}; + { + /* Linux: kernel/signal.c:do_sigprocmask() also holds siglock + * across read+update so the snapshot is atomic vs. concurrent + * signal delivery. */ + LockGuard guard(curr->sighand->siglock); + old = curr->blocked; + if (have_new) { + switch (how) { + case SIG_BLOCK: + curr->blocked = curr->blocked | newset; + break; + case SIG_UNBLOCK: + curr->blocked = curr->blocked & ~newset; + break; + case SIG_SETMASK: + curr->blocked = newset; + break; + default: + return -EINVAL; + } + } + } + + if (oset_uaddr != 0) { + int err = copy_to_user(oset_uaddr, &old, sizeof(sigset_t)); + if (err < 0) return -EFAULT; + } + return 0; +} + +/* ================================================================== */ +/* sys_rt_sigreturn — restore context after signal handler */ +/* ================================================================== */ + +/* Arch-specific: implemented in arch/riscv/signal.cc */ +extern int64 arch_sigreturn(arch::TrapFrame* tf); + +int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64) +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->trapframe) return -EINVAL; + + return arch_sigreturn(curr->trapframe); +} + +/* ================================================================== */ +/* sys_rt_sigtimedwait (SYS_rt_sigtimedwait = 137) */ +/* ================================================================== */ +#include +#include + +struct linux_timespec { + int64 tv_sec; + int64 tv_nsec; +}; + +int64 sys_rt_sigtimedwait(uint64 /* set_ptr */, uint64 /* info_ptr */, uint64 timeout_ptr, uint64 /* sigsetsize */, uint64, uint64) +{ + /* + * COMPATIBILITY STUB: + * LumeOS lacks full real-time signal queues and timed waiting mechanism. + * When BusyBox init runs out of children to reap, it calls this to wait + * for incoming signals (like SIGCHLD) with a timeout. + * We simulate the timeout by yielding the CPU, then return -EAGAIN (Try again) + * which tells user space that the timeout expired without receiving a signal. + */ + if (timeout_ptr != 0) { + linux_timespec req; + if (copy_from_user(&req, timeout_ptr, sizeof(req)) == 0) { + uint64 target = g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000); + while (g_jiffies < target) { + schedule(); + } + } + } + return -EAGAIN; +} diff --git a/kernel/syscall.cc b/kernel/syscall.cc new file mode 100644 index 0000000..a5b80ec --- /dev/null +++ b/kernel/syscall.cc @@ -0,0 +1,208 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * System Call MI Layer — Jump Table & Dispatcher + * + * O(1) function pointer array dispatch. No switch-case. + * syscall_init() registers all implemented handlers. + * + * Reference: docs/specs/syscall.md §6 + */ + +#include +#include +#include +#include +#include +#include +#include + +/* ====================================================================== + * Jump table + * ====================================================================== */ + +inline constexpr int kNrSyscalls = 512; + +static SyscallHandler sys_call_table[kNrSyscalls]; + +/* ====================================================================== + * MI Dispatcher + * ====================================================================== */ + +int64 syscall_dispatch(uint64 nr, uint64 args[6]) +{ + if (nr >= static_cast(kNrSyscalls)) { + kprintf("[syscall] nr=%llu out of range\n", nr); + return -ENOSYS; + } + + /* Per-syscall entry trace. KLOG_DEBUG keeps it out of the console + * by default; enable via klog_set_level(KLOG_DEBUG) or read the + * ring buffer to investigate. Subsys mask gates this further. */ + pr_debug_s(KLOG_SUBSYS_SYSCALL, + "syscall: nr=%llu a0=%llx a1=%llx a2=%llx a3=%llx\n", + nr, args[0], args[1], args[2], args[3]); + + SyscallHandler handler = sys_call_table[nr]; + if (!handler) { + kprintf("[syscall] unimplemented nr=%llu\n", nr); + return -ENOSYS; + } + + return handler(args[0], args[1], args[2], + args[3], args[4], args[5]); +} + +/* ====================================================================== + * Forward declarations of sys_* handlers + * ====================================================================== */ + +/* sys_process.cc */ +int64 sys_exit(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_exit_group(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_clone_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_wait4_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_nanosleep(uint64, uint64, uint64, uint64, uint64, uint64); + +/* sys_mm.cc */ +int64 sys_brk(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_mmap(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_munmap(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_mprotect(uint64, uint64, uint64, uint64, uint64, uint64); + +/* sys_info.cc */ +int64 sys_uname(uint64, uint64, uint64, uint64, uint64, uint64); + +/* sys_fs.cc */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_ppoll(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64); + +/* sys_signal.cc */ +int64 sys_kill(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_tkill(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_tgkill(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_rt_sigaction(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_rt_sigprocmask(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_rt_sigtimedwait(uint64, uint64, uint64, uint64, uint64, uint64); + +/* exec.cc */ +int64 sys_execve_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); + +/* sys_misc.cc */ +int64 sys_reboot(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_prctl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getsid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_setpgid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getpgid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_sigaltstack(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_clock_gettime(uint64, uint64, uint64, uint64, uint64, uint64); + +/* ====================================================================== + * syscall_init — register all handlers + * ====================================================================== */ + +void syscall_init() +{ + /* Zero the table (redundant for BSS, but explicit) */ + for (int i = 0; i < kNrSyscalls; i++) + sys_call_table[i] = nullptr; + + /* Phase 2: minimal runnable set */ + sys_call_table[SYS_exit] = sys_exit; + sys_call_table[SYS_exit_group] = sys_exit_group; + sys_call_table[SYS_getpid] = sys_getpid; + sys_call_table[SYS_gettid] = sys_gettid; + sys_call_table[SYS_getppid] = sys_getppid; + sys_call_table[SYS_sched_yield] = sys_sched_yield_wrapper; + sys_call_table[SYS_clone] = sys_clone_wrapper; + sys_call_table[SYS_wait4] = sys_wait4_wrapper; + sys_call_table[SYS_set_tid_address] = sys_set_tid_address; + sys_call_table[SYS_getuid] = sys_getuid; + sys_call_table[SYS_getgid] = sys_getgid; + sys_call_table[SYS_brk] = sys_brk; + sys_call_table[SYS_mmap] = sys_mmap; + sys_call_table[SYS_munmap] = sys_munmap; + sys_call_table[SYS_mprotect] = sys_mprotect; + sys_call_table[SYS_uname] = sys_uname; + + /* Phase 3: filesystem */ + sys_call_table[SYS_openat] = sys_openat; + sys_call_table[SYS_close] = sys_close; + sys_call_table[SYS_read] = sys_read; + sys_call_table[SYS_write] = sys_write; + sys_call_table[SYS_lseek] = sys_lseek; + sys_call_table[SYS_fstat] = sys_fstat_wrapper; + sys_call_table[SYS_getdents64] = sys_getdents64; + sys_call_table[SYS_dup] = sys_dup; + sys_call_table[SYS_dup3] = sys_dup3; + sys_call_table[SYS_getcwd] = sys_getcwd; + sys_call_table[SYS_chdir] = sys_chdir; + sys_call_table[SYS_mkdirat] = sys_mkdirat; + sys_call_table[SYS_unlinkat] = sys_unlinkat; + sys_call_table[SYS_writev] = sys_writev; + sys_call_table[SYS_ioctl] = sys_ioctl; + sys_call_table[SYS_ppoll] = sys_ppoll; + + /* Phase 4: signals */ + sys_call_table[SYS_kill] = sys_kill; + sys_call_table[SYS_tkill] = sys_tkill; + sys_call_table[SYS_tgkill] = sys_tgkill; + sys_call_table[SYS_rt_sigaction] = sys_rt_sigaction; + sys_call_table[SYS_rt_sigprocmask] = sys_rt_sigprocmask; + sys_call_table[SYS_rt_sigreturn] = sys_rt_sigreturn; + sys_call_table[SYS_rt_sigtimedwait] = sys_rt_sigtimedwait; + + /* Phase 5: exec */ + sys_call_table[SYS_execve] = sys_execve_wrapper; + + /* Phase 6: pipe + fcntl */ + sys_call_table[SYS_pipe2] = sys_pipe2; + sys_call_table[SYS_fcntl] = sys_fcntl; + + /* Phase 7: busybox bootstrap */ + sys_call_table[SYS_reboot] = sys_reboot; + sys_call_table[SYS_prctl] = sys_prctl; + sys_call_table[SYS_setsid] = sys_setsid; + sys_call_table[SYS_getsid] = sys_getsid; + sys_call_table[SYS_setpgid] = sys_setpgid; + sys_call_table[SYS_getpgid] = sys_getpgid; + sys_call_table[SYS_geteuid] = sys_geteuid; + sys_call_table[SYS_getegid] = sys_getegid; + sys_call_table[SYS_sigaltstack] = sys_sigaltstack; + sys_call_table[SYS_clock_gettime] = sys_clock_gettime; + sys_call_table[SYS_nanosleep] = sys_nanosleep; + sys_call_table[SYS_socket] = sys_socket; + sys_call_table[SYS_sendto] = sys_sendto; + + printk(KERN_INFO "init: syscall_init done (%d handlers registered)\n", 46); +} diff --git a/kernel/task.cc b/kernel/task.cc new file mode 100644 index 0000000..9f9b043 --- /dev/null +++ b/kernel/task.cc @@ -0,0 +1,780 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Task Management — create, exit, destroy, kthread + * + * Implements task lifecycle management: kernel thread creation, + * task exit (zombie transition), and resource reclamation. + * + * Reference: docs/specs/task.md §4-§6 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ====================================================================== + * Forward declarations + * ====================================================================== */ + +extern "C" void kthread_entry_trampoline(); +extern "C" void ret_from_fork(); + +/* ====================================================================== + * Global task list + * ====================================================================== */ + +list_node g_all_tasks; +Spinlock g_task_list_lock; +int32 g_nr_tasks = 0; + +/* Special tasks */ +TaskControlBlock* g_kernel_task = nullptr; /* PID 0 container */ +TaskControlBlock* g_init_task = nullptr; /* PID 1 (future) */ + +/* ====================================================================== + * task_init() — called once from kernel_main before sched_init + * ====================================================================== */ + +void task_init() +{ + g_all_tasks.init(); + g_task_list_lock.init("task_list"); + g_pid_alloc.init(); + + printk(KERN_INFO "init: task_init done (pid_alloc ready)\n"); +} + +/* ====================================================================== + * alloc_task — allocate TCB + kernel stack + * + * This is the SINGLE canonical TCB initializer. Both kthread_create + * (task.cc) and sched_init (sched_core.cc) must use this function. + * NEVER duplicate this logic — adding fields to TaskControlBlock + * requires updating only this function. + * ====================================================================== */ + +TaskControlBlock* alloc_task(const char* name, uint8 priority, + uint8 flags, uint8 cpu) +{ + auto* task = new TaskControlBlock; + if (!task) return nullptr; + + task->context = {}; + + /* Allocate 8KB kernel stack (order-1 = 2 pages) */ + Page* stack_page = pmm_alloc_pages(1); + if (!stack_page) { + delete task; + return nullptr; + } + task->kstack_pa = page_to_pa(stack_page).raw; + task->kstack_va = pa_to_va(phys_addr(task->kstack_pa)).raw; + + /* Write stack canary at bottom */ + *reinterpret_cast(task->kstack_va) = kStackCanary; + + /* Place TrapFrame at the top of the kernel stack (like Linux pt_regs). + * ALL tasks — kernel threads and user processes — have a TrapFrame + * here. kthread_create sets the initial SP below the TrapFrame so + * the kthread function never touches this reserved area. + * kernel_execve / do_execve can later fill it in for user-mode return. */ + uint64 tf_addr = task->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); + task->trapframe = reinterpret_cast(tf_addr); + __builtin_memset(task->trapframe, 0, sizeof(arch::TrapFrame)); + + /* Basic fields */ + task->name = name; + task->priority = priority; + task->time_slice = priority_to_timeslice(priority); + task->state = TaskState::Runnable; + task->cpu_id = cpu; + task->flags = flags; + task->exit_code = 0; + task->exit_signal = 0; + task->preempt_count = 0; + task->clear_child_tid = 0; + task->stopped_code = 0; + task->notify_continued = false; + task->parent = nullptr; + task->mm = nullptr; + task->fpstate = {}; + task->files = nullptr; + task->fs = nullptr; + task->pending.init(); + task->blocked.clear(); + task->sighand = nullptr; + task->signal = nullptr; + + /* Init all list nodes */ + task->run_link.init(); + task->all_link.init(); + task->children.init(); + task->sibling.init(); + task->lock.init("task"); + task->wait_chld.init("wait_chld"); + + /* Default sched class */ + extern const SchedClass g_sched_class_o1; + task->sched_class = &g_sched_class_o1; + task->sched_entity = &task->run_link; + + return task; +} + +/* Add task to global task list */ +static void task_list_add(TaskControlBlock* task) +{ + LockGuard guard(g_task_list_lock); + + list_node* head = &g_all_tasks; + task->all_link.prev = head->prev; + task->all_link.next = head; + head->prev->next = &task->all_link; + head->prev = &task->all_link; + + g_nr_tasks++; +} + +/* Remove task from global task list */ +static void task_list_remove(TaskControlBlock* task) +{ + LockGuard guard(g_task_list_lock); + + task->all_link.prev->next = task->all_link.next; + task->all_link.next->prev = task->all_link.prev; + task->all_link.init(); + + if (g_nr_tasks > 0) + g_nr_tasks--; +} + +/* ====================================================================== + * kthread_create — create a kernel thread + * ====================================================================== */ + +TaskControlBlock* kthread_create(const char* name, + void (*entry)(void*), + void* arg, + uint8 priority) +{ + TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD, + static_cast(arch::cpu::id())); + if (!task) return nullptr; + + /* Allocate PID */ + task->pid = g_pid_alloc.alloc(); + if (task->pid < 0) { + /* PID exhausted — clean up */ + pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); + delete task; + return nullptr; + } + task->tgid = task->pid; + + /* Kernel threads: exit_signal = -1 → auto-reap on exit. + * Linux: kernel_thread() passes (flags & ~CSIGNAL) → exit_signal = 0, + * but kthreads' parent (kthreadd) reaps them. We have no kthreadd, + * so we use -1 which makes exit_notify() call release_task() directly, + * identical to Linux's CLONE_THREAD autoreap path. */ + task->exit_signal = -1; + + /* Set up context for kthread_entry_trampoline. + * SP starts BELOW the TrapFrame reserved at the stack top. + * This ensures the kthread function never clobbers the TrapFrame area. */ + uint64 stack_top = task->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); + task->context = {}; + task->context.ra = reinterpret_cast(kthread_entry_trampoline); + task->context.sp = stack_top; + task->context.s[0] = reinterpret_cast(entry); + task->context.s[1] = reinterpret_cast(arg); + + /* Parent is kernel_task (PID 0) or current caller */ + TaskControlBlock* parent = g_kernel_task ? g_kernel_task : current_task(); + task->parent = parent; + if (parent) { + LockGuard guard(parent->lock); + list_node* head = &parent->children; + task->sibling.prev = head->prev; + task->sibling.next = head; + head->prev->next = &task->sibling; + head->prev = &task->sibling; + } + + /* Add to global list and enqueue */ + task_list_add(task); + sched_enqueue(task); + + return task; +} + +/* ====================================================================== + * task_exit — transition current task to Zombie + * ====================================================================== */ + +void task_exit(int code) +{ + TaskControlBlock* curr = current_task(); + if (!curr) kernel_panic("task_exit: no current task"); + + /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */ + curr->exit_code = (code & 0xFF) << 8; + + /* CLONE_CHILD_CLEARTID / set_tid_address: write 0 to user tidptr. + * This is how pthread_join detects thread termination. + * TODO: futex_wake(clear_child_tid, 1) after writing 0. */ + if (curr->clear_child_tid != 0 && curr->mm != nullptr) { + int32 zero = 0; + copy_to_user(curr->clear_child_tid, &zero, sizeof(zero)); + curr->clear_child_tid = 0; + } + + /* Release user address space (refcount-safe). + * MUST switch to kernel page table FIRST — otherwise we're running + * on a freed page table after put() deallocates it. */ + if (curr->mm != nullptr) { + extern uint64 g_kernel_pgtbl; + pmap::activate(g_kernel_pgtbl); + curr->mm->put(); + curr->mm = nullptr; + } + + /* Reparent children to init (or kernel_task if no init yet) */ + TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; + if (foster && !curr->children.is_empty()) { + LockGuard guard(curr->lock); + + list_node* child_node = curr->children.next; + while (child_node != &curr->children) { + list_node* next_sib = child_node->next; + auto* child = list_entry(child_node); + child->parent = foster; + + /* Move sibling link to foster's children */ + child_node->prev->next = child_node->next; + child_node->next->prev = child_node->prev; + + list_node* fhead = &foster->children; + child_node->prev = fhead->prev; + child_node->next = fhead; + fhead->prev->next = child_node; + fhead->prev = child_node; + + child_node = next_sib; + } + curr->children.init(); + } + + + /* ---- exit_notify: Linux kernel/exit.c:exit_notify() ---- + * Determine whether this task should be auto-reaped or become zombie. + * + * Linux logic (simplified): + * - exit_signal == -1 (kthreads, sub-threads): autoreap = true + * - exit_signal >= 1 (user processes): notify parent, become zombie + * + * If autoreap, we call release_task() equivalent inline: + * free PID, remove from task list, then deschedule to Dead. */ + bool autoreap = (curr->exit_signal <= 0); + + if (autoreap) { + /* Auto-reap: release resources immediately (no zombie state). + * Linux: exit_notify() → tsk->exit_state = EXIT_DEAD → release_task() */ + task_list_remove(curr); + g_pid_alloc.free(curr->pid); + + /* Remove from parent's children list */ + curr->sibling.prev->next = curr->sibling.next; + curr->sibling.next->prev = curr->sibling.prev; + curr->sibling.init(); + + /* Drop signal_struct ref: last task in the thread group also + * detaches us from the pgrp hash. */ + if (curr->signal) { + put_signal_struct(curr->signal); + curr->signal = nullptr; + } + if (curr->sighand) { + put_sighand(curr->sighand); + curr->sighand = nullptr; + } + + curr->state = TaskState::Dead; + schedule(); + __builtin_unreachable(); + } + + /* User process path: become Zombie for parent wait() */ + curr->state = TaskState::Zombie; + + /* Wake parent->wait_chld queue */ + if (curr->parent) { + curr->parent->wait_chld.wake_all(); + } + + /* Yield CPU — never returns */ + schedule(); + + __builtin_unreachable(); +} + +void task_kill_current(int sig) +{ + TaskControlBlock* curr = current_task(); + if (!curr) kernel_panic("task_kill_current: no current task"); + + /* Linux ABI: signal-killed encodes status as (signo & 0x7F) */ + curr->exit_code = sig & 0x7F; + + /* Release user address space (refcount-safe). + * Switch to kernel page table first (same as task_exit). */ + if (curr->mm) { + extern uint64 g_kernel_pgtbl; + pmap::activate(g_kernel_pgtbl); + curr->mm->put(); + curr->mm = nullptr; + } + + /* Reparent children (same as task_exit) */ + TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; + if (foster && !curr->children.is_empty()) { + LockGuard guard(curr->lock); + + list_node* child_node = curr->children.next; + while (child_node != &curr->children) { + list_node* next_sib = child_node->next; + auto* child = list_entry(child_node); + child->parent = foster; + + child_node->prev->next = child_node->next; + child_node->next->prev = child_node->prev; + + list_node* fhead = &foster->children; + child_node->prev = fhead->prev; + child_node->next = fhead; + fhead->prev->next = child_node; + fhead->prev = child_node; + + child_node = next_sib; + } + curr->children.init(); + } + + curr->state = TaskState::Zombie; + + if (curr->parent) { + curr->parent->wait_chld.wake_all(); + } + + schedule(); + __builtin_unreachable(); +} + +/* ====================================================================== + * task_destroy — fully reclaim a Zombie task (called by waitpid) + * ====================================================================== */ + +void task_destroy(TaskControlBlock* task) +{ + /* Remove from global list */ + task_list_remove(task); + + /* Remove from parent's children */ + task->sibling.prev->next = task->sibling.next; + task->sibling.next->prev = task->sibling.prev; + task->sibling.init(); + + /* Free PID */ + g_pid_alloc.free(task->pid); + + /* Drop signal_struct + sighand refs. For zombies these are delayed + * until reap (here) so a waitpid() walker can still inspect + * pgid/sid; Linux defers __exit_signal/__cleanup_sighand to + * release_task() for the same reason. */ + if (task->signal) { + put_signal_struct(task->signal); + task->signal = nullptr; + } + if (task->sighand) { + put_sighand(task->sighand); + task->sighand = nullptr; + } + + /* Free kernel stack */ + if (task->kstack_pa != 0) { + pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); + } + + /* Free TCB */ + delete task; +} + +/* ====================================================================== + * do_wait4 — wait for a child to exit (Linux-compatible) + * + * @wait_pid: >0 = specific child PID + * -1 = any child + * 0 = any child in same process group (TODO) + * <-1 = any child in specific process group (TODO) + * @options: WNOHANG = return immediately if no zombie + * @exit_code: output parameter (already in Linux wstatus format) + * + * Returns: PID of exited child, -ECHILD, or -EAGAIN (WNOHANG). + * ====================================================================== */ + +int32 do_wait4(int32 wait_pid, int options, int32* exit_code) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + while (true) { + TaskControlBlock* zombie = nullptr; + TaskControlBlock* report = nullptr; /* Stopped/Continued report */ + int32 report_status = 0; + bool has_children = false; + + /* + * Two-phase wait: put ourselves on the wait queue BEFORE + * checking the condition. This closes the race window where + * a child exits between our check and schedule(). + * + * If a child exits and calls wake_all() while we're checking, + * our state will be flipped back to Runnable, and schedule() + * will simply return immediately. + */ + curr->wait_chld.prepare_to_wait(curr, TaskState::Sleeping); + + /* Scan children */ + curr->lock.acquire(); + + if (curr->children.is_empty()) { + curr->lock.release(); + curr->wait_chld.finish_wait(curr); + return -ECHILD; + } + has_children = true; + + list_node* node = curr->children.next; + while (node != &curr->children) { + auto* child = list_entry(node); + bool matches = (wait_pid == -1) || (wait_pid == child->pid); + if (!matches) { + node = node->next; + continue; + } + + /* Zombie reaping has priority — it frees resources. */ + if (child->state == TaskState::Zombie) { + zombie = child; + break; + } + + /* WUNTRACED: report Stopped child once per stop event. */ + if ((options & WUNTRACED) && child->stopped_code != 0) { + report = child; + report_status = child->stopped_code; + break; + } + + /* WCONTINUED: report a one-shot SIGCONT resume. */ + if ((options & WCONTINUED) && child->notify_continued) { + report = child; + report_status = W_CONTINUED(); + break; + } + + node = node->next; + } + curr->lock.release(); + + if (zombie) { + curr->wait_chld.finish_wait(curr); + int32 pid = zombie->pid; + if (exit_code) *exit_code = zombie->exit_code; + task_destroy(zombie); + return pid; + } + + if (report) { + /* Status-only report: clear the one-shot flag, leave the + * child alive. WNOWAIT (peek) would skip the clear, but + * we don't yet honour it. */ + curr->wait_chld.finish_wait(curr); + int32 pid = report->pid; + if (exit_code) *exit_code = report_status; + if (W_CONTINUED() == report_status) { + report->notify_continued = false; + } else { + report->stopped_code = 0; + } + return pid; + } + + /* No event to report */ + if (!has_children) { + curr->wait_chld.finish_wait(curr); + return -ECHILD; + } + if (options & WNOHANG) { + curr->wait_chld.finish_wait(curr); + return 0; + } + + /* Condition still false — deschedule. + * We are already on the wait queue and marked Sleeping. */ + schedule(); + + /* Woken up — finish_wait removes us from queue, restores Runnable */ + curr->wait_chld.finish_wait(curr); + + /* Loop back to re-check the condition */ + } +} + +/* Legacy wrapper for internal callers */ +int32 task_wait(int32* exit_code) +{ + return do_wait4(-1, 0, exit_code); +} + +/* ====================================================================== + * task_clone — clone current task with Linux-compatible flags + * + * @tf: trap frame to copy to child + * @clone_flags: CLONE_VM | CLONE_THREAD | CSIGNAL etc. + * @child_stack: if non-zero, set as child's user SP (for pthread) + * @parent_tidptr: write child tid here in parent (CLONE_PARENT_SETTID) + * @child_tidptr: write child tid here in child (CLONE_CHILD_SETTID) + * @tls: TLS pointer (CLONE_SETTLS → tp register) + * + * Returns: child PID in parent, 0 in child, or negative errno. + * ====================================================================== */ + +int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, + uint64 child_stack, uint64 parent_tidptr, + uint64 child_tidptr, uint64 tls) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + TaskControlBlock* child = alloc_task("cloned", curr->priority, 0, + static_cast(arch::cpu::id())); + if (!child) return -ENOMEM; + + /* Allocate PID */ + child->pid = g_pid_alloc.alloc(); + if (child->pid < 0) { + pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); + delete child; + return -ENOMEM; + } + + /* exit_signal + tgid: from clone_flags. + * Linux: fork.c line 2418/2424 */ + if (clone_flags & CLONE_THREAD) { + child->tgid = curr->tgid; + child->exit_signal = -1; /* Linux: sub-threads auto-reap */ + } else { + child->tgid = child->pid; + child->exit_signal = static_cast(clone_flags & 0xFF); /* CSIGNAL */ + if (child->exit_signal == 0) { + child->exit_signal = SIGCHLD; /* default for fork() */ + } + } + + /* Address space: CLONE_VM shares, otherwise deep copy */ + if (clone_flags & CLONE_VM) { + if (curr->mm) { + curr->mm->incref(); + child->mm = curr->mm; + } + } else { + if (curr->mm) { + child->mm = vmm_clone(curr->mm); + if (!child->mm) { + g_pid_alloc.free(child->pid); + pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); + delete child; + return -ENOMEM; + } + } + } + + /* Copy TrapFrame to the child's kernel stack */ + uint64 tf_addr = child->kstack_va + kKernelStackSize - sizeof(arch::TrapFrame); + child->trapframe = reinterpret_cast(tf_addr); + *child->trapframe = *tf; + + /* Set Kernel SP */ + child->trapframe->kernel_sp = reinterpret_cast(child->trapframe); + child->trapframe->kernel_cpu_id = child->cpu_id; + + /* Child returns 0 from fork/clone */ + child->trapframe->a[0] = 0; + + /* If caller specified a child stack, use it (pthread_create) */ + if (child_stack != 0) { + child->trapframe->sp = child_stack; + } + + /* CLONE_SETTLS: set thread pointer register */ + if (clone_flags & CLONE_SETTLS) { + child->trapframe->tp = tls; + } + + /* Set up context for ret_from_fork */ + uint64 stack_top = tf_addr; + child->context = {}; + child->context.ra = reinterpret_cast(ret_from_fork); + child->context.sp = stack_top; + child->context.s[0] = tf_addr; + + /* ---- Copy fs_struct (Linux: copy_fs in fork.c) ---- + * CLONE_FS: share the same fs_struct (increment refcount). + * Otherwise: allocate a new fs_struct copying root/pwd. */ + if (curr->fs != nullptr) { + if (clone_flags & CLONE_FS) { + curr->fs->count.fetch_add(1, __ATOMIC_RELAXED); + child->fs = curr->fs; + } else { + child->fs = copy_fs_struct(curr->fs); + } + } + + /* ---- Copy files_struct (Linux: copy_files in fork.c) ---- + * CLONE_FILES: share the same fd table. + * Otherwise: duplicate the fd table. */ + if (curr->files != nullptr) { + if (clone_flags & CLONE_FILES) { + curr->files->count.fetch_add(1, __ATOMIC_RELAXED); + child->files = curr->files; + } else { + child->files = dup_fd(curr->files); + } + } + + /* ---- Copy sighand (Linux kernel/fork.c:copy_sighand) ---- + * CLONE_SIGHAND: share signal handlers (incref parent's). + * Otherwise: deep copy of the action table; child gets its own + * sighand_struct (and its own siglock). Both branches MUST yield + * a non-null sighand — every user task needs one so signal paths + * can take the siglock without lazy-allocation races. */ + if (clone_flags & CLONE_SIGHAND) { + if (!curr->sighand) { + /* CLONE_SIGHAND requires the parent to have a sighand. */ + return -EINVAL; + } + get_sighand(curr->sighand); + child->sighand = curr->sighand; + } else { + child->sighand = copy_sighand(curr->sighand); + if (!child->sighand) { + /* OOM — same best-effort policy as fs/files/signal above. + * A separate cleanup-on-OOM pass is on the to-do list. */ + } + } + + /* ---- Copy signal_struct (Linux: copy_signal in fork.c) ---- + * CLONE_THREAD shares the parent's signal_struct (incref). + * Otherwise allocate a new one that inherits pgid/sid/tty from the + * parent so the child stays in the same process group and session — + * this is what makes fork() participate in the existing job-control + * identity without explicit setpgid() calls. */ + if (curr->signal != nullptr) { + if (clone_flags & CLONE_THREAD) { + get_signal_struct(curr->signal); + child->signal = curr->signal; + } else { + child->signal = fork_signal_struct(curr->signal, child); + } + } + + /* Add to task tree */ + child->parent = curr; + { + LockGuard guard(curr->lock); + list_node* head = &curr->children; + child->sibling.prev = head->prev; + child->sibling.next = head; + head->prev->next = &child->sibling; + head->prev = &child->sibling; + } + + /* Add to global list and enqueue */ + task_list_add(child); + sched_enqueue(child); + + /* CLONE_PARENT_SETTID: write child tid to parent's user address */ + if ((clone_flags & CLONE_PARENT_SETTID) && parent_tidptr != 0) { + copy_to_user(parent_tidptr, &child->pid, sizeof(child->pid)); + } + + /* CLONE_CHILD_SETTID: write child tid into child's user address. + * Note: this runs in parent context, but the address is in the + * child's mm. For CLONE_VM (threads), parent and child share mm + * so this works directly. For fork, the page was just CoW-copied. */ + if ((clone_flags & CLONE_CHILD_SETTID) && child_tidptr != 0) { + if (clone_flags & CLONE_VM) { + /* Same address space — write directly */ + copy_to_user(child_tidptr, &child->pid, sizeof(child->pid)); + } + /* For non-CLONE_VM fork, the child's mm is separate. + * The write would need to go into the child's pagetable. + * Linux handles this in the child's first schedule via + * a special flag. For now, threads (CLONE_VM) are the + * primary consumer. */ + } + + /* CLONE_CHILD_CLEARTID: kernel writes 0 to child_tidptr on exit */ + if (clone_flags & CLONE_CHILD_CLEARTID) { + child->clear_child_tid = child_tidptr; + } + + return child->pid; +} + +/* Legacy wrapper — plain fork() semantics */ +int32 task_fork(arch::TrapFrame* tf) +{ + return task_clone(tf, SIGCHLD, 0, 0, 0, 0); +} + + +/* ====================================================================== + * task_check_stack_canary + * ====================================================================== */ + +void task_check_stack_canary(TaskControlBlock* task) +{ + if (!task || task->kstack_va == 0) return; + + auto* canary = reinterpret_cast(task->kstack_va); + if (*canary != kStackCanary) { + kernel_panic("kernel stack overflow detected"); + } +} diff --git a/kernel/timer.cc b/kernel/timer.cc new file mode 100644 index 0000000..3cf03cf --- /dev/null +++ b/kernel/timer.cc @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include +#include +#include + +#include + +uint64 g_jiffies = 0; +static uint64 g_timer_freq = 10000000; +static uint64 g_tick_interval = 0; + +static list_node g_timer_list; +static Spinlock g_timer_lock; + + +void add_timer(TimerList* timer) { + LockGuard guard(g_timer_lock); + + list_node* pos; + list_for_each(pos, &g_timer_list) { + TimerList* t = list_entry(pos); + if (t->expires > timer->expires) { + break; + } + } + + timer->entry.prev = pos->prev; + timer->entry.next = pos; + pos->prev->next = &timer->entry; + pos->prev = &timer->entry; +} + +int del_timer(TimerList* timer) { + LockGuard guard(g_timer_lock); + if (timer->entry.next != nullptr && timer->entry.next != &timer->entry) { + timer->entry.prev->next = timer->entry.next; + timer->entry.next->prev = timer->entry.prev; + timer->entry.init(); + return 1; + } + return 0; +} + +int mod_timer(TimerList* timer, uint64 expires) { + int ret = del_timer(timer); + timer->expires = expires; + add_timer(timer); + return ret; +} + +static void run_timers() { + g_timer_lock.acquire(); + + while (!g_timer_list.is_empty()) { + TimerList* t = list_entry(g_timer_list.next); + if (t->expires > g_jiffies) { + break; + } + + t->entry.prev->next = t->entry.next; + t->entry.next->prev = t->entry.prev; + t->entry.init(); + + g_timer_lock.release(); + t->function(t); + g_timer_lock.acquire(); + } + + g_timer_lock.release(); +} + +static void timer_set_next() { + arch::sbi::set_timer(arch::cpu::read_time() + g_tick_interval); +} + +void timer_init() { + DeviceNode* cpus = fdt_get_node_by_path("/cpus"); + if (cpus) { + g_timer_freq = cpus->get_prop_u32("timebase-frequency", g_timer_freq); + } + + // Default 100 Hz (10ms per tick) + g_tick_interval = g_timer_freq / 100; + + g_timer_lock.init("timer_list"); + g_timer_list.init(); + + timer_set_next(); + + arch::cpu::timer_intr_on(); +} + + +void timer_init_ap() { + timer_set_next(); + arch::cpu::timer_intr_on(); +} + +extern "C" uint32 bsp_hart_id; + +void timer_tick() { + if (arch::cpu::id() == bsp_hart_id) { + g_jiffies++; + run_timers(); + } + + timer_set_next(); + sched_tick(); +} + + +uint64 timer_get_freq() { + return g_timer_freq; +} diff --git a/kernel/trap.cc b/kernel/trap.cc new file mode 100644 index 0000000..2116d91 --- /dev/null +++ b/kernel/trap.cc @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Trap MI Layer — Machine-Independent Semantic Dispatcher + * + * Receives a translated TrapCause from the MD layer and routes to + * the appropriate handler. + * + * *** MI INVARIANT: This file contains ZERO inline assembly, + * ZERO arch-specific constants (no CSR reads, no scause codes). *** + * arch/trapframe.h is included because TrapFrame is the cross-layer + * data contract defined in docs/specs/arch_abstraction.md §3.2. + * + * Reference: docs/specs/trap.md §4 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* TrapFrame: cross-layer data contract */ +#include /* search_extable: kernel fault recovery */ + +/* ======================================================================== + * trap_handle() — MI-layer dispatcher for traps + * + * Called by MD layer after scause → TrapInfo translation. + * ======================================================================== */ + +static TrapStats g_trap_stats = {}; + +static uint32 trap_cause_index(TrapCause cause) +{ + uint32 raw = static_cast(cause); + + /* Exceptions 0-7 map directly to indices 0-7 */ + if (raw <= 7) { + return raw; + } + /* IRQs 16-18 map to indices 8-10 */ + if (raw >= 16 && raw <= 18) { + return raw - 16 + 8; + } + /* Unknown / out-of-range */ + return 11; +} + +static void trap_record(const TrapInfo& info) +{ + uint32 idx = trap_cause_index(info.cause); + __atomic_fetch_add(&g_trap_stats.total, 1, __ATOMIC_RELAXED); + __atomic_fetch_add(&g_trap_stats.by_cause[idx], 1, __ATOMIC_RELAXED); + g_trap_stats.last_cause = info.cause; + g_trap_stats.last_class = info.cls; + g_trap_stats.last_origin = info.origin; + g_trap_stats.last_val = info.val; + g_trap_stats.last_epc = info.epc; + g_trap_stats.last_scause = info.scause; +} + +static VmFaultCause vm_fault_cause_from_trap(TrapCause cause) +{ + switch (cause) { + case TrapCause::PageFaultStore: + return VmFaultCause::Write; + case TrapCause::PageFaultExec: + return VmFaultCause::Exec; + case TrapCause::PageFaultLoad: + default: + return VmFaultCause::Read; + } +} + +void trap_stats_get(TrapStats* out) +{ + if (!out) { + return; + } + *out = g_trap_stats; +} + +void trap_stats_reset() +{ + g_trap_stats = {}; +} + +void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) +{ + trap_record(info); + + /* ---- Kernel exception handling ---- + * Interrupts are always allowed through. For exceptions: + * - Page faults: handled in the switch below (extable fixup path) + * - Other exceptions: try extable, then panic. + * + * This replaces the old blanket kernel_panic, enabling + * copy_from_user/copy_to_user to safely fault on user pages. */ + if (info.origin == TrapOrigin::FromKernel && + info.cls != TrapClass::Interrupt) { + + /* Page faults are dispatched in the switch — let them through */ + bool is_page_fault = + (info.cause == TrapCause::PageFaultLoad || + info.cause == TrapCause::PageFaultStore || + info.cause == TrapCause::PageFaultExec); + + if (!is_page_fault) { + /* Non-page-fault kernel exception: try extable fixup */ + uint64 fixup = search_extable(info.epc); + if (fixup != 0) { + tf->epc = fixup; + return; + } + kprintf("[trap] fatal kernel exception: cause=%u epc=0x%llx val=0x%llx\n", + static_cast(info.cause), info.epc, info.val); + kernel_panic("unrecoverable kernel exception"); + } + } + + switch (info.cause) { + /* ---- Page Faults ---- */ + case TrapCause::PageFaultLoad: + case TrapCause::PageFaultStore: + case TrapCause::PageFaultExec: + if (info.origin == TrapOrigin::FromUser) { + TaskControlBlock* curr = current_task(); + if (!curr || !curr->mm) { + kernel_panic("user page fault with no current mm"); + } + + int ret = vmm_handle_page_fault(curr->mm, info.val, + vm_fault_cause_from_trap(info.cause)); + if (ret == -EFAULT) { + kprintf("[trap] user SIGSEGV (MAPERR) epc=0x%llx addr=0x%llx\n", + info.epc, info.val); + force_sig_fault(SIGSEGV, 1 /* SEGV_MAPERR */, info.val); + } else if (ret == -ENOMEM) { + kprintf("[trap] user SIGKILL (OOM) epc=0x%llx addr=0x%llx\n", + info.epc, info.val); + force_sig_fault(SIGKILL, 0, info.val); + } else if (ret < 0) { + kprintf("[trap] user SIGSEGV (ACCERR) epc=0x%llx addr=0x%llx\n", + info.epc, info.val); + force_sig_fault(SIGSEGV, 2 /* SEGV_ACCERR */, info.val); + } + break; + } + + /* Kernel page fault — try exception table fixup. + * This is the path used by copy_from_user/copy_to_user when + * accessing unmapped or faulting user addresses from S-mode. */ + { + uint64 fixup = search_extable(info.epc); + if (fixup != 0) { + tf->epc = fixup; + break; + } + } + kprintf("[trap] kernel page fault: epc=0x%llx addr=0x%llx\n", + info.epc, info.val); + { + TaskControlBlock* curr = current_task(); + if (curr) { + kprintf("[trap] pid=%d state=%d\n", + curr->pid, static_cast(curr->state)); + kprintf("[trap] tf->epc=%llx tf->ra=%llx tf->sp=%llx\n", + tf->epc, tf->ra, tf->sp); + } + } + kernel_panic("unhandled kernel page fault (no extable entry)"); + break; + + /* ---- System Call ---- */ + case TrapCause::Syscall: + syscall_dispatch_md(tf); + break; + + /* ---- Interrupts ---- */ + case TrapCause::IrqTimer: + /* Timer tick */ + timer_tick(); + break; + + case TrapCause::IrqExternal: { + /* External interrupt: generic claim → dispatch → complete */ + uint32 irq = g_irq_manager.claim(); + if (irq != 0) { + g_irq_manager.dispatch(irq); + g_irq_manager.complete(irq); + } + break; + } + + case TrapCause::IrqSoftware: + /* S-mode software interrupt = IPI from another hart */ + smp_handle_ipi(); + break; + + /* ---- Fatal exceptions ---- */ + case TrapCause::IllegalInst: + if (info.origin == TrapOrigin::FromUser) { + kprintf("[trap] user illegal instruction at epc=0x%llx stval=0x%llx\n", + info.epc, info.val); + force_sig_fault(SIGILL, 1 /* ILL_ILLOPC */, info.epc); + } else { + kprintf("[trap] illegal instruction at epc=0x%llx\n", info.epc); + kernel_panic("illegal instruction in kernel mode"); + } + break; + + case TrapCause::AccessFault: + kprintf("[trap] access fault\n"); + kernel_panic("access fault"); + break; + + case TrapCause::AlignFault: + kprintf("[trap] alignment fault\n"); + kernel_panic("alignment fault"); + break; + + case TrapCause::Breakpoint: + kprintf("[trap] breakpoint\n"); + /* ebreak advance is handled by MD layer (arch-specific instruction size) */ + break; + + case TrapCause::Unknown: + default: + kprintf("[trap] unknown trap\n"); + kernel_panic("unknown trap"); + break; + } + + /* Process pending signals before returning to user mode */ + if (info.origin == TrapOrigin::FromUser) { + do_signal(tf); + } + + /* Preemption check on return from any interrupt. + * This is the kernel's equivalent of Linux's retint_kernel path: + * if a timer tick (or IPI) set need_resched, we must call schedule() + * before returning — otherwise the idle loop never yields to + * runnable tasks like PID 1. */ + if (info.cls == TrapClass::Interrupt) { + sched_check_preempt(); + } +} diff --git a/kernel/uaccess.cc b/kernel/uaccess.cc new file mode 100644 index 0000000..14c5fb7 --- /dev/null +++ b/kernel/uaccess.cc @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * User-Kernel Memory Copy — Phase 3 (Exception Tables) + * + * Validates user pointers by checking they fall within user address space + * (below KERNEL_VA_BASE). Uses assembly-level safe copy functions that + * integrate with the kernel's Exception Table mechanism. + * + * Reference: docs/specs/syscall.md §8 + */ + +#include +#include +#include +#include +#include + +extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len); +extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len); + +int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len) +{ + if (len == 0) return 0; + if (!is_user_range(user_src, len)) return -EFAULT; + + return __uaccess_memcpy(kernel_dst, reinterpret_cast(user_src), len); +} + +int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len) +{ + if (len == 0) return 0; + if (!is_user_range(user_dst, len)) return -EFAULT; + + return __uaccess_memcpy(reinterpret_cast(user_dst), kernel_src, len); +} + +int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len) +{ + if (!is_user_range(user_src, 1)) return -EFAULT; + + /* We must also check that the string does not cross into kernel space. + This is safely handled by the assembly routine which will fault if it hits + an unmapped page. However, to strictly respect kKernelVaBase, we should + restrict max_len to the distance to kKernelVaBase. */ + uint64 max_allowed = kKernelVaBase - user_src; + if (max_len > max_allowed) { + max_len = max_allowed; + } + + return __uaccess_strncpy(kernel_dst, reinterpret_cast(user_src), max_len); +} diff --git a/kernel/waitqueue.cc b/kernel/waitqueue.cc new file mode 100644 index 0000000..9689417 --- /dev/null +++ b/kernel/waitqueue.cc @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * WaitQueue — blocking synchronization implementation + * + * Two-phase wait protocol: + * + * prepare_to_wait(): task → Sleeping, link into queue + * [caller checks condition — if still false, calls schedule()] + * finish_wait(): remove from queue, restore Runnable + * + * Legacy sleep() wraps the two-phase pattern for simple callers. + * + * wake_one()/wake_all(): dequeue waiters → Runnable, sched_enqueue() + * + * The internal spinlock protects the wait list only. + * schedule() is called AFTER releasing all spinlocks to avoid + * holding a lock across a context switch. + */ + +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Two-phase wait API */ +/* ------------------------------------------------------------------ */ + +void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state) +{ + LockGuard guard(lock_); + + /* Set state BEFORE linking — this is the key to preventing lost wakeups. + * If wake_one fires between now and schedule(), it will set state back + * to Runnable, and schedule() will see Runnable and not block. */ + task->state = new_state; + + /* Only add to queue if not already linked (idempotent) */ + if (task->run_link.next == &task->run_link) { + /* Link into wait queue tail */ + list_node* node = &task->run_link; + node->prev = head_.prev; + node->next = &head_; + head_.prev->next = node; + head_.prev = node; + } +} + +void WaitQueue::finish_wait(TaskControlBlock* task) +{ + LockGuard guard(lock_); + + /* Remove from wait queue if still linked */ + if (task->run_link.next != &task->run_link) { + list_node* node = &task->run_link; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + } + + /* Ensure task is Runnable (it may have been woken by wake_one/wake_all + * already, or it may never have slept if the condition was true). */ + if (task->state == TaskState::Sleeping) { + task->state = TaskState::Runnable; + } +} + +/* ------------------------------------------------------------------ */ +/* Legacy convenience API */ +/* ------------------------------------------------------------------ */ + +void WaitQueue::sleep(Spinlock* external_lock) +{ + TaskControlBlock* curr = current_task(); + if (!curr) { + if (external_lock) external_lock->release(); + return; + } + + /* Phase 1: add to queue, mark Sleeping */ + prepare_to_wait(curr, TaskState::Sleeping); + + /* Release external lock AFTER we are on the queue (critical ordering) */ + if (external_lock) { + external_lock->release(); + } + + /* Phase 2: context switch away — will return here when woken */ + schedule(); + + /* Phase 3: clean up */ + finish_wait(curr); +} + +/* ------------------------------------------------------------------ */ +/* Wakeup */ +/* ------------------------------------------------------------------ */ + +bool WaitQueue::wake_one() +{ + LockGuard guard(lock_); + + if (head_.is_empty()) + return false; + + /* Dequeue the first (oldest) waiter */ + list_node* node = head_.next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + + TaskControlBlock* task = + list_entry(node); + + task->state = TaskState::Runnable; + sched_enqueue(task); + + return true; +} + +int WaitQueue::wake_all() +{ + LockGuard guard(lock_); + + int count = 0; + while (!head_.is_empty()) { + list_node* node = head_.next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + + TaskControlBlock* task = + list_entry(node); + + task->state = TaskState::Runnable; + sched_enqueue(task); + count++; + } + + return count; +} diff --git a/lib/cxx_abi.cc b/lib/cxx_abi.cc new file mode 100644 index 0000000..872b4a2 --- /dev/null +++ b/lib/cxx_abi.cc @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * cxx_abi.cc — Minimal C++ ABI stubs for freestanding kernel + * + * Provides symbols that the compiler/linker expects from the C++ + * runtime library. All error paths are fatal (kernel_panic). + * + * Reference: docs/specs/cpp_runtime.md + */ + +#include + +/* Pure virtual function call trap. + * Generated by the compiler in vtable entries for pure virtual methods. + * Reaching this means a derived class failed to implement a pure virtual. */ +extern "C" void __cxa_pure_virtual() +{ + kernel_panic("pure virtual function call"); +} + +/* Global destructor registration. + * The kernel does not support global object destruction — all global + * objects use Placement New with explicit lifetime management. + * If the compiler emits a call to this, it indicates a code violation. */ +extern "C" int __cxa_atexit(void (*)(void*), void*, void*) +{ + kernel_panic("__cxa_atexit called in kernel"); + return 0; // unreachable +} + +/* DSO handle — required by __cxa_atexit ABI. + * The kernel is statically linked; this symbol just needs to exist. */ +extern "C" { +void* __dso_handle __attribute__((visibility("hidden"))) = nullptr; +} diff --git a/lib/fdt.c b/lib/fdt.c new file mode 100644 index 0000000..1b20383 --- /dev/null +++ b/lib/fdt.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt.c" diff --git a/lib/fdt_addresses.c b/lib/fdt_addresses.c new file mode 100644 index 0000000..d58f896 --- /dev/null +++ b/lib/fdt_addresses.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_addresses.c" diff --git a/lib/fdt_empty_tree.c b/lib/fdt_empty_tree.c new file mode 100644 index 0000000..37f8ef8 --- /dev/null +++ b/lib/fdt_empty_tree.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_empty_tree.c" diff --git a/lib/fdt_ro.c b/lib/fdt_ro.c new file mode 100644 index 0000000..3599d49 --- /dev/null +++ b/lib/fdt_ro.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_ro.c" diff --git a/lib/fdt_rw.c b/lib/fdt_rw.c new file mode 100644 index 0000000..65d7a71 --- /dev/null +++ b/lib/fdt_rw.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_rw.c" diff --git a/lib/fdt_strerror.c b/lib/fdt_strerror.c new file mode 100644 index 0000000..dae35ea --- /dev/null +++ b/lib/fdt_strerror.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_strerror.c" diff --git a/lib/fdt_sw.c b/lib/fdt_sw.c new file mode 100644 index 0000000..d04f220 --- /dev/null +++ b/lib/fdt_sw.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_sw.c" diff --git a/lib/fdt_wip.c b/lib/fdt_wip.c new file mode 100644 index 0000000..ebfb3c1 --- /dev/null +++ b/lib/fdt_wip.c @@ -0,0 +1,2 @@ +#include +#include "../scripts/dtc/libfdt/fdt_wip.c" diff --git a/lib/klog.cc b/lib/klog.cc new file mode 100644 index 0000000..86c67f8 --- /dev/null +++ b/lib/klog.cc @@ -0,0 +1,574 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * klog.cc — Kernel logging subsystem implementation + * + * Implements the ring buffer, spinlock-protected writes, log level + * filtering, and synchronous console output. + * + * Phase 2 TODO: async flush via kernel thread (see docs/specs/klog.md) + */ + +#include +#include +#include +#include +#include +#include + +/* Forward declaration — mpaland/printf */ +extern "C" int vsnprintf_(char* buffer, size_t count, + const char* format, __builtin_va_list va); + +/* ======================================================================== + * Ring Buffer State + * ======================================================================== */ + +struct KlogEntryHdr { + uint32 seq; + uint32 len; + uint64 ts; + uint16 cpu; + uint8 level; + uint8 subsys; + uint16 reserved; +}; + +static struct { + char data[KLOG_BUF_SIZE]; + uint32 head; /* Next write position */ + uint32 tail; /* Next read position (for /proc/kmsg) */ + uint32 console_tail; /* Next position to flush to console */ + uint32 dropped; + lume::atomic seq; + uint64 subsys_mask; + Spinlock lock; + KlogLevel console_level; /* Messages <= this level go to UART */ + bool initialized; + bool panic_mode; +} g_klog; + +/* ======================================================================== + * Level name table + * ======================================================================== */ + +static const char* klog_level_name(KlogLevel level) +{ + static const char* names[] = { + "EMERG", "ALERT", "CRIT", "ERR", + "WARN", "NOTICE", "INFO", "DEBUG" + }; + if (level <= KLOG_DEBUG) { + return names[level]; + } + return "?"; +} + +static const char* klog_subsys_name(KlogSubsys subsys) +{ + static const char* names[] = { + "GEN", + "SCHED", + "TRAP", + "IRQ", + "TIMER", + "VMM", + "PMM", + "SLAB", + "FS", + "DRV", + "TEST", + }; + if (subsys < KLOG_SUBSYS_MAX) { + return names[subsys]; + } + return "?"; +} + +static const char* parse_kern_level(const char* fmt, KlogLevel* level) +{ + if (!fmt || !level) { + return fmt; + } + + /* Linux-style "\001n" prefix (KERN_SOH) */ + if (fmt[0] == '\001' && fmt[1] >= '0' && fmt[1] <= '7') { + *level = static_cast(fmt[1] - '0'); + return fmt + 2; + } + + /* Alternate "" prefix */ + if (fmt[0] == '<' && fmt[1] >= '0' && fmt[1] <= '7' && fmt[2] == '>') { + *level = static_cast(fmt[1] - '0'); + return fmt + 3; + } + + return fmt; +} + +/* ======================================================================== + * Ring Buffer Operations + * ======================================================================== */ + +/* Write a byte to the ring buffer. Overwrites oldest data on overflow. */ +static inline uint32 min_u32(uint32 a, uint32 b) +{ + return (a < b) ? a : b; +} + +static void ring_write_bytes(const void* src, uint32 len) +{ + const char* p = reinterpret_cast(src); + for (uint32 i = 0; i < len; ++i) { + g_klog.data[(g_klog.head + i) & (KLOG_BUF_SIZE - 1)] = p[i]; + } + g_klog.head += len; +} + +static void ring_read_bytes(uint32 pos, void* dst, uint32 len) +{ + char* out = reinterpret_cast(dst); + for (uint32 i = 0; i < len; ++i) { + out[i] = g_klog.data[(pos + i) & (KLOG_BUF_SIZE - 1)]; + } +} + +static void ring_drop_oldest(uint32 needed) +{ + uint32 min_tail = min_u32(g_klog.tail, g_klog.console_tail); + + while (KLOG_BUF_SIZE - (g_klog.head - min_tail) < needed) { + KlogEntryHdr hdr; + ring_read_bytes(min_tail, &hdr, sizeof(hdr)); + + uint32 entry = sizeof(hdr) + hdr.len; + if (entry == 0 || entry > KLOG_BUF_SIZE) { + min_tail++; + g_klog.dropped++; + continue; + } + min_tail += entry; + g_klog.dropped++; + } + + if (g_klog.tail < min_tail) { + g_klog.tail = min_tail; + } + if (g_klog.console_tail < min_tail) { + g_klog.console_tail = min_tail; + } +} + +/* ======================================================================== + * Console Output (synchronous) + * ======================================================================== */ + +static void console_emit(const char* s, int len) +{ + console_write_all(s, len); +} + +static int klog_format_line(char* out, int out_size, + const KlogEntryHdr& hdr, + const char* msg, int msg_len) +{ + const char* lvl = klog_level_name(static_cast(hdr.level)); + const char* subsys = klog_subsys_name(static_cast(hdr.subsys)); + + int prefix = ksnprintf(out, out_size, + "[%s][cpu%u][%llu][%s] ", + lvl, (uint32)hdr.cpu, + (unsigned long long)hdr.ts, subsys); + if (prefix < 0) { + return 0; + } + + int total = prefix; + int avail = out_size - 1 - total; + if (avail > 0) { + int to_copy = (msg_len < avail) ? msg_len : avail; + for (int i = 0; i < to_copy; ++i) { + out[total + i] = msg[i]; + } + total += to_copy; + } + + if (total > 0 && out[total - 1] != '\n' && total < out_size - 1) { + out[total++] = '\n'; + } + + out[total] = '\0'; + return total; +} + +static int klog_read_internal(char* buf, int size, uint32* tail_ptr, bool consume) +{ + if (size <= 0) { + return 0; + } + + int written = 0; + uint32 pos = *tail_ptr; + + while (pos != g_klog.head) { + KlogEntryHdr hdr; + ring_read_bytes(pos, &hdr, sizeof(hdr)); + uint32 raw_len = hdr.len; + if (raw_len > KLOG_BUF_SIZE) { + break; + } + uint32 copy_len = raw_len; + if (copy_len > KLOG_LINE_MAX) { + copy_len = KLOG_LINE_MAX; + } + + char msg[KLOG_LINE_MAX + 1]; + ring_read_bytes(pos + sizeof(hdr), msg, copy_len); + msg[copy_len] = '\0'; + + char line[KLOG_LINE_MAX + 64]; + int line_len = klog_format_line(line, sizeof(line), hdr, msg, + static_cast(copy_len)); + + if (line_len <= 0 || written + line_len > size) { + break; + } + + for (int i = 0; i < line_len; ++i) { + buf[written + i] = line[i]; + } + written += line_len; + + pos += sizeof(hdr) + raw_len; + } + + if (consume) { + *tail_ptr = pos; + } + + return written; +} + +static void klog_vex(KlogLevel level, KlogSubsys subsys, + const char* fmt, __builtin_va_list va); + +/* ======================================================================== + * Public API + * ======================================================================== */ + +void klog_init(void) +{ + g_klog.head = 0; + g_klog.tail = 0; + g_klog.console_tail = 0; + g_klog.dropped = 0; + g_klog.seq.store(0, __ATOMIC_RELAXED); + g_klog.subsys_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; + g_klog.console_level = static_cast(KLOG_DEFAULT_LEVEL); + g_klog.initialized = true; + g_klog.panic_mode = false; + g_klog.lock.init("klog"); +} + +void klog(KlogLevel level, const char* fmt, ...) +{ + __builtin_va_list va; + __builtin_va_start(va, fmt); + klog_vex(level, KLOG_SUBSYS_GENERIC, fmt, va); + __builtin_va_end(va); +} + +void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...) +{ + __builtin_va_list va; + __builtin_va_start(va, fmt); + klog_vex(level, subsys, fmt, va); + __builtin_va_end(va); +} + +static void klog_vex(KlogLevel level, KlogSubsys subsys, + const char* fmt, __builtin_va_list va) +{ + if (subsys >= KLOG_SUBSYS_MAX) { + subsys = KLOG_SUBSYS_GENERIC; + } + + if (g_klog.initialized && ((g_klog.subsys_mask & (1ULL << subsys)) == 0)) { + return; + } + + char msg[KLOG_LINE_MAX + 1]; + int msg_len = vsnprintf_(msg, KLOG_LINE_MAX, fmt, va); + + if (msg_len < 0) { + return; + } + if (msg_len > KLOG_LINE_MAX) { + msg_len = KLOG_LINE_MAX; + } + msg[msg_len] = '\0'; + + KlogEntryHdr hdr; + hdr.seq = g_klog.seq.fetch_add(1, __ATOMIC_RELAXED); + hdr.len = static_cast(msg_len); + hdr.ts = arch::cpu::read_time(); + hdr.cpu = static_cast(arch::cpu::id()); + hdr.level = static_cast(level); + hdr.subsys = static_cast(subsys); + hdr.reserved = 0; + + bool do_console = (!g_klog.initialized || level <= g_klog.console_level); + + if (g_klog.initialized && !g_klog.panic_mode) { + LockGuard guard(g_klog.lock); + ring_drop_oldest(sizeof(hdr) + hdr.len); + ring_write_bytes(&hdr, sizeof(hdr)); + ring_write_bytes(msg, hdr.len); + if (do_console) { + g_klog.console_tail = g_klog.head; + /* Emit to UART while holding the lock to prevent + * interleaved output from concurrent CPUs. */ + char line[KLOG_LINE_MAX + 64]; + int line_len = klog_format_line(line, sizeof(line), + hdr, msg, hdr.len); + if (line_len > 0) { + console_emit(line, line_len); + } + } + } else if (do_console) { + /* Pre-init or panic mode: emit without lock */ + char line[KLOG_LINE_MAX + 64]; + int line_len = klog_format_line(line, sizeof(line), + hdr, msg, hdr.len); + if (line_len > 0) { + console_emit(line, line_len); + } + } +} + +int vprintk(const char* fmt, __builtin_va_list va) +{ + KlogLevel level = KLOG_INFO; + const char* real_fmt = parse_kern_level(fmt, &level); + + __builtin_va_list va_copy; + __builtin_va_copy(va_copy, va); + + char tmp[KLOG_LINE_MAX + 1]; + int len = vsnprintf_(tmp, KLOG_LINE_MAX, real_fmt, va_copy); + __builtin_va_end(va_copy); + + klog_vex(level, KLOG_SUBSYS_GENERIC, real_fmt, va); + + if (len < 0) { + return 0; + } + return len; +} + +int printk(const char* fmt, ...) +{ + __builtin_va_list va; + __builtin_va_start(va, fmt); + int ret = vprintk(fmt, va); + __builtin_va_end(va); + return ret; +} + +void klog_set_level(KlogLevel level) +{ + g_klog.console_level = level; +} + +void klog_set_subsys_mask(uint64 mask) +{ + g_klog.subsys_mask = mask; +} + +void klog_enable_subsys(KlogSubsys subsys) +{ + if (subsys < KLOG_SUBSYS_MAX) { + g_klog.subsys_mask |= (1ULL << subsys); + } +} + +void klog_disable_subsys(KlogSubsys subsys) +{ + if (subsys < KLOG_SUBSYS_MAX) { + g_klog.subsys_mask &= ~(1ULL << subsys); + } +} + +int klog_read(char* buf, int size) +{ + if (!g_klog.initialized || size <= 0) { + return 0; + } + + LockGuard guard(g_klog.lock); + return klog_read_internal(buf, size, &g_klog.tail, false); +} + +int klog_read_consume(char* buf, int size) +{ + if (!g_klog.initialized || size <= 0) { + return 0; + } + + LockGuard guard(g_klog.lock); + return klog_read_internal(buf, size, &g_klog.tail, true); +} + +void klog_flush(void) +{ + if (!g_klog.initialized) { + return; + } + + LockGuard guard(g_klog.lock); + + uint32 pos = g_klog.console_tail; + while (pos != g_klog.head) { + KlogEntryHdr hdr; + ring_read_bytes(pos, &hdr, sizeof(hdr)); + uint32 raw_len = hdr.len; + if (raw_len > KLOG_BUF_SIZE) { + break; + } + uint32 copy_len = raw_len; + if (copy_len > KLOG_LINE_MAX) { + copy_len = KLOG_LINE_MAX; + } + + char msg[KLOG_LINE_MAX + 1]; + ring_read_bytes(pos + sizeof(hdr), msg, copy_len); + msg[copy_len] = '\0'; + + char line[KLOG_LINE_MAX + 64]; + int line_len = klog_format_line(line, sizeof(line), hdr, msg, + static_cast(copy_len)); + if (line_len > 0) { + console_emit(line, line_len); + } + + pos += sizeof(hdr) + raw_len; + } + + g_klog.console_tail = pos; +} + +bool klog_ratelimit(KlogRateLimit* rl, uint64 now) +{ + if (!rl) { + return true; + } + + if (rl->interval == 0 || rl->burst == 0) { + return true; + } + + if (now - rl->last_ts >= rl->interval) { + rl->last_ts = now; + rl->printed = 0; + rl->suppressed = 0; + } + + if (rl->printed < rl->burst) { + rl->printed++; + return true; + } + + rl->suppressed++; + return false; +} + +void klog_stats(KlogStats* out) +{ + if (!out) { + return; + } + + out->head = g_klog.head; + out->tail = g_klog.tail; + out->console_tail = g_klog.console_tail; + out->dropped = g_klog.dropped; + out->seq = g_klog.seq.load(__ATOMIC_RELAXED); +} + +/* ======================================================================== + * Kernel Panic — Fatal Error Handler + * + * Dumps all critical S-mode CSRs and the stack pointer to the console, + * then halts the CPU. Uses kprintf (NOT klog) to avoid deadlocking + * if the panic was triggered while holding the klog spinlock. + * ======================================================================== */ + +extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) { + /* Disable interrupts immediately */ + arch::cpu::intr_off(); + + g_klog.panic_mode = true; + + /* ---- Banner ---- */ + kprintf("\n\n"); + kprintf("========================================\n"); + kprintf(" *** KERNEL PANIC ***\n"); + kprintf("========================================\n"); + + if (msg != nullptr) { + kprintf(" Reason : %s", msg); + if (detail != nullptr) { + kprintf(": %s", detail); + } + kprintf("\n"); + } + + /* ---- Read S-mode CSRs ---- */ + uint64 sepc, scause, stval, sstatus, satp, stvec, sscratch, sp_val; + + __asm__ volatile("csrr %0, sepc" : "=r"(sepc)); + __asm__ volatile("csrr %0, scause" : "=r"(scause)); + __asm__ volatile("csrr %0, stval" : "=r"(stval)); + __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); + __asm__ volatile("csrr %0, satp" : "=r"(satp)); + __asm__ volatile("csrr %0, stvec" : "=r"(stvec)); + __asm__ volatile("csrr %0, sscratch" : "=r"(sscratch)); + __asm__ volatile("mv %0, sp" : "=r"(sp_val)); + + kprintf("\n--- S-mode CSR Dump ---\n"); + kprintf(" sepc = 0x%016llx\n", sepc); + kprintf(" scause = 0x%016llx", scause); + + /* Decode scause for convenience */ + bool is_interrupt = (scause >> 63) != 0; + uint64 cause_code = scause & 0x7FFFFFFFFFFFFFFFULL; + if (is_interrupt) { + kprintf(" (interrupt, code=%llu)\n", cause_code); + } else { + kprintf(" (exception, code=%llu)\n", cause_code); + } + + kprintf(" stval = 0x%016llx\n", stval); + kprintf(" sstatus = 0x%016llx", sstatus); + kprintf(" [SIE=%llu SPP=%llu SPIE=%llu]\n", + (sstatus >> 1) & 1, /* SIE */ + (sstatus >> 8) & 1, /* SPP */ + (sstatus >> 5) & 1); /* SPIE */ + kprintf(" satp = 0x%016llx", satp); + uint64 satp_mode = satp >> 60; + kprintf(" [mode=%llu]\n", satp_mode); + kprintf(" stvec = 0x%016llx\n", stvec); + kprintf(" sscratch = 0x%016llx\n", sscratch); + kprintf(" sp = 0x%016llx\n", sp_val); + + kprintf(" cpu_id = %llu\n", arch::cpu::id()); + kprintf(" time = %llu\n", arch::cpu::read_time()); + + kprintf("========================================\n\n"); + + /* Halt — spin forever with WFI to save power */ + while (true) { + arch::cpu::halt_until_interrupt(); + } +} diff --git a/lib/kobject.cc b/lib/kobject.cc new file mode 100644 index 0000000..8e8329e --- /dev/null +++ b/lib/kobject.cc @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * kobject — implementation. + * + * Lives in lib/ (mirroring Linux's lib/kobject.c) because kobject is a + * generic primitive, not tied to the driver model. + * + * Reference: docs/specs/storage_stack.md §4.2 + */ + +#include +#include +#include + +void kobject_init(kobject* k, const kobj_type* type) +{ + if (k == nullptr) { + return; + } + k->name = nullptr; + k->parent = nullptr; + k->ktype = type; + k->state_in_sysfs = false; + k->entry.init(); + k->children.init(); + k->lock.init("kobject"); + kref_init(&k->refcount); +} + +int kobject_add(kobject* k, kobject* parent, const char* name) +{ + if (k == nullptr) { + return -1; + } + k->name = name; + if (parent != nullptr) { + kobject_get(parent); + k->parent = parent; + LockGuard g(parent->lock); + list_add_tail(&k->entry, &parent->children); + } + return 0; +} + +void kobject_del(kobject* k) +{ + if (k == nullptr || k->parent == nullptr) { + return; + } + kobject* parent = k->parent; + { + LockGuard g(parent->lock); + list_del_init(&k->entry); + } + k->parent = nullptr; + kobject_put(parent); +} + +void kobject_get(kobject* k) +{ + if (k != nullptr) { + kref_get(&k->refcount); + } +} + +static void kobject_release(kref* r) +{ + kobject* k = container_of(r, kobject, refcount); + if (k->ktype != nullptr && k->ktype->release != nullptr) { + k->ktype->release(k); + } +} + +void kobject_put(kobject* k) +{ + if (k == nullptr) { + return; + } + kref_put(&k->refcount, kobject_release); +} diff --git a/lib/kprintf.cc b/lib/kprintf.cc new file mode 100644 index 0000000..a98de71 --- /dev/null +++ b/lib/kprintf.cc @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * kprintf.cc — Kernel formatted output + * + * Uses mpaland/printf's vsnprintf_ as the core formatting engine. + * + * Architecture: + * vsnprintf_() ← pure formatting, writes to buffer + * ↓ + * kprintf() ← format + emit via console_write_all() + * kvprintf() ← va_list variant + * ksnprintf() ← format into caller's buffer + * + * Output path: + * Pre-registration: console_write_all() → early_putc() fallback + * Post-registration: console_write_all() → struct console chain + */ + +#include +#include +#include +#include +#include "printf.h" + +/* ------------------------------------------------------------------ */ +/* Console spinlock (raw, no scheduler dependency — safe in panic) */ +/* ------------------------------------------------------------------ */ + +static lume::atomic g_console_lock{0}; + +static inline void console_lock_acquire() +{ + while (g_console_lock.exchange(1, __ATOMIC_ACQUIRE) != 0) { + /* spin */ + } +} + +static inline void console_lock_release() +{ + g_console_lock.store(0, __ATOMIC_RELEASE); +} + +/* ------------------------------------------------------------------ */ +/* _putchar: required by mpaland/printf for printf_() / vprintf_() */ +/* ------------------------------------------------------------------ */ + +void _putchar(char character) +{ + early_putc(character); +} + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +extern "C" int kprintf(const char* fmt, ...) +{ + char buf[256]; + va_list va; + va_start(va, fmt); + int len = vsnprintf_(buf, sizeof(buf), fmt, va); + va_end(va); + + /* Emit to console under lock */ + console_lock_acquire(); + console_write_all(buf, len); + console_lock_release(); + return len; +} + +extern "C" int kvprintf(const char* fmt, va_list va) +{ + char buf[256]; + int len = vsnprintf_(buf, sizeof(buf), fmt, va); + + console_lock_acquire(); + console_write_all(buf, len); + console_lock_release(); + return len; +} + +extern "C" int ksnprintf(char* buf, int count, const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + int ret = vsnprintf_(buf, (size_t)count, fmt, va); + va_end(va); + return ret; +} diff --git a/lib/module.mk b/lib/module.mk new file mode 100644 index 0000000..0b178f8 --- /dev/null +++ b/lib/module.mk @@ -0,0 +1,66 @@ +# lib/module.mk +SRCS_CC += lib/cxx_abi.cc +SRCS_CC += lib/rbtree.cc +SRCS_CC += lib/kobject.cc +SRCS_CC += lib/kprintf.cc +SRCS_CC += lib/klog.cc +SRCS_CC += lib/random.cc +SRCS_C += lib/printf.c + +# ============================================================ +# musl string library (cross-platform with arch asm overrides) +# ============================================================ + +# Map LumeOS ARCH to musl string arch directory +# riscv has no musl asm optimizations → pure C fallback +MUSL_STRING_ARCH_riscv := +MUSL_STRING_ARCH_x86_64 := x86_64 +MUSL_STRING_ARCH_i386 := i386 +MUSL_STRING_ARCH_aarch64:= aarch64 +MUSL_STRING_ARCH_arm := arm + +MUSL_STRING_ARCH := $(MUSL_STRING_ARCH_$(ARCH)) + +# Collect arch-specific asm sources (if any for this arch) +MUSL_ASM_SRCS := +ifneq ($(MUSL_STRING_ARCH),) + MUSL_ASM_SRCS += $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.S) \ + $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.s) +endif + +# Derive which C files are overridden by asm (strip path+ext to get base name) +MUSL_ASM_BASENAMES := $(sort $(basename $(notdir $(MUSL_ASM_SRCS)))) + +# All C sources, then filter out those with asm replacements +MUSL_ALL_C := $(wildcard lib/string/*.c) +MUSL_EXCLUDED_C := $(foreach name,$(MUSL_ASM_BASENAMES),lib/string/$(name).c) +MUSL_C_SRCS := $(filter-out $(MUSL_EXCLUDED_C),$(MUSL_ALL_C)) + +SRCS_C += $(MUSL_C_SRCS) +SRCS_S += $(filter %.S,$(MUSL_ASM_SRCS)) +SRCS_s += $(filter %.s,$(MUSL_ASM_SRCS)) + +# ============================================================ +# libfdt — kernel adapter shims around scripts/dtc/libfdt/. +# +# Each shim is two lines: +# #include +# #include "../scripts/dtc/libfdt/fdt*.c" +# matching Linux lib/fdt*.c verbatim. The compile recipe needs: +# - reachable via the default -Iinclude path +# - -I scripts/dtc/libfdt so the upstream source's #include +# and friends resolve to scripts/dtc/libfdt/libfdt.h +# Overriding CFLAGS_LIBC (which the %.o: %.c rule uses) avoids dragging +# in libc-shim headers — libfdt is freestanding-compatible and uses +# only the string functions forward-declared in . +# ============================================================ +LIBFDT_FILES := fdt.c fdt_ro.c fdt_wip.c fdt_rw.c fdt_sw.c \ + fdt_strerror.c fdt_empty_tree.c fdt_addresses.c +SRCS_C += $(addprefix lib/,$(LIBFDT_FILES)) + +# Per-target CFLAGS override: plain CFLAGS plus the upstream include dir. +# Use `=` (recursive) not `:=` because $(CFLAGS) is defined in the top +# Makefile AFTER this file is included — `:=` would expand it now (empty) +# and lose -Iinclude, breaking . +$(addprefix $(BUILD)/lib/,$(LIBFDT_FILES:.c=.o)): CFLAGS_LIBC = \ + $(CFLAGS) -I scripts/dtc/libfdt diff --git a/lib/printf.c b/lib/printf.c new file mode 100644 index 0000000..8a700ad --- /dev/null +++ b/lib/printf.c @@ -0,0 +1,914 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" + + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; (void)buffer; (void)idx; (void)maxlen; +} + + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)buffer; (void)idx; (void)maxlen; + if (character) { + _putchar(character); + } +} + + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)idx; (void)maxlen; + if (character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s); + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) +{ + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } + else if (diff < 0.5) { + } + else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } + else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } + else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } + else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } + else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; + case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; + case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; + case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; + case '#': flags |= FLAGS_HASH; format++; n = 1U; break; + default : n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } + else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } + else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } + else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; + } + + // evaluate specifier + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } + else if (*format == 'o') { + base = 8U; + } + else if (*format == 'b') { + base = 2U; + } + else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } + else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f' : + case 'F' : + if (*format == 'F') flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c' : { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p' : { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); + } + else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%' : + out('%', buffer, idx++, maxlen); + format++; + break; + + default : + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) +{ + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + + +int snprintf_(char* buffer, size_t count, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + + +int vprintf_(const char* format, va_list va) +{ + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) +{ + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) +{ + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = { out, arg }; + const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} diff --git a/lib/printf.h b/lib/printf.h new file mode 100644 index 0000000..8aa23cf --- /dev/null +++ b/lib/printf.h @@ -0,0 +1,129 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include + +/* LumeOS: avoid stddef.h — define types from compiler builtins directly. + * This works in both C and C++ and matches our types.h definition. */ +#ifndef _SIZE_T_DEFINED +#define _SIZE_T_DEFINED +typedef __SIZE_TYPE__ size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __INTMAX_TYPE__ intmax_t; +#endif + +/* LumeOS: kernel does not need floating-point formatted output */ +#define PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_DISABLE_SUPPORT_EXPONENTIAL + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +#define printf printf_ +int printf_(const char* format, ...); + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char* buffer, const char* format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char* buffer, size_t count, const char* format, ...); +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char* format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/lib/random.cc b/lib/random.cc new file mode 100644 index 0000000..428c75c --- /dev/null +++ b/lib/random.cc @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include + +#include + +/* + * Early Boot PRNG (xoshiro256**) + * State size: 256 bits (4 * 64-bit words) + * Extremely fast, excellent statistical properties. + */ + +static uint64 g_state[4]; +static bool g_initialized = false; +static uint64 g_init_seed = 0; +static RandomSeedSource g_seed_source = RandomSeedSource::Unknown; + +static inline uint64 rotl(const uint64 x, int k) { + return (x << k) | (x >> (64 - k)); +} + +/* SplitMix64 used to initialize xoshiro state from a single 64-bit seed */ +static uint64 splitmix64(uint64& state) { + uint64 z = (state += 0x9e3779b97f4a7c15ULL); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; + return z ^ (z >> 31); +} + +void random_early_init(uint64 seed) +{ + if (g_initialized) return; + + RandomSeedSource source = RandomSeedSource::Unknown; + + /* If seed is 0 (not provided by bootloader), try hardware */ + if (seed == 0) { + uint64 hw_val; + if (arch::get_hw_random(hw_val)) { + seed = hw_val; + source = RandomSeedSource::HwRandom; + } else { + /* Fallback: use boot cycles mixed with a constant */ + seed = arch::get_cycles() ^ 0xdeadbeefbadc0ffeULL; + source = RandomSeedSource::CycleFallback; + } + } else { + source = RandomSeedSource::FdtProvided; + } + + uint64 sm_state = seed; + g_state[0] = splitmix64(sm_state); + g_state[1] = splitmix64(sm_state); + g_state[2] = splitmix64(sm_state); + g_state[3] = splitmix64(sm_state); + + g_init_seed = seed; + g_seed_source = source; + g_initialized = true; + kprintf("[random] early PRNG initialized, seed=0x%llx, source=%s\n", + seed, random_seed_source_name(source)); +} + +uint64 get_random_u64() +{ + if (!g_initialized) { + random_early_init(0); + } + + const uint64 result = rotl(g_state[1] * 5, 7) * 9; + + const uint64 t = g_state[1] << 17; + + g_state[2] ^= g_state[0]; + g_state[3] ^= g_state[1]; + g_state[1] ^= g_state[2]; + g_state[0] ^= g_state[3]; + + g_state[2] ^= t; + g_state[3] = rotl(g_state[3], 45); + + /* Mix with current cycles for slight additional unpredictability */ + return result ^ arch::get_cycles(); +} + +uint32 get_random_u32() +{ + return static_cast(get_random_u64() >> 32); +} + +void get_random_bytes(void* buf, size_t len) +{ + uint8* p = static_cast(buf); + while (len >= 8) { + uint64 val = get_random_u64(); + for (int i = 0; i < 8; i++) { + p[i] = (val >> (i * 8)) & 0xFF; + } + p += 8; + len -= 8; + } + if (len > 0) { + uint64 val = get_random_u64(); + for (size_t i = 0; i < len; i++) { + p[i] = (val >> (i * 8)) & 0xFF; + } + } +} + +void add_device_randomness(const void* buf, size_t len) +{ + /* + * For now, just mix into the xoshiro state using a simple XOR. + * In the future, this will feed the CSPRNG entropy pool. + */ + const uint8* p = static_cast(buf); + uint64 mix = arch::get_cycles(); + for (size_t i = 0; i < len; i++) { + mix = (mix << 5) ^ (mix >> 59) ^ p[i]; + } + + g_state[0] ^= mix; + g_state[1] ^= arch::get_cycles(); +} + +RandomSeedSource random_early_seed_source() +{ + return g_seed_source; +} + +uint64 random_early_seed_value() +{ + return g_init_seed; +} + +const char* random_seed_source_name(RandomSeedSource source) +{ + switch (source) { + case RandomSeedSource::FdtProvided: + return "fdt"; + case RandomSeedSource::HwRandom: + return "hw"; + case RandomSeedSource::CycleFallback: + return "cycle"; + case RandomSeedSource::Unknown: + default: + return "unknown"; + } +} diff --git a/lib/rbtree.cc b/lib/rbtree.cc new file mode 100644 index 0000000..6718a1a --- /dev/null +++ b/lib/rbtree.cc @@ -0,0 +1,596 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + (C) 2002 David Woodhouse + (C) 2012 Michel Lespinasse + + + linux/lib/rbtree.c +*/ + +#include + + +/* + * red-black trees properties: https://en.wikipedia.org/wiki/Rbtree + * + * 1) A node is either red or black + * 2) The root is black + * 3) All leaves (nullptr) are black + * 4) Both children of every red node are black + * 5) Every simple path from root to leaves contains the same number + * of black nodes. + * + * 4 and 5 give the O(log n) guarantee, since 4 implies you cannot have two + * consecutive red nodes in a path and every red node is therefore followed by + * a black. So if B is the number of black nodes on every simple path (as per + * 5), then the longest possible path due to 4 is 2B. + * + * We shall indicate color with case, where black nodes are uppercase and red + * nodes will be lowercase. Unknown color nodes shall be drawn as red within + * parentheses and have some accompanying text comment. + */ + +/* + * Notes on lockless lookups: + * + * All stores to the tree structure (rb_left and rb_right) must be done using + * WRITE_ONCE(). And we must not inadvertently cause (temporary) loops in the + * tree structure as seen in program order. + * + * These two requirements will allow lockless iteration of the tree -- not + * correct iteration mind you, tree rotations are not atomic so a lookup might + * miss entire subtrees. + * + * But they do guarantee that any such traversal will only see valid elements + * and that it will indeed complete -- does not get stuck in a loop. + * + * It also guarantees that if the lookup returns an element it is the 'correct' + * one. But not returning an element does _NOT_ mean it's not present. + * + * NOTE: + * + * Stores to __rb_parent_color are not important for simple lookups so those + * are left undone as of now. Nor did I check for loops involving parent + * pointers. + */ + +static inline void rb_set_black(struct rb_node *rb) +{ + rb->__rb_parent_color += RB_BLACK; +} + +static inline struct rb_node *rb_red_parent(struct rb_node *red) +{ + return (struct rb_node *)red->__rb_parent_color; +} + +/* + * Helper function for rotations: + * - old's parent and color get assigned to _new + * - old gets assigned _new as a parent and 'color' as a color. + */ +static inline void +__rb_rotate_set_parents(struct rb_node *old, struct rb_node *_new, + struct rb_root *root, int color) +{ + struct rb_node *parent = rb_parent(old); + _new->__rb_parent_color = old->__rb_parent_color; + rb_set_parent_color(old, _new, color); + __rb_change_child(old, _new, parent, root); +} + +static __always_inline void +__rb_insert(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) +{ + struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; + + while (true) { + /* + * Loop invariant: node is red. + */ + if (unlikely(!parent)) { + /* + * The inserted node is root. Either this is the + * first node, or we recursed at Case 1 below and + * are no longer violating 4). + */ + rb_set_parent_color(node, nullptr, RB_BLACK); + break; + } + + /* + * If there is a black parent, we are done. + * Otherwise, take some corrective action as, + * per 4), we don't want a red root or two + * consecutive red nodes. + */ + if(rb_is_black(parent)) + break; + + gparent = rb_red_parent(parent); + + tmp = gparent->rb_right; + if (parent != tmp) { /* parent == gparent->rb_left */ + if (tmp && rb_is_red(tmp)) { + /* + * Case 1 - node's uncle is red (color flips). + * + * G g + * / \ / \ + * p u --> P U + * / / + * n n + * + * However, since g's parent might be red, and + * 4) does not allow this, we need to recurse + * at g. + */ + rb_set_parent_color(tmp, gparent, RB_BLACK); + rb_set_parent_color(parent, gparent, RB_BLACK); + node = gparent; + parent = rb_parent(node); + rb_set_parent_color(node, parent, RB_RED); + continue; + } + + tmp = parent->rb_right; + if (node == tmp) { + /* + * Case 2 - node's uncle is black and node is + * the parent's right child (left rotate at parent). + * + * G G + * / \ / \ + * p U --> n U + * \ / + * n p + * + * This still leaves us in violation of 4), the + * continuation into Case 3 will fix that. + */ + tmp = node->rb_left; + WRITE_ONCE(parent->rb_right, tmp); + WRITE_ONCE(node->rb_left, parent); + if (tmp) + rb_set_parent_color(tmp, parent, + RB_BLACK); + rb_set_parent_color(parent, node, RB_RED); + augment_rotate(parent, node); + parent = node; + tmp = node->rb_right; + } + + /* + * Case 3 - node's uncle is black and node is + * the parent's left child (right rotate at gparent). + * + * G P + * / \ / \ + * p U --> n g + * / \ + * n U + */ + WRITE_ONCE(gparent->rb_left, tmp); /* == parent->rb_right */ + WRITE_ONCE(parent->rb_right, gparent); + if (tmp) + rb_set_parent_color(tmp, gparent, RB_BLACK); + __rb_rotate_set_parents(gparent, parent, root, RB_RED); + augment_rotate(gparent, parent); + break; + } else { + tmp = gparent->rb_left; + if (tmp && rb_is_red(tmp)) { + /* Case 1 - color flips */ + rb_set_parent_color(tmp, gparent, RB_BLACK); + rb_set_parent_color(parent, gparent, RB_BLACK); + node = gparent; + parent = rb_parent(node); + rb_set_parent_color(node, parent, RB_RED); + continue; + } + + tmp = parent->rb_left; + if (node == tmp) { + /* Case 2 - right rotate at parent */ + tmp = node->rb_right; + WRITE_ONCE(parent->rb_left, tmp); + WRITE_ONCE(node->rb_right, parent); + if (tmp) + rb_set_parent_color(tmp, parent, + RB_BLACK); + rb_set_parent_color(parent, node, RB_RED); + augment_rotate(parent, node); + parent = node; + tmp = node->rb_left; + } + + /* Case 3 - left rotate at gparent */ + WRITE_ONCE(gparent->rb_right, tmp); /* == parent->rb_left */ + WRITE_ONCE(parent->rb_left, gparent); + if (tmp) + rb_set_parent_color(tmp, gparent, RB_BLACK); + __rb_rotate_set_parents(gparent, parent, root, RB_RED); + augment_rotate(gparent, parent); + break; + } + } +} + +/* + * Inline version for rb_erase() use - we want to be able to inline + * and eliminate the dummy_rotate callback there + */ +static __always_inline void +____rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) +{ + struct rb_node *node = nullptr, *sibling, *tmp1, *tmp2; + + while (true) { + /* + * Loop invariants: + * - node is black (or nullptr on first iteration) + * - node is not the root (parent is not nullptr) + * - All leaf paths going through parent and node have a + * black node count that is 1 lower than other leaf paths. + */ + sibling = parent->rb_right; + if (node != sibling) { /* node == parent->rb_left */ + if (rb_is_red(sibling)) { + /* + * Case 1 - left rotate at parent + * + * P S + * / \ / \ + * N s --> p Sr + * / \ / \ + * Sl Sr N Sl + */ + tmp1 = sibling->rb_left; + WRITE_ONCE(parent->rb_right, tmp1); + WRITE_ONCE(sibling->rb_left, parent); + rb_set_parent_color(tmp1, parent, RB_BLACK); + __rb_rotate_set_parents(parent, sibling, root, + RB_RED); + augment_rotate(parent, sibling); + sibling = tmp1; + } + tmp1 = sibling->rb_right; + if (!tmp1 || rb_is_black(tmp1)) { + tmp2 = sibling->rb_left; + if (!tmp2 || rb_is_black(tmp2)) { + /* + * Case 2 - sibling color flip + * (p could be either color here) + * + * (p) (p) + * / \ / \ + * N S --> N s + * / \ / \ + * Sl Sr Sl Sr + * + * This leaves us violating 5) which + * can be fixed by flipping p to black + * if it was red, or by recursing at p. + * p is red when coming from Case 1. + */ + rb_set_parent_color(sibling, parent, + RB_RED); + if (rb_is_red(parent)) + rb_set_black(parent); + else { + node = parent; + parent = rb_parent(node); + if (parent) + continue; + } + break; + } + /* + * Case 3 - right rotate at sibling + * (p could be either color here) + * + * (p) (p) + * / \ / \ + * N S --> N sl + * / \ \ + * sl Sr S + * \ + * Sr + * + * Note: p might be red, and then both + * p and sl are red after rotation(which + * breaks property 4). This is fixed in + * Case 4 (in __rb_rotate_set_parents() + * which set sl the color of p + * and set p RB_BLACK) + * + * (p) (sl) + * / \ / \ + * N sl --> P S + * \ / \ + * S N Sr + * \ + * Sr + */ + tmp1 = tmp2->rb_right; + WRITE_ONCE(sibling->rb_left, tmp1); + WRITE_ONCE(tmp2->rb_right, sibling); + WRITE_ONCE(parent->rb_right, tmp2); + if (tmp1) + rb_set_parent_color(tmp1, sibling, + RB_BLACK); + augment_rotate(sibling, tmp2); + tmp1 = sibling; + sibling = tmp2; + } + /* + * Case 4 - left rotate at parent + color flips + * (p and sl could be either color here. + * After rotation, p becomes black, s acquires + * p's color, and sl keeps its color) + * + * (p) (s) + * / \ / \ + * N S --> P Sr + * / \ / \ + * (sl) sr N (sl) + */ + tmp2 = sibling->rb_left; + WRITE_ONCE(parent->rb_right, tmp2); + WRITE_ONCE(sibling->rb_left, parent); + rb_set_parent_color(tmp1, sibling, RB_BLACK); + if (tmp2) + rb_set_parent(tmp2, parent); + __rb_rotate_set_parents(parent, sibling, root, + RB_BLACK); + augment_rotate(parent, sibling); + break; + } else { + sibling = parent->rb_left; + if (rb_is_red(sibling)) { + /* Case 1 - right rotate at parent */ + tmp1 = sibling->rb_right; + WRITE_ONCE(parent->rb_left, tmp1); + WRITE_ONCE(sibling->rb_right, parent); + rb_set_parent_color(tmp1, parent, RB_BLACK); + __rb_rotate_set_parents(parent, sibling, root, + RB_RED); + augment_rotate(parent, sibling); + sibling = tmp1; + } + tmp1 = sibling->rb_left; + if (!tmp1 || rb_is_black(tmp1)) { + tmp2 = sibling->rb_right; + if (!tmp2 || rb_is_black(tmp2)) { + /* Case 2 - sibling color flip */ + rb_set_parent_color(sibling, parent, + RB_RED); + if (rb_is_red(parent)) + rb_set_black(parent); + else { + node = parent; + parent = rb_parent(node); + if (parent) + continue; + } + break; + } + /* Case 3 - left rotate at sibling */ + tmp1 = tmp2->rb_left; + WRITE_ONCE(sibling->rb_right, tmp1); + WRITE_ONCE(tmp2->rb_left, sibling); + WRITE_ONCE(parent->rb_left, tmp2); + if (tmp1) + rb_set_parent_color(tmp1, sibling, + RB_BLACK); + augment_rotate(sibling, tmp2); + tmp1 = sibling; + sibling = tmp2; + } + /* Case 4 - right rotate at parent + color flips */ + tmp2 = sibling->rb_right; + WRITE_ONCE(parent->rb_left, tmp2); + WRITE_ONCE(sibling->rb_right, parent); + rb_set_parent_color(tmp1, sibling, RB_BLACK); + if (tmp2) + rb_set_parent(tmp2, parent); + __rb_rotate_set_parents(parent, sibling, root, + RB_BLACK); + augment_rotate(parent, sibling); + break; + } + } +} + +/* Non-inline version for rb_erase_augmented() use */ +void __rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) +{ + ____rb_erase_color(parent, root, augment_rotate); +} + +/* + * Non-augmented rbtree manipulation functions. + * + * We use dummy augmented callbacks here, and have the compiler optimize them + * out of the rb_insert_color() and rb_erase() function definitions. + */ + +static inline void dummy_propagate(struct rb_node *node, struct rb_node *stop) {} +static inline void dummy_copy(struct rb_node *old, struct rb_node *_new) {} +static inline void dummy_rotate(struct rb_node *old, struct rb_node *_new) {} + +static const struct rb_augment_callbacks dummy_callbacks = { + .propagate = dummy_propagate, + .copy = dummy_copy, + .rotate = dummy_rotate +}; + +void rb_insert_color(struct rb_node *node, struct rb_root *root) +{ + __rb_insert(node, root, dummy_rotate); +} + +void rb_erase(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *rebalance; + rebalance = __rb_erase_augmented(node, root, &dummy_callbacks); + if (rebalance) + ____rb_erase_color(rebalance, root, dummy_rotate); +} + + +/* + * Augmented rbtree manipulation functions. + * + * This instantiates the same __always_inline functions as in the non-augmented + * case, but this time with user-defined callbacks. + */ + +void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) +{ + __rb_insert(node, root, augment_rotate); +} + +struct rb_node *rb_next(const struct rb_node *node) +{ + struct rb_node *parent; + + if (RB_EMPTY_NODE(node)) + return nullptr; + + /* + * If we have a right-hand child, go down and then left as far + * as we can. + */ + if (node->rb_right) { + node = node->rb_right; + while (node->rb_left) + node = node->rb_left; + return (struct rb_node *)node; + } + + /* + * No right-hand children. Everything down and left is smaller than us, + * so any 'next' node must be in the general direction of our parent. + * Go up the tree; any time the ancestor is a right-hand child of its + * parent, keep going up. First time it's a left-hand child of its + * parent, said parent is our 'next' node. + */ + while ((parent = rb_parent(node)) && node == parent->rb_right) + node = parent; + + return parent; +} + +struct rb_node *rb_prev(const struct rb_node *node) +{ + struct rb_node *parent; + + if (RB_EMPTY_NODE(node)) + return nullptr; + + /* + * If we have a left-hand child, go down and then right as far + * as we can. + */ + if (node->rb_left) { + node = node->rb_left; + while (node->rb_right) + node = node->rb_right; + return (struct rb_node *)node; + } + + /* + * No left-hand children. Go up till we find an ancestor which + * is a right-hand child of its parent. + */ + while ((parent = rb_parent(node)) && node == parent->rb_left) + node = parent; + + return parent; +} + +void rb_replace_node(struct rb_node *victim, struct rb_node *_new, + struct rb_root *root) +{ + struct rb_node *parent = rb_parent(victim); + + /* Copy the pointers/colour from the victim to the replacement */ + *_new = *victim; + + /* Set the surrounding nodes to point to the replacement */ + if (victim->rb_left) + rb_set_parent(victim->rb_left, _new); + if (victim->rb_right) + rb_set_parent(victim->rb_right, _new); + __rb_change_child(victim, _new, parent, root); +} + + +static struct rb_node *rb_left_deepest_node(const struct rb_node *node) +{ + for (;;) { + if (node->rb_left) + node = node->rb_left; + else if (node->rb_right) + node = node->rb_right; + else + return (struct rb_node *)node; + } +} + +struct rb_node *rb_next_postorder(const struct rb_node *node) +{ + const struct rb_node *parent; + if (!node) + return nullptr; + parent = rb_parent(node); + + /* If we're sitting on node, we've already seen our children */ + if (parent && node == parent->rb_left && parent->rb_right) { + /* If we are the parent's left node, go to the parent's right + * node then all the way down to the left */ + return rb_left_deepest_node(parent->rb_right); + } else + /* Otherwise we are the parent's right node, and the parent + * should be next */ + return (struct rb_node *)parent; +} + +struct rb_node *rb_first_postorder(const struct rb_root *root) +{ + if (!root->rb_node) + return nullptr; + + return rb_left_deepest_node(root->rb_node); +} + +struct rb_node *rb_first(const struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return nullptr; + while (n->rb_left) + n = n->rb_left; + return n; +} + +struct rb_node *rb_last(const struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return nullptr; + while (n->rb_right) + n = n->rb_right; + return n; +} diff --git a/lib/string/COPYRIGHT b/lib/string/COPYRIGHT new file mode 100644 index 0000000..2f15edc --- /dev/null +++ b/lib/string/COPYRIGHT @@ -0,0 +1,193 @@ +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + +Authors/contributors include: + +A. Wilcox +Ada Worcester +Alex Dowad +Alex Suykov +Alexander Monakov +Andre McCurdy +Andrew Kelley +Anthony G. Basile +Aric Belsito +Arvid Picciani +Bartosz Brachaczek +Benjamin Peterson +Bobby Bingham +Boris Brezillon +Brent Cook +Chris Spiegel +Clément Vasseur +Daniel Micay +Daniel Sabogal +Daurnimator +David Carlier +David Edelsohn +Denys Vlasenko +Dmitry Ivanov +Dmitry V. Levin +Drew DeVault +Emil Renner Berthing +Fangrui Song +Felix Fietkau +Felix Janda +Gianluca Anzolin +Hauke Mehrtens +He X +Hiltjo Posthuma +Isaac Dunham +Jaydeep Patil +Jens Gustedt +Jeremy Huntwork +Jo-Philipp Wich +Joakim Sindholt +John Spencer +Julien Ramseier +Justin Cormack +Kaarle Ritvanen +Khem Raj +Kylie McClain +Leah Neukirchen +Luca Barbato +Luka Perkov +Lynn Ochs +M Farkas-Dyck (Strake) +Mahesh Bodapati +Markus Wichmann +Masanori Ogino +Michael Clark +Michael Forney +Mikhail Kremnyov +Natanael Copa +Nicholas J. Kain +orc +Pascal Cuoq +Patrick Oppenlander +Petr Hosek +Petr Skocik +Pierre Carrier +Reini Urban +Rich Felker +Richard Pennington +Ryan Fairfax +Samuel Holland +Segev Finer +Shiz +sin +Solar Designer +Stefan Kristiansson +Stefan O'Rear +Szabolcs Nagy +Timo Teräs +Trutz Behn +Will Dietz +William Haddon +William Pitcock + +Portions of this software are derived from third-party works licensed +under terms compatible with the above MIT license: + +The TRE regular expression implementation (src/regex/reg* and +src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +under a 2-clause BSD license (license text in the source files). The +included version has been heavily modified by Rich Felker in 2012, in +the interests of size, simplicity, and namespace cleanliness. + +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. + +The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 +The Android Open Source Project and is licensed under a two-clause BSD +license. It was taken from Bionic libc, used on Android. + +The AArch64 memcpy and memset code (src/string/aarch64/*) are +Copyright © 1999-2019, Arm Limited. + +The implementation of DES for crypt (src/crypt/crypt_des.c) is +Copyright © 1994 David Burren. It is licensed under a BSD license. + +The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was +originally written by Solar Designer and placed into the public +domain. The code also comes with a fallback permissive license for use +in jurisdictions that may not recognize the public domain. + +The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 +Lynn Ochs and is licensed under an MIT-style license. + +The x86_64 port was written by Nicholas J. Kain and is licensed under +the standard MIT terms. + +The mips and microblaze ports were originally written by Richard +Pennington for use in the ellcc project. The original code was adapted +by Rich Felker for build system and code conventions during upstream +integration. It is licensed under the standard MIT terms. + +The mips64 port was contributed by Imagination Technologies and is +licensed under the standard MIT terms. + +The powerpc port was also originally written by Richard Pennington, +and later supplemented and integrated by John Spencer. It is licensed +under the standard MIT terms. + +All other files which have no copyright comments are original works +produced specifically for use as part of this library, written either +by Rich Felker, the main author of the library, or by one or more +contibutors listed above. Details on authorship of individual files +can be found in the git version control history of the project. The +omission of copyright and license comments in each file is in the +interest of source tree size. + +In addition, permission is hereby granted for all public header files +(include/* and arch/*/bits/*) and crt files intended to be linked into +applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit +the copyright notice and permission notice otherwise required by the +license, and to use these files without any requirement of +attribution. These files include substantial contributions from: + +Bobby Bingham +John Spencer +Nicholas J. Kain +Rich Felker +Richard Pennington +Stefan Kristiansson +Szabolcs Nagy + +all of whom have explicitly granted such permission. + +This file previously contained text expressing a belief that most of +the files covered by the above exception were sufficiently trivial not +to be subject to copyright, resulting in confusion over whether it +negated the permissions granted in the license. In the spirit of +permissive licensing, and of not having licensing issues being an +obstacle to adoption, that text has been removed. diff --git a/lib/string/aarch64/memcpy.S b/lib/string/aarch64/memcpy.S new file mode 100644 index 0000000..48bb8a8 --- /dev/null +++ b/lib/string/aarch64/memcpy.S @@ -0,0 +1,186 @@ +/* + * memcpy - copy memory area + * + * Copyright (c) 2012-2020, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +/* Assumptions: + * + * ARMv8-a, AArch64, unaligned accesses. + * + */ + +#define dstin x0 +#define src x1 +#define count x2 +#define dst x3 +#define srcend x4 +#define dstend x5 +#define A_l x6 +#define A_lw w6 +#define A_h x7 +#define B_l x8 +#define B_lw w8 +#define B_h x9 +#define C_l x10 +#define C_lw w10 +#define C_h x11 +#define D_l x12 +#define D_h x13 +#define E_l x14 +#define E_h x15 +#define F_l x16 +#define F_h x17 +#define G_l count +#define G_h dst +#define H_l src +#define H_h srcend +#define tmp1 x14 + +/* This implementation of memcpy uses unaligned accesses and branchless + sequences to keep the code small, simple and improve performance. + + Copies are split into 3 main cases: small copies of up to 32 bytes, medium + copies of up to 128 bytes, and large copies. The overhead of the overlap + check is negligible since it is only required for large copies. + + Large copies use a software pipelined loop processing 64 bytes per iteration. + The destination pointer is 16-byte aligned to minimize unaligned accesses. + The loop tail is handled by always copying 64 bytes from the end. +*/ + +.global memcpy +.type memcpy,%function +memcpy: + add srcend, src, count + add dstend, dstin, count + cmp count, 128 + b.hi .Lcopy_long + cmp count, 32 + b.hi .Lcopy32_128 + + /* Small copies: 0..32 bytes. */ + cmp count, 16 + b.lo .Lcopy16 + ldp A_l, A_h, [src] + ldp D_l, D_h, [srcend, -16] + stp A_l, A_h, [dstin] + stp D_l, D_h, [dstend, -16] + ret + + /* Copy 8-15 bytes. */ +.Lcopy16: + tbz count, 3, .Lcopy8 + ldr A_l, [src] + ldr A_h, [srcend, -8] + str A_l, [dstin] + str A_h, [dstend, -8] + ret + + .p2align 3 + /* Copy 4-7 bytes. */ +.Lcopy8: + tbz count, 2, .Lcopy4 + ldr A_lw, [src] + ldr B_lw, [srcend, -4] + str A_lw, [dstin] + str B_lw, [dstend, -4] + ret + + /* Copy 0..3 bytes using a branchless sequence. */ +.Lcopy4: + cbz count, .Lcopy0 + lsr tmp1, count, 1 + ldrb A_lw, [src] + ldrb C_lw, [srcend, -1] + ldrb B_lw, [src, tmp1] + strb A_lw, [dstin] + strb B_lw, [dstin, tmp1] + strb C_lw, [dstend, -1] +.Lcopy0: + ret + + .p2align 4 + /* Medium copies: 33..128 bytes. */ +.Lcopy32_128: + ldp A_l, A_h, [src] + ldp B_l, B_h, [src, 16] + ldp C_l, C_h, [srcend, -32] + ldp D_l, D_h, [srcend, -16] + cmp count, 64 + b.hi .Lcopy128 + stp A_l, A_h, [dstin] + stp B_l, B_h, [dstin, 16] + stp C_l, C_h, [dstend, -32] + stp D_l, D_h, [dstend, -16] + ret + + .p2align 4 + /* Copy 65..128 bytes. */ +.Lcopy128: + ldp E_l, E_h, [src, 32] + ldp F_l, F_h, [src, 48] + cmp count, 96 + b.ls .Lcopy96 + ldp G_l, G_h, [srcend, -64] + ldp H_l, H_h, [srcend, -48] + stp G_l, G_h, [dstend, -64] + stp H_l, H_h, [dstend, -48] +.Lcopy96: + stp A_l, A_h, [dstin] + stp B_l, B_h, [dstin, 16] + stp E_l, E_h, [dstin, 32] + stp F_l, F_h, [dstin, 48] + stp C_l, C_h, [dstend, -32] + stp D_l, D_h, [dstend, -16] + ret + + .p2align 4 + /* Copy more than 128 bytes. */ +.Lcopy_long: + + /* Copy 16 bytes and then align dst to 16-byte alignment. */ + + ldp D_l, D_h, [src] + and tmp1, dstin, 15 + bic dst, dstin, 15 + sub src, src, tmp1 + add count, count, tmp1 /* Count is now 16 too large. */ + ldp A_l, A_h, [src, 16] + stp D_l, D_h, [dstin] + ldp B_l, B_h, [src, 32] + ldp C_l, C_h, [src, 48] + ldp D_l, D_h, [src, 64]! + subs count, count, 128 + 16 /* Test and readjust count. */ + b.ls .Lcopy64_from_end + +.Lloop64: + stp A_l, A_h, [dst, 16] + ldp A_l, A_h, [src, 16] + stp B_l, B_h, [dst, 32] + ldp B_l, B_h, [src, 32] + stp C_l, C_h, [dst, 48] + ldp C_l, C_h, [src, 48] + stp D_l, D_h, [dst, 64]! + ldp D_l, D_h, [src, 64]! + subs count, count, 64 + b.hi .Lloop64 + + /* Write the last iteration and copy 64 bytes from the end. */ +.Lcopy64_from_end: + ldp E_l, E_h, [srcend, -64] + stp A_l, A_h, [dst, 16] + ldp A_l, A_h, [srcend, -48] + stp B_l, B_h, [dst, 32] + ldp B_l, B_h, [srcend, -32] + stp C_l, C_h, [dst, 48] + ldp C_l, C_h, [srcend, -16] + stp D_l, D_h, [dst, 64] + stp E_l, E_h, [dstend, -64] + stp A_l, A_h, [dstend, -48] + stp B_l, B_h, [dstend, -32] + stp C_l, C_h, [dstend, -16] + ret + +.size memcpy,.-memcpy diff --git a/lib/string/aarch64/memset.S b/lib/string/aarch64/memset.S new file mode 100644 index 0000000..f0d29b7 --- /dev/null +++ b/lib/string/aarch64/memset.S @@ -0,0 +1,115 @@ +/* + * memset - fill memory with a constant byte + * + * Copyright (c) 2012-2020, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +/* Assumptions: + * + * ARMv8-a, AArch64, Advanced SIMD, unaligned accesses. + * + */ + +#define dstin x0 +#define val x1 +#define valw w1 +#define count x2 +#define dst x3 +#define dstend x4 +#define zva_val x5 + +.global memset +.type memset,%function +memset: + + dup v0.16B, valw + add dstend, dstin, count + + cmp count, 96 + b.hi .Lset_long + cmp count, 16 + b.hs .Lset_medium + mov val, v0.D[0] + + /* Set 0..15 bytes. */ + tbz count, 3, 1f + str val, [dstin] + str val, [dstend, -8] + ret + nop +1: tbz count, 2, 2f + str valw, [dstin] + str valw, [dstend, -4] + ret +2: cbz count, 3f + strb valw, [dstin] + tbz count, 1, 3f + strh valw, [dstend, -2] +3: ret + + /* Set 17..96 bytes. */ +.Lset_medium: + str q0, [dstin] + tbnz count, 6, .Lset96 + str q0, [dstend, -16] + tbz count, 5, 1f + str q0, [dstin, 16] + str q0, [dstend, -32] +1: ret + + .p2align 4 + /* Set 64..96 bytes. Write 64 bytes from the start and + 32 bytes from the end. */ +.Lset96: + str q0, [dstin, 16] + stp q0, q0, [dstin, 32] + stp q0, q0, [dstend, -32] + ret + + .p2align 4 +.Lset_long: + and valw, valw, 255 + bic dst, dstin, 15 + str q0, [dstin] + cmp count, 160 + ccmp valw, 0, 0, hs + b.ne .Lno_zva + +#ifndef SKIP_ZVA_CHECK + mrs zva_val, dczid_el0 + and zva_val, zva_val, 31 + cmp zva_val, 4 /* ZVA size is 64 bytes. */ + b.ne .Lno_zva +#endif + str q0, [dst, 16] + stp q0, q0, [dst, 32] + bic dst, dst, 63 + sub count, dstend, dst /* Count is now 64 too large. */ + sub count, count, 128 /* Adjust count and bias for loop. */ + + .p2align 4 +.Lzva_loop: + add dst, dst, 64 + dc zva, dst + subs count, count, 64 + b.hi .Lzva_loop + stp q0, q0, [dstend, -64] + stp q0, q0, [dstend, -32] + ret + +.Lno_zva: + sub count, dstend, dst /* Count is 16 too large. */ + sub dst, dst, 16 /* Dst is biased by -32. */ + sub count, count, 64 + 16 /* Adjust count and bias for loop. */ +.Lno_zva_loop: + stp q0, q0, [dst, 32] + stp q0, q0, [dst, 64]! + subs count, count, 64 + b.hi .Lno_zva_loop + stp q0, q0, [dstend, -64] + stp q0, q0, [dstend, -32] + ret + +.size memset,.-memset + diff --git a/lib/string/arm/__aeabi_memcpy.s b/lib/string/arm/__aeabi_memcpy.s new file mode 100644 index 0000000..3a527e4 --- /dev/null +++ b/lib/string/arm/__aeabi_memcpy.s @@ -0,0 +1,45 @@ +.syntax unified + +.global __aeabi_memcpy8 +.global __aeabi_memcpy4 +.global __aeabi_memcpy +.global __aeabi_memmove8 +.global __aeabi_memmove4 +.global __aeabi_memmove + +.type __aeabi_memcpy8,%function +.type __aeabi_memcpy4,%function +.type __aeabi_memcpy,%function +.type __aeabi_memmove8,%function +.type __aeabi_memmove4,%function +.type __aeabi_memmove,%function + +__aeabi_memmove8: +__aeabi_memmove4: +__aeabi_memmove: + cmp r0, r1 + bls 3f + cmp r2, #0 + beq 2f + adds r0, r0, r2 + adds r2, r1, r2 +1: subs r2, r2, #1 + ldrb r3, [r2] + subs r0, r0, #1 + strb r3, [r0] + cmp r1, r2 + bne 1b +2: bx lr +__aeabi_memcpy8: +__aeabi_memcpy4: +__aeabi_memcpy: +3: cmp r2, #0 + beq 2f + adds r2, r1, r2 +1: ldrb r3, [r1] + adds r1, r1, #1 + strb r3, [r0] + adds r0, r0, #1 + cmp r1, r2 + bne 1b +2: bx lr diff --git a/lib/string/arm/__aeabi_memset.s b/lib/string/arm/__aeabi_memset.s new file mode 100644 index 0000000..f9f6058 --- /dev/null +++ b/lib/string/arm/__aeabi_memset.s @@ -0,0 +1,31 @@ +.syntax unified + +.global __aeabi_memclr8 +.global __aeabi_memclr4 +.global __aeabi_memclr +.global __aeabi_memset8 +.global __aeabi_memset4 +.global __aeabi_memset + +.type __aeabi_memclr8,%function +.type __aeabi_memclr4,%function +.type __aeabi_memclr,%function +.type __aeabi_memset8,%function +.type __aeabi_memset4,%function +.type __aeabi_memset,%function + +__aeabi_memclr8: +__aeabi_memclr4: +__aeabi_memclr: + movs r2, #0 +__aeabi_memset8: +__aeabi_memset4: +__aeabi_memset: + cmp r1, #0 + beq 2f + adds r1, r0, r1 +1: strb r2, [r0] + adds r0, r0, #1 + cmp r1, r0 + bne 1b +2: bx lr diff --git a/lib/string/arm/memcpy.S b/lib/string/arm/memcpy.S new file mode 100644 index 0000000..869e344 --- /dev/null +++ b/lib/string/arm/memcpy.S @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +/* + * Optimized memcpy() for ARM. + * + * note that memcpy() always returns the destination pointer, + * so we have to preserve R0. + */ + +/* + * This file has been modified from the original for use in musl libc. + * The main changes are: addition of .type memcpy,%function to make the + * code safely callable from thumb mode, adjusting the return + * instructions to be compatible with pre-thumb ARM cpus, removal of + * prefetch code that is not compatible with older cpus and support for + * building as thumb 2 and big-endian. + */ + +.syntax unified + +.global memcpy +.type memcpy,%function +memcpy: + /* The stack must always be 64-bits aligned to be compliant with the + * ARM ABI. Since we have to save R0, we might as well save R4 + * which we can use for better pipelining of the reads below + */ + .fnstart + .save {r0, r4, lr} + stmfd sp!, {r0, r4, lr} + /* Making room for r5-r11 which will be spilled later */ + .pad #28 + sub sp, sp, #28 + + /* it simplifies things to take care of len<4 early */ + cmp r2, #4 + blo copy_last_3_and_return + + /* compute the offset to align the source + * offset = (4-(src&3))&3 = -src & 3 + */ + rsb r3, r1, #0 + ands r3, r3, #3 + beq src_aligned + + /* align source to 32 bits. We need to insert 2 instructions between + * a ldr[b|h] and str[b|h] because byte and half-word instructions + * stall 2 cycles. + */ + movs r12, r3, lsl #31 + sub r2, r2, r3 /* we know that r3 <= r2 because r2 >= 4 */ + ldrbmi r3, [r1], #1 + ldrbcs r4, [r1], #1 + ldrbcs r12,[r1], #1 + strbmi r3, [r0], #1 + strbcs r4, [r0], #1 + strbcs r12,[r0], #1 + +src_aligned: + + /* see if src and dst are aligned together (congruent) */ + eor r12, r0, r1 + tst r12, #3 + bne non_congruent + + /* Use post-incriment mode for stm to spill r5-r11 to reserved stack + * frame. Don't update sp. + */ + stmea sp, {r5-r11} + + /* align the destination to a cache-line */ + rsb r3, r0, #0 + ands r3, r3, #0x1C + beq congruent_aligned32 + cmp r3, r2 + andhi r3, r2, #0x1C + + /* conditionnaly copies 0 to 7 words (length in r3) */ + movs r12, r3, lsl #28 + ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ + ldmmi r1!, {r8, r9} /* 8 bytes */ + stmcs r0!, {r4, r5, r6, r7} + stmmi r0!, {r8, r9} + tst r3, #0x4 + ldrne r10,[r1], #4 /* 4 bytes */ + strne r10,[r0], #4 + sub r2, r2, r3 + +congruent_aligned32: + /* + * here source is aligned to 32 bytes. + */ + +cached_aligned32: + subs r2, r2, #32 + blo less_than_32_left + + /* + * We preload a cache-line up to 64 bytes ahead. On the 926, this will + * stall only until the requested world is fetched, but the linefill + * continues in the the background. + * While the linefill is going, we write our previous cache-line + * into the write-buffer (which should have some free space). + * When the linefill is done, the writebuffer will + * start dumping its content into memory + * + * While all this is going, we then load a full cache line into + * 8 registers, this cache line should be in the cache by now + * (or partly in the cache). + * + * This code should work well regardless of the source/dest alignment. + * + */ + + /* Align the preload register to a cache-line because the cpu does + * "critical word first" (the first word requested is loaded first). + */ + @ bic r12, r1, #0x1F + @ add r12, r12, #64 + +1: ldmia r1!, { r4-r11 } + subs r2, r2, #32 + + /* + * NOTE: if r12 is more than 64 ahead of r1, the following ldrhi + * for ARM9 preload will not be safely guarded by the preceding subs. + * When it is safely guarded the only possibility to have SIGSEGV here + * is because the caller overstates the length. + */ + @ ldrhi r3, [r12], #32 /* cheap ARM9 preload */ + stmia r0!, { r4-r11 } + bhs 1b + + add r2, r2, #32 + +less_than_32_left: + /* + * less than 32 bytes left at this point (length in r2) + */ + + /* skip all this if there is nothing to do, which should + * be a common case (if not executed the code below takes + * about 16 cycles) + */ + tst r2, #0x1F + beq 1f + + /* conditionnaly copies 0 to 31 bytes */ + movs r12, r2, lsl #28 + ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ + ldmmi r1!, {r8, r9} /* 8 bytes */ + stmcs r0!, {r4, r5, r6, r7} + stmmi r0!, {r8, r9} + movs r12, r2, lsl #30 + ldrcs r3, [r1], #4 /* 4 bytes */ + ldrhmi r4, [r1], #2 /* 2 bytes */ + strcs r3, [r0], #4 + strhmi r4, [r0], #2 + tst r2, #0x1 + ldrbne r3, [r1] /* last byte */ + strbne r3, [r0] + + /* we're done! restore everything and return */ +1: ldmfd sp!, {r5-r11} + ldmfd sp!, {r0, r4, lr} + bx lr + + /********************************************************************/ + +non_congruent: + /* + * here source is aligned to 4 bytes + * but destination is not. + * + * in the code below r2 is the number of bytes read + * (the number of bytes written is always smaller, because we have + * partial words in the shift queue) + */ + cmp r2, #4 + blo copy_last_3_and_return + + /* Use post-incriment mode for stm to spill r5-r11 to reserved stack + * frame. Don't update sp. + */ + stmea sp, {r5-r11} + + /* compute shifts needed to align src to dest */ + rsb r5, r0, #0 + and r5, r5, #3 /* r5 = # bytes in partial words */ + mov r12, r5, lsl #3 /* r12 = right */ + rsb lr, r12, #32 /* lr = left */ + + /* read the first word */ + ldr r3, [r1], #4 + sub r2, r2, #4 + + /* write a partial word (0 to 3 bytes), such that destination + * becomes aligned to 32 bits (r5 = nb of words to copy for alignment) + */ + movs r5, r5, lsl #31 + +#if __ARMEB__ + movmi r3, r3, ror #24 + strbmi r3, [r0], #1 + movcs r3, r3, ror #24 + strbcs r3, [r0], #1 + movcs r3, r3, ror #24 + strbcs r3, [r0], #1 +#else + strbmi r3, [r0], #1 + movmi r3, r3, lsr #8 + strbcs r3, [r0], #1 + movcs r3, r3, lsr #8 + strbcs r3, [r0], #1 + movcs r3, r3, lsr #8 +#endif + + cmp r2, #4 + blo partial_word_tail + +#if __ARMEB__ + mov r3, r3, lsr r12 + mov r3, r3, lsl r12 +#endif + + /* Align destination to 32 bytes (cache line boundary) */ +1: tst r0, #0x1c + beq 2f + ldr r5, [r1], #4 + sub r2, r2, #4 +#if __ARMEB__ + mov r4, r5, lsr lr + orr r4, r4, r3 + mov r3, r5, lsl r12 +#else + mov r4, r5, lsl lr + orr r4, r4, r3 + mov r3, r5, lsr r12 +#endif + str r4, [r0], #4 + cmp r2, #4 + bhs 1b + blo partial_word_tail + + /* copy 32 bytes at a time */ +2: subs r2, r2, #32 + blo less_than_thirtytwo + + /* Use immediate mode for the shifts, because there is an extra cycle + * for register shifts, which could account for up to 50% of + * performance hit. + */ + + cmp r12, #24 + beq loop24 + cmp r12, #8 + beq loop8 + +loop16: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + subs r2, r2, #32 + ldrhs r12, [r1], #4 +#if __ARMEB__ + orr r3, r3, r4, lsr #16 + mov r4, r4, lsl #16 + orr r4, r4, r5, lsr #16 + mov r5, r5, lsl #16 + orr r5, r5, r6, lsr #16 + mov r6, r6, lsl #16 + orr r6, r6, r7, lsr #16 + mov r7, r7, lsl #16 + orr r7, r7, r8, lsr #16 + mov r8, r8, lsl #16 + orr r8, r8, r9, lsr #16 + mov r9, r9, lsl #16 + orr r9, r9, r10, lsr #16 + mov r10, r10, lsl #16 + orr r10, r10, r11, lsr #16 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsl #16 +#else + orr r3, r3, r4, lsl #16 + mov r4, r4, lsr #16 + orr r4, r4, r5, lsl #16 + mov r5, r5, lsr #16 + orr r5, r5, r6, lsl #16 + mov r6, r6, lsr #16 + orr r6, r6, r7, lsl #16 + mov r7, r7, lsr #16 + orr r7, r7, r8, lsl #16 + mov r8, r8, lsr #16 + orr r8, r8, r9, lsl #16 + mov r9, r9, lsr #16 + orr r9, r9, r10, lsl #16 + mov r10, r10, lsr #16 + orr r10, r10, r11, lsl #16 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #16 +#endif + bhs 1b + b less_than_thirtytwo + +loop8: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + subs r2, r2, #32 + ldrhs r12, [r1], #4 +#if __ARMEB__ + orr r3, r3, r4, lsr #24 + mov r4, r4, lsl #8 + orr r4, r4, r5, lsr #24 + mov r5, r5, lsl #8 + orr r5, r5, r6, lsr #24 + mov r6, r6, lsl #8 + orr r6, r6, r7, lsr #24 + mov r7, r7, lsl #8 + orr r7, r7, r8, lsr #24 + mov r8, r8, lsl #8 + orr r8, r8, r9, lsr #24 + mov r9, r9, lsl #8 + orr r9, r9, r10, lsr #24 + mov r10, r10, lsl #8 + orr r10, r10, r11, lsr #24 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsl #8 +#else + orr r3, r3, r4, lsl #24 + mov r4, r4, lsr #8 + orr r4, r4, r5, lsl #24 + mov r5, r5, lsr #8 + orr r5, r5, r6, lsl #24 + mov r6, r6, lsr #8 + orr r6, r6, r7, lsl #24 + mov r7, r7, lsr #8 + orr r7, r7, r8, lsl #24 + mov r8, r8, lsr #8 + orr r8, r8, r9, lsl #24 + mov r9, r9, lsr #8 + orr r9, r9, r10, lsl #24 + mov r10, r10, lsr #8 + orr r10, r10, r11, lsl #24 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #8 +#endif + bhs 1b + b less_than_thirtytwo + +loop24: + ldr r12, [r1], #4 +1: mov r4, r12 + ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} + subs r2, r2, #32 + ldrhs r12, [r1], #4 +#if __ARMEB__ + orr r3, r3, r4, lsr #8 + mov r4, r4, lsl #24 + orr r4, r4, r5, lsr #8 + mov r5, r5, lsl #24 + orr r5, r5, r6, lsr #8 + mov r6, r6, lsl #24 + orr r6, r6, r7, lsr #8 + mov r7, r7, lsl #24 + orr r7, r7, r8, lsr #8 + mov r8, r8, lsl #24 + orr r8, r8, r9, lsr #8 + mov r9, r9, lsl #24 + orr r9, r9, r10, lsr #8 + mov r10, r10, lsl #24 + orr r10, r10, r11, lsr #8 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsl #24 +#else + orr r3, r3, r4, lsl #8 + mov r4, r4, lsr #24 + orr r4, r4, r5, lsl #8 + mov r5, r5, lsr #24 + orr r5, r5, r6, lsl #8 + mov r6, r6, lsr #24 + orr r6, r6, r7, lsl #8 + mov r7, r7, lsr #24 + orr r7, r7, r8, lsl #8 + mov r8, r8, lsr #24 + orr r8, r8, r9, lsl #8 + mov r9, r9, lsr #24 + orr r9, r9, r10, lsl #8 + mov r10, r10, lsr #24 + orr r10, r10, r11, lsl #8 + stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} + mov r3, r11, lsr #24 +#endif + bhs 1b + +less_than_thirtytwo: + /* copy the last 0 to 31 bytes of the source */ + rsb r12, lr, #32 /* we corrupted r12, recompute it */ + add r2, r2, #32 + cmp r2, #4 + blo partial_word_tail + +1: ldr r5, [r1], #4 + sub r2, r2, #4 +#if __ARMEB__ + mov r4, r5, lsr lr + orr r4, r4, r3 + mov r3, r5, lsl r12 +#else + mov r4, r5, lsl lr + orr r4, r4, r3 + mov r3, r5, lsr r12 +#endif + str r4, [r0], #4 + cmp r2, #4 + bhs 1b + +partial_word_tail: + /* we have a partial word in the input buffer */ + movs r5, lr, lsl #(31-3) +#if __ARMEB__ + movmi r3, r3, ror #24 + strbmi r3, [r0], #1 + movcs r3, r3, ror #24 + strbcs r3, [r0], #1 + movcs r3, r3, ror #24 + strbcs r3, [r0], #1 +#else + strbmi r3, [r0], #1 + movmi r3, r3, lsr #8 + strbcs r3, [r0], #1 + movcs r3, r3, lsr #8 + strbcs r3, [r0], #1 +#endif + + /* Refill spilled registers from the stack. Don't update sp. */ + ldmfd sp, {r5-r11} + +copy_last_3_and_return: + movs r2, r2, lsl #31 /* copy remaining 0, 1, 2 or 3 bytes */ + ldrbmi r2, [r1], #1 + ldrbcs r3, [r1], #1 + ldrbcs r12,[r1] + strbmi r2, [r0], #1 + strbcs r3, [r0], #1 + strbcs r12,[r0] + + /* we're done! restore sp and spilled registers and return */ + add sp, sp, #28 + ldmfd sp!, {r0, r4, lr} + bx lr + diff --git a/lib/string/bcmp.c b/lib/string/bcmp.c new file mode 100644 index 0000000..d4c093f --- /dev/null +++ b/lib/string/bcmp.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include + +int bcmp(const void *s1, const void *s2, size_t n) +{ + return memcmp(s1, s2, n); +} diff --git a/lib/string/bcopy.c b/lib/string/bcopy.c new file mode 100644 index 0000000..25e98a2 --- /dev/null +++ b/lib/string/bcopy.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include + +void bcopy(const void *s1, void *s2, size_t n) +{ + memmove(s2, s1, n); +} diff --git a/lib/string/bzero.c b/lib/string/bzero.c new file mode 100644 index 0000000..08f1398 --- /dev/null +++ b/lib/string/bzero.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include + +void bzero(void *s, size_t n) +{ + memset(s, 0, n); +} diff --git a/lib/string/explicit_bzero.c b/lib/string/explicit_bzero.c new file mode 100644 index 0000000..c94f25f --- /dev/null +++ b/lib/string/explicit_bzero.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include + +void explicit_bzero(void *d, size_t n) +{ + d = memset(d, 0, n); + __asm__ __volatile__ ("" : : "r"(d) : "memory"); +} diff --git a/lib/string/i386/memcpy.s b/lib/string/i386/memcpy.s new file mode 100644 index 0000000..0608dd8 --- /dev/null +++ b/lib/string/i386/memcpy.s @@ -0,0 +1,32 @@ +.global memcpy +.global __memcpy_fwd +.hidden __memcpy_fwd +.type memcpy,@function +memcpy: +__memcpy_fwd: + push %esi + push %edi + mov 12(%esp),%edi + mov 16(%esp),%esi + mov 20(%esp),%ecx + mov %edi,%eax + cmp $4,%ecx + jc 1f + test $3,%edi + jz 1f +2: movsb + dec %ecx + test $3,%edi + jnz 2b +1: mov %ecx,%edx + shr $2,%ecx + rep + movsl + and $3,%edx + jz 1f +2: movsb + dec %edx + jnz 2b +1: pop %edi + pop %esi + ret diff --git a/lib/string/i386/memmove.s b/lib/string/i386/memmove.s new file mode 100644 index 0000000..2a6a504 --- /dev/null +++ b/lib/string/i386/memmove.s @@ -0,0 +1,22 @@ +.global memmove +.type memmove,@function +memmove: + mov 4(%esp),%eax + sub 8(%esp),%eax + cmp 12(%esp),%eax +.hidden __memcpy_fwd + jae __memcpy_fwd + push %esi + push %edi + mov 12(%esp),%edi + mov 16(%esp),%esi + mov 20(%esp),%ecx + lea -1(%edi,%ecx),%edi + lea -1(%esi,%ecx),%esi + std + rep movsb + cld + lea 1(%edi),%eax + pop %edi + pop %esi + ret diff --git a/lib/string/i386/memset.s b/lib/string/i386/memset.s new file mode 100644 index 0000000..d00422c --- /dev/null +++ b/lib/string/i386/memset.s @@ -0,0 +1,76 @@ +.global memset +.type memset,@function +memset: + mov 12(%esp),%ecx + cmp $62,%ecx + ja 2f + + mov 8(%esp),%dl + mov 4(%esp),%eax + test %ecx,%ecx + jz 1f + + mov %dl,%dh + + mov %dl,(%eax) + mov %dl,-1(%eax,%ecx) + cmp $2,%ecx + jbe 1f + + mov %dx,1(%eax) + mov %dx,(-1-2)(%eax,%ecx) + cmp $6,%ecx + jbe 1f + + shl $16,%edx + mov 8(%esp),%dl + mov 8(%esp),%dh + + mov %edx,(1+2)(%eax) + mov %edx,(-1-2-4)(%eax,%ecx) + cmp $14,%ecx + jbe 1f + + mov %edx,(1+2+4)(%eax) + mov %edx,(1+2+4+4)(%eax) + mov %edx,(-1-2-4-8)(%eax,%ecx) + mov %edx,(-1-2-4-4)(%eax,%ecx) + cmp $30,%ecx + jbe 1f + + mov %edx,(1+2+4+8)(%eax) + mov %edx,(1+2+4+8+4)(%eax) + mov %edx,(1+2+4+8+8)(%eax) + mov %edx,(1+2+4+8+12)(%eax) + mov %edx,(-1-2-4-8-16)(%eax,%ecx) + mov %edx,(-1-2-4-8-12)(%eax,%ecx) + mov %edx,(-1-2-4-8-8)(%eax,%ecx) + mov %edx,(-1-2-4-8-4)(%eax,%ecx) + +1: ret + +2: movzbl 8(%esp),%eax + mov %edi,12(%esp) + imul $0x1010101,%eax + mov 4(%esp),%edi + test $15,%edi + mov %eax,-4(%edi,%ecx) + jnz 2f + +1: shr $2, %ecx + rep + stosl + mov 4(%esp),%eax + mov 12(%esp),%edi + ret + +2: xor %edx,%edx + sub %edi,%edx + and $15,%edx + mov %eax,(%edi) + mov %eax,4(%edi) + mov %eax,8(%edi) + mov %eax,12(%edi) + sub %edx,%ecx + add %edx,%edi + jmp 1b diff --git a/lib/string/index.c b/lib/string/index.c new file mode 100644 index 0000000..8f6894c --- /dev/null +++ b/lib/string/index.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include + +char *index(const char *s, int c) +{ + return strchr(s, c); +} diff --git a/lib/string/memccpy.c b/lib/string/memccpy.c new file mode 100644 index 0000000..99f146f --- /dev/null +++ b/lib/string/memccpy.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define ALIGN (sizeof(size_t)-1) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +void *memccpy(void *restrict dest, const void *restrict src, int c, size_t n) +{ + unsigned char *d = dest; + const unsigned char *s = src; + + c = (unsigned char)c; +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + word *wd; + const word *ws; + if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { + for (; ((uintptr_t)s & ALIGN) && n && (*d=*s)!=c; n--, s++, d++); + if ((uintptr_t)s & ALIGN) goto tail; + size_t k = ONES * c; + wd=(void *)d; ws=(const void *)s; + for (; n>=sizeof(size_t) && !HASZERO(*ws^k); + n-=sizeof(size_t), ws++, wd++) *wd = *ws; + d=(void *)wd; s=(const void *)ws; + } +#endif + for (; n && (*d=*s)!=c; n--, s++, d++); +tail: + if (n) return d+1; + return 0; +} diff --git a/lib/string/memchr.c b/lib/string/memchr.c new file mode 100644 index 0000000..d0f670f --- /dev/null +++ b/lib/string/memchr.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define SS (sizeof(size_t)) +#define ALIGN (sizeof(size_t)-1) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +void *memchr(const void *src, int c, size_t n) +{ + const unsigned char *s = src; + c = (unsigned char)c; +#ifdef __GNUC__ + for (; ((uintptr_t)s & ALIGN) && n && *s != c; s++, n--); + if (n && *s != c) { + typedef size_t __attribute__((__may_alias__)) word; + const word *w; + size_t k = ONES * c; + for (w = (const void *)s; n>=SS && !HASZERO(*w^k); w++, n-=SS); + s = (const void *)w; + } +#endif + for (; n && *s != c; s++, n--); + return n ? (void *)s : 0; +} diff --git a/lib/string/memcmp.c b/lib/string/memcmp.c new file mode 100644 index 0000000..15606f4 --- /dev/null +++ b/lib/string/memcmp.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +int memcmp(const void *vl, const void *vr, size_t n) +{ + const unsigned char *l=vl, *r=vr; + for (; n && *l == *r; n--, l++, r++); + return n ? *l-*r : 0; +} diff --git a/lib/string/memcpy.c b/lib/string/memcpy.c new file mode 100644 index 0000000..de26a00 --- /dev/null +++ b/lib/string/memcpy.c @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) +{ + unsigned char *d = dest; + const unsigned char *s = src; + +#ifdef __GNUC__ + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define LS >> +#define RS << +#else +#define LS << +#define RS >> +#endif + + typedef uint32_t __attribute__((__may_alias__)) u32; + uint32_t w, x; + + for (; (uintptr_t)s % 4 && n; n--) *d++ = *s++; + + if ((uintptr_t)d % 4 == 0) { + for (; n>=16; s+=16, d+=16, n-=16) { + *(u32 *)(d+0) = *(u32 *)(s+0); + *(u32 *)(d+4) = *(u32 *)(s+4); + *(u32 *)(d+8) = *(u32 *)(s+8); + *(u32 *)(d+12) = *(u32 *)(s+12); + } + if (n&8) { + *(u32 *)(d+0) = *(u32 *)(s+0); + *(u32 *)(d+4) = *(u32 *)(s+4); + d += 8; s += 8; + } + if (n&4) { + *(u32 *)(d+0) = *(u32 *)(s+0); + d += 4; s += 4; + } + if (n&2) { + *d++ = *s++; *d++ = *s++; + } + if (n&1) { + *d = *s; + } + return dest; + } + + if (n >= 32) switch ((uintptr_t)d % 4) { + case 1: + w = *(u32 *)s; + *d++ = *s++; + *d++ = *s++; + *d++ = *s++; + n -= 3; + for (; n>=17; s+=16, d+=16, n-=16) { + x = *(u32 *)(s+1); + *(u32 *)(d+0) = (w LS 24) | (x RS 8); + w = *(u32 *)(s+5); + *(u32 *)(d+4) = (x LS 24) | (w RS 8); + x = *(u32 *)(s+9); + *(u32 *)(d+8) = (w LS 24) | (x RS 8); + w = *(u32 *)(s+13); + *(u32 *)(d+12) = (x LS 24) | (w RS 8); + } + break; + case 2: + w = *(u32 *)s; + *d++ = *s++; + *d++ = *s++; + n -= 2; + for (; n>=18; s+=16, d+=16, n-=16) { + x = *(u32 *)(s+2); + *(u32 *)(d+0) = (w LS 16) | (x RS 16); + w = *(u32 *)(s+6); + *(u32 *)(d+4) = (x LS 16) | (w RS 16); + x = *(u32 *)(s+10); + *(u32 *)(d+8) = (w LS 16) | (x RS 16); + w = *(u32 *)(s+14); + *(u32 *)(d+12) = (x LS 16) | (w RS 16); + } + break; + case 3: + w = *(u32 *)s; + *d++ = *s++; + n -= 1; + for (; n>=19; s+=16, d+=16, n-=16) { + x = *(u32 *)(s+3); + *(u32 *)(d+0) = (w LS 8) | (x RS 24); + w = *(u32 *)(s+7); + *(u32 *)(d+4) = (x LS 8) | (w RS 24); + x = *(u32 *)(s+11); + *(u32 *)(d+8) = (w LS 8) | (x RS 24); + w = *(u32 *)(s+15); + *(u32 *)(d+12) = (x LS 8) | (w RS 24); + } + break; + } + if (n&16) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n&8) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n&4) { + *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; + } + if (n&2) { + *d++ = *s++; *d++ = *s++; + } + if (n&1) { + *d = *s; + } + return dest; +#endif + + for (; n; n--) *d++ = *s++; + return dest; +} diff --git a/lib/string/memmem.c b/lib/string/memmem.c new file mode 100644 index 0000000..cfd23bb --- /dev/null +++ b/lib/string/memmem.c @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _GNU_SOURCE +#include +#include + +static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) +{ + uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; + for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) + if (hw == nw) return (char *)h-2; + return hw == nw ? (char *)h-2 : 0; +} + +static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; + uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; + for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) + if (hw == nw) return (char *)h-3; + return hw == nw ? (char *)h-3 : 0; +} + +static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; + uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; + for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) + if (hw == nw) return (char *)h-4; + return hw == nw ? (char *)h-4 : 0; +} + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define MIN(a,b) ((a)<(b)?(a):(b)) + +#define BITOP(a,b,op) \ + ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) + +static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) +{ + size_t i, ip, jp, k, p, ms, p0, mem, mem0; + size_t byteset[32 / sizeof(size_t)] = { 0 }; + size_t shift[256]; + + /* Computing length of needle and fill shift table */ + for (i=0; i n[jp+k]) { + jp += k; + k = 1; + p = jp - ip; + } else { + ip = jp++; + k = p = 1; + } + } + ms = ip; + p0 = p; + + /* And with the opposite comparison */ + ip = -1; jp = 0; k = p = 1; + while (jp+k ms+1) ms = ip; + else p = p0; + + /* Periodic needle? */ + if (memcmp(n, n+p, ms+1)) { + mem0 = 0; + p = MAX(ms, l-ms-1) + 1; + } else mem0 = l-p; + mem = 0; + + /* Search loop */ + for (;;) { + /* If remainder of haystack is shorter than needle, done */ + if (z-h < l) return 0; + + /* Check last byte first; advance by shift on mismatch */ + if (BITOP(byteset, h[l-1], &)) { + k = l-shift[h[l-1]]; + if (k) { + if (k < mem) k = mem; + h += k; + mem = 0; + continue; + } + } else { + h += l; + mem = 0; + continue; + } + + /* Compare right half */ + for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); + if (k <= mem) return (char *)h; + h += p; + mem = mem0; + } +} + +void *memmem(const void *h0, size_t k, const void *n0, size_t l) +{ + const unsigned char *h = h0, *n = n0; + + /* Return immediately on empty needle */ + if (!l) return (void *)h; + + /* Return immediately when needle is longer than haystack */ + if (k +#include + +#ifdef __GNUC__ +typedef __attribute__((__may_alias__)) size_t WT; +#define WS (sizeof(WT)) +#endif + +void *memmove(void *dest, const void *src, size_t n) +{ + char *d = dest; + const char *s = src; + + if (d==s) return d; + if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n); + + if (d=WS; n-=WS, d+=WS, s+=WS) *(WT *)d = *(WT *)s; + } +#endif + for (; n; n--) *d++ = *s++; + } else { +#ifdef __GNUC__ + if ((uintptr_t)s % WS == (uintptr_t)d % WS) { + while ((uintptr_t)(d+n) % WS) { + if (!n--) return dest; + d[n] = s[n]; + } + while (n>=WS) n-=WS, *(WT *)(d+n) = *(WT *)(s+n); + } +#endif + while (n) n--, d[n] = s[n]; + } + + return dest; +} diff --git a/lib/string/mempcpy.c b/lib/string/mempcpy.c new file mode 100644 index 0000000..1f33e45 --- /dev/null +++ b/lib/string/mempcpy.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _GNU_SOURCE +#include + +void *mempcpy(void *dest, const void *src, size_t n) +{ + return (char *)memcpy(dest, src, n) + n; +} diff --git a/lib/string/memrchr.c b/lib/string/memrchr.c new file mode 100644 index 0000000..8d5c742 --- /dev/null +++ b/lib/string/memrchr.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +void *__memrchr(const void *m, int c, size_t n) +{ + const unsigned char *s = m; + c = (unsigned char)c; + while (n--) if (s[n]==c) return (void *)(s+n); + return 0; +} + +weak_alias(__memrchr, memrchr); diff --git a/lib/string/memset.c b/lib/string/memset.c new file mode 100644 index 0000000..e780c71 --- /dev/null +++ b/lib/string/memset.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include + +void *memset(void *dest, int c, size_t n) +{ + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) return dest; + s[0] = c; + s[n-1] = c; + if (n <= 2) return dest; + s[1] = c; + s[2] = c; + s[n-2] = c; + s[n-3] = c; + if (n <= 6) return dest; + s[3] = c; + s[n-4] = c; + if (n <= 8) return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t)s & 3; + s += k; + n -= k; + n &= -4; + +#ifdef __GNUC__ + typedef uint32_t __attribute__((__may_alias__)) u32; + typedef uint64_t __attribute__((__may_alias__)) u64; + + u32 c32 = ((u32)-1)/255 * (unsigned char)c; + + /* In preparation to copy 32 bytes at a time, aligned on + * an 8-byte bounary, fill head/tail up to 28 bytes each. + * As in the initial byte-based head/tail fill, each + * conditional below ensures that the subsequent offsets + * are valid (e.g. !(n<=24) implies n>=28). */ + + *(u32 *)(s+0) = c32; + *(u32 *)(s+n-4) = c32; + if (n <= 8) return dest; + *(u32 *)(s+4) = c32; + *(u32 *)(s+8) = c32; + *(u32 *)(s+n-12) = c32; + *(u32 *)(s+n-8) = c32; + if (n <= 24) return dest; + *(u32 *)(s+12) = c32; + *(u32 *)(s+16) = c32; + *(u32 *)(s+20) = c32; + *(u32 *)(s+24) = c32; + *(u32 *)(s+n-28) = c32; + *(u32 *)(s+n-24) = c32; + *(u32 *)(s+n-20) = c32; + *(u32 *)(s+n-16) = c32; + + /* Align to a multiple of 8 so we can fill 64 bits at a time, + * and avoid writing the same bytes twice as much as is + * practical without introducing additional branching. */ + + k = 24 + ((uintptr_t)s & 4); + s += k; + n -= k; + + /* If this loop is reached, 28 tail bytes have already been + * filled, so any remainder when n drops below 32 can be + * safely ignored. */ + + u64 c64 = c32 | ((u64)c32 << 32); + for (; n >= 32; n-=32, s+=32) { + *(u64 *)(s+0) = c64; + *(u64 *)(s+8) = c64; + *(u64 *)(s+16) = c64; + *(u64 *)(s+24) = c64; + } +#else + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; +#endif + + return dest; +} diff --git a/lib/string/rindex.c b/lib/string/rindex.c new file mode 100644 index 0000000..897e19e --- /dev/null +++ b/lib/string/rindex.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include + +char *rindex(const char *s, int c) +{ + return strrchr(s, c); +} diff --git a/lib/string/stpcpy.c b/lib/string/stpcpy.c new file mode 100644 index 0000000..0c1f1b4 --- /dev/null +++ b/lib/string/stpcpy.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define ALIGN (sizeof(size_t)) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +char *__stpcpy(char *restrict d, const char *restrict s) +{ +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + word *wd; + const word *ws; + if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { + for (; (uintptr_t)s % ALIGN; s++, d++) + if (!(*d=*s)) return d; + wd=(void *)d; ws=(const void *)s; + for (; !HASZERO(*ws); *wd++ = *ws++); + d=(void *)wd; s=(const void *)ws; + } +#endif + for (; (*d=*s); s++, d++); + + return d; +} + +weak_alias(__stpcpy, stpcpy); diff --git a/lib/string/stpncpy.c b/lib/string/stpncpy.c new file mode 100644 index 0000000..ca2ef78 --- /dev/null +++ b/lib/string/stpncpy.c @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define ALIGN (sizeof(size_t)-1) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +char *__stpncpy(char *restrict d, const char *restrict s, size_t n) +{ +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + word *wd; + const word *ws; + if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { + for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); + if (!n || !*s) goto tail; + wd=(void *)d; ws=(const void *)s; + for (; n>=sizeof(size_t) && !HASZERO(*ws); + n-=sizeof(size_t), ws++, wd++) *wd = *ws; + d=(void *)wd; s=(const void *)ws; + } +#endif + for (; n && (*d=*s); n--, s++, d++); +tail: + memset(d, 0, n); + return d; +} + +weak_alias(__stpncpy, stpncpy); + diff --git a/lib/string/strcat.c b/lib/string/strcat.c new file mode 100644 index 0000000..723c2ff --- /dev/null +++ b/lib/string/strcat.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strcat(char *restrict dest, const char *restrict src) +{ + strcpy(dest + strlen(dest), src); + return dest; +} diff --git a/lib/string/strchr.c b/lib/string/strchr.c new file mode 100644 index 0000000..ddeca01 --- /dev/null +++ b/lib/string/strchr.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strchr(const char *s, int c) +{ + char *r = __strchrnul(s, c); + return *(unsigned char *)r == (unsigned char)c ? r : 0; +} diff --git a/lib/string/strchrnul.c b/lib/string/strchrnul.c new file mode 100644 index 0000000..6fc86d1 --- /dev/null +++ b/lib/string/strchrnul.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define ALIGN (sizeof(size_t)) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +char *__strchrnul(const char *s, int c) +{ + c = (unsigned char)c; + if (!c) return (char *)s + strlen(s); + +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + const word *w; + for (; (uintptr_t)s % ALIGN; s++) + if (!*s || *(unsigned char *)s == c) return (char *)s; + size_t k = ONES * c; + for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); + s = (void *)w; +#endif + for (; *s && *(unsigned char *)s != c; s++); + return (char *)s; +} + +weak_alias(__strchrnul, strchrnul); diff --git a/lib/string/strcmp.c b/lib/string/strcmp.c new file mode 100644 index 0000000..afcb1b7 --- /dev/null +++ b/lib/string/strcmp.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +int strcmp(const char *l, const char *r) +{ + for (; *l==*r && *l; l++, r++); + return *(unsigned char *)l - *(unsigned char *)r; +} diff --git a/lib/string/strcpy.c b/lib/string/strcpy.c new file mode 100644 index 0000000..a761577 --- /dev/null +++ b/lib/string/strcpy.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strcpy(char *restrict dest, const char *restrict src) +{ + __stpcpy(dest, src); + return dest; +} diff --git a/lib/string/strcspn.c b/lib/string/strcspn.c new file mode 100644 index 0000000..ba55fe6 --- /dev/null +++ b/lib/string/strcspn.c @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +#define BITOP(a,b,op) \ + ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) + +size_t strcspn(const char *s, const char *c) +{ + const char *a = s; + size_t byteset[32/sizeof(size_t)]; + + if (!c[0] || !c[1]) return __strchrnul(s, *c)-a; + + memset(byteset, 0, sizeof byteset); + for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); + for (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++); + return s-a; +} diff --git a/lib/string/strlcat.c b/lib/string/strlcat.c new file mode 100644 index 0000000..a61821d --- /dev/null +++ b/lib/string/strlcat.c @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include + +size_t strlcat(char *d, const char *s, size_t n) +{ + size_t l = strnlen(d, n); + if (l == n) return l + strlen(s); + return l + strlcpy(d+l, s, n-l); +} diff --git a/lib/string/strlcpy.c b/lib/string/strlcpy.c new file mode 100644 index 0000000..c2a5142 --- /dev/null +++ b/lib/string/strlcpy.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _BSD_SOURCE +#include +#include +#include + +#define ALIGN (sizeof(size_t)-1) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +size_t strlcpy(char *d, const char *s, size_t n) +{ + char *d0 = d; + size_t *wd; + + if (!n--) goto finish; +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + const word *ws; + if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { + for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); + if (n && *s) { + wd=(void *)d; ws=(const void *)s; + for (; n>=sizeof(size_t) && !HASZERO(*ws); + n-=sizeof(size_t), ws++, wd++) *wd = *ws; + d=(void *)wd; s=(const void *)ws; + } + } +#endif + for (; n && (*d=*s); n--, s++, d++); + *d = 0; +finish: + return d-d0 + strlen(s); +} diff --git a/lib/string/strlen.c b/lib/string/strlen.c new file mode 100644 index 0000000..76e14f2 --- /dev/null +++ b/lib/string/strlen.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include +#include + +#define ALIGN (sizeof(size_t)) +#define ONES ((size_t)-1/UCHAR_MAX) +#define HIGHS (ONES * (UCHAR_MAX/2+1)) +#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) + +size_t strlen(const char *s) +{ + const char *a = s; +#ifdef __GNUC__ + typedef size_t __attribute__((__may_alias__)) word; + const word *w; + for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a; + for (w = (const void *)s; !HASZERO(*w); w++); + s = (const void *)w; +#endif + for (; *s; s++); + return s-a; +} diff --git a/lib/string/strncat.c b/lib/string/strncat.c new file mode 100644 index 0000000..a144e37 --- /dev/null +++ b/lib/string/strncat.c @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strncat(char *restrict d, const char *restrict s, size_t n) +{ + char *a = d; + d += strlen(d); + while (n && *s) n--, *d++ = *s++; + *d++ = 0; + return a; +} diff --git a/lib/string/strncmp.c b/lib/string/strncmp.c new file mode 100644 index 0000000..b98473b --- /dev/null +++ b/lib/string/strncmp.c @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +int strncmp(const char *_l, const char *_r, size_t n) +{ + const unsigned char *l=(void *)_l, *r=(void *)_r; + if (!n--) return 0; + for (; *l && *r && n && *l == *r ; l++, r++, n--); + return *l - *r; +} diff --git a/lib/string/strncpy.c b/lib/string/strncpy.c new file mode 100644 index 0000000..a828465 --- /dev/null +++ b/lib/string/strncpy.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strncpy(char *restrict d, const char *restrict s, size_t n) +{ + __stpncpy(d, s, n); + return d; +} diff --git a/lib/string/strnlen.c b/lib/string/strnlen.c new file mode 100644 index 0000000..8931c52 --- /dev/null +++ b/lib/string/strnlen.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +size_t strnlen(const char *s, size_t n) +{ + const char *p = memchr(s, 0, n); + return p ? p-s : n; +} diff --git a/lib/string/strpbrk.c b/lib/string/strpbrk.c new file mode 100644 index 0000000..82fbc92 --- /dev/null +++ b/lib/string/strpbrk.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strpbrk(const char *s, const char *b) +{ + s += strcspn(s, b); + return *s ? (char *)s : 0; +} diff --git a/lib/string/strrchr.c b/lib/string/strrchr.c new file mode 100644 index 0000000..a45b459 --- /dev/null +++ b/lib/string/strrchr.c @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strrchr(const char *s, int c) +{ + return __memrchr(s, c, strlen(s) + 1); +} diff --git a/lib/string/strsep.c b/lib/string/strsep.c new file mode 100644 index 0000000..2d513f4 --- /dev/null +++ b/lib/string/strsep.c @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#define _GNU_SOURCE +#include + +char *strsep(char **str, const char *sep) +{ + char *s = *str, *end; + if (!s) return NULL; + end = s + strcspn(s, sep); + if (*end) *end++ = 0; + else end = 0; + *str = end; + return s; +} diff --git a/lib/string/strspn.c b/lib/string/strspn.c new file mode 100644 index 0000000..f27710d --- /dev/null +++ b/lib/string/strspn.c @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +#define BITOP(a,b,op) \ + ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) + +size_t strspn(const char *s, const char *c) +{ + const char *a = s; + size_t byteset[32/sizeof(size_t)] = { 0 }; + + if (!c[0]) return 0; + if (!c[1]) { + for (; *s == *c; s++); + return s-a; + } + + for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); + for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++); + return s-a; +} diff --git a/lib/string/strstr.c b/lib/string/strstr.c new file mode 100644 index 0000000..07e2982 --- /dev/null +++ b/lib/string/strstr.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include +#include + +static char *twobyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; + for (h++; *h && hw != nw; hw = hw<<8 | *++h); + return *h ? (char *)h-1 : 0; +} + +static char *threebyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; + uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; + for (h+=2; *h && hw != nw; hw = (hw|*++h)<<8); + return *h ? (char *)h-2 : 0; +} + +static char *fourbyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; + uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; + for (h+=3; *h && hw != nw; hw = hw<<8 | *++h); + return *h ? (char *)h-3 : 0; +} + +#define MAX(a,b) ((a)>(b)?(a):(b)) +#define MIN(a,b) ((a)<(b)?(a):(b)) + +#define BITOP(a,b,op) \ + ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) + +static char *twoway_strstr(const unsigned char *h, const unsigned char *n) +{ + const unsigned char *z; + size_t l, ip, jp, k, p, ms, p0, mem, mem0; + size_t byteset[32 / sizeof(size_t)] = { 0 }; + size_t shift[256]; + + /* Computing length of needle and fill shift table */ + for (l=0; n[l] && h[l]; l++) + BITOP(byteset, n[l], |=), shift[n[l]] = l+1; + if (n[l]) return 0; /* hit the end of h */ + + /* Compute maximal suffix */ + ip = -1; jp = 0; k = p = 1; + while (jp+k n[jp+k]) { + jp += k; + k = 1; + p = jp - ip; + } else { + ip = jp++; + k = p = 1; + } + } + ms = ip; + p0 = p; + + /* And with the opposite comparison */ + ip = -1; jp = 0; k = p = 1; + while (jp+k ms+1) ms = ip; + else p = p0; + + /* Periodic needle? */ + if (memcmp(n, n+p, ms+1)) { + mem0 = 0; + p = MAX(ms, l-ms-1) + 1; + } else mem0 = l-p; + mem = 0; + + /* Initialize incremental end-of-haystack pointer */ + z = h; + + /* Search loop */ + for (;;) { + /* Update incremental end-of-haystack pointer */ + if (z-h < l) { + /* Fast estimate for MAX(l,63) */ + size_t grow = l | 63; + const unsigned char *z2 = memchr(z, 0, grow); + if (z2) { + z = z2; + if (z-h < l) return 0; + } else z += grow; + } + + /* Check last byte first; advance by shift on mismatch */ + if (BITOP(byteset, h[l-1], &)) { + k = l-shift[h[l-1]]; + if (k) { + if (k < mem) k = mem; + h += k; + mem = 0; + continue; + } + } else { + h += l; + mem = 0; + continue; + } + + /* Compare right half */ + for (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++); + if (n[k]) { + h += k-ms; + mem = 0; + continue; + } + /* Compare left half */ + for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); + if (k <= mem) return (char *)h; + h += p; + mem = mem0; + } +} + +char *strstr(const char *h, const char *n) +{ + /* Return immediately on empty needle */ + if (!n[0]) return (char *)h; + + /* Use faster algorithms for short needles */ + h = strchr(h, *n); + if (!h || !n[1]) return (char *)h; + if (!h[1]) return 0; + if (!n[2]) return twobyte_strstr((void *)h, (void *)n); + if (!h[2]) return 0; + if (!n[3]) return threebyte_strstr((void *)h, (void *)n); + if (!h[3]) return 0; + if (!n[4]) return fourbyte_strstr((void *)h, (void *)n); + + return twoway_strstr((void *)h, (void *)n); +} diff --git a/lib/string/strtok.c b/lib/string/strtok.c new file mode 100644 index 0000000..86d9c97 --- /dev/null +++ b/lib/string/strtok.c @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strtok(char *restrict s, const char *restrict sep) +{ + static char *p; + if (!s && !(s = p)) return NULL; + s += strspn(s, sep); + if (!*s) return p = 0; + p = s + strcspn(s, sep); + if (*p) *p++ = 0; + else p = 0; + return s; +} diff --git a/lib/string/strtok_r.c b/lib/string/strtok_r.c new file mode 100644 index 0000000..a702703 --- /dev/null +++ b/lib/string/strtok_r.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ +/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ + +#include + +char *strtok_r(char *restrict s, const char *restrict sep, char **restrict p) +{ + if (!s && !(s = *p)) return NULL; + s += strspn(s, sep); + if (!*s) return *p = 0; + *p = s + strcspn(s, sep); + if (**p) *(*p)++ = 0; + else *p = 0; + return s; +} diff --git a/lib/string/x86_64/memcpy.s b/lib/string/x86_64/memcpy.s new file mode 100644 index 0000000..3d960ef --- /dev/null +++ b/lib/string/x86_64/memcpy.s @@ -0,0 +1,25 @@ +.global memcpy +.global __memcpy_fwd +.hidden __memcpy_fwd +.type memcpy,@function +memcpy: +__memcpy_fwd: + mov %rdi,%rax + cmp $8,%rdx + jc 1f + test $7,%edi + jz 1f +2: movsb + dec %rdx + test $7,%edi + jnz 2b +1: mov %rdx,%rcx + shr $3,%rcx + rep + movsq + and $7,%edx + jz 1f +2: movsb + dec %edx + jnz 2b +1: ret diff --git a/lib/string/x86_64/memmove.s b/lib/string/x86_64/memmove.s new file mode 100644 index 0000000..172c025 --- /dev/null +++ b/lib/string/x86_64/memmove.s @@ -0,0 +1,16 @@ +.global memmove +.type memmove,@function +memmove: + mov %rdi,%rax + sub %rsi,%rax + cmp %rdx,%rax +.hidden __memcpy_fwd + jae __memcpy_fwd + mov %rdx,%rcx + lea -1(%rdi,%rdx),%rdi + lea -1(%rsi,%rdx),%rsi + std + rep movsb + cld + lea 1(%rdi),%rax + ret diff --git a/lib/string/x86_64/memset.s b/lib/string/x86_64/memset.s new file mode 100644 index 0000000..2d3f5e5 --- /dev/null +++ b/lib/string/x86_64/memset.s @@ -0,0 +1,72 @@ +.global memset +.type memset,@function +memset: + movzbq %sil,%rax + mov $0x101010101010101,%r8 + imul %r8,%rax + + cmp $126,%rdx + ja 2f + + test %edx,%edx + jz 1f + + mov %sil,(%rdi) + mov %sil,-1(%rdi,%rdx) + cmp $2,%edx + jbe 1f + + mov %ax,1(%rdi) + mov %ax,(-1-2)(%rdi,%rdx) + cmp $6,%edx + jbe 1f + + mov %eax,(1+2)(%rdi) + mov %eax,(-1-2-4)(%rdi,%rdx) + cmp $14,%edx + jbe 1f + + mov %rax,(1+2+4)(%rdi) + mov %rax,(-1-2-4-8)(%rdi,%rdx) + cmp $30,%edx + jbe 1f + + mov %rax,(1+2+4+8)(%rdi) + mov %rax,(1+2+4+8+8)(%rdi) + mov %rax,(-1-2-4-8-16)(%rdi,%rdx) + mov %rax,(-1-2-4-8-8)(%rdi,%rdx) + cmp $62,%edx + jbe 1f + + mov %rax,(1+2+4+8+16)(%rdi) + mov %rax,(1+2+4+8+16+8)(%rdi) + mov %rax,(1+2+4+8+16+16)(%rdi) + mov %rax,(1+2+4+8+16+24)(%rdi) + mov %rax,(-1-2-4-8-16-32)(%rdi,%rdx) + mov %rax,(-1-2-4-8-16-24)(%rdi,%rdx) + mov %rax,(-1-2-4-8-16-16)(%rdi,%rdx) + mov %rax,(-1-2-4-8-16-8)(%rdi,%rdx) + +1: mov %rdi,%rax + ret + +2: test $15,%edi + mov %rdi,%r8 + mov %rax,-8(%rdi,%rdx) + mov %rdx,%rcx + jnz 2f + +1: shr $3,%rcx + rep + stosq + mov %r8,%rax + ret + +2: xor %edx,%edx + sub %edi,%edx + and $15,%edx + mov %rax,(%rdi) + mov %rax,8(%rdi) + sub %rdx,%rcx + add %rdx,%rdi + jmp 1b diff --git a/linker.ld b/linker.ld deleted file mode 100644 index 3a51718..0000000 --- a/linker.ld +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -OUTPUT_ARCH( "riscv" ) -ENTRY( _start ) - -SECTIONS -{ - . = 0x80200000; - - /* Text Segment */ - .text : { - *(.text.entry) - *(.text .text.*) - . = ALIGN(0x1000); - PROVIDE(etext = .); - } - - /* Read Only Data Segment */ - .rodata : { - . = ALIGN(16); - *(.srodata .srodata.*) - . = ALIGN(16); - *(.rodata .rodata.*) - } - - /* Data Segment */ - .data : { - . = ALIGN(16); - *(.sdata .sdata.*) - . = ALIGN(16); - *(.data .data.*) - } - - /* BBS Segment */ - .bss : { - . = ALIGN(16); - PROVIDE(sbss = .); - *(.sbss .sbss.*) - . = ALIGN(16); - *(.bss .bss.*) - PROVIDE(ebss = .); - } - - . = ALIGN(0x1000); - PROVIDE(end = .); - PROVIDE(kernel_end = .); -} \ No newline at end of file diff --git a/lume-legacy/Makefile b/lume-legacy/Makefile new file mode 100644 index 0000000..0722f8c --- /dev/null +++ b/lume-legacy/Makefile @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +# Try to find a suitable RISC-V toolchain prefix +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-gnu-gcc >/dev/null 2>&1; then \ + echo riscv64-unknown-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_DIR := build +SRC_DIR := src + +CFLAGS := -Wall -Wextra -O2 -g -ffreestanding -nostdlib -mcmodel=medany -Iinclude +CXXFLAGS := $(CFLAGS) -fno-exceptions -fno-rtti -fno-use-cxa-atexit +LINKER_SCRIPT := linker.ld + +USER_LD := $(SRC_DIR)/user/user.ld +ULIB_OBJS := $(BUILD_DIR)/user/entry.o \ + $(BUILD_DIR)/user/usys.o \ + $(BUILD_DIR)/ulib/stdio.o \ + $(BUILD_DIR)/ulib/stdlib.o \ + $(BUILD_DIR)/ulib/string.o \ + $(BUILD_DIR)/ulib/cxx.o \ + $(BUILD_DIR)/ulib/iostream.o \ + $(BUILD_DIR)/ulib/syscalls.o + +INITCODE_BIN := $(BUILD_DIR)/user/initcode + + +USER_SRCS := $(wildcard $(SRC_DIR)/user/*.cc) +UPROGS := $(patsubst $(SRC_DIR)/user/%.cc, $(BUILD_DIR)/user/%, $(USER_SRCS)) + +SRCS_S := $(shell find $(SRC_DIR) -name "*.S" -not -path "$(SRC_DIR)/user/*" -not -path "$(SRC_DIR)/ulib/*") +SRCS_CXX := $(shell find $(SRC_DIR) -name "*.cc" -not -path "$(SRC_DIR)/user/*" -not -path "$(SRC_DIR)/ulib/*") +OBJS := $(SRCS_S:%.S=$(BUILD_DIR)/%.o) $(SRCS_CXX:%.cc=$(BUILD_DIR)/%.o) + +TARGET := $(BUILD_DIR)/kernel.elf + +QEMUOPTS = -drive file=fs.img,if=none,format=raw,id=x0 +QEMUOPTS += -device virtio-blk-device,drive=x0 -global virtio-mmio.force-legacy=false + +.PHONY: all clean run debug + +all: $(TARGET) $(INITCODE_BIN) $(UPROGS) fs.img + + +$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.c + @mkdir -p $(dir $@) + @echo " ULIB_C $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.cc + @mkdir -p $(dir $@) + @echo " ULIB_CC $<" + @$(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/ulib/%.o: $(SRC_DIR)/ulib/%.cpp + @mkdir -p $(dir $@) + @echo " ULIB_CPP $<" + @$(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/user/%.o: $(SRC_DIR)/user/%.cc + @mkdir -p $(dir $@) + @echo " USER_CC $<" + @$(CXX) $(CXXFLAGS) -c $< -o $@ + +$(BUILD_DIR)/user/%.o: $(SRC_DIR)/user/%.S + @mkdir -p $(dir $@) + @echo " USER_AS $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +$(INITCODE_BIN): $(BUILD_DIR)/user/init.o $(ULIB_OBJS) $(USER_LD) + @echo " U_LD $@" + @$(LD) -T $(USER_LD) -o $@.out $(BUILD_DIR)/user/init.o $(ULIB_OBJS) + @$(OBJCOPY) -S -O binary $@.out $@ + @$(OBJDUMP) -S $@.out > $@.asm + + +$(BUILD_DIR)/user/%: $(BUILD_DIR)/user/%.o $(ULIB_OBJS) $(USER_LD) + @echo " USER_LD $@" + @$(LD) -T $(USER_LD) -o $@ $< $(ULIB_OBJS) + @$(OBJDUMP) -S $@ > $@.asm + @$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $@.sym + + +$(TARGET): $(OBJS) $(LINKER_SCRIPT) + @echo " K_LD $@" + @$(LD) -T $(LINKER_SCRIPT) -o $@ $(OBJS) + @echo " K_ASM $@.asm" + @$(OBJDUMP) -S $@ > $@.asm + @$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $@.sym + +$(BUILD_DIR)/src/kernel/initcode.o: $(SRC_DIR)/kernel/initcode.S $(INITCODE_BIN) + @mkdir -p $(dir $@) + @echo " AS_INC $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/%.o: %.S + @mkdir -p $(dir $@) + @echo " K_AS $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/%.o: %.cc + @mkdir -p $(dir $@) + @echo " K_CXX $<" + @$(CXX) $(CXXFLAGS) -c $< -o $@ + +fs.img: $(UPROGS) + @echo " IMG Generating fs.img (FAT32)" + @dd if=/dev/zero of=fs.img bs=1M count=32 2>/dev/null + @mkfs.vfat -F 32 fs.img >/dev/null + $(foreach prog,$(UPROGS),mcopy -i fs.img $(prog) ::/$(notdir $(prog));) + +run: $(TARGET) fs.img + @echo " QEMU Running..." + @qemu-system-riscv64 -machine virt -nographic \ + -kernel $(TARGET) -m 128M $(QEMUOPTS) + +debug: $(TARGET) fs.img + @echo " QEMU (DEBUG) Waiting for GDB..." + @qemu-system-riscv64 -machine virt -nographic \ + -kernel $(TARGET) -m 128M -s -S $(QEMUOPTS) + +clean: + rm -rf $(BUILD_DIR) fs.img \ No newline at end of file diff --git a/lume-legacy/include/common/fcntl.h b/lume-legacy/include/common/fcntl.h new file mode 100644 index 0000000..6059538 --- /dev/null +++ b/lume-legacy/include/common/fcntl.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once + +#define O_RDONLY 0x000 +#define O_WRONLY 0x001 +#define O_RDWR 0x002 +#define O_CREATE 0x200 +#define O_TRUNC 0x400 + +#define NOFILE 16 \ No newline at end of file diff --git a/lume-legacy/include/common/types.h b/lume-legacy/include/common/types.h new file mode 100644 index 0000000..aa2cb08 --- /dev/null +++ b/lume-legacy/include/common/types.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef signed long long int64_t; + +typedef uint64_t size_t; +typedef uint64_t uintptr_t; + +typedef uint64_t uint64; +typedef uint32_t uint32; +typedef uint16_t uint16; +typedef uint8_t uint8; +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; + +typedef int64_t int64; +typedef int32_t int32; +typedef int16_t int16; +typedef int8_t int8; \ No newline at end of file diff --git a/lume-legacy/include/drivers/plic.h b/lume-legacy/include/drivers/plic.h new file mode 100644 index 0000000..cdcd0ce --- /dev/null +++ b/lume-legacy/include/drivers/plic.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +namespace PLIC +{ + // Global initialization (executed only once by hart 0) + void init(); + + // Per-core initialization (executed when each CPU starts) + void inithart(); + + // Claim interrupt: Get the currently occurring interrupt number (IRQ) + int claim(); + + // End of interrupt: notify the PLIC that this IRQ has been handled + void complete(int irq); +} // namespace PILC diff --git a/lume-legacy/include/drivers/uart.h b/lume-legacy/include/drivers/uart.h new file mode 100644 index 0000000..a56624c --- /dev/null +++ b/lume-legacy/include/drivers/uart.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ + +#pragma once +#include "common/types.h" +#include "drivers/uart_color.h" +#include "kernel/fdt.h" + +// UART register offsets (NS16550A) +#define RHR 0 // Receive Holding Register (read mode) +#define THR 0 // Transmit Holding Register (write mode) +#define IER 1 // Interrupt Enable Register +#define FCR 2 // FIFO Control Register +#define ISR 2 // Interrupt Status Register +#define LCR 3 // Line Control Register +#define LSR 5 // Line Status Register + +#define LSR_RX_READY 0x01 // input is waiting to be read from RHR +#define LSR_TX_IDLE 0x20 // THR can accept another character to send + +#define UART_RX_BUF_SIZE 128 + +// UART base address from FDT (use inline function to avoid macro issues) +static inline uint64 uart_base() { return g_devices.uart.base_addr; } + +namespace Drivers +{ + void uart_init(); + void uart_intr(); + int console_read(uint64 target, int len); + + void uart_putc(char c); + void uart_puts(const char *s); + void uart_put_int(int value); + void print_hex(uint64 value); +} \ No newline at end of file diff --git a/lume-legacy/include/drivers/uart_color.h b/lume-legacy/include/drivers/uart_color.h new file mode 100644 index 0000000..7ecc28b --- /dev/null +++ b/lume-legacy/include/drivers/uart_color.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +// ANSI Escape Codes for Modern Terminal Colors +#define ANSI_RESET "\033[0m" +#define ANSI_RED "\033[31m" +#define ANSI_GREEN "\033[32m" +#define ANSI_YELLOW "\033[33m" +#define ANSI_BLUE "\033[34m" +#define ANSI_CLEAR "\033[2J\033[1;1H" \ No newline at end of file diff --git a/lume-legacy/include/drivers/virtio.h b/lume-legacy/include/drivers/virtio.h new file mode 100644 index 0000000..9fda8cf --- /dev/null +++ b/lume-legacy/include/drivers/virtio.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +// VirtIO MMIO Base Address +#define VIRTIO0 0x10001000 + +// MMIO Registers (Modern / Version 2) +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 +#define VIRTIO_MMIO_VERSION 0x004 +#define VIRTIO_MMIO_DEVICE_ID 0x008 +#define VIRTIO_MMIO_VENDOR_ID 0x00c +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 +#define VIRTIO_MMIO_QUEUE_SEL 0x030 +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 +#define VIRTIO_MMIO_QUEUE_NUM 0x038 +#define VIRTIO_MMIO_QUEUE_READY 0x044 +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 +#define VIRTIO_MMIO_STATUS 0x070 + +// 64-bit Queue Address Registers (Modern) +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 +#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 +#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 +#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0a0 +#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0a4 + +// Status Bits +#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 +#define VIRTIO_CONFIG_S_DRIVER 2 +#define VIRTIO_CONFIG_S_DRIVER_OK 4 +#define VIRTIO_CONFIG_S_FEATURES_OK 8 + +// Feature Bits +#define VIRTIO_BLK_F_RO 5 +#define VIRTIO_BLK_F_SCSI 7 +#define VIRTIO_BLK_F_CONFIG_WCE 11 +#define VIRTIO_BLK_F_MQ 12 +#define VIRTIO_F_ANY_LAYOUT 27 +#define VIRTIO_RING_F_INDIRECT_DESC 28 +#define VIRTIO_RING_F_EVENT_IDX 29 +#define VIRTIO_F_VERSION_1 32 + +// Descriptor Flags +#define VRING_DESC_F_NEXT 1 +#define VRING_DESC_F_WRITE 2 + +#define NUM_DESCS 8 + +#define VIRTIO_BLK_T_IN 0 +#define VIRTIO_BLK_T_OUT 1 + +// Device Configuration Layout (Offset 0x100) +#define VIRTIO_MMIO_CONFIG 0x100 + +namespace VirtIO +{ + struct VRingDesc + { + uint64 addr; + uint32 len; + uint16 flags; + uint16 next; + }; + + struct VRingAvail + { + uint16 flags; + uint16 idx; + uint16 ring[NUM_DESCS]; + }; + + struct VRingUsedElem + { + uint32 id; + uint32 len; + }; + + struct VRingUsed + { + uint16 flags; + uint16 idx; + struct VRingUsedElem ring[NUM_DESCS]; + }; + + struct __attribute__((packed)) BlockRequest + { + uint32 type; // 0=Read, 1=Write + uint32 reserved; + uint64 sector; + }; + + void init(); + void intr(); + void rw(int write, uint64 blockno, char *buf); + void test_rw(); + void test_bio(); +} \ No newline at end of file diff --git a/lume-legacy/include/fs/fat32.h b/lume-legacy/include/fs/fat32.h new file mode 100644 index 0000000..63d62ae --- /dev/null +++ b/lume-legacy/include/fs/fat32.h @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "fs/fs.h" +#include "kernel/buf.h" + +// Disk Layout Structures (On-Disk) + +// FAT32 Boot Sector (BPB) +struct FAT32_BPB +{ + uint8 jmpBoot[3]; + char oemName[8]; + uint16 bytesPerSector; + uint8 secPerClus; + uint16 reservedSecCnt; + uint8 numFATs; + uint16 rootEntCnt; + uint16 totSec16; + uint8 media; + uint16 FATSz16; + uint16 secPerTrk; + uint16 numHeads; + uint32 hiddSec; + uint32 totSec32; + uint32 FATSz32; + uint16 extFlags; + uint16 FSVer; + uint32 rootClus; + uint16 FSInfo; + uint16 bkBootSec; + uint8 reserved[12]; + uint8 drvNum; + uint8 reserved1; + uint8 bootSig; + uint32 volID; + char volLab[11]; + char filSysType[8]; +} __attribute__((packed)); + +// FAT32 Directory Entry (32 bytes) +struct FAT32_DirEnt +{ + char name[11]; // 8.3 filename + uint8 attr; // File attributes + uint8 ntRes; + uint8 crtTimeTenth; + uint16 crtTime; + uint16 crtDate; + uint16 lstAccDate; + uint16 fstClusHI; // High 16 bits of first cluster + uint16 wrtTime; + uint16 wrtDate; + uint16 fstClusLO; // Low 16 bits of first cluster + uint32 fileSize; +} __attribute__((packed)); + +#define ATTR_READ_ONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLUME_ID 0x08 +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LONG_NAME 0x0F + +// In-Memory Classes + +class FAT32FileSystem; + +class FAT32Inode : public Inode +{ +public: + FAT32FileSystem *fs; + + // FAT32 Private Data + uint32 first_clus; // The starting cluster number of the file + uint32 parent_clus; // Starting cluster number of the parent directory + uint32 dir_offset; // The byte offset of this directory entry in the parent directory + uint32 ent_sec; // Sector number of the directory entry + uint32 ent_off; // Offset of the directory entry within the sector + + FAT32Inode() : fs(nullptr), first_clus(0) {} + // Implement VFS Interface + void update() override; + int read(char *dst, uint32 off, uint32 n, int isUser) override; + int write(const char *src, uint32 off, uint32 n, int isUser) override; + Inode *lookup(const char *name) override; + Inode *create(const char *name, short type, short major, short minor) override; + void truncate() override; + int unlink(const char *name) override; +}; + +class FAT32FileSystem : public FileSystem +{ +public: + uint32 fat_start_sector; + uint32 data_start_sector; + uint32 root_cluster; + uint32 sectors_per_cluster; + uint32 bytes_per_cluster; + uint32 fat_sz; // Size of the FAT table (number of sectors) + + void init() override; + Inode* root() override; + + // Helper functions + uint32 next_cluster(uint32 cluster); // Check the FAT table for the next cluster + uint32 alloc_cluster(); // Assign a new cluster + uint32 cluster_to_sector(uint32 cluster); + + // directory entry parsing + void read_dir_ent(struct FAT32_DirEnt *de, char *filename); + // Release the entire cluster chain starting from the cluster + void free_cluster_chain(uint32 cluster); +}; + +extern FAT32FileSystem fat32_fs; + +void fat32_test(); \ No newline at end of file diff --git a/lume-legacy/include/fs/file.h b/lume-legacy/include/fs/file.h new file mode 100644 index 0000000..9aff1af --- /dev/null +++ b/lume-legacy/include/fs/file.h @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "fs/fs.h" + +enum FileType +{ + FD_NONE, + FD_INODE, + FD_DEVICE, + FD_PIPE +}; + +class Pipe; + +struct file +{ + enum FileType type; + int ref; + char readable; + char writable; + + Pipe *pipe; // pipeline pointer + Inode *ip; // VFS Inode + uint32 off; // Current Offset +}; + +class Pipe +{ +public: + static constexpr uint32_t SIZE = 512; + + Pipe() : nread(0), nwrite(0), read_open(true), write_open(true) + { + lock.init("pipe"); + } + + // forbid copy + Pipe(const Pipe &) = delete; + Pipe &operator=(const Pipe &) = delete; + + ~Pipe() + { + // + } + + // creates a pair of connected file objects + static int create_pair(struct file **f0, struct file **f1); + + int read(uint64_t addr, int n); + int write(uint64_t addr, int n); + void close(bool is_writer); + +private: + Spinlock lock; + char data[SIZE]; + uint32_t nread; // read + uint32_t nwrite; // write + bool read_open; + bool write_open; +}; + +namespace FileTable +{ + void init(); + struct file *alloc(); // Allocate a free file handle + struct file *dup(struct file *f); // Duplicate handle (sys_dup) + void close(struct file *f); // Close handle + + int stat(struct file *f, uint64 addr); + int read(struct file *f, uint64 addr, int n); + int write(struct file *f, uint64 addr, int n); + int lseek(struct file *f, int offset, int whence); +} // namespace FileTable diff --git a/lume-legacy/include/fs/fs.h b/lume-legacy/include/fs/fs.h new file mode 100644 index 0000000..b511a84 --- /dev/null +++ b/lume-legacy/include/fs/fs.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "kernel/spinlock.h" +#include "fs/stat.h" + +class FileSystem; + +// VFS Inode (Abstract Base Class) + +class Inode +{ +private: + ; +public: + // VFS Common Fields + uint32 dev; // Device number (VirtIO Block ID) + uint32 inum; // Inode number + int ref_cnt; // Memory reference count (number of struct file or processes referencing it) + int valid; // Whether the data is valid (whether metadata has been read from disk) + + Mutex lock; // Sleep lock: protects internal Inode data and atomicity of read/write operations + + // Metadata Cache (for speeding up getattr) + short type; // T_DIR, T_FILE, T_DEVICE + short major; // Major device number (only for T_DEVICE) + short minor; // Minor device number (only for T_DEVICE) + short nlink; // Number of hard links + uint32 size; // File size + + Inode() : ref_cnt(0), valid(0) + { + lock.init("inode"); + } + virtual ~Inode() {} + + /* Core Operation Interface (Subclass Must Implement) */ + // Update metadata + // Sync size, type, links, etc. from memory back to disk + virtual void update() = 0; + // Read content + // Read n bytes from the file at offset into dst + // Returns the actual number of bytes read, returns -1 on error + virtual int read(char *dst, uint32 off, uint32 n, int isUser) = 0; + // 3. Write content + // Write n bytes from src to the file at the given offset + // Need to handle file growth + virtual int write(const char *src, uint32 off, uint32 n, int isUser) = 0; + // Directory Lookup (only valid when type == T_DIR) + // Search for a file named 'name' in the current directory and return its Inode pointer (locked) + // Return nullptr if not found + virtual Inode *lookup(const char *name) = 0; + // Create file/directory (only effective when type == T_DIR) + // Create a new entry in the current directory + virtual Inode *create(const char *name, short type, short major, short minor) = 0; + // Get file status (populate the stat structure) + // Default implementation, subclasses can override to provide more detailed information + virtual void getattr(struct kstat *st); + // Truncate file (set size to 0, release all data blocks) + virtual void truncate() = 0; + // Delete the file named 'name' from the directory (Unlink) + virtual int unlink(const char *name) = 0; +}; + +// File System Abstraction (SuperBlock) +class FileSystem +{ +private: + ; +public: + virtual void init() = 0; + virtual Inode* root() = 0; + virtual ~FileSystem() {}; +}; + +// Inode Cache Interface (Global Function) +namespace VFS +{ + void init(); + + // Decrease the reference count, and if it reaches 0, free the memory + void iput(Inode *ip); + // Increase reference count + Inode *idup(Inode *ip); + // lock and load data + void ilock(Inode *ip); + // unlock + void iunlock(Inode *ip); + void iunlockput(Inode *ip); + // Mount the root filesystem + void mount_root(Inode *root); + // Get system root node + Inode *get_root(); + // Path Analysis + Inode *namei(const char *path); + // Find the parent directory inode of the path, and copy the last part of the path (the file name) into the name buffer + Inode *nameiparent(const char *path, char *name); + // Register a file system + void register_fs(FileSystem *fs); + +} // namespace VFS diff --git a/lume-legacy/include/fs/stat.h b/lume-legacy/include/fs/stat.h new file mode 100644 index 0000000..e60ae42 --- /dev/null +++ b/lume-legacy/include/fs/stat.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +#define T_DIR 1 // Directory +#define T_FILE 2 // File +#define T_DEVICE 3 // Device + +struct kstat +{ + int dev; // File System's disk device + uint32 ino; // Inode number + short type; //Type of file + short nlink; // Number of links to file + uint64 size; // Size of file in bytes + + uint32 mode; // Protection: rwx + uint32 uid; // User ID + uint32 gid; // Group ID + uint32 atime; // Access time + uint32 mtime; // Modified time + uint32 ctime; // Change time +}; diff --git a/lume-legacy/include/kernel/buf.h b/lume-legacy/include/kernel/buf.h new file mode 100644 index 0000000..d0d849c --- /dev/null +++ b/lume-legacy/include/kernel/buf.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "kernel/spinlock.h" + +#define BSIZE 1024 +#define NBUF 30 + +struct Buf +{ + int valid; // Whether the data has been read from the disk (1=valid, 0=invalid) + int disk; // Whether it has been held by the driver (dirty bit/write-back flag is usually extended here) + uint32 dev; // Device Number + uint32 blockno; // Block number + uint32 refcnt; // Reference Counting + struct Buf *prev; // LRU linked list + struct Buf *next; + uchar data[BSIZE]; + + Mutex lock; // Sleep Lock +}; + +namespace BufferCache +{ + void init(); + struct Buf *bread(uint32 dev, uint32 blockno); + void bwrite(struct Buf *b); + void brelse(struct Buf *b); + void bpin(struct Buf *b); + void bunpin(struct Buf *b); +} // namespace BufferCache diff --git a/lume-legacy/include/kernel/config.h b/lume-legacy/include/kernel/config.h new file mode 100644 index 0000000..49df2ff --- /dev/null +++ b/lume-legacy/include/kernel/config.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ + +#pragma once +#include "common/types.h" +#include "kernel/fdt.h" + +// Legacy compat — these are still referenced in some places +extern uint64 g_uart_base; +extern uint64 g_dtb_addr; \ No newline at end of file diff --git a/lume-legacy/include/kernel/cpu.h b/lume-legacy/include/kernel/cpu.h new file mode 100644 index 0000000..62bdc17 --- /dev/null +++ b/lume-legacy/include/kernel/cpu.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "kernel/proc_def.h" + +struct RunQueue; + +struct cpu +{ + struct Proc *proc; + struct Context context; + int noff; + int intena; + + struct RunQueue *runqueue; +}; + + +extern struct cpu cpus[]; +struct cpu *mycpu(); \ No newline at end of file diff --git a/lume-legacy/include/kernel/elf.h b/lume-legacy/include/kernel/elf.h new file mode 100644 index 0000000..30193d2 --- /dev/null +++ b/lume-legacy/include/kernel/elf.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian + +// ELF File Header +struct elfhdr +{ + uint32 magic; // Must equal ELF_MAGIC + uint8 elf[12]; + uint16 type; + uint16 machine; + uint32 version; + uint64 entry; // Program Entry Point (Virtual Address) + uint64 phoff; // File offset of the Program Header Table + uint64 shoff; + uint32 flags; + uint16 ehsize; + uint16 phentsize; + uint16 phnum; // Number of Program Headers + uint16 shentsize; + uint16 shnum; + uint16 shstrndx; +}; + +// Program Header +// Describes how to load segments from a file into memory +struct proghdr +{ + uint32 type; + uint32 flags; + uint64 off; // Offset of the section in the file + uint64 vaddr; // Virtual address of the segment in memory + uint64 paddr; + uint64 filesz; // Size of the section in the file + uint64 memsz; // The size of the segment in memory (memsz >= filesz) + uint64 align; +}; + +// Values for Proghdr type +#define ELF_PROG_LOAD 1 + +// Flag bits for Proghdr flags +#define ELF_PROG_FLAG_EXEC 1 +#define ELF_PROG_FLAG_WRITE 2 +#define ELF_PROG_FLAG_READ 4 \ No newline at end of file diff --git a/lume-legacy/include/kernel/exec.h b/lume-legacy/include/kernel/exec.h new file mode 100644 index 0000000..304e03f --- /dev/null +++ b/lume-legacy/include/kernel/exec.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once + +namespace Exec +{ + int exec(char *path, char **argv); +} \ No newline at end of file diff --git a/lume-legacy/include/kernel/fdt.h b/lume-legacy/include/kernel/fdt.h new file mode 100644 index 0000000..89cbae6 --- /dev/null +++ b/lume-legacy/include/kernel/fdt.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * LumeOS - FDT Device Discovery + */ + +#pragma once +#include "common/types.h" + +// Single device descriptor (populated from FDT) +struct FdtDevice { + uint64 base_addr; // MMIO base address + uint64 size; // MMIO region size + uint32 irq; // Interrupt number (0 = none) +}; + +// Device registry — populated by fdt_parse(), consumed by drivers +struct DeviceRegistry { + // Memory + uint64 mem_base; + uint64 mem_size; + + // Core platform devices + FdtDevice uart; + FdtDevice plic; + FdtDevice clint; + + // VirtIO devices (multiple possible) + static constexpr int MAX_VIRTIO = 8; + FdtDevice virtio[MAX_VIRTIO]; + int virtio_count; + + // MMIO ranges for kvminit() to map + static constexpr int MAX_MMIO = 16; + struct MmioRegion { + uint64 base; + uint64 size; + } mmio_regions[MAX_MMIO]; + int mmio_count; +}; + +extern DeviceRegistry g_devices; + +// Parse FDT and populate g_devices +void fdt_parse(uint64 dtb); diff --git a/lume-legacy/include/kernel/mm.h b/lume-legacy/include/kernel/mm.h new file mode 100644 index 0000000..e3de09a --- /dev/null +++ b/lume-legacy/include/kernel/mm.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +namespace VM +{ + extern uint64 *kernel_pagetable; + + void kvminit(); + void kvminithart(); + + uint64 *uvmcreate(); + void uvminit(uint64 *pagetable, uchar *src, uint64 sz); + uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm); + uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz); + void uvmfree(uint64 *pagetable, uint64 sz); + int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz); + void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free); + + int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len); + int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len); + int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max); + + int handle_cow_fault(uint64 *pagetable, uint64 va); + int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm); + + uint64 walkaddr(uint64 *pagetable, uint64 va); +} \ No newline at end of file diff --git a/lume-legacy/include/kernel/pmm.h b/lume-legacy/include/kernel/pmm.h new file mode 100644 index 0000000..261786d --- /dev/null +++ b/lume-legacy/include/kernel/pmm.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ + +#pragma once +#include "common/types.h" +#include "kernel/spinlock.h" +#include "kernel/fdt.h" // For g_devices + +// Physical Memory Layout Definition +#define KERNBASE 0x80000000 + +// Dynamic memory size from FDT +// Default to 128MB if FDT not present/parsed +extern uint64 g_phys_size; + +#define PHYSIZE (g_phys_size) +#define PHYSTOP (KERNBASE + PHYSIZE) + +// Buddy System Max Order +// Order 10 => 2^10 pages = 1024 * 4KB = 4MB contiguous block +// Order 11 => 2^11 pages = 8MB +#define MAX_ORDER 11 + +// Page flag +#define PG_used 0x01 // This page is occupied +#define PG_reserved 0x02 // This page is reserved (kernel code/data, DTB, MemMap) + +// Physical Page Metadata Structure +struct Page +{ + uint8 flags; // Page Status (PG_used, PG_reserved) + uint8 order; // If it is the first page of a free block, record the order of the current block + uint16 refcnt; // Reference Counting + struct Page *next; // Free list pointer (points to the next free block of the same size) + struct Page *prev; // Doubly linked list (easy to remove) +}; + +namespace PMM +{ + // Initialize physical memory management + void init(); + + // Core Allocation Interface (Buddy System) + // order: 0 ~ MAX_ORDER-1 + void *alloc_pages(int order); + void free_pages(void *pa, int order); + + // Compatible with old interface (allocate single page with order=0 and automatically reset to zero) + void *alloc_page(); + void free_page(void *pa); + + // Helper function + struct Page *pa_to_page(uint64 pa); + uint64 page_to_pa(struct Page *page); + + // Debug information + void print_free_lists(); +} \ No newline at end of file diff --git a/lume-legacy/include/kernel/proc.h b/lume-legacy/include/kernel/proc.h new file mode 100644 index 0000000..e11dc47 --- /dev/null +++ b/lume-legacy/include/kernel/proc.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "kernel/proc_def.h" +#include "kernel/spinlock.h" + +namespace ProcManager +{ + void init(); + void scheduler(); + void yield(); + + void create_kernel_thread(void (*func)(), const char *name); + + void user_init(); + void exit(int status); + int wait(uint64 addr); + + void sleep(void *chan, Spinlock *lk); + void wakeup(void *chan); + + int growproc(int n); + + int kill(int pid); +} + +void forkret(); +struct Proc *myproc(); +void usertrapret(); +int fork(); \ No newline at end of file diff --git a/lume-legacy/include/kernel/proc_def.h b/lume-legacy/include/kernel/proc_def.h new file mode 100644 index 0000000..50f27d7 --- /dev/null +++ b/lume-legacy/include/kernel/proc_def.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "common/fcntl.h" + +struct cpu; +struct Trapframe; +class Inode; +struct file; + +enum ProcState +{ + UNUSED, + USED, + SLEEPING, + RUNNABLE, + RUNNING, + ZOMBIE, +}; + +#define PRIO_MAX 0 +#define PRIO_MIN 3 +#define N_PRIO 4 + +// Context structure +struct Context +{ + uint64 ra; + uint64 sp; + uint64 s0; + uint64 s1; + uint64 s2; + uint64 s3; + uint64 s4; + uint64 s5; + uint64 s6; + uint64 s7; + uint64 s8; + uint64 s9; + uint64 s10; + uint64 s11; +}; + +struct Proc +{ + struct Context context; + struct Trapframe *tf; + uint64 *pagetable; + enum ProcState state; + int pid; + void *kstack; + char name[16]; + uint64 sz; + struct Proc *parent; + void *chan; + int xstate; + int killed; + + Inode *cwd; + struct file *ofile[NOFILE]; + + struct Proc *next; + struct Proc *prev; + + struct Proc *all_next; + struct Proc *all_prev; + + int priority; + int ticks; + struct cpu *cpu; +}; \ No newline at end of file diff --git a/lume-legacy/include/kernel/riscv.h b/lume-legacy/include/kernel/riscv.h new file mode 100644 index 0000000..0c586a9 --- /dev/null +++ b/lume-legacy/include/kernel/riscv.h @@ -0,0 +1,404 @@ +#ifndef __ASSEMBLER__ + +// which hart (core) is this? +static inline uint64 +r_mhartid() +{ + uint64 x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +// Machine Status Register, mstatus + +#define MSTATUS_MPP_MASK (3L << 11) // previous mode. +#define MSTATUS_MPP_M (3L << 11) +#define MSTATUS_MPP_S (1L << 11) +#define MSTATUS_MPP_U (0L << 11) + +static inline uint64 +r_mstatus() +{ + uint64 x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void +w_mstatus(uint64 x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +// machine exception program counter, holds the +// instruction address to which a return from +// exception will go. +static inline void +w_mepc(uint64 x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +// Supervisor Status Register, sstatus + +#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User +#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable +#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable +#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable +#define SSTATUS_UIE (1L << 0) // User Interrupt Enable + +static inline uint64 +r_sstatus() +{ + uint64 x; + asm volatile("csrr %0, sstatus" : "=r" (x) ); + return x; +} + +static inline void +w_sstatus(uint64 x) +{ + asm volatile("csrw sstatus, %0" : : "r" (x)); +} + +// Supervisor Interrupt Pending +static inline uint64 +r_sip() +{ + uint64 x; + asm volatile("csrr %0, sip" : "=r" (x) ); + return x; +} + +static inline void +w_sip(uint64 x) +{ + asm volatile("csrw sip, %0" : : "r" (x)); +} + +// Supervisor Interrupt Enable +#define SIE_SEIE (1L << 9) // external +#define SIE_STIE (1L << 5) // timer +#define SIE_SSIE (1L << 1) // software +static inline uint64 +r_sie() +{ + uint64 x; + asm volatile("csrr %0, sie" : "=r" (x) ); + return x; +} + +static inline void +w_sie(uint64 x) +{ + asm volatile("csrw sie, %0" : : "r" (x)); +} + +// Machine-mode Interrupt Enable +#define MIE_STIE (1L << 5) // supervisor timer +static inline uint64 +r_mie() +{ + uint64 x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void +w_mie(uint64 x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +// supervisor exception program counter, holds the +// instruction address to which a return from +// exception will go. +static inline void +w_sepc(uint64 x) +{ + asm volatile("csrw sepc, %0" : : "r" (x)); +} + +static inline uint64 +r_sepc() +{ + uint64 x; + asm volatile("csrr %0, sepc" : "=r" (x) ); + return x; +} + +// Machine Exception Delegation +static inline uint64 +r_medeleg() +{ + uint64 x; + asm volatile("csrr %0, medeleg" : "=r" (x) ); + return x; +} + +static inline void +w_medeleg(uint64 x) +{ + asm volatile("csrw medeleg, %0" : : "r" (x)); +} + +// Machine Interrupt Delegation +static inline uint64 +r_mideleg() +{ + uint64 x; + asm volatile("csrr %0, mideleg" : "=r" (x) ); + return x; +} + +static inline void +w_mideleg(uint64 x) +{ + asm volatile("csrw mideleg, %0" : : "r" (x)); +} + +// Supervisor Trap-Vector Base Address +// low two bits are mode. +static inline void +w_stvec(uint64 x) +{ + asm volatile("csrw stvec, %0" : : "r" (x)); +} + +static inline uint64 +r_stvec() +{ + uint64 x; + asm volatile("csrr %0, stvec" : "=r" (x) ); + return x; +} + +// Supervisor Timer Comparison Register +static inline uint64 +r_stimecmp() +{ + uint64 x; + // asm volatile("csrr %0, stimecmp" : "=r" (x) ); + asm volatile("csrr %0, 0x14d" : "=r" (x) ); + return x; +} + +static inline void +w_stimecmp(uint64 x) +{ + // asm volatile("csrw stimecmp, %0" : : "r" (x)); + asm volatile("csrw 0x14d, %0" : : "r" (x)); +} + +// Machine Environment Configuration Register +static inline uint64 +r_menvcfg() +{ + uint64 x; + // asm volatile("csrr %0, menvcfg" : "=r" (x) ); + asm volatile("csrr %0, 0x30a" : "=r" (x) ); + return x; +} + +static inline void +w_menvcfg(uint64 x) +{ + //asm volatile("csrw menvcfg, %0" : : "r" (x)); + asm volatile("csrw 0x30a, %0" : : "r" (x)); +} + +// Physical Memory Protection +static inline void +w_pmpcfg0(uint64 x) +{ + asm volatile("csrw pmpcfg0, %0" : : "r" (x)); +} + +static inline void +w_pmpaddr0(uint64 x) +{ + asm volatile("csrw pmpaddr0, %0" : : "r" (x)); +} + +// use riscv's sv39 page table scheme. +#define SATP_SV39 (8L << 60) + +#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) + +// supervisor address translation and protection; +// holds the address of the page table. +static inline void +w_satp(uint64 x) +{ + asm volatile("csrw satp, %0" : : "r" (x)); +} + +static inline uint64 +r_satp() +{ + uint64 x; + asm volatile("csrr %0, satp" : "=r" (x) ); + return x; +} + +// Supervisor Trap Cause +static inline uint64 +r_scause() +{ + uint64 x; + asm volatile("csrr %0, scause" : "=r" (x) ); + return x; +} + +// Supervisor Trap Value +static inline uint64 +r_stval() +{ + uint64 x; + asm volatile("csrr %0, stval" : "=r" (x) ); + return x; +} + +// Machine-mode Counter-Enable +static inline void +w_mcounteren(uint64 x) +{ + asm volatile("csrw mcounteren, %0" : : "r" (x)); +} + +static inline uint64 +r_mcounteren() +{ + uint64 x; + asm volatile("csrr %0, mcounteren" : "=r" (x) ); + return x; +} + +// machine-mode cycle counter +static inline uint64 +r_time() +{ + uint64 x; + asm volatile("csrr %0, time" : "=r" (x) ); + return x; +} + +// enable device interrupts +static inline void +intr_on() +{ + w_sstatus(r_sstatus() | SSTATUS_SIE); +} + +// disable device interrupts +static inline void +intr_off() +{ + w_sstatus(r_sstatus() & ~SSTATUS_SIE); +} + +// are device interrupts enabled? +static inline int +intr_get() +{ + uint64 x = r_sstatus(); + return (x & SSTATUS_SIE) != 0; +} + +static inline uint64 +r_sp() +{ + uint64 x; + asm volatile("mv %0, sp" : "=r" (x) ); + return x; +} + +static inline uint64 +r_fp() +{ + uint64 x; + asm volatile("mv %0, s0" : "=r" (x) ); + return x; +} + +// read and write tp, the thread pointer, which xv6 uses to hold +// this core's hartid (core number), the index into cpus[]. +static inline uint64 +r_tp() +{ + uint64 x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +static inline void +w_tp(uint64 x) +{ + asm volatile("mv tp, %0" : : "r" (x)); +} + +static inline uint64 +r_ra() +{ + uint64 x; + asm volatile("mv %0, ra" : "=r" (x) ); + return x; +} + +// flush the TLB. +static inline void +sfence_vma() +{ + // the zero, zero means flush all TLB entries. + asm volatile("sfence.vma zero, zero"); +} + +typedef uint64 pte_t; +typedef uint64 *pagetable_t; // 512 PTEs + +#endif // __ASSEMBLER__ + +#define PGSIZE 4096 // bytes per page +#define PGSHIFT 12 // bits of offset within a page + +#ifdef LAB_PGTBL +#define SUPERPGSIZE (2 * (1 << 20)) // bytes per page +#define SUPERPGROUNDUP(sz) (((sz)+SUPERPGSIZE-1) & ~(SUPERPGSIZE-1)) +#define SUPERPGROUNDDOWN(sz) (SUPERPGROUNDUP(sz)-SUPERPGSIZE) +#endif + +#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1)) +#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) + +#define PTE_V (1L << 0) // valid +#define PTE_R (1L << 1) +#define PTE_W (1L << 2) +#define PTE_X (1L << 3) +#define PTE_U (1L << 4) // user can access + +// COW Flag (PTE bit 8) +#define PTE_COW (1L << 8) + +#if defined(LAB_MMAP) || defined(LAB_PGTBL) || defined(LAB_COW) +#define PTE_LEAF(pte) (((pte) & PTE_R) | ((pte) & PTE_W) | ((pte) & PTE_X)) +#endif + +// shift a physical address to the right place for a PTE. +#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) + +#define PTE2PA(pte) (((pte) >> 10) << 12) + +#define PTE_FLAGS(pte) ((pte) & 0x3FF) + +// extract the three 9-bit page table indices from a virtual address. +#define PXMASK 0x1FF // 9 bits +#define PXSHIFT(level) (PGSHIFT+(9*(level))) +#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) + +// one beyond the highest possible virtual address. +// MAXVA is actually one bit less than the max allowed by +// Sv39, to avoid having to sign-extend virtual addresses +// that have the high bit set. +#define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) +#define TRAMPOLINE (MAXVA - PGSIZE) +#define TRAPFRAME (TRAMPOLINE - PGSIZE) \ No newline at end of file diff --git a/lume-legacy/include/kernel/runqueue.h b/lume-legacy/include/kernel/runqueue.h new file mode 100644 index 0000000..14704ce --- /dev/null +++ b/lume-legacy/include/kernel/runqueue.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "kernel/spinlock.h" +#include "kernel/proc_def.h" + +struct RunQueue +{ + Spinlock lock; + struct Proc *heads[N_PRIO]; + struct Proc *tails[N_PRIO]; + uint64 count; + + void init(const char *name); + void enqueue(struct Proc *p); + struct Proc *dequeue(); + struct Proc *steal(); +}; \ No newline at end of file diff --git a/lume-legacy/include/kernel/sbi.h b/lume-legacy/include/kernel/sbi.h new file mode 100644 index 0000000..a6be5b3 --- /dev/null +++ b/lume-legacy/include/kernel/sbi.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +// SBI Extension IDs +#define SBI_EID_SRST 0x53525354 + +// SBI Function IDs for SRST +#define SBI_FID_SRST_RESET 0 + +// Reset Types +#define SBI_SRST_TYPE_SHUTDOWN 0 +#define SBI_SRST_TYPE_COLD_REBOOT 1 +#define SBI_SRST_TYPE_WARM_REBOOT 2 + +// Reset Reasons +#define SBI_SRST_REASON_NONE 0 +#define SBI_SRST_REASON_SYSTEM_FAILURE 1 + +namespace SBI +{ + void set_timer(uint64 stime_value); + void sbi_shutdown(); + void sbi_reboot(); +} // namespace SBI diff --git a/lume-legacy/include/kernel/slab.h b/lume-legacy/include/kernel/slab.h new file mode 100644 index 0000000..bb7b699 --- /dev/null +++ b/lume-legacy/include/kernel/slab.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +namespace Slab +{ + void init(); + + void *kmalloc(uint64 size); + void kfree(void *ptr); +} \ No newline at end of file diff --git a/lume-legacy/include/kernel/spinlock.h b/lume-legacy/include/kernel/spinlock.h new file mode 100644 index 0000000..1511199 --- /dev/null +++ b/lume-legacy/include/kernel/spinlock.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +struct cpu; + +// Spinlock +// Features: Non-interruptible, busy-wait loop, extremely short hold time. +// Scenario: Protecting core data such as scheduler, interrupt handling, PMM, etc. +class Spinlock +{ +public: + volatile uint32 locked; // Whether it is locked (0: unlocked, 1: locked) + const char *name; // Lock Name (for debugging) + struct cpu *cpu; // The CPU holding the lock + + // Constructor + Spinlock() : locked(0), name("uninit"), cpu(nullptr) {} + + // Initialize lock name + void init(const char *name); + + // Acquire lock + void acquire(); + + // Release lock + void release(); + + // Check if the current CPU holds the lock + bool holding(); +}; + +// Mutex +// Features: Allows interruption, allows sleeping (yield CPU), can hold for a long time. +// Scenarios: File system IO, device driver waiting, long-term buffering operations. +class Mutex +{ +public: + uint32 locked; // Whether it is locked + const char *name; // Lock Name + int pid; // PID of the process holding the lock (for debugging) + + Spinlock lk; // Internal spin lock, protects the state of the Mutex itself + + void init(const char *name); + void acquire(); + void release(); + bool holding(); +}; \ No newline at end of file diff --git a/lume-legacy/include/kernel/sync.h b/lume-legacy/include/kernel/sync.h new file mode 100644 index 0000000..7f7c208 --- /dev/null +++ b/lume-legacy/include/kernel/sync.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" +#include "kernel/spinlock.h" +#include "kernel/proc_def.h" + + +struct WaitQueue +{ + Spinlock lock; + struct Proc *head; + struct Proc *tail; + + void init(const char *name); + void sleep(); + void wakeup(); + void wakeup_all(); +}; + +struct Semaphore +{ + int value; + WaitQueue wq; + Spinlock lock; + + void init(int val, const char *name); + void wait(); + void post(); +}; + +struct CondVar +{ + WaitQueue wq; + + void init(const char *name); + void wait(Spinlock *mutex_lock); + void signal(); + void broadcast(); +}; \ No newline at end of file diff --git a/lume-legacy/include/kernel/syscall.h b/lume-legacy/include/kernel/syscall.h new file mode 100644 index 0000000..3594c08 --- /dev/null +++ b/lume-legacy/include/kernel/syscall.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * Lume OS - Linux Compatibility Layer + * + * Strictly aligned with Linux RISC-V 64 ABI (asm-generic) + * Reference: linux/usr/include/asm-generic/unistd.h + */ + +#pragma once + +// File System +#define SYS_getcwd 17 +#define SYS_dup 23 +#define SYS_dup3 24 +#define SYS_fcntl 25 +#define SYS_ioctl 29 +#define SYS_mknodat 33 +#define SYS_mkdirat 34 +#define SYS_unlinkat 35 +#define SYS_linkat 37 +#define SYS_umount2 39 +#define SYS_mount 40 +#define SYS_statfs 43 +#define SYS_ftruncate 46 +#define SYS_faccessat 48 +#define SYS_chdir 49 +#define SYS_fchmod 52 +#define SYS_fchmodat 53 +#define SYS_fchownat 54 +#define SYS_openat 56 +#define SYS_close 57 +#define SYS_vhangup 58 +#define SYS_pipe2 59 +#define SYS_getdents64 61 +#define SYS_lseek 62 +#define SYS_read 63 +#define SYS_write 64 +#define SYS_readv 65 +#define SYS_writev 66 +#define SYS_pread64 67 +#define SYS_pwrite64 68 +#define SYS_pselect6 72 +#define SYS_ppoll 73 +#define SYS_readlinkat 78 +#define SYS_fstatat 79 // newfstatat +#define SYS_fstat 80 + +// Process Management +#define SYS_exit 93 +#define SYS_exit_group 94 +#define SYS_set_tid_address 96 +#define SYS_nanosleep 101 +#define SYS_kill 129 +#define SYS_rt_sigaction 134 +#define SYS_times 153 +#define SYS_uname 160 +#define SYS_gettimeofday 169 +#define SYS_getpid 172 +#define SYS_getppid 173 +#define SYS_getuid 174 +#define SYS_geteuid 175 +#define SYS_getgid 176 +#define SYS_getegid 177 +#define SYS_sysinfo 179 +#define SYS_brk 214 +#define SYS_munmap 215 +#define SYS_mremap 216 +#define SYS_clone 220 +#define SYS_execve 221 +#define SYS_mmap 222 +#define SYS_wait4 260 +#define SYS_prlimit64 261 + +// System +#define SYS_reboot 142 // Used for shutdown + +// === Custom / Debug (Non-Standard) === +// Placed above 1000 to avoid conflicts with Linux ABI +#define SYS_putc 1000 +#define SYS_disk_test 1001 + +#ifndef __ASSEMBLER__ +void syscall(); +#endif \ No newline at end of file diff --git a/lume-legacy/include/kernel/timer.h b/lume-legacy/include/kernel/timer.h new file mode 100644 index 0000000..8b97bbe --- /dev/null +++ b/lume-legacy/include/kernel/timer.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#include "common/types.h" + +namespace Timer{ + uint64 get_time(); + void set_next_trigger(); + void init(); + + void tick(); + uint64 get_ticks(); + Spinlock *get_lock(); + void *get_tick_chan(); +} \ No newline at end of file diff --git a/lume-legacy/include/kernel/trap.h b/lume-legacy/include/kernel/trap.h new file mode 100644 index 0000000..ddc1d12 --- /dev/null +++ b/lume-legacy/include/kernel/trap.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + +struct Trapframe +{ + /* 0 */ uint64 kernel_satp; + /* 8 */ uint64 kernel_sp; + /* 16 */ uint64 kernel_trap; + /* 24 */ uint64 epc; + /* 32 */ uint64 kernel_hartid; + + /* 40 */ uint64 ra; // x1 + /* 48 */ uint64 sp; // x2 + /* 56 */ uint64 gp; // x3 + /* 64 */ uint64 tp; // x4 + /* 72 */ uint64 t0; // x5 + /* 80 */ uint64 t1; // x6 + /* 88 */ uint64 t2; // x7 + /* 96 */ uint64 s0; // x8 (Frame Pointer) + /* 104 */ uint64 s1; // x9 + /* 112 */ uint64 a0; // x10 + /* 120 */ uint64 a1; // x11 + /* 128 */ uint64 a2; // x12 + /* 136 */ uint64 a3; // x13 + /* 144 */ uint64 a4; // x14 + /* 152 */ uint64 a5; // x15 + /* 160 */ uint64 a6; // x16 + /* 168 */ uint64 a7; // x17 + /* 176 */ uint64 s2; // x18 + /* 184 */ uint64 s3; // x19 + /* 192 */ uint64 s4; // x20 + /* 200 */ uint64 s5; // x21 + /* 208 */ uint64 s6; // x22 + /* 216 */ uint64 s7; // x23 + /* 224 */ uint64 s8; // x24 + /* 232 */ uint64 s9; // x25 + /* 240 */ uint64 s10; // x26 + /* 248 */ uint64 s11; // x27 + /* 256 */ uint64 t3; // x28 + /* 264 */ uint64 t4; // x29 + /* 272 */ uint64 t5; // x30 + /* 280 */ uint64 t6; // x31 +}; + +namespace Trap +{ + void init(); + void inithart(); + int devintr(); +} \ No newline at end of file diff --git a/lume-legacy/include/lib/errno.h b/lume-legacy/include/lib/errno.h new file mode 100644 index 0000000..f0c1436 --- /dev/null +++ b/lume-legacy/include/lib/errno.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Lume OS - Linux Compatibility Layer + * + * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). + * 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 +#define _ERRNO_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* 1-34: 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 */ + +/* * CRITICAL FIX: Missing definitions (35-37) from asm-generic + * Without these, your file system and locking code will fail compliance. + */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ + +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ + +#define EDEADLOCK EDEADLK + +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Can not access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + +extern __thread int errno; + + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/lume-legacy/include/lib/string.h b/lume-legacy/include/lib/string.h new file mode 100644 index 0000000..6a74942 --- /dev/null +++ b/lume-legacy/include/lib/string.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#pragma once +#include "common/types.h" + + +void *memset(void *dst, int c, uint n); +int memcmp(const void *v1, const void *v2, uint n); +void *memmove(void *dst, const void *src, uint n); +void *memcpy(void *dst, const void *src, uint n); + + +void strcpy(char *dst, const char *src); +int strlen(const char *s); +int strcmp(const char *s1, const char *s2); +int strcasecmp(const char *s1, const char *s2); \ No newline at end of file diff --git a/lume-legacy/include/ulib/assert.h b/lume-legacy/include/ulib/assert.h new file mode 100644 index 0000000..014be9b --- /dev/null +++ b/lume-legacy/include/ulib/assert.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _ASSERT_H +#define _ASSERT_H + +#ifdef NDEBUG +#define assert(ignore) ((void)0) +#else +#ifdef __cplusplus +extern "C" +{ +#endif + void __assert_fail(const char *expr, const char *file, int line, const char *func); +#ifdef __cplusplus +} +#endif + +#define assert(expr) \ + ((expr) ? (void)0 : __assert_fail(#expr, __FILE__, __LINE__, __func__)) +#endif + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/cstdlib b/lume-legacy/include/ulib/cstdlib new file mode 100644 index 0000000..8b07338 --- /dev/null +++ b/lume-legacy/include/ulib/cstdlib @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _CPP_CSTDLIB +#define _CPP_CSTDLIB + +#include "stdlib.h" + +namespace std +{ + using :: malloc; + using :: free; + using :: calloc; + using :: realloc; + + using :: exit; + using :: abort; + using :: atexit; + using :: getenv; + using :: system; + + using :: atoi; + using :: atol; + using :: atoll; + using :: atof; + + using :: strtol; + using :: strtoul; + using :: strtoll; + using :: strtoull; + + using :: strtod; + using :: strtof; + + using :: bsearch; + using :: qsort; + + using :: abs; + using :: labs; + using :: llabs; + using :: div; + using :: ldiv; + using :: lldiv; + + using :: rand; + using :: srand; + + using :: mblen; + using :: mbtowc; + using :: wctomb; + using :: mbstowcs; + using :: wcstombs; +} + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/cstring b/lume-legacy/include/ulib/cstring new file mode 100644 index 0000000..d65dc8d --- /dev/null +++ b/lume-legacy/include/ulib/cstring @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _CPP_CSTRING +#define _CPP_CSTRING + +#include "string.h" + +namespace std +{ + using::memchr; + using::memcmp; + using::memcpy; + using::memmove; + using::memset; + using::strcat; + using::strchr; + using::strcmp; + using::strcpy; + using::strcspn; + using::strdup; + using::strerror; + using::strlcat; + using::strlcpy; + using::strlen; + using::strncat; + using::strncmp; + using::strpbrk; + using::strrchr; + using::strspn; + using::strstr; + using::strtok; +} // namespace std + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/ctypes.h b/lume-legacy/include/ulib/ctypes.h new file mode 100644 index 0000000..1ec204e --- /dev/null +++ b/lume-legacy/include/ulib/ctypes.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _CTYPE_H +#define _CTYPE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + + static inline int isdigit(int c) { return c >= '0' && c <= '9'; } + static inline int islower(int c) { return c >= 'a' && c <= 'z'; } + static inline int isupper(int c) { return c >= 'A' && c <= 'Z'; } + static inline int isalpha(int c) { return islower(c) || isupper(c); } + static inline int isalnum(int c) { return isalpha(c) || isdigit(c); } + static inline int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } + static inline int isprint(int c) { return c >= 32 && c <= 126; } + static inline int iscntrl(int c) { return c < 32 || c == 127; } + static inline int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } + + static inline int tolower(int c) { return isupper(c) ? c + 32 : c; } + static inline int toupper(int c) { return islower(c) ? c - 32 : c; } + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/cxx.cc b/lume-legacy/include/ulib/cxx.cc new file mode 100644 index 0000000..f454fea --- /dev/null +++ b/lume-legacy/include/ulib/cxx.cc @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#include "ulib/stdlib.h" + + +void *operator new(unsigned long n) +{ + return malloc(n); +} + +void *operator new[](unsigned long n) +{ + return malloc(n); +} + +void operator delete(void *p) +{ + free(p); +} + +void operator delete[](void *p) +{ + free(p); +} + +void operator delete(void *p, unsigned long) +{ + free(p); +} + +void operator delete[](void *p, unsigned long) +{ + free(p); +} \ No newline at end of file diff --git a/lume-legacy/include/ulib/errno.h b/lume-legacy/include/ulib/errno.h new file mode 100644 index 0000000..dd51598 --- /dev/null +++ b/lume-legacy/include/ulib/errno.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Lume OS - Linux Compatibility Layer + * + * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). + * 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 +#define _ERRNO_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#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 */ +/* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ +#define ENOSYS 38 /* Invalid system call number */ + +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ + +#define EDEADLOCK EDEADLK + +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Can not access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ + +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ + +/* for robust mutexes */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ + +#define ERFKILL 132 /* Operation not possible due to RF-kill */ + +#define EHWPOISON 133 /* Memory page has hardware error */ + +extern int errno; +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/iostream b/lume-legacy/include/ulib/iostream new file mode 100644 index 0000000..b05f603 --- /dev/null +++ b/lume-legacy/include/ulib/iostream @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _IOSTREAM +#define _IOSTREAM + +#include "stdio.h" + +namespace std +{ + class ostream + { + private: + FILE* _stream; + public: + ostream(FILE* f) : _stream(f) {} + + ostream& operator<<(const char* str) + { + fprintf(_stream, "%s", str); + return *this; + } + + ostream& operator<<(int n) + { + fprintf(_stream, "%d", n); + return *this; + } + + ostream &operator<<(long n) + { + fprintf(_stream, "%ld", n); + return *this; + } + + ostream &operator<<(long long n) + { + fprintf(_stream, "%lld", n); + return *this; + } + + ostream &operator<<(double f) + { + fprintf(_stream, "%f", f); + return *this; + } + + ostream &operator<<(char c) + { + fputc(c, _stream); + return *this; + } + + ostream &operator<<(void *p) + { + fprintf(_stream, "%p", p); + return *this; + } + + ostream &operator<<(ostream &(*func)(ostream &)) + { + return func(*this); + } + + ostream &flush() + { + fflush(_stream); + return *this; + } + }; + + class istream{ + private: + FILE* _stream; + public: + istream(FILE *f) : _stream(f) {} + + istream& operator>>(int n) + { + fscanf(_stream, "%d", n); + return *this; + } + + istream &operator>>(double &f) + { + fscanf(_stream, "%lf", &f); + return *this; + } + + istream &operator>>(char *str) + { + fscanf(_stream, "%s", str); + return *this; + } + + istream &operator>>(char &c) + { + char tmp[2]; + fscanf(_stream, " %c", tmp); + c = tmp[0]; + return *this; + } + }; + + inline ostream &endl(ostream &os) + { + os << '\n'; + os.flush(); + return os; + } +} + +#endif \ No newline at end of file diff --git a/lume-legacy/include/ulib/limits.h b/lume-legacy/include/ulib/limits.h new file mode 100644 index 0000000..b317e6a --- /dev/null +++ b/lume-legacy/include/ulib/limits.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#pragma once + +#define CHAR_BIT 8 +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 +#define UCHAR_MAX 255 + +#define SHRT_MIN (-32768) +#define SHRT_MAX 32767 +#define USHRT_MAX 65535 + +#define INT_MIN (-2147483648) +#define INT_MAX 2147483647 +#define UINT_MAX 4294967295U + +#define LONG_MAX 9223372036854775807L +#define LONG_MIN (-LONG_MAX - 1L) +#define ULONG_MAX 18446744073709551615UL + +#define LLONG_MAX 9223372036854775807LL +#define LLONG_MIN (-LLONG_MAX - 1LL) +#define ULLONG_MAX 18446744073709551615ULL \ No newline at end of file diff --git a/lume-legacy/include/ulib/stb_sprintf.h b/lume-legacy/include/ulib/stb_sprintf.h new file mode 100644 index 0000000..00dc123 --- /dev/null +++ b/lume-legacy/include/ulib/stb_sprintf.h @@ -0,0 +1,1916 @@ +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef stbsp__uintptr +#if defined(__riscv) && (__riscv_xlen == 64) +#define stbsp__uintptr unsigned long long +#elif defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr unsigned long long +#else +#define stbsp__uintptr unsigned int +#endif +#endif + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff + #define stbsp__chk_cb_bufL(bytes) \ + { \ + int len = (int)(bf - buf); \ + if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ + tlen += len; \ + if (0 == (bf = buf = callback(buf, user, len))) \ + goto done; \ + } \ + } + #define stbsp__chk_cb_buf(bytes) \ + { \ + if (callback) { \ + stbsp__chk_cb_bufL(bytes); \ + } \ + } + #define stbsp__flush_cb() \ + { \ + stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ + } // flush if there is even one byte in the buffer + #define stbsp__cb_buf_clamp(cl, v) \ + cl = v; \ + if (callback) { \ + int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ + if (cl > lg) \ + cl = lg; \ + } + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; + #ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else + #endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { + #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); +// add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } +endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + +done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ + { \ + int cn; \ + for (cn = 0; cn < 8; cn++) \ + ((char *)&dest)[cn] = ((char *)&src)[cn]; \ + } + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ + { \ + double ahi = 0, alo, bhi = 0, blo; \ + stbsp__int64 bt; \ + oh = xh * yh; \ + STBSP__COPYFP(bt, xh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(ahi, bt); \ + alo = xh - ahi; \ + STBSP__COPYFP(bt, yh); \ + bt &= ((~(stbsp__uint64)0) << 27); \ + STBSP__COPYFP(bhi, bt); \ + blo = yh - bhi; \ + ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ + } + +#define stbsp__ddtoS64(ob, xh, xl) \ + { \ + double ahi = 0, alo, vh, t; \ + ob = (stbsp__int64)xh; \ + vh = (double)ob; \ + ahi = (xh - vh); \ + t = (ahi - xh); \ + alo = (xh - (ahi - t)) - (vh + t); \ + ob += (stbsp__int64)(ahi + alo + xl); \ + } + +#define stbsp__ddrenorm(oh, ol) \ + { \ + double s; \ + s = oh + ol; \ + ol = ol - (s - oh); \ + oh = s; \ + } + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/lume-legacy/include/ulib/stdio.h b/lume-legacy/include/ulib/stdio.h new file mode 100644 index 0000000..a6df21d --- /dev/null +++ b/lume-legacy/include/ulib/stdio.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +/* Standard I/O Library for Lume OS */ + +#ifndef _STDIO_H +#define _STDIO_H + +#include +#include "ulib/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*Type Definition (Adjusted According to Architecture)*/ +#ifndef NULL +#define NULL ((void *)0) +#endif + +/*Macro definition*/ +#define EOF (-1) + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#define BUFSIZ 1024 + + +/* FILE structure */ +typedef struct +{ + int fd; + int flags; + int error; + int eof; + + char buffer[BUFSIZ]; + + int buf_write_pos; + + int buf_read_pos; + int buf_read_cnt; + + int buf_mode; +} FILE; + +/* standard stream object */ +extern FILE *stdin; +extern FILE *stdout; +extern FILE *stderr; + +/* Function Declaration */ + +/* File Operations */ +FILE *fopen(const char *filename, const char *mode); +int fclose(FILE *stream); +int fflush(FILE *stream); // Not implement + +/* Formatted input and output */ +int printf(const char *format, ...); +int scanf(const char *format, ...); +int sprintf(char *str, const char *format, ...); +int snprintf(char *str, uint64 size, const char *format, ...); +int vsnprintf(char *str, uint64 size, const char *format, va_list ap); +int sscanf(const char *str, const char *format, ...); + +int fprintf(FILE *stream, const char *format, ...); +int fscanf(FILE *stream, const char *format, ...); + +/* Character/String Read and Write */ +int fputc(int c, FILE *stream); +int fputs(const char *s, FILE *stream); +int fgetc(FILE *stream); +char *fgets(char *str, int n, FILE *stream); +int ungetc(int c, FILE *stream); + +/* Common Macros/Wrapper Functions */ +#define getchar() fgetc(stdin) +#define putchar(c) fputc((c), stdout) + +/* Binary read and write */ +uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream); +uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream); + +/* File location */ +int fseek(FILE *stream, long offset, int whence); +long ftell(FILE *stream); +void rewind(FILE *stream); + +/* Status Inquiry */ +int feof(FILE *stream); +int ferror(FILE *stream); +void clearerr(FILE *stream); + +#ifdef __cplusplus +} +#endif +#endif /* _STDIO_H */ \ No newline at end of file diff --git a/lume-legacy/include/ulib/stdlib.h b/lume-legacy/include/ulib/stdlib.h new file mode 100644 index 0000000..6d8f75d --- /dev/null +++ b/lume-legacy/include/ulib/stdlib.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ + +#ifndef _STDLIB_H +#define _STDLIB_H + +#include "ulib/types.h" +#include "ulib/limits.h" +#include "ulib/errno.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define ERANGE 34 /* Math result not representable */ +#define ENOMEM 12 /* Out of memory */ + +#define EXIT_SUCCESS 0 +#define EXIT_FAILURE 1 +#define RAND_MAX 32767 +#define MB_CUR_MAX 1 + +typedef struct +{ + int quot; + int rem; +} div_t; +typedef struct +{ + long quot; + long rem; +} ldiv_t; +typedef struct +{ + long long quot; + long long rem; +} lldiv_t; + +typedef int (*__compar_fn_t)(const void *, const void *); + +/* Memory Management */ +void *malloc(size_t size); +void free(void *ptr); +void *calloc(size_t nmemb, size_t size); +void *realloc(void *ptr, size_t size); + +/* Process Control */ +int exit(int status); +void abort(void); +int atexit(void (*function)(void)); +char *getenv(const char *name); +int system(const char *command); + +/* String Conversion */ +int atoi(const char *nptr); +long atol(const char *nptr); +long long atoll(const char *nptr); +double atof(const char *nptr); + +long strtol(const char *nptr, char **endptr, int base); +unsigned long strtoul(const char *nptr, char **endptr, int base); +long long strtoll(const char *nptr, char **endptr, int base); +unsigned long long strtoull(const char *nptr, char **endptr, int base); + +double strtod(const char *nptr, char **endptr); +float strtof(const char *nptr, char **endptr); + +/* Search & Sort*/ +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); + +/* Math & Mics */ +int abs(int j); +long labs(long j); +long long llabs(long long j); +div_t div(int numer, int denom); +ldiv_t ldiv(long numer, long denom); +lldiv_t lldiv(long long numer, long long denom); + +int rand(void); +void srand(unsigned int seed); + +int mblen(const char *s, size_t n); +int mbtowc(wchar_t *pwc, const char *s, size_t n); +int wctomb(char *s, wchar_t wchar); +size_t mbstowcs(wchar_t *dest, const char *src, size_t n); +size_t wcstombs(char *dest, const wchar_t *src, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* _STDLIB_H */ \ No newline at end of file diff --git a/lume-legacy/include/ulib/string.h b/lume-legacy/include/ulib/string.h new file mode 100644 index 0000000..c7519ea --- /dev/null +++ b/lume-legacy/include/ulib/string.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#ifndef _STRING_H +#define _STRING_H + +#include "ulib/types.h" +#include "ulib/errno.h" + +#ifndef _SIZE_T +#define _SIZE_T +typedef unsigned long long size_t; +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + /* Memory Manipulation*/ + void *memcpy(void *dest, const void *src, size_t n); + + void *memmove(void *dest, const void *src, size_t n); + + void *memset(void *dest, int c, size_t n); + + int memcmp(const void *s1, const void *s2, size_t n); + + void *memchr(const void *s, int c, size_t n); + + /* String Manipulation */ + + size_t strlen(const char *s); + + char *strcpy(char *dest, const char *src); + + char *strncpy(char *dest, const char *src, size_t n); + + char *strcat(char *dest, const char *src); + + char *strncat(char *dest, const char *src, size_t n); + + int strcmp(const char *s1, const char *s2); + + int strncmp(const char *s1, const char *s2, size_t n); + + char *strchr(const char *s, int c); + + char *strrchr(const char *s, int c); + + char *strstr(const char *haystack, const char *needle); + + char *strdup(const char *s); + + size_t strlcpy(char *dest, const char *src, size_t size); + + size_t strlcat(char *dest, const char *src, size_t size); + + size_t strspn(const char *s, const char *accept); + + size_t strcspn(const char *s, const char *reject); + + char *strpbrk(const char *s, const char *accept); + + char *strtok(char *str, const char *delim); + + char *strerror(int errnum); + +#ifdef __cplusplus +} +#endif + +#endif /* _STRING_H */ \ No newline at end of file diff --git a/lume-legacy/include/ulib/types.h b/lume-legacy/include/ulib/types.h new file mode 100644 index 0000000..69ee233 --- /dev/null +++ b/lume-legacy/include/ulib/types.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#ifndef _ULIB_TYPES_H +#define _ULIB_TYPES_H + +#include "common/types.h" + +#ifndef _ULIB_ALIASES +#define _ULIB_ALIASES +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#ifndef __cplusplus +typedef int wchar_t; +#endif + +#endif \ No newline at end of file diff --git a/lume-legacy/include/user/user.h b/lume-legacy/include/user/user.h new file mode 100644 index 0000000..7af8bdd --- /dev/null +++ b/lume-legacy/include/user/user.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ +#pragma once + +typedef unsigned long long uint64; + +#define AT_FDCWD -100 + +#ifdef __cplusplus +extern "C" +{ +#endif + + // Raw Linux System Calls + int sys_openat(int dfd, const char *filename, int flags, int mode); + int sys_mknodat(int dfd, const char *filename, int mode, int dev); + int sys_mkdirat(int dfd, const char *path, int mode); + int sys_unlinkat(int dfd, const char *path, int flags); + int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); + int sys_chdir(const char *path); + int sys_dup(int oldfd); + int sys_fstat(int fd, void *statbuf); + int sys_read(int fd, void *buf, int count); + int sys_write(int fd, const void *buf, int count); + int sys_close(int fd); + int sys_pipe2(int pipefd[2], int flags); + int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); + int sys_execve(const char *path, char *const argv[], char *const envp[]); + int sys_wait4(int pid, int *status, int options, void *rusage); + int sys_exit(int status); + int sys_getpid(); + int sys_brk(int n); + int sys_kill(int pid, int sig); + int sys_nanosleep(void *req, void *rem); + int sys_reboot(int magic1, int magic2, int cmd, void *arg); + int sys_lseek(int fd, int offset, int whence); + + // Custom + void sys_putc(char c); + int sys_disk_test(); + + // POSIX Wrappers + int fork(); + int exit(int status); + int wait(uint64 status_addr); + int pipe(int p[]); + int write(int fd, const void *buf, int n); + int read(int fd, void *buf, int n); + int close(int fd); + int kill(int pid); + int exec(char *, char **); + int open(const char *, int); + int mknod(const char *, short, short); + int unlink(const char *); + int link(const char *, const char *); + int fstat(int fd, void *st); + int mkdir(const char *); + int chdir(const char *); + int dup(int fd); + int getpid(); + char *sbrk(int n); + int sleep(int ticks); + int uptime(); + int shutdown(); + int lseek(int fd, int offset, int whence); + + // Debug + void putc(char c); + int disk_test(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lume-legacy/linker.ld b/lume-legacy/linker.ld new file mode 100644 index 0000000..3a51718 --- /dev/null +++ b/lume-legacy/linker.ld @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +OUTPUT_ARCH( "riscv" ) +ENTRY( _start ) + +SECTIONS +{ + . = 0x80200000; + + /* Text Segment */ + .text : { + *(.text.entry) + *(.text .text.*) + . = ALIGN(0x1000); + PROVIDE(etext = .); + } + + /* Read Only Data Segment */ + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } + + /* Data Segment */ + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } + + /* BBS Segment */ + .bss : { + . = ALIGN(16); + PROVIDE(sbss = .); + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + PROVIDE(ebss = .); + } + + . = ALIGN(0x1000); + PROVIDE(end = .); + PROVIDE(kernel_end = .); +} \ No newline at end of file diff --git a/lume-legacy/src/boot/entry.S b/lume-legacy/src/boot/entry.S new file mode 100644 index 0000000..8910ddc --- /dev/null +++ b/lume-legacy/src/boot/entry.S @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text.entry +.global _start + +_start: + mv tp, a0 + + la t0, stack_base + li t1, 4096 + addi t2, a0, 1 + mul t1, t1, t2 + add sp, t0, t1 + + bnez a0, .L_jump_main + + la t0, sbss + la t1, ebss +.L_clear_bss: + bge t0, t1, .L_save_dtb + sd zero, 0(t0) + addi t0, t0, 8 + j .L_clear_bss + +.L_save_dtb: + la t0, g_dtb_addr + sd a1, 0(t0) + +.L_jump_main: + call kernel_main + +.L_park: + wfi + j .L_park + +.section .bss +.align 4 +.global stack_top +stack_base: + .space 4096 * 8 +stack_top: + +.align 8 +.global g_dtb_addr +g_dtb_addr: + .space 8 \ No newline at end of file diff --git a/lume-legacy/src/drivers/plic.cc b/lume-legacy/src/drivers/plic.cc new file mode 100644 index 0000000..a975c44 --- /dev/null +++ b/lume-legacy/src/drivers/plic.cc @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "drivers/plic.h" +#include "drivers/uart.h" +#include "kernel/fdt.h" + +// Helper Macros — all relative to PLIC base from FDT +#define PLIC_BASE (g_devices.plic.base_addr) +#define PLIC_PRIORITY (PLIC_BASE + 0x0) +#define PLIC_PENDING (PLIC_BASE + 0x1000) + +#define PLIC_SENABLE_ADDR(hart) (PLIC_BASE + 0x2080 + (hart) * 0x100) +#define PLIC_STHRESHOLD_ADDR(hart) (PLIC_BASE + 0x201000 + (hart) * 0x2000) +#define PLIC_SCLAIM_ADDR(hart) (PLIC_BASE + 0x201004 + (hart) * 0x2000) + +// Helper for Volatile Access +static inline volatile uint32 *REG32(uint64 addr) +{ + return reinterpret_cast(addr); +} + +namespace PLIC +{ + void init() + { + // Set priority for UART IRQ + if (g_devices.uart.irq > 0) + *REG32(PLIC_PRIORITY + g_devices.uart.irq * 4) = 1; + + // Set priority for all VirtIO IRQs + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (g_devices.virtio[i].irq > 0) + *REG32(PLIC_PRIORITY + g_devices.virtio[i].irq * 4) = 1; + } + } + + void inithart() + { + int hart = static_cast(r_tp()); + + // Build enable bitmask from discovered devices + uint32 enable = 0; + + if (g_devices.uart.irq > 0 && g_devices.uart.irq < 32) + enable |= (1 << g_devices.uart.irq); + + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (g_devices.virtio[i].irq > 0 && g_devices.virtio[i].irq < 32) + enable |= (1 << g_devices.virtio[i].irq); + } + + *REG32(PLIC_SENABLE_ADDR(hart)) = enable; + + // Set Priority Threshold to 0 (Allow all) + *REG32(PLIC_STHRESHOLD_ADDR(hart)) = 0; + + Drivers::uart_puts("[PLIC] Hart Init Done.\n"); + } + + int claim() + { + int hart = static_cast(r_tp()); + return *REG32(PLIC_SCLAIM_ADDR(hart)); + } + + void complete(int irq) + { + int hart = static_cast(r_tp()); + *REG32(PLIC_SCLAIM_ADDR(hart)) = static_cast(irq); + } +} \ No newline at end of file diff --git a/lume-legacy/src/drivers/uart.cc b/lume-legacy/src/drivers/uart.cc new file mode 100644 index 0000000..397a402 --- /dev/null +++ b/lume-legacy/src/drivers/uart.cc @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "drivers/uart.h" +#include "kernel/riscv.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +#include "kernel/mm.h" +#include "lib/string.h" + +namespace Drivers +{ + struct + { + Spinlock lock; + char buf[128]; // Line buffer + uint32 r; // Read pointer (position taken by the user) + uint32 w; // Write pointer (position confirmed by Enter) + uint32 e; // Editing cursor (current typing position) + } cons; + + // Circular buffer structure + struct + { + Spinlock lock; + char buf[UART_RX_BUF_SIZE]; + uint32 r; // read index + uint32 w; // write index + } uart_rx; + + Spinlock uart_tx_lock; + + void uart_init() + { + uart_rx.lock.init("uart_rx"); + cons.lock.init("console"); + uart_tx_lock.init("uart_tx"); + uart_rx.r = 0; + uart_rx.w = 0; + + // Disable interrupts and configure + *(volatile uint8 *)(uart_base() + IER) = 0x00; + + // Enter baud rate setting mode (LCR_DLAB) = 0x80; + *(volatile uint8 *)(uart_base() + LCR) = 0x80; + *(volatile uint8 *)(uart_base() + 0) = 0x03; // DLL: 38.4K baud + *(volatile uint8 *)(uart_base() + 1) = 0x00; // DLM + + // Exit baud rate setting, set 8-bit word length, no parity + *(volatile uint8 *)(uart_base() + LCR) = 0x03; + + // Reset and enable FIFO + *(volatile uint8 *)(uart_base() + FCR) = 0x07; + + // Enable Receive Data Available Interrupt + *(volatile uint8 *)(uart_base() + IER) = 0x01; + } + + void uart_putc(char c) + { + uart_tx_lock.acquire(); + while ((*(volatile uint8 *)(uart_base() + LSR) & LSR_TX_IDLE) == 0) + ; + *(volatile uint8 *)(uart_base() + THR) = c; + uart_tx_lock.release(); + } + + void uart_puts(const char *s) + { + while (*s) + uart_putc(*s++); + } + + void uart_put_int(int val) + { + char buf[16]; + int i = 0; + if (val == 0) + { + uart_puts("0"); + return; + } + if (val < 0) + { + uart_puts("-"); + val = -val; + } + while (val > 0) + { + buf[i++] = (val % 10) + '0'; + val /= 10; + } + while (i > 0) + { + uart_putc(buf[--i]); + } + } + + void print_hex(uint64 val) + { + char hex[] = "0123456789ABCDEF"; + uart_puts("0x"); + for (int i = 60; i >= 0; i -= 4) + { + uart_putc(hex[(val >> i) & 0xF]); + } + } + + void uart_intr() + { + uart_rx.lock.acquire(); + + while (1) + { + if ((*(volatile uint8 *)(uart_base() + LSR) & LSR_RX_READY) == 0) + break; + + char c = *(volatile uint8 *)(uart_base() + RHR); + + uart_rx.buf[uart_rx.w % UART_RX_BUF_SIZE] = c; + uart_rx.w++; + + ProcManager::wakeup(&uart_rx); + } + + uart_rx.lock.release(); + } + + // Modern console read: Line buffering + Editing Keys + Echo + int console_read(uint64 target, int len) + { + struct Proc *p = myproc(); + int target_idx = 0; + + cons.lock.acquire(); + + while (target_idx < len) + { + while (cons.r < cons.w) + { + char c = cons.buf[cons.r++ % sizeof(cons.buf)]; + cons.lock.release(); + + if (VM::copyout(p->pagetable, target + target_idx, &c, 1) < 0) + return -1; + + target_idx++; + if (c == '\n') + return target_idx; + if (target_idx == len) + return target_idx; + + cons.lock.acquire(); + } + + uart_rx.lock.acquire(); + while (uart_rx.r == uart_rx.w) + { + if (p->xstate) + { + uart_rx.lock.release(); + cons.lock.release(); + return -1; + } + cons.lock.release(); + ProcManager::sleep(&uart_rx, &uart_rx.lock); + cons.lock.acquire(); + } + + char c = uart_rx.buf[uart_rx.r++ % UART_RX_BUF_SIZE]; + uart_rx.lock.release(); + + if (c == 127 || c == '\b') + { + if (cons.e != cons.w) + { + cons.e--; + uart_putc('\b'); + uart_putc(' '); + uart_putc('\b'); + } + } + else + { + if (c == '\r') + c = '\n'; + + uart_putc(c); + + cons.buf[cons.e++ % sizeof(cons.buf)] = c; + + if (c == '\n' || cons.e == cons.w + sizeof(cons.buf)) + { + cons.w = cons.e; + ProcManager::wakeup(&cons); + } + } + } + + cons.lock.release(); + return target_idx; + } +} \ No newline at end of file diff --git a/lume-legacy/src/drivers/virtio_dist.cc b/lume-legacy/src/drivers/virtio_dist.cc new file mode 100644 index 0000000..8292874 --- /dev/null +++ b/lume-legacy/src/drivers/virtio_dist.cc @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "drivers/virtio.h" +#include "kernel/fdt.h" +#include "kernel/riscv.h" +#include "kernel/pmm.h" +#include "kernel/spinlock.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/proc.h" +#include "kernel/buf.h" + +static uint64 virtio_base = 0x10001000; + +// VirtIO Block Device Config Structure +struct virtio_blk_config +{ + uint64 capacity; + uint32 size_max; + uint32 seg_max; + struct virtio_blk_geometry + { + uint16 cylinders; + uint8 heads; + uint8 sectors; + } geometry; + uint32 blk_size; + uint8 physical_block_exp; + uint8 alignment_offset; + uint16 min_io_size; + uint32 opt_io_size; +} __attribute__((packed)); + +static inline volatile uint32 *REG(uint32 offset) +{ + return reinterpret_cast(virtio_base + offset); +} + +namespace VirtIO +{ + struct Disk + { + struct VRingDesc *desc; + struct VRingAvail *avail; + struct VRingUsed *used; + + int free_head; + int num_free; + int free_chain[NUM_DESCS]; + + uint16 used_idx; + + struct TraceInfo + { + int busy; + int status; + } info[NUM_DESCS]; + + struct BlockRequest reqs[NUM_DESCS]; + uint8 req_status[NUM_DESCS]; + + Spinlock lock; + } disk; + + void init() + { + disk.lock.init("virtio_disk"); + + // 1. Probe VirtIO devices discovered by FDT + bool found = false; + for (int i = 0; i < g_devices.virtio_count; i++) + { + uint64 addr = g_devices.virtio[i].base_addr; + if (addr == 0) + continue; + + volatile uint32 *magic = (volatile uint32 *)(addr + VIRTIO_MMIO_MAGIC_VALUE); + volatile uint32 *dev_id = (volatile uint32 *)(addr + VIRTIO_MMIO_DEVICE_ID); + + if (*magic == 0x74726976 && *dev_id == 2) // ID 2 = Block Device + { + virtio_base = addr; + found = true; + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Found Block Device @ "); + Drivers::print_hex(addr); + Drivers::uart_puts(ANSI_RESET "\n"); + break; + } + } + + if (!found) + { + Drivers::uart_puts(ANSI_RED "[VirtIO] Fatal - No Block Device found.\n" ANSI_RESET); + return; + } + + // 2. Check Device Version (We want Modern v2) + if (*REG(VIRTIO_MMIO_VERSION) != 2) + { + Drivers::uart_puts(ANSI_YELLOW "[VirtIO] Warning: Legacy device detected (expected v2).\n" ANSI_RESET); + } + + // 3. Reset Device + *REG(VIRTIO_MMIO_STATUS) = 0; + + // 4. Set ACKNOWLEDGE & DRIVER status + uint32 status = *REG(VIRTIO_MMIO_STATUS); + status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER; + *REG(VIRTIO_MMIO_STATUS) = status; + + // 5. Feature Negotiation + *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 0; + uint32 device_features_lo = *REG(VIRTIO_MMIO_DEVICE_FEATURES); + (void)device_features_lo; + + *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 1; + uint32 device_features_hi = *REG(VIRTIO_MMIO_DEVICE_FEATURES); + (void)device_features_hi; + + *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 0; + *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 0; + + *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 1; + // VIRTIO_F_VERSION_1 is usually bit 32. + // If not defined in header, we hardcode logic here: (1ULL << 0) for high bits + *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 1; + + status |= VIRTIO_CONFIG_S_FEATURES_OK; + *REG(VIRTIO_MMIO_STATUS) = status; + + if (!(*REG(VIRTIO_MMIO_STATUS) & VIRTIO_CONFIG_S_FEATURES_OK)) + { + Drivers::uart_puts("VirtIO: Feature negotiation failed.\n"); + return; + } + + // 6. Queue Configuration + *REG(VIRTIO_MMIO_QUEUE_SEL) = 0; + if (*REG(VIRTIO_MMIO_QUEUE_READY)) + { + Drivers::uart_puts("VirtIO: Queue 0 not ready.\n"); + } + + uint32 max_queue = *REG(VIRTIO_MMIO_QUEUE_NUM_MAX); + if (max_queue < NUM_DESCS) + { + Drivers::uart_puts("VirtIO: Queue size too small.\n"); + return; + } + + *REG(VIRTIO_MMIO_QUEUE_NUM) = NUM_DESCS; + + // Allocate Memory for VirtQueues + void *ptr = PMM::alloc_pages(1); // 4KB is enough for small rings + memset(ptr, 0, PGSIZE); + + uint64 base_addr = (uint64)ptr; + disk.desc = reinterpret_cast(base_addr); + disk.avail = reinterpret_cast(base_addr + NUM_DESCS * 16); + disk.used = reinterpret_cast(base_addr + PGSIZE / 2); + + *REG(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint32)base_addr; + *REG(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint32)(base_addr >> 32); + + uint64 avail_addr = (uint64)disk.avail; + *REG(VIRTIO_MMIO_QUEUE_DRIVER_LOW) = (uint32)avail_addr; + *REG(VIRTIO_MMIO_QUEUE_DRIVER_HIGH) = (uint32)(avail_addr >> 32); + + uint64 used_addr = (uint64)disk.used; + *REG(VIRTIO_MMIO_QUEUE_DEVICE_LOW) = (uint32)used_addr; + *REG(VIRTIO_MMIO_QUEUE_DEVICE_HIGH) = (uint32)(used_addr >> 32); + + *REG(VIRTIO_MMIO_QUEUE_READY) = 1; + + // 7. Initialize Internal Structures + disk.free_head = 0; + disk.num_free = NUM_DESCS; + disk.used_idx = 0; + for (int i = 0; i < NUM_DESCS; i++) + { + disk.free_chain[i] = i + 1; + disk.info[i].busy = 0; + } + + // 8. Finalize Setup + status |= VIRTIO_CONFIG_S_DRIVER_OK; + *REG(VIRTIO_MMIO_STATUS) = status; + + // === [Feature] Read Capacity === + volatile struct virtio_blk_config *config = (volatile struct virtio_blk_config *)(virtio_base + VIRTIO_MMIO_CONFIG); + uint64 capacity_sectors = config->capacity; + uint64 size_mb = (capacity_sectors * 512) / (1024 * 1024); + + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Initialized. Capacity: "); + Drivers::uart_put_int(size_mb); + Drivers::uart_puts(" MB\n" ANSI_RESET); + } + + static int alloc_desc() + { + if (disk.num_free == 0) + return -1; + int id = disk.free_head; + disk.free_head = disk.free_chain[id]; + disk.num_free--; + return id; + } + + static void free_desc(int id) + { + disk.free_chain[id] = disk.free_head; + disk.free_head = id; + disk.num_free++; + ProcManager::wakeup(&disk.free_head); + } + + static void free_chain_soft(int head_idx) + { + int curr = head_idx; + while (1) + { + int flag = disk.desc[curr].flags; + int next = disk.desc[curr].next; + free_desc(curr); + if (flag & VRING_DESC_F_NEXT) + curr = next; + else + break; + } + } + + void rw(int write, uint64 blockno, char *buf) + { + uint64 sector = blockno * (1024 / 512); // Assume 1KB blocks + + disk.lock.acquire(); + + // Wait for enough descriptors + while (disk.num_free < 3) + { + ProcManager::sleep(&disk.free_head, &disk.lock); + } + + int idx[3]; + idx[0] = alloc_desc(); + idx[1] = alloc_desc(); + idx[2] = alloc_desc(); + + // 1. Request Header + struct BlockRequest *req = &disk.reqs[idx[0]]; + req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN; + req->reserved = 0; + req->sector = sector; + + disk.desc[idx[0]].addr = (uint64)req; + disk.desc[idx[0]].len = sizeof(struct BlockRequest); + disk.desc[idx[0]].flags = VRING_DESC_F_NEXT; + disk.desc[idx[0]].next = idx[1]; + + // 2. Data Buffer + disk.desc[idx[1]].addr = (uint64)buf; + disk.desc[idx[1]].len = 1024; // Fixed 1KB for this test + disk.desc[idx[1]].flags = VRING_DESC_F_NEXT; + if (!write) + disk.desc[idx[1]].flags |= VRING_DESC_F_WRITE; + disk.desc[idx[1]].next = idx[2]; + + // 3. Status Byte + disk.desc[idx[2]].addr = (uint64)&disk.req_status[idx[0]]; + disk.desc[idx[2]].len = 1; + disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; + disk.desc[idx[2]].next = 0; + + disk.info[idx[0]].busy = 1; + + // Submit to Avail Ring + disk.avail->ring[disk.avail->idx % NUM_DESCS] = idx[0]; + __sync_synchronize(); + disk.avail->idx++; + __sync_synchronize(); + + // Notify Device + *REG(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; + + // Wait for Completion + while (disk.info[idx[0]].busy) + { + ProcManager::sleep(&disk.info[idx[0]], &disk.lock); + } + + free_chain_soft(idx[0]); + disk.lock.release(); + } + + void intr() + { + disk.lock.acquire(); + + uint32 status = *REG(VIRTIO_MMIO_INTERRUPT_STATUS); + *REG(VIRTIO_MMIO_INTERRUPT_ACK) = status & 0x3; + + volatile struct VRingUsed *used_ring = (volatile struct VRingUsed *)disk.used; + + while (disk.used_idx != used_ring->idx) + { + int id = used_ring->ring[disk.used_idx % NUM_DESCS].id; + + if (disk.info[id].busy) + { + disk.info[id].busy = 0; + ProcManager::wakeup(&disk.info[id]); + } + disk.used_idx++; + } + + disk.lock.release(); + } + + void test_rw() + { + Drivers::uart_puts(ANSI_BLUE "[VirtIO] Disk Test Start...\n" ANSI_RESET); + + char *buf = (char *)PMM::alloc_page(); + if (!buf) + return; + + const char *msg = "Lume OS Disk Driver Test Pattern"; + strcpy(buf, msg); + + rw(1, 0, buf); // Write + memset(buf, 0, PGSIZE); + rw(0, 0, buf); // Read + + if (strcmp(buf, msg) == 0) + { + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Test PASSED (R/W Verified).\n" ANSI_RESET); + } + else + { + Drivers::uart_puts(ANSI_RED "[VirtIO] Test FAILED.\n" ANSI_RESET); + } + + PMM::free_page(buf); + } + + void test_bio() + { + Drivers::uart_puts(ANSI_BLUE "[Bio] Starting Buffer Cache Test...\n" ANSI_RESET); + + struct Buf *b = BufferCache::bread(0, 100); + + Drivers::uart_puts("[Bio] Bread block 100 (1st time).\n"); + + b->data[0] = 'L'; + b->data[1] = 'u'; + b->data[2] = 'm'; + b->data[3] = 'e'; + b->data[4] = '\0'; + + BufferCache::bwrite(b); + Drivers::uart_puts("[Bio] Bwrite block 100.\n"); + + BufferCache::brelse(b); + Drivers::uart_puts("[Bio] Brelease block 100.\n"); + + struct Buf *b2 = BufferCache::bread(0, 100); + Drivers::uart_puts("[Bio] Bread block 100 (2nd time).\n"); + + if (b2->data[0] == 'L' && b2->data[1] == 'u' && b2->data[3] == 'e') + { + Drivers::uart_puts(ANSI_GREEN "[Bio] Data verification PASSED.\n" ANSI_RESET); + } + else + { + Drivers::uart_puts(ANSI_RED "[Bio] Data verification FAILED.\n" ANSI_RESET); + } + + BufferCache::brelse(b2); + } +} \ No newline at end of file diff --git a/lume-legacy/src/fs/fat32.cc b/lume-legacy/src/fs/fat32.cc new file mode 100644 index 0000000..e4089d4 --- /dev/null +++ b/lume-legacy/src/fs/fat32.cc @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/fat32.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/mm.h" +#include "kernel/proc.h" + +FAT32FileSystem fat32_fs; + +// Helper function +static void generate_short_name(char *dst, const char *src) +{ + memset(dst, ' ', 11); + + int i = 0; + for (; *src && *src != '.' && i < 8; src++) + { + char c = *src; + if (c >= 'a' && c <= 'z') + c -= 32; + dst[i++] = c; + } + + while (*src && *src != '.') + src++; + if (*src == '.') + src++; + + i = 8; + for (; *src && i < 11; src++) + { + char c = *src; + if (c >= 'a' && c <= 'z') + c -= 32; + dst[i++] = c; + } +} + +// FAT32 FileSystem Implementation + +void FAT32FileSystem::init() +{ + struct Buf *b = BufferCache::bread(0, 0); + uint8 *data = (uint8 *)b->data; + struct FAT32_BPB *bpb = (struct FAT32_BPB *)data; + + uint32 partition_lba = 0; + + bool is_valid_bpb = (bpb->bytesPerSector == 512) && (bpb->rootClus != 0); + + if (!is_valid_bpb) + { + Drivers::uart_puts("[FAT32] Sector 0 is not a valid BPB. Checking MBR...\n"); + + if (data[510] != 0x55 || data[511] != 0xAA) + { + Drivers::uart_puts("[FAT32] Panic: Invalid Disk Signature!\n"); + while (1) + ; + } + + uint32 lba = *(uint32 *)(data + 446 + 8); + + if (lba > 0) + { + Drivers::uart_puts("[FAT32] MBR Partition 1 found @ Sector "); + Drivers::uart_put_int(lba); + Drivers::uart_puts("\n"); + partition_lba = lba; + } + else + { + Drivers::uart_puts("[FAT32] Panic: No partition found.\n"); + while (1) + ; + } + + BufferCache::brelse(b); + + b = BufferCache::bread(0, partition_lba / 2); + bpb = (struct FAT32_BPB *)b->data; + } + + if (bpb->bytesPerSector != 512) + { + Drivers::uart_puts("[FAT32] Panic: Invalid FAT32 BPB in partition.\n"); + while (1) + ; + } + + uint32 fat_sz = bpb->FATSz32; + this->fat_sz = fat_sz; + uint32 reserved_sec = bpb->reservedSecCnt; + uint8 num_fats = bpb->numFATs; + + sectors_per_cluster = bpb->secPerClus; + bytes_per_cluster = sectors_per_cluster * 512; + root_cluster = bpb->rootClus; + + fat_start_sector = partition_lba + reserved_sec; + data_start_sector = partition_lba + reserved_sec + (fat_sz * num_fats); + + Drivers::uart_puts(ANSI_GREEN); + Drivers::uart_puts("[FAT32] Init Success: RootClus="); + Drivers::uart_put_int(root_cluster); + Drivers::uart_puts(", DataStart="); + Drivers::uart_put_int(data_start_sector); + Drivers::uart_puts(ANSI_RESET); + Drivers::uart_puts("\n"); + + BufferCache::brelse(b); +} + +Inode *FAT32FileSystem::root() +{ + FAT32Inode *ip = new FAT32Inode(); + + ip->fs = this; + ip->dev = 0; + ip->inum = root_cluster; + ip->ref_cnt = 1; + ip->valid = 1; + ip->type = T_DIR; + ip->size = 0; + ip->nlink = 1; + + ip->first_clus = root_cluster; + ip->parent_clus = 0; + + return ip; +} + +uint32 FAT32FileSystem::cluster_to_sector(uint32 cluster) +{ + return data_start_sector + (cluster - 2) * sectors_per_cluster; +} + +uint32 FAT32FileSystem::next_cluster(uint32 cluster) +{ + uint32 fat_offset = cluster * 4; + uint32 fat_sector = fat_start_sector + (fat_offset / 512); + uint32 ent_offset = fat_offset % 512; + + // block = sector / 2 + struct Buf *b = BufferCache::bread(0, fat_sector / 2); + + uint32 block_offset = (fat_sector % 2) * 512 + ent_offset; + uint32 next = *(uint32 *)(b->data + block_offset); + + BufferCache::brelse(b); + + return next & 0x0FFFFFFF; +} + +// FAT32 Inode Implementation + +static bool match_name(const char *disk_name, const char *target) +{ + char temp[13]; + int i, j; + + for (i = 0; i < 8 && disk_name[i] != ' '; i++) + temp[i] = disk_name[i]; + if (disk_name[8] != ' ') + { + temp[i++] = '.'; + for (j = 0; j < 3 && disk_name[8 + j] != ' '; j++) + temp[i++] = disk_name[8 + j]; + } + temp[i] = 0; + return strcasecmp(temp, target) == 0; +} + +int FAT32Inode::read(char *dst, uint32 off, uint32 n, int isUser) +{ + if (first_clus == 0) + return 0; + + // [FIX] 只有非目录文件才检查 size 边界 + // 目录文件 size 可能为 0,应允许读取直到簇链结束 + if (type != T_DIR) + { + if (off >= size) + { + return 0; + } + if (off + n > size) + { + n = size - off; + } + } + + uint32 tot = 0; + uint32 clus = first_clus; + uint32 clus_sz = fs->bytes_per_cluster; + + uint32 skip = off / clus_sz; + uint32 current_off = off % clus_sz; + + while (skip > 0) + { + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + return 0; + skip--; + } + + while (tot < n) + { + uint32 sec_in_clus = current_off / 512; + + if (sec_in_clus >= fs->sectors_per_cluster) + { + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + break; + current_off = 0; + continue; + } + + uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; + uint32 off_in_sec = current_off % 512; + + uint32 m = 512 - off_in_sec; + if (n - tot < m) + m = n - tot; + + struct Buf *b = BufferCache::bread(dev, sec / 2); + uint32 block_offset = (sec % 2) * 512 + off_in_sec; + + if (isUser) + { + if (VM::copyout(myproc()->pagetable, (uint64)dst, (char *)(b->data + block_offset), m) < 0) + { + BufferCache::brelse(b); + return -1; + } + } + else + { + memmove(dst, b->data + block_offset, m); + } + BufferCache::brelse(b); + + tot += m; + dst += m; + current_off += m; + } + return tot; +} + +Inode *FAT32Inode::lookup(const char *name) +{ + if (type != T_DIR) + return nullptr; + + uint32 clus = first_clus; + struct FAT32_DirEnt de; + + while (clus < 0x0FFFFFF8) + { + uint32 sec = fs->cluster_to_sector(clus); + + for (uint32 i = 0; i < fs->sectors_per_cluster; i++) + { + uint32 current_sec = sec + i; + struct Buf *b = BufferCache::bread(dev, current_sec / 2); + uint32 block_base = (current_sec % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + memmove(&de, b->data + block_base + off, 32); + + if (de.name[0] == 0x00) + { + BufferCache::brelse(b); + return nullptr; + } + if (de.name[0] == 0xE5) + continue; + if (de.attr & ATTR_LONG_NAME) + continue; + + if (match_name(de.name, name)) + { + FAT32Inode *ip = new FAT32Inode(); + ip->fs = fs; + ip->dev = dev; + ip->type = (de.attr & ATTR_DIRECTORY) ? T_DIR : T_FILE; + ip->size = de.fileSize; + ip->first_clus = (de.fstClusHI << 16) | de.fstClusLO; + ip->inum = ip->first_clus; + ip->nlink = 1; + ip->ref_cnt = 1; + ip->valid = 1; + ip->ent_sec = current_sec; // Log Sector + ip->ent_off = off; // Log Setoff + + BufferCache::brelse(b); + return ip; + } + } + BufferCache::brelse(b); + } + clus = fs->next_cluster(clus); + } + return nullptr; +} + +// Placeholder +uint32 FAT32FileSystem::alloc_cluster() +{ + uint32 fat_entries_per_sector = 512 / 4; + for (uint32 i = 0; i < fat_sz; i++) + { + uint32 current_fat_sec = fat_start_sector + i; + struct Buf *b = BufferCache::bread(0, current_fat_sec / 2); + uint32 *table = (uint32 *)(b->data + (current_fat_sec % 2) * 512); + for (uint32 j = 0; j < fat_entries_per_sector; j++) + { + if (i == 0 && j < 2) + continue; + + if ((table[j] & 0x0FFFFFFF) == 0) + { + // Find the free cluster and mark it as EOF (0x0FFFFFFF) + table[j] = 0x0FFFFFFF; + BufferCache::bwrite(b); // Write back to disk + BufferCache::brelse(b); + + uint32 cluster_idx = i * fat_entries_per_sector + j; + + // Clear the data content of the new cluster + uint32 data_sec = cluster_to_sector(cluster_idx); + for (uint32 k = 0; k < sectors_per_cluster; k++) + { + struct Buf *db = BufferCache::bread(0, (data_sec + k) / 2); + memset(db->data + ((data_sec + k) % 2) * 512, 0, 512); + BufferCache::bwrite(db); + BufferCache::brelse(db); + } + + return cluster_idx; + } + } + BufferCache::brelse(b); + } + Drivers::uart_puts("[FAT32] Out of space!\n"); + return 0; +} + +void FAT32FileSystem::free_cluster_chain(uint32 cluster) +{ + while (cluster >= 2 && cluster < 0x0FFFFFF8) + { + uint32 next = next_cluster(cluster); + + // Calculate the current cluster's position in the FAT table + uint32 fat_offset = cluster * 4; + uint32 fat_sector = fat_start_sector + (fat_offset / 512); + uint32 ent_offset = fat_offset % 512; + + // Read FAT sector + struct Buf *b = BufferCache::bread(0, fat_sector / 2); + + // Marked as idle (0x00000000) + uint32 *entry = (uint32 *)(b->data + (fat_sector % 2) * 512 + ent_offset); + *entry = 0; + + BufferCache::bwrite(b); + BufferCache::brelse(b); + + cluster = next; + } +} + +void FAT32Inode::update() +{ + struct Buf *b = BufferCache::bread(dev, ent_sec / 2); + uint32 offset = (ent_sec % 2) * 512 + ent_off; + + struct FAT32_DirEnt *de = (struct FAT32_DirEnt *)(b->data + offset); + + // Update file size and starting cluster + de->fileSize = size; + de->fstClusHI = (uint16)((first_clus >> 16) & 0xFFFF); + de->fstClusLO = (uint16)(first_clus & 0xFFFF); + + BufferCache::bwrite(b); + BufferCache::brelse(b); +} + +int FAT32Inode::write(const char *src, uint32 off, uint32 n, int isUser) +{ + if (off > size) + { + return -1; + } + + uint32 tot = 0; + uint32 clus = first_clus; + uint32 clus_sz = fs->bytes_per_cluster; + uint32 prev_clus = 0; + + if (clus == 0) + { + clus = fs->alloc_cluster(); + if (clus == 0) + return -1; + first_clus = clus; + // Update the metadata immediately to prevent data loss in case of a power outage. + update(); + } + + uint32 skip = off / clus_sz; + uint32 current_off = off % clus_sz; + + while (skip > 0) + { + prev_clus = clus; + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + { + return -1; + } + skip--; + } + while (tot < n) + { + if (current_off == clus_sz) + { + uint32 next = fs->next_cluster(clus); + if (next >= 0x0FFFFFF8) + { + uint32 new_clus = fs->alloc_cluster(); + if (new_clus == 0) + break; + + // Point the current cluster to the new cluster (update the FAT table) + uint32 fat_offset = clus * 4; + uint32 fat_sec = fs->fat_start_sector + (fat_offset / 512); + struct Buf *fat_b = BufferCache::bread(dev, fat_sec / 2); + uint32 *entry = (uint32 *)(fat_b->data + (fat_sec % 2) * 512 + (fat_offset % 512)); + *entry = new_clus; // Link + BufferCache::bwrite(fat_b); + BufferCache::brelse(fat_b); + + clus = new_clus; + } + else + { + clus = next; + } + current_off = 0; + } + + // Calculate the physical sector to write + uint32 sec_in_clus = current_off / 512; + uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; + uint32 off_in_sec = current_off % 512; + + // Calculate how many bytes can be written this time + uint32 m = 512 - off_in_sec; + if (n - tot < m) + m = n - tot; + + struct Buf *b = BufferCache::bread(dev, sec / 2); + uint32 block_offset = (sec % 2) * 512 + off_in_sec; + + if (isUser) + { + if (VM::copyin(myproc()->pagetable, (char *)(b->data + block_offset), (uint64)src, m) < 0) + { + BufferCache::brelse(b); + return -1; + } + } + else + { + memmove(b->data + block_offset, src, m); + } + + BufferCache::bwrite(b); + BufferCache::brelse(b); + + tot += m; + src += m; + current_off += m; + } + if (off + n > size) + { + size = off + n; + update(); + } + + return tot; +} + +Inode *FAT32Inode::create(const char *name, short type, short major, short minor) +{ + Inode *exist = lookup(name); + if (exist) + { + return exist; + } + + struct FAT32_DirEnt de; + memset(&de, 0, sizeof(de)); + generate_short_name(de.name, name); + de.attr = (type == T_DIR) ? ATTR_DIRECTORY : ATTR_ARCHIVE; + + uint32 new_dir_clus = 0; + if (type == T_DIR) + { + new_dir_clus = fs->alloc_cluster(); + if (new_dir_clus == 0) + return nullptr; + + de.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); + de.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); + } + else + { + de.fileSize = 0; + } + + uint32 clus = first_clus; + bool written = false; + + while (true) + { + uint32 sec = fs->cluster_to_sector(clus); + + for (uint32 i = 0; i < fs->sectors_per_cluster; i++) + { + struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); + uint8 *data = (uint8 *)b->data + ((sec + i) % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + if (data[off] == 0x00 || data[off] == 0xE5) + { + memmove(data + off, &de, 32); + BufferCache::bwrite(b); + BufferCache::brelse(b); + written = true; + break; + } + } + + if (written) + break; + BufferCache::brelse(b); + } + + if (written) + break; + + uint32 next = fs->next_cluster(clus); + if (next >= 0x0FFFFFF8) + { + uint32 new_parent_clus = fs->alloc_cluster(); + if (new_parent_clus == 0) + return nullptr; + + uint32 fat_off = clus * 4; + uint32 fat_sec = fs->fat_start_sector + (fat_off / 512); + struct Buf *fb = BufferCache::bread(dev, fat_sec / 2); + *(uint32 *)(fb->data + (fat_sec % 2) * 512 + (fat_off % 512)) = new_parent_clus; + BufferCache::bwrite(fb); + BufferCache::brelse(fb); + + uint32 s = fs->cluster_to_sector(new_parent_clus); + for (uint32 k = 0; k < fs->sectors_per_cluster; k++) + { + struct Buf *zb = BufferCache::bread(dev, (s + k) / 2); + uint8 *zdata = (uint8 *)zb->data + ((s + k) % 2) * 512; + memset(zdata, 0, 512); + + if (k == 0) + { + memmove(zdata, &de, 32); + } + + BufferCache::bwrite(zb); + BufferCache::brelse(zb); + } + + written = true; + break; + } + clus = next; + } + + if (written && type == T_DIR) + { + struct FAT32_DirEnt dot; + memset(&dot, 0, 32); + memset(dot.name, ' ', 11); + dot.name[0] = '.'; + dot.attr = ATTR_DIRECTORY; + dot.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); + dot.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); + + struct FAT32_DirEnt dotdot; + memset(&dotdot, 0, 32); + memset(dotdot.name, ' ', 11); + dotdot.name[0] = '.'; + dotdot.name[1] = '.'; + dotdot.attr = ATTR_DIRECTORY; + + uint32 parent_clus_num = this->first_clus; + if (parent_clus_num == fs->root_cluster) + { + parent_clus_num = 0; + } + dotdot.fstClusHI = (uint16)((parent_clus_num >> 16) & 0xFFFF); + dotdot.fstClusLO = (uint16)(parent_clus_num & 0xFFFF); + + uint32 s = fs->cluster_to_sector(new_dir_clus); + + struct Buf *zb = BufferCache::bread(dev, s / 2); + uint8 *zdata = (uint8 *)zb->data + (s % 2) * 512; + + memmove(zdata, &dot, 32); // Offset 0 + memmove(zdata + 32, &dotdot, 32); // Offset 32 + + BufferCache::bwrite(zb); + BufferCache::brelse(zb); + } + + if (written) + { + return lookup(name); + } + + return nullptr; +} + +void FAT32Inode::truncate() +{ + if (first_clus == 0) + { + if (size != 0) + { + size = 0; + update(); + } + return; + } + + fs->free_cluster_chain(first_clus); + + size = 0; + first_clus = 0; + + update(); +} + +int FAT32Inode::unlink(const char *name) +{ + if (type != T_DIR) + return -1; + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + return -1; + + uint32_t clus = first_clus; + bool found = false; + + // [FIX] Deadlock prevention: Do not hold buffer while looking up inode + uint32_t target_block = 0; + uint32_t target_offset = 0; + struct FAT32_DirEnt de; + + while (true) + { + uint32_t sec = fs->cluster_to_sector(clus); + for (uint32 i = 0; i < fs->sectors_per_cluster; ++i) + { + struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); + uint8_t *data = (uint8_t *)b->data + ((sec + i) % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + memmove(&de, data + off, 32); + if (de.name[0] == 0x00) // end of dir + { + BufferCache::brelse(b); + return -1; // Not found + } + if (de.name[0] == 0xE5) + continue; // have been unlink + if (de.attr & ATTR_LONG_NAME) + continue; + + if (match_name(de.name, name)) + { + target_block = (sec + i) / 2; + target_offset = ((sec + i) % 2) * 512 + off; + found = true; + break; + } + } + + BufferCache::brelse(b); // Always release buffer + if (found) + break; + } + + if (found) + break; + + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + return -1; + } + + if (found) + { + Inode *target_ip = lookup(name); + if (target_ip) + { + VFS::ilock(target_ip); + if (target_ip->type == T_DIR) + { + VFS::iunlockput(target_ip); + return -1; + } + + target_ip->nlink--; + target_ip->update(); + VFS::iunlockput(target_ip); + } + + // Re-read buffer to mark deletion + struct Buf *target_buf = BufferCache::bread(dev, target_block); + uint8_t *ptr = (uint8_t *)target_buf->data + target_offset; + *ptr = 0xE5; + + BufferCache::bwrite(target_buf); + BufferCache::brelse(target_buf); + return 0; + } + + return -1; +} + +// Test Function +void fat32_test() +{ + static bool inited = false; + if (!inited) + { + fat32_fs.init(); + inited = true; + } + + Drivers::uart_puts("\n[FAT32] Starting Test via Syscall...\n"); + + Inode *root = fat32_fs.root(); + if (root) + { + Drivers::uart_puts("[FAT32] Root Inode retrieved. Inum: "); + Drivers::uart_put_int(root->inum); + Drivers::uart_puts("\n"); + + Drivers::uart_puts("[FAT32] Looking for TEST.TXT...\n"); + Inode *node = root->lookup("test.txt"); + + if (node) + { + Drivers::uart_puts("[FAT32] Found TEST.TXT! Size: "); + Drivers::uart_put_int(node->size); + Drivers::uart_puts("\n"); + + char buf[128]; + int n = node->read(buf, 0, 127, 0); + if (n > 0) + { + buf[n] = 0; + Drivers::uart_puts("[FAT32] Content:\n>> "); + Drivers::uart_puts(buf); + Drivers::uart_puts("\n"); + } + else + { + Drivers::uart_puts("[FAT32] Read returned 0.\n"); + } + VFS::iput(node); + } + else + { + Drivers::uart_puts("[FAT32] TEST.TXT not found.\n"); + } + VFS::iput(root); + } + else + { + Drivers::uart_puts("[FAT32] Failed to get root inode.\n"); + } + + Drivers::uart_puts("[FAT32] Test Finished.\n"); +} \ No newline at end of file diff --git a/lume-legacy/src/fs/file.cc b/lume-legacy/src/fs/file.cc new file mode 100644 index 0000000..d814332 --- /dev/null +++ b/lume-legacy/src/fs/file.cc @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/file.h" +#include "kernel/spinlock.h" +#include "kernel/mm.h" +#include "drivers/uart.h" +#include "kernel/proc.h" +#include "kernel/slab.h" + +#define NFILE 100 +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +extern void *operator new(unsigned long, void *p); + +int Pipe::create_pair(file** f0, file** f1) +{ + void *mem = Slab::kmalloc(sizeof(Pipe)); + if (!mem) + return -1; + Pipe *pi = new (mem) Pipe(); + + *f0 = FileTable::alloc(); + *f1 = FileTable::alloc(); + + if (*f0 == nullptr || *f1 == nullptr) + { + if (*f0) + FileTable::close(*f0); + if (*f1) + FileTable::close(*f1); + pi->~Pipe(); + Slab::kfree(pi); + return -1; + } + + (*f0)->type = FD_PIPE; + (*f0)->readable = 1; + (*f0)->writable = 0; + (*f0)->pipe = pi; + + (*f1)->type = FD_PIPE; + (*f1)->readable = 0; + (*f1)->writable = 1; + (*f1)->pipe = pi; + + return 0; +} + +void Pipe::close(bool is_writer) +{ + lock.acquire(); + if (is_writer) + { + write_open = false; + ProcManager::wakeup(&nread); // Wake up the process that is currently reading + } + else + { + read_open = false; + ProcManager::wakeup(&nwrite); // Wake up the process that is writing + } + + bool empty = !read_open && !write_open; + lock.release(); + + if (empty) + { + this->~Pipe(); + Slab::kfree(this); + } +} + +int Pipe::write(uint64_t addr, int n) +{ + int i = 0; + struct Proc *p = myproc(); + + lock.acquire(); + while (i < n) + { + if (!read_open || p->xstate) // The read end is closed or the process is abnormal + { + lock.release(); + return -1; + } + + if (nwrite == nread + SIZE) // buffer is full + { + ProcManager::wakeup(&nread); + ProcManager::sleep(&nwrite, &lock); + } + else + { + char ch; + if (VM::copyin(p->pagetable, &ch, addr + i, 1) < 0) + break; + data[nwrite++ % SIZE] = ch; + i++; + } + } + ProcManager::wakeup(&nread); + lock.release(); + return i; +} + +int Pipe::read(uint64_t addr, int n) +{ + int i = 0; + struct Proc *p = myproc(); + + lock.acquire(); + while (nread == nwrite && write_open) // The buffer is empty and the write end is not closed + { + if (p->xstate) + { + lock.release(); + return -1; + } + ProcManager::sleep(&nread, &lock); + } + + for (i = 0; i < n; ++i) + { + if (nread == nwrite) + break; + char ch = data[nread++ % SIZE]; + if (VM::copyout(p->pagetable, addr + i, &ch, 1) < 0) + break; + } + ProcManager::wakeup(&nwrite); + lock.release(); + return i; +} + +namespace FileTable +{ + struct + { + Spinlock lock; + struct file files[NFILE]; + } ftable; + + void init() + { + ftable.lock.init("file_table"); + } + + struct file *alloc() + { + struct file *f; + ftable.lock.acquire(); + for (f = ftable.files; f < ftable.files + NFILE; f++) + { + if (f->ref == 0) + { + f->ref = 1; + ftable.lock.release(); + return f; + } + } + ftable.lock.release(); + return nullptr; + } + + struct file *dup(struct file *f) + { + ftable.lock.acquire(); + if (f->ref < 1) + { + Drivers::uart_puts("panic: file dup\n"); + while (1) + ; + } + f->ref++; + ftable.lock.release(); + return f; + } + + void close(struct file *f) + { + struct file ff; + + ftable.lock.acquire(); + if(f->ref < 1) + { + Drivers::uart_puts("panic: file close\n"); + while (1) + ; + } + f->ref--; + if (f->ref > 0) + { + ftable.lock.release(); + return; + } + + ff = *f; + f->ref = 0; + f->type = FD_NONE; + ftable.lock.release(); + if (ff.type == FD_INODE) + { + // Release the corresponding Inode + VFS::iput(ff.ip); + } + else if (ff.type == FD_PIPE) + { + if (ff.pipe) + ff.pipe->close(ff.writable); + } + } + + int read(struct file *f, uint64 addr, int n) + { + if (f->readable == 0) + return -1; + if (f->type == FD_INODE) + { + VFS::ilock(f->ip); + + if (f->ip->type == T_DEVICE) + { + if (f->ip->major == 1) + { + VFS::iunlock(f->ip); + return Drivers::console_read(addr, n); + } + } + + int r = f->ip->read((char*)addr, f->off, n, 1); + if(r > 0) + { + f->off += r; + } + VFS::iunlock(f->ip); + return r; + } + else if (f->type == FD_PIPE) + { + if (f->pipe) + return f->pipe->read(addr, n); + } + return -1; + } + + int write(struct file *f, uint64 addr, int n) + { + if (f->writable == 0) + return -1; + if (f->type == FD_INODE) + { + VFS::ilock(f->ip); + + if (f->ip->type == T_DEVICE) + { + if (f->ip->major == 1) // Console + { + VFS::iunlock(f->ip); + struct Proc *proc = myproc(); + constexpr int MAX_WRITE_BUF = 128; + char buf[MAX_WRITE_BUF]; + int i = 0; + while (i < n) + { + int len = n - i; + if (len > MAX_WRITE_BUF) + len = MAX_WRITE_BUF; + + if (VM::copyin(proc->pagetable, buf, addr + i, static_cast(len)) < 0) + { + return -1; + } + + for (int j = 0; j < len; j++) + { + Drivers::uart_putc(buf[j]); + } + i += len; + } + return n; + } + } + + int r = f->ip->write((char *)addr, f->off, n, 1); + if (r > 0) + { + f->off += r; + } + VFS::iunlock(f->ip); + return r; + } + else if (f->type == FD_PIPE) + { + if (f->pipe) + return f->pipe->write(addr, n); + } + + return -1; + } + + int stat(struct file *f, uint64 addr) + { + if (f->type == FD_INODE) + { + struct kstat st; + VFS::ilock(f->ip); + f->ip->getattr(&st); + VFS::iunlock(f->ip); + + struct Proc *p = myproc(); + if (VM::copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) + return -1; + return 0; + } + return -1; + } + + int lseek(struct file *f, int offset, int whence) + { + if (f->type != FD_INODE) + return -1; + + VFS::ilock(f->ip); + + int new_off = f->off; + int size = f->ip->size; + + switch (whence) + { + case SEEK_SET: + new_off = offset; + break; + case SEEK_CUR: + new_off += offset; + break; + case SEEK_END: + new_off = size + offset; + break; + default: + VFS::iunlock(f->ip); + return -1; + } + + if (new_off < 0) + { + VFS::iunlock(f->ip); + return -1; + } + + f->off = new_off; + VFS::iunlock(f->ip); + return new_off; + } +} // namespace FileTable diff --git a/lume-legacy/src/fs/fs.cc b/lume-legacy/src/fs/fs.cc new file mode 100644 index 0000000..fc8f555 --- /dev/null +++ b/lume-legacy/src/fs/fs.cc @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/fs.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +// #include "fs/fat32.h" // Decoupled +#include "lib/string.h" + +static Inode *global_root_inode = nullptr; + +static const char *skipelem(const char *path, char *name) +{ + const char *s; + int len; + + while (*path == '/') + path++; + if (*path == 0) + return nullptr; + + s = path; + while (*path != '/' && *path != 0) + path++; + + len = path - s; + if (len >= 128) + len = 127; + + memmove(name, s, len); + name[len] = 0; + + while (*path == '/') + path++; + return path; +} + +void Inode::getattr(struct kstat *st) +{ + st->dev = dev; + st->ino = inum; + st->type = type; + st->nlink = nlink; + st->size = size; + + st->uid = 0; + st->gid = 0; + st->mode = 0; +} + +namespace VFS +{ + // File System Registry + static FileSystem *filesystems[8]; + static int fs_count = 0; + + void register_fs(FileSystem *fs) + { + if (fs_count < 8) + { + filesystems[fs_count++] = fs; + } + } + + void init() + { + Drivers::uart_puts("[VFS] Inode subsystem initialized.\n"); + } + + void iput(Inode *ip) + { + if (ip == nullptr) + return; + + ip->lock.acquire(); + if(ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: iput ref\n"); + while (1) + ; + } + ip->ref_cnt--; + int ref = ip->ref_cnt; + ip->lock.release(); + + if (ref == 0) + { + delete ip; + } + } + + Inode* idup(Inode *ip) + { + if (ip == nullptr) + return nullptr; + + ip->lock.acquire(); + ip->ref_cnt++; + ip->lock.release(); + return ip; + } + + void ilock(Inode *ip) + { + if (ip == 0 || ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: ilock\n"); + while (1) + ; + } + + ip->lock.acquire(); + + } + + void iunlock(Inode *ip) + { + if (ip == 0 || !ip->lock.holding() || ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: iunlock\n"); + while (1) + ; + } + ip->lock.release(); + } + + void iunlockput(Inode *ip) + { + if (!ip) + return; + iunlock(ip); + iput(ip); + } + + void mount_root(Inode *root) + { + if (global_root_inode) + { + Drivers::uart_puts("VFS: Root already mounted!\n"); + return; + } + global_root_inode = root; + Drivers::uart_puts("[VFS] Root filesystem mounted.\n"); + } + + Inode *get_root() + { + if (global_root_inode == nullptr) + { + // Try to mount the first registered FS + if (fs_count > 0) + { + Drivers::uart_puts("[VFS] Lazy init: Mounting root FS...\n"); + filesystems[0]->init(); + mount_root(filesystems[0]->root()); + } + else + { + Drivers::uart_puts("[VFS] No filesystem registered!\n"); + } + } + if (!global_root_inode) + return nullptr; + return idup(global_root_inode); + } + + Inode *namei(const char *path) + { + char name[128]; + Inode *ip, *next; + struct Proc *p = myproc(); + + // 1. 确定起点 + if (*path == '/') + { + ip = get_root(); + } + else + { + if (p->cwd == nullptr) + { + ip = get_root(); + } + else + { + ip = idup(p->cwd); + } + } + + if (ip == nullptr) + return nullptr; + + while ((path = skipelem(path, name)) != nullptr) + { + if (strcmp(name, ".") == 0) + { + continue; + } + + if (strcmp(name, "..") == 0) + { + Inode *root = get_root(); + bool is_root = (ip->inum == root->inum); + iput(root); + + if (is_root) + { + continue; + } + } + + ilock(ip); + + if (ip->type != T_DIR) + { + iunlockput(ip); + return nullptr; + } + + next = ip->lookup(name); + iunlockput(ip); + + ip = next; + if (ip == nullptr) + return nullptr; + } + + return ip; + } + + Inode *nameiparent(const char* path, char* name) + { + Inode* ip; + Inode* next; + + if (*path == '/') + ip = get_root(); + else + ip = idup(myproc()->cwd ? myproc()->cwd : get_root()); + + while ((path = skipelem(path, name)) != nullptr) + { + if (*path == 0) + { + return ip; + } + + ilock(ip); + if (ip->type != T_DIR) + { + iunlockput(ip); + return nullptr; + } + + next = ip->lookup(name); + iunlockput(ip); + + ip = next; + if (!ip) + return nullptr; + } + iput(ip); + return nullptr; + } +} // namespace VFS diff --git a/lume-legacy/src/kernel/bio.cc b/lume-legacy/src/kernel/bio.cc new file mode 100644 index 0000000..5f1aae9 --- /dev/null +++ b/lume-legacy/src/kernel/bio.cc @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/buf.h" +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "drivers/virtio.h" +#include "drivers/uart.h" + +namespace BufferCache +{ + struct + { + Spinlock lock; + struct Buf buf[NBUF]; + struct Buf head; + } bcache; + + void init() + { + bcache.lock.init("bcache"); + // Initialize LRU doubly circular linked list + bcache.head.prev = &bcache.head; + bcache.head.next = &bcache.head; + for (struct Buf *b = bcache.buf; b < bcache.buf + NBUF; ++b) + { + b->lock.init("buffer"); + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + Drivers::uart_puts("[Bio] Buffer cache initialized.\n"); + } + + static struct Buf* bget(uint32 dev, uint32 blockno) + { + struct Buf* b; + bcache.lock.acquire(); + + // Attempt to hit the cache + for (b = bcache.head.next; b != &bcache.head; b = b->next) + { + if (b->dev == dev && b->blockno == blockno) + { + b->refcnt++; + bcache.lock.release(); + b->lock.acquire(); // Acquire Sleep Lock + return b; + } + } + + // Cache miss, evicting LRU + for (b = bcache.head.prev; b != &bcache.head; b = b->prev) + { + if (b->refcnt == 0) + { + b->dev = dev; + b->blockno = blockno; + b->valid = 0; + b->refcnt = 1; + bcache.lock.release(); + b->lock.acquire(); + return b; + } + } + + Drivers::uart_puts("panic: bget: no buffers\n"); + while (1) + ; + } + + struct Buf* bread(uint32 dev, uint32 blockno) + { + struct Buf *b = bget(dev, blockno); + + if (!b->valid) + { + VirtIO::rw(0, b->blockno, (char *)b->data); + b->valid = 1; + } + + return b; + } + + void bwrite(struct Buf *b) + { + if (!b->lock.holding()) + { + Drivers::uart_puts("panic: bwrite\n"); + while (1) + ; + } + VirtIO::rw(1, b->blockno, (char *)b->data); + } + + void brelse(struct Buf *b) + { + if (!b->lock.holding()) + { + Drivers::uart_puts("panic: brelse\n"); + while (1) + ; + } + b->lock.release(); + + bcache.lock.acquire(); + b->refcnt--; + + if (b->refcnt == 0) + { + b->next->prev = b->prev; + b->prev->next = b->next; + + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + + bcache.lock.release(); + } + + void bpin(struct Buf *b) + { + bcache.lock.acquire(); + b->refcnt++; + bcache.lock.release(); + } + + void bunpin(struct Buf* b) + { + bcache.lock.acquire(); + b->refcnt --; + bcache.lock.release(); + } + +} // namespace BufferCache diff --git a/lume-legacy/src/kernel/config.cc b/lume-legacy/src/kernel/config.cc new file mode 100644 index 0000000..36858a1 --- /dev/null +++ b/lume-legacy/src/kernel/config.cc @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" + +uint64 g_uart_base = 0; +// uint64 g_clint_base = 0; +uint64 g_plic_base = 0; +uint64 g_ram_start = 0; +uint64 g_ram_size = 0; + +extern "C" uint64 g_dtb_addr; \ No newline at end of file diff --git a/lume-legacy/src/kernel/cxx.cc b/lume-legacy/src/kernel/cxx.cc new file mode 100644 index 0000000..00f2799 --- /dev/null +++ b/lume-legacy/src/kernel/cxx.cc @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/pmm.h" +#include "drivers/uart.h" + +/* + * C++ Kernel Runtime Support + * + * * Strategy: Map directly to PMM::alloc_page(). + * Pros: Simple, works immediately for VFS. + * Cons: Allocates 4KB for every object (wasteful for small objects), + * but acceptable for current stage. + */ + +void *operator new(unsigned long size) +{ + // Safety check: PMM allocates 4096 bytes max per call + if (size > 4096) + { + Drivers::uart_puts("panic: new allocation too big (max 4096)\n"); + while (1) + ; + } + + void *p = PMM::alloc_page(); + if (!p) + { + Drivers::uart_puts("panic: new out of memory\n"); + while (1) + ; + } + // PMM::alloc_page returns zeroed memory usually, + // but standard 'new' doesn't guarantee it. + // The constructor will handle initialization. + return p; +} + +void *operator new[](unsigned long size) +{ + return operator new(size); +} + +void operator delete(void *p) +{ + if (p) + PMM::free_page(p); +} + +void operator delete[](void *p) +{ + if (p) + PMM::free_page(p); +} + +// C++14 Sized Deallocation (Required by modern compilers) +// The compiler optimizes 'delete' calls to pass the size of the object +void operator delete(void *p, unsigned long size) +{ + (void)size; // We ignore size because PMM always frees a full page + if (p) + PMM::free_page(p); +} + +void operator delete[](void *p, unsigned long size) +{ + (void)size; + if (p) + PMM::free_page(p); +} + +void *operator new(unsigned long size, void *ptr) +{ + (void)size; + return ptr; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/fdt.cc b/lume-legacy/src/kernel/fdt.cc new file mode 100644 index 0000000..ae09c84 --- /dev/null +++ b/lume-legacy/src/kernel/fdt.cc @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * LumeOS - FDT Parser + * + * Parses the Flattened Device Tree to discover hardware. + * Populates g_devices for use by all drivers. + */ +#include "common/types.h" +#include "kernel/fdt.h" +#include "lib/string.h" + +// Global device registry +DeviceRegistry g_devices; + +// Keep g_dtb_addr for PMM to protect DTB pages +// g_dtb_addr is defined in entry.S, g_uart_base in config.cc +extern "C" uint64 g_dtb_addr; +extern uint64 g_uart_base; + +static inline uint32 bswap(uint32 x) +{ + return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | + ((x & 0xFF0000) >> 8) | ((x >> 24) & 0xFF); +} + +struct fdt_header +{ + uint32 magic; + uint32 totalsize; + uint32 off_dt_struct; + uint32 off_dt_strings; + uint32 off_mem_rsvmap; + uint32 version; + uint32 last_comp_version; + uint32 boot_cpuid_phys; + uint32 size_dt_strings; + uint32 size_dt_struct; +}; + +#define FDT_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 1 +#define FDT_END_NODE 2 +#define FDT_PROP 3 +#define FDT_NOP 4 +#define FDT_END 9 + +// Check if 'str' starts with 'prefix' +static bool starts_with(const char *str, const char *prefix) +{ + while (*prefix) + { + if (*str != *prefix) + return false; + str++; + prefix++; + } + return true; +} + +// Check if 'haystack' contains 'needle' as a substring within 'len' bytes +// (compatible strings may have multiple null-separated entries) +static bool compat_match(const uint8 *data, uint32 len, const char *needle) +{ + uint32 nlen = strlen(needle); + for (uint32 i = 0; i + nlen <= len; i++) + { + if (memcmp(data + i, needle, nlen) == 0) + return true; + } + return false; +} + +// Add a MMIO region to the registry (avoids duplicates) +static void add_mmio_region(uint64 base, uint64 size) +{ + if (base == 0 || size == 0) + return; + // Only track I/O regions below RAM + if (base >= 0x80000000ULL) + return; + if (g_devices.mmio_count >= DeviceRegistry::MAX_MMIO) + return; + + // Check for overlap/duplicate + for (int i = 0; i < g_devices.mmio_count; i++) + { + if (g_devices.mmio_regions[i].base == base) + return; + } + + g_devices.mmio_regions[g_devices.mmio_count].base = base; + g_devices.mmio_regions[g_devices.mmio_count].size = size; + g_devices.mmio_count++; +} + +void fdt_parse(uint64 dtb) +{ + // Clear registry + memset(&g_devices, 0, sizeof(g_devices)); + + if (dtb == 0) + return; + + g_dtb_addr = dtb; + + struct fdt_header *header = (struct fdt_header *)dtb; + if (bswap(header->magic) != FDT_MAGIC) + return; + + uint8 *struct_ptr = (uint8 *)(dtb + bswap(header->off_dt_struct)); + char *strings_ptr = (char *)(dtb + bswap(header->off_dt_strings)); + + // Parsing state + char current_node_name[64]; + current_node_name[0] = '\0'; + + // Per-node state + uint64 current_reg_addr = 0; + uint64 current_reg_size = 0; + uint32 current_irq = 0; + const uint8 *current_compat = nullptr; + uint32 current_compat_len = 0; + bool has_reg = false; + bool has_compat = false; + // bool has_irq = false; // Unused + + // Depth tracking (for #address-cells / #size-cells) + // Default for root: 2 address-cells, 2 size-cells + int depth = 0; + + while (1) + { + uint32 token = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + + if (token == FDT_END) + break; + if (token == FDT_NOP) + continue; + + if (token == FDT_BEGIN_NODE) + { + // Save node name + // char *name = (char *)struct_ptr; + int i = 0; + while (*struct_ptr != 0 && i < 63) + { + current_node_name[i++] = *struct_ptr; + struct_ptr++; + } + current_node_name[i] = '\0'; + while (*struct_ptr != 0) + struct_ptr++; + struct_ptr++; + struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); + + // Reset per-node state + current_reg_addr = 0; + current_reg_size = 0; + current_irq = 0; + current_compat = nullptr; + current_compat_len = 0; + has_reg = false; + has_compat = false; + // has_irq = false; + + depth++; + } + else if (token == FDT_END_NODE) + { + // Process the node we just finished + if (has_reg && has_compat) + { + // UART: ns16550a + if (compat_match(current_compat, current_compat_len, "ns16550")) + { + g_devices.uart.base_addr = current_reg_addr; + g_devices.uart.size = current_reg_size ? current_reg_size : 0x100; + g_devices.uart.irq = current_irq; + add_mmio_region(current_reg_addr, g_devices.uart.size); + } + // PLIC + else if (compat_match(current_compat, current_compat_len, "plic")) + { + g_devices.plic.base_addr = current_reg_addr; + g_devices.plic.size = current_reg_size ? current_reg_size : 0x4000000; + g_devices.plic.irq = 0; + add_mmio_region(current_reg_addr, g_devices.plic.size); + } + // CLINT + else if (compat_match(current_compat, current_compat_len, "clint")) + { + g_devices.clint.base_addr = current_reg_addr; + g_devices.clint.size = current_reg_size ? current_reg_size : 0x10000; + g_devices.clint.irq = 0; + add_mmio_region(current_reg_addr, g_devices.clint.size); + } + // VirtIO + else if (compat_match(current_compat, current_compat_len, "virtio")) + { + if (g_devices.virtio_count < DeviceRegistry::MAX_VIRTIO) + { + int idx = g_devices.virtio_count++; + g_devices.virtio[idx].base_addr = current_reg_addr; + g_devices.virtio[idx].size = current_reg_size ? current_reg_size : 0x1000; + g_devices.virtio[idx].irq = current_irq; + add_mmio_region(current_reg_addr, g_devices.virtio[idx].size); + } + } + } + + // Memory node: name starts with "memory" + if (has_reg && starts_with(current_node_name, "memory")) + { + g_devices.mem_base = current_reg_addr; + g_devices.mem_size = current_reg_size; + } + + depth--; + } + else if (token == FDT_PROP) + { + uint32 len = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + uint32 nameoff = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + + char *name = strings_ptr + nameoff; + uint8 *data = struct_ptr; + + struct_ptr += len; + struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); + + if (strcmp(name, "reg") == 0 && len >= 8) + { + // Assume 2 address-cells, 2 size-cells (standard for QEMU virt) + uint32 hi = bswap(*(uint32 *)data); + uint32 lo = bswap(*(uint32 *)(data + 4)); + current_reg_addr = ((uint64)hi << 32) | lo; + + if (len >= 16) + { + uint32 shi = bswap(*(uint32 *)(data + 8)); + uint32 slo = bswap(*(uint32 *)(data + 12)); + current_reg_size = ((uint64)shi << 32) | slo; + } + has_reg = true; + } + else if (strcmp(name, "compatible") == 0) + { + current_compat = data; + current_compat_len = len; + has_compat = true; + } + else if (strcmp(name, "interrupts") == 0 && len >= 4) + { + current_irq = bswap(*(uint32 *)data); + // has_irq = true; + } + else if (strcmp(name, "interrupts-extended") == 0 && len >= 8) + { + // interrupts-extended = <&phandle irq_num> + // IRQ number is typically the second cell + current_irq = bswap(*(uint32 *)(data + 4)); + // has_irq = true; + } + } + } + + // Legacy compat + g_uart_base = g_devices.uart.base_addr; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/initcode.S b/lume-legacy/src/kernel/initcode.S new file mode 100644 index 0000000..22e55ca --- /dev/null +++ b/lume-legacy/src/kernel/initcode.S @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .rodata +.global initcode_start +.global initcode_end +.align 4 + +initcode_start: + .incbin "build/user/initcode" +initcode_end: diff --git a/lume-legacy/src/kernel/main.cc b/lume-legacy/src/kernel/main.cc new file mode 100644 index 0000000..6a19915 --- /dev/null +++ b/lume-legacy/src/kernel/main.cc @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "drivers/plic.h" +#include "drivers/virtio.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/slab.h" +#include "kernel/proc.h" +#include "kernel/trap.h" +#include "kernel/timer.h" +#include "kernel/cpu.h" +#include "kernel/buf.h" +#include "fs/fs.h" +#include "fs/file.h" +#include "fs/fat32.h" +#include "lib/string.h" + +#include "kernel/fdt.h" // fdt_parse + +extern uint64 g_dtb_addr; + +// Placement new support (referenced from cxx.cc) +void *operator new(unsigned long size, void *ptr); + +volatile static int started = 0; + +extern "C" void kernel_main(uint64 hartid, uint64 dtb) +{ + if (hartid == 0) + { + fdt_parse(dtb); + + Drivers::uart_init(); + Drivers::uart_puts("\n[Lume OS] Booting...\n"); + + Drivers::uart_puts("[Boot] PMP configured.\n"); + + PMM::init(); // Physical Memory Management + VM::kvminit(); // Kernel Pagetable + VM::kvminithart(); // Enable MMU + Slab::init(); + Trap::init(); // Trap Management + Trap::inithart(); + PLIC::init(); + PLIC::inithart(); + VirtIO::init(); + BufferCache::init(); + VFS::init(); + // Initialize global FAT32 object manually (global constructors not supported yet) + new (&fat32_fs) FAT32FileSystem(); + VFS::register_fs(&fat32_fs); + FileTable::init(); + Timer::init(); + ProcManager::init(); // Process Management + ProcManager::user_init(); // Initialize first user process + + __atomic_store_n(&started, 1, __ATOMIC_RELEASE); + + Drivers::uart_puts("[Boot] System initialized. Entering scheduler...\n"); + } + else + { + while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0) + ; + + VM::kvminithart(); + Trap::inithart(); + PLIC::inithart(); + Timer::init(); + Drivers::uart_puts("[Boot] Hart started.\n"); + } + + ProcManager::scheduler(); // Enable Scheduler +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/mm/pmm.cc b/lume-legacy/src/kernel/mm/pmm.cc new file mode 100644 index 0000000..d3d99ba --- /dev/null +++ b/lume-legacy/src/kernel/mm/pmm.cc @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/fdt.h" +// #include "kernel/spinlock.h" +#include "drivers/uart.h" +#include "lib/string.h" + +extern "C" char kernel_end[]; +extern uint64 g_dtb_addr; + +// Default memory size 128MB, will be overwritten by FDT +uint64 g_phys_size = 128 * 1024 * 1024; + +namespace PMM +{ + // Global metadata array pointer + struct Page *mem_map = nullptr; + // Total number of physical memory pages + uint64 total_pages = 0; + // Free list array (free_area[k] stores the head of the free block list of order=k) + struct Page *free_area[MAX_ORDER]; + // Protective lock + Spinlock pmm_lock; + + struct Page *pa_to_page(uint64 pa) + { + if (pa < KERNBASE || pa >= PHYSTOP) + return nullptr; + uint64 pfn = (pa - KERNBASE) >> PGSHIFT; + if (pfn >= total_pages) + return nullptr; + return &mem_map[pfn]; + } + + uint64 page_to_pa(struct Page *page) + { + uint64 pfn = page - mem_map; + return KERNBASE + (pfn << PGSHIFT); + } + + void list_add(struct Page *page, int order) + { + page->order = order; + page->flags = 0; // Mark as available + page->next = free_area[order]; + page->prev = nullptr; + + if (free_area[order]) + { + free_area[order]->prev = page; + } + free_area[order] = page; + } + + void list_del(struct Page* page, int order) + { + if (page->prev) + { + page->prev->next = page->next; + } + else + { + free_area[order] = page->next; + } + if (page->next) + { + page->next->prev = page->prev; + } + page->next = nullptr; + page->prev = nullptr; + } + + void init() + { + // Use FDT memory size if available + if (g_devices.mem_size > 0) + { + g_phys_size = g_devices.mem_size; + } + + pmm_lock.init("pmm"); + + uint64 kern_end_pa = PGROUNDUP((uint64)kernel_end); + total_pages = (PHYSTOP - KERNBASE) / PGSIZE; + + Drivers::uart_puts(ANSI_GREEN "[PMM] Total RAM size: "); + Drivers::uart_put_int(g_phys_size / (1024 * 1024)); + Drivers::uart_puts(" MB\n" ANSI_RESET); + // Place the mem_map array + // mem_map is located immediately after kernel_end + mem_map = (struct Page*)kern_end_pa; + + uint64 mem_map_size = total_pages * sizeof(struct Page); + uint64 free_mem_start = PGROUNDUP(kern_end_pa + mem_map_size); + + Drivers::uart_puts(ANSI_GREEN "[PMM] MemMap placed at: " ); + Drivers::print_hex((uint64)mem_map); + Drivers::uart_puts("\n" ANSI_RESET); + + Drivers::uart_puts(ANSI_GREEN "[PMM] Free memory starts at: "); + Drivers::print_hex(free_mem_start); + Drivers::uart_puts("\n" ANSI_RESET); + + if (free_mem_start >= PHYSTOP) + { + Drivers::uart_puts(ANSI_RED "[PMM] Error - Kernel too large, no memory left!\n" ANSI_RESET); + while (1) + ; + } + + // Initialize all page states + for (uint64 i = 0; i < total_pages; i++) + { + mem_map[i].flags = PG_reserved; + mem_map[i].order = 0; + mem_map[i].refcnt = 0; + mem_map[i].next = nullptr; + mem_map[i].prev = nullptr; + } + + for (int i = 0; i < MAX_ORDER; i++) + { + free_area[i] = nullptr; + } + + // Release actual free memory to the allocator + // Range: [free_mem_start, PHYSTOP) + // Note: need to avoid the page where the DTB is located + uint64 dtb_pa_start = PGROUNDDOWN(g_dtb_addr); + uint64 dtb_pa_end = PGROUNDUP(g_dtb_addr + 0x10000); + + uint64 p = free_mem_start; + while (p < PHYSTOP) + { + if (p >= dtb_pa_start && p < dtb_pa_end) + { + p += PGSIZE; + continue; + } + + // Mark the page as Used, then call free_pages to release it + // This allows free_pages to handle the merging logic automatically + struct Page *page = pa_to_page(p); + if (page) + { + page->flags = PG_used; + free_pages((void *)p, 0); // Release page by page, let Buddy merge automatically + } + p += PGSIZE; + + + } + + } + + void *alloc_pages(int order) + { + if (order < 0 || order >= MAX_ORDER) + return nullptr; + + pmm_lock.acquire(); + + int current_order = order; + while (current_order < MAX_ORDER) + { + if (free_area[current_order]) + { + // Find free block + struct Page *page = free_area[current_order]; + list_del(page, current_order); + + // If the block is too large, split it. + while (current_order > order) + { + current_order--; + // page is the left part (Buddy A) + // buddy is the right part (Buddy B) + // Pointer addition: Page* + n is equivalent to address offset n * sizeof(Page) + // Since mem_map is linearly mapped, mem_map[i + (1<flags = PG_used; + page->order = order; // Record the orders that have been assigned + page->refcnt = 1; + + pmm_lock.release(); + return (void *)page_to_pa(page); + } + current_order++; + } + pmm_lock.release(); + Drivers::uart_puts("PMM: OOM alloc_pages\n"); + return nullptr; + } + + void free_pages(void *pa, int order) + { + if (!pa) + return; + struct Page *page = pa_to_page((uint64)pa); + if (!page) + return; + pmm_lock.acquire(); + + if (!(page->flags & PG_used)) + { + Drivers::uart_puts("PMM: Double free warning\n"); + pmm_lock.release(); + return; + } + + // Mark as basic status + page->flags = 0; + page->refcnt = 0; + + // try merge + uint64 page_idx = page - mem_map; + + while (order < MAX_ORDER - 1) + { + // calculate Buddy index: index ^ (1 << order) + uint64 buddy_idx = page_idx ^ (1 << order); + struct Page *buddy = &mem_map[buddy_idx]; + + // Check if the Buddy can be merged: + // 1. Must be within the memory range + // 2. Must be free (flags == 0) + // 3. Must be at the current order (to prevent merging already split small blocks) + if (buddy_idx >= total_pages || (buddy->flags & PG_used) || (buddy->flags & PG_reserved) || buddy->order != order) + { + break; + } + + // Can be merged! + // Remove Buddy from the free list + list_del(buddy, order); + + // Update the current block to the merged block + // The starting address of the merged block is the smaller one between index and buddy_index + if (buddy_idx < page_idx) + { + page = buddy; + page_idx = buddy_idx; + } + + order++; + } + + // Insert into the final order linked list + list_add(page, order); + + pmm_lock.release(); + } + + void *alloc_page() + { + void *p = alloc_pages(0); + if (p) + { + memset(p, 0, PGSIZE); + } + return p; + } + + void free_page(void *pa) + { + free_pages(pa, 0); + } + + void print_free_lists() + { + // Debug print the number of free blocks at each level + Drivers::uart_puts("PMM Stats: [ "); + for (int i = 0; i < MAX_ORDER; i++) + { + int count = 0; + struct Page *p = free_area[i]; + while (p) + { + count++; + p = p->next; + } + if (count > 0) + { + Drivers::uart_puts("Order"); + // Drivers::uart_put_int(i); + Drivers::uart_puts(":"); + // Drivers::uart_put_int(count); + Drivers::uart_puts(" "); + } + } + Drivers::uart_puts("]\n"); + } +} // namespace PMM diff --git a/lume-legacy/src/kernel/mm/slab.cc b/lume-legacy/src/kernel/mm/slab.cc new file mode 100644 index 0000000..3bb6b1b --- /dev/null +++ b/lume-legacy/src/kernel/mm/slab.cc @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/slab.h" +#include "kernel/pmm.h" +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "lib/string.h" + +#define PG_slab 0x04 + +// Supported object size (must be a multiple of 8 and <= 2048) +const uint64 kmalloc_sizes[] = { + 16, 32, 64, 128, 256, 512, 1024, 2048}; +const int NUM_CACHES = 8; + +struct SlabHeader +{ + struct SlabHeader *next; // Next slab in the linked list + uint16 free_idx; // Index of the head of the free list (0xFFFF means none) + uint16 used_count; // Number of allocated objects + // Followed immediately by object data... + // The free list of objects is embedded in the first 2 bytes of the object itself (stores the index of the next object) + uint32 padding; +}; + +struct KmemCache +{ + Spinlock lock; + uint64 obj_size; + uint64 obj_count_per_slab; + struct SlabHeader *slab_list; // Slab list with free objects + const char *name; +}; + +struct KmemCache caches[NUM_CACHES]; + +// Virtual address -> struct Page* +static struct Page *virt_to_page(void *ptr) +{ + uint64 pa = (uint64)ptr; + return PMM::pa_to_page(pa); +} + +// Slab Implementation + +namespace Slab +{ + + void init() + { + for (int i = 0; i < NUM_CACHES; i++) + { + caches[i].lock.init("slab_lock"); + caches[i].obj_size = kmalloc_sizes[i]; + caches[i].name = "kmalloc"; + + // Calculate how many objects can fit on a page + // Excluding the overhead of the SlabHeader + uint64 available = PGSIZE - sizeof(struct SlabHeader); + caches[i].obj_count_per_slab = available / caches[i].obj_size; + caches[i].slab_list = nullptr; + } + Drivers::uart_puts("[Slab] Initialized kmalloc caches.\n"); + } + + // Allocate a new slab page for the cache + // Must hold cache->lock + static struct SlabHeader *grow_cache(struct KmemCache *c) + { + void *p = PMM::alloc_page(); + if (!p) + return nullptr; + + // Mark this page as a Slab page + struct Page *page = virt_to_page(p); + if (page) + { + page->flags |= PG_slab; + int cache_idx = c - caches; + page->order = (uint8)cache_idx; + } + + struct SlabHeader *slab = (struct SlabHeader *)p; + slab->next = nullptr; + slab->used_count = 0; + slab->free_idx = 0; + slab->padding = 0; + + // Initialize the free list of objects + // The starting position of each object stores the index of the next object + // Starting position of the object data area + char *base = (char *)(slab + 1); + + for (uint i = 0; i < c->obj_count_per_slab - 1; i++) + { + // The content of the i-th object is i+1 + uint16 *ptr = (uint16 *)(base + i * c->obj_size); + *ptr = i + 1; + } + // The last object points to 0xFFFF + uint16 *ptr = (uint16 *)(base + (c->obj_count_per_slab - 1) * c->obj_size); + *ptr = 0xFFFF; + + return slab; + } + + void *kmalloc(uint64 size) + { + if (size == 0) + return nullptr; + + // Large objects go directly through PMM + if (size > 2048) + { + // calculate order + int order = 0; + uint64 req = size; + uint64 pages = (size + PGSIZE - 1) / PGSIZE; + (void)req; + + // calculate order: 2^order >= pages + while ((1UL << order) < pages) + { + order++; + } + if (order > MAX_ORDER) + return nullptr; + + void *p = PMM::alloc_pages(order); + // Here, page->flags defaults to PG_used, not PG_slab + // kfree needs to be able to recognize it + return p; + } + + int idx = 0; + while (idx < NUM_CACHES && kmalloc_sizes[idx] < size) + { + idx++; + } + + struct KmemCache *c = &caches[idx]; + c->lock.acquire(); + + if (!c->slab_list) + { + c->slab_list = grow_cache(c); + if (!c->slab_list) + { + c->lock.release(); + return nullptr; + } + } + + struct SlabHeader *slab = c->slab_list; + + int obj_idx = slab->free_idx; + char *base = (char *)(slab + 1); + void *ptr = base + obj_idx * c->obj_size; + + uint16 *link = (uint16 *)ptr; + slab->free_idx = *link; + slab->used_count++; + + if (slab->free_idx == 0xFFFF) + { + if (slab->next) + { + c->slab_list = slab->next; + slab->next = nullptr; + } + else + { + c->slab_list = nullptr; + } + } + + c->lock.release(); + + memset(ptr, 0, size); + return ptr; + } + + void kfree(void *ptr) + { + if (!ptr) + return; + + struct Page *page = virt_to_page(ptr); + if (!page) + return; + + if (!page || !(page->flags & PG_slab)) + { + PMM::free_pages(ptr, page->order); + return; + } + + int cache_idx = page->order; + if (cache_idx >= NUM_CACHES) + { + Drivers::uart_puts("Slab: Corrupted page metadata\n"); + return; + } + + struct KmemCache *c = &caches[cache_idx]; + c->lock.acquire(); + + struct SlabHeader *slab = (struct SlabHeader *)PGROUNDDOWN((uint64)ptr); + + if ((uint64)ptr < (uint64)(slab + 1) || (uint64)ptr >= (uint64)slab + PGSIZE) + { + Drivers::uart_puts("Slab: kfree invalid pointer\n"); + c->lock.release(); + return; + } + + char *base = (char *)(slab + 1); + int obj_idx = ((uint64)ptr - (uint64)base) / c->obj_size; + + uint16 *link = (uint16 *)ptr; + *link = slab->free_idx; + slab->free_idx = (uint16)obj_idx; + + slab->used_count--; + + if (slab->used_count == 0) + { + struct SlabHeader **pp = &c->slab_list; + // bool found = false; + while (*pp) + { + if (*pp == slab) + { + *pp = slab->next; + // found = true; + break; + } + pp = &(*pp)->next; + } + + page->flags &= ~PG_slab; + PMM::free_page(slab); + } + else + { + + if (slab->used_count == c->obj_count_per_slab - 1) + { + slab->next = c->slab_list; + c->slab_list = slab; + } + } + + c->lock.release(); + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/mm/vm.cc b/lume-legacy/src/kernel/mm/vm.cc new file mode 100644 index 0000000..be2f2f2 --- /dev/null +++ b/lume-legacy/src/kernel/mm/vm.cc @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/mm.h" +#include "kernel/pmm.h" +#include "kernel/riscv.h" +#include "common/types.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/fdt.h" + +extern "C" +{ + extern char kernel_end[]; + extern char trampoline[]; +} + +namespace VM +{ + + uint64 *kernel_pagetable; + + static void page_ref_inc(uint64 pa) + { + struct Page *p = PMM::pa_to_page(pa); + if (p) + { + __atomic_add_fetch(&p->refcnt, 1, __ATOMIC_RELAXED); + } + } + + static bool page_ref_dec(uint64 pa) + { + struct Page *p = PMM::pa_to_page(pa); + if (p) + { + if (__atomic_sub_fetch(&p->refcnt, 1, __ATOMIC_RELEASE) == 0) + { + return true; + } + } + return false; + } + + static uint64 *walk(uint64 *pagetable, uint64 va, int alloc) + { + if (va >= MAXVA) + return nullptr; + + for (int level = 2; level > 0; level--) + { + uint64 *pte = &pagetable[PX(level, va)]; + if (*pte & PTE_V) + { + pagetable = (uint64 *)PTE2PA(*pte); + } + else + { + if (!alloc || (pagetable = (uint64 *)PMM::alloc_page()) == nullptr) + return nullptr; + + memset(pagetable, 0, PGSIZE); + *pte = PA2PTE(pagetable) | PTE_V; + } + } + return &pagetable[PX(0, va)]; + } + + int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm) + { + uint64 a, last; + uint64 *pte; + + if (size == 0) + return -1; + + a = PGROUNDDOWN(va); + last = PGROUNDDOWN(va + size - 1); + + for (;;) + { + if ((pte = walk(pagetable, a, 1)) == nullptr) + return -1; + + if (*pte & PTE_V) + { + return -1; + } + + *pte = PA2PTE(pa) | perm | PTE_V; + + if (a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; + } + + uint64 *uvmcreate() + { + uint64 *pagetable = (uint64 *)PMM::alloc_page(); + if (pagetable) + { + memset(pagetable, 0, PGSIZE); + + if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0) + { + PMM::free_page(pagetable); + return nullptr; + } + } + return pagetable; + } + + void uvminit(uint64 *pagetable, uchar *src, uint64 sz) + { + char *mem; + + if (sz >= PGSIZE) + { + Drivers::uart_puts("uvminit: initcode too big!\n"); + return; + } + + mem = (char *)PMM::alloc_page(); + memset(mem, 0, PGSIZE); + + mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W | PTE_R | PTE_X | PTE_U); + memmove(mem, src, sz); + } + + uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm) + { + char *mem; + uint64 a; + + if (newsz < oldsz) + return oldsz; + + oldsz = PGROUNDUP(oldsz); + for (a = oldsz; a < newsz; a += PGSIZE) + { + mem = (char *)PMM::alloc_page(); + if (mem == nullptr) + { + uvmdealloc(pagetable, a, oldsz); + return 0; + } + memset(mem, 0, PGSIZE); + if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_U | xperm) != 0) + { + PMM::free_page(mem); + uvmdealloc(pagetable, a, oldsz); + return 0; + } + } + return newsz; + } + + uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz) + { + if (newsz >= oldsz) + return oldsz; + + if (PGROUNDUP(newsz) < PGROUNDUP(oldsz)) + { + int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE; + uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1); + } + + return newsz; + } + + void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free) + { + uint64 a; + uint64 *pte; + + if ((va % PGSIZE) != 0) + return; + + for (a = va; a < va + npages * PGSIZE; a += PGSIZE) + { + if ((pte = walk(pagetable, a, 0)) == nullptr) + continue; + + if ((*pte & PTE_V) == 0) + continue; + + if ((PTE_FLAGS(*pte) & (PTE_R | PTE_W | PTE_X)) == 0) + { + continue; + } + + if (do_free) + { + uint64 pa = PTE2PA(*pte); + if (page_ref_dec(pa)) + { + PMM::free_page((void *)pa); + } + } + *pte = 0; + } + } + + static void freewalk(uint64 *pagetable) + { + for (int i = 0; i < 512; i++) + { + uint64 pte = pagetable[i]; + if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) + { + uint64 *child = (uint64 *)PTE2PA(pte); + freewalk(child); + pagetable[i] = 0; + } + } + PMM::free_page(pagetable); + } + + void uvmfree(uint64 *pagetable, uint64 sz) + { + if (sz > 0) + uvmunmap(pagetable, 0, PGROUNDUP(sz) / PGSIZE, 1); + freewalk(pagetable); + } + + int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz) + { + uint64 *pte; + uint64 pa, i; + uint flags; + + for (i = 0; i < sz; i += PGSIZE) + { + if ((pte = walk(oldpt, i, 0)) == nullptr) + continue; + if ((*pte & PTE_V) == 0) + continue; + + pa = PTE2PA(*pte); + flags = PTE_FLAGS(*pte); + + if (flags & PTE_W) + { + flags &= ~PTE_W; + flags |= PTE_COW; + *pte = PA2PTE(pa) | flags; + } + + if (mappages(newpt, i, PGSIZE, pa, flags) != 0) + { + return -1; + } + + page_ref_inc(pa); + } + + sfence_vma(); + return 0; + } + + int handle_cow_fault(uint64 *pagetable, uint64 va); + + int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len) + { + uint64 n, va0, pa0; + uint64 *pte; + + while (len > 0) + { + va0 = PGROUNDDOWN(dstva); + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + // Note: Maybe a bug + if (*pte & PTE_COW) + { + if (handle_cow_fault(pagetable, va0) < 0) + return -1; + // Re-walk because handle_cow_fault replaced the page mapping + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + } + + pa0 = PTE2PA(*pte); + n = PGSIZE - (dstva - va0); + if (n > len) + n = len; + + memmove((void *)(pa0 + (dstva - va0)), src, n); + + len -= n; + src += n; + dstva = va0 + PGSIZE; + } + return 0; + } + + int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len) + { + uint64 n, va0, pa0; + uint64 *pte; + + while (len > 0) + { + va0 = PGROUNDDOWN(srcva); + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + pa0 = PTE2PA(*pte); + n = PGSIZE - (srcva - va0); + if (n > len) + n = len; + + memmove(dst, (void *)(pa0 + (srcva - va0)), n); + + len -= n; + dst += n; + srcva = va0 + PGSIZE; + } + return 0; + } + + int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max) + { + uint64 n, va0, pa0; + uint64 *pte; + int got_null = 0; + uint64 len = 0; + + while (len < max) + { + va0 = PGROUNDDOWN(srcva); + pte = walk(pagetable, va0, 0); + + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + pa0 = PTE2PA(*pte); + + n = PGSIZE - (srcva - va0); + if (n > max - len) + n = max - len; + + char *p = (char *)(pa0 + (srcva - va0)); + while (n > 0) + { + if (*p == '\0') + { + *dst = '\0'; + got_null = 1; + break; + } + *dst = *p; + n--; + srcva++; + dst++; + p++; + len++; + } + if (got_null) + break; + } + + if (got_null) + return 0; + + return -1; + } + + int handle_cow_fault(uint64 *pagetable, uint64 va) + { + if (va >= MAXVA) + return -1; + + uint64 *pte = walk(pagetable, va, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + if ((*pte & PTE_COW) == 0) + return -1; + + uint64 pa = PTE2PA(*pte); + uint64 flags = PTE_FLAGS(*pte); + + char *mem = (char *)PMM::alloc_page(); + if (mem == nullptr) + return -1; + + memmove(mem, (char *)pa, PGSIZE); + + uint64 new_flags = (flags | PTE_W) & ~PTE_COW; + *pte = PA2PTE((uint64)mem) | new_flags | PTE_V; + + if (page_ref_dec(pa)) + { + PMM::free_page((void *)pa); + } + + sfence_vma(); + return 0; + } + + static void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm) + { + if (mappages(kernel_pagetable, va, sz, pa, perm) != 0) + { + Drivers::uart_puts("kvmmap: failed\n"); + while (1) + ; + } + } + + void kvminit() + { + kernel_pagetable = uvmcreate(); + + // 1. Map all device MMIO regions discovered by FDT + for (int i = 0; i < g_devices.mmio_count; i++) + { + uint64 base = g_devices.mmio_regions[i].base; + uint64 size = g_devices.mmio_regions[i].size; + kvmmap(base, base, size, PTE_R | PTE_W); + } + + // 2. Map Kernel Memory (PHYSTOP is now dynamic based on FDT) + kvmmap(KERNBASE, KERNBASE, PHYSTOP - KERNBASE, PTE_R | PTE_W | PTE_X); + } + + void kvminithart() + { + w_satp(MAKE_SATP(kernel_pagetable)); + sfence_vma(); + Drivers::uart_puts("[Boot] MMU ENABLED! Using virtual addresses.\n"); + } + + uint64 walkaddr(uint64 *pagetable, uint64 va) + { + uint64 *pte; + uint64 pa; + + if (va >= MAXVA) + return 0; + + pte = walk(pagetable, va, 0); + if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return 0; + + pa = PTE2PA(*pte); + return pa; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/exec.cc b/lume-legacy/src/kernel/proc/exec.cc new file mode 100644 index 0000000..8d8c467 --- /dev/null +++ b/lume-legacy/src/kernel/proc/exec.cc @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "kernel/proc.h" +#include "kernel/mm.h" +#include "kernel/elf.h" +#include "fs/fs.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/trap.h" + +extern char trampoline[]; + +// RAII: Page Table Resource Management +class VmGuard +{ +public: + uint64 *pagetable = nullptr; + uint64 sz = 0; + + VmGuard() : pagetable(VM::uvmcreate()) {} + + ~VmGuard() + { + if (pagetable) + { + VM::uvmfree(pagetable, sz); + } + } + + // Copying is prohibited + VmGuard(const VmGuard &) = delete; + VmGuard &operator=(const VmGuard &) = delete; + + // Submit resources: Ownership transfer, destructor no longer releases + uint64 *commit() + { + uint64 *pt = pagetable; + pagetable = nullptr; + return pt; + } +}; + +// RAII: Inode Lock Management +class InodeGuard +{ +public: + Inode *ip; + InodeGuard(Inode *i) : ip(i) + { + if (ip) + VFS::ilock(ip); + } + ~InodeGuard() + { + if (ip) + { + VFS::iunlockput(ip); // Automatically unlock and release references + } + } + // Copying is prohibited + InodeGuard(const InodeGuard &) = delete; + InodeGuard &operator=(const InodeGuard &) = delete; +}; + +// laod segment +static int loadseg(uint64 *pagetable, uint64 va, Inode *ip, uint64 offset, uint64 sz) +{ + for (uint64 i = 0; i < sz; i += PGSIZE) + { + uint64 pa = VM::walkaddr(pagetable, va + i); + if (pa == 0) + { + Drivers::uart_puts("[Exec Debug] loadseg: walkaddr failed\n"); + return -1; + } + + uint64 n = PGSIZE - (va + i) % PGSIZE; + if (i + n > sz) + n = sz - i; + + // Read physical address (isUser=0) + if (ip->read((char *)(pa + (va + i) % PGSIZE), offset + i, n, 0) != (int)n) + { + Drivers::uart_puts("[Exec Debug] loadseg: read failed\n"); + return -1; + } + } + return 0; +} + +namespace Exec +{ + int exec(char *path, char **argv) + { + // 1. Get Inode + Inode *raw_ip = VFS::namei(path); + if (!raw_ip) + { + Drivers::uart_puts("[Exec Debug] namei failed for path: "); + Drivers::uart_puts(path); + Drivers::uart_puts("\n"); + return -1; + } + InodeGuard iguard(raw_ip); + + // Get ELF head + struct elfhdr elf; + if (raw_ip->read((char *)&elf, 0, sizeof(elf), 0) != sizeof(elf)) + { + Drivers::uart_puts("[Exec Debug] read elf header failed\n"); + return -1; + } + if (elf.magic != ELF_MAGIC) + { + Drivers::uart_puts("[Exec Debug] invalid elf magic\n"); + return -1; + } + + // Create new pagetable + VmGuard vm_guard; + if (!vm_guard.pagetable) + { + Drivers::uart_puts("[Exec Debug] uvmcreate failed\n"); + return -1; + } + + // Load Program Segment + struct proghdr ph; + for (int i = 0; i < elf.phnum; i++) + { + uint64 off = elf.phoff + i * sizeof(ph); + if (raw_ip->read((char *)&ph, off, sizeof(ph), 0) != sizeof(ph)) + { + Drivers::uart_puts("[Exec Debug] read proghdr failed\n"); + return -1; + } + + if (ph.type != ELF_PROG_LOAD) + continue; + if (ph.memsz < ph.filesz) + return -1; + if (ph.vaddr + ph.memsz < ph.vaddr) + return -1; + + uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, vm_guard.sz, ph.vaddr + ph.memsz, PTE_W | PTE_X | PTE_R | PTE_U); + if (sz1 == 0) + { + Drivers::uart_puts("[Exec Debug] uvmalloc failed (segment)\n"); + return -1; + } + vm_guard.sz = sz1; + + if (loadseg(vm_guard.pagetable, ph.vaddr, raw_ip, ph.off, ph.filesz) < 0) + return -1; + } + + // Prepare user stack + uint64 sz = PGROUNDUP(vm_guard.sz); + + const int stack_data_pages = 16; + const int stack_total_pages = stack_data_pages + 1; + + uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, sz, sz + stack_total_pages * PGSIZE, PTE_W | PTE_R | PTE_U); + if (sz1 == 0) + { + Drivers::uart_puts("[Exec Debug] uvmalloc failed (stack)\n"); + return -1; + } + vm_guard.sz = sz1; + + VM::uvmunmap(vm_guard.pagetable, sz, 1, 0); + + uint64 sp = sz1; + uint64 stackbase = sp - stack_data_pages * PGSIZE; + uint64 ustack[32]; + uint64 argc = 0; + + // Push parameter string + for (; argv[argc]; argc++) + { + if (argc >= 31) + return -1; + sp -= strlen(argv[argc]) + 1; + sp -= sp % 16; + if (sp < stackbase) + { + Drivers::uart_puts("[Exec Debug] stack overflow (args)\n"); + return -1; + } + + if (VM::copyout(vm_guard.pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0) + { + Drivers::uart_puts("[Exec Debug] copyout arg string failed\n"); + return -1; + } + ustack[argc] = sp; + } + ustack[argc] = 0; + + // Push into parameter array + sp -= (argc + 1) * sizeof(uint64); + sp -= sp % 16; + if (sp < stackbase) + { + Drivers::uart_puts("[Exec Debug] stack overflow (argv array)\n"); + return -1; + } + if (VM::copyout(vm_guard.pagetable, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) < 0) + { + Drivers::uart_puts("[Exec Debug] copyout argv array failed\n"); + return -1; + } + + // Commit Changes + struct Proc *p = myproc(); + if (VM::mappages(vm_guard.pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) + { + Drivers::uart_puts("[Exec Debug] mappages TRAPFRAME failed\n"); + return -1; + } + + uint64 oldsz = p->sz; + uint64 *oldpagetable = p->pagetable; + + p->tf->a0 = argc; + p->tf->a1 = sp; + p->tf->sp = sp; + p->tf->epc = elf.entry; + + char *s = path, *last = path; + while (*s) + { + if (*s == '/') + last = s + 1; + s++; + } + + size_t len = 0; + while (last[len] && len < sizeof(p->name) - 1) + { + p->name[len] = last[len]; + len++; + } + p->name[len] = '\0'; + + // change pagetable + p->pagetable = vm_guard.commit(); + p->sz = vm_guard.sz; + + // release old pagetable + VM::uvmfree(oldpagetable, oldsz); + asm volatile("fence.i"); + + return 0; // iguard unlocks and releases IP upon destruction + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/proc.cc b/lume-legacy/src/kernel/proc/proc.cc new file mode 100644 index 0000000..3923fe8 --- /dev/null +++ b/lume-legacy/src/kernel/proc/proc.cc @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/proc.h" +#include "kernel/proc_def.h" +#include "kernel/runqueue.h" +#include "kernel/cpu.h" +#include "kernel/spinlock.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/trap.h" +#include "kernel/riscv.h" +#include "kernel/slab.h" +#include "drivers/uart.h" +#include "fs/fs.h" +#include "fs/file.h" +#include "lib/string.h" + +// #define NPROC 64 +#define NCPU 8 + +extern "C" void swtch(struct Context *old_ctx, struct Context *new_ctx); +extern char uservec[], trampoline[]; +extern "C" void usertrap(); +extern "C" void userret(uint64, uint64); +extern char initcode_start[], initcode_end[]; + +// struct Proc procs[NPROC]; +struct Proc *proc_list_head = nullptr; +struct Proc *initproc = nullptr; +struct Spinlock proc_mem_lock; +struct cpu cpus[NCPU]; +struct RunQueue runqueues[NCPU]; + + +static int nextpid = 1; + + +struct cpu *mycpu() +{ + int id = r_tp(); + return &cpus[id]; +} + + +void RunQueue::init(const char *name) +{ + lock.init(name); + count = 0; + for (int i = 0; i < N_PRIO; i++) + { + heads[i] = nullptr; + tails[i] = nullptr; + } +} + +void RunQueue::enqueue(struct Proc *p) +{ + if (p->priority < 0 || p->priority >= N_PRIO) + p->priority = PRIO_MIN; + p->next = nullptr; + if (heads[p->priority] == nullptr) + { + heads[p->priority] = p; + tails[p->priority] = p; + } + else + { + tails[p->priority]->next = p; + tails[p->priority] = p; + } + count++; +} + +struct Proc *RunQueue::dequeue() +{ + for (int i = 0; i < N_PRIO; i++) + { + if (heads[i]) + { + struct Proc *p = heads[i]; + heads[i] = p->next; + if (!heads[i]) + tails[i] = nullptr; + p->next = nullptr; + count--; + return p; + } + } + return nullptr; +} + +struct Proc *RunQueue::steal() +{ + for (int i = N_PRIO - 1; i >= 0; i--) + { + if (heads[i]) + { + struct Proc *p = heads[i]; + heads[i] = p->next; + if (!heads[i]) + tails[i] = nullptr; + p->next = nullptr; + count--; + return p; + } + } + return nullptr; +} + +// Trap Return Functions + +void usertrapret() +{ + struct Proc *p = myproc(); + + intr_off(); + + // Set stvec to point to uservec in the trampoline + // When an interrupt occurs in user mode, the CPU will jump here + uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline); + w_stvec(trampoline_uservec); + + // Fill in the Trapframe, used when uservec saves the kernel state + p->tf->kernel_satp = r_satp(); + p->tf->kernel_sp = (uint64)p->kstack + PGSIZE; + p->tf->kernel_trap = (uint64)usertrap; + p->tf->kernel_hartid = r_tp(); + + // Set SSTATUS + // SPP = 0 (return to User Mode) + // SPIE = 1 (enable interrupts) + unsigned long x = r_sstatus(); + x &= ~SSTATUS_SPP; + x |= SSTATUS_SPIE; + w_sstatus(x); + + // Set SEPC to the user program counter + w_sepc(p->tf->epc); + + // Prepare user page table SATP + uint64 satp = MAKE_SATP(p->pagetable); + + // Calculate the virtual address of userret in the trampoline + uint64 fn = TRAMPOLINE + ((uint64)userret - (uint64)trampoline); + + // Jump to userret, with parameters being the TRAPFRAME address and the user page table SATP + ((void (*)(uint64, uint64))fn)(TRAPFRAME, satp); +} + + +void forkret() +{ + + struct cpu *c = mycpu(); + c->runqueue->lock.release(); + + // Debug + // Drivers::uart_puts("[Proc] forkret: jumping to user.\n"); + + // Enter user mode + usertrapret(); +} + +// ProcManager Implementation + +static Proc *allocproc() +{ + struct Proc *p; + proc_mem_lock.acquire(); + + p = (struct Proc *)Slab::kmalloc(sizeof(struct Proc)); + + if (!p) + { + proc_mem_lock.release(); + return 0; + } + + memset(p, 0, sizeof(struct Proc)); + + p->all_next = proc_list_head; + p->all_prev = nullptr; + if (proc_list_head) + proc_list_head->all_prev = p; + proc_list_head = p; + + + p->state = USED; + p->pid = nextpid++; + p->priority = 0; + p->killed = 0; + p->cpu = mycpu(); + p->cwd = nullptr; + for (int i = 0; i < NOFILE; i++) + p->ofile[i] = nullptr; + + if ((p->tf = (struct Trapframe *)PMM::alloc_page()) == 0) + { + p->state = UNUSED; + if (p->all_next) + p->all_next->all_prev = p->all_prev; + if (p->all_prev) + p->all_prev->all_next = p->all_next; + if (p == proc_list_head) + proc_list_head = p->all_next; + Slab::kfree(p); + proc_mem_lock.release(); + return 0; + } + + if ((p->kstack = PMM::alloc_page()) == 0) + { + PMM::free_page(p->tf); + p->state = UNUSED; + if (p->all_next) + p->all_next->all_prev = p->all_prev; + if (p->all_prev) + p->all_prev->all_next = p->all_next; + if (p == proc_list_head) + proc_list_head = p->all_next; + Slab::kfree(p); + + proc_mem_lock.release(); + return 0; + } + memset(p->kstack, 0, PGSIZE); + + memset(&p->context, 0, sizeof(p->context)); + + // Set the context so that the next time this process is scheduled, it starts executing directly from forkret + p->context.ra = (uint64)forkret; + p->context.sp = (uint64)p->kstack + PGSIZE; + return p; +} + +static void freeproc(struct Proc *p) +{ + if (p->tf) + PMM::free_page(p->tf); + p->tf = 0; + if (p->kstack) + PMM::free_page(p->kstack); + p->kstack = 0; + if (p->pagetable) + VM::uvmfree(p->pagetable, p->sz); + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i]) + { + FileTable::close(p->ofile[i]); + p->ofile[i] = nullptr; + } + } + if (p->cwd) + { + VFS::iput(p->cwd); + p->cwd = nullptr; + } + + if (p->all_prev) + p->all_prev->all_next = p->all_next; + else + proc_list_head = p->all_next; + + if (p->all_next) + p->all_next->all_prev = p->all_prev; + + Slab::kfree(p); +} + +int fork() +{ + struct Proc *np; + struct Proc *p = myproc(); + + if ((np = allocproc()) == 0) + return -1; + + np->pagetable = VM::uvmcreate(); + np->sz = p->sz; + if (VM::uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) + { + freeproc(np); + proc_mem_lock.release(); + return -1; + } + + + if (VM::mappages(np->pagetable, TRAPFRAME, PGSIZE, (uint64)np->tf, PTE_R | PTE_W) < 0) + { + freeproc(np); + proc_mem_lock.release(); + return -1; + } + + *(np->tf) = *(p->tf); + np->tf->a0 = 0; + strcpy(np->name, p->name); + np->parent = p; + np->priority = p->priority; + + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i]) + { + np->ofile[i] = FileTable::dup(p->ofile[i]); + } + } + + if (p->cwd) + { + np->cwd = VFS::idup(p->cwd); + } + + proc_mem_lock.release(); + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + np->state = RUNNABLE; + c->runqueue->enqueue(np); + c->runqueue->lock.release(); + + return np->pid; +} + +namespace ProcManager +{ + + void init() + { + proc_mem_lock.init("proc_mem"); + + proc_list_head = nullptr; + + for (int i = 0; i < NCPU; i++) + { + cpus[i].runqueue = &runqueues[i]; + cpus[i].runqueue->init("runqueue"); + } + } + + void scheduler() + { + struct Proc *p; + struct cpu *c = mycpu(); + c->proc = 0; + Drivers::uart_puts("[Proc] Scheduler started (Per-CPU).\n"); + + for (;;) + { + intr_on(); + + // Get from the local queue + c->runqueue->lock.acquire(); + p = c->runqueue->dequeue(); + c->runqueue->lock.release(); + + // Work Stealing + if (!p) + { + for (int i = 0; i < NCPU; i++) + { + if ((uint64)i == r_tp()) + continue; + + struct cpu *victim = &cpus[i]; + victim->runqueue->lock.acquire(); + if (victim->runqueue->count > 0) + p = victim->runqueue->steal(); + victim->runqueue->lock.release(); + if (p) + break; + } + } + + if (p) + { + p->state = RUNNING; + c->proc = p; + + c->runqueue->lock.acquire(); + swtch(&c->context, &p->context); + c->runqueue->lock.release(); + + c->proc = 0; + } + } + } + + void yield() + { + struct Proc *p = myproc(); + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + if (p && p->state == RUNNING) + { + p->state = RUNNABLE; + if (p->priority < PRIO_MIN) + p->priority++; + c->runqueue->enqueue(p); + swtch(&p->context, &c->context); + } + c->runqueue->lock.release(); + } + + void user_init() + { + struct Proc *p = allocproc(); + if (!p) + return; + + initproc = p; + + p->pagetable = VM::uvmcreate(); + + uint64 initcode_sz = initcode_end - initcode_start; + Drivers::uart_puts("[Proc] initcode size: "); + Drivers::uart_put_int(initcode_sz); + Drivers::uart_puts(" bytes\n"); + + uint64 code_pages = PGROUNDUP(initcode_sz) / PGSIZE; + + for (uint64 i = 0; i < code_pages; i++) + { + void *mem = PMM::alloc_page(); + if (!mem) + { + Drivers::uart_puts("user_init: out of memory for code\n"); + return; + } + memset(mem, 0, PGSIZE); + + uint64 src_offset = i * PGSIZE; + uint64 copy_len = PGSIZE; + if (src_offset + copy_len > initcode_sz) + { + copy_len = initcode_sz - src_offset; + } + memmove(mem, (void *)(initcode_start + src_offset), copy_len); + + if (VM::mappages(p->pagetable, i * PGSIZE, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_X | PTE_U) < 0) + { + Drivers::uart_puts("user_init: mappages code failed\n"); + return; + } + } + + uint64 stack_va = (code_pages + 1) * PGSIZE; + + void *stack = PMM::alloc_page(); + if (!stack) + return; + memset(stack, 0, PGSIZE); + + if (VM::mappages(p->pagetable, stack_va, PGSIZE, (uint64)stack, PTE_R | PTE_W | PTE_U) < 0) + return; + + if (VM::mappages(p->pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) + return; + + p->tf->epc = 0; + p->tf->sp = stack_va + PGSIZE; + p->sz = stack_va + PGSIZE; + strcpy(p->name, "initcode"); + + p->cwd = nullptr; + + proc_mem_lock.release(); + + struct cpu *c = &cpus[0]; + c->runqueue->lock.acquire(); + p->state = RUNNABLE; + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + + Drivers::uart_puts("[Proc] user_init success. PID: 1\n"); + asm volatile("fence.i"); + } + + void sleep(void *chan, Spinlock *lk) + { + struct Proc *p = myproc(); + struct cpu *c = mycpu(); + + if (lk && lk != &proc_mem_lock) + lk->release(); + + c->runqueue->lock.acquire(); + p->chan = chan; + p->state = SLEEPING; + swtch(&p->context, &c->context); + p->chan = 0; + c->runqueue->lock.release(); + + if (lk && lk != &proc_mem_lock) + lk->acquire(); + } + + void wakeup(void *chan) + { + for (struct Proc *p = proc_list_head; p != nullptr; p = p->all_next) + { + if (p->state == SLEEPING && p->chan == chan) + { + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + p->state = RUNNABLE; + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + } + } + } + + void exit(int status) + { + struct Proc *p = myproc(); + if (p == initproc) + while (1) + ; + + wakeup(p->parent); + + proc_mem_lock.acquire(); + + for (struct Proc *pp = proc_list_head; pp != nullptr; pp = pp->all_next) + { + if (pp->parent == p) + { + pp->parent = initproc; + wakeup(initproc); + } + } + + proc_mem_lock.release(); + + p->xstate = status; + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + p->state = ZOMBIE; + swtch(&p->context, &c->context); + while (1) + ; + } + + int wait(uint64 addr) + { + + struct Proc *np; + int havekids, pid; + struct Proc *p = myproc(); + for (;;) + { + havekids = 0; + proc_mem_lock.acquire(); + + for (np = proc_list_head; np != nullptr; np = np->all_next) + { + if (np->parent == p) + { + havekids = 1; + if (np->state == ZOMBIE) + { + + struct cpu *child_cpu = np->cpu; + if (child_cpu) + { + proc_mem_lock.release(); + child_cpu->runqueue->lock.acquire(); + child_cpu->runqueue->lock.release(); + proc_mem_lock.acquire(); + } + + pid = np->pid; + if (addr != 0 && VM::copyout(p->pagetable, addr, (char *)&np->xstate, sizeof(np->xstate)) < 0) + { + proc_mem_lock.release(); + return -1; + } + freeproc(np); + proc_mem_lock.release(); + return pid; + } + } + } + proc_mem_lock.release(); + + if (!havekids || p->state == ZOMBIE) + return -1; + sleep(p, &proc_mem_lock); + } + } + + int kill(int pid) + { + Proc* p; + proc_mem_lock.acquire(); + for (p = proc_list_head; p != nullptr; p = p->all_next) + { + if (p->pid == pid) + { + p->killed = 1; + + if (p->state == SLEEPING) + { + p->state = RUNNABLE; + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + } + + proc_mem_lock.release(); + return 0; + } + } + proc_mem_lock.release(); + return -1; + } + + int growproc(int n) + { + uint64 sz; + struct Proc *p = myproc(); + + sz = p->sz; + + if (n > 0) + { + // Call VM::uvmalloc to allocate memory + // Parameters: page table, old size, new size, permissions (PTE_W) + if ((sz = VM::uvmalloc(p->pagetable, sz, sz + n, PTE_W | PTE_R | PTE_U)) == 0) + { + return -1; + } + } + else if (n < 0) + { + sz = VM::uvmdealloc(p->pagetable, sz, sz + n); + } + + p->sz = sz; + return 0; + } + + void create_kernel_thread(void (*func)(), const char *name) + { + (void)func; + (void)name; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/swtch.S b/lume-legacy/src/kernel/proc/swtch.S new file mode 100644 index 0000000..d55c03e --- /dev/null +++ b/lume-legacy/src/kernel/proc/swtch.S @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.global swtch + +swtch: + 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 diff --git a/lume-legacy/src/kernel/sbi.cc b/lume-legacy/src/kernel/sbi.cc new file mode 100644 index 0000000..5ae7edb --- /dev/null +++ b/lume-legacy/src/kernel/sbi.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/sbi.h" + +namespace SBI +{ + struct sbiret + { + long error; + long value; + }; + + struct sbiret sbi_call(int ext, int fid, uint64 arg0, uint64 arg1, uint64 arg2) + { + struct sbiret ret; + register uint64 a0 asm("a0") = arg0; + register uint64 a1 asm("a1") = arg1; + register uint64 a2 asm("a2") = arg2; + register uint64 a6 asm("a6") = fid; + register uint64 a7 asm("a7") = ext; + + asm volatile( + "ecall" + : "=r"(a0), "=r"(a1) + : "r"(a0), "r"(a1), "r"(a2), "r"(a6), "r"(a7) + : "memory"); + ret.error = a0; + ret.value = a1; + return ret; + } + + void set_timer(uint64 stime_value) + { + sbi_call(0x54494D45, 0, stime_value, 0, 0); + } + + void sbi_shutdown() + { + sbi_call(0x53525354, 0, 0, 0, 0); + sbi_call(0x08, 0, 0, 0, 0); + while (1) + ; + } + + void sbi_reboot() + { + sbi_call(SBI_EID_SRST, SBI_FID_SRST_RESET, SBI_SRST_TYPE_COLD_REBOOT, SBI_SRST_REASON_NONE, 0); + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/spinlock.cc b/lume-legacy/src/kernel/spinlock.cc new file mode 100644 index 0000000..aadce21 --- /dev/null +++ b/lume-legacy/src/kernel/spinlock.cc @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "kernel/cpu.h" +#include "kernel/proc_def.h" +#include "drivers/uart.h" + +extern struct cpu cpus[]; + +namespace ProcManager +{ + void sleep(void *chan, Spinlock *lk); + void wakeup(void *chan); +} + +// Interrupt Depth Management +void push_off(void); +void pop_off(void); + +// // Get the pointer of the current CPU +// // Depends on r_tp() in riscv.h to read the tp register +// struct cpu *mycpu() +// { +// int id = r_tp(); +// return &cpus[id]; +// } + +// Get the current process +// Must be called with interrupts disabled, or read atomically in a safe manner +struct Proc *myproc() +{ + push_off(); + struct cpu *c = mycpu(); + struct Proc *p = c->proc; + pop_off(); + return p; +} + +// Spinlock Implementation + +void Spinlock::init(const char *lock_name) +{ + name = lock_name; + locked = 0; + cpu = nullptr; +} + +void Spinlock::acquire() +{ + push_off(); // Disable interrupts: prevent deadlocks (interrupt handler requests a lock that is already held) + + if (holding()) + { + Drivers::uart_puts("panic: recursive acquire "); + Drivers::uart_puts(name); + Drivers::uart_puts("\n"); + while (1) + ; + } + + // Atomic Exchange + // __ATOMIC_ACQUIRE: Ensures that reads and writes after the acquire are not reordered before the acquire + while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE)) + { + // Busy-wait (Spinning) + // Optimization: Read-only, no writes, reducing cache coherence traffic + while (__atomic_load_n(&locked, __ATOMIC_RELAXED) == 1) + { + asm volatile("nop"); // RISC-V indicates that the CPU is in a spin state + } + } + + // Record holder + cpu = mycpu(); +} + +void Spinlock::release() +{ + if (!holding()) + { + Drivers::uart_puts("panic: release not holding\n"); + while (1) + ; + } + + cpu = nullptr; + + // Atomic Store + // __ATOMIC_RELEASE: Ensures all reads and writes before the Release are completed + __atomic_store_n(&locked, 0, __ATOMIC_RELEASE); + + pop_off(); +} + +// Check if the current CPU holds the lock +bool Spinlock::holding() +{ + // Interrupt checks must be disabled, otherwise a process switch may occur causing CPU fluctuations. + int r; + push_off(); + r = (locked && cpu == mycpu()); + pop_off(); + return r; +} + + +// Mutex Implementation (Sleep Locks) + +void Mutex::init(const char *lock_name) +{ + name = lock_name; + locked = 0; + pid = 0; + lk.init("mutex_internal"); +} + +void Mutex::acquire() +{ + lk.acquire(); // Acquire the internal lock to protect the locked field + + while (locked) + { + // Lock is held, preparing to sleep + // Atomicity contract of sleep(): + // 1. Mark the current process as SLEEPING + // 2. Release lk (so others can operate on locked) + // 3. Switch CPU (context switch) + // 4. After being woken up, reacquire the lk lock and return + ProcManager::sleep(this, &lk); + } + + locked = 1; + pid = myproc()->pid; + + lk.release(); +} + +void Mutex::release() +{ + lk.acquire(); + + locked = 0; + pid = 0; + + // Wake up all processes sleeping on this lock (chan = this) + ProcManager::wakeup(this); + + lk.release(); +} + +bool Mutex::holding() +{ + lk.acquire(); // Must use a lock when reading to prevent race conditions. + bool r = locked && (pid == myproc()->pid); + lk.release(); + return r; +} + +// Interrupt Depth Management + +void push_off(void) +{ + int old = intr_get(); + intr_off(); + + struct cpu *c = mycpu(); + + if (c->noff == 0) + c->intena = old; + + c->noff += 1; +} + +void pop_off(void) +{ + struct cpu *c = mycpu(); + + if (intr_get()) + { + Drivers::uart_puts("panic: pop_off - interruptible\n"); + while (1) + ; + } + if (c->noff < 1) + { + Drivers::uart_puts("panic: pop_off count\n"); + while (1) + ; + } + + c->noff -= 1; + + // An interrupt is only truly enabled when the nesting level returns to zero and the outermost layer allows interrupts. + if (c->noff == 0 && c->intena) + intr_on(); +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/syscall.cc b/lume-legacy/src/kernel/syscall.cc new file mode 100644 index 0000000..0979c24 --- /dev/null +++ b/lume-legacy/src/kernel/syscall.cc @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ +#include "kernel/syscall.h" +#include "common/types.h" +#include "common/fcntl.h" +#include "kernel/proc.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "drivers/virtio.h" +#include "kernel/timer.h" +#include "kernel/mm.h" +#include "kernel/trap.h" +#include "kernel/slab.h" +#include "kernel/sbi.h" +#include "fs/fat32.h" +#include "fs/file.h" +#include "lib/string.h" +#include "lib/errno.h" + +extern "C++" int fork(); +namespace Exec +{ + int exec(char *path, char **argv); +} + +namespace ProcManager +{ + void exit(int status); + int wait(uint64_t addr); +} + +static uint64_t argraw(int n) +{ + Proc *p = myproc(); + switch (n) + { + case 0: + return p->tf->a0; + case 1: + return p->tf->a1; + case 2: + return p->tf->a2; + case 3: + return p->tf->a3; + case 4: + return p->tf->a4; + case 5: + return p->tf->a5; + default: + return static_cast(-1); + } +} + +static int argint(int n, int *ip) +{ + *ip = static_cast(argraw(n)); + return 0; +} + +static int argstr(int n, char *buf, int max) +{ + uint64 addr = argraw(n); + return VM::copyinstr(myproc()->pagetable, buf, addr, max); +} + +struct KernelArgv +{ + static const int MAX_ARGS = 32; + static const int MAX_ARG_LEN = 128; + char *args[MAX_ARGS]; + + KernelArgv() { memset(args, 0, sizeof(args)); } + ~KernelArgv() + { + for (int i = 0; i < MAX_ARGS; i++) + if (args[i]) + { + Slab::kfree(args[i]); + args[i] = nullptr; + } + } + KernelArgv(const KernelArgv &) = delete; + KernelArgv &operator=(const KernelArgv &) = delete; +}; + +static int fdalloc(struct file *f) +{ + struct Proc *p = myproc(); + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i] == 0) + { + p->ofile[i] = f; + return i; + } + } + return -1; +} + +// sys_openat(int dfd, const char *filename, int flags, mode_t mode) +static uint64 sys_openat() +{ + char path[128]; + int dfd, omode, mode_unused; + int fd; + struct file *f; + Inode *ip; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &omode) < 0 || argint(3, &mode_unused) < 0) + return -EINVAL; + + if (omode & O_CREATE) + { + ip = VFS::namei(path); + if (ip == nullptr) + { + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp) + { + VFS::ilock(dp); + ip = dp->create(name, T_FILE, 0, 0); + VFS::iunlockput(dp); + } + } + } + else + { + ip = VFS::namei(path); + } + + if (ip == nullptr) + return -ENOENT; + + VFS::ilock(ip); + if ((omode & O_TRUNC) && ip->type == T_FILE) + { + ip->truncate(); + } + VFS::iunlock(ip); + + f = FileTable::alloc(); + if (f == nullptr) + { + VFS::iput(ip); + return -ENFILE; + } + + fd = fdalloc(f); + if (fd < 0) + { + f->ref = 0; + f->type = FD_NONE; + VFS::iput(ip); + return -EMFILE; + } + + f->type = FD_INODE; + f->ip = ip; + f->off = 0; + f->readable = !(omode & O_WRONLY); + f->writable = (omode & O_WRONLY) || (omode & O_RDWR); + + return fd; +} + +// sys_mkdirat(int dfd, const char *pathname, mode_t mode) +static uint64 sys_mkdirat() +{ + char path[128]; + int dfd, mode; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0) + return -EINVAL; + + Inode *ip = VFS::namei(path); + if (ip != nullptr) + { + VFS::iput(ip); + return -EEXIST; + } + + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp == nullptr) + return -ENOENT; + + VFS::ilock(dp); + ip = dp->create(name, T_DIR, 0, 0); + VFS::iunlockput(dp); + + if (ip == nullptr) + return -EIO; + + VFS::iput(ip); + return 0; +} + +static uint64 sys_chdir() +{ + char path[128]; + if (argstr(0, path, 128) < 0) + return -EFAULT; + + Inode *ip = VFS::namei(path); + if (ip == nullptr) + return -ENOENT; + + VFS::ilock(ip); + if (ip->type != T_DIR) + { + VFS::iunlockput(ip); + return -ENOTDIR; + } + VFS::iunlock(ip); + + struct Proc *p = myproc(); + if (p->cwd) + VFS::iput(p->cwd); + p->cwd = ip; + return 0; +} + +static uint64 sys_dup() +{ + int oldfd; + if (argint(0, &oldfd) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (oldfd < 0 || oldfd >= NOFILE || p->ofile[oldfd] == nullptr) + return -EBADF; + + struct file *f = FileTable::dup(p->ofile[oldfd]); + int newfd = fdalloc(f); + if (newfd < 0) + { + FileTable::close(f); + return -EMFILE; + } + return newfd; +} + +static uint64 sys_fstat() +{ + int fd; + uint64 stat_addr; + + if (argint(0, &fd) < 0 || argint(1, (int *)&stat_addr) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == nullptr) + return -EBADF; + + if (FileTable::stat(p->ofile[fd], stat_addr) < 0) + return -EIO; + + return 0; +} + +// sys_mknodat(int dfd, const char *filename, mode_t mode, dev_t dev) +static uint64 sys_mknodat() +{ + char path[128]; + int dfd, mode, dev; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0 || argint(3, &dev) < 0) + return -EINVAL; + + Inode *ip = VFS::namei(path); + if (ip != nullptr) + { + VFS::iput(ip); + return -EEXIST; + } + + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp == nullptr) + return -ENOENT; + + // Decode dev_t (Assuming simplified minor/major for now) + int major = (dev >> 20) & 0xFFF; // Just a guess for Linux packing, or pass raw + int minor = dev & 0xFFFFF; + + // For Phase 1 compatibility with init.cc: + // init.cc passes raw major/minor. Shim will pack them. + // We just unpack naively or assume Shim passed us correct structure. + + VFS::ilock(dp); + ip = dp->create(name, T_DEVICE, major, minor); + VFS::iunlockput(dp); + + if (ip == nullptr) + return -EIO; + + VFS::iput(ip); + return 0; +} + +static uint64 sys_close() +{ + int fd; + if (argint(0, &fd) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) + return -EBADF; + + FileTable::close(p->ofile[fd]); + p->ofile[fd] = 0; + return 0; +} + +static uint64 sys_pipe2() +{ + uint64 fdarray; + int flags; + struct file *rf, *wf; + int fd0, fd1; + struct Proc *p = myproc(); + + if (argint(0, (int *)&fdarray) < 0 || argint(1, &flags) < 0) + return -EFAULT; + + if (Pipe::create_pair(&rf, &wf) < 0) + return -ENFILE; + + if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) + { + if (fd0 >= 0) + p->ofile[fd0] = nullptr; + FileTable::close(rf); + FileTable::close(wf); + return -EMFILE; + } + + if (VM::copyout(p->pagetable, fdarray, (char *)&fd0, sizeof(int)) < 0 || + VM::copyout(p->pagetable, fdarray + sizeof(int), (char *)&fd1, sizeof(int)) < 0) + { + p->ofile[fd0] = nullptr; + p->ofile[fd1] = nullptr; + FileTable::close(rf); + FileTable::close(wf); + return -EFAULT; + } + return 0; +} + +static uint64 sys_unlinkat() +{ + char path[128], name[128]; + int dfd, flag; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &flag) < 0) + return -EINVAL; + + Inode *dp = VFS::nameiparent(path, name); + if (!dp) + return -ENOENT; + + VFS::ilock(dp); + int ret = dp->unlink(name); + VFS::iunlockput(dp); + + return (ret < 0) ? -EIO : 0; +} + +// sys_linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) +static uint64 sys_linkat() +{ + // FAT32 does not support hard links. + // Return Error to allow compilation/running to proceed (ln will fail gracefully). + return -EPERM; +} + +static uint64 sys_execve() +{ + char path[128]; + uint64 uargv_ptr; + + if (argstr(0, path, sizeof(path)) < 0) + return -EFAULT; + + uargv_ptr = argraw(1); + // uenvp_ptr = argraw(2); // Ignored + + KernelArgv kargv; + + for (int i = 0; i < KernelArgv::MAX_ARGS; ++i) + { + uint64 uarg_str_ptr; + if (VM::copyin(myproc()->pagetable, (char *)&uarg_str_ptr, uargv_ptr + i * sizeof(uint64), sizeof(uint64)) < 0) + { + return -EFAULT; + } + + if (uarg_str_ptr == 0) + { + kargv.args[i] = nullptr; + break; + } + + kargv.args[i] = (char *)Slab::kmalloc(KernelArgv::MAX_ARG_LEN); + if (kargv.args[i] == nullptr) + { + return -ENOMEM; + } + + if (VM::copyinstr(myproc()->pagetable, kargv.args[i], uarg_str_ptr, KernelArgv::MAX_ARG_LEN) < 0) + { + return -EFAULT; + } + } + return Exec::exec(path, kargv.args); +} + +static uint64 sys_exit() +{ + int n; + if (argint(0, &n) < 0) + return -EINVAL; + ProcManager::exit(n); + return 0; +} + +static uint64 sys_clone() +{ + return static_cast(fork()); +} + +static uint64 sys_wait4() +{ + uint64 status_addr; + + // argraw(0) is pid (ignored) + status_addr = argraw(1); + // argraw(2) is options (ignored) + // argraw(3) is rusage (ignored) + + if (status_addr == static_cast(-1)) + return -EINVAL; + + return static_cast(ProcManager::wait(status_addr)); +} + +static uint64 sys_getpid() +{ + return static_cast(myproc()->pid); +} + +static uint64 sys_putc() +{ + char c = static_cast(argraw(0)); + Drivers::uart_putc(c); + return 0; +} + +static uint64 sys_write() +{ + struct file *f; + int n; + uint64 p; + int fd; + + if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) + return -EINVAL; + + p = argraw(1); + + struct Proc *proc = myproc(); + + if (fd == 1 && proc->ofile[fd] == nullptr) + { + constexpr int MAX_WRITE_BUF = 128; + char buf[MAX_WRITE_BUF]; + int i = 0; + + while (i < n) + { + int len = n - i; + if (len > MAX_WRITE_BUF) + len = MAX_WRITE_BUF; + + if (VM::copyin(proc->pagetable, buf, p + i, static_cast(len)) < 0) + return -EFAULT; + + for (int j = 0; j < len; j++) + { + Drivers::uart_putc(buf[j]); + } + i += len; + } + return static_cast(n); + } + + if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) + return -EBADF; + + return FileTable::write(f, p, n); +} + +static uint64_t sys_read() +{ + struct file *f; + int n; + uint64 p; + int fd; + + if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) + return -EINVAL; + + p = argraw(1); + + struct Proc *proc = myproc(); + if (fd == 0 && proc->ofile[fd] == nullptr) + return Drivers::console_read(p, n); + + if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) + return -EBADF; + + return FileTable::read(f, p, n); +} + +static uint64 sys_brk() +{ + int n; + if (argint(0, &n) < 0) + return -EINVAL; + + uint64 addr = myproc()->sz; + if (ProcManager::growproc(n) < 0) + return -ENOMEM; + + return addr; +} + +static uint64 sys_kill() +{ + int pid; + if (argint(0, &pid) < 0) + return -EINVAL; + return ProcManager::kill(pid); +} + +static uint64 sys_sleep() +{ + int n; + uint64 ticks0; + + if (argint(0, &n) < 0) + return -EINVAL; + + Spinlock *lk = Timer::get_lock(); + lk->acquire(); + ticks0 = Timer::get_ticks(); + while (Timer::get_ticks() - ticks0 < (uint64)n) + { + if (myproc()->killed) + { + lk->release(); + return -EINTR; + } + ProcManager::sleep(Timer::get_tick_chan(), lk); + } + lk->release(); + return 0; +} + +static uint64 sys_disk_test() +{ + // VirtIO::test_rw(); + // VirtIO::test_bio(); + fat32_test(); + return 0; +} + +static uint64 sys_lseek() +{ + int fd, offset, whence; + if (argint(0, &fd) < 0 || argint(1, &offset) < 0 || argint(2, &whence) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) + return -EBADF; + + return FileTable::lseek(p->ofile[fd], offset, whence); +} + +static uint64 sys_reboot() +{ + SBI::sbi_shutdown(); + return 0; +} + +void syscall() +{ + Proc *p = myproc(); + int num = p->tf->a7; + uint64 ret = static_cast(-ENOSYS); + + switch (num) + { + case SYS_mknodat: + ret = sys_mknodat(); + break; + case SYS_mkdirat: + ret = sys_mkdirat(); + break; + case SYS_unlinkat: + ret = sys_unlinkat(); + break; + case SYS_linkat: + ret = sys_linkat(); + break; + case SYS_chdir: + ret = sys_chdir(); + break; + case SYS_openat: + ret = sys_openat(); + break; + case SYS_dup: + ret = sys_dup(); + break; + case SYS_close: + ret = sys_close(); + break; + case SYS_pipe2: + ret = sys_pipe2(); + break; + case SYS_lseek: + ret = sys_lseek(); + break; + case SYS_read: + ret = sys_read(); + break; + case SYS_write: + ret = sys_write(); + break; + case SYS_fstat: + ret = sys_fstat(); + break; + case SYS_exit: + ret = sys_exit(); + break; + case SYS_exit_group: + ret = sys_exit(); + break; + case SYS_kill: + ret = sys_kill(); + break; + case SYS_getpid: + ret = sys_getpid(); + break; + case SYS_brk: + ret = sys_brk(); + break; + case SYS_clone: + ret = sys_clone(); + break; + case SYS_execve: + ret = sys_execve(); + if (ret == 0) + return; + break; + case SYS_wait4: + ret = sys_wait4(); + break; + + // Custom / Debug + case SYS_reboot: + ret = sys_reboot(); + break; + case SYS_putc: + ret = sys_putc(); + break; + case SYS_disk_test: + ret = sys_disk_test(); + break; + case SYS_nanosleep: + ret = sys_sleep(); + break; + + default: + Drivers::uart_puts("Unknown Syscall ID: "); + Drivers::print_hex(num); + Drivers::uart_puts("\n"); + ret = -ENOSYS; + break; + } + + p->tf->a0 = ret; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/timer.cc b/lume-legacy/src/kernel/timer.cc new file mode 100644 index 0000000..fc02f27 --- /dev/null +++ b/lume-legacy/src/kernel/timer.cc @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/config.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +#include "kernel/sbi.h" + +#define INTERVAL 1000000 + +namespace Timer +{ + uint64 ticks; + Spinlock tickslock; + + uint64 get_time() + { + uint64 time; + asm volatile("rdtime %0" : "=r"(time)); + return time; + } + + void set_next_trigger() + { + SBI::set_timer(get_time() + INTERVAL); + } + + void init() + { + set_next_trigger(); + + uint64 sie; + asm volatile("csrr %0, sie" : "=r"(sie)); + sie |= (1L << 5); + asm volatile("csrw sie, %0" : : "r"(sie)); + } + + void tick() + { + tickslock.acquire(); + ticks++; + ProcManager::wakeup(&ticks); + tickslock.release(); + set_next_trigger(); + } + + uint64 get_ticks() + { + tickslock.acquire(); + uint64 t = ticks; + tickslock.release(); + return t; + } + + Spinlock *get_lock() + { + return &tickslock; + } + + void *get_tick_chan() + { + return &ticks; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/handler.cc b/lume-legacy/src/kernel/trap/handler.cc new file mode 100644 index 0000000..b7bd5f0 --- /dev/null +++ b/lume-legacy/src/kernel/trap/handler.cc @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/trap.h" +#include "kernel/riscv.h" +#include "kernel/proc.h" +#include "drivers/uart.h" +#include "kernel/syscall.h" +#include "kernel/mm.h" +#include "kernel/timer.h" +#include "drivers/plic.h" +#include "drivers/virtio.h" +#include "kernel/fdt.h" +#include "lib/string.h" + + +extern "C" void kernelvec(); + +// Handle Function + +void panic(const char *s) +{ + Drivers::uart_puts("\n\n!!! KERNEL PANIC !!!\n"); + Drivers::uart_puts(s); + Drivers::uart_puts("\nSystem Halted.\n"); + while (1) + ; +} + +// Trap Implementation + +namespace Trap +{ + void init() + { + Drivers::uart_puts("[Trap] Global init done.\n"); + } + + // Each CPU core needs to call this function when starting to set its own stvec + void inithart() + { + w_stvec((uint64)kernelvec); + + // Ensure that the S-Mode interrupt enable bit (SIE) is cleared during initialization + // Interrupt enabling will be done by the scheduler when the first process runs + w_sstatus(r_sstatus() & ~SSTATUS_SIE); + + // Enable the external interrupt, clock interrupt, and software interrupt bits in S-Mode (in SIE register) + uint64 sie = r_sie(); + sie |= SIE_SEIE | SIE_STIE | SIE_SSIE; + w_sie(sie); + + Drivers::uart_puts("[Trap] Hart initialized (stvec set).\n"); + } + + // Device Interrupt Check + int devintr() + { + uint64 scause = r_scause(); + + bool is_interrupt = (scause & 0x8000000000000000L) != 0; + uint64 code = scause & 0xff; + + if (is_interrupt && code == 9) + { + // Supervisor External Interrupt (IRQ 9) + int irq = PLIC::claim(); + + if (irq == (int)g_devices.uart.irq) + { + Drivers::uart_intr(); + } + else + { + // Check if it's a VirtIO IRQ + bool handled = false; + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (irq == (int)g_devices.virtio[i].irq) + { + VirtIO::intr(); + handled = true; + break; + } + } + } + + if (irq > 0) + { + PLIC::complete(irq); + } + + return 1; + } + else if (is_interrupt && code == 5) + { + // Supervisor Timer Interrupt (IRQ 5) + // Timer::set_next_trigger(); + Timer::tick(); + // w_sip(r_sip() & ~2); + return 2; + } + + return 0; + } +} + +// Exception Handling Entry + +extern "C" void kerneltrap(struct Trapframe *tf) +{ + uint64 sepc = r_sepc(); + uint64 sstatus = r_sstatus(); + uint64 scause = r_scause(); + + if ((sstatus & SSTATUS_SPP) == 0) + panic("kerneltrap: not from supervisor mode"); + + if (intr_get() != 0) + panic("kerneltrap: interrupts enabled"); + + if (Trap::devintr() == 0) + { + Drivers::uart_puts("\n--- KERNEL EXCEPTION ---\n"); + Drivers::uart_puts("scause: "); + Drivers::print_hex(scause); + Drivers::uart_puts("\n"); + Drivers::uart_puts("sepc: "); + Drivers::print_hex(sepc); + Drivers::uart_puts("\n"); + Drivers::uart_puts("stval: "); + Drivers::print_hex(r_stval()); + Drivers::uart_puts("\n"); + panic("kerneltrap exception"); + } + + w_sepc(sepc); + w_sstatus(sstatus); +} + +extern "C" void usertrap() +{ + // Drivers::uart_puts("[Debug] Entered usertrap!\n"); + int which_dev = 0; + + if ((r_sstatus() & SSTATUS_SPP) != 0) + panic("usertrap: not from user mode"); + + w_stvec((uint64)kernelvec); + + struct Proc *p = myproc(); + if (!p) + panic("usertrap: no process"); + + p->tf->epc = r_sepc(); + + uint64 scause = r_scause(); + uint64 stval = r_stval(); + + if (scause == 8) // Syscall + { + if (p->state == ZOMBIE) + ProcManager::exit(-1); + p->tf->epc += 4; + intr_on(); + syscall(); + intr_off(); + } + else if (scause == 15) // Store Page Fault + { + if (VM::handle_cow_fault(p->pagetable, PGROUNDDOWN(stval)) != 0) + { + uint64 pa = VM::walkaddr(p->pagetable, stval); + + if (pa == 0) + { + Drivers::uart_puts("usertrap: Segmentation Fault (unmapped) at "); + } + else + { + Drivers::uart_puts("usertrap: Protection Fault (or OOM) at "); + } + + Drivers::print_hex(stval); + Drivers::uart_puts("\n"); + p->state = ZOMBIE; + ProcManager::exit(-1); + } + } + else if ((which_dev = Trap::devintr()) != 0) + { + if (which_dev == 2) + { + ProcManager::yield(); + } + } + else + { + Drivers::uart_puts("\nusertrap(): unexpected scause "); + Drivers::print_hex(scause); + Drivers::uart_puts(" pid="); + Drivers::uart_put_int(p->pid); + Drivers::uart_puts(" sepc="); + Drivers::print_hex(p->tf->epc); + Drivers::uart_puts(" stval="); + Drivers::print_hex(stval); + Drivers::uart_puts("\n"); + p->state = ZOMBIE; + ProcManager::exit(-1); + } + + if (p->state == ZOMBIE) + ProcManager::exit(-1); + + usertrapret(); +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/trampoline.S b/lume-legacy/src/kernel/trap/trampoline.S new file mode 100644 index 0000000..57d6f25 --- /dev/null +++ b/lume-legacy/src/kernel/trap/trampoline.S @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.globl trampoline +.globl uservec +.globl userret + +.align 12 +trampoline: +.align 4 +uservec: + + csrrw a0, sscratch, a0 + + sd ra, 40(a0) + sd sp, 48(a0) + sd gp, 56(a0) + sd tp, 64(a0) + sd t0, 72(a0) + sd t1, 80(a0) + sd t2, 88(a0) + sd s0, 96(a0) + sd s1, 104(a0) + sd a1, 120(a0) + sd a2, 128(a0) + sd a3, 136(a0) + sd a4, 144(a0) + sd a5, 152(a0) + sd a6, 160(a0) + sd a7, 168(a0) + sd s2, 176(a0) + sd s3, 184(a0) + sd s4, 192(a0) + sd s5, 200(a0) + sd s6, 208(a0) + sd s7, 216(a0) + sd s8, 224(a0) + sd s9, 232(a0) + sd s10, 240(a0) + sd s11, 248(a0) + sd t3, 256(a0) + sd t4, 264(a0) + sd t5, 272(a0) + sd t6, 280(a0) + + csrr t0, sscratch + sd t0, 112(a0) + + ld sp, 8(a0) + ld tp, 32(a0) + ld t0, 16(a0) + ld t1, 0(a0) + + csrw satp, t1 + sfence.vma zero, zero + + jr t0 + +.align 4 +userret: + + csrw satp, a1 + sfence.vma zero, zero + + csrw sscratch, a0 + + ld ra, 40(a0) + ld sp, 48(a0) + ld gp, 56(a0) + ld tp, 64(a0) + ld t0, 72(a0) + ld t1, 80(a0) + ld t2, 88(a0) + ld s0, 96(a0) + ld s1, 104(a0) + ld a1, 120(a0) + ld a2, 128(a0) + ld a3, 136(a0) + ld a4, 144(a0) + ld a5, 152(a0) + ld a6, 160(a0) + ld a7, 168(a0) + ld s2, 176(a0) + ld s3, 184(a0) + ld s4, 192(a0) + ld s5, 200(a0) + ld s6, 208(a0) + ld s7, 216(a0) + ld s8, 224(a0) + ld s9, 232(a0) + ld s10, 240(a0) + ld s11, 248(a0) + ld t3, 256(a0) + ld t4, 264(a0) + ld t5, 272(a0) + ld t6, 280(a0) + + ld a0, 112(a0) + + sret \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/trap.S b/lume-legacy/src/kernel/trap/trap.S new file mode 100644 index 0000000..8bce53d --- /dev/null +++ b/lume-legacy/src/kernel/trap/trap.S @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.global kernelvec +.global uservec +.global userret +.align 12 + +kernelvec: + addi sp, sp, -288 + + sd ra, 40(sp) + sd sp, 48(sp) + sd gp, 56(sp) + sd tp, 64(sp) + sd t0, 72(sp) + sd t1, 80(sp) + sd t2, 88(sp) + sd s0, 96(sp) + sd s1, 104(sp) + sd a0, 112(sp) + sd a1, 120(sp) + sd a2, 128(sp) + sd a3, 136(sp) + sd a4, 144(sp) + sd a5, 152(sp) + sd a6, 160(sp) + sd a7, 168(sp) + sd s2, 176(sp) + sd s3, 184(sp) + sd s4, 192(sp) + sd s5, 200(sp) + sd s6, 208(sp) + sd s7, 216(sp) + sd s8, 224(sp) + sd s9, 232(sp) + sd s10, 240(sp) + sd s11, 248(sp) + sd t3, 256(sp) + sd t4, 264(sp) + sd t5, 272(sp) + sd t6, 280(sp) + + csrr t0, sepc + csrr t1, sstatus + sd t0, 24(sp) # Trapframe.epc + sd t1, 32(sp) # Trapframe.kernel_hartid + + mv a0, sp + call kerneltrap + + ld t0, 24(sp) + ld t1, 32(sp) + csrw sepc, t0 + csrw sstatus, t1 + + ld ra, 40(sp) + ld gp, 56(sp) + ld tp, 64(sp) + ld t0, 72(sp) + ld t1, 80(sp) + ld t2, 88(sp) + ld s0, 96(sp) + ld s1, 104(sp) + ld a0, 112(sp) + ld a1, 120(sp) + ld a2, 128(sp) + ld a3, 136(sp) + ld a4, 144(sp) + ld a5, 152(sp) + ld a6, 160(sp) + ld a7, 168(sp) + ld s2, 176(sp) + ld s3, 184(sp) + ld s4, 192(sp) + ld s5, 200(sp) + ld s6, 208(sp) + ld s7, 216(sp) + ld s8, 224(sp) + ld s9, 232(sp) + ld s10, 240(sp) + ld s11, 248(sp) + ld t3, 256(sp) + ld t4, 264(sp) + ld t5, 272(sp) + ld t6, 280(sp) + + addi sp, sp, 288 + sret \ No newline at end of file diff --git a/lume-legacy/src/lib/string.cc b/lume-legacy/src/lib/string.cc new file mode 100644 index 0000000..e8eccb3 --- /dev/null +++ b/lume-legacy/src/lib/string.cc @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "lib/string.h" + +void *memset(void *dst, int c, uint n) +{ + char *cdst = (char *)dst; + for (uint i = 0; i < n; i++) + cdst[i] = c; + return dst; +} + +int memcmp(const void *v1, const void *v2, uint n) +{ + const uchar *s1 = (const uchar *)v1; + const uchar *s2 = (const uchar *)v2; + while (n-- > 0) + { + if (*s1 != *s2) + return *s1 - *s2; + s1++, s2++; + } + return 0; +} + +void *memmove(void *dst, const void *src, uint n) +{ + const char *s = (const char *)src; + char *d = (char *)dst; + if (s < d && s + n > d) + { + s += n; + d += n; + while (n-- > 0) + *--d = *--s; + } + else + { + while (n-- > 0) + *d++ = *s++; + } + return dst; +} + +void *memcpy(void *dst, const void *src, uint n) +{ + return memmove(dst, src, n); +} + +void strcpy(char *dst, const char *src) +{ + while (*src) + *dst++ = *src++; + *dst = 0; +} + +int strlen(const char *s) +{ + int n = 0; + while (s[n]) + n++; + return n; +} + +int strcmp(const char *s1, const char *s2) +{ + while (*s1 && (*s1 == *s2)) + { + s1++; + s2++; + } + return *(const unsigned char *)s1 - *(const unsigned char *)s2; +} + +int strcasecmp(const char *s1, const char *s2) +{ + while (*s1) + { + if(*s1 == *s2 || *s1 + 32 == *s2 || *s1 == *s2 + 32){ + s1++; + s2++; + } + else break; + } + return *(const unsigned char *)s1 - *(const unsigned char *)s2; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/assert.c b/lume-legacy/src/ulib/assert.c new file mode 100644 index 0000000..c60643a --- /dev/null +++ b/lume-legacy/src/ulib/assert.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "stdio.h" +#include "stdlib.h" + +void __assert_fail(const char *expr, const char *file, int line, const char *func) +{ + fprintf(stderr, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); + abort(); +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/cxx.cc b/lume-legacy/src/ulib/cxx.cc new file mode 100644 index 0000000..d8f510d --- /dev/null +++ b/lume-legacy/src/ulib/cxx.cc @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdlib.h" + +void *operator new(unsigned long n) +{ + return malloc(n); +} + +void *operator new[](unsigned long n) +{ + return malloc(n); +} + +void operator delete(void *p) +{ + free(p); +} + +void operator delete[](void *p) +{ + free(p); +} + +void operator delete(void *p, unsigned long) +{ + free(p); +} + +void operator delete[](void *p, unsigned long) +{ + free(p); +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/iostream.cpp b/lume-legacy/src/ulib/iostream.cpp new file mode 100644 index 0000000..4d721c1 --- /dev/null +++ b/lume-legacy/src/ulib/iostream.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/iostream" + +namespace std +{ + istream cin(stdin); + ostream cout(stdout); + ostream cerr(stderr); +} // namespace std diff --git a/lume-legacy/src/ulib/stdio.c b/lume-legacy/src/ulib/stdio.c new file mode 100644 index 0000000..e81f69a --- /dev/null +++ b/lume-legacy/src/ulib/stdio.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/string.h" +#include "ulib/stdlib.h" + +/* Include STB Sprintf Implementation */ +#define STB_SPRINTF_IMPLEMENTATION +#include "ulib/stb_sprintf.h" + +const int STDIN_FD = 0; +const int STDOUT_FD = 1; +const int STDERR_FD = 2; + + + +extern int open(const char *path, int flags); +extern int close(int fd); +extern int read(int fd, void *buf, uint64 count); +extern int write(int fd, const void *buf, uint64 count); +extern int lseek(int fd, int offset, int whence); + +static int isspace(int c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +/* Core Formatting Implementation */ + +static int _ul_to_str_invert(char *buf, uint64 val, int base, int uppercase) +{ + int i = 0; + const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; + if (val == 0) + { + buf[i++] = '0'; + return i; + } + while (val != 0) + { + buf[i++] = digits[val % base]; + val /= base; + } + return i; +} + +typedef struct +{ + char *buf; + uint64 limit; + uint64 written; + FILE *stream; +} _OutContext; + +static void _out_char(_OutContext *ctx, char c) +{ + if (ctx->stream) + { + fputc(c, ctx->stream); + ctx->written++; + } + else + { + if (ctx->limit > 0 && ctx->written < ctx->limit - 1) + { + ctx->buf[ctx->written] = c; + } + ctx->written++; + } +} + +static int _vprintf_core(_OutContext *ctx, const char *format, va_list ap) +{ + const char *p = format; + char num_buf[32]; + + while (*p) + { + if (*p != '%') + { + _out_char(ctx, *p++); + continue; + } + p++; + + int flag_left = 0, flag_zero = 0, flag_plus = 0, flag_space = 0, flag_hash = 0; + while (1) + { + if (*p == '-') + flag_left = 1; + else if (*p == '0') + flag_zero = 1; + else if (*p == '+') + flag_plus = 1; + else if (*p == ' ') + flag_space = 1; + else if (*p == '#') + flag_hash = 1; + else + break; + p++; + } + + int width = 0; + if (*p == '*') + { + width = va_arg(ap, int); + if (width < 0) + { + flag_left = 1; + width = -width; + } + p++; + } + else + { + while (*p >= '0' && *p <= '9') + { + width = width * 10 + (*p - '0'); + p++; + } + } + + int precision = -1; + if (*p == '.') + { + p++; + if (*p == '*') + { + precision = va_arg(ap, int); + p++; + } + else + { + precision = 0; + while (*p >= '0' && *p <= '9') + { + precision = precision * 10 + (*p - '0'); + p++; + } + } + } + + int len_mod = 0; + if (*p == 'l') + { + len_mod = 1; + p++; + if (*p == 'l') + { + len_mod = 2; + p++; + } + } + else if (*p == 'z') + { + len_mod = 2; + p++; + } + + char spec = *p++; + if (!spec) + break; + + if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x' || spec == 'X' || spec == 'p') + { + uint64 val; + int is_signed = (spec == 'd' || spec == 'i'); + int base = (spec == 'x' || spec == 'X' || spec == 'p') ? 16 : 10; + + if (spec == 'p') + val = (uint64)va_arg(ap, void *); + else if (len_mod == 2) + val = is_signed ? (uint64)va_arg(ap, int64) : va_arg(ap, uint64); + else if (len_mod == 1) + val = is_signed ? (uint64)va_arg(ap, long) : (uint64)va_arg(ap, unsigned long); + else + val = is_signed ? (uint64)((int64)va_arg(ap, int)) : (uint64)va_arg(ap, uint32); + + char sign_char = 0; + if (is_signed) + { + int64 sval = (int64)val; + if (sval < 0) + { + sign_char = '-'; + val = (uint64)(-sval); + } + else if (flag_plus) + sign_char = '+'; + else if (flag_space) + sign_char = ' '; + } + + int digit_len = _ul_to_str_invert(num_buf, val, base, (spec == 'X')); + if (precision >= 0) + flag_zero = 0; + int prec_zeros = (precision > digit_len) ? precision - digit_len : 0; + if (precision == 0 && val == 0) + digit_len = 0; + + int prefix_len = sign_char ? 1 : 0; + char *prefix_str = NULL; + if (spec == 'p' || (flag_hash && val != 0 && (spec == 'x' || spec == 'X'))) + { + prefix_str = (spec == 'X') ? "0X" : "0x"; + prefix_len += 2; + } + + int padding = (width > prefix_len + prec_zeros + digit_len) ? width - (prefix_len + prec_zeros + digit_len) : 0; + + if (!flag_left && !flag_zero) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + if (sign_char) + _out_char(ctx, sign_char); + if (prefix_str) + { + _out_char(ctx, prefix_str[0]); + _out_char(ctx, prefix_str[1]); + } + int total_zeros = prec_zeros + ((flag_zero && !flag_left) ? padding : 0); + for (int i = 0; i < total_zeros; i++) + _out_char(ctx, '0'); + for (int i = digit_len - 1; i >= 0; i--) + _out_char(ctx, num_buf[i]); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') + { + char fmt_tmp[32]; + int f_idx = 0; + fmt_tmp[f_idx++] = '%'; + if (flag_left) + fmt_tmp[f_idx++] = '-'; + if (flag_zero) + fmt_tmp[f_idx++] = '0'; + if (flag_plus) + fmt_tmp[f_idx++] = '+'; + if (flag_space) + fmt_tmp[f_idx++] = ' '; + if (flag_hash) + fmt_tmp[f_idx++] = '#'; + if (width > 0) + f_idx += sprintf(&fmt_tmp[f_idx], "%d", width); + if (precision >= 0) + f_idx += sprintf(&fmt_tmp[f_idx], ".%d", precision); + fmt_tmp[f_idx++] = spec; + fmt_tmp[f_idx] = '\0'; + double f_val = va_arg(ap, double); + char f_buf[128]; + stbsp_snprintf(f_buf, sizeof(f_buf), fmt_tmp, f_val); + for (int i = 0; f_buf[i] != '\0'; i++) + _out_char(ctx, f_buf[i]); + } + else if (spec == 's') + { + const char *s = va_arg(ap, char *); + if (!s) + s = "(null)"; + int len = 0; + const char *tmp = s; + while (*tmp && (precision < 0 || len < precision)) + { + tmp++; + len++; + } + int padding = (width > len) ? width - len : 0; + if (!flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + for (int i = 0; i < len; i++) + _out_char(ctx, s[i]); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else if (spec == 'c') + { + char c = (char)va_arg(ap, int); + int padding = (width > 1) ? width - 1 : 0; + if (!flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + _out_char(ctx, c); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else + { + _out_char(ctx, '%'); + if (spec != '%') + _out_char(ctx, spec); + } + } + return (int)ctx->written; +} + +int vsnprintf(char *str, uint64 size, const char *format, va_list ap) +{ + _OutContext ctx; + ctx.buf = str; + ctx.limit = size; + ctx.written = 0; + ctx.stream = NULL; + _vprintf_core(&ctx, format, ap); + if (size > 0) + { + if (ctx.written < size) + str[ctx.written] = '\0'; + else + str[size - 1] = '\0'; + } + return (int)ctx.written; +} +int snprintf(char *str, uint64 size, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int ret = vsnprintf(str, size, format, ap); + va_end(ap); + return ret; +} +int sprintf(char *str, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int ret = vsnprintf(str, (uint64)-1, format, ap); + va_end(ap); + return ret; +} +int printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _OutContext ctx; + ctx.buf = NULL; + ctx.limit = 0; + ctx.written = 0; + ctx.stream = stdout; + _vprintf_core(&ctx, format, ap); + va_end(ap); + return (int)ctx.written; +} + +/* Core Function Implementation */ + +typedef struct +{ + const char *buf; + int fd; + uint64 pos; + int ungotten; +} _InContext; + +static int _in_char(_InContext *ctx) +{ + if (ctx->ungotten >= 0) + { + int c = ctx->ungotten; + ctx->ungotten = -1; + return c; + } + if (ctx->fd == -1) + { + if (ctx->buf[ctx->pos] == '\0') + return -1; + return (unsigned char)ctx->buf[ctx->pos++]; + } + char c; + if (read(ctx->fd, &c, 1) == 1) + return (unsigned char)c; + return -1; +} + +static void _unget_char(_InContext *ctx, int c) +{ + if (c != -1) + ctx->ungotten = c; +} +static void _skip_space(_InContext *ctx) +{ + int c; + while ((c = _in_char(ctx)) != -1) + { + if (!isspace(c)) + { + _unget_char(ctx, c); + break; + } + } +} + +static double _simple_strtod(const char *str, char **endptr) +{ + double val = 0.0; + int sign = 1; + if (*str == '-') + { + sign = -1; + str++; + } + else if (*str == '+') + str++; + while (*str >= '0' && *str <= '9') + { + val = val * 10.0 + (*str - '0'); + str++; + } + if (*str == '.') + { + str++; + double div = 10.0; + while (*str >= '0' && *str <= '9') + { + val += (*str - '0') / div; + div *= 10.0; + str++; + } + } + if (*str == 'e' || *str == 'E') + { + str++; + int esign = 1; + if (*str == '-') + { + esign = -1; + str++; + } + else if (*str == '+') + str++; + int eval = 0; + while (*str >= '0' && *str <= '9') + { + eval = eval * 10 + (*str - '0'); + str++; + } + double p = 1.0; + while (eval--) + p *= 10.0; + if (esign == 1) + val *= p; + else + val /= p; + } + if (endptr) + *endptr = (char *)str; + return sign * val; +} + +static int _vscanf_core(_InContext *ctx, const char *format, va_list ap) +{ + int assigned = 0; + const char *p = format; + while (*p) + { + if (isspace(*p)) + { + _skip_space(ctx); + while (isspace(*p)) + p++; + continue; + } + if (*p != '%') + { + _skip_space(ctx); + int c = _in_char(ctx); + if (c != *p) + { + _unget_char(ctx, c); + return assigned; + } + p++; + continue; + } + p++; + int suppress = 0; + if (*p == '*') + { + suppress = 1; + p++; + } + int width = -1; + while (*p >= '0' && *p <= '9') + { + if (width == -1) + width = 0; + width = width * 10 + (*p - '0'); + p++; + } + int len_mod = 0; + if (*p == 'l') + { + len_mod = 1; + p++; + if (*p == 'l') + { + len_mod = 2; + p++; + } + } + else if (*p == 'h') + { + p++; + if (*p == 'h') + p++; + } + char spec = *p++; + if (!spec) + break; + + if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x') + { + _skip_space(ctx); + char num_buf[64]; + int i = 0, c = _in_char(ctx); + if (c == '-' || c == '+') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + int base = (spec == 'x') ? 16 : (spec == 'i' ? 0 : 10); + while (c != -1 && i < 63) + { + int digit = (c >= '0' && c <= '9'); + int hex = (base == 16 || base == 0) && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + int x = (c == 'x' || c == 'X'); + if (base == 0 && i == 1 && num_buf[0] == '0' && x) + base = 16; + if (width > 0 && i >= width) + break; + if (digit || hex || x) + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + else + break; + } + num_buf[i] = '\0'; + _unget_char(ctx, c); + if (i == 0) + return assigned; + if (!suppress) + { + int64 val = strtoll(num_buf, NULL, base); + if (len_mod == 2) + *va_arg(ap, int64 *) = val; + else if (len_mod == 1) + *va_arg(ap, long *) = (long)val; + else + *va_arg(ap, int *) = (int)val; + assigned++; + } + } + else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') + { + _skip_space(ctx); + char num_buf[64]; + int i = 0, c = _in_char(ctx); + if (c == '-' || c == '+') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + while (c != -1 && i < 63) + { + if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + else + break; + } + num_buf[i] = '\0'; + _unget_char(ctx, c); + if (i == 0) + return assigned; + if (!suppress) + { + double val = _simple_strtod(num_buf, NULL); + if (len_mod == 1) + *va_arg(ap, double *) = val; + else + *va_arg(ap, float *) = (float)val; + assigned++; + } + } + else if (spec == 's') + { + _skip_space(ctx); + char *buf = suppress ? NULL : va_arg(ap, char *); + int i = 0, c = _in_char(ctx); + while (c != -1 && !isspace(c)) + { + if (width > 0 && i >= width) + break; + if (!suppress) + buf[i] = (char)c; + i++; + c = _in_char(ctx); + } + if (!suppress) + buf[i] = '\0'; + _unget_char(ctx, c); + if (i > 0 && !suppress) + assigned++; + } + else if (spec == 'c') + { + int count = (width > 0) ? width : 1; + char *buf = suppress ? NULL : va_arg(ap, char *); + for (int i = 0; i < count; i++) + { + int c = _in_char(ctx); + if (c == -1) + return assigned; + if (!suppress) + buf[i] = (char)c; + } + if (!suppress) + assigned++; + } + } + return assigned; +} + +int sscanf(const char *str, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = str; + ctx.fd = -1; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} +int scanf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = NULL; + ctx.fd = STDIN_FD; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} + +/* FILE Stream Implementation */ + +#define O_RDONLY 00 +#define O_WRONLY 01 +#define O_RDWR 02 +#define O_CREAT 0100 +#define O_TRUNC 01000 +#define O_APPEND 02000 + + +static FILE _stdin = {.fd = 0, .buf_mode = 1}; +static FILE _stdout = {.fd = 1, .buf_mode = 1}; +static FILE _stderr = {.fd = 2, .buf_mode = 2}; + +FILE *stdin = &_stdin; +FILE *stdout = &_stdout; +FILE *stderr = &_stderr; + +FILE *fopen(const char *filename, const char *mode) +{ + int flags = 0; + if (strcmp(mode, "r") == 0) + flags = O_RDONLY; + else if (strcmp(mode, "w") == 0) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else if (strcmp(mode, "a") == 0) + flags = O_WRONLY | O_CREAT | O_APPEND; + else if (strcmp(mode, "r+") == 0) + flags = O_RDWR; + else + return NULL; + + int64 fd = open(filename, flags); + if (fd < 0) + return NULL; + + FILE *f = (FILE *)malloc(sizeof(FILE)); + if (!f) + { + close(fd); + return NULL; + } + + memset(f, 0, sizeof(FILE)); + f->fd = (int)fd; + f->flags = flags; + + if (fd == 1) + f->buf_mode = 1; + else if (fd == 2) + f->buf_mode = 2; + else + f->buf_mode = 0; + return f; +} + +int fclose(FILE *stream) +{ + if (!stream) + return -1; + fflush(stream); + if (stream != stdin && stream != stdout && stream != stderr) + { + close(stream->fd); + free(stream); + } + return 0; +} + +int fflush(FILE *stream) +{ + if (!stream) + return EOF; + + if (stream->buf_write_pos > 0) + { + int64 ret = write(stream->fd, stream->buffer, stream->buf_write_pos); + if (ret < stream->buf_write_pos) + { + stream->error = 1; + stream->buf_write_pos = 0; + return EOF; + } + stream->buf_write_pos = 0; + } + return 0; +} + +int fprintf(FILE *stream, const char *format, ...) +{ + if (!stream) + return -1; + va_list ap; + va_start(ap, format); + _OutContext ctx; + ctx.buf = NULL; + ctx.limit = 0; + ctx.written = 0; + ctx.stream = stream; + _vprintf_core(&ctx, format, ap); + va_end(ap); + return (int)ctx.written; +} + +int fscanf(FILE *stream, const char *format, ...) +{ + if (!stream) + return -1; + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = NULL; + ctx.fd = stream->fd; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} + +int fputc(int c, FILE *stream) +{ + if (!stream) + return EOF; + if (stream->buf_mode == 2) + { + char ch = (char)c; + if (write(stream->fd, &ch, 1) != 1) + { + stream->error = 1; + return EOF; + } + return c; + } + + stream->buffer[stream->buf_write_pos++] = (char)c; + + if (stream->buf_write_pos >= BUFSIZ) + { + fflush(stream); + } + else if (stream->buf_mode == 1 && c == '\n') + { + fflush(stream); + } + + return c; +} + +int fputs(const char *s, FILE *stream) +{ + if (!stream) + return -1; + while (*s) + { + if (fputc(*s++, stream) == EOF) + return EOF; + } + return 0; +} + +int fgetc(FILE *stream) +{ + if (!stream) + return EOF; + + if (stream->buf_write_pos > 0) + { + fflush(stream); + } + + if (stream->buf_read_pos < stream->buf_read_cnt) + { + return (unsigned char)stream->buffer[stream->buf_read_pos++]; + } + + int64 ret = read(stream->fd, stream->buffer, BUFSIZ); + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return EOF; + } + + stream->buf_read_cnt = (int)ret; + stream->buf_read_pos = 0; + return (unsigned char)stream->buffer[stream->buf_read_pos++]; +} + +char *fgets(char *str, int n, FILE *stream) +{ + if (!stream || n <= 0) + return NULL; + int i = 0, c; + while (i < n - 1) + { + c = fgetc(stream); + if (c == -1) + { + if (i == 0) + return NULL; + break; + } + str[i++] = (char)c; + if (c == '\n') + break; + } + str[i] = '\0'; + return str; +} + +uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream) +{ + if (!stream || size == 0 || nmemb == 0) + return 0; + + uint64 total_bytes = size * nmemb; + uint64 bytes_read = 0; + char *dst = (char *)ptr; + + if (stream->buf_write_pos > 0) + fflush(stream); + + while (bytes_read < total_bytes) + { + if (stream->buf_read_pos < stream->buf_read_cnt) + { + uint64 available = stream->buf_read_cnt - stream->buf_read_pos; + uint64 remaining = total_bytes - bytes_read; + uint64 to_copy = (available < remaining) ? available : remaining; + + memcpy(dst + bytes_read, stream->buffer + stream->buf_read_pos, to_copy); + + stream->buf_read_pos += to_copy; + bytes_read += to_copy; + } + else + { + uint64 remaining = total_bytes - bytes_read; + + if (remaining >= BUFSIZ) + { + int64 ret = read(stream->fd, dst + bytes_read, remaining); + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return bytes_read / size; + } + bytes_read += ret; + } + else + { + stream->buf_read_pos = 0; + stream->buf_read_cnt = 0; + int64 ret = read(stream->fd, stream->buffer, BUFSIZ); + + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return bytes_read / size; + } + stream->buf_read_cnt = (int)ret; + } + } + } + return bytes_read / size; +} + +uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream) +{ + if (!stream || !ptr || size == 0 || nmemb == 0) + return 0; + uint64 total_bytes = size * nmemb; + const char *src = (const char *)ptr; + uint64 bytes_written = 0; + + if (stream->buf_mode == 2) + { + while (bytes_written < total_bytes) + { + int64 ret = write(stream->fd, src + bytes_written, total_bytes - bytes_written); + if (ret <= 0) + { + stream->error = 1; + break; + } + bytes_written += ret; + } + return bytes_written / size; + } + + while (bytes_written < total_bytes) + { + uint64 space_left = BUFSIZ - stream->buf_write_pos; + uint64 remaining = total_bytes - bytes_written; + + if (remaining <= space_left) + { + memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, remaining); + stream->buf_write_pos += remaining; + bytes_written += remaining; + if (stream->buf_write_pos == BUFSIZ) + fflush(stream); + } + else + { + memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, space_left); + stream->buf_write_pos += space_left; + bytes_written += space_left; + fflush(stream); + } + } + + return bytes_written / size; +} + +int fseek(FILE *stream, long offset, int whence) +{ + if (!stream) + return -1; + + if (stream->buf_write_pos > 0) + { + fflush(stream); + } + stream->buf_read_pos = 0; + stream->buf_read_cnt = 0; + stream->eof = 0; + + if (lseek(stream->fd, offset, whence) < 0) + { + stream->error = 1; + return -1; + } + return 0; +} +long ftell(FILE *stream) +{ + if (!stream) + return -1L; + int64 ret = lseek(stream->fd, 0, SEEK_CUR); + if (ret < 0) + { + stream->error = 1; + return -1L; + } + return (long)ret; +} +void rewind(FILE *stream) +{ + if (stream) + { + fseek(stream, 0, SEEK_SET); + stream->error = 0; + } +} + +int feof(FILE *stream) +{ + return (stream) ? stream->eof : 1; +} +int ferror(FILE *stream) +{ + return (stream) ? stream->error : 1; +} +void clearerr(FILE *stream) +{ + if (stream) + { + stream->error = 0; + stream->eof = 0; + } +} + +int ungetc(int c, FILE *stream) +{ + if (!stream || c == EOF) + return EOF; + + if (stream->buf_read_pos > 0) + { + stream->buf_read_pos--; + stream->buffer[stream->buf_read_pos] = (char)c; + stream->eof = 0; + return c; + } + return EOF; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/stdlib.c b/lume-legacy/src/ulib/stdlib.c new file mode 100644 index 0000000..8b22b6f --- /dev/null +++ b/lume-legacy/src/ulib/stdlib.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "ulib/ctypes.h" + +extern int sys_exit(int status); +extern void *sbrk(long increment); + +#undef errno +int errno = 0; + +#define ATEXIT_MAX 32 +static void (*__atexit_funcs[ATEXIT_MAX])(void); +static int __atexit_func_count = 0; + +int atexit(void (*function)(void)) +{ + if (__atexit_func_count >= ATEXIT_MAX) + return -1; + __atexit_funcs[__atexit_func_count++] = function; + return 0; +} + +int exit(int status) +{ + while (__atexit_func_count > 0) + { + (__atexit_funcs[--__atexit_func_count])(); + } + sys_exit(status); + + while (1) + ; + return 0; +} + +void abort(void) +{ + exit(127); +} + +char *getenv(const char *name) +{ + /* None */ + return NULL; +} + +int system(const char *command) +{ + /* None */ + return -1; +} + +/* Memory Management */ +#define ALIGNMENT 16 +#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1)) + +typedef struct header +{ + size_t size; + struct header *next; +} header_t; + +static header_t base; +static header_t *freep = NULL; + + +#define LOCK() +#define UNLOCK() + +void free(void *ptr) +{ + header_t *bp, *p; + + if (!ptr) + return; + + + bp = (header_t *)ptr - 1; + + LOCK(); + + + for (p = freep; !(bp > p && bp < p->next); p = p->next) + { + if (p >= p->next && (bp > p || bp < p->next)) + break; + } + + if ((char *)bp + bp->size == (char *)p->next) + { + bp->size += p->next->size; + bp->next = p->next->next; + } + else + { + bp->next = p->next; + } + + if ((char *)p + p->size == (char *)bp) + { + p->size += bp->size; + p->next = bp->next; + } + else + { + p->next = bp; + } + + freep = p; + UNLOCK(); +} + +static header_t *morecore(size_t nu) +{ + char *cp; + header_t *up; + + if (nu < 4096) + nu = 4096; + + nu = ALIGN(nu); + + cp = (char *)sbrk(nu); + if (cp == (void *)-1) + return NULL; + + up = (header_t *)cp; + up->size = nu; + + free((void *)(up + 1)); + return freep; +} + +void *malloc(size_t size) +{ + header_t *p, *prevp; + + if (size == 0) + return NULL; + + size = ALIGN(size + sizeof(header_t)); + + LOCK(); + + if ((prevp = freep) == NULL) + { + base.next = freep = prevp = &base; + base.size = 0; + } + + for (p = prevp->next;; prevp = p, p = p->next) + { + if (p->size >= size) + { + if (p->size == size) + { + prevp->next = p->next; + } + else + { + p->size -= size; + + p = (header_t *)((char *)p + p->size); + + p->size = size; + } + freep = prevp; + UNLOCK(); + return (void *)(p + 1); + } + + if (p == freep) + { + if ((p = morecore(size)) == NULL) + { + UNLOCK(); + errno = ENOMEM; + return NULL; + } + } + } +} + +void *calloc(size_t nmemb, size_t size) +{ + size_t total; + if (__builtin_mul_overflow(nmemb, size, &total)) + { + errno = ENOMEM; + return NULL; + } + void *p = malloc(total); + if (p) + memset(p, 0, total); + return p; +} + +void *realloc(void *ptr, size_t size) +{ + if (!ptr) + return malloc(size); + if (size == 0) + { + free(ptr); + return NULL; + } + + header_t *bp = (header_t *)ptr - 1; + size_t new_size = ALIGN(size + sizeof(header_t)); + + if (bp->size >= new_size) + return ptr; + + void *new_ptr = malloc(size); + if (!new_ptr) + return NULL; + + memcpy(new_ptr, ptr, bp->size - sizeof(header_t)); + free(ptr); + return new_ptr; +} + +/* String Conversion*/ + +unsigned long long strtoull(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + unsigned long long cutoff; + int c; + int neg = 0, any, cutlim; + + if (base < 0 || base == 1 || base > 36) + { + if (endptr) + *endptr = (char *)nptr; + errno = 0; /* EINVAL */ + return 0; + } + + do + { + c = *s++; + } while (isspace(c)); + + if (c == '-') + { + neg = 1; + c = *s++; + } + else if (c == '+') + { + c = *s++; + } + + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) + { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = (c == '0' ? 8 : 10); + + cutoff = ULLONG_MAX / (unsigned long long)base; + cutlim = ULLONG_MAX % (unsigned long long)base; + + for (acc = 0, any = 0;; c = *s++) + { + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + + if (c >= base) + break; + + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else + { + any = 1; + acc *= base; + acc += c; + } + } + + if (any < 0) + { + acc = ULLONG_MAX; + errno = ERANGE; + } + else if (neg) + acc = -acc; + + if (endptr != 0) + *endptr = (char *)(any ? s - 1 : nptr); + + return acc; +} + +long long strtoll(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + int c, neg = 0; + + do + { + c = *s++; + } while (isspace(c)); + if (c == '-') + neg = 1; + + acc = strtoull(nptr, endptr, base); + + if (errno == ERANGE && acc == ULLONG_MAX) + return neg ? LLONG_MIN : LLONG_MAX; + + if (!neg && acc > (unsigned long long)LLONG_MAX) + { + errno = ERANGE; + return LLONG_MAX; + } + if (neg && acc > (unsigned long long)LLONG_MAX + 1) + { + errno = ERANGE; + return LLONG_MIN; + } + + return neg ? -(long long)acc : (long long)acc; +} + +unsigned long strtoul(const char *nptr, char **endptr, int base) +{ + unsigned long long ret = strtoull(nptr, endptr, base); + if (ret > ULONG_MAX) + { + errno = ERANGE; + return ULONG_MAX; + } + return (unsigned long)ret; +} + +long strtol(const char *nptr, char **endptr, int base) +{ + long long ret = strtoll(nptr, endptr, base); + if (ret > LONG_MAX) + { + errno = ERANGE; + return LONG_MAX; + } + if (ret < LONG_MIN) + { + errno = ERANGE; + return LONG_MIN; + } + return (long)ret; +} + +int atoi(const char *nptr) { return (int)strtol(nptr, NULL, 10); } +long atol(const char *nptr) { return strtol(nptr, NULL, 10); } +long long atoll(const char *nptr) { return strtoll(nptr, NULL, 10); } + +double strtod(const char *nptr, char **endptr) +{ + double val = 0.0; + int sign = 1; + const char *s = nptr; + + while (isspace(*s)) + s++; + if (*s == '-') + { + sign = -1; + s++; + } + else if (*s == '+') + s++; + + while (isdigit(*s)) + { + val = val * 10.0 + (*s - '0'); + s++; + } + if (*s == '.') + { + double factor = 1.0; + s++; + while (isdigit(*s)) + { + factor *= 0.1; + val += (*s - '0') * factor; + s++; + } + } + + if (endptr) + *endptr = (char *)s; + return val * sign; +} + +double atof(const char *nptr) { return strtod(nptr, NULL); } +float strtof(const char *nptr, char **endptr) { return (float)strtod(nptr, endptr); } + +/* Sort & Search */ + +static void swap_bytes(char *a, char *b, size_t size) +{ + char temp; + while (size--) + { + temp = *a; + *a++ = *b; + *b++ = temp; + } +} + +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) +{ + if (nmemb < 2 || size == 0) + return; + + char *base_ptr = (char *)base; + char *pivot = base_ptr + (nmemb - 1) * size; + char *i = base_ptr; + char *j = base_ptr; + + /* Partition */ + for (; j < pivot; j += size) + { + if (compar(j, pivot) < 0) + { + if (i != j) + swap_bytes(i, j, size); + i += size; + } + } + swap_bytes(i, pivot, size); + + /* Recurse */ + size_t left_count = (i - base_ptr) / size; + qsort(base, left_count, size, compar); + qsort(i + size, nmemb - left_count - 1, size, compar); +} + +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) +{ + size_t l = 0, u = nmemb; + while (l < u) + { + size_t idx = (l + u) / 2; + const void *p = (void *)((const char *)base + (idx * size)); + int cmp = compar(key, p); + if (cmp < 0) + u = idx; + else if (cmp > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} + +/* Math & Mics */ + +int abs(int j) { return (j < 0) ? -j : j; } +long labs(long j) { return (j < 0) ? -j : j; } +long long llabs(long long j) { return (j < 0) ? -j : j; } + +div_t div(int numer, int denom) +{ + div_t res = {numer / denom, numer % denom}; + return res; +} +ldiv_t ldiv(long numer, long denom) +{ + ldiv_t res = {numer / denom, numer % denom}; + return res; +} +lldiv_t lldiv(long long numer, long long denom) +{ + lldiv_t res = {numer / denom, numer % denom}; + return res; +} + +static unsigned long next = 1; +int rand(void) +{ + next = next * 1103515245 + 12345; + return (unsigned int)(next / 65536) % 32768; +} +void srand(unsigned int seed) { next = seed; } + +/* Stub */ +int mblen(const char *s, size_t n) { return (s && *s && n) ? 1 : 0; } +int mbtowc(wchar_t *pwc, const char *s, size_t n) +{ + if (!s) + return 0; + if (n == 0) + return -1; + if (pwc) + *pwc = (wchar_t)*s; + return (*s != 0); +} +int wctomb(char *s, wchar_t wchar) +{ + if (!s) + return 0; + *s = (char)wchar; + return 1; +} +size_t mbstowcs(wchar_t *dest, const char *src, size_t n) +{ + size_t i = 0; + while (src[i] && i < n) + { + dest[i] = (wchar_t)src[i]; + i++; + } + if (i < n) + dest[i] = 0; + return i; +} +size_t wcstombs(char *dest, const wchar_t *src, size_t n) +{ + size_t i = 0; + while (src[i] && i < n) + { + dest[i] = (char)src[i]; + i++; + } + if (i < n) + dest[i] = 0; + return i; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/string.c b/lume-legacy/src/ulib/string.c new file mode 100644 index 0000000..c3dbe9a --- /dev/null +++ b/lume-legacy/src/ulib/string.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/string.h" +#include "ulib/stdlib.h" + + +/* Memory Manipulation Functions */ + +void *memset(void *dst, int c, size_t n) +{ + char *cdst = (char *)dst; + for (size_t i = 0; i < n; i++) + cdst[i] = (char)c; + return dst; +} + +void *memmove(void *vdst, const void *vsrc, size_t n) +{ + char *dst = (char *)vdst; + const char *src = (const char *)vsrc; + if (dst == src) + return vdst; + + if (src > dst) + { + while (n--) + *dst++ = *src++; + } + else + { + dst += n; + src += n; + while (n-- > 0) + *--dst = *--src; + } + return vdst; +} + +void *memcpy(void *vdst, const void *vsrc, size_t n) +{ + return memmove(vdst, vsrc, n); +} + +int memcmp(const void *s1, const void *s2, size_t n) +{ + const unsigned char *p1 = (const unsigned char *)s1; + const unsigned char *p2 = (const unsigned char *)s2; + while (n--) + { + if (*p1 != *p2) + return *p1 - *p2; + p1++; + p2++; + } + return 0; +} + +void *memchr(const void *s, int c, size_t n) +{ + const unsigned char *p = (const unsigned char *)s; + while (n--) + { + if (*p == (unsigned char)c) + return (void *)p; + p++; + } + return NULL; +} + +/* String Manipulation Functions */ + +size_t strlen(const char *s) +{ + const char *p = s; + while (*p) + p++; + return (size_t)(p - s); +} + +char *strcpy(char *dst, const char *src) +{ + char *os = dst; + while ((*dst++ = *src++) != 0) + ; + return os; +} + +char *strncpy(char *dst, const char *src, size_t n) +{ + char *os = dst; + size_t i; + + for (i = 0; i < n && src[i] != '\0'; i++) + dst[i] = src[i]; + + for (; i < n; i++) + dst[i] = '\0'; + + return os; +} + +char *strcat(char *dst, const char *src) +{ + char *os = dst; + while (*dst) + dst++; + while ((*dst++ = *src++) != 0) + ; + return os; +} + +char *strncat(char *dst, const char *src, size_t n) +{ + char *os = dst; + size_t dest_len = strlen(dst); + size_t i; + + dst += dest_len; + + for (i = 0; i < n && src[i] != '\0'; i++) + dst[i] = src[i]; + + dst[i] = '\0'; + + return os; +} + +int strcmp(const char *p, const char *q) +{ + while (*p && *p == *q) + { + p++; + q++; + } + return (int)((unsigned char)*p - (unsigned char)*q); +} + +int strncmp(const char *s1, const char *s2, size_t n) +{ + if (n == 0) + return 0; + + while (n-- > 0 && *s1 && *s1 == *s2) + { + if (n == 0 || *s1 == '\0') + break; + s1++; + s2++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +char *strchr(const char *s, int c) +{ + while (*s != (char)c) + { + if (!*s++) + return NULL; + } + return (char *)s; +} + +char *strrchr(const char *s, int c) +{ + const char *last = NULL; + do + { + if (*s == (char)c) + last = s; + } while (*s++); + + return (char *)last; +} + + +char *strstr(const char *haystack, const char *needle) +{ + size_t n_len = strlen(needle); + if (n_len == 0) + return (char *)haystack; + + for (; *haystack; haystack++) + { + if (*haystack != *needle) + continue; + + if (strncmp(haystack, needle, n_len) == 0) + return (char *)haystack; + } + return NULL; +} + +char *strdup(const char *s) +{ + size_t len = strlen(s) + 1; + char *new_str = (char *)malloc(len); + if (new_str == NULL) + return NULL; + + return (char *)memcpy(new_str, s, len); +} + +size_t strlcpy(char *dst, const char *src, size_t size) +{ + size_t src_len = strlen(src); + + if (size > 0) + { + size_t copy_len = (src_len >= size) ? size - 1 : src_len; + memcpy(dst, src, copy_len); + dst[copy_len] = '\0'; + } + + return src_len; +} + +size_t strlcat(char *dst, const char *src, size_t size) +{ + size_t dst_len = 0; + size_t src_len = strlen(src); + + while (dst_len < size && dst[dst_len] != '\0') + dst_len++; + + if (dst_len == size) + return size + src_len; + + size_t space_left = size - dst_len; + size_t copy_len = (src_len >= space_left) ? space_left - 1 : src_len; + + memcpy(dst + dst_len, src, copy_len); + dst[dst_len + copy_len] = '\0'; + + return dst_len + src_len; +} + + +size_t strspn(const char *s, const char *accept) +{ + const char *p = s; + const char *a; + + while (*p) + { + for (a = accept; *a; a++) + { + if (*p == *a) + break; + } + if (*a == '\0') + return (size_t)(p - s); + p++; + } + return (size_t)(p - s); +} + +size_t strcspn(const char *s, const char *reject) +{ + const char *p = s; + const char *r; + + while (*p) + { + for (r = reject; *r; r++) + { + if (*p == *r) + return (size_t)(p - s); + } + p++; + } + return (size_t)(p - s); +} + +char *strpbrk(const char *s, const char *accept) +{ + const char *a; + while (*s) + { + for (a = accept; *a; a++) + { + if (*s == *a) + return (char *)s; + } + s++; + } + return NULL; +} + +char *strtok(char *str, const char *delim) +{ + static char *olds; + char *token; + + if (str == NULL) + str = olds; + + str += strspn(str, delim); + if (*str == '\0') + { + olds = str; + return NULL; + } + + token = str; + str = strpbrk(token, delim); + + if (str == NULL) + { + olds = strchr(token, '\0'); + } + else + { + *str = '\0'; + olds = str + 1; + } + + return token; +} + +char *strerror(int errnum) +{ + + switch (errnum) + { + case 0: + return "Success"; + // case EINVAL: return "Invalid argument"; + case ENOMEM: return "Out of memory"; + + default: + return "Unknown error"; + } +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/syscalls.c b/lume-legacy/src/ulib/syscalls.c new file mode 100644 index 0000000..676bf18 --- /dev/null +++ b/lume-legacy/src/ulib/syscalls.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * Libc Syscall Shims + */ + +#include "common/types.h" +#include "user/user.h" + +// Define external raw syscalls from usys.S +extern int sys_openat(int dfd, const char *filename, int flags, int mode); +extern int sys_mknodat(int dfd, const char *filename, int mode, int dev); +extern int sys_mkdirat(int dfd, const char *path, int mode); +extern int sys_unlinkat(int dfd, const char *path, int flags); +extern int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); +extern int sys_chdir(const char *path); +extern int sys_dup(int oldfd); +extern int sys_fstat(int fd, void *statbuf); +extern int sys_read(int fd, void *buf, int count); +extern int sys_write(int fd, const void *buf, int count); +extern int sys_close(int fd); +extern int sys_pipe2(int pipefd[2], int flags); +extern int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); +extern int sys_execve(const char *path, char *const argv[], char *const envp[]); +extern int sys_wait4(int pid, int *status, int options, void *rusage); +extern int sys_exit(int status); +extern int sys_getpid(); +extern int sys_brk(int n); +extern int sys_kill(int pid, int sig); +extern int sys_nanosleep(void *req, void *rem); +extern int sys_reboot(int magic1, int magic2, int cmd, void *arg); +extern int sys_lseek(int fd, int offset, int whence); +extern void sys_putc(char c); +extern int sys_disk_test(); + +// --- POSIX Implementation --- + +int open(const char *path, int flags) +{ + return sys_openat(AT_FDCWD, path, flags, 0); +} + +int mknod(const char *path, short major, short minor) +{ + // Pack major/minor into simplified dev_t (major << 20 | minor) + int dev = (major << 20) | (minor & 0xFFFFF); + return sys_mknodat(AT_FDCWD, path, 0, dev); +} + +int mkdir(const char *path) +{ + return sys_mkdirat(AT_FDCWD, path, 0); +} + +int unlink(const char *path) +{ + return sys_unlinkat(AT_FDCWD, path, 0); +} + +int link(const char *oldpath, const char *newpath) +{ + return sys_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0); +} + +int chdir(const char *path) +{ + return sys_chdir(path); +} + +int fork() +{ + return sys_clone(0, 0, 0, 0, 0); +} + +int exec(char *path, char **argv) +{ + return sys_execve(path, argv, 0); +} + +int wait(uint64 status_addr) +{ + return sys_wait4(-1, (int *)status_addr, 0, 0); +} + +int pipe(int p[]) +{ + return sys_pipe2(p, 0); +} + +int read(int fd, void *buf, int n) +{ + return sys_read(fd, buf, n); +} + +int write(int fd, const void *buf, int n) +{ + return sys_write(fd, buf, n); +} + +int close(int fd) +{ + return sys_close(fd); +} + +int fstat(int fd, void *st) +{ + return sys_fstat(fd, st); +} + +int dup(int fd) +{ + return sys_dup(fd); +} + +int getpid() +{ + return sys_getpid(); +} + +char *sbrk(int n) +{ + return (char *)(long)sys_brk(n); +} + +int kill(int pid) +{ + return sys_kill(pid, 0); +} + +int sleep(int ticks) +{ + return sys_nanosleep((void *)(long)ticks, 0); +} + +int shutdown() +{ + return sys_reboot(0, 0, 0, 0); +} + +void putc(char c) +{ + sys_putc(c); +} + +int disk_test() +{ + return sys_disk_test(); +} + +int lseek(int fd, int offset, int whence) +{ + return sys_lseek(fd, offset, whence); +} \ No newline at end of file diff --git a/lume-legacy/src/user/cat.cc b/lume-legacy/src/user/cat.cc new file mode 100644 index 0000000..b518d77 --- /dev/null +++ b/lume-legacy/src/user/cat.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +void cat(int fd) +{ + int n; + while ((n = read(fd, buf, sizeof(buf))) > 0) + { + if (write(1, buf, n) != n) + { + printf("cat: write error\n"); + exit(1); + } + } + if (n < 0) + { + printf("cat: read error\n"); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + int fd, i; + + if (argc <= 1) + { + cat(0); + exit(0); + } + + for (i = 1; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("cat: cannot open %s\n", argv[i]); + exit(1); + } + cat(fd); + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/cp.cc b/lume-legacy/src/user/cp.cc new file mode 100644 index 0000000..ca35e82 --- /dev/null +++ b/lume-legacy/src/user/cp.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +int main(int argc, char *argv[]) +{ + int fd_src, fd_dst, n; + + if (argc != 3) + { + printf("Usage: cp src dst\n"); + exit(1); + } + + if ((fd_src = open(argv[1], O_RDONLY)) < 0) + { + printf("cp: cannot open source %s\n", argv[1]); + exit(1); + } + + if ((fd_dst = open(argv[2], O_WRONLY | O_CREATE | O_TRUNC)) < 0) + { + printf("cp: cannot create destination %s\n", argv[2]); + close(fd_src); + exit(1); + } + + while ((n = read(fd_src, buf, sizeof(buf))) > 0) + { + if (write(fd_dst, buf, n) != n) + { + printf("cp: write error\n"); + exit(1); + } + } + + if (n < 0) + { + printf("cp: read error\n"); + } + + close(fd_src); + close(fd_dst); + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/echo.cc b/lume-legacy/src/user/echo.cc new file mode 100644 index 0000000..cc9dc2a --- /dev/null +++ b/lume-legacy/src/user/echo.cc @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "user/user.h" +#include "ulib/stdio.h" +#include "ulib/string.h" + +int main(int argc, char* argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + write(1, argv[i], strlen(argv[i])); + if (i + 1 < argc) + { + write(1, " ", 1); + } + else + { + write(1, "\n", 1); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/entry.S b/lume-legacy/src/user/entry.S new file mode 100644 index 0000000..41e0964 --- /dev/null +++ b/lume-legacy/src/user/entry.S @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.global _start + +_start: + call main + + call exit + +1: j 1b \ No newline at end of file diff --git a/lume-legacy/src/user/grep.cc b/lume-legacy/src/user/grep.cc new file mode 100644 index 0000000..1e1c42d --- /dev/null +++ b/lume-legacy/src/user/grep.cc @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[1024]; + +void grep(char *pattern, int fd) +{ + int n, m; + char *p, *q; + + m = 0; + while ((n = read(fd, buf + m, sizeof(buf) - m - 1)) > 0) + { + m += n; + buf[m] = '\0'; + p = buf; + while ((q = strchr(p, '\n')) != 0) + { + *q = 0; + if (strstr(p, pattern) != 0) + { + printf("%s\n", p); + } + p = q + 1; + } + if (p == buf) + m = 0; + if (m > 0) + { + m -= p - buf; + memmove(buf, p, m); + } + } +} + +int main(int argc, char *argv[]) +{ + int fd, i; + char *pattern; + + if (argc < 2) + { + printf("Usage: grep pattern [file...]\n"); + exit(1); + } + pattern = argv[1]; + + if (argc == 2) + { + grep(pattern, 0); // stdin + exit(0); + } + + for (i = 2; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("grep: cannot open %s\n", argv[i]); + exit(1); + } + grep(pattern, fd); + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/init.cc b/lume-legacy/src/user/init.cc new file mode 100644 index 0000000..376e697 --- /dev/null +++ b/lume-legacy/src/user/init.cc @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "drivers/uart_color.h" + +int main() +{ + char *argv[] = {(char *)"sh", 0}; + + printf(ANSI_BLUE "\n[Init] Starting Lume OS Shell..." ANSI_RESET); + printf("\n"); + + while (1) + { + // printf("fork start\n"); + int pid = fork(); + // printf("fork suceess, pid=%i\n", pid); + + if (pid < 0) + { + printf("[Init] fork failed!\n"); + exit(1); + } + + if (pid == 0) + { + + char sh_path[] = "/sh"; + exec(sh_path, argv); + + printf("[Init] exec sh failed\n"); + return 0; + exit(1); + } + else + { + // break; + int status; + int wpid; + while (1) + { + wpid = wait((uint64)&status); + + if (wpid == pid) + { + printf("Shell Exit"); + break; + } + if (wpid < 0) + { + printf("[Init] wait error or no child\n"); + + break; + } + } + } + } + return 0; +} \ No newline at end of file diff --git a/lume-legacy/src/user/kill.cc b/lume-legacy/src/user/kill.cc new file mode 100644 index 0000000..d38cdd6 --- /dev/null +++ b/lume-legacy/src/user/kill.cc @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2) + { + printf("Usage: kill pid...\n"); + exit(1); + } + for (i = 1; i < argc; i++) + { + int pid = atoi(argv[i]); + if (kill(pid) < 0) + { + printf("kill: failed to kill %d\n", pid); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/ln.cc b/lume-legacy/src/user/ln.cc new file mode 100644 index 0000000..a21ddf9 --- /dev/null +++ b/lume-legacy/src/user/ln.cc @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + printf("Usage: ln old new\n"); + exit(1); + } + if (link(argv[1], argv[2]) < 0) + { + printf("link %s %s failed\n", argv[1], argv[2]); + exit(1); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/ls.cc b/lume-legacy/src/user/ls.cc new file mode 100644 index 0000000..c44c799 --- /dev/null +++ b/lume-legacy/src/user/ls.cc @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + + +struct FAT32_DirEnt +{ + char name[11]; + uint8 attr; + uint8 lcase; + uint8 ctime_cs; + uint16 ctime; + uint16 cdate; + uint16 adate; + uint16 fstClusHI; + uint16 wtime; + uint16 wdate; + uint16 fstClusLO; + uint32 fileSize; +} __attribute__((packed)); + +#define ATTR_DIRECTORY 0x10 +#define ATTR_LONG_NAME 0x0F + +void fmt_name(char *dst, const char *src) +{ + int i; + char *d = dst; + + for (i = 0; i < 8; i++) + { + if (src[i] == ' ') + break; + *d++ = src[i]; + } + + if (src[8] != ' ') + { + *d++ = '.'; + for (i = 8; i < 11; i++) + { + if (src[i] == ' ') + break; + *d++ = src[i]; + } + } + *d = 0; + + for (d = dst; *d; d++) + { + if (*d >= 'A' && *d <= 'Z') + *d += 32; + } +} + +void ls(const char *path) +{ + int fd; + struct FAT32_DirEnt de; + char name_buf[16]; + + if ((fd = open(path, O_RDONLY)) < 0) + { + printf("ls: cannot open %s\n", path); + return; + } + + while (read(fd, &de, sizeof(de)) == sizeof(de)) + { + if (de.name[0] == 0) + { + break; + } + + if ((unsigned char)de.name[0] == 0xE5) + { + continue; + } + + if ((de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) + { + continue; + } + + fmt_name(name_buf, de.name); + + printf("%s %d\t%s\n", + (de.attr & ATTR_DIRECTORY) ? "" : " ", + de.fileSize, + name_buf); + } + + close(fd); +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + ls("."); + } + else + { + for (int i = 1; i < argc; i++) + { + ls(argv[i]); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/mkdir.cc b/lume-legacy/src/user/mkdir.cc new file mode 100644 index 0000000..6da8b49 --- /dev/null +++ b/lume-legacy/src/user/mkdir.cc @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "user/user.h" + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + printf("Usage: mkdir ...\n"); + exit(1); + } + + for (int i = 1; i < argc; i++) + { + if (mkdir(argv[i]) < 0) + { + printf("mkdir: cannot create directory '%s'\n", argv[i]); + } + } + + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/mv.cc b/lume-legacy/src/user/mv.cc new file mode 100644 index 0000000..a196145 --- /dev/null +++ b/lume-legacy/src/user/mv.cc @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + printf("Usage: mv old new\n"); + exit(1); + } + + if (link(argv[1], argv[2]) < 0) + { + printf("mv: failed to link %s to %s\n", argv[1], argv[2]); + exit(1); + } + + if (unlink(argv[1]) < 0) + { + printf("mv: failed to unlink %s\n", argv[1]); + exit(1); + } + + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/rm.cc b/lume-legacy/src/user/rm.cc new file mode 100644 index 0000000..4777ade --- /dev/null +++ b/lume-legacy/src/user/rm.cc @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2) + { + printf("Usage: rm files...\n"); + exit(1); + } + + for (i = 1; i < argc; i++) + { + if (unlink(argv[i]) < 0) + { + printf("rm: %s failed to delete\n", argv[i]); + exit(1); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/sh.cc b/lume-legacy/src/user/sh.cc new file mode 100644 index 0000000..47aad9e --- /dev/null +++ b/lume-legacy/src/user/sh.cc @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" +#include "drivers/uart_color.h" + +constexpr int MAX_ARGS = 10; +const char *WHITESPACE = " \t\r\n\v"; +const char *SYMBOLS = "<|>&;()"; + +void panic(const char *s) +{ + printf("shell panic: %s\n", s); + exit(1); +} + +int fork_or_panic() +{ + int pid = fork(); + if (pid == -1) + panic("fork"); + return pid; +} + +struct Command +{ + virtual ~Command() = default; + + virtual void run() = 0; +}; + +struct ExecCommand : public Command +{ + char *argv[MAX_ARGS]; + + ExecCommand() + { + memset(argv, 0, sizeof(argv)); + } + + void run() override + { + if (argv[0] == nullptr) + exit(1); + + exec(argv[0], argv); + + printf("exec %s failed\n", argv[0]); + exit(1); + } +}; + +struct RedirCommand : public Command +{ + Command *sub_cmd; + char *file; + char *efile; // file name end pointer (used by parser) + int mode; + int fd; + + RedirCommand(Command *c, char *f, char *ef, int m, int fd_in) + : sub_cmd(c), file(f), efile(ef), mode(m), fd(fd_in) {} + + ~RedirCommand() { delete sub_cmd; } + + void run() override + { + close(fd); + + int newfd = open(file, mode); + + if (newfd < 0) + { + printf("open %s failed\n", file); + exit(1); + } + + if (newfd != fd) + { + + if (dup(newfd) != fd) + { + printf("Redirection failed: could not move fd %d to %d\n", newfd, fd); + exit(1); + } + close(newfd); + } + + sub_cmd->run(); + } +}; + +struct PipeCommand : public Command +{ + Command *left; + Command *right; + + PipeCommand(Command *l, Command *r) : left(l), right(r) {} + ~PipeCommand() + { + delete left; + delete right; + } + + void run() override + { + int p[2]; + if (pipe(p) < 0) + panic("pipe"); + + if (fork_or_panic() == 0) + { + // Left child: write to pipe + close(1); // close stdout + dup(p[1]); // dup write-end to stdout + close(p[0]); + close(p[1]); + left->run(); + } + + if (fork_or_panic() == 0) + { + // Right child: read from pipe + close(0); // close stdin + dup(p[0]); // dup read-end to stdin + close(p[0]); + close(p[1]); + right->run(); + } + + close(p[0]); + close(p[1]); + wait(0); + wait(0); + exit(0); + } +}; + +struct ListCommand : public Command +{ + Command *left; + Command *right; + + ListCommand(Command *l, Command *r) : left(l), right(r) {} + ~ListCommand() + { + delete left; + delete right; + } + + void run() override + { + if (fork_or_panic() == 0) + { + left->run(); + } + wait(0); + right->run(); + } +}; + +struct BackCommand : public Command +{ + Command *sub_cmd; + + BackCommand(Command *c) : sub_cmd(c) {} + ~BackCommand() { delete sub_cmd; } + + void run() override + { + if (fork_or_panic() == 0) + { + sub_cmd->run(); + } + // Parent doesn't wait + exit(0); + } +}; + +class Parser +{ +public: + static Command *parse(char *s) + { + char *es = s + strlen(s); + Command *cmd = parse_line(&s, es); + peek(&s, es, ""); + if (s != es) + { + printf("leftovers: %s\n", s); + // panic("syntax"); // strict syntax check + } + terminate_strings(cmd); + return cmd; + } + +private: + // Helper: NUL-terminate strings found during parsing + static void terminate_strings(Command *cmd) + { + if (!cmd) + return; + } + + static char get_token(char **ps, char *es, char **q, char **eq) + { + char *s; + int ret; + + s = *ps; + while (s < es && strchr(WHITESPACE, *s)) + s++; + if (q) + *q = s; + ret = *s; + switch (*s) + { + case 0: + break; + case '|': + case '(': + case ')': + case ';': + case '&': + case '<': + s++; + break; + case '>': + s++; + if (*s == '>') + { + ret = '+'; + s++; + } + break; + default: + ret = 'a'; + while (s < es && !strchr(WHITESPACE, *s) && !strchr(SYMBOLS, *s)) + s++; + break; + } + if (eq) + *eq = s; + + while (s < es && strchr(WHITESPACE, *s)) + s++; + *ps = s; + return ret; + } + + static int peek(char **ps, char *es, const char *toks) + { + char *s = *ps; + while (s < es && strchr(WHITESPACE, *s)) + s++; + *ps = s; + return *s && strchr(toks, *s); + } + + // Recursive Descent Parsers + + static Command *parse_line(char **ps, char *es) + { + Command *cmd = parse_pipe(ps, es); + while (peek(ps, es, "&")) + { + get_token(ps, es, 0, 0); + cmd = new BackCommand(cmd); + } + if (peek(ps, es, ";")) + { + get_token(ps, es, 0, 0); + cmd = new ListCommand(cmd, parse_line(ps, es)); + } + return cmd; + } + + static Command *parse_pipe(char **ps, char *es) + { + Command *cmd = parse_exec(ps, es); + if (peek(ps, es, "|")) + { + get_token(ps, es, 0, 0); + cmd = new PipeCommand(cmd, parse_pipe(ps, es)); + } + return cmd; + } + + static Command *parse_exec(char **ps, char *es) + { + char *q, *eq; + int tok, argc; + ExecCommand *cmd = new ExecCommand(); + Command *ret = cmd; + + argc = 0; + ret = parse_redirs(ret, ps, es); + while (!peek(ps, es, "|)&;")) + { + if ((tok = get_token(ps, es, &q, &eq)) == 0) + break; + if (tok != 'a') + panic("syntax"); + + cmd->argv[argc] = q; + *eq = 0; // Temp + + argc++; + if (argc >= MAX_ARGS) + panic("too many args"); + ret = parse_redirs(ret, ps, es); + } + cmd->argv[argc] = 0; + return ret; + } + + static Command *parse_redirs(Command *cmd, char **ps, char *es) + { + int tok; + char *q, *eq; + + while (peek(ps, es, "<>")) + { + tok = get_token(ps, es, 0, 0); + if (get_token(ps, es, &q, &eq) != 'a') + panic("missing file for redirection"); + + *eq = 0; // Null terminate filename immediately + + switch (tok) + { + case '<': + cmd = new RedirCommand(cmd, q, eq, O_RDONLY, 0); + break; + case '>': + cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE | O_TRUNC, 1); + break; + case '+': // >> + cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE, 1); // Append not fully supported in simple OS yet? + break; + } + } + return cmd; + } +}; + +int getcmd(char *buf, int nbuf) +{ + write(1, "$ ", 2); + memset(buf, 0, nbuf); + + int n = read(0, buf, nbuf - 1); + + if (n < 0) + return -1; + + buf[n] = 0; + return 0; +} + +int main() +{ + static char buf[100]; + + + printf(ANSI_GREEN "\n[Lume Shell] Welcome! \n" ANSI_RESET); + printf(ANSI_YELLOW "[Lume Shell] Start your journey of exploration!" ANSI_RESET); + printf("\n"); + + while (getcmd(buf, sizeof(buf)) >= 0) + { + int len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + { + buf[len - 1] = 0; + } + + if (buf[0] == 'c' && buf[1] == 'd' && (buf[2] == ' ' || buf[2] == '\n' || buf[2] == 0)) + { + if (buf[strlen(buf) - 1] == '\n') + buf[strlen(buf) - 1] = 0; + + char *path = buf + 3; + if (buf[2] == '\n' || buf[2] == 0) + { + printf("cd: argument missing\n"); + continue; + } + + if (chdir(path) < 0) + { + printf("cannot cd %s\n", path); + } + continue; + } + if (strcmp(buf, "shutdown") == 0 || strcmp(buf, "poweroff") == 0) + { + printf(ANSI_BLUE "System shutting down..." ANSI_RESET); + printf("\n"); + shutdown(); + + continue; + } + + if (fork_or_panic() == 0) + { + Command *cmd = Parser::parse(buf); + if (cmd) + { + cmd->run(); + delete cmd; + } + exit(0); + } + wait(0); + } + return 0; +} \ No newline at end of file diff --git a/lume-legacy/src/user/touch.cc b/lume-legacy/src/user/touch.cc new file mode 100644 index 0000000..2f9f9f6 --- /dev/null +++ b/lume-legacy/src/user/touch.cc @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" +#include "common/fcntl.h" + +int main(int argc, char *argv[]) +{ + int i, fd; + + if (argc < 2) + { + printf("Usage: touch files...\n"); + exit(1); + } + + for (i = 1; i < argc; i++) + { + fd = open(argv[i], O_CREATE | O_RDWR); + if (fd < 0) + { + printf("touch: cannot touch %s\n", argv[i]); + exit(1); + } + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/user.ld b/lume-legacy/src/user/user.ld new file mode 100644 index 0000000..f0a4b27 --- /dev/null +++ b/lume-legacy/src/user/user.ld @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +OUTPUT_ARCH( "riscv" ) +ENTRY( _start ) + +SECTIONS +{ + . = 0x0; + + .text : { + *(.text.entry) + *(.text .text.*) + } + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } + + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } + + .bss : { + . = ALIGN(16); + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + } +} \ No newline at end of file diff --git a/lume-legacy/src/user/usys.S b/lume-legacy/src/user/usys.S new file mode 100644 index 0000000..1e373d3 --- /dev/null +++ b/lume-legacy/src/user/usys.S @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +#include "kernel/syscall.h" + +#define SYSCALL_NAME(name, sysname) \ + .global name; \ + name: \ + li a7, SYS_ ## sysname; \ + ecall; \ + ret + +SYSCALL_NAME(sys_openat, openat) +SYSCALL_NAME(sys_mknodat, mknodat) +SYSCALL_NAME(sys_mkdirat, mkdirat) +SYSCALL_NAME(sys_unlinkat, unlinkat) +SYSCALL_NAME(sys_linkat, linkat) +SYSCALL_NAME(sys_chdir, chdir) +SYSCALL_NAME(sys_dup, dup) +SYSCALL_NAME(sys_fstat, fstat) +SYSCALL_NAME(sys_read, read) +SYSCALL_NAME(sys_write, write) +SYSCALL_NAME(sys_close, close) +SYSCALL_NAME(sys_pipe2, pipe2) +SYSCALL_NAME(sys_clone, clone) +SYSCALL_NAME(sys_execve, execve) +SYSCALL_NAME(sys_wait4, wait4) +SYSCALL_NAME(sys_exit, exit) +SYSCALL_NAME(sys_getpid, getpid) +SYSCALL_NAME(sys_brk, brk) +SYSCALL_NAME(sys_kill, kill) +SYSCALL_NAME(sys_nanosleep, nanosleep) +SYSCALL_NAME(sys_reboot, reboot) +SYSCALL_NAME(sys_lseek, lseek) + +# === Custom / Debug Syscalls === +SYSCALL_NAME(sys_putc, putc) +SYSCALL_NAME(sys_disk_test, disk_test) \ No newline at end of file diff --git a/lume-legacy/src/user/wc.cc b/lume-legacy/src/user/wc.cc new file mode 100644 index 0000000..fbe9735 --- /dev/null +++ b/lume-legacy/src/user/wc.cc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +void wc(int fd, char *name) +{ + int i, n; + int l, w, c, inword; + + l = w = c = 0; + inword = 0; + while ((n = read(fd, buf, sizeof(buf))) > 0) + { + for (i = 0; i < n; i++) + { + c++; + if (buf[i] == '\n') + l++; + if (strchr(" \r\t\n\v", buf[i])) + inword = 0; + else if (!inword) + { + w++; + inword = 1; + } + } + } + printf("%d %d %d %s\n", l, w, c, name); +} + +int main(int argc, char *argv[]) +{ + int fd, i; + + if (argc <= 1) + { + wc(0, ""); + exit(0); + } + + for (i = 1; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("wc: cannot open %s\n", argv[i]); + exit(1); + } + wc(fd, argv[i]); + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/mm/memblock.cc b/mm/memblock.cc new file mode 100644 index 0000000..aee0245 --- /dev/null +++ b/mm/memblock.cc @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * memblock.cc — Boot-time physical memory allocator + * + * Simple region tracker: one memory region (from FDT) plus an array + * of reserved regions. Allocation searches for gaps between reserved + * regions. All operations are O(n_reserved), which is fine for the + * ~20 allocations that happen during early boot. + * + * Reference: inspired by Linux kernel's mm/memblock.c + */ + +#include +#include +#include +#include +#include +#include + +extern "C" char _stext[], _kernel_end[]; + +/* ------------------------------------------------------------------ */ +/* Module state */ +/* ------------------------------------------------------------------ */ + +namespace { + +constexpr uint64 kMB = 1024ULL * 1024ULL; + +struct { + uint64 mem_base; + uint64 mem_size; + + struct { + uint64 base; + uint64 size; + } reserved[kMemblockMaxRegions]; + int reserved_count; + bool active; +} g_mb; + +/* ------------------------------------------------------------------ */ +/* Internal helpers */ +/* ------------------------------------------------------------------ */ + +/* Insert a reserved region, keeping the array sorted by base. */ +void insert_reserved(uint64 base, uint64 size) +{ + if (g_mb.reserved_count >= kMemblockMaxRegions) { + kernel_panic("memblock: too many reserved regions"); + } + + int i = g_mb.reserved_count; + while (i > 0 && g_mb.reserved[i - 1].base > base) { + g_mb.reserved[i] = g_mb.reserved[i - 1]; + i--; + } + g_mb.reserved[i] = {base, size}; + g_mb.reserved_count++; +} + +/* Check if [base, base+size) overlaps any reserved region. */ +bool overlaps_reserved(uint64 base, uint64 size) +{ + uint64 end = base + size; + for (int i = 0; i < g_mb.reserved_count; i++) { + uint64 rb = g_mb.reserved[i].base; + uint64 re = rb + g_mb.reserved[i].size; + if (base < re && end > rb) { + return true; + } + } + return false; +} + +/* Find the end of the first reserved region that covers `addr`. */ +uint64 reserved_end_covering(uint64 addr) +{ + for (int i = 0; i < g_mb.reserved_count; i++) { + uint64 rb = g_mb.reserved[i].base; + uint64 re = rb + g_mb.reserved[i].size; + if (addr >= rb && addr < re) { + return re; + } + } + return addr; +} + +} // namespace + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void memblock_init(uint64 fdt_paddr) +{ + + /* 1. Get memory layout from FDT */ + uint64 mem_base = 0; + uint64 mem_size = 0; + fdt_early_get_mem_info(&mem_base, &mem_size); + if (mem_size == 0) { + kernel_panic("memblock_init: FDT reports zero memory"); + } + + g_mb.mem_base = mem_base; + g_mb.mem_size = mem_size; + g_mb.reserved_count = 0; + g_mb.active = true; + + /* 2. Reserve firmware region [mem_base, kPhysStart). + * + * Everything below the kernel's linked load address is firmware + * territory (OpenSBI), which is PMP-protected. */ + { + uint64 fw_end = kPhysStart; + if (fw_end > mem_base) { + memblock_reserve(mem_base, fw_end - mem_base); + } + } + + /* 2b. Reserve stale pre-relocation kernel image. + * + * After physical KASLR, the kernel is relocated from its original + * load address (kPhysStart) to a new PA. The old copy at + * [kPhysStart, kPhysStart + kernel_size) must not be allocated — + * it contains stale code/data that would corrupt page tables or + * other structures if reused. */ + { + uint64 ks_pa = boot_va_to_pa(virt_addr( + reinterpret_cast(_stext))).raw; + if (ks_pa != kPhysStart) { + /* Kernel was relocated — reserve the original location */ + uint64 kernel_size = boot_va_to_pa(virt_addr( + reinterpret_cast(_kernel_end))).raw - ks_pa; + memblock_reserve(kPhysStart, kernel_size); + } + } + + /* 3. Reserve kernel image [_stext, _kernel_end) */ + uint64 ks_pa = boot_va_to_pa(virt_addr( + reinterpret_cast(_stext))).raw; + uint64 ke_pa = boot_va_to_pa(virt_addr( + reinterpret_cast(_kernel_end))).raw; + memblock_reserve(ks_pa, ke_pa - ks_pa); + + /* 4. Reserve FDT blob — it lives in RAM and must not be overwritten + * by vmemmap/page_map or buddy before fdt_unflatten() copies it. */ + { + struct fdt_hdr { uint32 magic; uint32 totalsize; }; + const auto* fh = reinterpret_cast(fdt_paddr); + uint32 fdt_size = ((fh->totalsize & 0xFF) << 24) | + (((fh->totalsize >> 8) & 0xFF) << 16) | + (((fh->totalsize >> 16) & 0xFF) << 8) | + ((fh->totalsize >> 24) & 0xFF); + memblock_reserve(fdt_paddr, fdt_size); + kprintf("[memblock] FDT reserved [0x%llx, 0x%llx)\n", + fdt_paddr, fdt_paddr + fdt_size); + } + + kprintf("[memblock] RAM [0x%llx, 0x%llx) = %llu MB\n", + mem_base, mem_base + mem_size, mem_size / kMB); + kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa); +} + +void memblock_reserve(uint64 base, uint64 size) +{ + uint64 aligned_base = page_align_down(base); + uint64 aligned_end = page_align_up(base + size); + insert_reserved(aligned_base, aligned_end - aligned_base); +} + +uint64 memblock_alloc_top(uint64 size, uint64 align) +{ + uint64 mem_end = g_mb.mem_base + g_mb.mem_size; + + /* Search from top of RAM downward */ + uint64 end = page_align_down(mem_end); + uint64 start = (end - size) & ~(align - 1); + + while (start >= g_mb.mem_base && start + size <= mem_end) { + if (!overlaps_reserved(start, size)) { + memblock_reserve(start, size); + return start; + } + if (start < align) { + break; + } + start -= align; + } + + kernel_panic("memblock_alloc_top: out of memory"); + return 0; +} + +uint64 memblock_alloc(uint64 size, uint64 align) +{ + uint64 mem_end = g_mb.mem_base + g_mb.mem_size; + uint64 start = page_align_up(g_mb.mem_base); + + while (start + size <= mem_end) { + start = (start + align - 1) & ~(align - 1); + if (start + size > mem_end) { + break; + } + + if (!overlaps_reserved(start, size)) { + memblock_reserve(start, size); + return start; + } + + /* Skip past the overlapping reserved region */ + uint64 skip = reserved_end_covering(start); + if (skip <= start) { + start += align; + } else { + start = skip; + } + } + + kernel_panic("memblock_alloc: out of memory"); + return 0; +} + +uint64 memblock_get_mem_base() { return g_mb.mem_base; } +uint64 memblock_get_mem_size() { return g_mb.mem_size; } + +bool memblock_is_active() { return g_mb.active; } +void memblock_retire() { g_mb.active = false; } + +void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), + void* ctx) +{ + uint64 pos = g_mb.mem_base; + uint64 mem_end = g_mb.mem_base + g_mb.mem_size; + + for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) { + uint64 rb = g_mb.reserved[i].base; + if (rb < pos) { + uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size; + if (re > pos) { + pos = re; + } + continue; + } + if (rb > pos) { + cb(pos, rb - pos, ctx); + } + pos = g_mb.reserved[i].base + g_mb.reserved[i].size; + } + + if (pos < mem_end) { + cb(pos, mem_end - pos, ctx); + } +} diff --git a/mm/module.mk b/mm/module.mk new file mode 100644 index 0000000..93cef80 --- /dev/null +++ b/mm/module.mk @@ -0,0 +1,7 @@ +SRCS_CC += mm/memblock.cc +SRCS_CC += mm/pmm.cc +SRCS_CC += mm/slab.cc +SRCS_CC += mm/vmm.cc +SRCS_CC += mm/vma.cc +SRCS_CC += mm/page_cache.cc +SRCS_CC += mm/tlb.cc diff --git a/mm/page_cache.cc b/mm/page_cache.cc new file mode 100644 index 0000000..25c6a60 --- /dev/null +++ b/mm/page_cache.cc @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * page_cache.cc — Minimal page cache implementation + * + * Open-addressing hash table indexed by (mapping, index) pairs. + * Fixed-size bucket array allocated at init time via slab. + * + * This is intentionally simple: no LRU, no writeback, no eviction. + * The interface in page_cache.h is designed so that upgrading to a + * radix tree + LRU scheme later only changes this file. + * + * Concurrency: protected by a single spinlock. Acceptable for + * initial bring-up; will be replaced by per-bucket or RCU-based + * locking when contention becomes measurable. + */ + +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Hash table internals */ +/* ------------------------------------------------------------------ */ + +struct CacheEntry { + void* mapping; // nullptr = empty slot + uint64 index; + Page* page; +}; + +namespace { + +constexpr uint64 kCacheBuckets = 1024; + +CacheEntry g_buckets[kCacheBuckets]; +Spinlock g_cache_lock; + +uint64 hash_key(void* mapping, uint64 index) +{ + uint64 m = reinterpret_cast(mapping); + /* FNV-1a inspired mixing */ + uint64 h = m ^ (index * 0x517cc1b727220a95ULL); + h ^= h >> 17; + h *= 0xbf58476d1ce4e5b9ULL; + h ^= h >> 31; + return h % kCacheBuckets; +} + +/* Linear probe to find an entry or an empty slot. */ +CacheEntry* find_slot(void* mapping, uint64 index) +{ + uint64 start = hash_key(mapping, index); + for (uint64 i = 0; i < kCacheBuckets; i++) { + uint64 idx = (start + i) % kCacheBuckets; + CacheEntry* e = &g_buckets[idx]; + if (e->mapping == nullptr) { + return e; // Empty slot + } + if (e->mapping == mapping && e->index == index) { + return e; // Found + } + } + return nullptr; // Table full +} + +/* Find an existing entry (not an empty slot). */ +CacheEntry* find_entry(void* mapping, uint64 index) +{ + uint64 start = hash_key(mapping, index); + for (uint64 i = 0; i < kCacheBuckets; i++) { + uint64 idx = (start + i) % kCacheBuckets; + CacheEntry* e = &g_buckets[idx]; + if (e->mapping == nullptr) { + return nullptr; // Empty = not found + } + if (e->mapping == mapping && e->index == index) { + return e; + } + } + return nullptr; +} + +/* + * Remove an entry and fix up the linear-probe chain. + * Standard open-addressing deletion with backward shift. + */ +void remove_entry(CacheEntry* entry) +{ + uint64 removed = static_cast(entry - g_buckets); + entry->mapping = nullptr; + entry->page = nullptr; + + /* Backward-shift deletion to maintain probe chain integrity */ + uint64 cur = (removed + 1) % kCacheBuckets; + while (g_buckets[cur].mapping != nullptr) { + uint64 ideal = hash_key(g_buckets[cur].mapping, g_buckets[cur].index); + /* Check if cur's ideal position is "before" removed in probe order */ + bool needs_shift = false; + if (removed <= cur) { + needs_shift = (ideal <= removed || ideal > cur); + } else { + needs_shift = (ideal <= removed && ideal > cur); + } + + if (needs_shift) { + g_buckets[removed] = g_buckets[cur]; + g_buckets[cur].mapping = nullptr; + g_buckets[cur].page = nullptr; + removed = cur; + } + cur = (cur + 1) % kCacheBuckets; + } +} + +} // namespace + +/* ------------------------------------------------------------------ */ +/* Public API */ +/* ------------------------------------------------------------------ */ + +void page_cache_init() +{ + g_cache_lock.init("page_cache"); + for (uint64 i = 0; i < kCacheBuckets; i++) { + g_buckets[i].mapping = nullptr; + g_buckets[i].index = 0; + g_buckets[i].page = nullptr; + } +} + +Page* page_cache_get(void* mapping, uint64 index) +{ + LockGuard guard(g_cache_lock); + CacheEntry* e = find_entry(mapping, index); + if (!e) { + return nullptr; + } + page_incref(e->page); + return e->page; +} + +int page_cache_insert(void* mapping, uint64 index, Page* page) +{ + if (!mapping || !page) { + return -EINVAL; + } + + LockGuard guard(g_cache_lock); + + /* Check for existing entry */ + CacheEntry* existing = find_entry(mapping, index); + if (existing) { + return -EEXIST; + } + + /* Find an empty slot */ + CacheEntry* slot = find_slot(mapping, index); + if (!slot || slot->mapping != nullptr) { + return -ENOMEM; // Table full + } + + slot->mapping = mapping; + slot->index = index; + slot->page = page; + page_incref(page); // Cache holds a reference + + return 0; +} + +void page_cache_put(Page* page) +{ + if (!page) { + return; + } + page_decref(page); +} + +Page* page_cache_remove(void* mapping, uint64 index) +{ + LockGuard guard(g_cache_lock); + CacheEntry* e = find_entry(mapping, index); + if (!e) { + return nullptr; + } + + Page* page = e->page; + remove_entry(e); + page_decref(page); // Release cache's reference + return page; +} + +void page_cache_invalidate(void* mapping) +{ + if (!mapping) { + return; + } + + LockGuard guard(g_cache_lock); + + /* Scan all buckets — acceptable for current table size */ + for (uint64 i = 0; i < kCacheBuckets; i++) { + if (g_buckets[i].mapping == mapping) { + Page* page = g_buckets[i].page; + remove_entry(&g_buckets[i]); + page_decref(page); + /* After removal, re-check this index since backward shift + * may have moved an entry into this slot. */ + i--; + } + } +} + +Page* page_cache_get_or_alloc(void* mapping, uint64 index) +{ + /* Fast path: already cached */ + Page* page = page_cache_get(mapping, index); + if (page) { + return page; + } + + /* Slow path: allocate and insert */ + page = pmm_alloc_page(); + if (!page) { + return nullptr; + } + + int ret = page_cache_insert(mapping, index, page); + if (ret == -EEXIST) { + /* Race: another thread inserted between our get and insert. + * Free our allocation and return the existing page. */ + pmm_free_page(page); + return page_cache_get(mapping, index); + } + if (ret < 0) { + pmm_free_page(page); + return nullptr; + } + + /* Caller gets a ref (insert already added cache's ref, + * we need one more for the caller). */ + /* Actually, insert did incref for cache. The page already has + * refcount=1 from alloc + 1 from insert = 2. + * We return it to caller who must put() when done. + * So we incref once more for the caller's reference. */ + page_incref(page); + return page; +} diff --git a/mm/pmm.cc b/mm/pmm.cc new file mode 100644 index 0000000..b7cf611 --- /dev/null +++ b/mm/pmm.cc @@ -0,0 +1,528 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * pmm.cc — Physical Memory Manager implementation + * + * Buddy allocator with Per-CPU single-page caches (PCP). + * + * Initialization sequence (called by BSP in single-core period): + * 1. memblock + vmm_init allocate and map the page_map at VMEMMAP. + * 2. Zero and mark all Page descriptors reserved by default. + * 3. Carve free regions from memblock into the buddy system. + * + * The page_map is a flat array indexed by physical page frame number (PFN). + * PFN = (pa - mem_base) / PAGE_SIZE. This keeps address conversion O(1). + * + * Reference: docs/specs/pmm.md + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Per-CPU page cache */ +/* ------------------------------------------------------------------ */ + +struct PerCpuCache { + list_node free_list; // Single-page free list + uint32 count; // Number of cached pages +}; + +/* ------------------------------------------------------------------ */ +/* Module-private state */ +/* ------------------------------------------------------------------ */ + +namespace { + +/* PCP Cache tuning parameters */ +constexpr uint32 kPcpBatchDivisor = 1024; +constexpr uint32 kPcpMinBatchSize = 16; +constexpr uint64 kMB = 1024ULL * 1024ULL; + +Spinlock g_pmm_lock; +list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10] +PerCpuCache g_pcp[kMaxCpus]; + +Page *g_page_map = nullptr; // Flat array of Page descriptors +uint64 g_page_map_pa = 0; // Physical base of page_map storage +uint64 g_page_map_bytes = 0; // Byte size of page_map storage +uint64 g_mem_base = 0; // Physical memory base (from FDT) +uint64 g_mem_size = 0; // Total physical memory size +uint64 g_num_pages = 0; // Total managed pages + +} // namespace + +uint32 g_pcp_high_watermark = 0; +uint32 g_pcp_batch_size = 0; + +/* ------------------------------------------------------------------ */ +/* Address conversion helpers */ +/* ------------------------------------------------------------------ */ + +/* + * PFN (Page Frame Number) — fundamental conversion unit. + * When sparse vmemmap is implemented, only these two functions change. + * All other PA↔Page conversions go through them. + */ +uint64 page_to_pfn(const Page *page) { + uintptr base = reinterpret_cast(g_page_map); + uintptr addr = reinterpret_cast(page); + uintptr end = base + g_num_pages * sizeof(Page); + + if (addr < base || addr >= end) { + kernel_panic("page_to_pfn: page outside page_map bounds"); + } + + uint64 idx = (addr - base) / sizeof(Page); + return (g_mem_base / kPageSize) + idx; +} + +Page *pfn_to_page(uint64 pfn) { + uint64 base_pfn = g_mem_base / kPageSize; + if (pfn < base_pfn || pfn >= base_pfn + g_num_pages) { + kernel_panic("pfn_to_page: PFN outside managed range"); + } + return &g_page_map[pfn - base_pfn]; +} + +PhysAddr page_to_pa(const Page *page) { + return pfn_to_pa(Pfn{page_to_pfn(page)}); +} + +Page *pa_to_page(PhysAddr pa) { + return pfn_to_page(pa_to_pfn(pa).raw); +} + +VirtAddr page_to_va(const Page *page) { + return pa_to_va(page_to_pa(page)); +} + + +/* ------------------------------------------------------------------ */ +/* Buddy system internals (caller must hold g_pmm_lock) */ +/* ------------------------------------------------------------------ */ + +namespace { + +/* + * Find a page's buddy at a given order. + * Buddy PFN = PFN ^ (1 << order). + */ +inline Page *buddy_of(Page *page, uint8 order) { + uint64 pfn = static_cast(page - g_page_map); + uint64 buddy_pfn = pfn ^ (1ULL << order); + if (buddy_pfn >= g_num_pages) { + return nullptr; + } + return &g_page_map[buddy_pfn]; +} + +/* + * Insert a free block into the buddy free list at the given order. + */ +void buddy_list_add(Page *page, uint8 order) { + page->type = PageType::Free; + page->order = order; + list_node *head = &g_free_areas[order]; + list_node *node = &page->free.free_link; + node->next = head->next; + node->prev = head; + head->next->prev = node; + head->next = node; +} + +/* + * Remove a block from whatever buddy free list it is on. + */ +void buddy_list_del(Page *page) { + list_node *node = &page->free.free_link; + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = node; + node->prev = node; +} + +/* + * Allocate a block of 2^order contiguous pages from the buddy system. + * Splits higher-order blocks if no exact match is available. + * Returns nullptr if no memory available. + */ +Page *buddy_alloc(uint8 order) { + for (int cur = order; cur < kMaxOrder; cur++) { + list_node *head = &g_free_areas[cur]; + if (head->next == head) { + continue; // Empty at this order + } + + // Pop the first block from this order's free list + list_node *node = head->next; + // Compute Page* from the free_link member offset + Page *page = + reinterpret_cast(reinterpret_cast(node) - + __builtin_offsetof(Page, free.free_link)); + buddy_list_del(page); + + // Split down to the requested order + while (cur > order) { + cur--; + // The upper half becomes a free buddy + Page *split = &g_page_map[(page - g_page_map) + (1ULL << cur)]; + buddy_list_add(split, static_cast(cur)); + } + + page->type = PageType::Allocated; + page->order = order; + page->refcount.store(1, __ATOMIC_RELAXED); + page->mapcount.store(0, __ATOMIC_RELAXED); + page->flags.store(0, __ATOMIC_RELAXED); + page->mapping = nullptr; + page->index = 0; + return page; + } + return nullptr; +} + +/* + * Return a block of 2^order pages to the buddy system. + * Coalesces with its buddy recursively up to kMaxOrder-1. + */ +void buddy_free(Page *page, uint8 order) { + uint64 pfn = static_cast(page - g_page_map); + + while (order < kMaxOrder - 1) { + Page *buddy = buddy_of(page, order); + if (!buddy) { + break; + } + // Buddy must be free AND at the same order to coalesce + if (buddy->type != PageType::Free || buddy->order != order) { + break; + } + + // Remove buddy from its free list and merge + buddy_list_del(buddy); + + // The merged block starts at the lower PFN + uint64 buddy_pfn = static_cast(buddy - g_page_map); + if (buddy_pfn < pfn) { + page = buddy; + pfn = buddy_pfn; + } + order++; + } + + buddy_list_add(page, order); +} + +/* ------------------------------------------------------------------ */ +/* Per-CPU cache operations (interrupts must be disabled by caller) */ +/* ------------------------------------------------------------------ */ + +/* + * Refill the PCP from the global buddy system. + * Acquires g_pmm_lock internally. + */ +void refill_pcp(PerCpuCache *pcp) { + LockGuard guard(g_pmm_lock); + for (uint32 i = 0; i < g_pcp_batch_size; i++) { + Page *page = buddy_alloc(0); + if (!page) { + break; + } + // Put onto PCP list (reuse the free_link for PCP chain) + page->type = PageType::PcpCached; + list_node *node = &page->free.free_link; + node->next = pcp->free_list.next; + node->prev = &pcp->free_list; + pcp->free_list.next->prev = node; + pcp->free_list.next = node; + pcp->count++; + } +} + +/* + * Drain half the PCP back to the global buddy system. + * Acquires g_pmm_lock internally. + */ +void drain_pcp(PerCpuCache *pcp) { + LockGuard guard(g_pmm_lock); + int to_drain = pcp->count / 2; + for (int i = 0; i < to_drain; i++) { + list_node *head = &pcp->free_list; + if (head->next == head) { + break; + } + list_node *node = head->next; + // Unlink from PCP + node->prev->next = node->next; + node->next->prev = node->prev; + pcp->count--; + + Page *page = + reinterpret_cast(reinterpret_cast(node) - + __builtin_offsetof(Page, free.free_link)); + buddy_free(page, 0); + } +} + +} // namespace + +/* ------------------------------------------------------------------ */ +/* Public API: single-page (fast path via PCP) */ +/* ------------------------------------------------------------------ */ + +Page *pmm_alloc_page() { + /* + * Disable interrupts to prevent reentry on the same CPU + * (e.g. timer IRQ handler calling pmm_alloc_page while we + * are mid-way through manipulating the PCP list). + */ + bool was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + uint64 cpu = arch::cpu::id(); + PerCpuCache *pcp = &g_pcp[cpu]; + + if (pcp->count == 0) { + refill_pcp(pcp); + } + + Page *page = nullptr; + if (pcp->count > 0) { + list_node *node = pcp->free_list.next; + // Unlink + node->prev->next = node->next; + node->next->prev = node->prev; + pcp->count--; + + page = + reinterpret_cast(reinterpret_cast(node) - + __builtin_offsetof(Page, free.free_link)); + + // Validate the page is within the page_map bounds + if (page < g_page_map || page >= g_page_map + g_num_pages) { + kernel_panic("pmm_alloc_page: PCP returned out-of-bounds page"); + } + + page->type = PageType::Allocated; + page->order = 0; + page->refcount.store(1, __ATOMIC_RELAXED); + page->mapcount.store(0, __ATOMIC_RELAXED); + page->flags.store(0, __ATOMIC_RELAXED); + page->mapping = nullptr; + page->index = 0; + } + + if (was_on) { + arch::cpu::intr_on(); + } + return page; +} + +void pmm_free_page(Page *page) { + if (!page) { + kernel_panic("pmm_free_page: null page"); + } + + bool was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + uint64 cpu = arch::cpu::id(); + PerCpuCache *pcp = &g_pcp[cpu]; + + // Push onto PCP + page->type = PageType::PcpCached; + page->order = 0; + list_node *node = &page->free.free_link; + node->next = pcp->free_list.next; + node->prev = &pcp->free_list; + pcp->free_list.next->prev = node; + pcp->free_list.next = node; + pcp->count++; + + // Drain if over high watermark + if (pcp->count >= g_pcp_high_watermark) { + drain_pcp(pcp); + } + + if (was_on) { + arch::cpu::intr_on(); + } +} + +/* ------------------------------------------------------------------ */ +/* Public API: multi-page (slow path, direct buddy) */ +/* ------------------------------------------------------------------ */ + +Page *pmm_alloc_pages(uint8 order) { + if (order >= kMaxOrder) { + return nullptr; + } + LockGuard guard(g_pmm_lock); + return buddy_alloc(order); +} + +void pmm_free_pages(Page *page, uint8 order) { + if (!page) { + kernel_panic("pmm_free_pages: null page"); + } + if (order >= kMaxOrder) { + kernel_panic("pmm_free_pages: order out of range"); + } + LockGuard guard(g_pmm_lock); + buddy_free(page, order); +} + +/* ------------------------------------------------------------------ */ +/* Public API: reference counting */ +/* ------------------------------------------------------------------ */ + +void page_incref(Page *page) { page->refcount.fetch_add(1); } + +void page_decref(Page *page) { + uint32 old = page->refcount.fetch_sub(1); + if (old == 1) { + // Refcount dropped to 0 — return to pool based on originally allocated + // order + if (page->order == 0) { + pmm_free_page(page); + } else { + pmm_free_pages(page, page->order); + } + } +} + +/* ------------------------------------------------------------------ */ +/* Initialization */ +/* ------------------------------------------------------------------ */ + +/* + * Helper: zero out a range of bytes. We cannot use memset here + * because lib/string may not be linked yet. + */ +namespace { +void zero_range(void *start, uint64 bytes) { + uint8 *p = static_cast(start); + for (uint64 i = 0; i < bytes; i++) { + p[i] = 0; + } +} +} // namespace + +void pmm_init() { + /* 1. Get memory layout from memblock (already scanned FDT) */ + g_mem_base = memblock_get_mem_base(); + g_mem_size = memblock_get_mem_size(); + g_num_pages = vmm_get_num_pages(); + + if (g_mem_size == 0) { + kernel_panic("pmm_init: memblock reports zero memory"); + } + + /* 2. PCP parameters */ + uint32 calc_batch = static_cast(g_num_pages / (kMaxCpus * kPcpBatchDivisor)); + g_pcp_batch_size = (calc_batch > kPcpMinBatchSize) ? calc_batch : kPcpMinBatchSize; + g_pcp_high_watermark = g_pcp_batch_size * 4; + + /* 3. page_map was allocated by vmm_init at vmemmap VA. + * No relocation needed — page_map starts life at the right VA. */ + g_page_map_pa = vmm_get_page_map_pa(); + g_page_map_bytes = vmm_get_page_map_size(); + g_page_map = reinterpret_cast(arch::g_vmemmap_base); + + /* 4. Zero out the entire page_map */ + zero_range(g_page_map, g_page_map_bytes); + + /* 4b. Default every page descriptor to reserved/non-free. */ + for (uint64 i = 0; i < g_num_pages; i++) { + Page *page = &g_page_map[i]; + page->refcount.store(0, __ATOMIC_RELAXED); + page->mapcount.store(0, __ATOMIC_RELAXED); + page->flags.store(0, __ATOMIC_RELAXED); + page->type = PageType::Allocated; + page->order = 0; + page->mapping = nullptr; + page->index = 0; + page->lru.init(); + page->free.free_link.init(); + } + + /* 5. Initialize buddy free list heads */ + g_pmm_lock.init("pmm"); + for (int i = 0; i < kMaxOrder; i++) { + g_free_areas[i].init(); + } + + /* 6. Initialize PCP list heads */ + for (int i = 0; i < kMaxCpus; i++) { + g_pcp[i].free_list.init(); + g_pcp[i].count = 0; + } + + /* 7. Feed free regions from memblock into the buddy system. + * memblock knows about all reserved regions (firmware, kernel, + * page table pages, page_map). Everything else is free. */ + struct FeedCtx { + uint64 total_pages; + }; + FeedCtx ctx{0}; + + memblock_for_each_free( + [](uint64 base, uint64 size, void *arg) { + auto *c = static_cast(arg); + uint64 start_pfn = (base - memblock_get_mem_base()) / kPageSize; + uint64 end_pfn = start_pfn + size / kPageSize; + + /* Access page_map via the globals directly (they are in anon namespace now) */ + + uint64 pfn = start_pfn; + while (pfn < end_pfn) { + uint8 order = 0; + for (int o = kMaxOrder - 1; o >= 0; o--) { + uint64 block_size = 1ULL << o; + if ((pfn & (block_size - 1)) == 0 && pfn + block_size <= end_pfn) { + order = static_cast(o); + break; + } + } + + Page *page = &g_page_map[pfn]; + page->type = PageType::Free; + page->order = order; + page->refcount.store(0, __ATOMIC_RELAXED); + page->free.free_link.init(); + + /* Inline buddy_list_add since we're inside a lambda */ + list_node *head = &g_free_areas[order]; + list_node *node = &page->free.free_link; + node->next = head->next; + node->prev = head; + head->next->prev = node; + head->next = node; + + pfn += (1ULL << order); + c->total_pages += (1ULL << order); + } + }, + &ctx); + + kprintf("[pmm] buddy: %llu free pages (%llu MB)\n", ctx.total_pages, + (ctx.total_pages * kPageSize) / kMB); + + /* 8. Retire memblock — all future allocations go through PMM */ + memblock_retire(); +} + +PhysAddr pmm_get_mem_base() { return phys_addr(g_mem_base); } +uint64 pmm_get_mem_size() { return g_mem_size; } diff --git a/mm/slab.cc b/mm/slab.cc new file mode 100644 index 0000000..43d8c4a --- /dev/null +++ b/mm/slab.cc @@ -0,0 +1,321 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * slab.cc — SLUB allocator implementation + * + * Key design points: + * - Free object list is embedded in the objects themselves (first 8 bytes). + * - Per-page metadata lives in Page.slab (cache, obj_count, freelist). + * - Per-CPU "active page" avoids lock contention on the fast path. + * - partial_ list protected by per-KmemCache Spinlock. + * - PMM calls happen outside the lock to avoid lock ordering issues. + * + * Address note: the kernel may be running at physical addresses via + * identity mapping (before vmm_init). We handle PA/VA carefully: + * PMM gives us Page*, page_to_pa() gives PA, and we access page + * contents via pa_to_va() (higher-half VA, always mapped). + * + * Reference: docs/specs/slab.md + */ + +#include "slab.h" +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +/* + * Convert a Page* to the usable virtual address of the page's contents. + * Uses the higher-half mapping which is always active. + */ +static inline void* page_to_data_va(Page* page) +{ + return reinterpret_cast(page_to_va(page).raw); +} + +/* Round up x to the next multiple of align (align must be power of 2) */ +static inline uint32 align_up(uint32 x, uint32 align) +{ + return (x + align - 1) & ~(align - 1); +} + +/* ------------------------------------------------------------------ */ +/* Global size-class caches */ +/* ------------------------------------------------------------------ */ + +KmemCache g_size_caches[kNumSizeClasses]; + +/* ------------------------------------------------------------------ */ +/* KmemCache::init */ +/* ------------------------------------------------------------------ */ + +void KmemCache::init(const char* name, uint32 obj_size, uint32 obj_align) +{ + name_ = name; + obj_align_ = (obj_align < 8) ? 8 : obj_align; // minimum 8 for freelist ptr + obj_size_ = align_up((obj_size < 8) ? 8 : obj_size, obj_align_); + objs_per_slab_ = kPageSize / obj_size_; + + lock_.init(name); + partial_.init(); + + for (int i = 0; i < kMaxCpus; i++) + cpu_slab_[i].active = nullptr; +} + +/* ------------------------------------------------------------------ */ +/* KmemCache::new_slab — allocate and format a fresh slab page */ +/* ------------------------------------------------------------------ */ + +Page* KmemCache::new_slab() +{ + Page* page = pmm_alloc_page(); + if (!page) + return nullptr; + + page->type = PageType::Slab; + page->slab.cache = this; + page->slab.obj_count = 0; + + /* Build the embedded free list across all object slots */ + uint8* base = static_cast(page_to_data_va(page)); + void* head = nullptr; + + /* Chain objects from last to first so freelist order = low addr first */ + for (int i = static_cast(objs_per_slab_) - 1; i >= 0; i--) { + void* obj = base + i * obj_size_; + *reinterpret_cast(obj) = head; + head = obj; + } + page->slab.freelist = head; + + return page; +} + +/* ------------------------------------------------------------------ */ +/* Partial list management (lock must be held) */ +/* ------------------------------------------------------------------ */ + +Page* KmemCache::get_partial() +{ + /* + * Phase 1 simplification: no partial list tracking. + * The slow path always allocates a fresh page from PMM. + * Full pages stay alive (Page.slab.cache is valid for kfree). + * Empty pages are returned to PMM immediately. + * + * TODO: add list_node to Page.slab and implement partial tracking + * to avoid wasting partially-filled pages. + */ + (void)partial_; + return nullptr; +} + +void KmemCache::put_partial(Page* page) +{ + (void)page; +} + +void KmemCache::remove_partial(Page* page) +{ + (void)page; +} + +/* ------------------------------------------------------------------ */ +/* KmemCache::alloc */ +/* ------------------------------------------------------------------ */ + +void* KmemCache::alloc() +{ + bool was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + uint64 cpu = arch::cpu::id(); + Page* active = cpu_slab_[cpu].active; + + /* Fast path: pop from active page's freelist */ + if (active && active->slab.freelist) { + void* obj = active->slab.freelist; + active->slab.freelist = *reinterpret_cast(obj); + active->slab.obj_count++; + if (was_on) arch::cpu::intr_on(); + return obj; + } + + /* Slow path: need a new active page */ + + /* If current active is full, detach it (it stays as Slab state, + * cache pointer still valid for kfree to find it) */ + if (active && !active->slab.freelist) { + cpu_slab_[cpu].active = nullptr; + } + + if (was_on) arch::cpu::intr_on(); + + /* Try to get a partial page or allocate a fresh one. + * PMM call happens without any lock held. */ + Page* new_page = new_slab(); + if (!new_page) + return nullptr; + + /* Install as new active page */ + was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + /* Another interrupt handler might have set a new active while we + * were allocating. If so, put ours aside (simplified: just use ours). */ + cpu_slab_[cpu].active = new_page; + + void* obj = new_page->slab.freelist; + new_page->slab.freelist = *reinterpret_cast(obj); + new_page->slab.obj_count++; + + if (was_on) arch::cpu::intr_on(); + return obj; +} + +/* ------------------------------------------------------------------ */ +/* KmemCache::free */ +/* ------------------------------------------------------------------ */ + +void KmemCache::free(void* obj) +{ + if (!obj) + return; + + /* Find the Page for this object's page */ + uint64 obj_addr = reinterpret_cast(obj); + + /* Convert VA to PA. Like pmm.cc, handle both PA and VA cases. */ + uint64 obj_pa = va_to_pa(virt_addr(obj_addr)).raw; + uint64 page_pa = obj_pa & ~(kPageSize - 1); + Page* page = pa_to_page(phys_addr(page_pa)); + + if (page->type != PageType::Slab || page->slab.cache != this) + kernel_panic("slab free: object does not belong to this cache"); + + bool was_on = arch::cpu::intr_enabled(); + arch::cpu::intr_off(); + + /* Push object back onto the page's freelist */ + *reinterpret_cast(obj) = page->slab.freelist; + page->slab.freelist = obj; + page->slab.obj_count--; + + /* If page is now completely empty, return it to PMM */ + if (page->slab.obj_count == 0) { + /* If this page is our active, clear it */ + uint64 cpu = arch::cpu::id(); + if (cpu_slab_[cpu].active == page) + cpu_slab_[cpu].active = nullptr; + + if (was_on) arch::cpu::intr_on(); + + /* Return page to PMM */ + page->slab.cache = nullptr; + page->slab.freelist = nullptr; + pmm_free_page(page); + return; + } + + if (was_on) arch::cpu::intr_on(); +} + +/* ------------------------------------------------------------------ */ +/* Public free-function API */ +/* ------------------------------------------------------------------ */ + +/* Find the smallest size class >= size */ +static KmemCache* find_cache(uint32 size) +{ + for (int i = 0; i < kNumSizeClasses; i++) { + if (kSizeClasses[i] >= size) + return &g_size_caches[i]; + } + return nullptr; // Too large for slab +} + +void* kmalloc(uint32 size) +{ + if (size == 0) + return nullptr; + + /* Large allocation: fall back to PMM directly */ + if (size > kSizeClasses[kNumSizeClasses - 1]) { + uint8 order = 0; + uint64 needed = (size + kPageSize - 1) / kPageSize; + while ((1ULL << order) < needed) + order++; + Page* page = pmm_alloc_pages(order); + if (!page) + return nullptr; + return reinterpret_cast(pa_to_va(page_to_pa(page)).raw); + } + + KmemCache* cache = find_cache(size); + if (!cache) + return nullptr; + return cache->alloc(); +} + +void kfree(void* ptr) +{ + if (!ptr) + return; + + uint64 addr = reinterpret_cast(ptr); + uint64 pa = va_to_pa(virt_addr(addr)).raw; + uint64 page_pa = pa & ~(kPageSize - 1); + Page* page = pa_to_page(phys_addr(page_pa)); + + if (page->type == PageType::Slab) { + KmemCache* cache = page->slab.cache; + if (!cache) + kernel_panic("kfree: slab page has null cache"); + cache->free(ptr); + } else if (page->type == PageType::Allocated) { + /* Large allocation — return directly to PMM */ + pmm_free_pages(page, page->order); + } else { + kernel_panic("kfree: invalid page state"); + } +} + +/* ------------------------------------------------------------------ */ +/* C++ operator new / delete */ +/* ------------------------------------------------------------------ */ + +/* Use decltype(sizeof(0)) instead of size_t to match the compiler's + * built-in expectation for operator new (unsigned long vs unsigned long long). */ +using __sz = decltype(sizeof(0)); + +void* operator new(__sz size) { return kmalloc(static_cast(size)); } +void* operator new[](__sz size) { return kmalloc(static_cast(size)); } + +void operator delete(void* ptr) noexcept { kfree(ptr); } +void operator delete[](void* ptr) noexcept { kfree(ptr); } +void operator delete(void* ptr, __sz) noexcept { kfree(ptr); } +void operator delete[](void* ptr, __sz) noexcept { kfree(ptr); } + +/* ------------------------------------------------------------------ */ +/* Initialization */ +/* ------------------------------------------------------------------ */ + +void slab_init() +{ + for (int i = 0; i < kNumSizeClasses; i++) { + /* Build a name string like "kmalloc-32" */ + static const char* names[kNumSizeClasses] = { + "kmalloc-8", "kmalloc-16", "kmalloc-32", + "kmalloc-64", "kmalloc-128", "kmalloc-256", + "kmalloc-512", "kmalloc-1024","kmalloc-2048" + }; + g_size_caches[i].init(names[i], kSizeClasses[i], 8); + } +} diff --git a/mm/slab.h b/mm/slab.h new file mode 100644 index 0000000..909ee62 --- /dev/null +++ b/mm/slab.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * SLUB Allocator — module-private header + * + * SLUB = Simple List of Unfull Blocks. + * Metadata lives in Page descriptors (no per-page Slab header). + * Free object list is embedded inside the objects themselves. + * Per-CPU "active page" eliminates lock contention on the fast path. + * + * Reference: docs/specs/slab.md + */ + +#include +#include +#include +#include +#include + +/* Size classes: 8, 16, 32, 64, 128, 256, 512, 1024, 2048 */ +inline constexpr int kNumSizeClasses = 9; +inline constexpr uint32 kSizeClasses[kNumSizeClasses] = { + 8, 16, 32, 64, 128, 256, 512, 1024, 2048 +}; + +/* Per-CPU active slab page pointer */ +struct PerCpuSlab { + Page* active; // Currently used slab page (nullptr if none) +}; + +/* + * KmemCache — manages a pool of fixed-size objects. + * + * Lock contract: + * lock_ protects partial_ list only. + * Per-CPU cpu_slab_ access requires interrupts disabled. + * PMM calls happen outside lock_ to avoid lock ordering issues. + */ +class KmemCache { +public: + KmemCache() = default; + + // Explicit init (Placement New / BSS pattern) + void init(const char* name, uint32 obj_size, uint32 obj_align); + + // Allocate one object. Returns nullptr on OOM. + void* alloc(); + + // Free one object back to its owning page. + void free(void* obj); + + const char* name() const { return name_; } + uint32 obj_size() const { return obj_size_; } + +private: + // Get a partially-filled page from the partial list (lock must be held) + Page* get_partial(); + + // Put a page onto the partial list (lock must be held) + void put_partial(Page* page); + + // Remove a page from the partial list (lock must be held) + void remove_partial(Page* page); + + // Allocate a new page from PMM and format it as a slab + Page* new_slab(); + + Spinlock lock_; // Protects partial_ only + list_node partial_; // Partially-filled slab pages + + PerCpuSlab cpu_slab_[kMaxCpus]; // Per-CPU active page + + const char* name_ = "uninit"; + uint32 obj_size_ = 0; // Aligned object size (bytes) + uint32 obj_align_ = 0; // Alignment requirement + uint32 objs_per_slab_ = 0; // Objects per 4KB page +}; + +/* Global size-class caches */ +extern KmemCache g_size_caches[kNumSizeClasses]; diff --git a/mm/tlb.cc b/mm/tlb.cc new file mode 100644 index 0000000..b7a8ba7 --- /dev/null +++ b/mm/tlb.cc @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * tlb.cc — TLB shootdown implementation + * + * Uses RISC-V SBI Remote Fence extension to perform cross-hart + * TLB invalidation. SBI firmware handles the IPI delivery and + * synchronization — the call blocks until all target harts have + * completed their local sfence.vma. + * + * When a VmSpace is provided, we use its cpu_mask to target only + * the CPUs that have this address space loaded in satp. This avoids + * unnecessary IPI storms on unrelated cores. + * + * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.1 + */ + +#include +#include +#include +#include +#include +#include + +/* + * If a shootdown range exceeds this many pages, do a full flush + * instead of per-page invalidation. + */ +static constexpr uint64 kShootdownPageThreshold = 32; + +/* + * Build a cpu mask for all online CPUs (fallback when mm is null). + */ +namespace { +uint64 all_cpus_mask() +{ + uint64 mask = 0; + for (uint32 i = 0; i < kMaxCpus; i++) { + mask |= (1ULL << i); + } + return mask; +} + +/* + * Get the target cpu mask: use mm->cpu_mask when available, + * fall back to all_cpus_mask for kernel address space (mm == nullptr). + */ +uint64 target_cpu_mask(VmSpace* mm) +{ + if (mm) { + uint64 mask = mm->cpu_mask.load(__ATOMIC_RELAXED); + /* Always include our own CPU — we may still have stale TLB */ + mask |= (1ULL << arch::cpu::id()); + return mask; + } + return all_cpus_mask(); +} +} // namespace + +void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end) +{ + if (start >= end) { + return; + } + + uint64 num_pages = (end - start) / kPageSize; + uint64 cpu_mask = target_cpu_mask(mm); + + if (num_pages > kShootdownPageThreshold) { + /* Full flush on target CPUs via arch backend */ + arch::tlb::flush_remote(cpu_mask, 0, ~0ULL); + return; + } + + /* Range flush on target CPUs via arch backend */ + arch::tlb::flush_remote(cpu_mask, start, end - start); +} + +void tlb_shootdown_page(VmSpace* mm, uint64 va) +{ + uint64 cpu_mask = target_cpu_mask(mm); + arch::tlb::flush_remote(cpu_mask, va, kPageSize); +} + +void tlb_shootdown_all() +{ + uint64 cpu_mask = all_cpus_mask(); + arch::tlb::flush_remote(cpu_mask, 0, ~0ULL); +} diff --git a/mm/vma.cc b/mm/vma.cc new file mode 100644 index 0000000..da89e08 --- /dev/null +++ b/mm/vma.cc @@ -0,0 +1,640 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vma.cc — Virtual Memory Area management (MI layer) + * + * Manages user address spaces using a red-black tree of VMA regions. + * Implements demand paging: vmm_map_user records intent only; + * physical pages are allocated on page fault via vmm_handle_page_fault. + * + * MI/MD separation: this file NEVER references pmap::PTE_* constants. + * Permission translation is delegated to pmap::vm_perm_to_pte(). + * + * Reference: docs/specs/vmm.md + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Rbtree callbacks for VMA interval tree */ +/* ------------------------------------------------------------------ */ + +static bool vma_less(struct rb_node* node, const struct rb_node* parent) { + VmAreaStruct* n = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); + const VmAreaStruct* p = reinterpret_cast( + reinterpret_cast(parent) - __builtin_offsetof(VmAreaStruct, rb)); + return n->start < p->start; +} + +static int vma_cmp(const void* key, const struct rb_node* node) { + uint64 addr = *static_cast(key); + const VmAreaStruct* n = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); + if (addr < n->start) return -1; + if (addr >= n->end) return 1; + return 0; +} + +static VmAreaStruct* node_to_vma(struct rb_node* node) +{ + return reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); +} + +static uint64 align_up_any(uint64 value, uint64 align) +{ + if (align == 0) { + return value; + } + return ((value + align - 1) / align) * align; +} + +static bool range_overlaps(uint64 a_start, uint64 a_end, + uint64 b_start, uint64 b_end) +{ + return a_start < b_end && b_start < a_end; +} + + + +static bool vma_overlaps_locked(VmSpace* space, uint64 start, uint64 end) +{ + struct rb_node* node = rb_first(&space->vma_tree); + while (node) { + VmAreaStruct* vma = node_to_vma(node); + if (range_overlaps(start, end, vma->start, vma->end)) { + return true; + } + node = rb_next(node); + } + return false; +} + +static bool vma_range_covered_locked(VmSpace* space, uint64 start, uint64 end) +{ + uint64 cursor = start; + struct rb_node* node = rb_first(&space->vma_tree); + while (node && cursor < end) { + VmAreaStruct* vma = node_to_vma(node); + if (vma->end <= cursor) { + node = rb_next(node); + continue; + } + if (vma->start > cursor) { + return false; + } + if (vma->end > cursor) { + cursor = vma->end; + } + node = rb_next(node); + } + return cursor >= end; +} + +static void vma_try_merge_neighbors_locked(VmSpace* space, VmAreaStruct* vma) +{ + struct rb_node* prev_node = rb_prev(&vma->rb); + if (prev_node) { + VmAreaStruct* prev = node_to_vma(prev_node); + if (prev->end == vma->start && prev->perm == vma->perm) { + prev->end = vma->end; + rb_erase(&vma->rb, &space->vma_tree); + delete vma; + vma = prev; + } + } + + struct rb_node* next_node = rb_next(&vma->rb); + if (next_node) { + VmAreaStruct* next = node_to_vma(next_node); + if (vma->end == next->start && vma->perm == next->perm) { + vma->end = next->end; + rb_erase(&next->rb, &space->vma_tree); + delete next; + } + } +} + +static int vma_split_locked(VmSpace* space, VmAreaStruct* vma, uint64 split) +{ + if (split <= vma->start || split >= vma->end) { + return 0; + } + + VmAreaStruct* right = new VmAreaStruct; + if (!right) { + return -ENOMEM; + } + + right->start = split; + right->end = vma->end; + right->perm = vma->perm; + right->mm = space; + + vma->end = split; + rb_add(&right->rb, &space->vma_tree, vma_less); + return 0; +} + +static int vma_split_range_locked(VmSpace* space, uint64 start, uint64 end) +{ + if (end <= start) { + return 0; + } + + struct rb_node* start_node = rb_find(&start, &space->vma_tree, vma_cmp); + if (!start_node) { + return -ENOMEM; + } + VmAreaStruct* start_vma = node_to_vma(start_node); + int ret = vma_split_locked(space, start_vma, start); + if (ret < 0) { + return ret; + } + + uint64 end_key = end - 1; + struct rb_node* end_node = rb_find(&end_key, &space->vma_tree, vma_cmp); + if (!end_node) { + return -ENOMEM; + } + VmAreaStruct* end_vma = node_to_vma(end_node); + return vma_split_locked(space, end_vma, end); +} + +/* ------------------------------------------------------------------ */ +/* VmSpace lifecycle */ +/* ------------------------------------------------------------------ */ + +VmSpace* VmSpace::create() { + VmSpace* space = new VmSpace; + if (!space) return nullptr; + + space->root_pa = pmap::create(); + if (!space->root_pa) { + delete space; + return nullptr; + } + + space->vma_tree = RB_ROOT; + space->lock.init("VmSpace"); + space->refcount.store(1, __ATOMIC_RELAXED); + space->brk_start = 0; + space->brk_end = 0; + return space; +} + +void VmSpace::incref() { + refcount.fetch_add(1, __ATOMIC_RELAXED); +} + +void VmSpace::put() { + if (refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) { + destroy(); + } +} + +void VmSpace::destroy() { + /* Drain VMA tree under lock, then release BEFORE delete this */ + lock.acquire(); + uint64 root = root_pa; + struct rb_node* node = rb_first(&vma_tree); + while (node) { + VmAreaStruct* vma = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); + + /* Decref and dec mapcount for all mapped physical pages */ + for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { + uint64 pa; + if (pmap::lookup(root, addr, &pa)) { + Page* pg = pa_to_page(phys_addr(pa)); + page_dec_mapcount(pg); + page_decref(pg); + } + } + + node = rb_next(node); + rb_erase(&vma->rb, &vma_tree); + delete vma; + } + lock.release(); + + /* Destroy hardware page tables outside the lock */ + pmap::destroy(root); + + /* Finally free VmSpace itself */ + delete this; +} + +/* ------------------------------------------------------------------ */ +/* Public API: VMA manipulation */ +/* ------------------------------------------------------------------ */ + +int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) { + if (!space) return -EINVAL; + + va = page_align_down(va); + len = page_align_up(len); + if (len == 0) return 0; + if (va < kUserVaBase) return -EINVAL; + if (va >= kUserVaLimit) return -EINVAL; + if (va + len < va || va + len > kUserVaLimit) return -EINVAL; + + VmAreaStruct* vma = new VmAreaStruct; + if (!vma) return -ENOMEM; + vma->start = va; + vma->end = va + len; + vma->perm = perm | VM_USER; // Enforce user-mode access (MI flag) + vma->mm = space; + + LockGuard guard(space->lock); + if (vma_overlaps_locked(space, vma->start, vma->end)) { + delete vma; + return -EINVAL; + } + rb_add(&vma->rb, &space->vma_tree, vma_less); + vma_try_merge_neighbors_locked(space, vma); + return 0; +} + +int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) { + if (!space) return -EINVAL; + va = page_align_down(va); + len = page_align_up(len); + if (len == 0) return 0; + if (va < kUserVaBase) return -EINVAL; + if (va >= kUserVaLimit) return -EINVAL; + if (va + len < va || va + len > kUserVaLimit) return -EINVAL; + uint64 end = va + len; + + LockGuard guard(space->lock); + struct rb_node* node = rb_first(&space->vma_tree); + while (node) { + VmAreaStruct* vma = node_to_vma(node); + struct rb_node* next = rb_next(node); + + if (vma->end <= va) { + node = next; + continue; + } + if (vma->start >= end) { + break; + } + + uint64 unmap_start = (vma->start > va) ? vma->start : va; + uint64 unmap_end = (vma->end < end) ? vma->end : end; + + if (unmap_start < unmap_end) { + for (uint64 addr = unmap_start; addr < unmap_end; addr += kPageSize) { + uint64 pa; + if (pmap::lookup(space->root_pa, addr, &pa)) { + pmap::unmap(space->root_pa, addr); + Page* pg = pa_to_page(phys_addr(pa)); + page_dec_mapcount(pg); + page_decref(pg); + } + } + } + + if (unmap_start == vma->start && unmap_end == vma->end) { + rb_erase(&vma->rb, &space->vma_tree); + delete vma; + } else if (unmap_start == vma->start) { + rb_erase(&vma->rb, &space->vma_tree); + vma->start = unmap_end; + rb_add(&vma->rb, &space->vma_tree, vma_less); + } else if (unmap_end == vma->end) { + vma->end = unmap_start; + } else { + VmAreaStruct* right = new VmAreaStruct; + if (!right) { + return -ENOMEM; + } + right->start = unmap_end; + right->end = vma->end; + right->perm = vma->perm; + right->mm = space; + + vma->end = unmap_start; + rb_add(&right->rb, &space->vma_tree, vma_less); + break; + } + + node = next; + } + return 0; +} + +int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) +{ + if (!space) return -EINVAL; + va = page_align_down(va); + len = page_align_up(len); + if (len == 0) return -EINVAL; + if (va < kUserVaBase) return -EINVAL; + if (va >= kUserVaLimit) return -EINVAL; + if (va + len < va || va + len > kUserVaLimit) return -EINVAL; + + uint64 end = va + len; + + LockGuard guard(space->lock); + if (!vma_range_covered_locked(space, va, end)) { + return -ENOMEM; + } + int ret = vma_split_range_locked(space, va, end); + if (ret < 0) { + return ret; + } + + bool tlb_flush_needed = false; + struct rb_node* node = rb_find(&va, &space->vma_tree, vma_cmp); + while (node) { + VmAreaStruct* vma = node_to_vma(node); + if (vma->start >= end) { + break; + } + if (vma->end <= va) { + node = rb_next(node); + continue; + } + + vma->perm = (perm | VM_USER); + + for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { + uint64 pa; + if (!pmap::lookup(space->root_pa, addr, &pa)) { + continue; + } + + if (perm == 0) { + Page* pg = pa_to_page(phys_addr(pa)); + page_dec_mapcount(pg); + page_decref(pg); + pmap::unmap(space->root_pa, addr); + tlb_flush_needed = true; + continue; + } + + uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); + if ((vma->perm & VM_WRITE) && pmap::is_cow(space->root_pa, addr)) { + pte_perm &= ~pmap::PTE_W; + pte_perm |= pmap::PTE_COW; + } + + int ret = pmap::protect(space->root_pa, addr, pte_perm); + if (ret < 0) { + return ret; + } + tlb_flush_needed = true; + } + + node = rb_next(node); + } + + if (tlb_flush_needed) { + tlb_shootdown_range(space, va, end); + } + return 0; +} + +uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align) +{ + if (!space) return 0; + length = page_align_up(length); + if (length == 0) return 0; + if (align < kPageSize) align = kPageSize; + + LockGuard guard(space->lock); + + /* Linux: arch_get_unmapped_area() starts from mm->mmap_base. + * RISC-V: TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3). + * Fall back to kUserVaBase if mmap_base not set (kernel VmSpaces). */ + uint64 base = space->mmap_base ? space->mmap_base : kUserVaBase; + uint64 cursor = align_up_any(base, align); + struct rb_node* node = rb_first(&space->vma_tree); + while (node) { + VmAreaStruct* vma = node_to_vma(node); + if (cursor <= vma->start) { + if (cursor > kUserVaLimit - length) { + return 0; + } + if (cursor + length <= vma->start) { + return cursor; + } + } + if (cursor < vma->end) { + cursor = vma->end; + } + cursor = align_up_any(cursor, align); + node = rb_next(node); + } + + if (cursor > kUserVaLimit - length) { + return 0; + } + return cursor; +} + +bool vma_range_covered(VmSpace* space, uint64 start, uint64 end) +{ + if (!space) return false; + start = page_align_down(start); + end = page_align_up(end); + if (end <= start) return true; + + LockGuard guard(space->lock); + return vma_range_covered_locked(space, start, end); +} + +/* ------------------------------------------------------------------ */ +/* Page fault handler (demand paging) */ +/* ------------------------------------------------------------------ */ + +static bool vma_allows(const VmAreaStruct* vma, VmFaultCause cause) +{ + switch (cause) { + case VmFaultCause::Read: + return (vma->perm & VM_READ) != 0; + case VmFaultCause::Write: + return (vma->perm & VM_WRITE) != 0; + case VmFaultCause::Exec: + return (vma->perm & VM_EXEC) != 0; + default: + return false; + } +} + +int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause) { + if (!space) return -EINVAL; + + uint64 aligned_addr = page_align_down(fault_addr); + + LockGuard guard(space->lock); + struct rb_node* node = rb_find(&fault_addr, &space->vma_tree, vma_cmp); + if (!node) { + return -EFAULT; // No VMA covers this address → segfault + } + + VmAreaStruct* vma = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); + + if (!vma_allows(vma, cause)) { + return -EFAULT; + } + + /* Check if page is already mapped */ + uint64 paddr; + if (pmap::lookup(space->root_pa, aligned_addr, &paddr)) { + if (cause == VmFaultCause::Write && pmap::is_cow(space->root_pa, aligned_addr)) { + /* Store Page Fault on a COW page */ + Page* page = pmm_alloc_page(); + if (!page) return -ENOMEM; + uint64 new_pa = page_to_pa(page).raw; + + /* Copy page contents */ + uint8* dst = reinterpret_cast(pa_to_va(phys_addr(new_pa)).raw); + const uint8* src = reinterpret_cast(pa_to_va(phys_addr(paddr)).raw); + for (uint64 i = 0; i < kPageSize; i++) { + dst[i] = src[i]; + } + + /* Remap with write permission, clearing COW */ + uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); + if (pmap::map(space->root_pa, aligned_addr, new_pa, pte_perm) != 0) { + pmm_free_page(page); + return -ENOMEM; + } + + /* New page gets mapcount = 1 (we just mapped it) */ + page_inc_mapcount(page); + + /* Decrement mapcount and refcount of old page */ + Page* old_page = pa_to_page(phys_addr(paddr)); + page_dec_mapcount(old_page); + page_decref(old_page); + + /* Flush TLB to remove old read-only cached translation */ + tlb_shootdown_page(space, aligned_addr); + return 0; + } + /* DEBUG: page already mapped but still faulting — dump PTE */ + { + uint64 cur_satp; + asm volatile("csrr %0, satp" : "=r"(cur_satp)); + printk(KERN_ERR "fault: va=%llx pa=%llx cause=%d pte=%llx satp=%llx root=%llx\n", + aligned_addr, paddr, static_cast(cause), + pmap::lookup_pte(space->root_pa, aligned_addr), + cur_satp, space->root_pa); + } + return 0; // Already mapped, maybe read fault on existing page + } + + /* Allocate physical page and zero it. + * Linux: do_anonymous_page() uses alloc_zeroed_user_highpage_movable(). + * Anonymous pages MUST be zeroed — userspace expects MAP_ANONYMOUS + * pages to contain all zeros (POSIX requirement). */ + Page* page = pmm_alloc_page(); + if (!page) return -ENOMEM; + + uint64 pa = page_to_pa(page).raw; + + /* Zero the page before mapping it to userspace */ + /* Zero the page before mapping it to userspace */ + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + for (uint64 i = 0; i < kPageSize; i++) kva[i] = 0; + + /* Translate MI permission flags to hardware PTE bits (MD layer) */ + uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); + + if (pmap::map(space->root_pa, aligned_addr, pa, pte_perm) != 0) { + pmm_free_page(page); + return -ENOMEM; + } + + /* Initial mapping: refcount is already 1 from pmm_alloc_page. + * Set mapcount = 1 to track this PTE mapping. */ + page_inc_mapcount(page); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Fork / Clone (COW infrastructure) */ +/* ------------------------------------------------------------------ */ + +VmSpace* vmm_clone(VmSpace* parent) { + if (!parent) return nullptr; + + VmSpace* child = VmSpace::create(); + if (!child) return nullptr; + + child->brk_start = parent->brk_start; + child->brk_end = parent->brk_end; + + LockGuard parent_guard(parent->lock); + + struct rb_node* node = rb_first(&parent->vma_tree); + while (node) { + VmAreaStruct* parent_vma = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); + + VmAreaStruct* child_vma = new VmAreaStruct; + if (!child_vma) { + child->destroy(); + return nullptr; + } + child_vma->start = parent_vma->start; + child_vma->end = parent_vma->end; + child_vma->perm = parent_vma->perm; + child_vma->mm = child; + + /* Copy pages and mark COW if writable */ + for (uint64 va = child_vma->start; va < child_vma->end; va += kPageSize) { + uint64 pa; + if (pmap::lookup(parent->root_pa, va, &pa)) { + /* Make parent COW if it was writable */ + if (child_vma->perm & VM_WRITE) { + pmap::make_cow(parent->root_pa, va); + /* Flush TLB for parent since we reduced permissions */ + tlb_shootdown_page(parent, va); + } + + /* Map in child as COW or read-only */ + uint64 pte_perm = pmap::vm_perm_to_pte(child_vma->perm); + if (child_vma->perm & VM_WRITE) { + pte_perm &= ~pmap::PTE_W; + pte_perm |= pmap::PTE_COW; + } + + if (pmap::map(child->root_pa, va, pa, pte_perm) != 0) { + /* If map fails, we leak what we did in this loop for now, + * but child->destroy() will clean it up safely since + * we increment refcounts as we go. */ + delete child_vma; + child->destroy(); + return nullptr; + } + + /* Increment physical page refcount and mapcount */ + Page* shared_page = pa_to_page(phys_addr(pa)); + page_incref(shared_page); + page_inc_mapcount(shared_page); + } + } + + rb_add(&child_vma->rb, &child->vma_tree, vma_less); + node = rb_next(node); + } + + return child; +} diff --git a/mm/vmm.cc b/mm/vmm.cc new file mode 100644 index 0000000..51617e3 --- /dev/null +++ b/mm/vmm.cc @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * vmm.cc — Virtual Memory Manager (Phase 1: Kernel Page Table) + * + * Builds a fine-grained kernel page table, activates it, and retires + * the early_pgdir identity mapping from entry.S. + * + * With memblock integration, the initialization order is: + * memblock_init → vmm_init (allocates page_map from memblock, + * maps vmemmap, activates page table) → pmm_init (buddy system + * starts with page_map already at vmemmap VA — no relocation). + * + * Reference: docs/specs/vmm.md + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Linker-exported section boundaries (virtual addresses) */ +extern "C" char _stext[], _etext[]; +extern "C" char _srodata[], _erodata[]; +extern "C" char _sdata[], _edata[]; +extern "C" char _sbss[], _ebss[]; +extern "C" char _kernel_end[]; + +/* Global kernel page table root PA — external linkage for task_exit */ +uint64 g_kernel_pgtbl = 0; + +namespace { + +/* VMM exports for PMM: page_map location after vmemmap mapping. */ +uint64 g_vmemmap_page_map_pa = 0; +uint64 g_vmemmap_page_map_size = 0; +uint64 g_vmemmap_num_pages = 0; + +/* Convert a linker symbol to PA (kernel is executing in higher-half VA). */ +inline uint64 sym_to_pa(const void *sym) { + return boot_va_to_pa(virt_addr(reinterpret_cast(sym))).raw; +} + +/* Map a PA range into the kernel page table via the direct map. */ +void map_range(uint64 root, uint64 pa_start, uint64 pa_end, + uint64 perm) { + for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { + uint64 va = pa_to_va(phys_addr(pa)).raw; + int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); + if (ret != 0) { + kernel_panic("vmm_init: pmap::map failed"); + } + } +} + +/* Map a PA range into the kernel page table at the fixed linked VA. */ +void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end, + uint64 perm) { + for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { + uint64 va = boot_pa_to_va(phys_addr(pa)).raw; + int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); + if (ret != 0) { + kernel_panic("vmm_init: pmap::map (linked) failed"); + } + } +} + +/* Map a PA range to an explicit VA base, using 2MB pages where possible. */ +void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start, + uint64 pa_end, uint64 perm) { + constexpr uint64 k2MB = 2ULL * 1024 * 1024; + uint64 pa = pa_start; + uint64 va = va_base; + + /* Map 2MB-aligned chunks with mega pages */ + while (pa + k2MB <= pa_end) { + if ((va & (k2MB - 1)) == 0 && (pa & (k2MB - 1)) == 0) { + int ret = pmap::map_2mb(root, va, pa, perm | pmap::PTE_G); + if (ret != 0) { + kernel_panic("vmm_init: pmap::map_2mb failed"); + } + pa += k2MB; + va += k2MB; + } else { + break; + } + } + + /* Map remaining 4KB tail */ + while (pa < pa_end) { + int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); + if (ret != 0) { + kernel_panic("vmm_init: vmemmap 4KB pmap::map failed"); + } + pa += kPageSize; + va += kPageSize; + } +} + +} // namespace + +uint64 vmm_get_page_map_pa() { return g_vmemmap_page_map_pa; } +uint64 vmm_get_page_map_size() { return g_vmemmap_page_map_size; } +uint64 vmm_get_num_pages() { return g_vmemmap_num_pages; } + +void vmm_init() { + /* 1. Allocate new root page table (from memblock) */ + uint64 root = pmap::create(); + if (!root) { + kernel_panic("vmm_init: failed to create root page table"); + } + + /* 2. Compute PA boundaries of each kernel section */ + uint64 text_start = page_align_down(sym_to_pa(_stext)); + uint64 text_end = page_align_up(sym_to_pa(_etext)); + uint64 ro_start = page_align_down(sym_to_pa(_srodata)); + uint64 ro_end = page_align_up(sym_to_pa(_erodata)); + uint64 data_start = page_align_down(sym_to_pa(_sdata)); + uint64 bss_end = page_align_up(sym_to_pa(_ebss)); + + /* Memory extent from memblock */ + uint64 mem_base = memblock_get_mem_base(); + uint64 mem_size = memblock_get_mem_size(); + uint64 mem_end = mem_base + mem_size; + + /* 3. Map kernel image at FIXED linked VA. + * + * Kernel text/data are linked at the bootstrap higher-half addresses + * (kDirectMapBaseDefault + PA). They must remain executable/readable at + * those addresses until full kernel-image relocation is implemented. + */ + map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X); + + /* 4. Map .rodata (R) at fixed linked VA. + * We map [_srodata, _sdata) so that the .lume.reloc section (which + * sits in its own padded output section between _erodata and _sdata) + * is also covered. Relocator only reads it during early boot, but + * mapping it keeps post-vmm accesses to __lume_reloc_* symbols sane. */ + map_range_linked(root, ro_start, page_align_down(sym_to_pa(_sdata)), + pmap::PTE_R); + + /* 5. Map .data + .bss (RW) at fixed linked VA */ + map_range_linked(root, data_start, bss_end, pmap::PTE_R | pmap::PTE_W); + + /* 6. Map usable physical RAM into the KASLR-slid direct map. + * + * The direct map provides pa_to_va() / va_to_pa() for the physical + * address space. We map from kPhysStart (the kernel's linked load + * address), skipping [mem_base, kPhysStart) because that region is + * PMP-protected firmware (OpenSBI) — S-mode cannot access it. + * + * Linux does the same on RISC-V: the linear map covers only the + * usable RAM range, not firmware-reserved regions. + * + * Uses 2MB huge pages for efficiency (64 entries for 128MB vs 32K). + */ + uint64 dm_start = kPhysStart; + if (dm_start < mem_base) { + dm_start = mem_base; + } + uint64 dm_va_base = pa_to_va(phys_addr(dm_start)).raw; + map_range_at_huge(root, dm_va_base, dm_start, mem_end, + pmap::PTE_R | pmap::PTE_W); + + /* 7. Allocate page_map from memblock (top of RAM) and map at VMEMMAP. + * + * Since memblock is active (PMM not yet initialized), pmap::map + * allocates page table pages from memblock too. page_map starts + * life at the vmemmap VA — no relocation ever needed. + */ + uint64 num_pages = mem_size / kPageSize; + uint64 page_map_bytes = num_pages * sizeof(Page); + uint64 page_map_span = page_align_up(page_map_bytes); + uint64 page_map_pa = memblock_alloc_top(page_map_span, kPageSize); + + if (!page_map_pa) { + kernel_panic("vmm_init: memblock failed to allocate page_map"); + } + + kprintf("[vmm] vmemmap: %llu pages, %llu KB at PA 0x%llx\n", num_pages, + page_map_span / 1024, page_map_pa); + + /* Map page_map at vmemmap_base from KaslrLayout (randomised by KASLR), using 2MB pages where possible */ + map_range_at_huge(root, arch::kaslr_layout_get().vmemmap_base, page_map_pa, + page_map_pa + page_map_span, pmap::PTE_R | pmap::PTE_W); + + /* Export for pmm_init() */ + g_vmemmap_page_map_pa = page_map_pa; + g_vmemmap_page_map_size = page_map_bytes; + g_vmemmap_num_pages = num_pages; + + /* 8. Pre-map console MMIO before page table switch */ + g_kernel_pgtbl = root; + console_init(); + + /* 9. Activate the new page table */ + pmap::activate(root); + + /* Console MMIO access must now use runtime (KASLR) direct-map base. */ + console_use_runtime_mapping(); + + kprintf("[vmm] vmm_init complete (vmemmap at 0x%llx)\n", arch::kaslr_layout_get().vmemmap_base); +} + +void vmm_init_ap() { + if (!g_kernel_pgtbl) { + kernel_panic("vmm_init_ap: kernel page table not ready"); + } + pmap::activate(g_kernel_pgtbl); +} + +void vmm_map_kernel_mmio(uint64 pa, uint64 size) { + if (!g_kernel_pgtbl) { + kernel_panic("vmm_map_kernel_mmio: kernel page table not ready"); + } + + uint64 aligned_pa = page_align_down(pa); + uint64 aligned_size = page_align_up(pa + size) - aligned_pa; + + map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size, + pmap::PTE_R | pmap::PTE_W | pmap::PTE_G); + tlb_shootdown_all(); +} diff --git a/na/.clang-format b/na/.clang-format deleted file mode 100644 index 141e1e2..0000000 --- a/na/.clang-format +++ /dev/null @@ -1,35 +0,0 @@ -Language: Cpp -Standard: c++23 -BasedOnStyle: Google - -# Core Formatting -IndentWidth: 4 -ColumnLimit: 100 -TabWidth: 4 -UseTab: Never - -# Pointers and References -PointerAlignment: Left -ReferenceAlignment: Left - -# Includes -SortIncludes: true - -# Alignment -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignConsecutiveMacros: true - -# Control Statements -BreakBeforeBraces: Attach -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Empty - -# Spaces -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpaceAfterTemplateKeyword: true diff --git a/na/.clang-tidy b/na/.clang-tidy deleted file mode 100644 index 1af2c5a..0000000 --- a/na/.clang-tidy +++ /dev/null @@ -1,23 +0,0 @@ -Checks: > - -*, - clang-analyzer-*, - performance-*, - bugprone-*, - readability-*, - misc-*, - modernize-*, - -modernize-use-trailing-return-type, - -modernize-use-nodiscard, - -readability-identifier-length - -WarningsAsErrors: '' -HeaderFilterRegex: '.*' -FormatStyle: 'file' -CheckOptions: - readability-identifier-naming.ClassCase: PascalCase - readability-identifier-naming.StructCase: PascalCase - readability-identifier-naming.VariableCase: lower_case - readability-identifier-naming.FunctionCase: lower_case - readability-identifier-naming.GlobalVariableCase: lower_case - readability-identifier-naming.GlobalVariablePrefix: g_ - readability-identifier-naming.PrivateMemberSuffix: _ diff --git a/na/Makefile b/na/Makefile deleted file mode 100644 index 88689f5..0000000 --- a/na/Makefile +++ /dev/null @@ -1,419 +0,0 @@ -# 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 - -# ============================================================ -# Build Configuration -# ============================================================ -BUILD := build - -# Make sure `make` with no arguments builds the kernel image, not the -# first rule encountered during makefile parsing. scripts/Kconfig.mk -# (included next) defines `defconfig:` at the top of its rule list, -# which would otherwise become the default goal and silently overwrite -# .config from arch/$(ARCH)/defconfig. -.DEFAULT_GOAL := all - -# Load Kbuild helpers (Q, $(call cmd,...), quiet_cmd_* / cmd_* templates, -# kbuild-objs translator). Must come before Kconfig.mk so subsequent -# rules can use $(call cmd,...) immediately. -include scripts/Kbuild.include - -# Load Kconfig infrastructure (reads .config, sets KCPPFLAGS) -include scripts/Kconfig.mk - -ARCH ?= $(CONFIG_ARCH) - -# Two boot products — same pair as Linux RISC-V's arch/riscv/boot/Makefile -# (vmlinux + Image), but named LumeOS-style: -# VMLUME -> build/vmlume.elf : ELF with .symtab/PT_DYNAMIC/.rela.dyn -# IMAGE -> build/vmlume.bin : raw binary (objcopy -O binary) -VMLUME := $(BUILD)/vmlume.elf -IMAGE := $(BUILD)/vmlume.bin - -# Initramfs paths -INITRAMFS_DIR := $(BUILD)/initramfs -INITRAMFS_CPIO := $(BUILD)/initramfs.cpio -INITRAMFS_OBJ := $(BUILD)/initramfs.o -USR_INIT_ELF := $(BUILD)/usr/init -# Relocations: linker emits .rela.dyn under -shared -Bsymbolic; boot -# code in arch/riscv/mm/init.cc::relocate_kernel walks it at startup. -LDS_SRC := arch/$(ARCH)/kernel/lumeos.lds.S -LINKER_SCRIPT := $(BUILD)/lumeos.lds -PYTHON ?= python3 - -# ============================================================ -# Toolchain Detection -# ============================================================ -ifndef TOOLCHAIN_PREFIX -ifeq ($(ARCH),riscv) - # Prefer the linux-gnu toolchain — it supports -shared / -pie which - # are required to emit .rela.dyn (Linux RISC-V kernel uses the same). - # The bare-metal `riscv64-unknown-elf-` is kept only as a last-resort - # fallback; with that one, `-shared` fails ("not supported") and the - # kernel can't relocate at runtime. - TOOLCHAIN_PREFIX := $(shell \ - if command -v riscv64-unknown-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-unknown-linux-gnu-; \ - elif command -v riscv64-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-linux-gnu-; \ - else \ - echo riscv64-unknown-linux-gnu-; \ - fi) -else ifeq ($(ARCH),aarch64) - TOOLCHAIN_PREFIX := aarch64-linux-gnu- -else ifeq ($(ARCH),x86_64) - TOOLCHAIN_PREFIX := x86_64-linux-gnu- -endif -endif - -CC := $(TOOLCHAIN_PREFIX)gcc -CXX := $(TOOLCHAIN_PREFIX)g++ -LD := $(TOOLCHAIN_PREFIX)ld -OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy -OBJDUMP := $(TOOLCHAIN_PREFIX)objdump -# Objcopy output formats for binary-to-ELF relocation blob -OBJCOPY_ELF_riscv := elf64-littleriscv -OBJCOPY_ELF_aarch64 := elf64-littleaarch64 -OBJCOPY_ELF_x86_64 := elf64-x86-64 -OBJCOPY_ELF := $(OBJCOPY_ELF_$(ARCH)) - -OBJCOPY_BINARCH_riscv := riscv -OBJCOPY_BINARCH_aarch64 := aarch64 -OBJCOPY_BINARCH_x86_64 := i386:x86-64 -OBJCOPY_BINARCH := $(OBJCOPY_BINARCH_$(ARCH)) - -# Common flags (base, before module includes) -WARNINGS := -Wall -Wextra -Wno-unused-parameter -INCLUDES := -Iinclude -I. - -# ============================================================ -# Source Collection (Non-Recursive Module Fragments) -# Module .mk files may append to SRCS_CC, SRCS_S, INCLUDES -# ============================================================ -SRCS_CC := -SRCS_C := -SRCS_S := -SRCS_s := - --include arch/$(ARCH)/module.mk -include kernel/module.mk -include mm/module.mk -include fs/module.mk -include drivers/base/module.mk -include drivers/module.mk -include drivers/tty/module.mk -include fdt/module.mk -include lib/module.mk -include test/module.mk - -# Compiler flags (defined AFTER includes so INCLUDES is complete) -CFLAGS := $(WARNINGS) -O2 -g \ - -ffreestanding -nostdlib \ - -mcmodel=medany \ - -fPIE \ - -MMD -MP \ - $(KCPPFLAGS) \ - $(INCLUDES) \ - -I$(BUILD)/generated - -# -fPIE makes the compiler emit position-independent code (PC-relative -# addressing + GOT for globals), which is what `-shared` linking expects. -# Mirrors Linux's arch/riscv/Makefile under CONFIG_RELOCATABLE=y: -# KBUILD_CFLAGS += -fPIE - -# C flags for musl libc sources: add libc-shim headers first -CFLAGS_LIBC := $(CFLAGS) -Iinclude/libc-shim \ - -Wno-parentheses -Wno-sign-compare - -CXXFLAGS := $(CFLAGS) \ - -std=c++17 \ - -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ - -fno-threadsafe-statics - -# Assembler flags MUST NOT include -fPIE. -# Linux's arch/riscv/Makefile adds -fPIE only to KBUILD_CFLAGS, NOT to -# KBUILD_AFLAGS: with -fPIE the gcc driver puts the assembler in PIC -# mode, where the `la` macro silently expands to a GOT-indirect load -# (`auipc + ld` reading a slot that holds the link-time VA). In our -# pre-MMU boot path that VA is unmapped, so any `la sym, sym` faults -# and reboots into a `_start` loop. Stripping -fPIE makes `la` emit -# direct PC-relative addressing (`auipc + addi`) which works at PA. -ASFLAGS := $(filter-out -fPIE,$(CFLAGS)) - -# Link as a position-independent shared object so the linker emits -# `.rela.dyn` with R_RISCV_RELATIVE entries that the boot stub applies -# at load time. Mirrors Linux's arch/riscv/Makefile §10: -# LDFLAGS_vmlinux += -shared -Bsymbolic -z notext -# `-Bsymbolic` resolves all global refs at link time (no dynamic -# resolution); `-z notext` lets us keep relocations in code pages. -LDFLAGS := -nostdlib -shared -Bsymbolic -z notext -z norelro --no-dynamic-linker - -# ============================================================ -# Object File Computation -# ============================================================ -OBJS := $(patsubst %.cc,$(BUILD)/%.o,$(SRCS_CC)) \ - $(patsubst %.c,$(BUILD)/%.o,$(SRCS_C)) \ - $(patsubst %.S,$(BUILD)/%.o,$(SRCS_S)) \ - $(patsubst %.s,$(BUILD)/%.o,$(SRCS_s)) - -DEPS := $(OBJS:.o=.d) - -# ============================================================ -# Build Rules -# ============================================================ -.PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig - -all: $(VMLUME) $(IMAGE) - -# Generate autoconf.h before any compilation -$(AUTOCONF_H): scripts/Kconfig.mk $(wildcard .config) - $(generate-autoconf) - -# ============================================================ -# Generated header: asm-offsets.h -# -# Linux-style trick (mirrors include/linux/kbuild.h + arch/*/kernel/ -# asm-offsets.c + scripts/Makefile.build): compile asm-offsets.c to -# assembly, scrape the magic `->SYM val` markers produced by the -# DEFINE/OFFSET inline-asm macros, rewrite them into `#define` -# directives via sed. head.S and friends then `#include -# ` to access struct field offsets without -# having to hand-maintain them — re-order a struct field, the -# generated constant follows. -# ============================================================ -ASM_OFFSETS_C := arch/$(ARCH)/kernel/asm-offsets.c -ASM_OFFSETS_S := $(BUILD)/arch/$(ARCH)/kernel/asm-offsets.s -ASM_OFFSETS_H := $(BUILD)/generated/asm-offsets.h -ASM_OFFSETS_SED := scripts/extract-asm-offsets.sed - -# Compile asm-offsets.c to *.s only — we never want the .o linked in. -$(ASM_OFFSETS_S): $(ASM_OFFSETS_C) $(AUTOCONF_H) - @echo " CC -S $<" - @mkdir -p $(dir $@) - @$(CC) $(CFLAGS_LIBC) -S $< -o $@ - -# Rewrite ->NAME markers into #define lines. Header guard hand-rolled -# at the top, sed body in the middle, closing #endif at the bottom. -$(ASM_OFFSETS_H): $(ASM_OFFSETS_S) $(ASM_OFFSETS_SED) - @echo " GEN $@" - @mkdir -p $(dir $@) - @(echo "/* AUTO-GENERATED — DO NOT EDIT */"; \ - echo "/* Generated from $(ASM_OFFSETS_C) by scripts/extract-asm-offsets.sed */"; \ - echo "#ifndef _LUME_GENERATED_ASM_OFFSETS_H"; \ - echo "#define _LUME_GENERATED_ASM_OFFSETS_H"; \ - echo ""; \ - sed -n -f $(ASM_OFFSETS_SED) $<; \ - echo ""; \ - echo "#endif /* _LUME_GENERATED_ASM_OFFSETS_H */") > $@ - -# All objects depend on autoconf.h and asm-offsets.h -$(OBJS): $(AUTOCONF_H) $(ASM_OFFSETS_H) - -# ============================================================ -# Generic %.pi.o post-processing rule -# -# Mirrors arch/riscv/kernel/pi/Makefile §26-30 in Linux: -# -# $(obj)/%.pi.o: OBJCOPYFLAGS := --prefix-symbols=__pi_ -# --remove-section=.note.gnu.property -# --prefix-alloc-sections=.init.pi -# $(obj)/%.pi.o: $(obj)/%.o FORCE -# $(call if_changed,objcopy) -# -# Purpose: take a regular .o produced by the normal compile rule and -# rewrite it so the early position-independent code can coexist with -# the rest of the kernel without symbol clashes: -# -# --prefix-symbols=__pi_ Every global symbol becomes __pi_. -# head.S calls them as __pi_fdt_path_offset -# etc., keeping the pre-MMU namespace -# cleanly isolated. -# -# --prefix-alloc-sections=.init.pi -# All SHF_ALLOC sections get re-prefixed -# so they land in .init.pi in vmlinux.lds.S, -# which positions them where the boot -# loader has already mapped memory. -# -# --remove-section=.note.gnu.property -# Discarded because post-rename it can -# confuse the linker about ELF features. -# -# Generic pattern (`$(BUILD)/%.pi.o`) so any subdir's source can opt in -# by listing a `.pi.o` target in its SRCS. The companion compile-side -# CFLAGS overrides (replacing -fPIE with -fpie, adding -Os, etc.) are -# expected to come from the subdir's own .mk fragment via target- -# specific CFLAGS_LIBC / CXXFLAGS overrides. -# ============================================================ -$(BUILD)/%.pi.o: OBJCOPYFLAGS := --prefix-symbols=__pi_ \ - --remove-section=.note.gnu.property \ - --prefix-alloc-sections=.init.pi - -$(BUILD)/%.pi.o: $(BUILD)/%.o - $(call cmd,objcopy) - -# Preprocess linker script template (.lds.S → .lds) -$(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h - @echo " LDS $@" - @mkdir -p $(dir $@) - @$(CC) -E -P -x c $(INCLUDES) $< -o $@ - -# ============================================================ -# Initramfs: pack a root filesystem into a newc cpio. -# -# Pipeline mirrors Linux's Kbuild: -# usr/gen_init_cpio — host C tool, produces cpio bytes -# scripts/gen_initramfs.sh — wrapper that prepares the listing, -# handles directories vs. listing files, -# applies uid/gid remap, then invokes the -# host tool. -# Sourced from $(CONFIG_INITRAMFS_SOURCE) — see scripts/Kconfig.mk. -# ============================================================ - -# Host compiler — distinct from $(CC), which may be the cross compiler. -HOSTCC ?= cc -HOSTCFLAGS ?= -O2 -Wall - -# Output path matches Linux convention (usr/gen_init_cpio) so the -# verbatim copy of scripts/gen_initramfs.sh from Linux finds it via its -# hardcoded relative path when invoked from the kernel source root. -GEN_INIT_CPIO := usr/gen_init_cpio - -$(GEN_INIT_CPIO): usr/gen_init_cpio.c - @echo " HOSTCC $@" - @$(HOSTCC) $(HOSTCFLAGS) -o $@ $< - -INITRAMFS_SOURCE := $(CONFIG_INITRAMFS_SOURCE) - -# Record the chosen source so changing CONFIG_INITRAMFS_SOURCE on the -# command line forces a rebuild — without this, `make` only compares -# file mtimes and would happily reuse a cpio packed from a different -# source as long as that other source isn't newer. Evaluated at -# Makefile parse time so the dep edge is correct from the first rule -# check. Mirrors the idea behind Linux's $(call if_changed,...). -INITRAMFS_SRC_STAMP := $(BUILD)/.initramfs_src.stamp -$(shell mkdir -p $(BUILD) 2>/dev/null; \ - prev=$$([ -f "$(INITRAMFS_SRC_STAMP)" ] && cat "$(INITRAMFS_SRC_STAMP)" || echo); \ - if [ "$$prev" != "$(INITRAMFS_SOURCE)" ]; then \ - printf '%s\n' "$(INITRAMFS_SOURCE)" > "$(INITRAMFS_SRC_STAMP)"; \ - fi) - -$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ - $(GEN_INIT_CPIO) scripts/gen_initramfs.sh - @echo " INITRAMFS $(INITRAMFS_SOURCE)" - @mkdir -p $(dir $@) - @./scripts/gen_initramfs.sh -o $@ \ - -u $(CONFIG_INITRAMFS_ROOT_UID) \ - -g $(CONFIG_INITRAMFS_ROOT_GID) \ - $(INITRAMFS_SOURCE) - -$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) - @echo " OBJCOPY $@ (initramfs)" - @$(OBJCOPY) -I binary -O $(OBJCOPY_ELF) -B $(OBJCOPY_BINARCH) \ - --rename-section .data=.init.ramfs,alloc,load,readonly,data,contents \ - $< $@ - -# Single-pass kernel link. With `-shared -Bsymbolic`, the linker -# emits `.rela.dyn` containing R_RISCV_RELATIVE entries directly into -# the output ELF — no separate gen_reloc pass. -$(VMLUME): $(OBJS) $(INITRAMFS_OBJ) $(LINKER_SCRIPT) - @echo " LD $@" - @mkdir -p $(dir $@) - @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) $(INITRAMFS_OBJ) - @$(TOOLCHAIN_PREFIX)size $@ - -# Image = raw binary, mirrors Linux's `arch/riscv/boot/Image` (objcopy -# vmlinux to raw bytes, no ELF header — what U-Boot or QEMU's `-bios` -# bare-loading would consume). Linux always builds both vmlinux and -# Image; LumeOS follows the same convention. -$(IMAGE): $(VMLUME) - @echo " OBJCOPY $@" - @$(OBJCOPY) -O binary $< $@ - -# Compile C++ sources. Recipe goes through $(call cmd,...) so that -# `make V=1` echoes the full command line. $(Q) prefix on mkdir keeps -# the directory-creation step silent in quiet mode but visible at V=1. -$(BUILD)/%.o: %.cc - $(Q)mkdir -p $(dir $@) - $(call cmd,cxx_o_cc) - -$(BUILD)/%.o: %.c - $(Q)mkdir -p $(dir $@) - $(call cmd,cc_o_c) - -$(BUILD)/%.o: %.S - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) - -$(BUILD)/%.o: %.s - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) - -# Auto-dependency tracking --include $(DEPS) - -# ============================================================ -# Run & Debug -# ============================================================ -ifeq ($(ARCH),riscv) -QEMU := qemu-system-riscv64 -QEMU_MACHINE := -machine virt -smp 4 -m 128M -nographic -QEMU_BIOS := -bios default -else ifeq ($(ARCH),aarch64) -QEMU := qemu-system-aarch64 -QEMU_MACHINE := -machine virt -cpu cortex-a53 -m 128M -nographic -QEMU_BIOS := -else ifeq ($(ARCH),x86_64) -QEMU := qemu-system-x86_64 -QEMU_MACHINE := -m 128M -nographic -QEMU_BIOS := -endif - -run: $(VMLUME) $(IMAGE) - @echo " QEMU Running..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) - -debug: $(VMLUME) $(IMAGE) - @echo " QEMU Waiting for GDB on :1234..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -s -S - -# ============================================================ -# Utilities -# ============================================================ -clean: - rm -rf $(BUILD) - -disasm: $(VMLUME) - @echo " OBJDUMP $<" - @$(OBJDUMP) -d -S $(VMLUME) > $(VMLUME).asm - @echo " Written to $(VMLUME).asm" - -fmt: - @echo " FMT Formatting source files..." - @find . -name '*.cc' -o -name '*.h' | xargs clang-format -i 2>/dev/null || true - -lint: - @echo " LINT Generating compile_commands.json via bear..." - @bear -- $(MAKE) all - @echo " LINT Running clang-tidy on C++ sources..." - @clang-tidy -p . $(SRCS_CC) || 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)" - @echo "KCPPFLAGS = $(KCPPFLAGS)" diff --git a/na/arch/riscv/config.cc b/na/arch/riscv/config.cc deleted file mode 100644 index a18a825..0000000 --- a/na/arch/riscv/config.cc +++ /dev/null @@ -1,276 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V Architecture Configuration — runtime state and KASLR - * - * Provides the runtime (post-KASLR) values of the higher-half - * virtual address bases. kaslr_init() is called once during - * early boot, before vmm_init(), to randomise the layout and - * fill the unified KaslrLayout structure. - * - * KASLR slide budget: - * The default Direct Map starts at 0xFFFFFFC0_0000_0000 (VPN[2]=256). - * The default Vmemmap starts at 0xFFFFFFD0_0000_0000 (VPN[2]=320). - * A 1GB superpage occupies one VPN[2] slot. We allow a slide of - * 0..63 slots (0..63 GB) upward, which keeps both regions inside - * the upper canonical half and avoids colliding with the kernel - * text region (linked at VPN[2] = 258 + 2 = ~0xFFFFFFC0_8020_0000). - * - * In practice, with 128MB RAM the kernel+direct map only touches - * VPN[2] 258-259, so a slide of up to 63GB is safe. - */ - -#include -#include -#include -#include -#include -#include -#include - -extern "C" char _stext[], _kernel_end[]; - -namespace arch { - -/* Global KASLR Layout Structure*/ -KaslrLayout g_kaslr_layout = {}; - -/* Runtime bases — updated by kaslr_layout_compute() */ -uint64 g_direct_map_base = kDirectMapBaseDefault; -uint64 g_vmemmap_base = kVmemmapBaseDefault; - -/* VA Range for validation */ -struct VaRange { - const char* name; - uint64 start; - uint64 end; // exclusive -}; - -/* ================================================================ - * Utilities - * ================================================================ */ -static inline uint64 align_down_page(uint64 v) -{ - return v & ~(kPageSize - 1); -} - -static inline uint64 align_up_page(uint64 v) -{ - return (v + kPageSize - 1) & ~(kPageSize - 1); -} - -static uint64 checked_add_u64(uint64 a, uint64 b, const char* detail) -{ - if (a > (~0ULL - b)) { - kernel_panic("kaslr: address overflow", detail); - } - return a + b; -} - -/* Splitmix64: non-plaintext hash derived from seed (used for seed_hash) */ -static uint64 splitmix64(uint64 x) -{ - x += 0x9e3779b97f4a7c15ULL; - x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; - x = x ^ (x >> 27); - return x * 0x94d049bb133111ebULL; -} - -/* SV39 canonical VA check: bits [63:39] must equal sign bit 38. */ -static bool is_sv39_canonical(uint64 va) -{ - uint64 sign = (va >> 38) & 1ULL; - uint64 upper = va >> 39; - return sign ? (upper == ((1ULL << 25) - 1ULL)) : (upper == 0); -} - -static bool is_sv39_kernel_half(uint64 va) -{ - return is_sv39_canonical(va) && (((va >> 38) & 1ULL) == 1ULL); -} - -static bool ranges_overlap(const VaRange& a, const VaRange& b) -{ - return (a.start < b.end) && (b.start < a.end); -} - -/* ================================================================ - * KASLR Computation (Phase A: Centralized in single function) - * - * This function: - * 1. Generates or derives random seed - * 2. Selects 1GB-aligned slot [0..63] - * 3. Computes seed_hash via splitmix64 - * 4. Fills KaslrLayout structure completely - * 5. Validates ranges (canonical, overlaps) - * 6. Logs according to CONFIG_KASLR_LOG_LEVEL - * ================================================================ */ -static void kaslr_layout_compute() -{ -#ifdef CONFIG_KASLR - constexpr uint64 k1GB = 1ULL << 30; - constexpr uint64 kMaxSlots = 64; - - /* 1. Get seed and source */ - uint64 seed = random_early_seed_value(); - RandomSeedSource rsrc = random_early_seed_source(); - KaslrSeedSource source; - switch (rsrc) { - case RandomSeedSource::FdtProvided: - source = KaslrSeedSource::kFdt; - break; - case RandomSeedSource::HwRandom: - source = KaslrSeedSource::kHardware; - break; - case RandomSeedSource::CycleFallback: - case RandomSeedSource::Unknown: - default: - source = KaslrSeedSource::kCycle; - break; - } - - /* 2. Select random slot and compute slide */ - uint64 raw = get_random_u64(); - uint64 slot = raw % kMaxSlots; - if (slot == 0) { - slot = 1; /* slot=0 causes direct_map to overlap with kernel image VA */ - } - uint64 slide = slot * k1GB; - - /* 3. Compute non-plaintext hash */ - uint64 seed_hash = splitmix64(seed ^ 0xdeadbeefdeadbeefULL); - - /* 4. Compute randomised bases */ - uint64 direct_map_base = kDirectMapBaseDefault + slide; - uint64 vmemmap_base = kVmemmapBaseDefault + slide; - - /* 5. Fill KaslrLayout */ - g_kaslr_layout.seed = seed; - g_kaslr_layout.seed_hash = seed_hash; - g_kaslr_layout.source = source; - g_kaslr_layout.slot = slot; - g_kaslr_layout.slide_bytes = slide; - g_kaslr_layout.policy_id = kKaslrPolicyId; - g_kaslr_layout.direct_map_base = direct_map_base; - g_kaslr_layout.vmemmap_base = vmemmap_base; - g_kaslr_layout.canonical_ok = false; - g_kaslr_layout.overlap_ok = false; - - /* Update global runtime bases */ - g_direct_map_base = direct_map_base; - g_vmemmap_base = vmemmap_base; - - /* 6. Self-check: canonical VA validation and overlap detection */ - uint64 mem_base = 0, mem_size = 0; - fdt_early_get_mem_info(&mem_base, &mem_size); - if (mem_size == 0) { - kernel_panic("kaslr_layout_compute: invalid memory from FDT"); - } - - /* Construct VA ranges */ - uint64 num_pages = mem_size / kPageSize; - uint64 page_bytes = num_pages * sizeof(Page); - uint64 page_span = align_up_page(page_bytes); - - VaRange direct_map { - "direct-map", - checked_add_u64(g_direct_map_base, mem_base, "direct_map start"), - checked_add_u64(checked_add_u64(g_direct_map_base, mem_base, - "direct_map start"), - mem_size, "direct_map end"), - }; - - VaRange vmemmap { - "vmemmap", - g_vmemmap_base, - checked_add_u64(g_vmemmap_base, page_span, "vmemmap end"), - }; - - VaRange kernel_img { - "kernel-image", - align_down_page(reinterpret_cast(_stext)), - align_up_page(reinterpret_cast(_kernel_end)), - }; - - /* Validate each range is canonical */ - auto check_kernel_half = [](const VaRange& r) { - if (r.start >= r.end) { - kernel_panic("kaslr: invalid VA range", r.name); - } - if (!is_sv39_kernel_half(r.start) || !is_sv39_kernel_half(r.end - 1)) { - kernel_panic("kaslr: non-canonical kernel VA range", r.name); - } - }; - - check_kernel_half(direct_map); - check_kernel_half(vmemmap); - check_kernel_half(kernel_img); - - /* Validate no overlaps */ - if (ranges_overlap(direct_map, kernel_img)) { - kernel_panic("kaslr: direct-map overlaps kernel image"); - } - if (ranges_overlap(vmemmap, kernel_img)) { - kernel_panic("kaslr: vmemmap overlaps kernel image"); - } - if (ranges_overlap(direct_map, vmemmap)) { - kernel_panic("kaslr: direct-map overlaps vmemmap"); - } - - /* Mark validation success */ - g_kaslr_layout.canonical_ok = true; - g_kaslr_layout.overlap_ok = true; - - /* 7. Logging according to CONFIG_KASLR_LOG_LEVEL */ - if (kKaslrLogLevel >= 1) { - /* Minimal logging: just the seed and slot (release mode) */ - kprintf("[boot][kaslr] policy_id=%u source=%s slot=%llu\n", - g_kaslr_layout.policy_id, - random_seed_source_name(rsrc), - slot); - } - - if (kKaslrLogLevel >= 2) { - /* Debug logging: full ranges and hashes */ - kprintf("[boot][kaslr] seed=0x%llx seed_hash=0x%llx\n", - seed, seed_hash); - kprintf("[boot][kaslr] direct-map=[0x%llx,0x%llx)\n", - direct_map.start, direct_map.end); - kprintf("[boot][kaslr] vmemmap=[0x%llx,0x%llx)\n", - vmemmap.start, vmemmap.end); - kprintf("[boot][kaslr] kernel-image=[0x%llx,0x%llx)\n", - kernel_img.start, kernel_img.end); - } - -#else /* CONFIG_KASLR disabled */ - g_kaslr_layout.seed = 0; - g_kaslr_layout.seed_hash = 0; - g_kaslr_layout.source = KaslrSeedSource::kCycle; - g_kaslr_layout.slot = 0; - g_kaslr_layout.slide_bytes = 0; - g_kaslr_layout.policy_id = kKaslrPolicyId; - g_kaslr_layout.direct_map_base = kDirectMapBaseDefault; - g_kaslr_layout.vmemmap_base = kVmemmapBaseDefault; - g_kaslr_layout.canonical_ok = true; - g_kaslr_layout.overlap_ok = true; - - g_direct_map_base = kDirectMapBaseDefault; - g_vmemmap_base = kVmemmapBaseDefault; - - if (kKaslrLogLevel >= 1) { - kprintf("[boot][kaslr] disabled policy_id=%u\n", - g_kaslr_layout.policy_id); - } -#endif -} - -/* ================================================================ - * Public entry point - * ================================================================ */ -void kaslr_init() -{ - kaslr_layout_compute(); -} - -} // namespace arch diff --git a/na/arch/riscv/defconfig b/na/arch/riscv/defconfig deleted file mode 100644 index 87d3ffd..0000000 --- a/na/arch/riscv/defconfig +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# LumeOS Default Kernel Configuration — RISC-V (virt) -# -# This is the "golden" default configuration for the RISC-V virt platform. -# To customise, copy this file to the source root as '.config': -# -# cp arch/riscv/defconfig .config -# $EDITOR .config -# make -# -# Any variable not set here inherits its default from scripts/Kconfig.mk. -# ====================================================================== - -# --- Platform ----------------------------------------------------------- -CONFIG_ARCH = riscv - -# --- CPU / SMP ---------------------------------------------------------- -CONFIG_NR_CPUS = 8 - -# --- Scheduler ---------------------------------------------------------- -CONFIG_HZ = 100 -CONFIG_NR_PRIORITIES = 32 - -# --- Memory Management -------------------------------------------------- -CONFIG_PAGE_SIZE = 4096 -CONFIG_MAX_ORDER = 11 -CONFIG_KERNEL_STACK_SIZE = 8192 - -# --- Security / Hardening ----------------------------------------------- -CONFIG_KASLR = y -CONFIG_STACK_CANARY = y - -# --- Debug --------------------------------------------------------------- -CONFIG_DEBUG = y -CONFIG_DEBUG_LOCKS = y -CONFIG_SELFTEST = y -CONFIG_KLOG = y diff --git a/na/arch/riscv/include/arch/config.h b/na/arch/riscv/include/arch/config.h deleted file mode 100644 index 8ae0502..0000000 --- a/na/arch/riscv/include/arch/config.h +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * RISC-V Architecture Configuration - * - * These constants define the architecture-specific memory layout. - * Each arch/ implementation must provide this header with the same symbols. - * - * KASLR: At boot, kaslr_init() randomises g_direct_map_base and - * g_vmemmap_base. All MI code must read these runtime variables - * (via pa_to_va / vmm helpers) rather than the static defaults. - */ - -#include - -namespace arch { - -/* ================================================================ - * Default (compile-time) virtual addresses - * - * SV39 gives a 512GB virtual address space. - * Higher-half layout (defaults before KASLR): - * - * 0xFFFFFFC0_00000000 (256) Direct map (PA+offset) - * 0xFFFFFFD0_00000000 (320) Vmemmap (Page array) - * - * The 64GB gap between them is the KASLR slide range. - * ================================================================ */ -inline constexpr uint64 kDirectMapBaseDefault = 0xFFFFFFC000000000ULL; -inline constexpr uint64 kVmemmapBaseDefault = 0xFFFFFFD000000000ULL; - -/* Physical memory base (where RAM starts on RISC-V virt platform) */ -inline constexpr uint64 kPhysBase = 0x80000000ULL; - -/* Kernel physical load address (= PHYS_BASE in linker script). - * Everything in [kPhysBase, kPhysStart) is firmware territory (PMP-protected). - * Analogous to Linux's CONFIG_PHYSICAL_START. */ -inline constexpr uint64 kPhysStart = 0x80200000ULL; - -/* ================================================================ - * Runtime (post-KASLR) virtual addresses - * - * These start equal to the defaults above. If CONFIG_KASLR is - * enabled, kaslr_init() adds a random, 1GB-aligned offset - * to each, sliding both regions upward in VA space. - * ================================================================ */ -extern uint64 g_direct_map_base; -extern uint64 g_vmemmap_base; - -/* ================================================================ - * KASLR Layout Structure (Phase A: Unified KASLR State) - * - * This struct holds all KASLR results: the random seed, derived - * hash, selected slot, and the randomised virtual address bases. - * It serves as the single source of truth for all KASLR decisions - * and is filled once during boot and read-only thereafter. - * ================================================================ */ - -enum class KaslrSeedSource : uint8 { - kFdt = 0, /* Loaded from FDT /random node */ - kHardware = 1, /* From hardware RNG (e.g., RDRAND) */ - kCycle = 2, /* Fallback: CPU cycle counter */ -}; - -struct KaslrLayout { - /* Seed and provenance */ - uint64 seed; /* 64-bit random seed value */ - uint64 seed_hash; /* Non-plaintext derived hash (splitmix64) */ - KaslrSeedSource source; /* Seed provenance (fdt/hw/cycle) */ - - /* Randomisation parameters */ - uint64 slot; /* Slot index: [0, 63] */ - uint64 slide_bytes; /* Byte offset: slot * 1GB */ - - /* Build-time policy */ - uint32 policy_id; /* CONFIG_KASLR_POLICY_ID at build time */ - - /* Randomised base addresses (post-KASLR) */ - uint64 direct_map_base; /* Direct map VA (kDirectMapBaseDefault + slide) */ - uint64 vmemmap_base; /* Vmemmap VA (kVmemmapBaseDefault + slide) */ - - /* Validation flags (for debug logging) */ - bool canonical_ok; /* All VA ranges passed canonical check */ - bool overlap_ok; /* No overlaps detected between regions */ -}; - -/* ================================================================ - * KASLR entry point - * - * Must be called AFTER random_early_init() and BEFORE vmm_init(). - * Fills g_kaslr_layout with randomised bases and self-check results. - * When CONFIG_KASLR is disabled this is a no-op. - * ================================================================ */ -extern KaslrLayout g_kaslr_layout; - -void kaslr_init(); - -/* Helper to query the KASLR layout safely */ -static inline const KaslrLayout& kaslr_layout_get() { - return g_kaslr_layout; -} - -} // namespace arch diff --git a/na/arch/riscv/include/arch/context.h b/na/arch/riscv/include/arch/context.h deleted file mode 100644 index 3d935fa..0000000 --- a/na/arch/riscv/include/arch/context.h +++ /dev/null @@ -1,33 +0,0 @@ -/* 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 deleted file mode 100644 index 8c573ec..0000000 --- a/na/arch/riscv/include/arch/cpu.h +++ /dev/null @@ -1,95 +0,0 @@ -/* 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) */ - -/* RISC-V sie register bit definitions */ -#define SIE_SSIE (1UL << 1) /* Supervisor Software Interrupt Enable */ -#define SIE_STIE (1UL << 5) /* Supervisor Timer Interrupt Enable */ -#define SIE_SEIE (1UL << 9) /* Supervisor External Interrupt Enable */ - -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; -} - -// Enable timer interrupt in sie (sie.STIE = 1) -static inline void timer_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_STIE)); -} - -// Disable timer interrupt in sie (sie.STIE = 0) -static inline void timer_intr_off() { - __asm__ volatile("csrc sie, %0" :: "r"(SIE_STIE)); -} - -// Enable software interrupt in sie (sie.SSIE = 1) — needed for IPI -static inline void software_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_SSIE)); -} - -// Enable external interrupt in sie (sie.SEIE = 1) — needed for PLIC (UART, etc.) -static inline void external_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_SEIE)); -} - -// Clear S-mode software interrupt pending bit (sip.SSIP = 0) -// Must be called in the IPI handler to acknowledge the interrupt. -static inline void clear_sip_ssip() { - __asm__ volatile("csrc sip, %0" :: "r"(1UL << 1)); -} - -} // namespace arch::cpu diff --git a/na/arch/riscv/include/arch/extable.h b/na/arch/riscv/include/arch/extable.h deleted file mode 100644 index eb2daac..0000000 --- a/na/arch/riscv/include/arch/extable.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#ifndef __ASSEMBLER__ - -#include - -/* - * Exception table entry. - * insn: offset from this struct to the faulting instruction. - * fixup: offset from this struct to the fixup code. - * - * Using relative offsets avoids absolute relocations and saves space. - */ -struct ExceptionTableEntry { - int32 insn; - int32 fixup; -}; - -/* Search the exception table for a given PC. Returns fixup address, or 0 if not found. */ -uint64 search_extable(uint64 pc); - -/* Sorts the exception table at boot for binary search O(log N). */ -void extable_init(); - -#endif /* __ASSEMBLER__ */ - -/* - * EXTABLE macro for assembly usage. - * Appends an entry to the .extable section. - */ -#ifdef __ASSEMBLER__ - -#define EXTABLE(insn, fixup) \ - .pushsection .extable, "a"; \ - .balign 4; \ - .long insn - .; \ - .long fixup - .; \ - .popsection - -#else /* !__ASSEMBLER__ */ - -/* Stringified macro for inline assembly */ -#define _ASM_EXTABLE(insn, fixup) \ - ".pushsection .extable, \"a\"\n" \ - ".balign 4\n" \ - ".long " #insn " - .\n" \ - ".long " #fixup " - .\n" \ - ".popsection\n" - -#define EXTABLE(insn, fixup) _ASM_EXTABLE(insn, fixup) - -#endif /* __ASSEMBLER__ */ diff --git a/na/arch/riscv/include/arch/fpstate.h b/na/arch/riscv/include/arch/fpstate.h deleted file mode 100644 index 4b42a22..0000000 --- a/na/arch/riscv/include/arch/fpstate.h +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FPU State — Floating-Point Register Context - * - * Pre-reserved structure for RISC-V F/D extension floating-point state. - * MUST be allocated in every TaskControlBlock so that future FPU - * save/restore logic has a stable destination without restructuring TCB. - * - * RISC-V F/D extension state: - * - 32 FP registers (f0-f31), each 64 bits for D extension - * - fcsr (Floating-point Control and Status Register) - * - * sstatus.FS field management: - * - FS=0 (Off): FPU disabled, any FP instruction traps - * - FS=1 (Initial): FPU enabled, registers are initial state - * - FS=2 (Clean): FPU enabled, registers not modified since last save - * - FS=3 (Dirty): FPU enabled, registers modified (need save) - * - * Current status: Structure defined, save/restore NOT implemented. - * When implemented, trap_dispatch and swtch.S will use lazy - * save/restore based on sstatus.FS dirty tracking. - * - * Reference: RISC-V ISA Manual §3.1.6.5 (FS field) - */ - -#include - -namespace arch { - -struct FpState { - uint64 f[32]; // f0-f31 (64-bit each for D extension) - uint32 fcsr; // Floating-point CSR (frm + fflags) - uint32 _pad; // Alignment padding -}; - -static_assert(sizeof(FpState) == 32 * 8 + 8, "FpState must be 264 bytes"); - -/* - * sstatus.FS field constants - */ -inline constexpr uint64 kSstatusFS_Off = 0ULL << 13; -inline constexpr uint64 kSstatusFS_Initial = 1ULL << 13; -inline constexpr uint64 kSstatusFS_Clean = 2ULL << 13; -inline constexpr uint64 kSstatusFS_Dirty = 3ULL << 13; -inline constexpr uint64 kSstatusFS_Mask = 3ULL << 13; - -/* - * Check if sstatus indicates FPU state is dirty (needs save). - */ -inline bool fpu_state_dirty(uint64 sstatus) { - return (sstatus & kSstatusFS_Mask) == kSstatusFS_Dirty; -} - -/* - * Check if FPU is enabled (FS != Off). - */ -inline bool fpu_enabled(uint64 sstatus) { - return (sstatus & kSstatusFS_Mask) != kSstatusFS_Off; -} - -} // namespace arch - -/* Assembly routines for FPU register save/restore (fpu.S) */ -extern "C" void fpu_save(arch::FpState* state); -extern "C" void fpu_restore(arch::FpState* state); - -namespace arch { - -/* - * Read sstatus.FS field (current FPU state) - */ -inline uint64 fpu_get_fs() { - uint64 sstatus; - __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); - return sstatus & kSstatusFS_Mask; -} - -/* - * Set sstatus.FS field. - * @fs: one of kSstatusFS_Off, kSstatusFS_Initial, kSstatusFS_Clean, kSstatusFS_Dirty - */ -inline void fpu_set_fs(uint64 fs) { - __asm__ volatile( - "csrc sstatus, %0\n" - "csrs sstatus, %1\n" - :: "r"(kSstatusFS_Mask), "r"(fs) - ); -} - -} // namespace arch diff --git a/na/arch/riscv/include/arch/mmu.h b/na/arch/riscv/include/arch/mmu.h deleted file mode 100644 index f4c9b26..0000000 --- a/na/arch/riscv/include/arch/mmu.h +++ /dev/null @@ -1,78 +0,0 @@ -/* 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; -} - -// Read the raw satp CSR value (mode + PPN). -// Used when the caller needs the exact register value (e.g. for TrapFrame metadata). -static inline uint64 read_satp() { - uint64 val; - __asm__ volatile("csrr %0, satp" : "=r"(val)); - return val; -} - -// 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"); -} - -// Flush the instruction cache. -// Required after loading executable code into memory (e.g. ELF loading). -// sfence.vma only flushes the TLB; fence.i is needed for the I-cache. -static inline void flush_icache() { - __asm__ volatile("fence.i" ::: "memory"); -} - -} // namespace arch::mmu diff --git a/na/arch/riscv/include/arch/pmap.h b/na/arch/riscv/include/arch/pmap.h deleted file mode 100644 index 0b43b9e..0000000 --- a/na/arch/riscv/include/arch/pmap.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * pmap — SV39 page table interface (MD layer) - * - * Namespace functions, zero runtime overhead. - * Implementation: arch/riscv/mm/pmap.cc - * - * Reference: docs/specs/vmm.md §3.2 - */ - -#include - -namespace pmap { - -/* PTE permission bits */ -inline constexpr uint64 PTE_V = 1ULL << 0; -inline constexpr uint64 PTE_R = 1ULL << 1; -inline constexpr uint64 PTE_W = 1ULL << 2; -inline constexpr uint64 PTE_X = 1ULL << 3; -inline constexpr uint64 PTE_U = 1ULL << 4; -inline constexpr uint64 PTE_G = 1ULL << 5; -inline constexpr uint64 PTE_A = 1ULL << 6; -inline constexpr uint64 PTE_D = 1ULL << 7; -inline constexpr uint64 PTE_COW = 1ULL << 8; /* Custom RSW bit for Copy-On-Write */ - -uint64 create(); -void destroy(uint64 root_pa); -int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); -int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); -void unmap(uint64 root_pa, uint64 va); -int protect(uint64 root_pa, uint64 va, uint64 perm); -int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm); -bool lookup(uint64 root_pa, uint64 va, uint64* pa_out); -uint64 lookup_pte(uint64 root_pa, uint64 va); -bool is_cow(uint64 root_pa, uint64 va); -int make_cow(uint64 root_pa, uint64 va); -void activate(uint64 root_pa); - -/* - * Translate MI-layer VM_* permission flags to hardware PTE bits. - * Called by vma.cc when faulting in a page. - */ -uint64 vm_perm_to_pte(uint64 vm_perm); - -} // namespace pmap diff --git a/na/arch/riscv/include/arch/random.h b/na/arch/riscv/include/arch/random.h deleted file mode 100644 index 8c8c051..0000000 --- a/na/arch/riscv/include/arch/random.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include - -namespace arch { - -/* - * Get a random 64-bit value from the hardware TRNG. - * Returns true if supported and successful, false otherwise. - */ -bool get_hw_random(uint64& value); - -/* - * Get the current high-resolution hardware cycle counter. - * Used as an entropy fallback (Jitter/Timer). - */ -inline uint64 get_cycles() -{ - uint64 cycles; - __asm__ volatile("rdcycle %0" : "=r"(cycles)); - return cycles; -} - -} // namespace arch diff --git a/na/arch/riscv/include/arch/sbi.h b/na/arch/riscv/include/arch/sbi.h deleted file mode 100644 index 80a6d7c..0000000 --- a/na/arch/riscv/include/arch/sbi.h +++ /dev/null @@ -1,111 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * sbi.h — RISC-V Supervisor Binary Interface (SBI) wrappers - * - * Provides inline ecall wrappers for SBI calls from S-mode. - * Uses the SBI v1.0+ extension-based calling convention. - * - * Reference: RISC-V SBI Specification v2.0 - */ - -#include - -namespace arch::sbi { - -struct sbiret { - long error; - long value; -}; - -/* SBI error codes */ -inline constexpr long SBI_SUCCESS = 0; -inline constexpr long SBI_ERR_NOT_SUPPORTED = -2; - -/* Extension IDs */ -inline constexpr int SBI_EXT_TIME = 0x54494D45; /* "TIME" */ -inline constexpr int SBI_EXT_IPI = 0x735049; /* "sPI" */ -inline constexpr int SBI_EXT_RFENCE = 0x52464E43; /* "RFNC" */ -inline constexpr int SBI_EXT_HSM = 0x48534D; /* "HSM" */ - -static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0 = 0, - uint64 arg1 = 0, uint64 arg2 = 0, - uint64 arg3 = 0, uint64 arg4 = 0, - uint64 arg5 = 0) { - sbiret ret; - register uint64 a0 asm("a0") = arg0; - register uint64 a1 asm("a1") = arg1; - register uint64 a2 asm("a2") = arg2; - register uint64 a3 asm("a3") = arg3; - register uint64 a4 asm("a4") = arg4; - register uint64 a5 asm("a5") = arg5; - register uint64 a6 asm("a6") = fid; - register uint64 a7 asm("a7") = ext; - __asm__ volatile("ecall" - : "+r"(a0), "+r"(a1) - : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) - : "memory"); - ret.error = static_cast(a0); - ret.value = static_cast(a1); - return ret; -} - -/* ---- Timer Extension (EID 0x54494D45) ---- */ - -static inline void set_timer(uint64 stime_value) { - sbi_ecall(SBI_EXT_TIME, 0, stime_value); -} - -/* ---- IPI Extension (EID 0x735049) ---- */ - -/* - * sbi_send_ipi — Send S-mode software interrupt to target harts - * - * @hart_mask: bitmask of target harts (relative to hart_mask_base) - * @hart_mask_base: base hart ID for the mask (0 = harts 0-63) - */ -static inline sbiret sbi_send_ipi(uint64 hart_mask, - uint64 hart_mask_base = 0) { - return sbi_ecall(SBI_EXT_IPI, 0, hart_mask, hart_mask_base); -} - -/* ---- Remote Fence Extension (EID 0x52464E43) ---- */ - -/* - * sbi_remote_sfence_vma — Execute SFENCE.VMA on target harts - * - * @hart_mask/base: target harts - * @start_addr: start of VA range to invalidate - * @size: size of range (use ~0UL for full flush) - */ -static inline sbiret sbi_remote_sfence_vma(uint64 hart_mask, - uint64 hart_mask_base, - uint64 start_addr, - uint64 size) { - return sbi_ecall(SBI_EXT_RFENCE, 1, - hart_mask, hart_mask_base, - start_addr, size); -} - -/* ---- HSM Extension (EID 0x48534D) ---- */ - -/* - * sbi_hart_start — Start (wake) a hart that is in STOPPED state - * - * @hartid: target hart ID to start - * @start_addr: PHYSICAL address where the hart should begin execution - * @opaque: passed in a1 register to the hart (e.g. FDT pointer) - * - * OpenSBI v1.0+ parks non-boot harts; they must be explicitly started. - */ -static inline sbiret sbi_hart_start(uint64 hartid, - uint64 start_addr, - uint64 opaque) { - return sbi_ecall(SBI_EXT_HSM, 0, hartid, start_addr, opaque); -} - -} // namespace arch::sbi - diff --git a/na/arch/riscv/include/arch/tlb.h b/na/arch/riscv/include/arch/tlb.h deleted file mode 100644 index 46c6b97..0000000 --- a/na/arch/riscv/include/arch/tlb.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include - -namespace arch::tlb { - -/* - * Perform a cross-CPU TLB flush (shootdown). - * - * @param cpu_mask: Bitmask of CPUs to target. - * @param start: Start virtual address, or 0 for full address space. - * @param size: Size in bytes, or ~0ULL for full address space. - */ -void flush_remote(uint64 cpu_mask, uint64 start, uint64 size); - -} // namespace arch::tlb diff --git a/na/arch/riscv/include/arch/trap.h b/na/arch/riscv/include/arch/trap.h deleted file mode 100644 index 8e55250..0000000 --- a/na/arch/riscv/include/arch/trap.h +++ /dev/null @@ -1,94 +0,0 @@ -/* 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; -} - -// Enable Supervisor User Memory access (set SUM bit in sstatus). -// When SUM=1, S-mode can read/write pages marked PTE_U. -// This is needed for syscall handlers to access user-space buffers. -static inline void enable_sum() { - __asm__ volatile("csrs sstatus, %0" :: "r"(1UL << 18)); -} - -// Disable Supervisor User Memory access (clear SUM bit in sstatus). -static inline void disable_sum() { - __asm__ volatile("csrc sstatus, %0" :: "r"(1UL << 18)); -} - -} // namespace arch::trap diff --git a/na/arch/riscv/include/arch/trapframe.h b/na/arch/riscv/include/arch/trapframe.h deleted file mode 100644 index b198646..0000000 --- a/na/arch/riscv/include/arch/trapframe.h +++ /dev/null @@ -1,140 +0,0 @@ -/* 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 -#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 - -/* Forward declaration — defined in trap_md.cc. - * Must be after TrapFrame definition. */ -extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); - -namespace arch { - -/* - * trapframe_init_user — prepare a TrapFrame for returning to user mode. - * - * Sets arch-specific sstatus: - * SPP = 0 (return to U-mode on sret) - * SPIE = 1 (enable interrupts in U-mode) - * FS = Initial (allow FP instructions) - * - * Called from MI exec.cc instead of setting sstatus directly. - */ -static inline void trapframe_init_user(TrapFrame* tf, uint64 entry, uint64 sp) -{ - /* Clear all registers */ - for (auto& t : tf->t) { t = 0; } - for (auto& s : tf->s) { s = 0; } - for (auto& a : tf->a) { a = 0; } - tf->ra = 0; - tf->gp = 0; - tf->tp = 0; - - tf->epc = entry; - tf->sp = sp; - - /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts), FS=Initial (FP ok) */ - constexpr uint64 kSPIE = (1ULL << 5); - constexpr uint64 kFS_Initial = (1ULL << 13); - tf->sstatus = kSPIE | kFS_Initial; - - /* Kernel metadata used by trap_vector.S on every trap from U-mode. - * These are RISC-V specific — trap_vector reads them from the - * TrapFrame to switch to kernel context. */ - uint64 satp; - asm volatile("csrr %0, satp" : "=r"(satp)); - tf->kernel_satp = satp; - tf->kernel_sp = reinterpret_cast(tf); - tf->kernel_trap = reinterpret_cast(&trap_dispatch); - tf->kernel_cpu_id = cpu::id(); -} - -} // namespace arch - -#endif /* __ASSEMBLER__ */ diff --git a/na/arch/riscv/include/asm/csr.h b/na/arch/riscv/include/asm/csr.h deleted file mode 100644 index 42f1eb4..0000000 --- a/na/arch/riscv/include/asm/csr.h +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_CSR_H -#define _ASM_RISCV_CSR_H - -/* - * RISC-V CSR (Control & Status Register) definitions used by the early - * boot / MMU-switch path. - * - * LumeOS-native subset of Linux arch/riscv/include/asm/csr.h. Only the - * symbols head.S and mm/init.cc actually reference are defined — adding - * the rest is straightforward when new code needs them, and skipping the - * Linux ALTERNATIVE / vendor-extension machinery keeps the cascade flat. - */ - -/* SATP (Supervisor Address Translation and Protection) layout — RV64 */ -#define SATP_PPN_MASK 0x00000FFFFFFFFFFFUL /* bits [43:0] = root PPN */ -#define SATP_ASID_SHIFT 44 -#define SATP_MODE_SHIFT 60 - -/* SATP MODE encodings (RV64) */ -#define SATP_MODE_BARE (0UL << SATP_MODE_SHIFT) -#define SATP_MODE_SV39 (8UL << SATP_MODE_SHIFT) -#define SATP_MODE_SV48 (9UL << SATP_MODE_SHIFT) - -/* Compose a SATP value from a page-table physical address. - * Result: MODE | (root_ppn) where root_ppn = pgd_pa >> PAGE_SHIFT. */ -#define MAKE_SATP(mode, pgd_pa) ((mode) | (((pgd_pa) >> 12) & SATP_PPN_MASK)) - -/* SSTATUS bits we touch at boot — disable FP / vector at entry so that an - * accidental FP / V instruction in early code traps cleanly rather than - * silently using dirty state from before reset. */ -#define SR_FS_OFF (0UL << 13) -#define SR_VS_OFF (0UL << 9) - -#endif /* _ASM_RISCV_CSR_H */ diff --git a/na/arch/riscv/include/asm/page.h b/na/arch/riscv/include/asm/page.h deleted file mode 100644 index 7b9f155..0000000 --- a/na/arch/riscv/include/asm/page.h +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_PAGE_H -#define _ASM_RISCV_PAGE_H - -/* - * RISC-V page / virtual-memory layout constants. - * - * Mirrors the subset of Linux arch/riscv/include/asm/page.h that LumeOS's - * early boot path needs: - * - PAGE_SIZE / PAGE_SHIFT / PAGE_MASK - * - PAGE_OFFSET (start of the higher-half direct map) - * - KERNEL_LINK_ADDR (link-time VA of _start) - * - SV39 PTE flag bits - * - PTRS_PER_PGD (512 for SV39) - * - * Values match Linux RISC-V SV39 defaults so that struct kernel_mapping - * (filled by setup_vm in arch/riscv/mm/init.cc) carries the same numeric - * relations Linux's mm/init.c maintains. - */ - -#define PAGE_SHIFT 12 -#define PAGE_SIZE (1UL << PAGE_SHIFT) -#define PAGE_MASK (~(PAGE_SIZE - 1)) - -/* Higher-half base of the kernel direct map. Bottom of the SV39 supervisor - * address space minus the unmapped guard region. Identical to Linux's - * CONFIG_PAGE_OFFSET default on RV64. */ -#define PAGE_OFFSET 0xFFFFFFC000000000UL - -/* Where _start was linked. OpenSBI hands control to PHYS_BASE; the kernel - * runs at KERNEL_LINK_ADDR after relocate_enable_mmu jumps to higher-half. */ -#define PHYS_BASE 0x80200000UL -#define KERNEL_LINK_ADDR 0xFFFFFFC080200000UL - -/* SATP mode field — SV39 = 8, shifted to bits [63:60]. */ -#define SATP_MODE_SV39 (8UL << 60) - -#define PTRS_PER_PGD 512 - -/* 1 GB superpage size — entry.S used to build the early page table with - * these; setup_vm in arch/riscv/mm/init.cc now does the same. */ -#define PGDIR_SHIFT 30 -#define PGDIR_SIZE (1UL << PGDIR_SHIFT) -#define PGDIR_MASK (~(PGDIR_SIZE - 1)) - -/* SV39 PTE flag bits (one byte). */ -#define _PAGE_PRESENT (1UL << 0) /* aka VALID — historic Linux name */ -#define _PAGE_READ (1UL << 1) -#define _PAGE_WRITE (1UL << 2) -#define _PAGE_EXEC (1UL << 3) -#define _PAGE_USER (1UL << 4) -#define _PAGE_GLOBAL (1UL << 5) -#define _PAGE_ACCESSED (1UL << 6) -#define _PAGE_DIRTY (1UL << 7) - -#define PAGE_KERNEL_EXEC \ - (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | \ - _PAGE_ACCESSED | _PAGE_DIRTY) - -#endif /* _ASM_RISCV_PAGE_H */ diff --git a/na/arch/riscv/include/asm/pgtable.h b/na/arch/riscv/include/asm/pgtable.h deleted file mode 100644 index 28fc4c5..0000000 --- a/na/arch/riscv/include/asm/pgtable.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_PGTABLE_H -#define _ASM_RISCV_PGTABLE_H - -/* - * Early page-table construction API used by arch/riscv/mm/init.cc::setup_vm. - * - * Mirrors the create_pgd_mapping interface from Linux's - * arch/riscv/mm/init.c — same name, same semantics: - * - * create_pgd_mapping(pgdir, va, pa, sz, prot) - * - * pgdir : root of the page table to populate - * va : virtual address to map - * pa : physical address to map to - * sz : mapping size — must be a supported leaf size (PAGE_SIZE, - * 2 MiB megapage, or 1 GiB gigapage) - * prot : PTE flag bits (PAGE_KERNEL_EXEC etc., from ) - * - * The boot-time variant in this file only supports gigapage (1 GiB) - * mappings — that is all setup_vm needs for the early_pg_dir / trampoline_pg_dir - * built before the slab allocator exists. Finer-grained mappings come - * later via vmm_init(). - */ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { unsigned long pgd; } pgd_t; - -/* Linker-provided early page-table storage (defined in lumeos.lds.S - * ARCH_BSS_EXTRA, lives in .bss so it's zero-initialized). Allocating - * them in the linker script — not in C — mirrors Linux and keeps the - * symbols visible from head.S without a forward C decl mismatch. */ -extern pgd_t early_pg_dir[PTRS_PER_PGD]; -extern pgd_t trampoline_pg_dir[PTRS_PER_PGD]; -extern pgd_t fixmap_pg_dir[PTRS_PER_PGD]; - -/* Install a single PGD entry mapping `va` → `pa` with size `sz` and PTE - * flags `prot`. Only sz == PGDIR_SIZE (1 GiB) is supported in this early - * variant — vmm_init() takes over with create_pgd_mapping_pmd later. */ -void create_pgd_mapping(pgd_t *pgdir, unsigned long va, unsigned long pa, - unsigned long sz, unsigned long prot); - -#ifdef __cplusplus -} -#endif - -#endif /* _ASM_RISCV_PGTABLE_H */ diff --git a/na/arch/riscv/include/asm/sections.h b/na/arch/riscv/include/asm/sections.h deleted file mode 100644 index 454d9de..0000000 --- a/na/arch/riscv/include/asm/sections.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_SECTIONS_H -#define _ASM_RISCV_SECTIONS_H - -/* - * Linker-emitted symbols that bracket each section of the kernel image. - * - * The lumeos.lds.S linker script defines these as the start / end - * addresses of contiguous regions. Code that walks a section - * (relocate_kernel walks .rela.dyn, the C++ ctor loop walks - * .init_array, the VMM mapping code walks .text vs .rodata, ...) - * imports the pair via this header. - * - * Mirrors Linux arch/riscv/include/asm/sections.h / asm-generic/sections.h. - * Each name and pair is identical to Linux so that direct ports of mm/init.c - * relocate_kernel logic compile unchanged against LumeOS. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -extern char _start[]; -extern char _end[]; - -extern char _stext[]; -extern char _etext[]; -extern char _srodata[]; -extern char _erodata[]; -extern char _sdata[]; -extern char _edata[]; -extern char _sbss[]; -extern char _ebss[]; - -extern char __init_array_start[]; -extern char __init_array_end[]; - -/* .rela.dyn — array of Elf64_Rela entries emitted by the linker under - * `-shared -Bsymbolic`. relocate_kernel walks this range and patches - * each R_RISCV_RELATIVE slot. */ -extern char __rela_dyn_start[]; -extern char __rela_dyn_end[]; - -#ifdef __cplusplus -} -#endif - -#endif /* _ASM_RISCV_SECTIONS_H */ diff --git a/na/arch/riscv/kernel/asm-offsets.c b/na/arch/riscv/kernel/asm-offsets.c deleted file mode 100644 index edecab7..0000000 --- a/na/arch/riscv/kernel/asm-offsets.c +++ /dev/null @@ -1,29 +0,0 @@ -// 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 -#include - -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/na/arch/riscv/kernel/head.S b/na/arch/riscv/kernel/head.S deleted file mode 100644 index e04c44c..0000000 --- a/na/arch/riscv/kernel/head.S +++ /dev/null @@ -1,317 +0,0 @@ -/* 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 - -#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\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/na/arch/riscv/kernel/lumeos.lds.S b/na/arch/riscv/kernel/lumeos.lds.S deleted file mode 100644 index 9bd083b..0000000 --- a/na/arch/riscv/kernel/lumeos.lds.S +++ /dev/null @@ -1,120 +0,0 @@ -/* 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 - -/* ===== 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/na/arch/riscv/kernel/signal.cc b/na/arch/riscv/kernel/signal.cc deleted file mode 100644 index 39fe34e..0000000 --- a/na/arch/riscv/kernel/signal.cc +++ /dev/null @@ -1,163 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* 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(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]; -} diff --git a/na/arch/riscv/lib/random.cc b/na/arch/riscv/lib/random.cc deleted file mode 100644 index afc0078..0000000 --- a/na/arch/riscv/lib/random.cc +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include - -namespace arch { - -bool get_hw_random(uint64& value) -{ - /* - * Real implementation would check for Zkr extension and execute: - * csrr x, seed - * - * Since QEMU virt doesn't guarantee Zkr, we return false to - * force the MI layer to use the timer fallback. - */ - return false; -} - -} // namespace arch diff --git a/na/arch/riscv/mm/init.cc b/na/arch/riscv/mm/init.cc deleted file mode 100644 index 3cebcd9..0000000 --- a/na/arch/riscv/mm/init.cc +++ /dev/null @@ -1,241 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V early memory-management bootstrap. - * - * Mirrors Linux arch/riscv/mm/init.c — same file name, same C function - * names (setup_vm / relocate_enable_mmu / relocate_kernel), same global - * state (kernel_map, early_pg_dir, trampoline_pg_dir). head.S calls - * setup_vm immediately after BSS-clear, then later calls - * relocate_enable_mmu, which switches SATP, jumps to higher-half VA, - * and finally calls relocate_kernel to apply .rela.dyn. - * - * Differences from Linux: - * - KASLR offset is hard-wired to 0 for now (no pi/ subsystem) — the - * code path that would consume kernel_map.virt_offset is in place - * so adding pi/-based seed reading later only requires populating - * that one field. - * - Only 1 GiB superpages supported in create_pgd_mapping; sufficient - * for the early page tables. Finer-grained mappings come from - * vmm_init() once slab is up. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Global kernel mapping descriptor — single source of truth for the - * runtime-vs-link address relationship. Lives in .data so its initial - * zero contents survive the BSS-clear that runs before setup_vm fills - * the fields. Read by VMM, page-fault handler, ptdump, etc. - * - * Linux equivalent: arch/riscv/mm/init.c::kernel_map - */ -struct kernel_mapping kernel_map __attribute__((section(".data"))); - -/* - * create_pgd_mapping — install one PGD entry covering a 1 GiB region. - * - * Only 1 GiB superpages are supported in this early variant; setup_vm - * needs nothing finer. Identical interface to Linux's same-named - * function in arch/riscv/mm/init.c, restricted in arity. - */ -extern "C" void create_pgd_mapping(pgd_t *pgdir, unsigned long va, - unsigned long pa, unsigned long sz, - unsigned long prot) -{ - /* This early variant only supports 1 GiB superpages. */ - if (sz != PGDIR_SIZE) { - return; - } - - /* SV39 PGD index = (va >> 30) & 0x1FF — see Volume II, §4.4.1. - * Each PGD entry is 8 bytes; pgdir[i] is the slot for that VPN[2]. */ - unsigned long idx = (va >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1); - - /* PTE PPN field: physical page number shifted up by 10 (SV39 PTE - * layout: bits [53:10] = PPN, bits [9:0] = flags). */ - unsigned long ppn = pa >> PAGE_SHIFT; - pgdir[idx].pgd = (ppn << 10) | prot; -} - -/* - * setup_vm — fill kernel_map and build early_pg_dir + trampoline_pg_dir. - * - * Called from head.S after BSS is cleared, with: - * load_pa = runtime physical address of _start - * dtb_pa = physical address of the device tree blob (from a1) - * - * Mirrors Linux arch/riscv/mm/init.c::setup_vm. The actual SATP write - * happens later in relocate_enable_mmu; here we only populate page - * tables and the kernel_map descriptor. - */ -extern "C" void __init setup_vm(uintptr_t load_pa, uintptr_t dtb_pa) -{ - (void)dtb_pa; /* KASLR pi/ subsystem will consume this later. */ - - /* --- 1. Fill kernel_map ----------------------------------------- */ - - /* KASLR offset = 0 in this configuration. */ - kernel_map.virt_offset = 0; - kernel_map.virt_addr = KERNEL_LINK_ADDR + kernel_map.virt_offset; - kernel_map.phys_addr = load_pa; - kernel_map.size = (uintptr_t)_end - (uintptr_t)_start; - kernel_map.page_offset = PAGE_OFFSET; - - /* Mirrors Linux: va_pa_offset is the constant added to a physical - * address to obtain its direct-map VA. */ - kernel_map.va_pa_offset = PAGE_OFFSET - load_pa; - - /* va_kernel_pa_offset is the constant added to a load PA to obtain - * the kernel's link VA (used by relocate_enable_mmu to fix sp/ra). */ - kernel_map.va_kernel_pa_offset = kernel_map.virt_addr - load_pa; - - /* --- 2. Build early_pg_dir ------------------------------------- * - * - * SV39 root page table — 4 entries cover identity + higher-half for - * both MMIO (PA 0x0000_0000, 1 GiB) and RAM (PA 0x8000_0000, 1 GiB): - * - * [0] identity PA 0x0000_0000 → VA 0x0000_0000 - * [2] identity PA 0x8000_0000 → VA 0x8000_0000 - * [256] high-half PA 0x0000_0000 → VA 0xFFFFFFC000000000 - * [258] high-half PA 0x8000_0000 → VA 0xFFFFFFC080000000 - * - * Identity mappings keep PC valid right after csrw satp. High-half - * mappings let the post-MMU `jr` to .Lhigher_half resolve. */ - create_pgd_mapping(early_pg_dir, 0x00000000UL, 0x00000000UL, - PGDIR_SIZE, PAGE_KERNEL_EXEC); - create_pgd_mapping(early_pg_dir, 0x80000000UL, 0x80000000UL, - PGDIR_SIZE, PAGE_KERNEL_EXEC); - create_pgd_mapping(early_pg_dir, PAGE_OFFSET + 0x00000000UL, - 0x00000000UL, PGDIR_SIZE, PAGE_KERNEL_EXEC); - create_pgd_mapping(early_pg_dir, PAGE_OFFSET + 0x80000000UL, - 0x80000000UL, PGDIR_SIZE, PAGE_KERNEL_EXEC); - - /* --- 3. Build trampoline_pg_dir -------------------------------- * - * - * The trampoline maps just the gigapage containing _start, both at - * its identity PA and at its higher-half VA. This is the page - * table SATP holds during the single instruction window between - * "MMU off" and "MMU on with early_pg_dir" — the PC must resolve - * under both, so we re-use the same two entries above. */ - create_pgd_mapping(trampoline_pg_dir, load_pa & PGDIR_MASK, - load_pa & PGDIR_MASK, PGDIR_SIZE, PAGE_KERNEL_EXEC); - create_pgd_mapping(trampoline_pg_dir, - (kernel_map.virt_addr) & PGDIR_MASK, - load_pa & PGDIR_MASK, PGDIR_SIZE, PAGE_KERNEL_EXEC); -} - -/* - * relocate_kernel — walk .rela.dyn and apply R_RISCV_RELATIVE entries. - * - * Called from relocate_enable_mmu *after* the SATP switch and the jump - * to higher-half VA — meaning __rela_dyn_start / __rela_dyn_end resolve - * to valid higher-half pointers we can dereference directly. - * - * Mirrors Linux arch/riscv/mm/init.c::relocate_kernel. Key invariant: - * even when kaslr_offset == 0, the slot store MUST still happen — the - * linker leaves R_RISCV_RELATIVE slots zero-filled at link time, and - * only relocate_kernel writes the link-time addend into them. Without - * this pass, .init_array entries (and every other relocated symbol) - * are NULL pointers. - */ -void __init relocate_kernel(void) -{ - auto *rela = (Elf64_Rela *)__rela_dyn_start; - auto *rela_end = (Elf64_Rela *)__rela_dyn_end; - - const uint64 kaslr_offset = kernel_map.virt_offset; - - for (; rela < rela_end; ++rela) { - if (ELF64_R_TYPE(rela->r_info) != R_RISCV_RELATIVE) { - continue; - } - auto *slot = (Elf64_Addr *)(rela->r_offset + kaslr_offset); - *slot = (Elf64_Addr)(rela->r_addend) + kaslr_offset; - } -} - -/* - * relocate_enable_mmu — switch SATP, jump to higher-half, apply relocations. - * - * Called from head.S after setup_vm has filled kernel_map and built the - * page tables. Equivalent to Linux's same-named asmlinkage routine but - * written in C++ so it can share kernel_map / pgtable definitions with - * setup_vm directly. - * - * Three logical steps: - * - * 1. csrw satp ← trampoline_pg_dir. PC continues running at the - * identity PA, which the trampoline still maps; the high-half PA - * mapping is now also live. - * - * 2. la t0, .Lvirt; add t0, t0, va_kernel_pa_offset; jr t0. - * A PC-relative `la` under medany yields the runtime PA of the - * label; adding va_kernel_pa_offset (= link_va - load_pa) converts - * it to the link-time higher-half VA, which the trampoline maps. - * The `jr` then atomically pushes PC into higher-half space. - * - * 3. csrw satp ← early_pg_dir. We're now executing at higher-half - * VA, so the trampoline can be retired in favour of the full - * early page table. sp / ra / gp must be slid to higher-half too, - * otherwise the next ret-or-call uses physical-low addresses - * whose mapping is about to disappear when vmm_init drops the - * identity entries. - * - * 4. Call relocate_kernel — now safe, because all link-time VA - * pointers in __rela_dyn_start[..] dereference correctly. - */ -extern "C" void __init relocate_enable_mmu(pgd_t *pgdir) -{ - (void)pgdir; /* head.S passes early_pg_dir for documentation; we - * read the global symbol directly inside the asm. */ - - const unsigned long va_kernel_pa_offset = kernel_map.va_kernel_pa_offset; - const unsigned long satp_mode = SATP_MODE_SV39; - - asm volatile ( - /* Step 1: csrw satp <- trampoline */ - "la t0, trampoline_pg_dir\n" - "srli t0, t0, 12\n" - "or t0, t0, %[mode]\n" - "csrw satp, t0\n" - "sfence.vma\n" - - /* Step 2: jump to higher-half VA */ - "la t0, 1f\n" - "add t0, t0, %[off]\n" - "jr t0\n" - "1:\n" - - /* Step 3a: csrw satp <- early_pg_dir (now in higher-half) */ - "la t0, early_pg_dir\n" - "srli t0, t0, 12\n" - "or t0, t0, %[mode]\n" - "csrw satp, t0\n" - "sfence.vma\n" - - /* Step 3b: slide sp / ra / gp to higher-half */ - "add sp, sp, %[off]\n" - "add ra, ra, %[off]\n" - ".option push\n" - ".option norelax\n" - "la gp, __global_pointer$\n" - ".option pop\n" - : - : [off] "r"(va_kernel_pa_offset), - [mode] "r"(satp_mode) - : "t0", "memory" - ); - - /* Step 4: apply .rela.dyn now that VA dereferences work. */ - relocate_kernel(); -} diff --git a/na/arch/riscv/mm/pmap.cc b/na/arch/riscv/mm/pmap.cc deleted file mode 100644 index f0a6848..0000000 --- a/na/arch/riscv/mm/pmap.cc +++ /dev/null @@ -1,361 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pmap.cc — SV39 page table operations (MD layer) - * - * Implements the pmap namespace interface for RISC-V SV39. - * 3-level page table: VPN[2] (9 bits) -> VPN[1] (9 bits) -> VPN[0] (9 bits) - * Each level is a 4KB page containing 512 PTEs of 8 bytes each. - * - * Address conversion: all page table operations work with physical addresses. - * To read/write page table entries we must convert PA to VA via pa_to_va(). - * - * Reference: docs/specs/vmm.md - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pmap { - -/* - * Before the first pmap::activate(), CPU runs on entry.S bootstrap page table - * (fixed direct-map base). After activation, runtime (KASLR) base is valid. - */ -static bool g_runtime_mapping_ready = false; - -/* PTE bit definitions */ -inline constexpr uint64 PTE_V = 1ULL << 0; -inline constexpr uint64 PTE_R = 1ULL << 1; -inline constexpr uint64 PTE_W = 1ULL << 2; -inline constexpr uint64 PTE_X = 1ULL << 3; -inline constexpr uint64 PTE_U = 1ULL << 4; -inline constexpr uint64 PTE_G = 1ULL << 5; -inline constexpr uint64 PTE_A = 1ULL << 6; -inline constexpr uint64 PTE_D = 1ULL << 7; -inline constexpr uint64 PTE_COW = 1ULL << 8; - -/* SV39: 3 levels, 9 bits per level, 12-bit page offset */ -inline constexpr int kLevels = 3; -inline constexpr int kPtePerPage = 512; - -/* SV39: upper half of VA space (kernel) starts at L2 index 256 */ -inline constexpr int kKernelPgdStart = kPtePerPage / 2; - -/* SATP PPN mask (44 bits) and page shift */ -inline constexpr uint64 kSatpPpnMask = 0xFFFFFFFFFFFULL; -inline constexpr int kPageShift = 12; - -/* Extract VPN[level] from a virtual address */ -static inline uint64 vpn(uint64 va, int level) -{ - return (va >> (12 + 9 * level)) & 0x1FF; -} - -/* Extract PA from a PTE */ -static inline uint64 pte_to_pa(uint64 pte) -{ - return ((pte >> 10) & 0xFFFFFFFFFFFULL) << 12; -} - -/* Create a PTE from a PA and flags */ -static inline uint64 pa_to_pte(uint64 pa, uint64 flags) -{ - return ((pa >> 12) << 10) | flags; -} - -/* Convert PA to kernel VA for accessing page table contents */ -static inline uint64 *pa_to_ptr(uint64 pa) -{ - uint64 va = g_runtime_mapping_ready - ? pa_to_va(phys_addr(pa)).raw - : boot_pa_to_va(phys_addr(pa)).raw; - return reinterpret_cast(va); -} - -/* Allocate a zeroed page for a page table level. - * During early boot (before PMM), uses memblock. - * After PMM init, uses the buddy allocator. - * Returns PA of the new page, or 0 on failure. */ -static uint64 alloc_pt_page() -{ - uint64 pa; - if (memblock_is_active()) { - pa = memblock_alloc(kPageSize, kPageSize); - } else { - Page *page = pmm_alloc_page(); - if (!page) { - return 0; - } - pa = page_to_pa(page).raw; - } - if (!pa) { - return 0; - } - /* Zero the page */ - uint64 *ptr = pa_to_ptr(pa); - for (int i = 0; i < kPtePerPage; i++) { - ptr[i] = 0; - } - return pa; -} - -uint64 create() -{ - uint64 new_root = alloc_pt_page(); - if (!new_root) return 0; - - /* Copy kernel mappings from the current kernel page table. - * In SV39, entries 256..511 (VPN[2] >= 256) cover the upper - * half of the 64-bit VA space where the kernel lives. - * Without these, switching to the new page table would - * immediately fault on any kernel VA access. - * - * Only do this after the runtime page table is active — - * during early boot the bootstrap PT doesn't cover all of - * RAM so pa_to_ptr on the kernel root would fault. */ - if (g_runtime_mapping_ready) { - uint64 kernel_satp; - asm volatile("csrr %0, satp" : "=r"(kernel_satp)); - uint64 kernel_root_pa = (kernel_satp & kSatpPpnMask) << kPageShift; - - uint64 *new_l2 = pa_to_ptr(new_root); - uint64 *kern_l2 = pa_to_ptr(kernel_root_pa); - - for (int i = kKernelPgdStart; i < kPtePerPage; i++) { - new_l2[i] = kern_l2[i]; - } - } - - return new_root; -} - -void destroy(uint64 root_pa) -{ - uint64 *l2 = pa_to_ptr(root_pa); - - /* Only free the user half (entries 0..255). - * Entries 256..511 are shared kernel PGD pointers copied - * from the kernel page table — freeing them would corrupt - * the kernel's own mappings. */ - for (int i = 0; i < kKernelPgdStart; i++) { - if (!(l2[i] & PTE_V)) - continue; - /* Non-leaf L2 entry? (no R/W/X bits = pointer to L1 table) */ - if (!(l2[i] & (PTE_R | PTE_W | PTE_X))) { - uint64 l1_pa = pte_to_pa(l2[i]); - uint64 *l1 = pa_to_ptr(l1_pa); - for (int j = 0; j < kPtePerPage; j++) { - if (!(l1[j] & PTE_V)) - continue; - if (!(l1[j] & (PTE_R | PTE_W | PTE_X))) { - /* L0 page table */ - uint64 l0_pa = pte_to_pa(l1[j]); - pmm_free_page(pa_to_page(phys_addr(l0_pa))); - } - } - pmm_free_page(pa_to_page(phys_addr(l1_pa))); - } - } - pmm_free_page(pa_to_page(phys_addr(root_pa))); -} - -int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) -{ - uint64 *table = pa_to_ptr(root_pa); - - /* Walk levels 2 -> 1, creating intermediate tables as needed */ - for (int level = 2; level > 0; level--) { - uint64 idx = vpn(va, level); - if (!(table[idx] & PTE_V)) { - uint64 child_pa = alloc_pt_page(); - if (!child_pa) - return -ENOMEM; /* -ENOMEM */ - table[idx] = pa_to_pte(child_pa, PTE_V); - } - table = pa_to_ptr(pte_to_pa(table[idx])); - } - - /* Level 0: install the leaf PTE */ - uint64 idx = vpn(va, 0); - table[idx] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); - return 0; -} - -int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) -{ - /* VA and PA must be 2MB-aligned */ - constexpr uint64 k2MB = 2ULL * 1024 * 1024; - if ((va & (k2MB - 1)) != 0 || (pa & (k2MB - 1)) != 0) { - return -EINVAL; /* -EINVAL */ - } - - uint64 *table = pa_to_ptr(root_pa); - - /* Walk L2: create intermediate table if needed */ - uint64 idx2 = vpn(va, 2); - if (!(table[idx2] & PTE_V)) { - uint64 child_pa = alloc_pt_page(); - if (!child_pa) { - return -ENOMEM; /* -ENOMEM */ - } - table[idx2] = pa_to_pte(child_pa, PTE_V); - } - table = pa_to_ptr(pte_to_pa(table[idx2])); - - /* L1: install leaf PTE (2MB mega page) */ - uint64 idx1 = vpn(va, 1); - table[idx1] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); - return 0; -} - -void unmap(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level > 0; level--) { - uint64 idx = vpn(va, level); - if (!(table[idx] & PTE_V)) - return; - table = pa_to_ptr(pte_to_pa(table[idx])); - } - table[vpn(va, 0)] = 0; -} - -int protect(uint64 root_pa, uint64 va, uint64 perm) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return -EFAULT; /* -EFAULT */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - uint64 pa = pte_to_pa(pte); - table[idx] = pa_to_pte(pa, perm | PTE_V); - return 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return -EFAULT; -} - -int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm) -{ - if (end <= start) { - return 0; - } - - uint64 va = page_align_down(start); - uint64 va_end = page_align_up(end); - - for (; va < va_end; va += kPageSize) { - int ret = protect(root_pa, va, perm); - if (ret == -14) { - continue; /* unmapped page: skip */ - } - if (ret < 0) { - return ret; - } - } - - arch::mmu::flush_tlb_all(); - return 0; -} - -bool lookup(uint64 root_pa, uint64 va, uint64 *pa_out) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return false; - /* Leaf PTE? (has R, W, or X) */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - if (pa_out) - *pa_out = pte_to_pa(pte); - return true; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return false; -} - -uint64 lookup_pte(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return 0; - if (pte & (PTE_R | PTE_W | PTE_X)) - return pte; // leaf PTE raw value - table = pa_to_ptr(pte_to_pa(pte)); - } - return 0; -} - -bool is_cow(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return false; - if (pte & (PTE_R | PTE_W | PTE_X)) { - return (pte & PTE_COW) != 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return false; -} - -int make_cow(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return -EFAULT; /* -EFAULT */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - /* Clear write bit, set COW bit */ - pte &= ~PTE_W; - pte |= PTE_COW; - table[idx] = pte; - return 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return -EFAULT; -} - -void activate(uint64 root_pa) -{ - arch::mmu::set_page_table(root_pa); - arch::mmu::flush_tlb_all(); - g_runtime_mapping_ready = true; -} - -uint64 vm_perm_to_pte(uint64 vm_perm) -{ - /* Translate MI VM_* flags to SV39 PTE bits. - * The MI flags are defined in . */ - uint64 pte = PTE_V | PTE_A | PTE_D; // Valid + Accessed + Dirty (avoid HW A/D faults) - if (vm_perm & (1ULL << 0)) pte |= PTE_R; // VM_READ - if (vm_perm & (1ULL << 1)) pte |= PTE_W; // VM_WRITE - if (vm_perm & (1ULL << 2)) pte |= PTE_X; // VM_EXEC - if (vm_perm & (1ULL << 3)) pte |= PTE_U; // VM_USER - return pte; -} - -} // namespace pmap diff --git a/na/arch/riscv/mm/tlb_md.cc b/na/arch/riscv/mm/tlb_md.cc deleted file mode 100644 index ed9aa29..0000000 --- a/na/arch/riscv/mm/tlb_md.cc +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include - -namespace arch::tlb { - -void flush_remote(uint64 cpu_mask, uint64 start, uint64 size) { - if (size == ~0ULL) { - arch::sbi::sbi_remote_sfence_vma(cpu_mask, 0, 0, ~0UL); - } else { - arch::sbi::sbi_remote_sfence_vma(cpu_mask, 0, start, size); - } -} - -} // namespace arch::tlb diff --git a/na/arch/riscv/module.mk b/na/arch/riscv/module.mk deleted file mode 100644 index 97d22de..0000000 --- a/na/arch/riscv/module.mk +++ /dev/null @@ -1,18 +0,0 @@ -# arch/riscv/module.mk -SRCS_CC += arch/riscv/mm/pmap.cc -SRCS_CC += arch/riscv/mm/init.cc -SRCS_CC += arch/riscv/config.cc -SRCS_CC += arch/riscv/trap/trap_md.cc -SRCS_CC += arch/riscv/syscall/syscall_md.cc -SRCS_CC += arch/riscv/lib/random.cc -SRCS_CC += arch/riscv/mm/tlb_md.cc -SRCS_S += arch/riscv/kernel/head.S -SRCS_S += arch/riscv/trap/swtch.S -SRCS_S += arch/riscv/trap/fpu.S -SRCS_S += arch/riscv/trap/trap_vector.S -SRCS_S += arch/riscv/uaccess/uaccess_memcpy.S -SRCS_CC += arch/riscv/trap/extable.cc -SRCS_CC += arch/riscv/kernel/signal.cc - -# Arch-specific headers (added to include path) -INCLUDES += -Iarch/riscv/include diff --git a/na/arch/riscv/syscall/syscall_md.cc b/na/arch/riscv/syscall/syscall_md.cc deleted file mode 100644 index 26320c8..0000000 --- a/na/arch/riscv/syscall/syscall_md.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Syscall MD Layer — RISC-V Register Extraction - * - * Extracts syscall number (a7) and arguments (a0-a5) from the TrapFrame, - * calls the MI dispatcher, and writes the return value back to a0. - * - * Reference: docs/specs/syscall.md §5 - */ - -#include -#include -#include -#include - -void syscall_dispatch_md(arch::TrapFrame* tf) -{ - uint64 nr = tf->a[7]; /* RISC-V ABI: syscall number in a7 */ - - uint64 args[6] = { - tf->a[0], tf->a[1], tf->a[2], - tf->a[3], tf->a[4], tf->a[5], - }; - - /* Enable interrupts so timer and preemption work during syscalls */ - arch::cpu::intr_on(); - - int64 ret = syscall_dispatch(nr, args); - - arch::cpu::intr_off(); - - /* sys_rt_sigreturn restores the entire TrapFrame from the signal - * frame on the user stack. Writing ret into a[0] would clobber - * the restored value, so we must skip the writeback. */ - if (nr == static_cast(SYS_rt_sigreturn)){ - return; - } - tf->a[0] = static_cast(ret); /* Return value in a0 */ -} - diff --git a/na/arch/riscv/trap/extable.cc b/na/arch/riscv/trap/extable.cc deleted file mode 100644 index 57a3dc1..0000000 --- a/na/arch/riscv/trap/extable.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include - -extern struct ExceptionTableEntry __extable_start[]; -extern struct ExceptionTableEntry __extable_end[]; - -static void swap_extable_entries(struct ExceptionTableEntry *a, struct ExceptionTableEntry *b) { - uint64 a_insn = reinterpret_cast(&a->insn) + a->insn; - uint64 a_fixup = reinterpret_cast(&a->fixup) + a->fixup; - uint64 b_insn = reinterpret_cast(&b->insn) + b->insn; - uint64 b_fixup = reinterpret_cast(&b->fixup) + b->fixup; - - a->insn = static_cast(b_insn - reinterpret_cast(&a->insn)); - a->fixup = static_cast(b_fixup - reinterpret_cast(&a->fixup)); - b->insn = static_cast(a_insn - reinterpret_cast(&b->insn)); - b->fixup = static_cast(a_fixup - reinterpret_cast(&b->fixup)); -} - -static void sort_extable(size_t left, size_t right) { - if (left >= right) return; - - size_t mid_idx = left + (right - left) / 2; - struct ExceptionTableEntry *pivot = &__extable_start[mid_idx]; - uint64 pivot_val = reinterpret_cast(&pivot->insn) + pivot->insn; - - size_t i = left; - size_t j = right; - - while (i <= j) { - while ((reinterpret_cast(&__extable_start[i].insn) + __extable_start[i].insn) < pivot_val) { - i++; - } - while ((reinterpret_cast(&__extable_start[j].insn) + __extable_start[j].insn) > pivot_val) { - if (j == 0) { - break; - } - j--; - } - if (i <= j) { - swap_extable_entries(&__extable_start[i], &__extable_start[j]); - i++; - if (j == 0) { - break; - } - j--; - } - } - - if (j > left) { - sort_extable(left, j); - } - if (i < right) { - sort_extable(i, right); - } -} - -void extable_init() { - size_t count = __extable_end - __extable_start; - if (count <= 1) return; - sort_extable(0, count - 1); -} - -uint64 search_extable(uint64 pc) { - size_t count = __extable_end - __extable_start; - if (count == 0) return 0; - - size_t left = 0; - size_t right = count - 1; - - /* Binary search */ - while (left <= right) { - size_t mid = left + (right - left) / 2; - const struct ExceptionTableEntry *entry = &__extable_start[mid]; - uint64 mid_pc = reinterpret_cast(&entry->insn) + entry->insn; - - if (mid_pc == pc) { - return reinterpret_cast(&entry->fixup) + entry->fixup; - } - - if (mid_pc < pc) { - left = mid + 1; - } else { - if (mid == 0) break; - right = mid - 1; - } - } - - return 0; -} diff --git a/na/arch/riscv/trap/fpu.S b/na/arch/riscv/trap/fpu.S deleted file mode 100644 index 90d96d9..0000000 --- a/na/arch/riscv/trap/fpu.S +++ /dev/null @@ -1,103 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * FPU Save / Restore (RISC-V D extension) - * - * void fpu_save(FpState* state); - * Saves f0-f31 and fcsr to the FpState structure. - * - * void fpu_restore(FpState* state); - * Restores f0-f31 and fcsr from the FpState structure. - * - * FpState layout: f[0..31] at offset 0 (each 8 bytes), fcsr at offset 256. - * - * Called from schedule() when switching between tasks that have - * used the FPU (sstatus.FS == Dirty on save path). - */ - - .section .text - -/* ====================================================================== - * void fpu_save(FpState* state); // a0 = state pointer - * ====================================================================== */ - .globl fpu_save - .align 2 -fpu_save: - fsd f0, 0(a0) - fsd f1, 8(a0) - fsd f2, 16(a0) - fsd f3, 24(a0) - fsd f4, 32(a0) - fsd f5, 40(a0) - fsd f6, 48(a0) - fsd f7, 56(a0) - fsd f8, 64(a0) - fsd f9, 72(a0) - fsd f10, 80(a0) - fsd f11, 88(a0) - fsd f12, 96(a0) - fsd f13, 104(a0) - fsd f14, 112(a0) - fsd f15, 120(a0) - fsd f16, 128(a0) - fsd f17, 136(a0) - fsd f18, 144(a0) - fsd f19, 152(a0) - fsd f20, 160(a0) - fsd f21, 168(a0) - fsd f22, 176(a0) - fsd f23, 184(a0) - fsd f24, 192(a0) - fsd f25, 200(a0) - fsd f26, 208(a0) - fsd f27, 216(a0) - fsd f28, 224(a0) - fsd f29, 232(a0) - fsd f30, 240(a0) - fsd f31, 248(a0) - frcsr t0 - sw t0, 256(a0) - ret - -/* ====================================================================== - * void fpu_restore(FpState* state); // a0 = state pointer - * ====================================================================== */ - .globl fpu_restore - .align 2 -fpu_restore: - fld f0, 0(a0) - fld f1, 8(a0) - fld f2, 16(a0) - fld f3, 24(a0) - fld f4, 32(a0) - fld f5, 40(a0) - fld f6, 48(a0) - fld f7, 56(a0) - fld f8, 64(a0) - fld f9, 72(a0) - fld f10, 80(a0) - fld f11, 88(a0) - fld f12, 96(a0) - fld f13, 104(a0) - fld f14, 112(a0) - fld f15, 120(a0) - fld f16, 128(a0) - fld f17, 136(a0) - fld f18, 144(a0) - fld f19, 152(a0) - fld f20, 160(a0) - fld f21, 168(a0) - fld f22, 176(a0) - fld f23, 184(a0) - fld f24, 192(a0) - fld f25, 200(a0) - fld f26, 208(a0) - fld f27, 216(a0) - fld f28, 224(a0) - fld f29, 232(a0) - fld f30, 240(a0) - fld f31, 248(a0) - lw t0, 256(a0) - fscsr t0 - ret diff --git a/na/arch/riscv/trap/swtch.S b/na/arch/riscv/trap/swtch.S deleted file mode 100644 index dbae43d..0000000 --- a/na/arch/riscv/trap/swtch.S +++ /dev/null @@ -1,96 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Context Switch & Kernel Thread Trampoline (RISC-V 64) - * - * context_switch: saves/restores callee-saved registers. - * kthread_entry_trampoline: entry point for newly created kernel threads. - * After context_switch loads the new Context, execution lands here via - * Context.ra = kthread_entry_trampoline. The entry function and argument - * are in s0 and s1 respectively, set by sched_init/create_tcb. - * - * The trampoline calls the MI-layer kthread_entry(entry, arg). - * - * Reference: docs/specs/scheduler.md §6 - */ - - .section .text - -/* ======================================================================== - * void context_switch(Context* old_ctx, Context* next_ctx); - * a0 = old_ctx_ptr, a1 = next_ctx_ptr - * ======================================================================== */ - .globl context_switch - .align 2 -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 - -/* ======================================================================== - * kthread_entry_trampoline - * - * Reached via context_switch when a new kthread is first scheduled. - * Context was set up by create_tcb(): - * s0 = entry function pointer - * s1 = argument pointer - * - * Calls the MI-layer C++ function: - * void kthread_entry(void (*entry)(void*), void* arg); - * ======================================================================== */ - .globl kthread_entry_trampoline - .align 2 -kthread_entry_trampoline: - mv a0, s0 /* a0 = entry function */ - mv a1, s1 /* a1 = argument */ - call kthread_entry /* MI-layer C++ function */ - - /* kthread_entry should never return, but if it does: */ - wfi - j kthread_entry_trampoline + 12 /* loop on wfi */ - -/* ======================================================================== - * ret_from_fork - * - * Trampoline for newly forked processes. - * Context is set up by fork() so that s0 contains the TrapFrame pointer. - * - * Calls the MI-layer C++ function: - * void fork_child_entry(TrapFrame* tf); - * ======================================================================== */ - .globl ret_from_fork - .align 2 -ret_from_fork: - mv a0, s0 /* a0 = TrapFrame pointer */ - call fork_child_entry /* does not return, eventually calls userret */ - wfi - j ret_from_fork + 8 \ No newline at end of file diff --git a/na/arch/riscv/trap/trap_md.cc b/na/arch/riscv/trap/trap_md.cc deleted file mode 100644 index e30aea6..0000000 --- a/na/arch/riscv/trap/trap_md.cc +++ /dev/null @@ -1,241 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Trap MD Layer (RISC-V 64) - * - * All architecture-specific trap logic lives here: - * - scause/stval reading (CSR access) - * - scause → MI TrapCause translation - * - RISC-V specific fixups (ecall epc += 4, ebreak epc += 2) - * - stvec setup (trap_init / trap_init_ap) - * - kernel_trap_handler (traps from S-mode) - * - * MI layer (kernel/trap.cc) contains ZERO arch-specific code. - * - * Reference: docs/specs/trap.md §7 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* PLIC interface (drivers/plic.cc) */ -uint32 plic_claim(); -void plic_complete(uint32 irq); - -/* trap_vector.S: assembly entry point */ -extern "C" void trap_vector(); - -/* ======================================================================== - * scause → TrapCause translation (RISC-V specific) - * ======================================================================== */ - -static TrapCause scause_to_trap_cause(uint64 scause) -{ - if (arch::trap::is_interrupt(scause)) { - switch (arch::trap::cause_code(scause)) { - case IRQ_S_TIMER: return TrapCause::IrqTimer; - case IRQ_S_SOFTWARE: return TrapCause::IrqSoftware; - case IRQ_S_EXTERNAL: return TrapCause::IrqExternal; - default: return TrapCause::Unknown; - } - } - - /* Exception (synchronous) */ - switch (scause) { - case EXC_INST_PAGE_FAULT: return TrapCause::PageFaultExec; - case EXC_LOAD_PAGE_FAULT: return TrapCause::PageFaultLoad; - case EXC_STORE_PAGE_FAULT: return TrapCause::PageFaultStore; - case EXC_ECALL_FROM_U: return TrapCause::Syscall; - case EXC_ILLEGAL_INST: return TrapCause::IllegalInst; - case EXC_BREAKPOINT: return TrapCause::Breakpoint; - - case EXC_INST_ACCESS_FAULT: - case EXC_LOAD_ACCESS_FAULT: - case EXC_STORE_ACCESS_FAULT: - return TrapCause::AccessFault; - - case EXC_INST_MISALIGNED: - case EXC_LOAD_MISALIGNED: - case EXC_STORE_MISALIGNED: - return TrapCause::AlignFault; - - default: - return TrapCause::Unknown; - } -} - -TrapCause trap_translate_scause(uint64 scause) -{ - return scause_to_trap_cause(scause); -} - -static TrapOrigin origin_from_sstatus(uint64 sstatus) -{ - constexpr uint64 kSstatusSPP = (1ULL << 8); - return (sstatus & kSstatusSPP) ? TrapOrigin::FromKernel - : TrapOrigin::FromUser; -} - -static TrapClass classify_trap(uint64 scause, TrapCause cause) -{ - if (arch::trap::is_interrupt(scause)) { - return TrapClass::Interrupt; - } - if (cause == TrapCause::Syscall) { - return TrapClass::Syscall; - } - if (cause == TrapCause::Unknown) { - return TrapClass::Unknown; - } - return TrapClass::Exception; -} - -static TrapInfo build_trap_info(arch::TrapFrame* tf, - uint64 scause, - uint64 stval, - TrapOrigin origin) -{ - TrapInfo info{}; - info.cause = scause_to_trap_cause(scause); - info.cls = classify_trap(scause, info.cause); - info.origin = origin; - info.val = stval; - info.epc = tf ? tf->epc : 0; - info.scause = scause; - return info; -} - -/* ======================================================================== - * trap_dispatch — user-mode trap entry (called from trap_vector.S) - * - * Reads RISC-V CSRs, translates to MI TrapCause, applies arch-specific - * fixups, then calls MI trap_handle(). - * - * After trap_handle returns, we must return to user mode via userret(). - * The assembly jalr t3 that called us has NO return-to-user code after it, - * so trap_dispatch must never return normally. - * ======================================================================== */ - -extern "C" void userret(arch::TrapFrame* tf); - -extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame* tf) -{ - /* Switch stvec to kernelvec immediately so that any traps during - * kernel execution (timer IRQs, page faults from copy_from_user) - * go through the kernel trap entry, not the user trap entry. - * This matches Linux which sets stvec to handle_exception (kernel) - * at the top of the user trap path. */ - extern void kernelvec(); - arch::trap::set_vector(reinterpret_cast(&kernelvec)); - - /* Enable SUM so S-mode can access user pages (PTE_U). - * Without this, any dereference of user-space pointers in syscall - * handlers (sys_write buf, sys_read buf, etc.) would page fault. */ - arch::trap::enable_sum(); - - uint64 scause = arch::trap::get_cause(); - uint64 stval = arch::trap::get_value(); - - TrapOrigin origin = origin_from_sstatus(tf->sstatus); - TrapInfo info = build_trap_info(tf, scause, stval, origin); - - /* Per-trap trace from user mode. Stored at KLOG_DEBUG / KLOG_SUBSYS_TRAP - * so it lives in the ring buffer for postmortem but stays off the console - * by default. No more in-band 20-trap window — flip console_level or - * dump the ring on demand instead. */ - if (origin == TrapOrigin::FromUser) { - pr_debug_s(KLOG_SUBSYS_TRAP, - "trap: cause=%u epc=%llx val=%llx\n", - static_cast(info.cause), info.epc, info.val); - } - - /* Save trapframe in TCB for syscall handlers (like fork) */ - TaskControlBlock* curr = current_task(); - if (curr) { - curr->trapframe = tf; - } - - /* RISC-V ecall fix: ecall does not increment sepc. - * Advance epc so sret returns to the instruction AFTER ecall. */ - if (info.cause == TrapCause::Syscall) { - tf->epc += 4; - info.epc = tf->epc; - } - - /* RISC-V ebreak fix: advance past the C.EBREAK instruction (2 bytes). - * This is arch-specific because instruction size depends on the ISA. */ - if (info.cause == TrapCause::Breakpoint) { - tf->epc += 2; - info.epc = tf->epc; - } - - /* Call MI-layer dispatcher (zero inline assembly from here on) */ - trap_handle(tf, info); - - /* Return to user mode. - * Set stvec back to trap_vector for the next user-mode trap, - * then jump to userret which restores registers and does sret. */ - arch::trap::set_vector(reinterpret_cast(&trap_vector)); - userret(tf); - __builtin_unreachable(); -} - -/* ======================================================================== - * kernel_trap_handler — traps taken while already in S-mode - * - * For Phase 2, only timer and external interrupts are expected. - * Exceptions in S-mode are always fatal. - * - * This belongs in MD because it reads scause directly. - * ======================================================================== */ - -extern "C" void kernel_trap_handler(arch::TrapFrame* tf) -{ - uint64 scause = arch::trap::get_cause(); - uint64 stval = arch::trap::get_value(); - - if (scause == EXC_LOAD_PAGE_FAULT || scause == EXC_STORE_PAGE_FAULT) { - uint64 fixup = search_extable(tf->epc); - if (fixup != 0) { - /* Exception table entry found: redirect execution to fixup code */ - tf->epc = fixup; - return; - } - } - - TrapInfo info = build_trap_info(tf, scause, stval, TrapOrigin::FromKernel); - trap_handle(tf, info); -} - -/* kernelvec assembly is in trap_vector.S */ -extern "C" void kernelvec(); - -/* ======================================================================== - * trap_init() — BSP: set stvec, init IrqManager - * ======================================================================== */ - -void trap_init() -{ - g_irq_manager.init(); - arch::trap::set_vector(reinterpret_cast(&kernelvec)); -} - -/* ======================================================================== - * trap_init_ap() — AP: set this CPU's stvec - * ======================================================================== */ - -void trap_init_ap() -{ - arch::trap::set_vector(reinterpret_cast(&kernelvec)); -} - diff --git a/na/arch/riscv/trap/trap_vector.S b/na/arch/riscv/trap/trap_vector.S deleted file mode 100644 index 8e45cae..0000000 --- a/na/arch/riscv/trap/trap_vector.S +++ /dev/null @@ -1,283 +0,0 @@ -/* 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 - - /* Step 6.5: restore kernel gp (global pointer). - * The compiler uses gp-relative addressing for global variable access - * (e.g. ld reg, offset(gp)). User mode may have set gp to anything, - * so we MUST reload it before entering any C++ code. */ -.option push -.option norelax - la gp, __global_pointer$ -.option pop - - /* Pass TrapFrame* to C++ handler (a0 = tf) */ - mv a0, tp - - /* Set tp = CPU ID (kernel convention) */ - mv tp, t2 - - /* Step 7: jump to C++ trap_handler */ - jalr t3 - - -/* ======================================================================== - * kernelvec — unified S-mode trap entry point for traps FROM S-MODE - * - * Traps from S-mode already have a valid kernel stack pointer in sp. - * We just allocate a TrapFrame on the stack and save registers. - * ======================================================================== */ - .globl kernelvec - .align 4 -kernelvec: - /* Allocate TrapFrame on stack */ - addi sp, sp, -296 - - /* Save all 31 general-purpose registers into TrapFrame */ - sd ra, TF_RA(sp) - /* Save the OLD sp (before we subtracted 296) */ - addi t0, sp, 296 - sd t0, TF_SP(sp) - sd gp, TF_GP(sp) - sd tp, TF_TP(sp) - sd t0, TF_T0(sp) - sd t1, TF_T1(sp) - sd t2, TF_T2(sp) - sd s0, TF_S0(sp) - sd s1, TF_S1(sp) - sd a0, TF_A0(sp) - sd a1, TF_A1(sp) - sd a2, TF_A2(sp) - sd a3, TF_A3(sp) - sd a4, TF_A4(sp) - sd a5, TF_A5(sp) - sd a6, TF_A6(sp) - sd a7, TF_A7(sp) - sd s2, TF_S2(sp) - sd s3, TF_S3(sp) - sd s4, TF_S4(sp) - sd s5, TF_S5(sp) - sd s6, TF_S6(sp) - sd s7, TF_S7(sp) - sd s8, TF_S8(sp) - sd s9, TF_S9(sp) - sd s10, TF_S10(sp) - sd s11, TF_S11(sp) - sd t3, TF_T3(sp) - sd t4, TF_T4(sp) - sd t5, TF_T5(sp) - sd t6, TF_T6(sp) - - /* Save sstatus and sepc */ - csrr t0, sstatus - sd t0, TF_SSTATUS(sp) - csrr t0, sepc - sd t0, TF_EPC(sp) - - /* Call C++ kernel_trap_handler(TrapFrame* tf) */ - mv a0, sp - call kernel_trap_handler - - /* Restore sstatus and sepc */ - ld t0, TF_SSTATUS(sp) - csrw sstatus, t0 - ld t0, TF_EPC(sp) - csrw sepc, t0 - - /* Restore all 31 general-purpose registers */ - ld ra, TF_RA(sp) - ld gp, TF_GP(sp) - ld tp, TF_TP(sp) - ld t0, TF_T0(sp) - ld t1, TF_T1(sp) - ld t2, TF_T2(sp) - ld s0, TF_S0(sp) - ld s1, TF_S1(sp) - ld a0, TF_A0(sp) - ld a1, TF_A1(sp) - ld a2, TF_A2(sp) - ld a3, TF_A3(sp) - ld a4, TF_A4(sp) - ld a5, TF_A5(sp) - ld a6, TF_A6(sp) - ld a7, TF_A7(sp) - ld s2, TF_S2(sp) - ld s3, TF_S3(sp) - ld s4, TF_S4(sp) - ld s5, TF_S5(sp) - ld s6, TF_S6(sp) - ld s7, TF_S7(sp) - ld s8, TF_S8(sp) - ld s9, TF_S9(sp) - ld s10, TF_S10(sp) - ld s11, TF_S11(sp) - ld t3, TF_T3(sp) - ld t4, TF_T4(sp) - ld t5, TF_T5(sp) - ld t6, TF_T6(sp) - - /* Restore sp last */ - ld sp, TF_SP(sp) - - sret - -/* - * 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/arch/riscv/uaccess/uaccess_memcpy.S b/na/arch/riscv/uaccess/uaccess_memcpy.S deleted file mode 100644 index 615680d..0000000 --- a/na/arch/riscv/uaccess/uaccess_memcpy.S +++ /dev/null @@ -1,117 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include - -/* - * extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len); - * - * a0: dst - * a1: src - * a2: len - * Returns: 0 on success, -EFAULT (-14) on page fault. - */ -.global __uaccess_memcpy -.type __uaccess_memcpy, @function -__uaccess_memcpy: - /* If len == 0, return 0 */ - beqz a2, .Lmemcpy_done - - /* Enable SUM (Supervisor User Memory) bit: sstatus.SUM is bit 18 */ - li t0, (1 << 18) - csrs sstatus, t0 - -.Lmemcpy_loop: - /* Load byte from src */ -1: lb t1, 0(a1) - /* Store byte to dst */ -2: sb t1, 0(a0) - - addi a0, a0, 1 - addi a1, a1, 1 - addi a2, a2, -1 - bnez a2, .Lmemcpy_loop - -.Lmemcpy_done: - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, 0 - ret - -.Lmemcpy_fault: - /* Fixup routine on fault */ - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, -14 /* -EFAULT */ - ret - -/* Exception table entries */ -EXTABLE(1b, .Lmemcpy_fault) -EXTABLE(2b, .Lmemcpy_fault) - - -/* - * extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len); - * - * a0: dst - * a1: src - * a2: max_len - * Returns: number of bytes copied (excluding null), or -EFAULT (-14) on page fault. - */ -.global __uaccess_strncpy -.type __uaccess_strncpy, @function -__uaccess_strncpy: - /* If max_len == 0, return 0 */ - beqz a2, .Lstrncpy_done_zero - - /* Enable SUM */ - li t0, (1 << 18) - csrs sstatus, t0 - - /* t2 will store the length copied */ - li t2, 0 - -.Lstrncpy_loop: - /* Load byte from src */ -3: lb t1, 0(a1) - /* Store byte to dst */ -4: sb t1, 0(a0) - - /* If it was the null terminator, we're done */ - beqz t1, .Lstrncpy_done - - addi a0, a0, 1 - addi a1, a1, 1 - addi t2, t2, 1 - addi a2, a2, -1 - bnez a2, .Lstrncpy_loop - - /* Reached max_len without finding null */ - /* We must null-terminate at max_len - 1 as per typical strncpy_from_user semantics */ - addi a0, a0, -1 - sb zero, 0(a0) - addi t2, t2, -1 /* length of string without the forced null terminator */ - -.Lstrncpy_done: - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - mv a0, t2 - ret - -.Lstrncpy_done_zero: - li a0, 0 - ret - -.Lstrncpy_fault: - /* Fixup routine on fault */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, -14 /* -EFAULT */ - ret - -/* Exception table entries */ -EXTABLE(3b, .Lstrncpy_fault) -EXTABLE(4b, .Lstrncpy_fault) diff --git a/na/drivers/base/bus.cc b/na/drivers/base/bus.cc deleted file mode 100644 index 975f676..0000000 --- a/na/drivers/base/bus.cc +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/bus.cc — bus_type registration. - * - * Mirrors Linux drivers/base/bus.c. Trivial in this iteration: - * initialize the device/driver lists and the per-bus spinlock. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include -#include -#include - -int bus_register(bus_type* bus) -{ - if (bus == nullptr || bus->name == nullptr) { - return -EINVAL; - } - if (bus->registered) { - return 0; - } - bus->devices.init(); - bus->drivers.init(); - bus->lock.init(bus->name); - bus->registered = true; - kprintf("[bus] '%s' registered\n", bus->name); - return 0; -} diff --git a/na/drivers/base/core.cc b/na/drivers/base/core.cc deleted file mode 100644 index f92b2a4..0000000 --- a/na/drivers/base/core.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/core.cc — device core. - * - * Mirrors Linux drivers/base/core.c (greatly simplified). Provides - * device_initialize, device_register, device_unregister, and the - * try_bind helper shared with driver registration. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* device ktype — generic release (no-op; subclasses override) */ -/* ------------------------------------------------------------------ */ - -static void device_release(kobject* kobj) -{ - /* Concrete device flavors (platform_device, virtio_device, ...) are - * expected to allocate via kmalloc and free in their own release - * paths after device_unregister + final kobject_put. The generic - * device kobj_type only fires for stack-allocated devices used in - * tests — nothing to do. */ - (void)kobj; -} - -static const kobj_type device_ktype = { - .release = device_release, -}; - -/* ------------------------------------------------------------------ */ -/* device_initialize / device_register */ -/* ------------------------------------------------------------------ */ - -void device_initialize(device* dev) -{ - if (dev == nullptr) { - return; - } - kobject_init(&dev->kobj, &device_ktype); - dev->parent = nullptr; - dev->driver = nullptr; - dev->driver_data = nullptr; - dev->platform_data = nullptr; - dev->devt = 0; - dev->bus_list.init(); - dev->driver_list.init(); -} - -const char* dev_name(const device* dev) -{ - if (dev == nullptr) { - return "(null)"; - } - if (dev->kobj.name != nullptr) { - return dev->kobj.name; - } - if (dev->init_name != nullptr) { - return dev->init_name; - } - return "(unnamed)"; -} - -/* try_bind: attempt to match `dev` against `drv` and probe. - * Returns 1 on successful bind, 0 otherwise. - * Caller must hold bus->lock. */ -int device_try_bind(device* dev, device_driver* drv); - -int device_try_bind(device* dev, device_driver* drv) -{ - bus_type* bus = dev->bus; - - if (bus->match != nullptr && bus->match(dev, drv) == 0) { - return 0; - } - - dev->driver = drv; - - int rc = 0; - if (bus->probe != nullptr) { - rc = bus->probe(dev); - } else if (drv->probe != nullptr) { - rc = drv->probe(dev); - } - - if (rc == 0) { - list_add_tail(&dev->driver_list, &drv->devices); - return 1; - } - dev->driver = nullptr; - return 0; -} - -int device_register(device* dev) -{ - if (dev == nullptr || dev->bus == nullptr) { - return -EINVAL; - } - if (!dev->bus->registered) { - kprintf("[device] register '%s' fail: bus '%s' not registered\n", - dev_name(dev), - dev->bus->name != nullptr ? dev->bus->name : "?"); - return -ENODEV; - } - - /* device_initialize is idempotent — safe to call again, but most - * paths will already have initialized. Detect via ktype set. */ - if (dev->kobj.ktype == nullptr) { - device_initialize(dev); - } - if (dev->kobj.name == nullptr) { - kobject_add(&dev->kobj, nullptr, dev->init_name); - } - dev->bus_list.init(); - dev->driver_list.init(); - - LockGuard g(dev->bus->lock); - list_add_tail(&dev->bus_list, &dev->bus->devices); - - list_node* cur = dev->bus->drivers.next; - while (cur != &dev->bus->drivers) { - device_driver* drv = - list_entry(cur); - cur = cur->next; - if (device_try_bind(dev, drv) != 0) { - break; - } - } - return 0; -} - -void device_unregister(device* dev) -{ - if (dev == nullptr || dev->bus == nullptr) { - return; - } - - LockGuard g(dev->bus->lock); - - if (dev->driver != nullptr) { - if (dev->driver->remove != nullptr) { - dev->driver->remove(dev); - } - list_del_init(&dev->driver_list); - dev->driver = nullptr; - } - list_del_init(&dev->bus_list); -} diff --git a/na/drivers/base/driver.cc b/na/drivers/base/driver.cc deleted file mode 100644 index ce15b6d..0000000 --- a/na/drivers/base/driver.cc +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/driver.cc — driver_register / driver_unregister. - * - * Mirrors Linux drivers/base/driver.c. Registers a device_driver on - * its bus and tries to bind any unbound matching devices. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include -#include -#include -#include - -/* Implemented in core.cc — bind a device to a driver on its bus. - * Returns 1 on success, 0 on no-match-or-probe-fail. */ -int device_try_bind(device* dev, device_driver* drv); - -int driver_register(device_driver* drv) -{ - if (drv == nullptr || drv->bus == nullptr) { - return -EINVAL; - } - if (!drv->bus->registered) { - kprintf("[driver] register '%s' fail: bus '%s' not registered\n", - drv->name != nullptr ? drv->name : "?", - drv->bus->name != nullptr ? drv->bus->name : "?"); - return -ENODEV; - } - - drv->bus_node.init(); - drv->devices.init(); - - LockGuard g(drv->bus->lock); - list_add_tail(&drv->bus_node, &drv->bus->drivers); - - /* Try to bind any unbound devices already on the bus. */ - list_node* cur = drv->bus->devices.next; - while (cur != &drv->bus->devices) { - device* dev = list_entry(cur); - cur = cur->next; - if (dev->driver == nullptr) { - device_try_bind(dev, drv); - } - } - return 0; -} - -void driver_unregister(device_driver* drv) -{ - if (drv == nullptr || drv->bus == nullptr) { - return; - } - - LockGuard g(drv->bus->lock); - - /* Unbind every device bound to this driver. */ - while (!drv->devices.is_empty()) { - list_node* first = drv->devices.next; - device* dev = list_entry(first); - if (drv->remove != nullptr) { - drv->remove(dev); - } - list_del_init(&dev->driver_list); - dev->driver = nullptr; - } - list_del_init(&drv->bus_node); -} diff --git a/na/drivers/base/module.mk b/na/drivers/base/module.mk deleted file mode 100644 index 1d8fb05..0000000 --- a/na/drivers/base/module.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Driver model core (mirrors Linux drivers/base/) - -SRCS_CC += drivers/base/bus.cc -SRCS_CC += drivers/base/core.cc -SRCS_CC += drivers/base/driver.cc -SRCS_CC += drivers/base/platform.cc diff --git a/na/drivers/base/platform.cc b/na/drivers/base/platform.cc deleted file mode 100644 index 65ec21e..0000000 --- a/na/drivers/base/platform.cc +++ /dev/null @@ -1,231 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/platform.cc — platform bus + FDT-driven device creation. - * - * Mirrors Linux drivers/base/platform.c. Provides: - * - platform_bus — the bus_type instance - * - platform_match — compatible-string matching via of_match_table - * - platform_dispatch — bus->probe forwarder that down-casts and calls - * platform_driver::probe(platform_device*) - * - driver_probe_all() — boot entry: register bus, register linker-set - * drivers, walk FDT, register platform_devices - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include -#include -#include -#include - -/* kmalloc is declared extern "C" in , but mm/slab.cc defines - * it WITHOUT extern "C" (i.e. C++-mangled). Declare locally with C++ - * linkage so the symbol matches what mm/slab.o exports. */ -void* kmalloc(uint32 size); -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Linker-section registry */ -/* ------------------------------------------------------------------ */ - -extern "C" { - extern platform_driver* __drivers_start[]; - extern platform_driver* __drivers_end[]; -} - -/* ------------------------------------------------------------------ */ -/* platform_bus_type */ -/* ------------------------------------------------------------------ */ - -static int platform_match(device* dev, device_driver* drv) -{ - platform_device* pdev = to_platform_device(dev); - platform_driver* pdrv = to_platform_driver(drv); - - if (pdrv->of_match_table == nullptr || pdev->of_node == nullptr) { - return 0; - } - const char* node_compat = pdev->of_node->get_prop_string("compatible"); - if (node_compat == nullptr) { - return 0; - } - for (const of_device_id* id = pdrv->of_match_table; - id->compatible != nullptr; ++id) { - if (strcmp(id->compatible, node_compat) == 0) { - return 1; - } - } - return 0; -} - -static int platform_dispatch(device* dev) -{ - platform_device* pdev = to_platform_device(dev); - platform_driver* pdrv = to_platform_driver(dev->driver); - if (pdrv->probe == nullptr) { - return 0; - } - return pdrv->probe(pdev); -} - -bus_type platform_bus = { - .name = "platform", - .match = platform_match, - .probe = platform_dispatch, - .devices = {}, - .drivers = {}, - .lock = {}, - .registered = false, -}; - -void platform_bus_init() -{ - bus_register(&platform_bus); -} - -/* ------------------------------------------------------------------ */ -/* platform_driver_register / platform_device_register */ -/* ------------------------------------------------------------------ */ - -int platform_driver_register(platform_driver* pdrv) -{ - if (pdrv == nullptr) { - return -EINVAL; - } - pdrv->driver.bus = &platform_bus; - /* driver.probe is unused — we route through bus->probe. Set name - * from the first of_match if not already provided. */ - if (pdrv->driver.name == nullptr && pdrv->of_match_table != nullptr) { - pdrv->driver.name = pdrv->of_match_table[0].compatible; - } - return driver_register(&pdrv->driver); -} - -int platform_device_register(platform_device* pdev) -{ - if (pdev == nullptr) { - return -EINVAL; - } - pdev->dev.bus = &platform_bus; - if (pdev->dev.init_name == nullptr && pdev->of_node != nullptr) { - pdev->dev.init_name = pdev->of_node->get_prop_string("compatible"); - } - device_initialize(&pdev->dev); - return device_register(&pdev->dev); -} - -/* ------------------------------------------------------------------ */ -/* FDT-driven device instantiation */ -/* ------------------------------------------------------------------ */ - -static platform_device* alloc_platform_device_from_node(DeviceNode* node, - const char* compat) -{ - auto* pdev = static_cast( - kmalloc(static_cast(sizeof(platform_device)))); - if (pdev == nullptr) { - return nullptr; - } - memset(pdev, 0, sizeof(*pdev)); - pdev->of_node = node; - pdev->mmio_base = node->get_prop_u64("reg", 0); - /* "reg" second cell is size — for now we don't bother parsing it - * separately; drivers that need it pull from of_node directly. */ - pdev->mmio_size = 0; - pdev->irq = -1; - pdev->dev.init_name = compat; - return pdev; -} - -/* For each registered platform_driver, walk its of_match_table and - * spawn a platform_device for each FDT compatible match found. - * - * NOTE: This is a simplified FDT walker — it only finds the FIRST FDT - * node matching each compatible string (delegating to - * fdt_find_compatible). Devices with multiple FDT instances of the - * same compatible will only see one bound here. A full FDT tree walk - * is left for when virtio-mmio or multi-instance UART arrives. - */ -static void instantiate_devices_for_driver(platform_driver* pdrv) -{ - if (pdrv == nullptr || pdrv->of_match_table == nullptr) { - return; - } - for (const of_device_id* id = pdrv->of_match_table; - id->compatible != nullptr; ++id) { - DeviceNode* node = fdt_find_compatible(id->compatible); - if (node == nullptr) { - continue; - } - platform_device* pdev = - alloc_platform_device_from_node(node, id->compatible); - if (pdev == nullptr) { - kprintf("[platform] OOM allocating pdev for %s\n", id->compatible); - continue; - } - kprintf("[platform] instantiating '%s' -> pdev mmio=0x%llx\n", - id->compatible, pdev->mmio_base); - platform_device_register(pdev); - } -} - -/* ------------------------------------------------------------------ */ -/* Boot entry — replaces the old driver_probe_all() */ -/* ------------------------------------------------------------------ */ - -void driver_probe_all() -{ - kprintf("[dpa] step 1: &platform_bus=%p (link 0xffffffc08033a0a8)\n", - &platform_bus); - kprintf("[dpa] step 1: pre-touch platform_bus.name slot...\n"); - const char** name_slot = &platform_bus.name; - kprintf("[dpa] step 1: name_slot=%p\n", name_slot); - kprintf("[dpa] step 1: about to deref *name_slot\n"); - const char* nm = *name_slot; - kprintf("[dpa] step 1: *name_slot=%p\n", nm); - kprintf("[dpa] step 1: about to read first byte of nm\n"); - char first = nm != nullptr ? nm[0] : '!'; - kprintf("[dpa] step 1: nm[0]='%c'\n", first); - - kprintf("[dpa] step 1: platform_bus_init enter\n"); - platform_bus_init(); - kprintf("[dpa] step 1: platform_bus_init done\n"); - - platform_driver** start = __drivers_start; - platform_driver** end = __drivers_end; - kprintf("[dpa] step 2: __drivers_start=%p __drivers_end=%p count=%lld\n", - start, end, (long long)(end - start)); - - for (platform_driver** slot = start; slot < end; ++slot) { - kprintf("[dpa] step 2: slot=%p *slot=%p\n", slot, *slot); - if (*slot != nullptr) { - platform_driver* pdrv = *slot; - kprintf("[dpa] step 2: pdrv->of_match_table=%p\n", - pdrv->of_match_table); - if (pdrv->of_match_table != nullptr) { - kprintf("[dpa] step 2: pdrv->of_match_table[0].compatible=%p\n", - pdrv->of_match_table[0].compatible); - if (pdrv->of_match_table[0].compatible != nullptr) { - kprintf("[dpa] step 2: compatible='%s'\n", - pdrv->of_match_table[0].compatible); - } - } - platform_driver_register(pdrv); - kprintf("[dpa] step 2: registered\n"); - } - } - - kprintf("[dpa] step 3: instantiate devices\n"); - for (platform_driver** slot = start; slot < end; ++slot) { - if (*slot != nullptr) { - instantiate_devices_for_driver(*slot); - } - } - kprintf("[dpa] done\n"); -} diff --git a/na/drivers/console.cc b/na/drivers/console.cc deleted file mode 100644 index f741dd2..0000000 --- a/na/drivers/console.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * console.cc — Console character device (/dev/console) - * - * Wraps the existing early_putc() UART driver as a file_operations - * chardev. read() is a stub for now (returns 0 = EOF). - * write() sends each byte through early_putc(). - */ - -#include -#include -#include - -extern "C" void early_putc(char c); - -/* ================================================================== */ -/* console_write */ -/* ================================================================== */ - -static int64 console_write(file* fp, const char* buf, uint64 count, int64* pos) -{ - (void)fp; - (void)pos; - - for (uint64 i = 0; i < count; i++) { - early_putc(buf[i]); - } - - return static_cast(count); -} - -/* ================================================================== */ -/* console_read — stub (returns 0 = EOF for now) */ -/* ================================================================== */ - -static int64 console_read(file* fp, char* buf, uint64 count, int64* pos) -{ - (void)fp; - (void)buf; - (void)count; - (void)pos; - /* TODO: implement UART RX polling/IRQ-based blocking read */ - return 0; -} - -/* ================================================================== */ -/* null device — /dev/null */ -/* ================================================================== */ - -static int64 null_read(file* /*fp*/, char* /*buf*/, uint64 /*count*/, int64* /*pos*/) -{ - return 0; /* EOF */ -} - -static int64 null_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/) -{ - return static_cast(count); /* discard */ -} - -/* ================================================================== */ -/* zero device — /dev/zero */ -/* ================================================================== */ - -static int64 zero_read(file* /*fp*/, char* buf, uint64 count, int64* /*pos*/) -{ - for (uint64 i = 0; i < count; i++) { - buf[i] = 0; - } - return static_cast(count); -} - -static int64 zero_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/) -{ - return static_cast(count); /* discard */ -} - -/* ================================================================== */ -/* Registration — runtime init (no static initializers!) */ -/* ================================================================== */ - -static file_operations console_fops; -static file_operations null_fops; -static file_operations zero_fops; - -static cdev console_cdev; -static cdev null_cdev; -static cdev zero_cdev; - -void console_cdev_init() -{ - /* Console: major=5, minor=1 */ - console_fops.read = console_read; - console_fops.write = console_write; - - console_cdev.name = "console"; - console_cdev.dev = MKDEV(5, 1); - console_cdev.fops = &console_fops; - cdev_register(&console_cdev); - - /* /dev/null: major=1, minor=3 */ - null_fops.read = null_read; - null_fops.write = null_write; - - null_cdev.name = "null"; - null_cdev.dev = MKDEV(1, 3); - null_cdev.fops = &null_fops; - cdev_register(&null_cdev); - - /* /dev/zero: major=1, minor=5 */ - zero_fops.read = zero_read; - zero_fops.write = zero_write; - - zero_cdev.name = "zero"; - zero_cdev.dev = MKDEV(1, 5); - zero_cdev.fops = &zero_fops; - cdev_register(&zero_cdev); -} diff --git a/na/drivers/module.mk b/na/drivers/module.mk deleted file mode 100644 index 9326f30..0000000 --- a/na/drivers/module.mk +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Drivers module - -SRCS_CC += drivers/uart.cc -SRCS_CC += drivers/console.cc - -ifeq ($(ARCH),riscv) -SRCS_CC += drivers/plic.cc -endif diff --git a/na/drivers/plic.cc b/na/drivers/plic.cc deleted file mode 100644 index 2818700..0000000 --- a/na/drivers/plic.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * plic.cc — Platform-Level Interrupt Controller (RISC-V) - * - * QEMU virt platform PLIC: - * Base PA: 0x0C000000 - * Size: 0x04000000 (64MB) - * - * Register layout (per RISC-V PLIC spec): - * 0x000000: Priority registers (source 0..1023, 4 bytes each) - * 0x001000: Pending bits (source 0..1023, bit per source) - * 0x002000: Enable bits per context - * 0x200000: Threshold + claim/complete per context - * - * Context mapping for QEMU virt: - * Context 2*hartid + 1 = S-mode context for hart N - * - * Reference: docs/specs/trap.md §5.1 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* PLIC virtual base (after MMIO mapping) */ -static uint64 plic_va; - -extern "C" uint32 bsp_hart_id; - -/* PLIC register offsets */ -static constexpr uint64 PLIC_PRIORITY_BASE = 0x000000; -static constexpr uint64 PLIC_PENDING_BASE = 0x001000; -static constexpr uint64 PLIC_ENABLE_BASE = 0x002000; -static constexpr uint64 PLIC_THRESHOLD_BASE = 0x200000; -static constexpr uint64 PLIC_CLAIM_BASE = 0x200004; - -/* Each context's enable/threshold/claim region is 0x80 bytes apart for enable, - * and 0x1000 bytes apart for threshold/claim. */ -static constexpr uint64 PLIC_ENABLE_STRIDE = 0x80; -static constexpr uint64 PLIC_CONTEXT_STRIDE = 0x1000; - -/* Dynamic S-mode context mapping */ -static uint32 g_s_context[kMaxCpus]; - -static inline uint64 s_context(uint64 hart_id) { - if (hart_id < kMaxCpus) - return g_s_context[hart_id]; - return 2 * hart_id + 1; /* safe fallback */ -} - -static bool str_equals_plic(const char *a, const char *b) { - if (!a || !b) - return false; - while (*a && *a == *b) { - a++; - b++; - } - return *a == *b; -} - -/* Read a 32-bit MMIO register */ -static inline uint32 plic_read32(uint64 offset) { - return *reinterpret_cast(plic_va + offset); -} - -/* Write a 32-bit MMIO register */ -static inline void plic_write32(uint64 offset, uint32 val) { - *reinterpret_cast(plic_va + offset) = val; -} - -/* ======================================================================== - * Public API - * ======================================================================== */ - -static void plic_probe(DeviceNode* node) { - uint64 plic_pa = 0; - - /* Initialize default fallback mapping */ - for (uint32 i = 0; i < kMaxCpus; i++) { - g_s_context[i] = 2 * i + 1; - } - - /* Dynamically discover PLIC base address and context mapping from FDT */ - if (node) { - plic_pa = node->get_prop_u64("reg", plic_pa); - - /* Build phandle to hart_id map */ - struct PhandleMap { - uint32 phandle; - uint32 hart_id; - } pmap[kMaxCpus]; - int num_cpus = 0; - - DeviceNode *cpus_node = fdt_get_node_by_path("/cpus"); - if (cpus_node) { - list_node *cur = cpus_node->children_list_head(); - list_node *end = cpus_node->children_list_sentinel(); - while (cur != end && num_cpus < kMaxCpus) { - DeviceNode *cpu = - list_entry(cur); - cur = cur->next; - - const char *type = cpu->get_prop_string("device_type"); - if (type && str_equals_plic(type, "cpu")) { - uint32 hart_id = cpu->get_prop_u32("reg"); - DeviceNode *intc = cpu->find_child("interrupt-controller"); - if (intc) { - uint32 phandle = intc->get_prop_u32("phandle"); - if (phandle == 0) - phandle = intc->get_prop_u32("linux,phandle"); - if (phandle != 0) { - pmap[num_cpus].phandle = phandle; - pmap[num_cpus].hart_id = hart_id; - num_cpus++; - } - } - } - } - } - - /* Parse interrupts-extended */ - const FdtProperty *ie_prop = node->get_prop("interrupts-extended"); - if (ie_prop && ie_prop->len > 0) { - uint32 len = ie_prop->len; - const uint8 *val = static_cast(ie_prop->value); - int context_idx = 0; - /* Each entry is 2 cells (8 bytes): [phandle, interrupt_type] */ - for (uint32 i = 0; i + 7 < len; i += 8) { - uint32 phandle = (uint32(val[i]) << 24) | (uint32(val[i + 1]) << 16) | - (uint32(val[i + 2]) << 8) | val[i + 3]; - uint32 irq_type = (uint32(val[i + 4]) << 24) | - (uint32(val[i + 5]) << 16) | - (uint32(val[i + 6]) << 8) | val[i + 7]; - - if (irq_type == 9) { /* IRQ_S_EXTERNAL */ - for (int h = 0; h < num_cpus; h++) { - if (pmap[h].phandle == phandle) { - if (pmap[h].hart_id < kMaxCpus) { - g_s_context[pmap[h].hart_id] = context_idx; - } - break; - } - } - } - context_idx++; - } - } - } - - if (plic_pa == 0) return; - - /* Map PLIC MMIO */ - vmm_map_kernel_mmio(plic_pa, 0x4000000); - plic_va = pa_to_va(phys_addr(plic_pa)).raw; - - /* Use the actual BSP hart ID (it may not be hart 0) */ - uint64 ctx = s_context(bsp_hart_id); - - /* Set priority for all sources to 1 (minimum non-zero = enabled) */ - for (uint32 irq = 1; irq < IrqManager::kMaxIrqs; irq++) { - plic_write32(PLIC_PRIORITY_BASE + irq * 4, 1); - } - - /* Set threshold to 0 for BSP context (accept all priorities > 0) */ - plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); -} - -static void plic_init_ap() { - uint64 ctx = s_context(arch::cpu::id()); - - /* Set threshold to 0 for this AP's context */ - plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); -} - -static void plic_enable(uint32 irq) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) { - return; - } - - uint64 ctx = s_context(arch::cpu::id()); - uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; - uint32 word_index = irq / 32; - uint32 bit_index = irq % 32; - - uint32 val = plic_read32(offset + word_index * 4); - val |= (1U << bit_index); - plic_write32(offset + word_index * 4, val); -} - -static void plic_disable(uint32 irq) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) { - return; - } - - uint64 ctx = s_context(arch::cpu::id()); - uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; - uint32 word_index = irq / 32; - uint32 bit_index = irq % 32; - - uint32 val = plic_read32(offset + word_index * 4); - val &= ~(1U << bit_index); - plic_write32(offset + word_index * 4, val); -} - -static void plic_set_priority(uint32 irq, uint32 priority) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) - return; - plic_write32(PLIC_PRIORITY_BASE + (irq * 4), priority); -} - -static uint32 plic_claim() { - uint64 ctx = s_context(arch::cpu::id()); - return plic_read32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE); -} - -static void plic_complete(uint32 irq) { - uint64 ctx = s_context(arch::cpu::id()); - plic_write32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE, irq); -} - -static const IrqChip plic_chip = { - "riscv,plic0", - plic_claim, - plic_complete, - plic_init_ap, - plic_enable, - plic_disable, - plic_set_priority -}; - -static int plic_platform_probe(platform_device* pdev) { - plic_probe(pdev->of_node); - g_irq_manager.set_chip(&plic_chip); - return 0; -} - -static const of_device_id plic_of_match[] = { - { .compatible = "riscv,plic0", .data = nullptr }, - { .compatible = nullptr, .data = nullptr }, /* sentinel */ -}; - -platform_driver plic_driver = { - .driver = { .name = "plic", .bus = nullptr, - .probe = nullptr, .remove = nullptr, - .bus_node = {}, .devices = {} }, - .probe = plic_platform_probe, - .remove = nullptr, - .of_match_table = plic_of_match, -}; - -PLATFORM_DRIVER(plic_driver); diff --git a/na/drivers/tty/module.mk b/na/drivers/tty/module.mk deleted file mode 100644 index 49190d4..0000000 --- a/na/drivers/tty/module.mk +++ /dev/null @@ -1,5 +0,0 @@ -# drivers/tty/module.mk — TTY subsystem build rules - -SRCS_CC += drivers/tty/tty_core.cc -SRCS_CC += drivers/tty/n_tty.cc -SRCS_CC += drivers/tty/tty_uart.cc diff --git a/na/drivers/tty/n_tty.cc b/na/drivers/tty/n_tty.cc deleted file mode 100644 index 0e312b0..0000000 --- a/na/drivers/tty/n_tty.cc +++ /dev/null @@ -1,539 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * n_tty.cc — N_TTY line discipline - * - * Implements the standard terminal line discipline: - * - * Canonical mode (ICANON): - * Input is buffered until newline/EOF. Backspace (VERASE) and - * kill-line (VKILL) editing are supported. - * - * Raw mode (!ICANON): - * Characters are available immediately (subject to VMIN/VTIME). - * - * Signal generation (ISIG): - * VINTR → SIGINT, VQUIT → SIGQUIT, VSUSP → SIGTSTP - * - * Echo (ECHO): - * Typed characters are reflected to output. - * - * Input processing (c_iflag): - * ICRNL: CR → NL mapping - * INLCR: NL → CR mapping - * IGNCR: ignore CR - * ISTRIP: strip high bit - * - * Output processing (c_oflag): - * OPOST + ONLCR: NL → CR+NL on output - * - * Reference: Linux drivers/tty/n_tty.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef ENOIOCTLCMD -#define ENOIOCTLCMD 515 -#endif - -extern void schedule(); - -/* ================================================================== */ -/* N_TTY internal data */ -/* ================================================================== */ - -inline constexpr int N_TTY_BUF_SIZE = 4096; - -struct n_tty_data { - char read_buf[N_TTY_BUF_SIZE]; - int read_head; /* producer writes here (from receive_buf) */ - int read_tail; /* consumer reads from here (from read()) */ - int canon_head; /* in canonical mode: end of last complete line */ - bool eof_pending; /* Ctrl+D was pressed */ - Spinlock lock; -}; - -static inline int buf_count(const n_tty_data* d) -{ - return (d->read_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; -} - -static inline int canon_count(const n_tty_data* d) -{ - return (d->canon_head - d->read_tail + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; -} - -static inline bool buf_full(const n_tty_data* d) -{ - return buf_count(d) >= N_TTY_BUF_SIZE - 1; -} - -static inline void buf_put(n_tty_data* d, char c) -{ - d->read_buf[d->read_head] = c; - d->read_head = (d->read_head + 1) % N_TTY_BUF_SIZE; -} - -static inline char buf_get(n_tty_data* d) -{ - char c = d->read_buf[d->read_tail]; - d->read_tail = (d->read_tail + 1) % N_TTY_BUF_SIZE; - return c; -} - -/* ================================================================== */ -/* Output helpers */ -/* ================================================================== */ - -static void tty_put_char(tty_struct* tty, char c) -{ - if (tty->driver && tty->driver->ops && tty->driver->ops->put_char) { - tty->driver->ops->put_char(tty, c); - } -} - -/* - * Process and output a single character, applying c_oflag transformations. - */ -static void tty_output_char(tty_struct* tty, char c) -{ - if (tty->termios_data.c_oflag & OPOST) { - if (c == '\n' && (tty->termios_data.c_oflag & ONLCR)) { - tty_put_char(tty, '\r'); - tty_put_char(tty, '\n'); - return; - } - if (c == '\r' && (tty->termios_data.c_oflag & OCRNL)) { - tty_put_char(tty, '\n'); - return; - } - } - tty_put_char(tty, c); -} - -/* - * Echo a character back to the terminal. - * Control characters are displayed as ^X. - */ -static void echo_char(tty_struct* tty, char c) -{ - if (!(tty->termios_data.c_lflag & ECHO)) - return; - - if (c == '\n') { - tty_output_char(tty, '\n'); - return; - } - - if (c == '\t') { - tty_put_char(tty, '\t'); - return; - } - - /* Control characters: echo as ^X */ - if ((tty->termios_data.c_lflag & ECHOCTL) && c < 0x20 && c != '\t' && c != '\n') { - tty_put_char(tty, '^'); - tty_put_char(tty, c + '@'); - return; - } - - if (c == 0x7f) { /* DEL */ - tty_put_char(tty, '^'); - tty_put_char(tty, '?'); - return; - } - - tty_output_char(tty, c); -} - -/* - * Erase one character visually (backspace + space + backspace). - */ -static void erase_char(tty_struct* tty, char erased) -{ - if (!(tty->termios_data.c_lflag & ECHO)) - return; - - if ((tty->termios_data.c_lflag & ECHOE)) { - /* If the erased char was a control char displayed as ^X, erase 2 cols */ - if ((erased < 0x20 && erased != '\t' && erased != '\n') || erased == 0x7f) { - tty_put_char(tty, '\b'); - tty_put_char(tty, ' '); - tty_put_char(tty, '\b'); - tty_put_char(tty, '\b'); - tty_put_char(tty, ' '); - tty_put_char(tty, '\b'); - } else { - tty_put_char(tty, '\b'); - tty_put_char(tty, ' '); - tty_put_char(tty, '\b'); - } - } -} - -/* ================================================================== */ -/* Input processing — receive_buf (called from IRQ or driver) */ -/* ================================================================== */ - -static void n_tty_receive_buf(tty_struct* tty, const char* cp, int count) -{ - auto* d = static_cast(tty->ldisc.data); - if (!d) return; - - const termios& t = tty->termios_data; - - LockGuard guard(d->lock); - - for (int i = 0; i < count; i++) { - char c = cp[i]; - - /* Strip high bit if requested */ - if (t.c_iflag & ISTRIP) - c &= 0x7f; - - /* Input CR/NL processing */ - if (c == '\r') { - if (t.c_iflag & IGNCR) - continue; - if (t.c_iflag & ICRNL) - c = '\n'; - } else if (c == '\n') { - if (t.c_iflag & INLCR) - c = '\r'; - } - - /* Signal generation */ - if (t.c_lflag & ISIG) { - if (c == t.c_cc[VINTR]) { - tty_signal_pgrp(tty, SIGINT); - echo_char(tty, c); - if (!(t.c_lflag & NOFLSH)) { - /* Flush input buffer */ - d->read_head = d->read_tail; - d->canon_head = d->read_tail; - } - continue; - } - if (c == t.c_cc[VQUIT]) { - tty_signal_pgrp(tty, SIGQUIT); - echo_char(tty, c); - if (!(t.c_lflag & NOFLSH)) { - d->read_head = d->read_tail; - d->canon_head = d->read_tail; - } - continue; - } - if (c == t.c_cc[VSUSP]) { - tty_signal_pgrp(tty, SIGTSTP); - echo_char(tty, c); - continue; - } - } - - /* Canonical mode processing */ - if (t.c_lflag & ICANON) { - /* EOF (Ctrl+D) */ - if (c == t.c_cc[VEOF]) { - d->eof_pending = true; - d->canon_head = d->read_head; - tty->read_wait.wake_all(); - continue; - } - - /* ERASE (backspace/DEL) */ - if (c == t.c_cc[VERASE]) { - if (d->read_head != d->canon_head && - d->read_head != d->read_tail) { - int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; - char erased = d->read_buf[prev]; - d->read_head = prev; - erase_char(tty, erased); - } - continue; - } - - /* KILL (Ctrl+U — erase entire line) */ - if (c == t.c_cc[VKILL]) { - while (d->read_head != d->canon_head && - d->read_head != d->read_tail) { - int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; - char erased = d->read_buf[prev]; - d->read_head = prev; - erase_char(tty, erased); - } - if (t.c_lflag & ECHOK) { - tty_output_char(tty, '\n'); - } - continue; - } - - /* WERASE (Ctrl+W — erase word) */ - if ((t.c_lflag & IEXTEN) && c == t.c_cc[VWERASE]) { - /* Skip trailing spaces */ - while (d->read_head != d->canon_head) { - int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; - if (d->read_buf[prev] != ' ' && d->read_buf[prev] != '\t') - break; - erase_char(tty, d->read_buf[prev]); - d->read_head = prev; - } - /* Erase word */ - while (d->read_head != d->canon_head) { - int prev = (d->read_head - 1 + N_TTY_BUF_SIZE) % N_TTY_BUF_SIZE; - if (d->read_buf[prev] == ' ' || d->read_buf[prev] == '\t') - break; - erase_char(tty, d->read_buf[prev]); - d->read_head = prev; - } - continue; - } - - /* REPRINT (Ctrl+R) */ - if ((t.c_lflag & IEXTEN) && c == t.c_cc[VREPRINT]) { - tty_output_char(tty, '\n'); - int pos = d->canon_head; - while (pos != d->read_head) { - echo_char(tty, d->read_buf[pos]); - pos = (pos + 1) % N_TTY_BUF_SIZE; - } - continue; - } - - /* Regular character — buffer it */ - if (!buf_full(d)) { - buf_put(d, c); - echo_char(tty, c); - } - - /* Line complete on newline or EOL */ - if (c == '\n' || c == t.c_cc[VEOL] || - (t.c_cc[VEOL2] && c == t.c_cc[VEOL2])) { - d->canon_head = d->read_head; - tty->read_wait.wake_all(); - } - } else { - /* Raw / non-canonical mode */ - if (!buf_full(d)) { - buf_put(d, c); - echo_char(tty, c); - tty->read_wait.wake_all(); - } - } - } -} - -/* ================================================================== */ -/* N_TTY read — blocking read from the input buffer */ -/* ================================================================== */ - -static int n_tty_read(tty_struct* tty, file* fp, char* buf, uint64 count) -{ - auto* d = static_cast(tty->ldisc.data); - if (!d) return -EIO; - - const termios& t = tty->termios_data; - TaskControlBlock* curr = current_task(); - int copied = 0; - - if (t.c_lflag & ICANON) { - /* Canonical mode: block until a complete line is available */ - while (canon_count(d) == 0 && !d->eof_pending) { - if (tty->flags & TTY_IO_ERROR) - return -EIO; - - /* Check for pending signals — return -EINTR so the - * trap return path can deliver them via do_signal() */ - if (signal_pending(curr)) - return -EINTR; - - tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping); - if (canon_count(d) == 0 && !d->eof_pending) - schedule(); - tty->read_wait.finish_wait(curr); - } - - if (d->eof_pending && canon_count(d) == 0) { - d->eof_pending = false; - return 0; /* EOF */ - } - - /* Copy up to one line */ - LockGuard guard(d->lock); - while (copied < static_cast(count) && d->read_tail != d->canon_head) { - buf[copied++] = buf_get(d); - } - } else { - /* Non-canonical mode */ - int vmin = t.c_cc[VMIN]; - /* VTIME not implemented (would need timers) */ - - if (vmin == 0) vmin = 1; - - while (buf_count(d) < vmin) { - if (tty->flags & TTY_IO_ERROR) - return -EIO; - - if (signal_pending(curr)) - return -EINTR; - - tty->read_wait.prepare_to_wait(curr, TaskState::Sleeping); - if (buf_count(d) < vmin) - schedule(); - tty->read_wait.finish_wait(curr); - } - - LockGuard guard(d->lock); - while (copied < static_cast(count) && buf_count(d) > 0) { - buf[copied++] = buf_get(d); - } - } - - return copied; -} - -/* ================================================================== */ -/* N_TTY write — output through the driver with post-processing */ -/* ================================================================== */ - -static int n_tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count) -{ - if (!tty->driver || !tty->driver->ops || !tty->driver->ops->write) - return -EIO; - - /* Apply output processing (OPOST) */ - if (tty->termios_data.c_oflag & OPOST) { - for (uint64 i = 0; i < count; i++) { - tty_output_char(tty, buf[i]); - } - return static_cast(count); - } - - /* Raw output: pass directly to driver */ - return tty->driver->ops->write(tty, buf, static_cast(count)); -} - -/* ================================================================== */ -/* N_TTY ioctl */ -/* ================================================================== */ - -static int n_tty_ioctl(tty_struct* tty, unsigned int cmd, uint64 arg) -{ - auto* d = static_cast(tty->ldisc.data); - - switch (cmd) { - case FIONREAD: { - int count = buf_count(d); - auto* p = reinterpret_cast(arg); - *p = count; - return 0; - } - default: - return -ENOIOCTLCMD; - } -} - -/* ================================================================== */ -/* N_TTY open / close */ -/* ================================================================== */ - -static int n_tty_open(tty_struct* tty) -{ - auto* d = new n_tty_data{}; - if (!d) return -ENOMEM; - - d->read_head = 0; - d->read_tail = 0; - d->canon_head = 0; - d->eof_pending = false; - d->lock.init("n_tty"); - - tty->ldisc.data = d; - return 0; -} - -static void n_tty_close(tty_struct* tty) -{ - auto* d = static_cast(tty->ldisc.data); - if (d) { - delete d; - tty->ldisc.data = nullptr; - } -} - -static void n_tty_flush_buffer(tty_struct* tty) -{ - auto* d = static_cast(tty->ldisc.data); - if (!d) return; - LockGuard guard(d->lock); - d->read_head = 0; - d->read_tail = 0; - d->canon_head = 0; - d->eof_pending = false; -} - -static int n_tty_chars_in_buffer(tty_struct* tty) -{ - auto* d = static_cast(tty->ldisc.data); - if (!d) return 0; - return buf_count(d); -} - -/* - * Probe input readiness for poll(). In canonical mode the read syscall - * blocks until a whole line is queued, so we mirror that: POLLIN only - * when canon_count() shows a finalized line (or pending EOF). In raw - * mode any character satisfies VMIN==1 immediately. Without this - * mirror, ppoll() would falsely signal POLLIN on partial canonical - * input, then read() would block — exactly the busy-loop the shell's - * ppoll-then-read pattern is trying to avoid. - */ -bool n_tty_input_ready(tty_struct* tty) -{ - auto* d = static_cast(tty->ldisc.data); - if (!d) return false; - const termios& t = tty->termios_data; - if (t.c_lflag & ICANON) - return canon_count(d) != 0 || d->eof_pending; - int vmin = t.c_cc[VMIN]; - if (vmin == 0) vmin = 1; - return buf_count(d) >= vmin; -} - -static void n_tty_set_termios(tty_struct* /*tty*/, const termios* /*old*/) -{ - /* Nothing special needed for now */ -} - -static void n_tty_hangup(tty_struct* tty) -{ - n_tty_flush_buffer(tty); - tty->read_wait.wake_all(); -} - -/* ================================================================== */ -/* N_TTY ops table */ -/* ================================================================== */ - -const tty_ldisc_ops n_tty_ops = { - .name = "n_tty", - .num = 0, /* N_TTY */ - .open = n_tty_open, - .close = n_tty_close, - .read = n_tty_read, - .write = n_tty_write, - .receive_buf = n_tty_receive_buf, - .ioctl = n_tty_ioctl, - .flush_buffer = n_tty_flush_buffer, - .chars_in_buffer = n_tty_chars_in_buffer, - .set_termios = n_tty_set_termios, - .hangup = n_tty_hangup, -}; diff --git a/na/drivers/tty/tty_core.cc b/na/drivers/tty/tty_core.cc deleted file mode 100644 index 5985bd9..0000000 --- a/na/drivers/tty/tty_core.cc +++ /dev/null @@ -1,536 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tty_core.cc — TTY subsystem core - * - * Provides the tty_struct lifecycle, file_operations dispatch, - * and ioctl routing between the line discipline and driver. - * - * Call chain: - * sys_read → tty_fops.read → tty_read → ldisc.read - * sys_write → tty_fops.write → tty_write → ldisc.write - * sys_ioctl → tty_fops.ioctl → tty_ioctl → {core / ldisc / driver} - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef ENOIOCTLCMD -#define ENOIOCTLCMD 515 -#endif - -/* ================================================================== */ -/* Global console TTY */ -/* ================================================================== */ - -tty_struct* g_console_tty = nullptr; - -/* ================================================================== */ -/* tty_struct allocation */ -/* ================================================================== */ - -tty_struct* tty_alloc(const tty_driver* driver, int index) -{ - auto* tty = new tty_struct{}; - if (!tty) return nullptr; - - tty->index = index; - tty->driver = driver; - tty->link = nullptr; - tty->pgrp = 0; - tty->session = 0; - tty->write_cnt = 0; - tty->flags = 0; - tty->open_count = 0; - tty->driver_data = nullptr; - - tty_default_termios(&tty->termios_data); - - tty->ws.ws_row = 24; - tty->ws.ws_col = 80; - tty->ws.ws_xpixel = 0; - tty->ws.ws_ypixel = 0; - - tty->lock.init("tty"); - tty->read_wait.init("tty_rd"); - tty->write_wait.init("tty_wr"); - - /* Attach default line discipline (N_TTY) */ - tty->ldisc.ops = &n_tty_ops; - tty->ldisc.data = nullptr; - - return tty; -} - -/* ================================================================== */ -/* tty_open / tty_close */ -/* ================================================================== */ - -int tty_open(tty_struct* tty) -{ - if (!tty) return -EINVAL; - - if (tty->open_count == 0) { - /* Initialize line discipline */ - if (tty->ldisc.ops && tty->ldisc.ops->open) { - int ret = tty->ldisc.ops->open(tty); - if (ret < 0) return ret; - } - - /* Initialize driver */ - if (tty->driver && tty->driver->ops && tty->driver->ops->open) { - int ret = tty->driver->ops->open(tty); - if (ret < 0) { - if (tty->ldisc.ops && tty->ldisc.ops->close) - tty->ldisc.ops->close(tty); - return ret; - } - } - } - - tty->open_count++; - return 0; -} - -void tty_close(tty_struct* tty) -{ - if (!tty) return; - - tty->open_count--; - if (tty->open_count <= 0) { - tty->open_count = 0; - - if (tty->driver && tty->driver->ops && tty->driver->ops->close) - tty->driver->ops->close(tty); - - if (tty->ldisc.ops && tty->ldisc.ops->close) - tty->ldisc.ops->close(tty); - } -} - -/* ================================================================== */ -/* Core I/O */ -/* ================================================================== */ - -int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count) -{ - if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->read) - return -EIO; - - return tty->ldisc.ops->read(tty, fp, buf, count); -} - -int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count) -{ - if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->write) - return -EIO; - - return tty->ldisc.ops->write(tty, fp, buf, count); -} - -/* ================================================================== */ -/* tty_receive_buf — called by driver on RX */ -/* ================================================================== */ - -void tty_receive_buf(tty_struct* tty, const char* buf, int count) -{ - if (!tty || !tty->ldisc.ops || !tty->ldisc.ops->receive_buf) - return; - - tty->ldisc.ops->receive_buf(tty, buf, count); -} - -/* ================================================================== */ -/* tty_signal_pgrp — send signal to foreground process group */ -/* ================================================================== */ - -extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); - -void tty_signal_pgrp(tty_struct* tty, int sig) -{ - if (!tty) return; - - int32 pgid; - { - LockGuard guard(tty->lock); - pgid = tty->pgrp; - } - if (pgid <= 0) return; - - /* Deliver to the leader of every thread group in the foreground - * process group. ldisc paths call this for SIGINT/SIGQUIT/SIGWINCH - * etc.; previously we only signalled the current task, which was - * incorrect when the fg pgrp had multiple processes. */ - kernel_siginfo info{}; - info.si_signo = sig; - info.si_code = 0; /* SI_KERNEL would be 0x80; user rarely checks */ - for_each_pgrp_member(pgid, [&](signal_struct* s) { - if (s->leader) - send_signal(sig, &info, s->leader); - }); -} - -/* ================================================================== */ -/* tty_ioctl — ioctl dispatch */ -/* ================================================================== */ - -long tty_ioctl(tty_struct* tty, file* /*fp*/, unsigned int cmd, uint64 arg) -{ - if (!tty) return -EINVAL; - - switch (cmd) { - case TCGETS: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - *p = tty->termios_data; - return 0; - } - - case TCSETS: - case TCSETSW: - case TCSETSF: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - termios old = tty->termios_data; - tty->termios_data = *p; - - /* Notify line discipline of termios change */ - if (tty->ldisc.ops && tty->ldisc.ops->set_termios) - tty->ldisc.ops->set_termios(tty, &old); - - /* If TCSETSF, flush input buffer */ - if (cmd == TCSETSF) { - if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer) - tty->ldisc.ops->flush_buffer(tty); - } - return 0; - } - - case TIOCGWINSZ: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - *p = tty->ws; - return 0; - } - - case TIOCSWINSZ: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - winsize old = tty->ws; - tty->ws = *p; - if (old.ws_row != tty->ws.ws_row || old.ws_col != tty->ws.ws_col) { - tty_signal_pgrp(tty, SIGWINCH); - } - return 0; - } - - case TIOCGPGRP: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ENOTTY; - if (curr->signal->tty != tty) return -ENOTTY; - - int32 pgid; - { - LockGuard guard(tty->lock); - pgid = tty->pgrp != 0 ? tty->pgrp : curr->signal->pgid; - } - *p = pgid; - return 0; - } - - case TIOCSPGRP: { - auto* p = reinterpret_cast(arg); - if (!p) return -EINVAL; - - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ENOTTY; - if (curr->signal->tty != tty) return -ENOTTY; - - int32 new_pgid = *p; - if (new_pgid <= 0) return -EINVAL; - - /* new_pgid must be in the caller's session. */ - bool ok = false; - for_each_pgrp_member(new_pgid, [&](signal_struct* s) { - if (s->sid == curr->signal->sid) ok = true; - }); - if (!ok) return -EPERM; - - LockGuard guard(tty->lock); - tty->pgrp = new_pgid; - return 0; - } - - case TIOCSCTTY: { - /* Become the controlling terminal of the caller's session. - * Linux: only session leader without ctty may do this; with - * arg==1, CAP_SYS_ADMIN may steal a ctty from another session. - * We support the basic path; "steal" is left unimplemented. */ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -EPERM; - signal_struct* sig = curr->signal; - - if (sig->sid != curr->pid) return -EPERM; /* not leader */ - - LockGuard sg(sig->lock); - if (sig->tty != nullptr) return -EPERM; /* already have one */ - - LockGuard tg(tty->lock); - if (tty->session != 0 && tty->session != sig->sid) { - return -EPERM; /* owned elsewhere */ - } - tty->session = sig->sid; - tty->pgrp = sig->pgid; - sig->tty = tty; - return 0; - } - - case TIOCNOTTY: { - /* Detach from controlling terminal. */ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ENOTTY; - signal_struct* sig = curr->signal; - if (sig->tty != tty) return -ENOTTY; - - /* Session leader: HUP everyone left in the old fg pgrp. - * Job-control conformance — without this, daemons spawned by - * a relinquishing leader keep blocking on a dead terminal. */ - bool was_leader = (sig->sid == curr->pid); - int32 hup_pgrp = 0; - { - LockGuard tg(tty->lock); - hup_pgrp = tty->pgrp; - if (was_leader) { - tty->session = 0; - tty->pgrp = 0; - } - } - { - LockGuard sg(sig->lock); - sig->tty = nullptr; - } - if (was_leader && hup_pgrp > 0) { - tty_signal_pgrp(tty, SIGHUP); - tty_signal_pgrp(tty, SIGCONT); - (void)hup_pgrp; - } - return 0; - } - - case FIONREAD: { - /* Delegate to line discipline */ - if (tty->ldisc.ops && tty->ldisc.ops->ioctl) { - int ret = tty->ldisc.ops->ioctl(tty, cmd, arg); - if (ret != -ENOIOCTLCMD) - return ret; - } - return 0; - } - - case TCFLSH: { - int queue = static_cast(arg); - if (queue == 0 || queue == 2) { - /* Flush input */ - if (tty->ldisc.ops && tty->ldisc.ops->flush_buffer) - tty->ldisc.ops->flush_buffer(tty); - } - if (queue == 1 || queue == 2) { - /* Flush output */ - if (tty->driver && tty->driver->ops && tty->driver->ops->flush_buffer) - tty->driver->ops->flush_buffer(tty); - } - return 0; - } - - default: - break; - } - - /* Try line discipline ioctl */ - if (tty->ldisc.ops && tty->ldisc.ops->ioctl) { - int ret = tty->ldisc.ops->ioctl(tty, cmd, arg); - if (ret != -ENOIOCTLCMD) - return ret; - } - - /* Try driver ioctl */ - if (tty->driver && tty->driver->ops && tty->driver->ops->ioctl) { - int ret = tty->driver->ops->ioctl(tty, cmd, arg); - if (ret != -ENOIOCTLCMD) - return ret; - } - - return -ENOTTY; -} - -/* ================================================================== */ -/* file_operations for TTY devices */ -/* ================================================================== */ - -static int64 tty_fops_read(file* fp, char* buf, uint64 count, int64* /*pos*/) -{ - auto* tty = static_cast(fp->private_data); - return tty_read(tty, fp, buf, count); -} - -static int64 tty_fops_write(file* fp, const char* buf, uint64 count, int64* /*pos*/) -{ - auto* tty = static_cast(fp->private_data); - return tty_write(tty, fp, buf, count); -} - -static long tty_fops_ioctl(file* fp, unsigned int cmd, uint64 arg) -{ - auto* tty = static_cast(fp->private_data); - return tty_ioctl(tty, fp, cmd, arg); -} - -/* - * tty poll callback — Linux drivers/tty/tty_io.c:tty_poll() does the - * same thing: register tty->read_wait so the poller is woken on input, - * then ask the ldisc whether data is currently available. Write side - * is always considered ready since our writes don't block. - */ -extern bool n_tty_input_ready(tty_struct* tty); -extern void poll_wait(file*, WaitQueue*, poll_table*); - -static uint32 tty_fops_poll(file* fp, void* pt) -{ - auto* tty = static_cast(fp->private_data); - if (!tty) return POLLNVAL; - - poll_wait(fp, &tty->read_wait, static_cast(pt)); - - uint32 mask = POLLOUT | POLLWRNORM; /* always writable */ - if (n_tty_input_ready(tty)) - mask |= POLLIN | POLLRDNORM; - return mask; -} - -/* - * Auto-acquire a controlling terminal on open. - * - * POSIX rule: when a session leader without a controlling tty opens a - * terminal that itself has no controlling session, and the caller did - * not pass O_NOCTTY, that terminal becomes the controlling terminal of - * the caller's session. tty->session and tty->pgrp are seeded from the - * caller's sid/pgid so that the very first tcgetpgrp() call returns a - * value matching the caller's pgid — which is precisely what BusyBox - * ash:setjobctl() needs to exit its setup loop. - * - * Reference: Linux drivers/tty/tty_jobctrl.c:tty_open_proc_set_tty() - */ -static void tty_acquire_ctty(tty_struct* tty, file* fp) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return; - if (fp->f_flags & O_NOCTTY) return; - - signal_struct* sig = curr->signal; - - /* Must be session leader (sid == pid). */ - if (sig->sid != curr->pid) return; - - LockGuard sig_guard(sig->lock); - if (sig->tty != nullptr) return; /* already have one */ - - LockGuard tty_guard(tty->lock); - if (tty->session != 0) return; /* tty owned by another sess */ - - tty->session = sig->sid; - tty->pgrp = sig->pgid; - sig->tty = tty; -} - -static int tty_fops_open(inode* /*ip*/, file* fp) -{ - /* The console TTY is pre-allocated; just attach it */ - if (!g_console_tty) return -ENXIO; - fp->private_data = g_console_tty; - int err = tty_open(g_console_tty); - if (err == 0) { - tty_acquire_ctty(g_console_tty, fp); - } - return err; -} - -static int tty_fops_release(inode* /*ip*/, file* fp) -{ - auto* tty = static_cast(fp->private_data); - tty_close(tty); - return 0; -} - -file_operations tty_fops; - -static void init_tty_fops() -{ - tty_fops.read = tty_fops_read; - tty_fops.write = tty_fops_write; - tty_fops.unlocked_ioctl = tty_fops_ioctl; - tty_fops.poll = tty_fops_poll; - tty_fops.open = tty_fops_open; - tty_fops.release = tty_fops_release; -} - -/* ================================================================== */ -/* TTY chardev registration */ -/* ================================================================== */ - -static cdev tty_cdev; - -void tty_init() -{ - init_tty_fops(); - - /* Allocate the console TTY */ - g_console_tty = tty_alloc(&uart_tty_driver, 0); - if (!g_console_tty) { - return; - } - - /* Open the TTY (initializes ldisc + driver) */ - tty_open(g_console_tty); - - /* Leave tty->session and tty->pgrp at 0. The first session-leader - * to open /dev/console without O_NOCTTY claims it as its controlling - * terminal via tty_acquire_ctty(), which fills both fields. Pre- - * seeding them here (as we used to with the hardcoded pid==1) would - * make every subsequent acquire path see "owned by another session" - * and bail out, leaving sh's tcgetpgrp() returning -ENOTTY and - * disabling job control. */ - - /* Initialize UART RX interrupts */ - uart_tty_init(g_console_tty); - - /* Register UART as a struct console driver. - * After this, printk/klog output goes through console_write_all() - * → uart_console_write() instead of raw early_putc(). */ - extern void uart_register_console(); - uart_register_console(); - - /* Register as /dev/console chardev — replace the old console_cdev. - * We use the same major/minor (5,1) so that existing opens - * to /dev/console will get the TTY fops. */ - tty_cdev.name = "console"; - tty_cdev.dev = MKDEV(5, 1); - tty_cdev.fops = &tty_fops; - cdev_register(&tty_cdev); -} diff --git a/na/drivers/tty/tty_uart.cc b/na/drivers/tty/tty_uart.cc deleted file mode 100644 index 973a6e9..0000000 --- a/na/drivers/tty/tty_uart.cc +++ /dev/null @@ -1,251 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tty_uart.cc — UART → tty_driver adapter - * - * Bridges the 16550A UART hardware to the TTY subsystem. - * - * TX: Calls early_putc() for each character (same as before). - * RX: Registers a PLIC IRQ handler. On UART RX interrupt, - * reads available bytes and feeds them to the line discipline - * via tty_receive_buf(). - * - * QEMU virt UART: - * - IRQ 10 on the PLIC - * - 16550A register layout at the base address discovered from FDT - */ - -#include -#include -#include -#include -#include -#include -#include - -#ifndef ENOIOCTLCMD -#define ENOIOCTLCMD 515 -#endif - -extern "C" void early_putc(char c); - -/* 16550A register offsets */ -static constexpr int UART_RBR = 0; /* Receive Buffer Register */ -static constexpr int UART_THR = 0; /* Transmit Holding Register */ -static constexpr int UART_IER = 1; /* Interrupt Enable Register */ -static constexpr int UART_IIR = 2; /* Interrupt Identification Register */ -static constexpr int UART_FCR = 2; /* FIFO Control Register */ -static constexpr int UART_LCR = 3; /* Line Control Register */ -static constexpr int UART_MCR = 4; /* Modem Control Register */ -static constexpr int UART_LSR = 5; /* Line Status Register */ - -/* LSR bits */ -static constexpr uint8 UART_LSR_DR = 0x01; /* Data Ready */ -static constexpr uint8 UART_LSR_THRE = 0x20; /* TX Holding Register Empty */ - -/* IER bits */ -static constexpr uint8 UART_IER_RDI = 0x01; /* Received Data Available */ - -/* MCR bits */ -static constexpr uint8 UART_MCR_DTR = 0x01; /* Data Terminal Ready */ -static constexpr uint8 UART_MCR_RTS = 0x02; /* Request To Send */ -static constexpr uint8 UART_MCR_OUT2 = 0x08; /* Auxiliary output 2 — gates the - IRQ pin on PC-style 16550A. - Without this the UART - internally asserts but does - NOT propagate to the PLIC. */ - -/* QEMU virt default UART IRQ */ -static constexpr uint32 UART_IRQ = 10; - -/* UART MMIO base (virtual address) */ -static volatile uint8* g_uart_base; -static tty_struct* g_uart_tty; - -/* ================================================================== */ -/* UART MMIO helpers */ -/* ================================================================== */ - -static inline uint8 uart_read(int reg) -{ - return g_uart_base[reg]; -} - -static inline void uart_write(int reg, uint8 val) -{ - g_uart_base[reg] = val; -} - -static inline bool uart_rx_ready() -{ - return (uart_read(UART_LSR) & UART_LSR_DR) != 0; -} - -static inline char uart_rx_char() -{ - return static_cast(uart_read(UART_RBR)); -} - -/* ================================================================== */ -/* UART RX interrupt handler */ -/* ================================================================== */ - -static void uart_irq_handler(uint32 /*irq*/, void* arg) -{ - auto* tty = static_cast(arg); - - /* Read all available bytes from the FIFO */ - char buf[16]; - int count = 0; - - while (uart_rx_ready() && count < 16) { - buf[count++] = uart_rx_char(); - } - - if (count > 0) { - tty_receive_buf(tty, buf, count); - } -} - -/* ================================================================== */ -/* tty_driver_ops for UART */ -/* ================================================================== */ - -static int uart_tty_open_op(tty_struct* /*tty*/) -{ - return 0; -} - -static void uart_tty_close_op(tty_struct* /*tty*/) -{ - /* Nothing to do */ -} - -static int uart_tty_write_op(tty_struct* /*tty*/, const char* buf, int count) -{ - for (int i = 0; i < count; i++) { - early_putc(buf[i]); - } - return count; -} - -static int uart_tty_write_room_op(tty_struct* /*tty*/) -{ - return TTY_BUF_SIZE; /* Always room (blocking putc) */ -} - -static void uart_tty_put_char_op(tty_struct* /*tty*/, char ch) -{ - early_putc(ch); -} - -static void uart_tty_flush_chars_op(tty_struct* /*tty*/) -{ - /* No buffering in early_putc, nothing to flush */ -} - -static int uart_tty_chars_in_buffer_op(tty_struct* /*tty*/) -{ - return 0; /* No TX buffer */ -} - -static void uart_tty_flush_buffer_op(tty_struct* /*tty*/) -{ - /* No TX buffer to flush */ -} - -static int uart_tty_ioctl_op(tty_struct* /*tty*/, unsigned int /*cmd*/, uint64 /*arg*/) -{ - return -ENOIOCTLCMD; -} - -static void uart_tty_hangup_op(tty_struct* /*tty*/) -{ - /* Disable UART RX interrupts */ - if (g_uart_base) { - uart_write(UART_IER, 0); - } -} - -static const tty_driver_ops uart_ops = { - .open = uart_tty_open_op, - .close = uart_tty_close_op, - .write = uart_tty_write_op, - .write_room = uart_tty_write_room_op, - .put_char = uart_tty_put_char_op, - .flush_chars = uart_tty_flush_chars_op, - .chars_in_buffer = uart_tty_chars_in_buffer_op, - .flush_buffer = uart_tty_flush_buffer_op, - .ioctl = uart_tty_ioctl_op, - .throttle = nullptr, - .unthrottle = nullptr, - .hangup = uart_tty_hangup_op, -}; - -const tty_driver uart_tty_driver = { - .name = "ttyS", - .major = 4, - .minor_start = 64, - .num = 1, - .ops = &uart_ops, -}; - -/* ================================================================== */ -/* UART TTY init — discover base address, enable RX IRQ */ -/* ================================================================== */ - -void uart_tty_init(tty_struct* tty) -{ - g_uart_tty = tty; - - /* Discover UART base from FDT */ - DeviceNode* node = fdt_find_compatible("ns16550a"); - if (!node) { - /* Try alternative compatible string */ - node = fdt_find_compatible("snps,dw-apb-uart"); - } - - uint64 uart_pa = 0; - if (node) { - uart_pa = node->get_prop_u64("reg", 0); - } - - if (uart_pa == 0) { - /* Fallback: QEMU virt default */ - uart_pa = 0x10000000; - } - - g_uart_base = reinterpret_cast( - pa_to_va(phys_addr(uart_pa)).raw); - - /* Set FIFO control: enable FIFOs, clear RX/TX FIFOs, 1-byte trigger */ - uart_write(UART_FCR, 0x07); - - /* MCR: assert OUT2 (gates the IRQ pin on PC-style 16550A), plus - * DTR/RTS for completeness. Mirrors Linux drivers/tty/serial/8250/ - * 8250_port.c: serial8250_do_startup() does the same. */ - uart_write(UART_MCR, UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); - - /* Enable UART receive-data-available interrupt */ - uart_write(UART_IER, UART_IER_RDI); - - /* Register IRQ handler with PLIC */ - uint32 irq = UART_IRQ; - if (node) { - /* Try to get IRQ number from FDT */ - const FdtProperty* irq_prop = node->get_prop("interrupts"); - if (irq_prop && irq_prop->len >= 4) { - const uint8* val = static_cast(irq_prop->value); - irq = (uint32(val[0]) << 24) | (uint32(val[1]) << 16) | - (uint32(val[2]) << 8) | val[3]; - } - } - - g_irq_manager.register_handler(irq, uart_irq_handler, tty); - g_irq_manager.enable(irq); - - printk(KERN_INFO "uart_tty_init: base=0x%llx irq=%u IER=0x%x LSR=0x%x\n", - (unsigned long long)uart_pa, irq, - uart_read(UART_IER), uart_read(UART_LSR)); -} diff --git a/na/drivers/uart.cc b/na/drivers/uart.cc deleted file mode 100644 index 467b496..0000000 --- a/na/drivers/uart.cc +++ /dev/null @@ -1,181 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * uart.cc — 16550a UART driver + console registry - * - * Two roles: - * 1. Early console: early_putc() for pre-init output (panic, kprintf) - * 2. Runtime console: struct console registered with register_console(). - * klog/printk route through console_write_all() → uart_console_write(). - * - * The console registry is a simple linked list of struct console. - * console_write_all() iterates the list and calls each console's write(). - * If no console is registered, it falls back to early_putc(). - * - * Reference: Linux kernel/printk/printk.c, drivers/tty/serial/8250/ - */ - -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* UART state */ -/* ================================================================== */ - -static uint64 g_uart_pa = 0; -static uint64 g_uart_size = 0; -static bool g_runtime_mapping_ready = false; -static bool g_mmu_enabled = false; - -/* ================================================================== */ -/* Early console (Phase 1) */ -/* ================================================================== */ - -extern "C" void console_set_mmu_enabled() { - g_mmu_enabled = true; -} - -extern "C" void early_putc(char c) { - if (!g_uart_pa) return; - - /* - * Before vmm_init() activates the final page table, UART MMIO is only - * reachable via the bootstrap fixed direct map from entry.S. - * After activation, switch to the runtime (possibly KASLR-slid) base. - */ - uint64 uart_va = 0; - if (g_runtime_mapping_ready) { - uart_va = pa_to_va(phys_addr(g_uart_pa)).raw; - } else if (!g_mmu_enabled) { - uart_va = g_uart_pa; - } else { - uart_va = boot_pa_to_va(phys_addr(g_uart_pa)).raw; - } - - volatile auto* uart = reinterpret_cast(uart_va); - *uart = static_cast(c); -} - -extern "C" void early_puts(const char* s) { - while (*s) early_putc(*s++); -} - -void console_early_init(uint64 fdt_paddr) { - uint64 pa = 0; - uint64 size = 0; - FdtManager::early_scan_uart(fdt_paddr, &pa, &size); - if (pa != 0) { - g_uart_pa = pa; - if (size != 0) g_uart_size = size; - } -} - -void console_init() { - /* - * Once vmm_init() activates the new page table, the 1GB identity map is dropped. - * The UART must be explicitly registered into the active page table. - */ - if (g_uart_pa != 0 && g_uart_size != 0) { - vmm_map_kernel_mmio(g_uart_pa, g_uart_size); - } -} - -void console_use_runtime_mapping() { - g_runtime_mapping_ready = true; -} - -/* ================================================================== */ -/* Console registry (Phase 2) — linked list of struct console */ -/* ================================================================== */ - -static list_node g_console_list; -static bool g_console_list_init = false; - -void register_console(console* con) -{ - if (!con) return; - - if (!g_console_list_init) { - g_console_list.init(); - g_console_list_init = true; - } - - con->flags |= CON_ENABLED; - con->list.init(); - - /* Insert at tail */ - list_add_tail(&con->list, &g_console_list); -} - -void unregister_console(console* con) -{ - if (!con) return; - - list_del_init(&con->list); - con->flags &= ~CON_ENABLED; -} - -bool console_drivers_registered() -{ - if (!g_console_list_init) return false; - return !list_empty(&g_console_list); -} - -void console_write_all(const char* s, int count) -{ - if (g_console_list_init && !list_empty(&g_console_list)) { - list_node* pos; - list_for_each(pos, &g_console_list) { - auto* con = container_of(pos, console, list); - if ((con->flags & CON_ENABLED) && con->write) { - con->write(con, s, static_cast(count)); - } - } - } else { - /* Fallback: direct early_putc (pre-registration) */ - for (int i = 0; i < count; i++) { - early_putc(s[i]); - } - } -} - -/* ================================================================== */ -/* UART console driver */ -/* ================================================================== */ - -static void uart_console_write(console* /*con*/, const char* s, unsigned int count) -{ - for (unsigned int i = 0; i < count; i++) { - if (s[i] == '\n') { - early_putc('\r'); - } - early_putc(s[i]); - } -} - -/* - * uart_console must be initialized at RUNTIME, not with static - * aggregate initializers. Reason: KASLR self-relocation processes - * .rela.dyn entries for simple pointer variables, but may not cover - * function pointer fields inside static aggregate initializers. - * A static `{ .write = uart_console_write }` would store the - * pre-relocation address, causing a crash when called. - */ -static console uart_console; - -void uart_register_console() -{ - uart_console.name = "ttyS"; - uart_console.write = uart_console_write; - uart_console.setup = nullptr; - uart_console.flags = CON_ENABLED | CON_BOOT; - uart_console.index = 0; - uart_console.data = nullptr; - uart_console.list.init(); - register_console(&uart_console); -} - diff --git a/na/fdt/device_node.cc b/na/fdt/device_node.cc deleted file mode 100644 index 0c8ad46..0000000 --- a/na/fdt/device_node.cc +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * device_node.cc — DeviceNode implementation - * - * Provides property lookup and child traversal for the unflattened - * device tree. After construction, the tree is globally read-only. - * - * Reference: docs/specs/fdt.md §3.2 - */ - -#include - -/* Minimal strcmp — no lib dependency */ -static bool str_eq(const char *a, const char *b) -{ - while (*a && *a == *b) { a++; b++; } - return *a == *b; -} - -/* ------------------------------------------------------------------ */ -/* Construction / Destruction */ -/* ------------------------------------------------------------------ */ - -DeviceNode::DeviceNode(DeviceNode *parent, const char *name) - : name_(name), parent_(parent) -{ - children_list_.init(); - props_list_.init(); - sibling_link_.init(); -} - -DeviceNode::~DeviceNode() -{ - /* Recursively delete children */ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - list_node *next = cur->next; - /* Recover DeviceNode* from sibling_link_ offset */ - DeviceNode *child = list_entry(cur); - delete child; - cur = next; - } - - /* Delete properties */ - list_node *pc = props_list_.next; - while (pc != &props_list_) { - list_node *pn = pc->next; - FdtProperty *prop = reinterpret_cast( - reinterpret_cast(pc) - - __builtin_offsetof(FdtProperty, link)); - delete prop; - pc = pn; - } -} - -/* ------------------------------------------------------------------ */ -/* Tree construction (used by FdtManager::unflatten) */ -/* ------------------------------------------------------------------ */ - -void DeviceNode::add_child(DeviceNode *child) -{ - /* Append to end of children list */ - list_node *node = &child->sibling_link_; - node->prev = children_list_.prev; - node->next = &children_list_; - children_list_.prev->next = node; - children_list_.prev = node; -} - -void DeviceNode::add_prop(FdtProperty *prop) -{ - list_node *node = &prop->link; - node->prev = props_list_.prev; - node->next = &props_list_; - props_list_.prev->next = node; - props_list_.prev = node; -} - -/* ------------------------------------------------------------------ */ -/* Property access */ -/* ------------------------------------------------------------------ */ - -const FdtProperty *DeviceNode::get_prop(const char *name) const -{ - const list_node *cur = props_list_.next; - while (cur != &props_list_) { - const FdtProperty *prop = reinterpret_cast( - reinterpret_cast(cur) - - __builtin_offsetof(FdtProperty, link)); - if (str_eq(prop->name, name)) - return prop; - cur = cur->next; - } - return nullptr; -} - -/* Big-endian helpers for decoding property values */ -static inline uint32 be32(const void *p) -{ - const uint8 *b = static_cast(p); - return (uint32(b[0]) << 24) | (uint32(b[1]) << 16) | - (uint32(b[2]) << 8) | uint32(b[3]); -} - -static inline uint64 be64(const void *p) -{ - const uint8 *b = static_cast(p); - return (uint64(be32(b)) << 32) | uint64(be32(b + 4)); -} - -uint32 DeviceNode::get_prop_u32(const char *name, uint32 default_val) const -{ - const FdtProperty *p = get_prop(name); - if (!p || p->len < 4) - return default_val; - return be32(p->value); -} - -uint64 DeviceNode::get_prop_u64(const char *name, uint64 default_val) const -{ - const FdtProperty *p = get_prop(name); - if (!p) - return default_val; - if (p->len >= 8) - return be64(p->value); - if (p->len >= 4) - return be32(p->value); - return default_val; -} - -const char *DeviceNode::get_prop_string(const char *name) const -{ - const FdtProperty *p = get_prop(name); - if (!p || p->len == 0) - return nullptr; - return static_cast(p->value); -} - -/* ------------------------------------------------------------------ */ -/* Child traversal */ -/* ------------------------------------------------------------------ */ - -DeviceNode *DeviceNode::find_child(const char *name) -{ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - DeviceNode *child = list_entry(cur); - if (str_eq(child->name_, name)) - return child; - cur = cur->next; - } - return nullptr; -} - -void DeviceNode::for_each_child(void (*callback)(DeviceNode *)) -{ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - list_node *next = cur->next; - DeviceNode *child = list_entry(cur); - callback(child); - cur = next; - } -} diff --git a/na/fdt/fdt.cc b/na/fdt/fdt.cc deleted file mode 100644 index d44ebda..0000000 --- a/na/fdt/fdt.cc +++ /dev/null @@ -1,625 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fdt.cc — Flattened Device Tree parser implementation - * - * Implements the two-phase FDT parsing strategy described in fdt.md: - * - * Phase 1 (Early Scan): - * Static methods that linearly walk the raw FDT blob in-place, - * performing zero memory allocation. Called before PMM/Slab exist. - * - * Phase 2 (Unflatten): - * Builds a persistent DeviceNode C++ object tree using Slab. - * Stub implementation — will be completed when Slab is available. - * - * Reference: docs/specs/fdt.md - */ - -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Global singleton (Placement New pattern, see global_init.md §3.1) */ -/* ------------------------------------------------------------------ */ - -alignas(FdtManager) uint8 g_fdt_buf[sizeof(FdtManager)]; -FdtManager *g_fdt = nullptr; - -/* ------------------------------------------------------------------ */ -/* FDT binary format constants (DTSpec v0.4) */ -/* ------------------------------------------------------------------ */ - -struct fdt_header { - uint32 magic; - uint32 totalsize; - uint32 off_dt_struct; - uint32 off_dt_strings; - uint32 off_mem_rsvmap; - uint32 version; - uint32 last_comp_version; - uint32 boot_cpuid_phys; - uint32 size_dt_strings; - uint32 size_dt_struct; -}; - -#define FDT_MAGIC 0xd00dfeed -#define FDT_BEGIN_NODE 1 -#define FDT_END_NODE 2 -#define FDT_PROP 3 -#define FDT_NOP 4 -#define FDT_END 9 - -/* ------------------------------------------------------------------ */ -/* Minimal string helpers (no lib/ dependency for early boot) */ -/* ------------------------------------------------------------------ */ - -static inline bool str_starts_with(const char *str, const char *prefix) { - while (*prefix) { - if (*str++ != *prefix++) - return false; - } - return true; -} - -static inline bool str_equals(const char *s1, const char *s2) { - while (*s1 && *s1 == *s2) { - s1++; - s2++; - } - return *s1 == *s2; -} - -/* - * Check whether a null-separated string list (e.g. the "compatible" - * property) contains @target. - */ -static bool prop_contains_string(const char *prop_val, uint32 len, - const char *target) { - uint32 i = 0; - while (i < len) { - const char *s = prop_val + i; - if (str_equals(s, target)) - return true; - while (i < len && prop_val[i] != '\0') - i++; - i++; // skip '\0' separator - } - return false; -} - -/* ------------------------------------------------------------------ */ -/* Byte-swap helpers */ -/* ------------------------------------------------------------------ */ - -/* - * Manual 32-bit big-endian to little-endian conversion. - * We cannot use __builtin_bswap32 because on RISC-V without the Zbb - * extension the compiler emits a call to libgcc's __bswapsi2, which - * is unavailable in a freestanding (-nostdlib) environment. - */ -static inline uint32 bswap32(uint32 x) { - return ((x & 0xFF000000u) >> 24) | ((x & 0x00FF0000u) >> 8) | - ((x & 0x0000FF00u) << 8) | ((x & 0x000000FFu) << 24); -} - -/* - * Read a big-endian cell value from the FDT struct block. - * @cells: number of 32-bit cells (1 or 2). - * 2-cell values are common for 64-bit addresses/sizes. - */ -static inline uint64 read_cells(const uint8 *data, uint32 cells) { - const uint32 *p = reinterpret_cast(data); - if (cells == 2) { - uint64 high = bswap32(p[0]); - uint64 low = bswap32(p[1]); - return (high << 32) | low; - } - return bswap32(p[0]); -} - -/* ------------------------------------------------------------------ */ -/* FdtManager construction */ -/* ------------------------------------------------------------------ */ - -FdtManager::FdtManager(uint64 fdt_paddr) - : fdt_paddr_(fdt_paddr) {} - -/* ------------------------------------------------------------------ */ -/* Phase 1: Early scan — memory layout */ -/* ------------------------------------------------------------------ */ - -/* - * Walk the raw FDT struct block to locate the /memory node and - * extract its "reg" property (base address + size). - * - * Cell sizes (#address-cells, #size-cells) are tracked per depth - * level to correctly decode the "reg" value under any bus topology. - * - * Panics on invalid magic or missing /memory node — there is no - * recovery path this early in boot. - */ -void FdtManager::early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size) { - const fdt_header *hdr = reinterpret_cast(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) - kernel_panic("FDT early_scan_mem: invalid magic number", nullptr); - - const uint8 *dt_struct = - reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - - // Per-depth cell sizes (inherit from parent on entry) - uint32 address_cells[16] = {2}; - uint32 size_cells[16] = {1}; - bool in_memory_node = false; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; // align to 4 bytes - - depth++; - - // Inherit parent's cell sizes - if (depth > 0 && depth < 16) { - address_cells[depth] = address_cells[depth - 1]; - size_cells[depth] = size_cells[depth - 1]; - } - - // Match top-level "memory" or "memory@..." nodes - in_memory_node = (depth == 1 && str_starts_with(name, "memory")); - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - in_memory_node = false; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const uint8 *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16) { - // Track cell size overrides at the current depth - if (str_equals(prop_name, "#address-cells")) { - address_cells[depth] = - bswap32(*reinterpret_cast(prop_val)); - } else if (str_equals(prop_name, "#size-cells")) { - size_cells[depth] = - bswap32(*reinterpret_cast(prop_val)); - } - // Decode the "reg" property using the parent's cell sizes - else if (in_memory_node && str_equals(prop_name, "reg")) { - uint32 p_depth = (depth > 0) ? depth - 1 : 0; - *base = read_cells(prop_val, address_cells[p_depth]); - *size = read_cells(prop_val + address_cells[p_depth] * 4, - size_cells[p_depth]); - return; // Success - } - } - - offset = (offset + len + 3) & ~3u; // align to 4 bytes - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - kernel_panic("FDT early_scan_mem: unknown token", nullptr); - } - } - - kernel_panic("FDT early_scan_mem: /memory node not found", nullptr); -} - -/* ------------------------------------------------------------------ */ -/* Phase 1: Early scan — UART address */ -/* ------------------------------------------------------------------ */ - -/* - * Locate a UART node by matching compatible = "ns16550a" and return - * its base address from the "reg" property. - * - * Unlike early_scan_mem, failure here is non-fatal: we fall back to - * the platform's default UART address (0x10000000 for QEMU virt). - */ -void FdtManager::early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, - uint64 *uart_size) { - if (!uart_addr || !uart_size) - return; - - auto set_default_uart = [uart_addr, uart_size]() { - *uart_addr = 0x10000000ULL; - *uart_size = 0x100; - }; - - const fdt_header *hdr = reinterpret_cast(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) { - set_default_uart(); - return; - } - - const uint8 *dt_struct = - reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - uint32 address_cells[16] = {2}; - uint32 size_cells[16] = {1}; - bool in_uart_node = false; - bool has_current_node_reg = false; - uint64 current_node_reg = 0; - uint64 current_node_size = 0; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth > 0 && depth < 16) { - address_cells[depth] = address_cells[depth - 1]; - size_cells[depth] = size_cells[depth - 1]; - } - in_uart_node = false; - has_current_node_reg = false; - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - in_uart_node = false; - has_current_node_reg = false; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const uint8 *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16) { - if (str_equals(prop_name, "#address-cells")) { - address_cells[depth] = - bswap32(*reinterpret_cast(prop_val)); - } else if (str_equals(prop_name, "#size-cells")) { - size_cells[depth] = - bswap32(*reinterpret_cast(prop_val)); - } else if (str_equals(prop_name, "compatible")) { - if (prop_contains_string(reinterpret_cast(prop_val), - len, "ns16550a")) { - in_uart_node = true; - if (has_current_node_reg) { - *uart_addr = current_node_reg; - *uart_size = current_node_size; - return; - } - } - } else if (str_equals(prop_name, "reg")) { - uint32 p_depth = (depth > 0) ? depth - 1 : 0; - current_node_reg = read_cells(prop_val, address_cells[p_depth]); - current_node_size = read_cells(prop_val + address_cells[p_depth] * 4, - size_cells[p_depth]); - has_current_node_reg = true; - if (in_uart_node) { - *uart_addr = current_node_reg; - *uart_size = current_node_size; - return; // Found it - } - } - } - - offset = (offset + len + 3) & ~3u; - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - break; // Unknown token — give up silently - } - } - - set_default_uart(); -} - -/* ------------------------------------------------------------------ */ -/* Phase 2: Unflatten (stub — requires Slab) */ -/* ------------------------------------------------------------------ */ - -void FdtManager::unflatten() { - const uint64 fdt_va = pa_to_va(phys_addr(fdt_paddr_)).raw; - const fdt_header *hdr = reinterpret_cast(fdt_va); - if (bswap32(hdr->magic) != FDT_MAGIC) - kernel_panic("fdt unflatten: invalid magic", nullptr); - - const uint8 *dt_struct = - reinterpret_cast(fdt_va) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast(fdt_va) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - - /* Stack to track current node at each depth (max 16 levels) */ - DeviceNode *stack[16] = {}; - int depth = -1; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth >= 16) - kernel_panic("fdt unflatten: tree too deep", nullptr); - - DeviceNode *parent = (depth > 0) ? stack[depth - 1] : nullptr; - DeviceNode *node = new DeviceNode(parent, name); - if (!node) - kernel_panic("fdt unflatten: OOM creating DeviceNode", nullptr); - - stack[depth] = node; - - if (parent) - parent->add_child(node); - else - root_ = node; /* Root node */ - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const void *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16 && stack[depth]) { - FdtProperty *prop = new FdtProperty; - if (!prop) - kernel_panic("fdt unflatten: OOM creating FdtProperty", nullptr); - prop->name = prop_name; - prop->value = prop_val; - prop->len = static_cast(len); - prop->link.init(); - stack[depth]->add_prop(prop); - } - - offset = (offset + len + 3) & ~3u; - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - kernel_panic("fdt unflatten: unknown token", nullptr); - } - } - - is_unflattened_ = true; -} - -/* Recursive helper: find first node with matching compatible string */ -static DeviceNode *find_compat_recursive(DeviceNode *node, const char *compat) { - const FdtProperty *prop = node->get_prop("compatible"); - if (prop) { - const char *val = static_cast(prop->value); - uint32 i = 0; - while (i < static_cast(prop->len)) { - const char *s = val + i; - if (str_equals(s, compat)) - return node; - while (i < static_cast(prop->len) && val[i] != '\0') - i++; - i++; - } - } - - /* Search children */ - list_node *cur = node->children_list_head(); - list_node *end = node->children_list_sentinel(); - while (cur != end) { - DeviceNode *child = list_entry(cur); - DeviceNode *found = find_compat_recursive(child, compat); - if (found) - return found; - cur = cur->next; - } - return nullptr; -} - -DeviceNode *FdtManager::find_compatible(const char *compat_string) { - if (!is_unflattened_) - kernel_panic("fdt: tree not unflattened yet", nullptr); - if (!root_) - return nullptr; - return find_compat_recursive(root_, compat_string); -} - -DeviceNode *FdtManager::get_node_by_path(const char *path) { - if (!is_unflattened_) - kernel_panic("fdt: tree not unflattened yet", nullptr); - if (!root_ || !path) - return nullptr; - - /* Skip leading '/' */ - if (*path == '/') - path++; - if (*path == '\0') - return root_; - - DeviceNode *cur = root_; - while (*path && cur) { - /* Extract next path component */ - const char *end = path; - while (*end && *end != '/') - end++; - - /* Search children for a name matching this component */ - bool found = false; - list_node *lc = cur->children_list_head(); - list_node *le = cur->children_list_sentinel(); - while (lc != le) { - DeviceNode *child = - list_entry(lc); - /* Compare name up to the component length */ - const char *cn = child->get_name(); - const char *p = path; - const char *e = end; - bool match = true; - while (p < e) { - if (*cn != *p) { - match = false; - break; - } - cn++; - p++; - } - if (match && *cn == '\0') { - cur = child; - found = true; - break; - } - lc = lc->next; - } - - if (!found) - return nullptr; - - path = (*end == '/') ? end + 1 : end; - } - return cur; -} - -/* ------------------------------------------------------------------ */ -/* Public free-function API */ -/* ------------------------------------------------------------------ */ - -void fdt_init(uint64 fdt_paddr) { - const uint32 *magic_ptr = reinterpret_cast(fdt_paddr); - if (bswap32(*magic_ptr) != FDT_MAGIC) - kernel_panic("fdt_init: invalid FDT magic number", nullptr); - - g_fdt = new (g_fdt_buf) FdtManager(fdt_paddr); -} - -void fdt_unflatten() { g_fdt->unflatten(); } - -DeviceNode *fdt_find_compatible(const char *compat_string) { - return g_fdt->find_compatible(compat_string); -} - -DeviceNode *fdt_get_node_by_path(const char *path) { - return g_fdt->get_node_by_path(path); -} - -void fdt_early_get_mem_info(uint64 *base, uint64 *size) { - FdtManager::early_scan_mem(g_fdt->get_paddr(), base, size); -} - -void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size) { - FdtManager::early_scan_uart(g_fdt->get_paddr(), uart_addr, uart_size); -} - -void FdtManager::early_scan_kaslr(uint64 fdt_paddr, uint64 *seed) -{ - if (!seed) return; - *seed = 0; - - const fdt_header *hdr = reinterpret_cast(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) return; - - const uint8 *dt_struct = reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = reinterpret_cast(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - bool in_chosen = false; - - while (true) { - uint32 token = bswap32(*reinterpret_cast(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth == 1 && str_starts_with(name, "chosen")) { - in_chosen = true; - } else if (depth == 1) { - in_chosen = false; - } - } else if (token == FDT_END_NODE) { - if (depth >= 0) depth--; - if (depth < 1) in_chosen = false; - } else if (token == FDT_PROP) { - uint32 len = bswap32(*reinterpret_cast(dt_struct + offset)); - uint32 nameoff = bswap32(*reinterpret_cast(dt_struct + offset + 4)); - offset += 8; - - if (in_chosen) { - const char *prop_name = dt_strings + nameoff; - if (str_starts_with(prop_name, "kaslr-seed") && len == 8) { - *seed = read_cells(dt_struct + offset, 2); - return; - } - } - offset = (offset + len + 3) & ~3u; - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - break; - } - } -} - -void fdt_early_get_kaslr_seed(uint64 *seed) { - FdtManager::early_scan_kaslr(g_fdt->get_paddr(), seed); -} diff --git a/na/fdt/module.mk b/na/fdt/module.mk deleted file mode 100644 index 3e490ca..0000000 --- a/na/fdt/module.mk +++ /dev/null @@ -1,3 +0,0 @@ -# fdt/module.mk -SRCS_CC += fdt/fdt.cc -SRCS_CC += fdt/device_node.cc diff --git a/na/fs/chardev.cc b/na/fs/chardev.cc deleted file mode 100644 index fa96276..0000000 --- a/na/fs/chardev.cc +++ /dev/null @@ -1,43 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * chardev.cc — Character device registry - * - * Simple linked list of registered cdevs, searched by dev number. - */ - -#include -#include -#include -#include - -static list_node g_cdev_list; -static Spinlock g_cdev_lock; - -void cdev_init() -{ - list_init(&g_cdev_list); -} - -void cdev_register(cdev* dev) -{ - LockGuard guard(g_cdev_lock); - list_init(&dev->list); - list_add_tail(&dev->list, &g_cdev_list); - printk("[cdev] registered '%s' (%d,%d)\n", - dev->name, MAJOR(dev->dev), MINOR(dev->dev)); -} - -cdev* cdev_find(uint32 dev_nr) -{ - LockGuard guard(g_cdev_lock); - list_node* pos; - for (pos = g_cdev_list.prev; pos != &g_cdev_list; pos = pos->prev) { - cdev* cd = container_of(pos, cdev, list); - if (cd->dev == dev_nr) { - return cd; - } - } - return nullptr; -} diff --git a/na/fs/dcache.cc b/na/fs/dcache.cc deleted file mode 100644 index 94ab287..0000000 --- a/na/fs/dcache.cc +++ /dev/null @@ -1,287 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * dcache.cc — Directory entry cache - * - * Provides dentry allocation, lookup, instantiation, and d_path. - */ - -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* Hash function */ -/* ================================================================== */ - -uint32 full_name_hash(const char* name, uint32 len) -{ - uint32 hash = 0; - for (uint32 i = 0; i < len; i++) - hash = hash * 31 + static_cast(name[i]); - return hash; -} - -/* ================================================================== */ -/* d_set_type — set DCACHE_*_TYPE bits from inode mode */ -/* ================================================================== */ - -void d_set_type(dentry* de, uint32 i_mode) -{ - de->d_flags &= ~DCACHE_ENTRY_TYPE; - - if (S_ISDIR(i_mode)) - de->d_flags |= DCACHE_DIRECTORY_TYPE; - else if (S_ISLNK(i_mode)) - de->d_flags |= DCACHE_SYMLINK_TYPE; - else if (S_ISREG(i_mode)) - de->d_flags |= DCACHE_REGULAR_TYPE; - else - de->d_flags |= DCACHE_SPECIAL_TYPE; -} - -/* ================================================================== */ -/* d_alloc — allocate a dentry under a parent */ -/* ================================================================== */ - -static void dentry_init(dentry* de, dentry* parent, const qstr* name) -{ - de->d_flags = DCACHE_MISS_TYPE; /* negative until instantiated */ - de->d_parent = parent ? parent : de; - de->d_inode = nullptr; - de->d_op = nullptr; - de->d_sb = parent ? parent->d_sb : nullptr; - de->d_time = 0; - de->d_fsdata = nullptr; - de->d_lockref.store(1); - - list_init(&de->d_lru); - list_init(&de->d_child); - list_init(&de->d_children); - list_init(&de->d_alias); - - /* Copy name */ - if (name && name->name && name->len > 0) { - uint32 len = name->len; - if (len < DNAME_INLINE_LEN) { - for (uint32 i = 0; i < len; i++) - de->d_iname[i] = name->name[i]; - de->d_iname[len] = '\0'; - de->d_name.name = reinterpret_cast(de->d_iname); - } else { - /* Heap-allocate for long names */ - char* buf = new char[len + 1]; - for (uint32 i = 0; i < len; i++) - buf[i] = name->name[i]; - buf[len] = '\0'; - de->d_name.name = buf; - } - de->d_name.len = len; - de->d_name.hash = name->hash ? name->hash : full_name_hash(name->name, len); - } else { - de->d_iname[0] = '\0'; - de->d_name.name = reinterpret_cast(de->d_iname); - de->d_name.len = 0; - de->d_name.hash = 0; - } -} - -dentry* d_alloc(dentry* parent, const qstr* name) -{ - auto* de = new dentry{}; - if (!de) return nullptr; - - dentry_init(de, parent, name); - de->d_sb = parent->d_sb; - - /* Use sb default d_op if set */ - if (parent->d_sb->s_d_op) - de->d_op = parent->d_sb->s_d_op; - - /* Link into parent's children list */ - if (parent) { - LockGuard guard(parent->d_lock); - list_add_tail(&de->d_child, &parent->d_children); - dget(parent); /* child holds ref on parent */ - } - - return de; -} - -dentry* d_alloc_anon(super_block* sb) -{ - auto* de = new dentry{}; - if (!de) return nullptr; - - dentry_init(de, nullptr, nullptr); - de->d_sb = sb; - de->d_parent = de; /* root: parent is self */ - - return de; -} - -/* ================================================================== */ -/* d_instantiate — bind an inode to a dentry */ -/* ================================================================== */ - -void d_instantiate(dentry* de, inode* ip) -{ - if (!ip) return; - - LockGuard guard(de->d_lock); - de->d_inode = ip; - d_set_type(de, ip->i_mode); - - /* Add to inode's alias list */ - list_add_tail(&de->d_alias, &ip->i_dentry); -} - -/* ================================================================== */ -/* d_make_root — create root dentry for a superblock */ -/* ================================================================== */ - -dentry* d_make_root(inode* root_inode) -{ - if (!root_inode) return nullptr; - - dentry* de = d_alloc_anon(root_inode->i_sb); - if (!de) { - iput(root_inode); - return nullptr; - } - - d_instantiate(de, root_inode); - return de; -} - -/* ================================================================== */ -/* d_add — combined d_instantiate + d_rehash */ -/* ================================================================== */ - -void d_add(dentry* de, inode* ip) -{ - d_instantiate(de, ip); - /* d_rehash would add to dcache hash table; simplified for now */ -} - -/* ================================================================== */ -/* d_lookup — search children of parent for a name match */ -/* ================================================================== */ - -dentry* d_lookup(const dentry* parent, const qstr* name) -{ - LockGuard guard(const_cast(parent->d_lock)); - - list_node* pos; - list_for_each(pos, &parent->d_children) { - dentry* child = list_entry(pos, dentry, d_child); - - if (child->d_name.len != name->len) - continue; - - /* Compare names */ - const char* a = child->d_name.name; - const char* b = name->name; - uint32 len = name->len; - bool match = true; - for (uint32 i = 0; i < len; i++) { - if (a[i] != b[i]) { match = false; break; } - } - if (match) { - dget(child); - return child; - } - } - - return nullptr; -} - -/* ================================================================== */ -/* dget / dput — dentry reference counting */ -/* ================================================================== */ - -dentry* dget(dentry* de) -{ - if (de) - de->d_lockref.fetch_add(1); - return de; -} - -void dput(dentry* de) -{ - if (!de) return; - - int old = de->d_lockref.fetch_sub(1); - if (old > 1) return; - - /* Last reference — could free, but for ramfs we keep dentries alive. - * A real implementation would check d_op->d_delete and possibly - * add to LRU for later reclaim. For now, just leak (Phase 1). */ -} - -/* ================================================================== */ -/* d_drop / d_delete */ -/* ================================================================== */ - -void d_drop(dentry* de) -{ - /* Remove from parent's children list */ - LockGuard guard(de->d_lock); - if (!list_empty(&de->d_child)) - list_del_init(&de->d_child); -} - -void d_delete(dentry* de) -{ - d_drop(de); -} - -/* ================================================================== */ -/* d_path — build absolute pathname from a path */ -/* ================================================================== */ - -char* d_path(const path* p, char* buf, int buflen) -{ - if (!p || !p->de || buflen <= 0) - return nullptr; - - /* Build path by walking up d_parent chain, writing backwards */ - char* end = buf + buflen - 1; - *end = '\0'; - - dentry* de = p->de; - - if (d_is_root(de)) { - if (end <= buf) return nullptr; - *(--end) = '/'; - return end; - } - - while (!d_is_root(de)) { - uint32 len = de->d_name.len; - end -= len; - if (end <= buf) return nullptr; - - const char* name = de->d_name.name; - for (uint32 i = 0; i < len; i++) - end[i] = name[i]; - - *(--end) = '/'; - de = de->d_parent; - } - - return end; -} - -/* ================================================================== */ -/* dcache_init */ -/* ================================================================== */ - -void dcache_init() -{ - /* Future: init dcache hash table */ -} diff --git a/na/fs/devfs.cc b/na/fs/devfs.cc deleted file mode 100644 index b4b2cf3..0000000 --- a/na/fs/devfs.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * devfs.cc — /dev population and chardev-aware open - * - * Creates /dev as a subdirectory under the ramfs root, then - * populates device nodes. On open(), if the inode is S_IFCHR, - * we look up the cdev by i_rdev and use its fops. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* devfs_create_node — create a char device inode under parent */ -/* ================================================================== */ - -static int devfs_create_node(dentry* parent, const char* name, - uint32 major, uint32 minor) -{ - inode* dir_ip = parent->d_inode; - if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mknod) { - return -EPERM; - } - - qstr q{}; - q.name = name; - q.len = 0; - for (const char* s = name; *s; s++) { - q.len++; - } - - dentry* de = d_alloc(parent, &q); - if (!de) { - return -ENOMEM; - } - - int err = dir_ip->i_op->mknod(dir_ip, de, S_IFCHR | 0666, - MKDEV(major, minor)); - if (err) { - return err; - } - - return 0; -} - -/* ================================================================== */ -/* devfs_open — chardev-aware open hook */ -/* ================================================================== */ - -/* - * Called from sys_openat when the inode is S_IFCHR. - * Replaces f_op with the chardev's file_operations. - */ -int devfs_chrdev_open(inode* ip, file* fp) -{ - if (!S_ISCHR(ip->i_mode)) { - return 0; - } - - cdev* cd = cdev_find(ip->i_rdev); - if (!cd || !cd->fops) { - return -ENXIO; /* No such device */ - } - - fp->f_op = cd->fops; - - /* Call the device's own open if it has one */ - if (fp->f_op->open) { - return fp->f_op->open(ip, fp); - } - - return 0; -} - -/* ================================================================== */ -/* devfs_init — create /dev and populate device nodes */ -/* ================================================================== */ - -void devfs_init() -{ - if (!g_root_dentry || !g_root_dentry->d_inode) { - printk(KERN_ERR "[devfs] no root filesystem!\n"); - return; - } - - /* Create /dev directory — or reuse if initramfs already created it. - * Linux: devtmpfs similarly handles pre-existing directories. */ - inode* root_ip = g_root_dentry->d_inode; - - qstr dev_name{}; - dev_name.name = "dev"; - dev_name.len = 3; - dev_name.hash = full_name_hash("dev", 3); - - dentry* dev_de = d_lookup(g_root_dentry, &dev_name); - if (dev_de) { - /* /dev already exists (from initramfs) — reuse it */ - printk("[devfs] reusing existing /dev directory\n"); - } else { - /* Create /dev from scratch */ - if (!root_ip->i_op || !root_ip->i_op->mkdir) { - printk(KERN_ERR "[devfs] root has no mkdir!\n"); - return; - } - - dev_de = d_alloc(g_root_dentry, &dev_name); - if (!dev_de) { - printk(KERN_ERR "[devfs] failed to alloc /dev dentry\n"); - return; - } - - int err = root_ip->i_op->mkdir(root_ip, dev_de, S_IFDIR | 0755); - if (err) { - printk(KERN_ERR "[devfs] mkdir /dev failed: %d\n", err); - return; - } - } - - /* Populate: /dev/console (5,1) */ - devfs_create_node(dev_de, "console", 5, 1); - - /* Populate: /dev/null (1,3) */ - devfs_create_node(dev_de, "null", 1, 3); - - /* Populate: /dev/zero (1,5) */ - devfs_create_node(dev_de, "zero", 1, 5); - - printk("[devfs] /dev populated (console, null, zero)\n"); -} diff --git a/na/fs/fd.cc b/na/fs/fd.cc deleted file mode 100644 index 5fc8ab0..0000000 --- a/na/fs/fd.cc +++ /dev/null @@ -1,216 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fd.cc — File descriptor table management - */ - -#include -#include -#include -#include - -/* ================================================================== */ -/* Bitmap helpers */ -/* ================================================================== */ - -static inline bool bit_test(const uint64* bmap, int bit) { - return (bmap[bit / 64] >> (bit % 64)) & 1; -} -static inline void bit_set(uint64* bmap, int bit) { - bmap[bit / 64] |= (1ULL << (bit % 64)); -} -static inline void bit_clear(uint64* bmap, int bit) { - bmap[bit / 64] &= ~(1ULL << (bit % 64)); -} - -/* ================================================================== */ -/* alloc_files_struct */ -/* ================================================================== */ - -files_struct* alloc_files_struct() -{ - auto* files = new files_struct{}; - if (!files) return nullptr; - - files->count.store(1); - files->next_fd = 0; - files->max_fds = NR_OPEN_DEFAULT; - - for (int i = 0; i < NR_OPEN_DEFAULT; i++) - files->fd_array[i] = nullptr; - - int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; - for (int i = 0; i < bmap_words; i++) { - files->close_on_exec_init[i] = 0; - files->open_fds_init[i] = 0; - } - - return files; -} - -/* ================================================================== */ -/* put_files_struct — drop reference, free on last */ -/* ================================================================== */ - -void put_files_struct(files_struct* files) -{ - if (!files) return; - - int old = files->count.fetch_sub(1); - if (old > 1) return; - - /* Close all open fds */ - for (int i = 0; i < files->max_fds; i++) { - if (files->fd_array[i]) { - fput(files->fd_array[i]); - files->fd_array[i] = nullptr; - } - } - - delete files; -} - -/* ================================================================== */ -/* dup_fd — duplicate entire fd table (for fork) */ -/* ================================================================== */ - -files_struct* dup_fd(files_struct* old_files) -{ - auto* new_files = alloc_files_struct(); - if (!new_files) return nullptr; - - LockGuard guard(old_files->file_lock); - - for (int i = 0; i < old_files->max_fds; i++) { - file* fp = old_files->fd_array[i]; - if (fp) { - get_file(fp); - new_files->fd_array[i] = fp; - } - } - - /* Copy bitmaps */ - int bmap_words = (NR_OPEN_DEFAULT + 63) / 64; - for (int i = 0; i < bmap_words; i++) { - new_files->open_fds_init[i] = old_files->open_fds_init[i]; - new_files->close_on_exec_init[i] = old_files->close_on_exec_init[i]; - } - - new_files->next_fd = old_files->next_fd; - - return new_files; -} - -/* ================================================================== */ -/* alloc_fd — find lowest free fd >= start */ -/* ================================================================== */ - -int alloc_fd(files_struct* files, unsigned int start) -{ - LockGuard guard(files->file_lock); - - for (int fd = static_cast(start); fd < files->max_fds; fd++) { - if (!bit_test(files->open_fds_init, fd)) { - bit_set(files->open_fds_init, fd); - files->fd_array[fd] = nullptr; /* placeholder, fd_install sets real file */ - if (fd >= files->next_fd) - files->next_fd = fd + 1; - return fd; - } - } - - return -EMFILE; -} - -/* ================================================================== */ -/* fd_install — place a file* at a given fd */ -/* ================================================================== */ - -void fd_install(files_struct* files, int fd, file* fp) -{ - LockGuard guard(files->file_lock); - files->fd_array[fd] = fp; -} - -/* ================================================================== */ -/* fcheck — look up file by fd (no refcount bump) */ -/* ================================================================== */ - -file* fcheck(files_struct* files, int fd) -{ - if (fd < 0 || fd >= files->max_fds) return nullptr; - return files->fd_array[fd]; -} - -/* ================================================================== */ -/* close_fd */ -/* ================================================================== */ - -int close_fd(files_struct* files, int fd) -{ - file* fp; - { - LockGuard guard(files->file_lock); - if (fd < 0 || fd >= files->max_fds) return -EBADF; - - fp = files->fd_array[fd]; - if (!fp) return -EBADF; - - files->fd_array[fd] = nullptr; - bit_clear(files->open_fds_init, fd); - bit_clear(files->close_on_exec_init, fd); - } - - fput(fp); - return 0; -} - -/* ================================================================== */ -/* do_dup2 — duplicate oldfd to newfd */ -/* ================================================================== */ - -int do_dup2(files_struct* files, int oldfd, int newfd, int flags) -{ - if (oldfd == newfd) return -EINVAL; - - file* existing = nullptr; - { - LockGuard guard(files->file_lock); - - if (oldfd < 0 || oldfd >= files->max_fds) return -EBADF; - if (newfd < 0 || newfd >= files->max_fds) return -EBADF; - - file* old_fp = files->fd_array[oldfd]; - if (!old_fp) return -EBADF; - - existing = files->fd_array[newfd]; - - get_file(old_fp); - files->fd_array[newfd] = old_fp; - bit_set(files->open_fds_init, newfd); - - if (flags & O_CLOEXEC) - bit_set(files->close_on_exec_init, newfd); - else - bit_clear(files->close_on_exec_init, newfd); - } - - if (existing) - fput(existing); - - return newfd; -} - -/* ================================================================== */ -/* do_close_on_exec — close all fds with CLOEXEC set */ -/* ================================================================== */ - -void do_close_on_exec(files_struct* files) -{ - for (int fd = 0; fd < files->max_fds; fd++) { - if (bit_test(files->close_on_exec_init, fd)) { - close_fd(files, fd); - } - } -} diff --git a/na/fs/file_table.cc b/na/fs/file_table.cc deleted file mode 100644 index 06095c1..0000000 --- a/na/fs/file_table.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * file_table.cc — Open file allocation and reference counting - */ - -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* alloc_empty_file */ -/* ================================================================== */ - -file* alloc_empty_file(int flags) -{ - auto* fp = new file{}; - if (!fp) return nullptr; - - fp->f_count.store(1); - fp->f_flags = flags; - fp->f_mode = 0; - fp->f_op = nullptr; - fp->f_mapping = nullptr; - fp->f_inode = nullptr; - fp->f_pos = 0; - fp->private_data = nullptr; - fp->f_path.mnt = nullptr; - fp->f_path.de = nullptr; - - /* Derive f_mode from O_* flags */ - int acc = flags & O_ACCMODE; - if (acc == O_RDONLY) - fp->f_mode = FMODE_READ | FMODE_CAN_READ; - else if (acc == O_WRONLY) - fp->f_mode = FMODE_WRITE | FMODE_CAN_WRITE; - else if (acc == O_RDWR) - fp->f_mode = FMODE_READ | FMODE_WRITE | FMODE_CAN_READ | FMODE_CAN_WRITE; - - fp->f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; - - return fp; -} - -/* ================================================================== */ -/* get_file / fput */ -/* ================================================================== */ - -file* get_file(file* f) -{ - f->f_count.fetch_add(1); - return f; -} - -void fput(file* fp) -{ - if (!fp) return; - - int old = fp->f_count.fetch_sub(1); - if (old > 1) return; - - /* Last reference — release */ - if (fp->f_op && fp->f_op->release) - fp->f_op->release(fp->f_inode, fp); - - if (fp->f_path.de) - dput(fp->f_path.de); - - /* Future: mntput(fp->f_path.mnt) */ - - delete fp; -} - -/* ================================================================== */ -/* vfs_read / vfs_write / vfs_llseek / vfs_fstat */ -/* ================================================================== */ - -int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos) -{ - if (!(fp->f_mode & FMODE_CAN_READ)) - return -EBADF; - if (!fp->f_op || !fp->f_op->read) - return -EINVAL; - - return fp->f_op->read(fp, buf, count, pos); -} - -int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos) -{ - if (!(fp->f_mode & FMODE_CAN_WRITE)) - return -EBADF; - if (!fp->f_op || !fp->f_op->write) - return -EINVAL; - - return fp->f_op->write(fp, buf, count, pos); -} - -int64 vfs_llseek(file* fp, int64 offset, int whence) -{ - if (fp->f_op && fp->f_op->llseek) - return fp->f_op->llseek(fp, offset, whence); - return generic_file_llseek(fp, offset, whence); -} - -int64 generic_file_llseek(file* fp, int64 offset, int whence) -{ - int64 new_pos; - switch (whence) { - case SEEK_SET: - new_pos = offset; - break; - case SEEK_CUR: - new_pos = fp->f_pos + offset; - break; - case SEEK_END: - new_pos = fp->f_inode->i_size + offset; - break; - default: - return -EINVAL; - } - - if (new_pos < 0) return -EINVAL; - fp->f_pos = new_pos; - return new_pos; -} - -int vfs_fstat(file* fp, kstat* stat) -{ - inode* ip = file_inode(fp); - if (!ip) return -EBADF; - - if (ip->i_op && ip->i_op->getattr) { - return ip->i_op->getattr(&fp->f_path, stat, 0, 0); - } - - generic_fillattr(ip, stat); - return 0; -} - -/* ================================================================== */ -/* path_get / path_put (simplified) */ -/* ================================================================== */ - -void path_get(const path* p) -{ - if (p->de) dget(p->de); - /* Future: mntget(p->mnt) */ -} - -void path_put(const path* p) -{ - if (p->de) dput(p->de); - /* Future: mntput(p->mnt) */ -} - -/* ================================================================== */ -/* mntget / mntput (stubs for Phase 1) */ -/* ================================================================== */ - -vfsmount* mntget(vfsmount* mnt) { return mnt; } -void mntput(vfsmount*) {} diff --git a/na/fs/fs_struct.cc b/na/fs/fs_struct.cc deleted file mode 100644 index 090819f..0000000 --- a/na/fs/fs_struct.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fs_struct.cc — Per-task filesystem context (cwd, root) - */ - -#include -#include - -/* ================================================================== */ -/* alloc_fs_struct */ -/* ================================================================== */ - -fs_struct* alloc_fs_struct() -{ - auto* fs = new fs_struct{}; - if (!fs) return nullptr; - - fs->count.store(1); - fs->umask = 022; - fs->root = {nullptr, nullptr}; - fs->pwd = {nullptr, nullptr}; - - return fs; -} - -/* ================================================================== */ -/* put_fs_struct */ -/* ================================================================== */ - -void put_fs_struct(fs_struct* fs) -{ - if (!fs) return; - - int old = fs->count.fetch_sub(1); - if (old > 1) return; - - path_put(&fs->root); - path_put(&fs->pwd); - delete fs; -} - -/* ================================================================== */ -/* copy_fs_struct — for fork without CLONE_FS */ -/* ================================================================== */ - -fs_struct* copy_fs_struct(const fs_struct* old_fs) -{ - auto* fs = alloc_fs_struct(); - if (!fs) return nullptr; - - fs->umask = old_fs->umask; - - fs->root = old_fs->root; - path_get(&fs->root); - - fs->pwd = old_fs->pwd; - path_get(&fs->pwd); - - return fs; -} - -/* ================================================================== */ -/* set_fs_root / set_fs_pwd */ -/* ================================================================== */ - -void set_fs_root(fs_struct* fs, const path* p) -{ - path old_root; - { - LockGuard guard(fs->lock); - old_root = fs->root; - fs->root = *p; - path_get(p); - } - path_put(&old_root); -} - -void set_fs_pwd(fs_struct* fs, const path* p) -{ - path old_pwd; - { - LockGuard guard(fs->lock); - old_pwd = fs->pwd; - fs->pwd = *p; - path_get(p); - } - path_put(&old_pwd); -} diff --git a/na/fs/initramfs.cc b/na/fs/initramfs.cc deleted file mode 100644 index ba10a3e..0000000 --- a/na/fs/initramfs.cc +++ /dev/null @@ -1,285 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * initramfs.cc — Unpack embedded CPIO archive into ramfs - * - * The build system embeds a newc-format CPIO archive into the kernel - * ELF as the section .init.ramfs. The linker script exports - * __initramfs_start / __initramfs_end symbols. - * - * At boot, populate_rootfs() walks the CPIO entries and creates - * directories and files in the already-mounted ramfs root. - * - * Reference: Linux init/initramfs.c - * - * CPIO "newc" header format (110 bytes, ASCII hex fields): - * magic[6] "070701" - * ino[8] mode[8] uid[8] gid[8] nlink[8] mtime[8] - * filesize[8] devmajor[8] devminor[8] rdevmajor[8] rdevminor[8] - * namesize[8] check[8] - * filename (namesize bytes, NUL terminated, padded to 4-byte boundary) - * file data (filesize bytes, padded to 4-byte boundary) - */ - -#include -#include -#include -#include -#include -#include - -/* Linker-provided symbols for the embedded CPIO archive */ -extern "C" const char __initramfs_start[]; -extern "C" const char __initramfs_end[]; - -/* ramfs inode creation (from fs/ramfs/inode.cc) */ -extern inode* ramfs_get_inode(super_block* sb, const inode* dir, - uint32 mode, uint32 rdev); - -/* Global root dentry (from fs/vfs_init.cc) */ -extern dentry* g_root_dentry; - -/* ================================================================== */ -/* CPIO newc header parsing */ -/* ================================================================== */ - -struct cpio_header { - char magic[6]; - char ino[8]; - char mode[8]; - char uid[8]; - char gid[8]; - char nlink[8]; - char mtime[8]; - char filesize[8]; - char devmajor[8]; - char devminor[8]; - char rdevmajor[8]; - char rdevminor[8]; - char namesize[8]; - char check[8]; -}; - -static constexpr int kCpioHeaderSize = 110; - -static uint32 parse_hex8(const char* s) -{ - uint32 val = 0; - for (int i = 0; i < 8; i++) { - char c = s[i]; - uint32 nibble; - if (c >= '0' && c <= '9') nibble = c - '0'; - else if (c >= 'a' && c <= 'f') nibble = c - 'a' + 10; - else if (c >= 'A' && c <= 'F') nibble = c - 'A' + 10; - else return val; - val = (val << 4) | nibble; - } - return val; -} - -static inline uint32 align4(uint32 v) -{ - return (v + 3) & ~3U; -} - -/* ================================================================== */ -/* Path resolution helpers */ -/* ================================================================== */ - -static dentry* cpio_mkdir_p(dentry* root, const char* path, - const char** out_name) -{ - dentry* cur = root; - - while (*path == '/') path++; - - while (true) { - const char* slash = path; - while (*slash && *slash != '/') slash++; - - if (*slash == '\0') { - *out_name = path; - return cur; - } - - uint32 len = static_cast(slash - path); - - qstr name; - name.name = path; - name.len = len; - name.hash = full_name_hash(path, len); - - dentry* child = d_lookup(cur, &name); - if (!child) { - child = d_alloc(cur, &name); - if (!child) return nullptr; - - inode* ip = ramfs_get_inode(cur->d_inode->i_sb, cur->d_inode, - S_IFDIR | 0755, 0); - if (!ip) return nullptr; - - d_instantiate(child, ip); - dget(child); - inc_nlink(cur->d_inode); - } - - cur = child; - path = slash + 1; - while (*path == '/') path++; - } -} - -/* ================================================================== */ -/* populate_rootfs — the main entry point */ -/* ================================================================== */ - -void populate_rootfs() -{ - const char* p = __initramfs_start; - const char* end = __initramfs_end; - uint64 archive_size = static_cast(end - p); - - if (archive_size == 0) { - printk("[initramfs] no embedded archive found\n"); - return; - } - - printk("[initramfs] unpacking %llu bytes...\n", archive_size); - - int file_count = 0; - int dir_count = 0; - - while (p + kCpioHeaderSize <= end) { - auto* hdr = reinterpret_cast(p); - - /* Verify magic */ - if (hdr->magic[0] != '0' || hdr->magic[1] != '7' || - hdr->magic[2] != '0' || hdr->magic[3] != '7' || - hdr->magic[4] != '0' || hdr->magic[5] != '1') { - printk("[initramfs] bad cpio magic at offset %llu\n", - static_cast(p - __initramfs_start)); - break; - } - - uint32 mode = parse_hex8(hdr->mode); - uint32 filesize = parse_hex8(hdr->filesize); - uint32 namesize = parse_hex8(hdr->namesize); - - const char* name = p + kCpioHeaderSize; - uint32 name_padded = align4(kCpioHeaderSize + namesize); - const char* data = p + name_padded; - uint32 data_padded = align4(filesize); - - const char* next = p + name_padded + data_padded; - - /* Check for TRAILER!!! end-of-archive marker */ - if (namesize == 11 && name[0] == 'T' && name[1] == 'R' && - name[2] == 'A' && name[3] == 'I' && name[4] == 'L' && - name[5] == 'E' && name[6] == 'R' && name[7] == '!' && - name[8] == '!' && name[9] == '!') { - break; - } - - /* Skip "." entry */ - if (namesize == 2 && name[0] == '.' && name[1] == '\0') { - p = next; - continue; - } - - /* Resolve parent directory, creating intermediate dirs */ - const char* basename = nullptr; - dentry* parent = cpio_mkdir_p(g_root_dentry, name, &basename); - if (!parent || !basename || basename[0] == '\0') { - p = next; - continue; - } - - /* Build qstr for the final component */ - uint32 blen = 0; - while (basename[blen] && basename[blen] != '/') blen++; - - qstr bname; - bname.name = basename; - bname.len = blen; - bname.hash = full_name_hash(basename, blen); - - if (S_ISDIR(mode)) { - dentry* existing = d_lookup(parent, &bname); - if (!existing) { - dentry* de = d_alloc(parent, &bname); - if (de) { - inode* ip = ramfs_get_inode(parent->d_inode->i_sb, - parent->d_inode, - S_IFDIR | (mode & 0777), 0); - if (ip) { - d_instantiate(de, ip); - dget(de); - inc_nlink(parent->d_inode); - dir_count++; - } - } - } - } else if (S_ISREG(mode)) { - dentry* de = d_alloc(parent, &bname); - if (de) { - inode* ip = ramfs_get_inode(parent->d_inode->i_sb, - parent->d_inode, - S_IFREG | (mode & 0777), 0); - if (ip) { - d_instantiate(de, ip); - dget(de); - - /* Write file content via ramfs write path */ - if (filesize > 0 && ip->i_fop && ip->i_fop->write) { - file tmp_fp = {}; - tmp_fp.f_inode = ip; - tmp_fp.f_path.de = de; - tmp_fp.f_pos = 0; - tmp_fp.f_flags = O_WRONLY; - tmp_fp.f_count.store(1); - - int64 pos = 0; - ip->i_fop->write(&tmp_fp, data, - filesize, &pos); - } - - file_count++; - printk("[initramfs] %s (%u bytes, mode %03o)\n", - name, filesize, mode & 0777); - } - } - } else if (S_ISLNK(mode)) { - /* Symlink: file data is the target path (not NUL-terminated). - * Linux: init/initramfs.c do_symlink() */ - dentry* de = d_alloc(parent, &bname); - if (de) { - inode* ip = ramfs_get_inode(parent->d_inode->i_sb, - parent->d_inode, - S_IFLNK | 0777, 0); - if (ip) { - /* Store symlink target in inode. - * Allocate a kernel buffer and copy the target. */ - char* target = new char[filesize + 1]; - if (target) { - for (uint32 i = 0; i < filesize; i++) - target[i] = data[i]; - target[filesize] = '\0'; - ip->i_link = target; - } - - d_instantiate(de, ip); - dget(de); - file_count++; - printk("[initramfs] %s -> %s (symlink)\n", - name, target ? target : "?"); - } - } - } - - p = next; - } - - printk("[initramfs] done: %d files, %d dirs\n", - file_count, dir_count); -} diff --git a/na/fs/inode.cc b/na/fs/inode.cc deleted file mode 100644 index 1d3d006..0000000 --- a/na/fs/inode.cc +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * inode.cc — Inode allocation, lifecycle, and generic helpers - */ - -#include -#include -#include -#include - -namespace { -lume::atomic g_next_ino{1}; -} // namespace - -/* ================================================================== */ -/* new_inode — allocate a fresh inode on a superblock */ -/* ================================================================== */ - -inode* new_inode(super_block* sb) -{ - inode* ip = nullptr; - - if (sb->s_op && sb->s_op->alloc_inode) - ip = sb->s_op->alloc_inode(sb); - else - ip = new inode{}; - - if (!ip) return nullptr; - - /* Zero-init key fields */ - ip->i_sb = sb; - ip->i_ino = g_next_ino.fetch_add(1); - ip->i_mode = 0; - ip->i_flags = 0; - ip->i_uid = 0; - ip->i_gid = 0; - ip->i_nlink = 1; - ip->i_rdev = 0; - ip->i_size = 0; - ip->i_blocks = 0; - ip->i_bytes = 0; - ip->i_blkbits = sb->s_blocksize_bits; - ip->i_generation = 0; - ip->i_state = I_NEW; - ip->i_count.store(1); - ip->i_writecount.store(0); - - ip->i_atime_sec = ip->i_mtime_sec = ip->i_ctime_sec = 0; - ip->i_atime_nsec = ip->i_mtime_nsec = ip->i_ctime_nsec = 0; - - ip->i_op = nullptr; - ip->i_fop = nullptr; - ip->i_mapping = &ip->i_data; - ip->i_private = nullptr; - ip->i_pipe = nullptr; - - /* Init embedded address_space */ - ip->i_data.host = ip; - ip->i_data.a_ops = nullptr; - ip->i_data.nrpages = 0; - ip->i_data.flags = 0; - - /* Init lists */ - list_init(&ip->i_sb_list); - list_init(&ip->i_dentry); - - /* Add to superblock's inode list */ - { - LockGuard guard(sb->s_inode_list_lock); - list_add_tail(&ip->i_sb_list, &sb->s_inodes); - } - - return ip; -} - -/* ================================================================== */ -/* ihold / iput — reference counting */ -/* ================================================================== */ - -void ihold(inode* ip) -{ - ip->i_count.fetch_add(1); -} - -void iput(inode* ip) -{ - if (!ip) return; - - int old = ip->i_count.fetch_sub(1); - if (old > 1) return; - - /* Last reference dropped — evict */ - if (ip->i_sb->s_op && ip->i_sb->s_op->evict_inode) - ip->i_sb->s_op->evict_inode(ip); - - /* Remove from sb list */ - { - LockGuard guard(ip->i_sb->s_inode_list_lock); - list_del(&ip->i_sb_list); - } - - if (ip->i_sb->s_op && ip->i_sb->s_op->destroy_inode) - ip->i_sb->s_op->destroy_inode(ip); - else - delete ip; -} - -/* ================================================================== */ -/* nlink helpers */ -/* ================================================================== */ - -void set_nlink(inode* ip, uint32 nlink) { ip->i_nlink = nlink; } -void inc_nlink(inode* ip) { ip->i_nlink++; } -void drop_nlink(inode* ip) { ip->i_nlink--; } -void clear_nlink(inode* ip) { ip->i_nlink = 0; } - -/* ================================================================== */ -/* inode_init_owner */ -/* ================================================================== */ - -void inode_init_owner(inode* ip, const inode* dir, uint32 mode) -{ - ip->i_uid = 0; /* Phase 1: always root */ - ip->i_gid = 0; - ip->i_mode = mode; -} - -/* ================================================================== */ -/* generic_fillattr — fill kstat from inode (used by getattr) */ -/* ================================================================== */ - -void generic_fillattr(inode* ip, kstat* stat) -{ - stat->st_dev = ip->i_sb->s_dev; - stat->st_ino = ip->i_ino; - stat->st_mode = ip->i_mode; - stat->st_nlink = ip->i_nlink; - stat->st_uid = ip->i_uid; - stat->st_gid = ip->i_gid; - stat->st_rdev = ip->i_rdev; - stat->__pad = 0; - stat->st_size = ip->i_size; - stat->st_blksize = 1 << ip->i_blkbits; - stat->__pad2 = 0; - stat->st_blocks = ip->i_blocks; - stat->st_atime_sec = ip->i_atime_sec; - stat->st_atime_nsec = ip->i_atime_nsec; - stat->st_mtime_sec = ip->i_mtime_sec; - stat->st_mtime_nsec = ip->i_mtime_nsec; - stat->st_ctime_sec = ip->i_ctime_sec; - stat->st_ctime_nsec = ip->i_ctime_nsec; - stat->__unused[0] = 0; - stat->__unused[1] = 0; -} - -/* ================================================================== */ -/* mark_inode_dirty (stub) */ -/* ================================================================== */ - -void mark_inode_dirty(inode* ip) -{ - ip->i_state |= I_DIRTY_SYNC; - /* Phase 1: no writeback, ramfs is memory-only */ -} diff --git a/na/fs/module.mk b/na/fs/module.mk deleted file mode 100644 index f1be157..0000000 --- a/na/fs/module.mk +++ /dev/null @@ -1,17 +0,0 @@ -# fs/module.mk — VFS subsystem build rules - -SRCS_CC += fs/super.cc -SRCS_CC += fs/inode.cc -SRCS_CC += fs/dcache.cc -SRCS_CC += fs/file_table.cc -SRCS_CC += fs/fd.cc -SRCS_CC += fs/fs_struct.cc -SRCS_CC += fs/namei.cc -SRCS_CC += fs/ramfs/inode.cc -SRCS_CC += fs/ramfs/file.cc -SRCS_CC += fs/ramfs/super.cc -SRCS_CC += fs/vfs_init.cc -SRCS_CC += fs/chardev.cc -SRCS_CC += fs/devfs.cc -SRCS_CC += fs/pipe.cc -SRCS_CC += fs/initramfs.cc diff --git a/na/fs/namei.cc b/na/fs/namei.cc deleted file mode 100644 index 9124ea9..0000000 --- a/na/fs/namei.cc +++ /dev/null @@ -1,250 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * namei.cc — Pathname resolution (path lookup) - * - * Simplified Phase 1: no symlink, no RCU, no mount traversal. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* Helpers */ -/* ================================================================== */ - -static const char* next_component(const char* path, qstr* result) -{ - while (*path == '/') path++; - if (*path == '\0') return nullptr; - - const char* start = path; - while (*path != '\0' && *path != '/') path++; - - uint32 len = static_cast(path - start); - result->name = start; - result->len = len; - result->hash = full_name_hash(start, len); - - return path; -} - -static bool is_dot(const qstr* name) { - return name->len == 1 && name->name[0] == '.'; -} -static bool is_dotdot(const qstr* name) { - return name->len == 2 && name->name[0] == '.' && name->name[1] == '.'; -} - -/* ================================================================== */ -/* walk_component */ -/* ================================================================== */ - -static dentry* walk_component(dentry* parent, const qstr* name) -{ - if (!parent->d_inode || !S_ISDIR(parent->d_inode->i_mode)) - return reinterpret_cast(-ENOTDIR); - - if (is_dot(name)) - return dget(parent); - - if (is_dotdot(name)) - return dget(parent->d_parent); - - /* Try dcache */ - dentry* child = d_lookup(parent, name); - if (child) - return child; - - /* Cache miss — call filesystem lookup */ - inode* dir = parent->d_inode; - if (!dir->i_op || !dir->i_op->lookup) - return reinterpret_cast(-ENOENT); - - child = d_alloc(parent, name); - if (!child) - return reinterpret_cast(-ENOMEM); - - dentry* result = dir->i_op->lookup(dir, child, 0); - if (result) { - dput(child); - return result; - } - - /* If lookup didn't set an inode, this is a negative dentry - * (file doesn't exist). Remove from dcache and return error. */ - if (!child->d_inode) { - d_drop(child); - dput(child); - return reinterpret_cast(-ENOENT); - } - - return child; -} - -/* ================================================================== */ -/* filename_lookup */ -/* ================================================================== */ - -int filename_lookup(int dfd, const char* name, unsigned int flags, path* result) -{ - if (!name || !result) - return -EINVAL; - - TaskControlBlock* task = current_task(); - dentry* start; - - /* Determine starting dentry */ - if (name[0] == '/') { - start = task->fs->root.de; - if (!start) start = g_root_dentry; - name++; - } else if (dfd == AT_FDCWD) { - start = task->fs->pwd.de; - if (!start) start = g_root_dentry; - } else { - file* dir_fp = fcheck(task->files, dfd); - if (!dir_fp) return -EBADF; - start = dir_fp->f_path.de; - if (!start || !start->d_inode || !S_ISDIR(start->d_inode->i_mode)) - return -ENOTDIR; - } - - dentry* current_de = dget(start); - const char* p = name; - - while (p && *p) { - qstr component; - const char* next = next_component(p, &component); - if (!next && component.len == 0) - break; - - const char* after = next; - while (after && *after == '/') after++; - bool is_last = (!after || *after == '\0'); - - if (is_last && (flags & LOOKUP_PARENT)) { - result->de = current_de; - result->mnt = task->fs->root.mnt; - return 0; - } - - dentry* child = walk_component(current_de, &component); - - int64 err = reinterpret_cast(child); - if (err < 0 && err >= -4095) { - dput(current_de); - return static_cast(err); - } - - /* ---- Symlink following (Linux: step_into → pick_link) ---- - * If the resolved component is a symlink, follow it. - * Linux limits this to MAXSYMLINKS (40); we use 8. */ - constexpr int kMaxSymlinks = 8; - int symlink_depth = 0; - while (child->d_inode && S_ISLNK(child->d_inode->i_mode) && - child->d_inode->i_link && symlink_depth < kMaxSymlinks) { - symlink_depth++; - const char* target = child->d_inode->i_link; - dput(child); - - /* Absolute symlink: restart from root. - * Relative symlink: resolve from current parent. */ - dentry* link_start; - if (target[0] == '/') { - link_start = dget(task->fs->root.de ? task->fs->root.de : g_root_dentry); - target++; - } else { - link_start = dget(current_de); - } - - /* Walk each component of the symlink target */ - dentry* link_cur = link_start; - const char* lp = target; - qstr lcomp; - while (lp && *lp) { - const char* lnext = next_component(lp, &lcomp); - if (!lnext && lcomp.len == 0) break; - - dentry* lchild = walk_component(link_cur, &lcomp); - int64 lerr = reinterpret_cast(lchild); - if (lerr < 0 && lerr >= -4095) { - dput(link_cur); - dput(current_de); - return static_cast(lerr); - } - - dput(link_cur); - link_cur = lchild; - lp = lnext; - } - - child = link_cur; - } - - if (symlink_depth >= kMaxSymlinks) { - dput(child); - dput(current_de); - return -ELOOP; - } - - if (!is_last && child->d_inode && !S_ISDIR(child->d_inode->i_mode)) { - dput(child); - dput(current_de); - return -ENOTDIR; - } - - if (!is_last && !child->d_inode) { - dput(child); - dput(current_de); - return -ENOENT; - } - - dput(current_de); - current_de = child; - p = next; - } - - if ((flags & LOOKUP_DIRECTORY) && current_de->d_inode && - !S_ISDIR(current_de->d_inode->i_mode)) { - dput(current_de); - return -ENOTDIR; - } - - /* Negative dentry: the path was walked, but the final component - * does not exist (d_inode == NULL). Return -ENOENT. */ - if (!current_de->d_inode) { - dput(current_de); - return -ENOENT; - } - - result->de = current_de; - result->mnt = task->fs->root.mnt; - return 0; -} - -int kern_path(const char* name, unsigned int flags, path* result) -{ - return filename_lookup(AT_FDCWD, name, flags, result); -} - -dentry* lookup_one_len(const char* name, dentry* parent, int len) -{ - qstr q; - q.name = name; - q.len = static_cast(len); - q.hash = full_name_hash(name, q.len); - - return walk_component(parent, &q); -} - -void namei_init() {} diff --git a/na/fs/pipe.cc b/na/fs/pipe.cc deleted file mode 100644 index f7708f5..0000000 --- a/na/fs/pipe.cc +++ /dev/null @@ -1,278 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pipe.cc — Pipe implementation - * - * Implements unidirectional byte-stream pipes with blocking I/O. - * Each pipe has a 4KB circular buffer. Reads block when empty, - * writes block when full (unless O_NONBLOCK). - * - * When all writers close, read returns 0 (EOF). - * When all readers close, write returns -EPIPE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* pipe_inode_info lifecycle */ -/* ================================================================== */ - -pipe_inode_info* alloc_pipe_info() -{ - auto* pi = new pipe_inode_info{}; - if (!pi) return nullptr; - - pi->rd_pos = 0; - pi->wr_pos = 0; - pi->len = 0; - pi->rd_wait.init("pipe_rd"); - pi->wr_wait.init("pipe_wr"); - pi->readers.store(0); - pi->writers.store(0); - pi->refcount.store(1); - return pi; -} - -void free_pipe_info(pipe_inode_info* pipe) -{ - delete pipe; -} - -static void pipe_get(pipe_inode_info* pi) -{ - pi->refcount.fetch_add(1); -} - -static void pipe_put(pipe_inode_info* pi) -{ - if (pi->refcount.fetch_sub(1) == 1) { - free_pipe_info(pi); - } -} - -/* ================================================================== */ -/* pipe_read */ -/* ================================================================== */ - -static int64 pipe_read(file* fp, char* buf, uint64 count, int64* /*pos*/) -{ - pipe_inode_info* pi = static_cast(fp->private_data); - if (!pi) return -EBADF; - - while (true) { - pi->lock.acquire(); - - if (pi->len > 0) { - /* Data available — copy out */ - int64 to_read = static_cast(count); - if (to_read > pi->len) to_read = pi->len; - - for (int64 i = 0; i < to_read; i++) { - buf[i] = pi->buf[pi->rd_pos]; - pi->rd_pos = (pi->rd_pos + 1) % PIPE_BUF_SIZE; - } - pi->len -= static_cast(to_read); - - pi->lock.release(); - - /* Wake any blocked writers */ - pi->wr_wait.wake_all(); - return to_read; - } - - /* Buffer empty */ - if (pi->writers.load() == 0) { - /* No writers left — EOF */ - pi->lock.release(); - return 0; - } - - if (fp->f_flags & O_NONBLOCK) { - pi->lock.release(); - return -EAGAIN; - } - - pi->lock.release(); - - /* Sleep until data or writer closes */ - pi->rd_wait.sleep(); - } -} - -/* ================================================================== */ -/* pipe_write */ -/* ================================================================== */ - -static int64 pipe_write(file* fp, const char* buf, uint64 count, int64* /*pos*/) -{ - pipe_inode_info* pi = static_cast(fp->private_data); - if (!pi) return -EBADF; - - if (pi->readers.load() == 0) { - return -EPIPE; /* Broken pipe */ - } - - int64 written = 0; - - while (static_cast(written) < count) { - pi->lock.acquire(); - - if (pi->len < PIPE_BUF_SIZE) { - /* Space available — copy in */ - int avail = PIPE_BUF_SIZE - pi->len; - int64 remaining = static_cast(count) - written; - int to_write = avail < static_cast(remaining) - ? avail - : static_cast(remaining); - - for (int i = 0; i < to_write; i++) { - pi->buf[pi->wr_pos] = buf[written + i]; - pi->wr_pos = (pi->wr_pos + 1) % PIPE_BUF_SIZE; - } - pi->len += to_write; - written += to_write; - - pi->lock.release(); - - /* Wake any blocked readers */ - pi->rd_wait.wake_all(); - continue; - } - - /* Buffer full */ - if (pi->readers.load() == 0) { - pi->lock.release(); - return written > 0 ? written : -EPIPE; - } - - if (fp->f_flags & O_NONBLOCK) { - pi->lock.release(); - return written > 0 ? written : -EAGAIN; - } - - pi->lock.release(); - - /* Sleep until space or reader closes */ - pi->wr_wait.sleep(); - } - - return written; -} - -/* ================================================================== */ -/* pipe_release — called on last fput of a pipe fd */ -/* ================================================================== */ - -static int pipe_read_release(inode* /*ip*/, file* fp) -{ - pipe_inode_info* pi = static_cast(fp->private_data); - if (pi) { - pi->readers.fetch_sub(1); - /* Wake writers so they see readers==0 and get EPIPE */ - pi->wr_wait.wake_all(); - pipe_put(pi); - } - return 0; -} - -static int pipe_write_release(inode* /*ip*/, file* fp) -{ - pipe_inode_info* pi = static_cast(fp->private_data); - if (pi) { - pi->writers.fetch_sub(1); - /* Wake readers so they see writers==0 and get EOF */ - pi->rd_wait.wake_all(); - pipe_put(pi); - } - return 0; -} - -/* ================================================================== */ -/* file_operations tables */ -/* ================================================================== */ - -static file_operations pipe_read_fops; -static file_operations pipe_write_fops; -static bool pipe_fops_initialized = false; - -static void pipe_fops_init() -{ - if (pipe_fops_initialized) return; - - pipe_read_fops = {}; - pipe_read_fops.read = pipe_read; - pipe_read_fops.release = pipe_read_release; - - pipe_write_fops = {}; - pipe_write_fops.write = pipe_write; - pipe_write_fops.release = pipe_write_release; - - pipe_fops_initialized = true; -} - -/* ================================================================== */ -/* do_pipe2 — create a pipe, install two fds */ -/* ================================================================== */ - -int do_pipe2(files_struct* files, int fds[2], int flags) -{ - pipe_fops_init(); - - pipe_inode_info* pi = alloc_pipe_info(); - if (!pi) return -ENOMEM; - - /* Create read-end file */ - file* rd_fp = alloc_empty_file(O_RDONLY | (flags & O_NONBLOCK)); - if (!rd_fp) { - free_pipe_info(pi); - return -ENFILE; - } - rd_fp->f_op = &pipe_read_fops; - rd_fp->private_data = pi; - - /* Create write-end file */ - file* wr_fp = alloc_empty_file(O_WRONLY | (flags & O_NONBLOCK)); - if (!wr_fp) { - fput(rd_fp); - free_pipe_info(pi); - return -ENFILE; - } - wr_fp->f_op = &pipe_write_fops; - wr_fp->private_data = pi; - - /* Bump pipe refcount for the two files */ - pipe_get(pi); /* wr_fp gets second ref */ - pi->readers.store(1); - pi->writers.store(1); - - /* Allocate fds */ - int rd_fd = alloc_fd(files, 0); - if (rd_fd < 0) { - fput(rd_fp); - fput(wr_fp); - return rd_fd; - } - - int wr_fd = alloc_fd(files, 0); - if (wr_fd < 0) { - close_fd(files, rd_fd); - fput(wr_fp); - return wr_fd; - } - - fd_install(files, rd_fd, rd_fp); - fd_install(files, wr_fd, wr_fp); - - fds[0] = rd_fd; - fds[1] = wr_fd; - - return 0; -} diff --git a/na/fs/ramfs/file.cc b/na/fs/ramfs/file.cc deleted file mode 100644 index 6d9a070..0000000 --- a/na/fs/ramfs/file.cc +++ /dev/null @@ -1,216 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/file.cc — RamFS file operations (read/write via in-memory pages) - * - * Phase 1: simple flat buffer per-inode via i_private. - * Will migrate to page_cache integration later. - */ - -#include -#include -#include -#include -#include -#include - -/* - * Simplest possible in-memory file storage: - * i_private points to a dynamically grown buffer. - */ -struct ramfs_file_data { - char* buf; - uint64 capacity; -}; - -static ramfs_file_data* get_or_create_data(inode* ip) -{ - if (!ip->i_private) { - auto* data = new ramfs_file_data{}; - data->buf = nullptr; - data->capacity = 0; - ip->i_private = data; - } - return static_cast(ip->i_private); -} - -static int ensure_capacity(ramfs_file_data* data, uint64 needed) -{ - if (needed <= data->capacity) return 0; - - /* Round up to page size */ - uint64 new_cap = (needed + 4095) & ~4095ULL; - char* new_buf = new char[new_cap]; - if (!new_buf) return -ENOMEM; - - /* Copy old data */ - if (data->buf && data->capacity > 0) { - for (uint64 i = 0; i < data->capacity; i++) - new_buf[i] = data->buf[i]; - } - /* Zero new region */ - for (uint64 i = data->capacity; i < new_cap; i++) - new_buf[i] = 0; - - delete[] data->buf; - data->buf = new_buf; - data->capacity = new_cap; - return 0; -} - -/* ================================================================== */ -/* ramfs_read */ -/* ================================================================== */ - -static int64 ramfs_read(file* fp, char* buf, uint64 count, int64* pos) -{ - inode* ip = file_inode(fp); - int64 offset = *pos; - - if (offset < 0) return -EINVAL; - if (offset >= ip->i_size) return 0; - - int64 avail = ip->i_size - offset; - if (static_cast(count) > avail) - count = static_cast(avail); - - auto* data = static_cast(ip->i_private); - if (!data || !data->buf) return 0; - - /* Copy to user buffer. - * TODO: use copy_to_user when called from syscall context */ - for (uint64 i = 0; i < count; i++) - buf[i] = data->buf[offset + i]; - - *pos += count; - return static_cast(count); -} - -/* ================================================================== */ -/* ramfs_write */ -/* ================================================================== */ - -static int64 ramfs_write(file* fp, const char* buf, uint64 count, int64* pos) -{ - inode* ip = file_inode(fp); - int64 offset = *pos; - - if (fp->f_flags & O_APPEND) - offset = ip->i_size; - - if (offset < 0) return -EINVAL; - - auto* data = get_or_create_data(ip); - uint64 end = static_cast(offset) + count; - - int err = ensure_capacity(data, end); - if (err) return err; - - /* Copy from user buffer */ - for (uint64 i = 0; i < count; i++) - data->buf[offset + i] = buf[i]; - - if (static_cast(end) > ip->i_size) - ip->i_size = end; - - *pos = end; - mark_inode_dirty(ip); - return static_cast(count); -} - -/* ================================================================== */ -/* ramfs_dir_iterate — getdents64 */ -/* ================================================================== */ - -static int ramfs_dir_iterate(file* fp, dir_context* ctx) -{ - dentry* dir_de = file_dentry(fp); - inode* dir_ip = file_inode(fp); - int64 pos = ctx->pos; - int count = 0; - - /* pos 0: emit "." */ - if (pos == 0) { - if (!ctx->actor(ctx, ".", 1, pos, dir_ip->i_ino, DT_DIR)) - return 0; - ctx->pos = 1; - pos = 1; - count++; - } - - /* pos 1: emit ".." */ - if (pos == 1) { - uint64 parent_ino = dir_de->d_parent->d_inode - ? dir_de->d_parent->d_inode->i_ino : dir_ip->i_ino; - if (!ctx->actor(ctx, "..", 2, pos, parent_ino, DT_DIR)) - return 0; - ctx->pos = 2; - pos = 2; - count++; - } - - /* pos >= 2: iterate children */ - int64 child_idx = 2; - list_node* node; - list_for_each(node, &dir_de->d_children) { - dentry* child = list_entry(node, dentry, d_child); - - if (!child->d_inode) - continue; /* skip negative dentries */ - - if (child_idx >= pos) { - uint8 d_type = DT_UNKNOWN; - if (S_ISDIR(child->d_inode->i_mode)) d_type = DT_DIR; - else if (S_ISREG(child->d_inode->i_mode)) d_type = DT_REG; - else if (S_ISCHR(child->d_inode->i_mode)) d_type = DT_CHR; - else if (S_ISBLK(child->d_inode->i_mode)) d_type = DT_BLK; - else if (S_ISLNK(child->d_inode->i_mode)) d_type = DT_LNK; - else if (S_ISFIFO(child->d_inode->i_mode)) d_type = DT_FIFO; - - if (!ctx->actor(ctx, child->d_name.name, - static_cast(child->d_name.len), - child_idx, child->d_inode->i_ino, d_type)) - return 0; - - ctx->pos = child_idx + 1; - count++; - } - child_idx++; - } - - return count; -} - -extern file_operations ramfs_file_operations; -extern file_operations ramfs_dir_operations; - -file_operations ramfs_file_operations; -file_operations ramfs_dir_operations; - -void ramfs_file_ops_init() -{ - ramfs_file_operations.llseek = nullptr; - ramfs_file_operations.read = ramfs_read; - ramfs_file_operations.write = ramfs_write; - ramfs_file_operations.iterate_shared = nullptr; - ramfs_file_operations.poll = nullptr; - ramfs_file_operations.unlocked_ioctl = nullptr; - ramfs_file_operations.mmap = nullptr; - ramfs_file_operations.open = nullptr; - ramfs_file_operations.flush = nullptr; - ramfs_file_operations.release = nullptr; - ramfs_file_operations.fsync = nullptr; - - ramfs_dir_operations.llseek = nullptr; - ramfs_dir_operations.read = nullptr; - ramfs_dir_operations.write = nullptr; - ramfs_dir_operations.iterate_shared = ramfs_dir_iterate; - ramfs_dir_operations.poll = nullptr; - ramfs_dir_operations.unlocked_ioctl = nullptr; - ramfs_dir_operations.mmap = nullptr; - ramfs_dir_operations.open = nullptr; - ramfs_dir_operations.flush = nullptr; - ramfs_dir_operations.release = nullptr; - ramfs_dir_operations.fsync = nullptr; -} diff --git a/na/fs/ramfs/inode.cc b/na/fs/ramfs/inode.cc deleted file mode 100644 index c5e49bf..0000000 --- a/na/fs/ramfs/inode.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/inode.cc — RamFS inode operations - * - * A simple in-memory filesystem (like Linux tmpfs without swap). - * All data lives in memory via the page cache. - */ - -#include -#include -#include -#include -#include -#include - -/* Forward declarations */ -extern file_operations ramfs_file_operations; -extern file_operations ramfs_dir_operations; -extern inode_operations ramfs_dir_inode_operations; - -/* ================================================================== */ -/* ramfs_get_inode — allocate + init an inode for ramfs */ -/* ================================================================== */ - -inode* ramfs_get_inode(super_block* sb, const inode* dir, uint32 mode, uint32 rdev) -{ - inode* ip = new_inode(sb); - if (!ip) return nullptr; - - inode_init_owner(ip, dir, mode); - /* TODO: set timestamps to current time when we have a time source */ - - switch (mode & S_IFMT) { - case S_IFREG: - ip->i_op = nullptr; /* regular files don't need inode_ops for Phase 1 */ - ip->i_fop = &ramfs_file_operations; - break; - case S_IFDIR: - ip->i_op = &ramfs_dir_inode_operations; - ip->i_fop = &ramfs_dir_operations; - inc_nlink(ip); /* directories start with nlink=2 (. and parent's link) */ - break; - case S_IFLNK: - /* Symlink: stub for Phase 1 */ - break; - default: - /* Character/block devices: i_rdev */ - ip->i_rdev = rdev; - break; - } - - return ip; -} - -/* ================================================================== */ -/* Directory inode operations */ -/* ================================================================== */ - -static dentry* ramfs_lookup(inode* dir, dentry* de, unsigned int flags) -{ - (void)dir; - (void)flags; - /* For ramfs, everything is in dcache. If we get here, it means - * d_lookup() already failed, so this is a negative dentry. - * Just return nullptr to let the caller see de->d_inode == NULL. */ - return nullptr; -} - -static int ramfs_create(inode* dir, dentry* de, uint32 mode, bool excl) -{ - (void)excl; - inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFREG, 0); - if (!ip) return -ENOMEM; - - d_instantiate(de, ip); - dget(de); - dir->i_size += de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -static int ramfs_mkdir(inode* dir, dentry* de, uint32 mode) -{ - inode* ip = ramfs_get_inode(dir->i_sb, dir, mode | S_IFDIR, 0); - if (!ip) return -ENOMEM; - - d_instantiate(de, ip); - dget(de); - inc_nlink(dir); - dir->i_size += de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -static int ramfs_rmdir(inode* dir, dentry* de) -{ - /* Check if directory is empty */ - if (!list_empty(&de->d_children)) - return -ENOTEMPTY; - - drop_nlink(de->d_inode); /* remove . link */ - drop_nlink(de->d_inode); /* remove parent's link */ - drop_nlink(dir); - d_delete(de); - dir->i_size -= de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -static int ramfs_unlink(inode* dir, dentry* de) -{ - drop_nlink(de->d_inode); - d_delete(de); - dir->i_size -= de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -static int ramfs_link(dentry* old_de, inode* dir, dentry* new_de) -{ - inode* ip = old_de->d_inode; - if (!ip) return -ENOENT; - - inc_nlink(ip); - ihold(ip); - d_instantiate(new_de, ip); - dget(new_de); - dir->i_size += new_de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -static int ramfs_mknod(inode* dir, dentry* de, uint32 mode, uint32 rdev) -{ - inode* ip = ramfs_get_inode(dir->i_sb, dir, mode, rdev); - if (!ip) return -ENOMEM; - - d_instantiate(de, ip); - dget(de); - dir->i_size += de->d_name.len; - mark_inode_dirty(dir); - return 0; -} - -inode_operations ramfs_dir_inode_operations; - -void ramfs_dir_inode_ops_init() -{ - ramfs_dir_inode_operations.lookup = ramfs_lookup; - ramfs_dir_inode_operations.create = ramfs_create; - ramfs_dir_inode_operations.link = ramfs_link; - ramfs_dir_inode_operations.unlink = ramfs_unlink; - ramfs_dir_inode_operations.symlink = nullptr; - ramfs_dir_inode_operations.mkdir = ramfs_mkdir; - ramfs_dir_inode_operations.rmdir = ramfs_rmdir; - ramfs_dir_inode_operations.mknod = ramfs_mknod; - ramfs_dir_inode_operations.rename = nullptr; - ramfs_dir_inode_operations.setattr = nullptr; - ramfs_dir_inode_operations.getattr = nullptr; - ramfs_dir_inode_operations.get_link = nullptr; - ramfs_dir_inode_operations.permission = nullptr; -} diff --git a/na/fs/ramfs/super.cc b/na/fs/ramfs/super.cc deleted file mode 100644 index 6f929f2..0000000 --- a/na/fs/ramfs/super.cc +++ /dev/null @@ -1,154 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/super.cc — RamFS superblock: mount, fill_super, and init - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -extern inode* ramfs_get_inode(super_block* sb, const inode* dir, - uint32 mode, uint32 rdev); - -/* Superblock allocation helper (defined in super.cc) */ -extern super_block* alloc_super(file_system_type* type); - -/* ================================================================== */ -/* ramfs super_operations */ -/* ================================================================== */ - -static inode* ramfs_alloc_inode(super_block* sb) -{ - return new inode{}; -} - -static void ramfs_destroy_inode(inode* ip) -{ - /* Free ramfs_file_data if any */ - if (ip->i_private) { - struct ramfs_file_data { - char* buf; - uint64 capacity; - }; - auto* data = static_cast(ip->i_private); - delete[] data->buf; - delete data; - } - delete ip; -} - -static int ramfs_statfs(dentry* de, kstatfs* buf) -{ - /* Simple fake values */ - buf->f_type = 0x858458f6; /* RAMFS_MAGIC */ - buf->f_bsize = 4096; - buf->f_blocks = 0; - buf->f_bfree = 0; - buf->f_bavail = 0; - buf->f_files = 0; - buf->f_ffree = 0; - buf->f_namelen = 255; - buf->f_frsize = 4096; - buf->f_flags = 0; - return 0; -} - -static super_operations ramfs_sops; - -static void ramfs_sops_init() -{ - ramfs_sops.alloc_inode = ramfs_alloc_inode; - ramfs_sops.destroy_inode = ramfs_destroy_inode; - ramfs_sops.free_inode = nullptr; - ramfs_sops.dirty_inode = nullptr; - ramfs_sops.write_inode = nullptr; - ramfs_sops.drop_inode = nullptr; - ramfs_sops.evict_inode = nullptr; - ramfs_sops.put_super = nullptr; - ramfs_sops.sync_fs = nullptr; - ramfs_sops.statfs = ramfs_statfs; - ramfs_sops.umount_begin = nullptr; -} - -/* ================================================================== */ -/* ramfs_fill_super */ -/* ================================================================== */ - -static int ramfs_fill_super(super_block* sb, void* data, int silent) -{ - sb->s_op = &ramfs_sops; - sb->s_magic = 0x858458f6; - sb->s_blocksize = 4096; - sb->s_blocksize_bits = 12; - sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; - sb->s_time_gran = 1; - - /* Create root inode */ - inode* root_ip = ramfs_get_inode(sb, nullptr, S_IFDIR | 0755, 0); - if (!root_ip) return -ENOMEM; - - /* Create root dentry */ - sb->s_root = d_make_root(root_ip); - if (!sb->s_root) - return -ENOMEM; - - return 0; -} - -/* ================================================================== */ -/* ramfs_mount */ -/* ================================================================== */ - -static super_block* ramfs_mount(file_system_type* type, int flags, - const char* dev_name, void* data) -{ - super_block* sb = alloc_super(type); - if (!sb) return nullptr; - - int err = ramfs_fill_super(sb, data, flags & SB_SILENT); - if (err) { - delete sb; - return nullptr; - } - - return sb; -} - -static void ramfs_kill_sb(super_block* sb) -{ - /* Phase 1: no cleanup needed for ramfs */ - (void)sb; -} - -/* ================================================================== */ -/* Filesystem type registration */ -/* ================================================================== */ - -static file_system_type ramfs_fs_type; - -extern void ramfs_file_ops_init(); -extern void ramfs_dir_inode_ops_init(); - -void ramfs_init() -{ - /* Runtime init to avoid un-relocated absolute pointers in .data */ - ramfs_sops_init(); - ramfs_file_ops_init(); - ramfs_dir_inode_ops_init(); - - ramfs_fs_type.name = "ramfs"; - ramfs_fs_type.fs_flags = FS_NO_DEVICE; - ramfs_fs_type.mount = ramfs_mount; - ramfs_fs_type.kill_sb = ramfs_kill_sb; - list_init(&ramfs_fs_type.fs_list); - - register_filesystem(&ramfs_fs_type); -} - diff --git a/na/fs/super.cc b/na/fs/super.cc deleted file mode 100644 index 231b57c..0000000 --- a/na/fs/super.cc +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * super.cc — Superblock lifecycle + filesystem type registry - */ - -#include -#include -#include -#include -#include -#include - -/* Global root filesystem entry points */ -vfsmount* g_root_mnt = nullptr; -dentry* g_root_dentry = nullptr; - -/* ================================================================== */ -/* Filesystem type registry */ -/* ================================================================== */ - -namespace { - -list_head g_fs_types; -Spinlock g_fs_type_lock; - -} // namespace - -int register_filesystem(file_system_type* fs) -{ - LockGuard guard(g_fs_type_lock); - list_add_tail(&fs->fs_list, &g_fs_types); - printk("[vfs] registered filesystem: %s\n", fs->name); - return 0; -} - -file_system_type* get_fs_type(const char* name) -{ - LockGuard guard(g_fs_type_lock); - list_node* pos; - list_for_each(pos, &g_fs_types) { - auto* fs = list_entry(pos, file_system_type, fs_list); - /* Simple strcmp */ - const char* a = fs->name; - const char* b = name; - while (*a && *a == *b) { a++; b++; } - if (*a == '\0' && *b == '\0') - return fs; - } - return nullptr; -} - -/* ================================================================== */ -/* Superblock allocation */ -/* ================================================================== */ - -super_block* alloc_super(file_system_type* type) -{ - auto* sb = new super_block{}; - if (!sb) return nullptr; - - list_init(&sb->s_list); - list_init(&sb->s_inodes); - sb->s_type = type; - sb->s_op = nullptr; - sb->s_root = nullptr; - sb->s_fs_info = nullptr; - sb->s_blocksize = 4096; - sb->s_blocksize_bits = 12; - sb->s_maxbytes = 0x7FFFFFFFFFFFFFFFLL; - sb->s_time_gran = 1; - sb->s_time_min = 0; - sb->s_time_max = 0x7FFFFFFFFFFFFFFFLL; - sb->s_flags = 0; - sb->s_magic = 0; - sb->s_active.store(1); - sb->s_count = 1; - sb->s_max_links = 0; - sb->s_d_op = nullptr; - sb->s_mounts = nullptr; - sb->s_dev = 0; - for (int i = 0; i < 32; i++) sb->s_id[i] = 0; - - return sb; -} - -/* ================================================================== */ -/* Init */ -/* ================================================================== */ - -void super_init() -{ - list_init(&g_fs_types); -} diff --git a/na/fs/vfs_init.cc b/na/fs/vfs_init.cc deleted file mode 100644 index ec56e88..0000000 --- a/na/fs/vfs_init.cc +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vfs_init.cc — Bootstrap the VFS: mount ramfs root, set global root - * - * Called from kernel_main() after slab_init() and ramfs_init(). - * This is the moral equivalent of Linux's vfs_caches_init() + - * mnt_init() + init_rootfs(). - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* vfs_init — mount ramfs as root filesystem */ -/* ================================================================== */ - -/* - * ramfs_mount is called internally via the file_system_type::mount - * callback registered by ramfs_init(). We look it up by name. - */ -extern file_system_type* get_fs_type(const char* name); -extern super_block* alloc_super(file_system_type* type); - -void vfs_init() -{ - /* 1. Look up the ramfs filesystem type */ - file_system_type* ramfs_type = get_fs_type("ramfs"); - if (!ramfs_type || !ramfs_type->mount) { - printk(KERN_ERR "vfs_init: ramfs not registered!\n"); - return; - } - - /* 2. Mount ramfs */ - super_block* sb = ramfs_type->mount(ramfs_type, 0, "ramfs", nullptr); - if (!sb || !sb->s_root) { - printk(KERN_ERR "vfs_init: failed to mount ramfs!\n"); - return; - } - - /* 3. Set global root */ - g_root_dentry = sb->s_root; - g_root_mnt = nullptr; /* TODO: allocate a proper vfsmount when mount.cc lands */ - - dget(g_root_dentry); /* Hold a reference for the global root */ - - printk(KERN_INFO "[vfs] root filesystem mounted (ramfs)\n"); - - /* 4. Set up VFS state for the current (BSP idle) task, - * which will be inherited by all subsequently created tasks. */ - TaskControlBlock* bsp = current_task(); - if (bsp) { - if (!bsp->files) { - bsp->files = alloc_files_struct(); - } - if (!bsp->fs) { - bsp->fs = alloc_fs_struct(); - if (bsp->fs) { - bsp->fs->root.de = g_root_dentry; - bsp->fs->root.mnt = g_root_mnt; - bsp->fs->pwd.de = g_root_dentry; - bsp->fs->pwd.mnt = g_root_mnt; - dget(g_root_dentry); /* root ref */ - dget(g_root_dentry); /* pwd ref */ - } - } - printk(KERN_INFO "[vfs] BSP task VFS context initialized\n"); - } -} diff --git a/na/include/libc-shim/bits/alltypes.h b/na/include/libc-shim/bits/alltypes.h deleted file mode 100644 index cf21a5e..0000000 --- a/na/include/libc-shim/bits/alltypes.h +++ /dev/null @@ -1,15 +0,0 @@ -/* Shim: provide types musl string functions need */ -#ifndef _BITS_ALLTYPES_H -#define _BITS_ALLTYPES_H - -#ifndef __cplusplus -typedef unsigned long size_t; -#endif - -typedef long ssize_t; -typedef unsigned long uintptr_t; - -/* locale_t stub — unused in kernel, but declared by some headers */ -typedef void *locale_t; - -#endif diff --git a/na/include/libc-shim/endian.h b/na/include/libc-shim/endian.h deleted file mode 100644 index 8346386..0000000 --- a/na/include/libc-shim/endian.h +++ /dev/null @@ -1,9 +0,0 @@ -/* Shim: endianness for musl string functions */ -#ifndef _ENDIAN_H -#define _ENDIAN_H - -#define __LITTLE_ENDIAN 1234 -#define __BIG_ENDIAN 4321 -#define __BYTE_ORDER __LITTLE_ENDIAN /* RISC-V is LE */ - -#endif diff --git a/na/include/libc-shim/features.h b/na/include/libc-shim/features.h deleted file mode 100644 index a3466c8..0000000 --- a/na/include/libc-shim/features.h +++ /dev/null @@ -1,12 +0,0 @@ -/* Shim: musl expects this header */ -#ifndef _FEATURES_H -#define _FEATURES_H - -/* weak_alias: musl uses this for symbol aliasing (e.g. __memrchr -> memrchr) */ -#define weak_alias(old, new) \ - extern __typeof(old) new __attribute__((__weak__, __alias__(#old))) - -/* hidden attribute used by some musl sources */ -#define hidden __attribute__((__visibility__("hidden"))) - -#endif diff --git a/na/include/libc-shim/limits.h b/na/include/libc-shim/limits.h deleted file mode 100644 index a1d546e..0000000 --- a/na/include/libc-shim/limits.h +++ /dev/null @@ -1,17 +0,0 @@ -/* Shim: limits for musl string functions (memmem uses SIZE_MAX) */ -#ifndef _LIMITS_H -#define _LIMITS_H - -#define CHAR_BIT 8 -#define UCHAR_MAX 255 -#define SCHAR_MAX 127 -#define SCHAR_MIN (-128) -#define INT_MAX 2147483647 -#define INT_MIN (-INT_MAX - 1) -#define UINT_MAX 4294967295U -#define LONG_MAX 9223372036854775807L -#define LONG_MIN (-LONG_MAX - 1L) -#define ULONG_MAX 18446744073709551615UL -#define SIZE_MAX ULONG_MAX - -#endif diff --git a/na/include/libc-shim/stdint.h b/na/include/libc-shim/stdint.h deleted file mode 100644 index 650bdcf..0000000 --- a/na/include/libc-shim/stdint.h +++ /dev/null @@ -1,21 +0,0 @@ -/* Shim: stdint types for musl string functions */ -#ifndef _STDINT_H -#define _STDINT_H - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef unsigned long uint64_t; -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef signed long int64_t; -typedef unsigned long uintptr_t; -typedef long intptr_t; - -#define UINT8_MAX 0xFF -#define UINT16_MAX 0xFFFF -#define UINT32_MAX 0xFFFFFFFFU -#define UINT64_MAX 0xFFFFFFFFFFFFFFFFUL - -#endif diff --git a/na/include/libc-shim/string.h b/na/include/libc-shim/string.h deleted file mode 100644 index 8a86304..0000000 --- a/na/include/libc-shim/string.h +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef _STRING_H -#define _STRING_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#if __cplusplus >= 201103L -#define NULL nullptr -#elif defined(__cplusplus) -#define NULL 0L -#else -#define NULL ((void*)0) -#endif - -#define __NEED_size_t -#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ - || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -#define __NEED_locale_t -#endif - -#include - -void *memcpy (void *__restrict, const void *__restrict, size_t); -void *memmove (void *, const void *, size_t); -void *memset (void *, int, size_t); -int memcmp (const void *, const void *, size_t); -void *memchr (const void *, int, size_t); - -char *strcpy (char *__restrict, const char *__restrict); -char *strncpy (char *__restrict, const char *__restrict, size_t); - -char *strcat (char *__restrict, const char *__restrict); -char *strncat (char *__restrict, const char *__restrict, size_t); - -int strcmp (const char *, const char *); -int strncmp (const char *, const char *, size_t); - -int strcoll (const char *, const char *); -size_t strxfrm (char *__restrict, const char *__restrict, size_t); - -char *strchr (const char *, int); -char *strrchr (const char *, int); - -size_t strcspn (const char *, const char *); -size_t strspn (const char *, const char *); -char *strpbrk (const char *, const char *); -char *strstr (const char *, const char *); -char *strtok (char *__restrict, const char *__restrict); - -size_t strlen (const char *); - -char *strerror (int); - -#if defined(_BSD_SOURCE) || defined(_GNU_SOURCE) -#include -#endif - -#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ - || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -char *strtok_r (char *__restrict, const char *__restrict, char **__restrict); -int strerror_r (int, char *, size_t); -char *stpcpy(char *__restrict, const char *__restrict); -char *stpncpy(char *__restrict, const char *__restrict, size_t); -size_t strnlen (const char *, size_t); -char *strdup (const char *); -char *strndup (const char *, size_t); -char *strsignal(int); -char *strerror_l (int, locale_t); -int strcoll_l (const char *, const char *, locale_t); -size_t strxfrm_l (char *__restrict, const char *__restrict, size_t, locale_t); -void *memmem(const void *, size_t, const void *, size_t); -#endif - -#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -void *memccpy (void *__restrict, const void *__restrict, int, size_t); -#endif - -#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) -char *strsep(char **, const char *); -size_t strlcat (char *, const char *, size_t); -size_t strlcpy (char *, const char *, size_t); -void explicit_bzero (void *, size_t); -#endif - -#ifdef _GNU_SOURCE -#define strdupa(x) strcpy(alloca(strlen(x)+1),x) -int strverscmp (const char *, const char *); -char *strchrnul(const char *, int); -char *strcasestr(const char *, const char *); -void *memrchr(const void *, int, size_t); -void *mempcpy(void *, const void *, size_t); -#endif - -/* musl internal symbols used across translation units */ -char *__strchrnul(const char *, int); -void *__memrchr(const void *, int, size_t); -char *__stpcpy(char *__restrict, const char *__restrict); -char *__stpncpy(char *__restrict, const char *__restrict, size_t); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/na/include/libc-shim/strings.h b/na/include/libc-shim/strings.h deleted file mode 100644 index b7a5ea0..0000000 --- a/na/include/libc-shim/strings.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _STRINGS_H -#define _STRINGS_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#define __NEED_size_t -#define __NEED_locale_t -#include - -#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) || defined(_POSIX_SOURCE) \ - || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE+0 < 200809L) \ - || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE+0 < 700) -int bcmp (const void *, const void *, size_t); -void bcopy (const void *, void *, size_t); -void bzero (void *, size_t); -char *index (const char *, int); -char *rindex (const char *, int); -#endif - -#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) || defined(_BSD_SOURCE) -int ffs (int); -int ffsl (long); -int ffsll (long long); -#endif - -int strcasecmp (const char *, const char *); -int strncasecmp (const char *, const char *, size_t); - -int strcasecmp_l (const char *, const char *, locale_t); -int strncasecmp_l (const char *, const char *, size_t, locale_t); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/na/include/lume/addr.h b/na/include/lume/addr.h deleted file mode 100644 index 572b6fe..0000000 --- a/na/include/lume/addr.h +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Machine-Independent Address Conversion Helpers - * - * All PA↔VA conversions in MI code (mm/, kernel/, etc.) must go through - * these functions. The actual offset is provided by . - * - * Reference: docs/specs/00_conventions.md §1.1 (MI/MD separation) - */ - -#include -#include -#include -#include - -/* Convert physical address to kernel virtual address (direct map). */ -inline VirtAddr pa_to_va(PhysAddr pa) -{ - return VirtAddr{pa.raw + arch::g_direct_map_base}; -} - -/* Convert kernel virtual address to physical address (direct map). */ -inline PhysAddr va_to_pa(VirtAddr va) -{ - return PhysAddr{va.raw - arch::g_direct_map_base}; -} - -/* - * Early-boot conversions pinned to the bootstrap direct-map base. - * - * Use these only before vmm_init() activates the final kernel page table. - * They are required when KASLR has already randomized g_direct_map_base, - * but the CPU is still running on the bootstrap mapping built in entry.S. - */ -inline VirtAddr boot_pa_to_va(PhysAddr pa) -{ - return VirtAddr{pa.raw + arch::kDirectMapBaseDefault}; -} - -inline PhysAddr boot_va_to_pa(VirtAddr va) -{ - return PhysAddr{va.raw - arch::kDirectMapBaseDefault}; -} - -/* Align up to page boundary */ -inline uint64 page_align_up(uint64 addr) -{ - return (addr + kPageSize - 1) & ~(kPageSize - 1); -} - -/* Align down to page boundary */ -inline uint64 page_align_down(uint64 addr) -{ - return addr & ~(kPageSize - 1); -} - -inline PhysAddr page_align_up(PhysAddr addr) -{ - return PhysAddr{page_align_up(addr.raw)}; -} - -inline PhysAddr page_align_down(PhysAddr addr) -{ - return PhysAddr{page_align_down(addr.raw)}; -} - -inline VirtAddr page_align_up(VirtAddr addr) -{ - return VirtAddr{page_align_up(addr.raw)}; -} - -inline VirtAddr page_align_down(VirtAddr addr) -{ - return VirtAddr{page_align_down(addr.raw)}; -} - -/* Convert physical address to page frame number (PFN) */ -inline Pfn pa_to_pfn(PhysAddr pa) -{ - return Pfn{pa.raw / kPageSize}; -} - -/* Convert page frame number (PFN) to physical address */ -inline PhysAddr pfn_to_pa(Pfn pfn) -{ - return PhysAddr{pfn.raw * kPageSize}; -} diff --git a/na/include/lume/addr_types.h b/na/include/lume/addr_types.h deleted file mode 100644 index 91a0779..0000000 --- a/na/include/lume/addr_types.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include - -struct PhysAddr { - uint64 raw; -}; - -struct VirtAddr { - uint64 raw; -}; - -struct Pfn { - uint64 raw; -}; - -inline constexpr PhysAddr phys_addr(uint64 raw) { return PhysAddr{raw}; } -inline constexpr VirtAddr virt_addr(uint64 raw) { return VirtAddr{raw}; } -inline constexpr Pfn pfn(uint64 raw) { return Pfn{raw}; } - -inline constexpr bool operator==(PhysAddr a, PhysAddr b) { return a.raw == b.raw; } -inline constexpr bool operator!=(PhysAddr a, PhysAddr b) { return a.raw != b.raw; } -inline constexpr bool operator==(VirtAddr a, VirtAddr b) { return a.raw == b.raw; } -inline constexpr bool operator!=(VirtAddr a, VirtAddr b) { return a.raw != b.raw; } -inline constexpr bool operator==(Pfn a, Pfn b) { return a.raw == b.raw; } -inline constexpr bool operator!=(Pfn a, Pfn b) { return a.raw != b.raw; } diff --git a/na/include/lume/args.h b/na/include/lume/args.h deleted file mode 100644 index 0562dc5..0000000 --- a/na/include/lume/args.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _LINUX_ARGS_H -#define _LINUX_ARGS_H - -/* - * How do these macros work? - * - * In __COUNT_ARGS() _0 to _15 are just placeholders from the start - * in order to make sure _n is positioned over the correct number - * from 15 to 0 (depending on X, which is a variadic argument list). - * They serve no purpose other than occupying a position. Since each - * macro parameter must have a distinct identifier, those identifiers - * are as good as any. - * - * In COUNT_ARGS() we use actual integers, so __COUNT_ARGS() returns - * that as _n. - */ - -/* This counts to 15. Any more, it will return 16th argument. */ -#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n -#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - -/* Concatenate two parameters, but allow them to be expanded beforehand. */ -#define __CONCAT(a, b) a ## b -#define CONCATENATE(a, b) __CONCAT(a, b) - -#endif /* _LINUX_ARGS_H */ diff --git a/na/include/lume/atomic.h b/na/include/lume/atomic.h deleted file mode 100644 index 6e31129..0000000 --- a/na/include/lume/atomic.h +++ /dev/null @@ -1,76 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * lume::atomic — Thin wrapper over __atomic builtins. - * - * All multi-core shared data that does NOT use a spinlock MUST use - * this wrapper instead of raw __atomic_* builtins. - * - * Reference: docs/specs/00_conventions.md §5.3 - */ - -#include - -namespace lume { - -template -class atomic { -public: - atomic() = default; - explicit atomic(T val) : val_(val) {} - - /* Prevent copies — an atomic value has identity */ - atomic(const atomic&) = delete; - atomic& operator=(const atomic&) = delete; - - /* Load with specified memory ordering */ - T load(int order = __ATOMIC_SEQ_CST) const { - return __atomic_load_n(&val_, order); - } - - /* Store with specified memory ordering */ - void store(T desired, int order = __ATOMIC_SEQ_CST) { - __atomic_store_n(&val_, desired, order); - } - - /* Atomic exchange, returns old value */ - T exchange(T desired, int order = __ATOMIC_SEQ_CST) { - return __atomic_exchange_n(&val_, desired, order); - } - - /* Fetch-and-add, returns old value */ - T fetch_add(T delta, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_add(&val_, delta, order); - } - - /* Fetch-and-subtract, returns old value */ - T fetch_sub(T delta, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_sub(&val_, delta, order); - } - - /* Fetch-and-or, returns old value */ - T fetch_or(T mask, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_or(&val_, mask, order); - } - - /* Fetch-and-and, returns old value */ - T fetch_and(T mask, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_and(&val_, mask, order); - } - - /* Compare-and-swap. Returns true on success. */ - bool compare_exchange(T& expected, T desired, - int success_order = __ATOMIC_ACQ_REL, - int failure_order = __ATOMIC_ACQUIRE) { - return __atomic_compare_exchange_n(&val_, &expected, desired, - false, success_order, failure_order); - } - -private: - T val_ = 0; -}; - -} // namespace lume diff --git a/na/include/lume/bitops.h b/na/include/lume/bitops.h deleted file mode 100644 index 4227ca3..0000000 --- a/na/include/lume/bitops.h +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Bit Operations — portable kernel bit manipulation utilities - * - * Provides ctz, clz, ffs, fls and related operations. - * All implementations are manual (no __builtin_ctz) to avoid - * generating libgcc __ctzdi2/__clzdi2 calls in freestanding. - */ - -#include - -namespace lume { - -/* ------------------------------------------------------------------ */ -/* Count Trailing Zeros (ctz) */ -/* Returns the number of trailing 0-bits in val. */ -/* Precondition: val != 0 (undefined if val == 0). */ -/* ------------------------------------------------------------------ */ - -static inline int ctz32(uint32 val) -{ - int n = 0; - if (!(val & 0x0000FFFFU)) { n += 16; val >>= 16; } - if (!(val & 0x000000FFU)) { n += 8; val >>= 8; } - if (!(val & 0x0000000FU)) { n += 4; val >>= 4; } - if (!(val & 0x00000003U)) { n += 2; val >>= 2; } - if (!(val & 0x00000001U)) { n += 1; } - return n; -} - -static inline int ctz64(uint64 val) -{ - if (static_cast(val) != 0) { - return ctz32(static_cast(val)); - } - return 32 + ctz32(static_cast(val >> 32)); -} - -/* ------------------------------------------------------------------ */ -/* Count Leading Zeros (clz) */ -/* Returns the number of leading 0-bits in val. */ -/* Precondition: val != 0 (undefined if val == 0). */ -/* ------------------------------------------------------------------ */ - -static inline int clz32(uint32 val) -{ - int n = 0; - if (!(val & 0xFFFF0000U)) { n += 16; val <<= 16; } - if (!(val & 0xFF000000U)) { n += 8; val <<= 8; } - if (!(val & 0xF0000000U)) { n += 4; val <<= 4; } - if (!(val & 0xC0000000U)) { n += 2; val <<= 2; } - if (!(val & 0x80000000U)) { n += 1; } - return n; -} - -static inline int clz64(uint64 val) -{ - if (static_cast(val >> 32) != 0) { - return clz32(static_cast(val >> 32)); - } - return 32 + clz32(static_cast(val)); -} - -/* ------------------------------------------------------------------ */ -/* Find First Set (ffs) — 1-indexed, returns 0 if val == 0 */ -/* ------------------------------------------------------------------ */ - -static inline int ffs32(uint32 val) -{ - return val ? ctz32(val) + 1 : 0; -} - -static inline int ffs64(uint64 val) -{ - return val ? ctz64(val) + 1 : 0; -} - -/* ------------------------------------------------------------------ */ -/* Find Last Set (fls) — 1-indexed, returns 0 if val == 0 */ -/* Returns the position of the highest set bit. */ -/* ------------------------------------------------------------------ */ - -static inline int fls32(uint32 val) -{ - return val ? 32 - clz32(val) : 0; -} - -static inline int fls64(uint64 val) -{ - return val ? 64 - clz64(val) : 0; -} - -/* ------------------------------------------------------------------ */ -/* Order (log2) — returns floor(log2(val)), val must be > 0 */ -/* ------------------------------------------------------------------ */ - -static inline int ilog2_32(uint32 val) -{ - return fls32(val) - 1; -} - -static inline int ilog2_64(uint64 val) -{ - return fls64(val) - 1; -} - -/* ------------------------------------------------------------------ */ -/* Power-of-two test */ -/* ------------------------------------------------------------------ */ - -static inline bool is_power_of_2(uint64 val) -{ - return val != 0 && (val & (val - 1)) == 0; -} - -} /* namespace lume */ diff --git a/na/include/lume/chardev.h b/na/include/lume/chardev.h deleted file mode 100644 index be81ab9..0000000 --- a/na/include/lume/chardev.h +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * chardev.h — Character device abstraction - * - * Minimal Linux-style cdev registry: each character device has a - * major/minor number and a file_operations table. - */ - -#include -#include - -struct file_operations; - -/* ================================================================== */ -/* MKDEV / MAJOR / MINOR */ -/* ================================================================== */ - -inline constexpr uint32 MKDEV(uint32 ma, uint32 mi) { - return (ma << 8) | (mi & 0xFF); -} -inline constexpr uint32 MAJOR(uint32 dev) { return dev >> 8; } -inline constexpr uint32 MINOR(uint32 dev) { return dev & 0xFF; } - -/* ================================================================== */ -/* cdev — character device descriptor */ -/* ================================================================== */ - -struct cdev { - const char* name; - uint32 dev; /* MKDEV(major, minor) */ - const file_operations* fops; - list_node list; /* global cdev list */ -}; - -/* ================================================================== */ -/* API */ -/* ================================================================== */ - -void cdev_init(); -void cdev_register(cdev* dev); -cdev* cdev_find(uint32 dev); diff --git a/na/include/lume/clone.h b/na/include/lume/clone.h deleted file mode 100644 index 156bae4..0000000 --- a/na/include/lume/clone.h +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Linux-compatible clone(2) flags. - * - * Only the flags relevant to current and near-future use are defined. - * Reference: include/uapi/linux/sched.h - */ - -#include - -/* Signal mask stored in low byte of clone flags */ -inline constexpr uint64 CSIGNAL = 0x000000ffULL; - -/* Sharing flags */ -inline constexpr uint64 CLONE_VM = 0x00000100ULL; // Share address space -inline constexpr uint64 CLONE_FS = 0x00000200ULL; // Share fs info -inline constexpr uint64 CLONE_FILES = 0x00000400ULL; // Share file descriptors -inline constexpr uint64 CLONE_SIGHAND = 0x00000800ULL; // Share signal handlers -inline constexpr uint64 CLONE_PIDFD = 0x00001000ULL; // pidfd -inline constexpr uint64 CLONE_PTRACE = 0x00002000ULL; // Trace child -inline constexpr uint64 CLONE_VFORK = 0x00004000ULL; // Parent blocks until child exits/execs -inline constexpr uint64 CLONE_PARENT = 0x00008000ULL; // Same parent as cloner -inline constexpr uint64 CLONE_THREAD = 0x00010000ULL; // Same thread group -inline constexpr uint64 CLONE_NEWNS = 0x00020000ULL; // New mount namespace -inline constexpr uint64 CLONE_SYSVSEM = 0x00040000ULL; // Share SysV semaphore undo -inline constexpr uint64 CLONE_SETTLS = 0x00080000ULL; // Set TLS -inline constexpr uint64 CLONE_PARENT_SETTID = 0x00100000ULL; // Write TID to parent -inline constexpr uint64 CLONE_CHILD_CLEARTID = 0x00200000ULL; // Clear TID on child exit -inline constexpr uint64 CLONE_DETACHED = 0x00400000ULL; // Unused, historical -inline constexpr uint64 CLONE_CHILD_SETTID = 0x01000000ULL; // Write TID to child diff --git a/na/include/lume/compiler.h b/na/include/lume/compiler.h deleted file mode 100644 index 243f0de..0000000 --- a/na/include/lume/compiler.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_COMPILER_H -#define _LUME_COMPILER_H - -/* - * Minimal compiler primitives used across LumeOS. - * - * Provides only what current code actually references: - * - READ_ONCE / WRITE_ONCE memory access opacity for concurrent code - * (rbtree.h, rbtree_augmented.h) - * - likely / unlikely branch hints for hot/cold paths - * - * Implementation mirrors Linux's semantics — same surface, same effect — - * without pulling in Linux's compiler_types.h cascade (KASAN/KCSAN/KMSAN - * sanitizer attribute machinery, BTF type tags, hardened-usercopy hooks). - * LumeOS doesn't build with those sanitizers, so the simpler form is - * sufficient and keeps the dependency graph flat. - */ - -/* Branch prediction hints */ -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) - -/* Compile-time barrier — prevent the compiler from reordering memory - * accesses across this point. Not a CPU fence; for CPU ordering use - * the architecture-specific fence intrinsics. */ -#define barrier() __asm__ __volatile__("" ::: "memory") - -/* - * __always_inline — force the compiler to actually inline a function, - * overriding -fno-inline / -Os / size heuristics. Used by rbtree helper - * templates whose performance depends on the call being inlined into - * the iterating loop. - */ -#define __always_inline inline __attribute__((__always_inline__)) - -/* - * READ_ONCE / WRITE_ONCE — opaque single-access memory operations. - * - * The volatile cast prevents the compiler from: - * - merging adjacent reads/writes to the same location, - * - splitting a load into multiple narrower loads, - * - hoisting reads out of a loop (or sinking writes past one). - * - * Used in lock-free / cross-CPU shared data paths (rbtree color writes, - * future RCU pointers, atomic flag polls). Does NOT imply any CPU-side - * memory ordering — pair with smp_mb()/acquire/release for that. - */ -#define READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x)) -#define WRITE_ONCE(x, val) (*(volatile __typeof__(x) *)&(x) = (val)) - -#endif /* _LUME_COMPILER_H */ diff --git a/na/include/lume/config.h b/na/include/lume/config.h deleted file mode 100644 index bde717a..0000000 --- a/na/include/lume/config.h +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * LumeOS Global Compile-Time Configuration - * - * All kernel-wide tuning constants live here. - * Architecture-specific constants live in . - * - * This header is safe to include from both C++ and assembly files. - * Assembly-visible constants use #define; C++ code gets typed constexpr. - * - * Values are injected by the build system via -DCONFIG_xxx flags - * (see scripts/Kconfig.mk). The #ifndef guards below provide - * safe defaults so the header is self-contained for IDE analysis. - * - * Reference: docs/specs/00_conventions.md §1.3 - */ - -/* ================================================================ - * Defaults — only used when CONFIG_xxx is NOT supplied by -D flag. - * In a normal build, scripts/Kconfig.mk always provides them. - * ================================================================ */ - -#ifndef CONFIG_NR_CPUS -#define CONFIG_NR_CPUS 8 -#endif - -#ifndef CONFIG_PAGE_SIZE -#define CONFIG_PAGE_SIZE 4096 -#endif - -#ifndef CONFIG_MAX_ORDER -#define CONFIG_MAX_ORDER 11 -#endif - -#ifndef CONFIG_KERNEL_STACK_SIZE -#define CONFIG_KERNEL_STACK_SIZE 16384 -#endif - -#ifndef CONFIG_HZ -#define CONFIG_HZ 100 -#endif - -#ifndef CONFIG_NR_PRIORITIES -#define CONFIG_NR_PRIORITIES 32 -#endif - -#ifndef CONFIG_DEFAULT_PRIORITY -#define CONFIG_DEFAULT_PRIORITY (CONFIG_NR_PRIORITIES / 2) -#endif - -#ifndef CONFIG_IDLE_PRIORITY -#define CONFIG_IDLE_PRIORITY (CONFIG_NR_PRIORITIES - 1) -#endif - -#ifndef CONFIG_DEFAULT_TIMESLICE -#define CONFIG_DEFAULT_TIMESLICE (CONFIG_HZ / 10) -#endif - -#ifndef CONFIG_KASLR_POLICY_ID -#define CONFIG_KASLR_POLICY_ID 0 /* Default: basic KASLR policy */ -#endif - -#ifndef CONFIG_KASLR_LOG_LEVEL -#define CONFIG_KASLR_LOG_LEVEL 1 /* 0=off, 1=minimal, 2=debug */ -#endif - -/* ================================================================ - * Legacy compatibility aliases (shared with assembly) - * ================================================================ */ -#define LUME_MAX_CPUS CONFIG_NR_CPUS -#define LUME_PAGE_SIZE CONFIG_PAGE_SIZE - -#ifndef __ASSEMBLER__ - -#include - -/* Maximum number of CPUs supported by the kernel. - * Compile-time limit for static arrays (boot stacks, per-cpu data). - * Actual CPU count is detected at runtime via FDT. */ -inline constexpr int kMaxCpus = CONFIG_NR_CPUS; - -/* Virtual memory page size (4KB, matching SV39 base page). */ -inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE; - -/* Page shift: log2(kPageSize). Used for PFN ↔ PA conversions. */ -inline constexpr int kPageShift = 12; // 4096 = 1 << 12 - -/* KASLR policy ID and logging level (build-time configurable) */ -inline constexpr uint32 kKaslrPolicyId = CONFIG_KASLR_POLICY_ID; -inline constexpr int kKaslrLogLevel = CONFIG_KASLR_LOG_LEVEL; - -#include - -using arch::kPhysStart; - -#endif /* __ASSEMBLER__ */ \ No newline at end of file diff --git a/na/include/lume/console.h b/na/include/lume/console.h deleted file mode 100644 index af187a6..0000000 --- a/na/include/lume/console.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * console.h — Kernel console abstraction - * - * Two-phase design following Linux: - * - * Phase 1 (early boot): - * early_putc() → direct UART MMIO write. Used before MMU and - * before the console registry is ready. kprintf() and panic - * use this path. - * - * Phase 2 (runtime): - * struct console registered via register_console(). printk/klog - * emit through the console driver chain. Multiple consoles can - * be registered (e.g., UART + framebuffer). - * - * Reference: Linux include/linux/console.h - */ - -#include -#include - -/* ================================================================== */ -/* Early console (Phase 1 — direct UART access) */ -/* ================================================================== */ - -extern "C" void early_putc(char c); -extern "C" void early_puts(const char* s); -extern "C" void console_set_mmu_enabled(); - -/* Early UART discovery via raw FDT parsing. */ -void console_early_init(uint64 fdt_paddr); - -/* Full console initialization. Maps MMIO. */ -void console_init(); - -/* - * Notify console that the runtime kernel page table is active. - * After this point early_putc() must use runtime direct-map base - * instead of the bootstrap fixed base. - */ -void console_use_runtime_mapping(); - -/* ================================================================== */ -/* struct console (Phase 2 — registered console drivers) */ -/* ================================================================== */ - -inline constexpr int CON_PRINTBUFFER = (1 << 0); /* Replay klog on register */ -inline constexpr int CON_ENABLED = (1 << 1); -inline constexpr int CON_BOOT = (1 << 2); /* Early boot console */ - -struct console { - const char* name; - void (*write)(console* con, const char* s, unsigned int count); - int (*setup)(console* con, char* options); - int flags; - int index; - void* data; /* driver private data */ - list_node list; -}; - -/* Register / unregister a console driver */ -void register_console(console* con); -void unregister_console(console* con); - -/* - * Write to all registered consoles. - * Falls back to early_putc() if no console is registered. - */ -void console_write_all(const char* s, int count); - -/* Check if any console is registered */ -bool console_drivers_registered(); diff --git a/na/include/lume/container_of.h b/na/include/lume/container_of.h deleted file mode 100644 index a6889eb..0000000 --- a/na/include/lume/container_of.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * container_of.h — Recover containing struct from member pointer - * - * Equivalent to Linux include/linux/container_of.h. - */ - -#include - -#define container_of(ptr, type, member) \ - reinterpret_cast( \ - reinterpret_cast(ptr) - \ - reinterpret_cast(&(reinterpret_cast(0)->member))) diff --git a/na/include/lume/dcache.h b/na/include/lume/dcache.h deleted file mode 100644 index a31402e..0000000 --- a/na/include/lume/dcache.h +++ /dev/null @@ -1,143 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * dcache.h — Directory entry cache - * - * Modeled after Linux include/linux/dcache.h. - * Reference: docs/vfs/03_dentry.md - */ - -#include -#include -#include -#include -#include - -struct inode; -struct super_block; -struct dentry; -struct dentry_operations; -struct path; - -/* ================================================================== */ -/* qstr — quick string (name + precomputed hash + length) */ -/* ================================================================== */ - -struct qstr { - union { - struct { - uint32 hash; - uint32 len; - }; - uint64 hash_len; - }; - const char* name; -}; - -uint32 full_name_hash(const char* name, uint32 len); - -/* ================================================================== */ -/* dentry */ -/* ================================================================== */ - -inline constexpr int DNAME_INLINE_LEN = 40; - -struct dentry { - /* ---- RCU / lookup hot fields ---- */ - unsigned int d_flags; - dentry* d_parent; - qstr d_name; - inode* d_inode; - unsigned char d_iname[DNAME_INLINE_LEN]; - - /* ---- Ref lookup ---- */ - const dentry_operations* d_op; - super_block* d_sb; - unsigned long d_time; - void* d_fsdata; - - /* ---- Refcount + lock ---- */ - lume::atomic d_lockref; - Spinlock d_lock; - - /* ---- Tree linkage ---- */ - list_node d_lru; - list_node d_child; /* Linkage in parent->d_children */ - list_head d_children; /* Children of this dentry */ - - /* ---- Inode alias ---- */ - list_node d_alias; /* Linkage in inode->i_dentry */ -}; - -/* ================================================================== */ -/* dentry_operations */ -/* ================================================================== */ - -struct dentry_operations { - int (*d_revalidate)(dentry* de, unsigned int flags); - int (*d_hash)(const dentry* de, qstr* name); - int (*d_compare)(const dentry* parent, unsigned int len, - const char* str, const qstr* name); - int (*d_delete)(const dentry* de); - int (*d_init)(dentry* de); - void (*d_release)(dentry* de); - void (*d_iput)(dentry* de, inode* ip); -}; - -/* ================================================================== */ -/* Dentry helpers */ -/* ================================================================== */ - -inline bool d_is_negative(const dentry* de) { - return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_MISS_TYPE; -} -inline bool d_is_positive(const dentry* de) { - return !d_is_negative(de); -} -inline bool d_is_dir(const dentry* de) { - return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_DIRECTORY_TYPE; -} -inline bool d_is_reg(const dentry* de) { - return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_REGULAR_TYPE; -} -inline bool d_is_symlink(const dentry* de) { - return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_SYMLINK_TYPE; -} -inline bool d_mountpoint(const dentry* de) { - return (de->d_flags & DCACHE_MOUNTED) != 0; -} -inline bool d_is_root(const dentry* de) { - return de == de->d_parent; -} - -inline inode* d_inode_ptr(const dentry* de) { - return de->d_inode; -} - -/* ================================================================== */ -/* Dcache API */ -/* ================================================================== */ - -dentry* d_alloc(dentry* parent, const qstr* name); -dentry* d_alloc_anon(super_block* sb); -void d_instantiate(dentry* de, inode* ip); -dentry* d_make_root(inode* root_inode); -void d_add(dentry* de, inode* ip); -dentry* d_lookup(const dentry* parent, const qstr* name); -dentry* dget(dentry* de); -void dput(dentry* de); -void d_drop(dentry* de); -void d_delete(dentry* de); -char* d_path(const path* p, char* buf, int buflen); - -/* Set dentry type flags based on inode mode */ -void d_set_type(dentry* de, uint32 i_mode); - -/* ================================================================== */ -/* Dcache init */ -/* ================================================================== */ - -void dcache_init(); diff --git a/na/include/lume/device.h b/na/include/lume/device.h deleted file mode 100644 index 46e22e4..0000000 --- a/na/include/lume/device.h +++ /dev/null @@ -1,75 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Driver model: device, device_driver, bus_type. - * - * Mirrors Linux include/linux/device.h. Every hardware unit embeds a - * `device` and binds to a `device_driver` via a `bus_type` that knows - * how to match the two. Buses we will have: - * - platform: FDT-described memory-mapped devices (this commit) - * - virtio: virtio-mmio devices (P5 of storage_stack.md) - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include -#include -#include - -struct bus_type; -struct device_driver; - -struct device { - kobject kobj; // embedded; first member by convention - device* parent; - const char* init_name; // used until kobj.name is set - bus_type* bus; - device_driver* driver; - void* driver_data; - void* platform_data; - uint32 devt; // for char/block dev_t allocation - list_node bus_list; // node in bus->devices - list_node driver_list; // node in driver->devices -}; - -struct device_driver { - const char* name; - bus_type* bus; - int (*probe)(device*); - void (*remove)(device*); - list_node bus_node; // node in bus->drivers - list_node devices; // head of devices bound to this driver -}; - -struct bus_type { - const char* name; - int (*match)(device*, device_driver*); - /* If non-null, this is the bus-level probe dispatcher. It is called - * before drv->probe and is responsible for downcasting device/driver - * to the concrete bus type and forwarding (e.g. platform_dispatch). */ - int (*probe)(device*); - list_node devices; - list_node drivers; - Spinlock lock; - bool registered; -}; - -int bus_register (bus_type* bus); - -/* device_initialize: kobject_init the embedded kobj with the generic - * device_ktype. device_register calls this automatically if the caller - * hasn't already. Separate entry point for callers that want a half- - * initialized device (so they can take refs before adding). */ -void device_initialize(device* dev); -int device_register (device* dev); -void device_unregister(device* dev); - -int driver_register (device_driver* drv); -void driver_unregister(device_driver* drv); - -/* Convenience accessor — name from kobject if set, else init_name. */ -const char* dev_name(const device* dev); diff --git a/na/include/lume/device_node.h b/na/include/lume/device_node.h deleted file mode 100644 index e35f22d..0000000 --- a/na/include/lume/device_node.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * DeviceNode — FDT object tree node - * - * Each DeviceNode represents a single node in the unflattened device tree. - * Nodes form a parent-children hierarchy via intrusive linked lists. - * After unflattening, the tree is globally read-only and requires no locks. - * - * Created when Slab is available. - * - * Reference: docs/specs/fdt.md §3.2 - */ - -#include -#include -#include - -class DeviceNode { -public: - // Construction / destruction (used by FdtManager::unflatten) - DeviceNode(DeviceNode *parent, const char *name); - ~DeviceNode(); - - // --- Property access --- - - // Look up a property by name. Returns nullptr if not found. - const FdtProperty *get_prop(const char *name) const; - - // Convenience helpers that decode the property value. - // Return default_val when the property does not exist. - uint32 get_prop_u32(const char *name, uint32 default_val = 0) const; - uint64 get_prop_u64(const char *name, uint64 default_val = 0) const; - const char *get_prop_string(const char *name) const; - - // --- Child traversal --- - - // Find a direct child by name. Returns nullptr if not found. - DeviceNode *find_child(const char *name); - - // Invoke callback for every direct child. - void for_each_child(void (*callback)(DeviceNode *)); - - // --- Internal (used by FdtManager to build the tree) --- - void add_child(DeviceNode *child); - void add_prop(FdtProperty *prop); - - // --- Accessors for tree traversal --- - const char *get_name() const { return name_; } - list_node *children_list_head() { return children_list_.next; } - list_node *children_list_sentinel() { return &children_list_; } - - // sibling_link_ offset needed by container_of in fdt.cc - list_node sibling_link_; // Link in parent's children_list_ - -private: - const char *name_; // Node name (e.g. "memory@80000000") - DeviceNode *parent_; // Parent node (nullptr for root) - list_node children_list_; // Head of the children linked list - list_node props_list_; // Head of the property linked list -}; diff --git a/na/include/lume/driver.h b/na/include/lume/driver.h deleted file mode 100644 index a8bd787..0000000 --- a/na/include/lume/driver.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * driver.h — boot-time driver probing entry point. - * - * The actual driver-model primitives (kobject, device, bus_type, - * platform_device, platform_driver) live in their own headers: - * , , . - * - * This header just exposes the single boot entry point that - * kernel_main() calls after the slab allocator + FDT unflatten are - * ready. The implementation lives in drivers/base/platform.cc. - * - * Reference: docs/specs/storage_stack.md §4 - */ - -/* Register the platform bus, register every linker-set platform_driver, - * walk the FDT, and instantiate a platform_device for each compatible - * match. Each device is bound to its driver via the platform bus - * match/probe path. Returns after all probes have run. */ -void driver_probe_all(); diff --git a/na/include/lume/elf.h b/na/include/lume/elf.h deleted file mode 100644 index 772ad97..0000000 --- a/na/include/lume/elf.h +++ /dev/null @@ -1,150 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * ELF64 Type Definitions — Minimal set for loading executables - * AND for the kernel's own .rela.dyn relocation walk at boot. - * - * Follows the ELF specification (Tool Interface Standard, TIS). - * Only includes what's needed for static ELF64 executable loading. - * - * Reference: https://refspecs.linuxfoundation.org/elf/elf.pdf - */ - -#include - -/* ================================================================== */ -/* Basic ELF types */ -/* ================================================================== */ - -using Elf64_Addr = uint64; -using Elf64_Off = uint64; -using Elf64_Half = uint16; -using Elf64_Word = uint32; -using Elf64_Sword = int32; -using Elf64_Xword = uint64; -using Elf64_Sxword = int64; - -/* ================================================================== */ -/* ELF Header */ -/* ================================================================== */ - -inline constexpr int EI_NIDENT = 16; - -struct Elf64_Ehdr { - uint8 e_ident[EI_NIDENT]; /* ELF identification */ - Elf64_Half e_type; /* Object file type */ - Elf64_Half e_machine; /* Machine type */ - Elf64_Word e_version; /* Object file version */ - Elf64_Addr e_entry; /* Entry point address */ - Elf64_Off e_phoff; /* Program header table offset */ - Elf64_Off e_shoff; /* Section header table offset */ - Elf64_Word e_flags; /* Processor-specific flags */ - Elf64_Half e_ehsize; /* ELF header size */ - Elf64_Half e_phentsize; /* Program header entry size */ - Elf64_Half e_phnum; /* Number of program headers */ - Elf64_Half e_shentsize; /* Section header entry size */ - Elf64_Half e_shnum; /* Number of section headers */ - Elf64_Half e_shstrndx; /* Section name string table index */ -}; - -/* e_ident[] indices */ -inline constexpr int EI_MAG0 = 0; -inline constexpr int EI_MAG1 = 1; -inline constexpr int EI_MAG2 = 2; -inline constexpr int EI_MAG3 = 3; -inline constexpr int EI_CLASS = 4; -inline constexpr int EI_DATA = 5; -inline constexpr int EI_VERSION = 6; -inline constexpr int EI_OSABI = 7; - -/* e_ident[] magic */ -inline constexpr uint8 ELFMAG0 = 0x7f; -inline constexpr uint8 ELFMAG1 = 'E'; -inline constexpr uint8 ELFMAG2 = 'L'; -inline constexpr uint8 ELFMAG3 = 'F'; - -/* e_ident[EI_CLASS] */ -inline constexpr uint8 ELFCLASS64 = 2; - -/* e_ident[EI_DATA] */ -inline constexpr uint8 ELFDATA2LSB = 1; /* Little-endian */ - -/* e_type */ -inline constexpr Elf64_Half ET_EXEC = 2; /* Executable */ -inline constexpr Elf64_Half ET_DYN = 3; /* Shared object (PIE) */ - -/* e_machine */ -inline constexpr Elf64_Half EM_RISCV = 243; - -/* ================================================================== */ -/* Program Header */ -/* ================================================================== */ - -struct Elf64_Phdr { - Elf64_Word p_type; /* Segment type */ - Elf64_Word p_flags; /* Segment flags */ - Elf64_Off p_offset; /* File offset */ - Elf64_Addr p_vaddr; /* Virtual address */ - Elf64_Addr p_paddr; /* Physical address (unused) */ - Elf64_Xword p_filesz; /* Size in file */ - Elf64_Xword p_memsz; /* Size in memory (>= p_filesz, diff is BSS) */ - Elf64_Xword p_align; /* Alignment */ -}; - -/* p_type values */ -inline constexpr Elf64_Word PT_NULL = 0; -inline constexpr Elf64_Word PT_LOAD = 1; -inline constexpr Elf64_Word PT_DYNAMIC = 2; -inline constexpr Elf64_Word PT_INTERP = 3; -inline constexpr Elf64_Word PT_NOTE = 4; -inline constexpr Elf64_Word PT_PHDR = 6; -inline constexpr Elf64_Word PT_GNU_STACK = 0x6474e551; - -/* p_flags values */ -inline constexpr Elf64_Word PF_X = 0x1; /* Execute */ -inline constexpr Elf64_Word PF_W = 0x2; /* Write */ -inline constexpr Elf64_Word PF_R = 0x4; /* Read */ - -/* ================================================================== */ -/* Auxiliary Vector types */ -/* ================================================================== */ - -struct Elf64_auxv_t { - uint64 a_type; - uint64 a_val; -}; - -inline constexpr uint64 AT_NULL = 0; -inline constexpr uint64 AT_PHDR = 3; -inline constexpr uint64 AT_PHENT = 4; -inline constexpr uint64 AT_PHNUM = 5; -inline constexpr uint64 AT_PAGESZ = 6; -inline constexpr uint64 AT_ENTRY = 9; -inline constexpr uint64 AT_UID = 11; -inline constexpr uint64 AT_EUID = 12; -inline constexpr uint64 AT_GID = 13; -inline constexpr uint64 AT_EGID = 14; -inline constexpr uint64 AT_RANDOM = 25; -inline constexpr uint64 AT_BASE = 7; /* PIE load base address */ - -/* ================================================================== */ -/* RELA relocation entry — used by the kernel's own .rela.dyn walk */ -/* at boot (relocate_kernel in arch/riscv/mm/init.cc). */ -/* ================================================================== */ - -struct Elf64_Rela { - Elf64_Addr r_offset; /* link-time VA of the slot to patch */ - Elf64_Xword r_info; /* r_sym << 32 | r_type */ - Elf64_Sxword r_addend; /* link-time value of the target */ -}; - -#define ELF64_R_TYPE(info) ((info) & 0xFFFFFFFFULL) - -/* RISC-V relocation types — only the ones a PIE-linked kernel emits - * into .rela.dyn under `-shared -Bsymbolic`. */ -inline constexpr uint32 R_RISCV_NONE = 0; -inline constexpr uint32 R_RISCV_64 = 2; -inline constexpr uint32 R_RISCV_RELATIVE = 3; diff --git a/na/include/lume/errno.h b/na/include/lume/errno.h deleted file mode 100644 index f3bf09f..0000000 --- a/na/include/lume/errno.h +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_ERRNO_H -#define _LUME_ERRNO_H - -/* - * Error codes — numeric values aligned with Linux's asm-generic/errno-base.h, - * asm-generic/errno.h, and linux/errno.h (kernel-internal codes 512+). - * - * Userspace-visible codes (1..133) match POSIX / Linux exactly so a LumeOS - * binary returning -EINVAL to a user program receives the same number Linux - * would. Kernel-internal codes (>= 512) follow Linux's convention of being - * out of POSIX range; they are translated to a user-visible code (typically - * EINTR) before crossing into user mode. - * - * Defined as plain #define so this header is usable from C, C++, and - * assembly (the macros expand to integer literals, no types). - */ - -/* ===== Base errors (asm-generic/errno-base.h equivalent) ===== */ -#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 */ -#define ERANGE 34 /* Math result not representable */ - -/* ===== Extended errors (asm-generic/errno.h equivalent) ===== */ -#define EDEADLK 35 /* Resource deadlock would occur */ -#define ENAMETOOLONG 36 /* File name too long */ -#define ENOLCK 37 /* No record locks available */ -#define ENOSYS 38 /* Invalid system call number */ -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ -#define EDEADLOCK EDEADLK -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Cannot access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Linking in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define ENOTSUP EOPNOTSUPP -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ -#define EOWNERDEAD 130 /* Owner died (for robust mutexes) */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ -#define ERFKILL 132 /* Operation not possible due to RF-kill */ -#define EHWPOISON 133 /* Memory page has hardware error */ - -/* ===== Kernel-internal errors (linux/errno.h equivalent, >= 512) ===== - * These should never be seen by user programs. signal/syscall layer - * translates them to a user-visible code (typically EINTR) before - * returning to userspace. - */ -#define ERESTARTSYS 512 -#define ERESTARTNOINTR 513 -#define ERESTARTNOHAND 514 /* restart if no signal handler */ -#define ENOIOCTLCMD 515 /* No ioctl command */ -#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */ -#define EPROBE_DEFER 517 /* Driver requests probe retry */ -#define EOPENSTALE 518 /* open() found a stale dentry */ -#define ENOPARAM 519 /* Parameter not supported */ - -/* NFSv3 reserved range (521..531) */ -#define EBADHANDLE 521 /* Illegal NFS file handle */ -#define ENOTSYNC 522 /* Update synchronization mismatch */ -#define EBADCOOKIE 523 /* Cookie is stale */ -#define ENOTSUPP 524 /* Operation is not supported */ -#define ETOOSMALL 525 /* Buffer or request is too small */ -#define ESERVERFAULT 526 /* An untranslatable error occurred */ -#define EBADTYPE 527 /* Type not supported by server */ -#define EJUKEBOX 528 /* Request initiated, will not complete before timeout */ -#define EIOCBQUEUED 529 /* iocb queued, will get completion event */ -#define ERECALLCONFLICT 530 /* conflict with recalled state */ -#define ENOGRACE 531 /* NFS file lock reclaim refused */ - -#endif /* _LUME_ERRNO_H */ diff --git a/na/include/lume/fdt.h b/na/include/lume/fdt.h deleted file mode 100644 index 626cc48..0000000 --- a/na/include/lume/fdt.h +++ /dev/null @@ -1,85 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FdtManager — Flattened Device Tree parser - * - * Two-phase design: - * Phase 1 (Early Scan): Static methods that walk the raw FDT blob - * without any memory allocation. Used before PMM is available. - * Phase 2 (Unflatten): Builds a persistent C++ DeviceNode object - * tree once Slab is ready. The tree is then globally read-only. - * - * Concurrency: all init is done by BSP in the single-core window. - * After unflattening the tree is immutable — no locks required. - * - * Reference: docs/specs/fdt.md - */ - -#include -#include - -class FdtManager { -public: - // Construct with the physical address of the FDT blob. - // Called via Placement New in fdt_init(). - FdtManager(uint64 fdt_paddr); - - // --- Phase 1: Early scan (static, allocation-free) --- - - // Extract physical memory base and size from the /memory node. - // Panics if the FDT is invalid or /memory is missing. - static void early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size); - - // Extract the UART base address and size by matching compatible = "ns16550a". - // Falls back to platform default on failure. - static void early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, - uint64 *uart_size); - - static void early_scan_kaslr(uint64 fdt_paddr, uint64 *seed); - - // --- Phase 2: Object tree construction (requires Slab) --- - - // Walk the FDT blob and build the DeviceNode tree. - void unflatten(); - - // --- Query interface (read-only after unflatten) --- - - // Find the first node whose "compatible" property contains the string. - // Returns nullptr if no match. - DeviceNode *find_compatible(const char *compat_string); - - // Look up a node by its full path (e.g. "/soc/uart@10000000"). - // Returns nullptr if not found. - DeviceNode *get_node_by_path(const char *path); - - // Return the stored FDT physical address. - uint64 get_paddr() const { return fdt_paddr_; } - -private: - uint64 fdt_paddr_; // Physical address of the FDT blob - bool is_unflattened_ = false; // True after unflatten() completes - DeviceNode *root_ = nullptr; // Root of the unflattened tree -}; - -/* Global FDT manager singleton (Placement New, see global_init.md §3.1) */ -extern FdtManager *g_fdt; - -// --- Public free-function API --- - -// Save FDT address and validate magic. Called early by BSP. -void fdt_init(uint64 fdt_paddr); - -// Build the DeviceNode object tree. Requires Slab. -void fdt_unflatten(); - -// Driver probing helpers (delegate to g_fdt). -DeviceNode *fdt_find_compatible(const char *compat_string); -DeviceNode *fdt_get_node_by_path(const char *path); - -// Early scan wrappers (delegate to FdtManager static methods). -void fdt_early_get_mem_info(uint64 *base, uint64 *size); -void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size); -void fdt_early_get_kaslr_seed(uint64 *seed); diff --git a/na/include/lume/fdt_prop.h b/na/include/lume/fdt_prop.h deleted file mode 100644 index f8a263c..0000000 --- a/na/include/lume/fdt_prop.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FDT Property - * - * Represents a single property within a DeviceNode (e.g. "reg", - * "compatible"). The value pointer points directly into the - * original FDT blob — no copies are made. - * - * Reference: docs/specs/fdt.md §3.1 - */ - -#include -#include - -struct FdtProperty { - const char *name; // Property name (points into FDT strings block) - const void *value; // Raw property data (points into FDT struct block) - int len; // Length of value in bytes - list_node link; // Intrusive link into DeviceNode's property list -}; diff --git a/na/include/lume/fdtable.h b/na/include/lume/fdtable.h deleted file mode 100644 index 6159519..0000000 --- a/na/include/lume/fdtable.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fdtable.h — Per-task file descriptor table - * - * Modeled after Linux include/linux/fdtable.h. - * Reference: docs/vfs/05_fdtable.md - */ - -#include -#include -#include - -struct file; - -/* ================================================================== */ -/* files_struct — per-task open file table */ -/* ================================================================== */ - -inline constexpr int NR_OPEN_DEFAULT = 256; - -struct files_struct { - lume::atomic count; /* Reference count (CLONE_FILES) */ - Spinlock file_lock; - - int next_fd; /* Hint for next free fd */ - int max_fds; /* Current max (= NR_OPEN_DEFAULT) */ - file* fd_array[NR_OPEN_DEFAULT]; - uint64 close_on_exec_init[(NR_OPEN_DEFAULT + 63) / 64]; - uint64 open_fds_init[(NR_OPEN_DEFAULT + 63) / 64]; -}; - -/* ================================================================== */ -/* API */ -/* ================================================================== */ - -files_struct* alloc_files_struct(); -void put_files_struct(files_struct* files); -files_struct* dup_fd(files_struct* old_files); - -int alloc_fd(files_struct* files, unsigned int start); -void fd_install(files_struct* files, int fd, file* fp); -file* fcheck(files_struct* files, int fd); -int close_fd(files_struct* files, int fd); -int do_dup2(files_struct* files, int oldfd, int newfd, int flags); -void do_close_on_exec(files_struct* files); diff --git a/na/include/lume/features.h b/na/include/lume/features.h deleted file mode 100644 index 85cfb72..0000000 --- a/na/include/lume/features.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _FEATURES_H -#define _FEATURES_H - -#if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE) -#define _GNU_SOURCE 1 -#endif - -#if defined(_DEFAULT_SOURCE) && !defined(_BSD_SOURCE) -#define _BSD_SOURCE 1 -#endif - -#if !defined(_POSIX_SOURCE) && !defined(_POSIX_C_SOURCE) \ - && !defined(_XOPEN_SOURCE) && !defined(_GNU_SOURCE) \ - && !defined(_BSD_SOURCE) && !defined(__STRICT_ANSI__) -#define _BSD_SOURCE 1 -#define _XOPEN_SOURCE 700 -#endif - -#if __STDC_VERSION__ >= 199901L -#define __restrict restrict -#elif !defined(__GNUC__) -#define __restrict -#endif - -#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) -#define __inline inline -#elif !defined(__GNUC__) -#define __inline -#endif - -#if __STDC_VERSION__ >= 201112L -#elif defined(__GNUC__) -#define _Noreturn __attribute__((__noreturn__)) -#else -#define _Noreturn -#endif - -#define __REDIR(x,y) __typeof__(x) x __asm__(#y) - -#endif diff --git a/na/include/lume/file.h b/na/include/lume/file.h deleted file mode 100644 index 349059b..0000000 --- a/na/include/lume/file.h +++ /dev/null @@ -1,132 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * file.h — Open file description + file_operations + path + vfsmount - * - * Modeled after Linux include/linux/fs.h struct file / file_operations. - * Reference: docs/vfs/04_file.md - */ - -#include -#include -#include -#include - -struct inode; -struct dentry; -struct super_block; -struct address_space; -struct dir_context; -struct VmAreaStruct; -struct kstat; - -/* ================================================================== */ -/* vfsmount (public part) */ -/* ================================================================== */ - -struct vfsmount { - dentry* mnt_root; - super_block* mnt_sb; - int mnt_flags; -}; - -vfsmount* mntget(vfsmount* mnt); -void mntput(vfsmount* mnt); - -/* ================================================================== */ -/* path */ -/* ================================================================== */ - -struct path { - vfsmount* mnt; - dentry* de; /* Linux calls this 'dentry' but that conflicts in C++ */ -}; - -void path_get(const path* p); -void path_put(const path* p); - -/* ================================================================== */ -/* file */ -/* ================================================================== */ - -struct file_operations; - -struct file { - Spinlock f_lock; - uint32 f_mode; /* FMODE_* */ - const file_operations* f_op; - address_space* f_mapping; - void* private_data; - inode* f_inode; - unsigned int f_flags; /* O_* */ - - path f_path; - - int64 f_pos; - lume::atomic f_count; -}; - -/* ================================================================== */ -/* Accessors */ -/* ================================================================== */ - -inline inode* file_inode(const file* f) { return f->f_inode; } -inline dentry* file_dentry(const file* f) { return f->f_path.de; } - -file* get_file(file* f); -file* alloc_empty_file(int flags); -void fput(file* fp); - -/* ================================================================== */ -/* dir_context (for iterate_shared / getdents64) */ -/* ================================================================== */ - -struct dir_context; -using filldir_t = bool (*)(dir_context* ctx, const char* name, int namelen, - int64 offset, uint64 ino, unsigned int d_type); - -struct dir_context { - filldir_t actor; - int64 pos; -}; - -/* ================================================================== */ -/* file_operations */ -/* ================================================================== */ - -struct file_operations { - int64 (*llseek)(file* fp, int64 offset, int whence); - int64 (*read)(file* fp, char* buf, uint64 count, int64* pos); - int64 (*write)(file* fp, const char* buf, uint64 count, int64* pos); - int (*iterate_shared)(file* fp, dir_context* ctx); - uint32 (*poll)(file* fp, void* pt); - long (*unlocked_ioctl)(file* fp, unsigned int cmd, uint64 arg); - int (*mmap)(file* fp, VmAreaStruct* vma); - int (*open)(inode* ip, file* fp); - int (*flush)(file* fp); - int (*release)(inode* ip, file* fp); - int (*fsync)(file* fp, int64 start, int64 end, int datasync); -}; - -/* ================================================================== */ -/* VFS read/write/seek */ -/* ================================================================== */ - -int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos); -int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos); -int64 vfs_llseek(file* fp, int64 offset, int whence); -int vfs_fstat(file* fp, kstat* stat); - -/* Generic helpers for simple fs */ -int64 generic_file_llseek(file* fp, int64 offset, int whence); - -/* ================================================================== */ -/* path helpers */ -/* ================================================================== */ - -inline bool path_equal(const path* a, const path* b) { - return a->mnt == b->mnt && a->de == b->de; -} diff --git a/na/include/lume/fs.h b/na/include/lume/fs.h deleted file mode 100644 index 8dac552..0000000 --- a/na/include/lume/fs.h +++ /dev/null @@ -1,310 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs.h — Core VFS data structures - * - * Defines super_block, inode, address_space, and their operations. - * Modeled after Linux include/linux/fs.h + include/linux/fs/super_types.h. - * - * Reference: docs/vfs/01_super_block.md, docs/vfs/02_inode.md - */ - -#include -#include -#include -#include -#include - -/* Forward declarations */ -struct super_block; -struct inode; -struct dentry; -struct file; -struct vfsmount; -struct path; -struct kstat; -struct iattr; -struct file_operations; -struct inode_operations; -struct dentry_operations; -struct dir_context; -struct pipe_inode_info; -struct VmAreaStruct; -struct Page; - -/* ================================================================== */ -/* address_space_operations */ -/* ================================================================== */ - -struct address_space_operations { - int (*read_folio)(file* fp, Page* page); - int (*write_begin)(struct address_space* mapping, int64 pos, - unsigned len, Page** pagep, void** fsdata); - int (*write_end)(struct address_space* mapping, int64 pos, - unsigned len, unsigned copied, - Page* page, void* fsdata); -}; - -/* ================================================================== */ -/* address_space */ -/* ================================================================== */ - -struct address_space { - inode* host; - const address_space_operations* a_ops; - uint64 nrpages; - uint64 flags; - Spinlock i_private_lock; -}; - -/* ================================================================== */ -/* super_operations */ -/* ================================================================== */ - -struct super_operations { - inode* (*alloc_inode)(super_block* sb); - void (*destroy_inode)(inode* ip); - void (*free_inode)(inode* ip); - - void (*dirty_inode)(inode* ip, int flags); - int (*write_inode)(inode* ip, bool sync); - int (*drop_inode)(inode* ip); - void (*evict_inode)(inode* ip); - void (*put_super)(super_block* sb); - int (*sync_fs)(super_block* sb, int wait); - int (*statfs)(dentry* de, struct kstatfs* buf); - void (*umount_begin)(super_block* sb); -}; - -/* ================================================================== */ -/* file_system_type */ -/* ================================================================== */ - -struct file_system_type { - const char* name; - int fs_flags; - super_block* (*mount)(file_system_type* type, int flags, - const char* dev_name, void* data); - void (*kill_sb)(super_block* sb); - list_node fs_list; -}; - -int register_filesystem(file_system_type* fs); -file_system_type* get_fs_type(const char* name); - -/* ================================================================== */ -/* super_block */ -/* ================================================================== */ - -struct mount; - -struct super_block { - list_node s_list; - uint64 s_dev; - uint8 s_blocksize_bits; - uint64 s_blocksize; - int64 s_maxbytes; - file_system_type* s_type; - const super_operations* s_op; - - uint64 s_flags; - uint64 s_magic; - dentry* s_root; - - char s_id[32]; - void* s_fs_info; - - uint32 s_time_gran; - int64 s_time_min; - int64 s_time_max; - - Spinlock s_inode_list_lock; - list_head s_inodes; - - lume::atomic s_active; - int s_count; - - mount* s_mounts; - uint32 s_max_links; - const dentry_operations* s_d_op; -}; - -/* ================================================================== */ -/* inode */ -/* ================================================================== */ - -struct inode { - /* ---- Path-walk hot fields ---- */ - uint32 i_mode; - uint32 i_flags; - uint32 i_uid; - uint32 i_gid; - - const inode_operations* i_op; - super_block* i_sb; - address_space* i_mapping; - - /* ---- Stat data ---- */ - uint64 i_ino; - uint32 i_nlink; /* Use set_nlink/inc_nlink/drop_nlink */ - uint32 i_rdev; - int64 i_size; - - int64 i_atime_sec; - int64 i_mtime_sec; - int64 i_ctime_sec; - uint32 i_atime_nsec; - uint32 i_mtime_nsec; - uint32 i_ctime_nsec; - uint32 i_generation; - - Spinlock i_lock; - unsigned short i_bytes; - uint8 i_blkbits; - int64 i_blocks; - - /* ---- State ---- */ - uint32 i_state; - - /* ---- Lists ---- */ - list_node i_sb_list; - list_head i_dentry; - - /* ---- Counters ---- */ - lume::atomic i_count; - lume::atomic i_writecount; - - /* ---- File ops ---- */ - const file_operations* i_fop; - - /* ---- Embedded address_space ---- */ - address_space i_data; - - /* ---- Type-specific ---- */ - union { - pipe_inode_info* i_pipe; - char* i_link; - uint32 i_dir_seq; - }; - - void* i_private; -}; - -/* ================================================================== */ -/* inode_operations */ -/* ================================================================== */ - -struct inode_operations { - dentry* (*lookup)(inode* dir, dentry* de, unsigned int flags); - int (*create)(inode* dir, dentry* de, uint32 mode, bool excl); - int (*link)(dentry* old_de, inode* dir, dentry* new_de); - int (*unlink)(inode* dir, dentry* de); - int (*symlink)(inode* dir, dentry* de, const char* target); - int (*mkdir)(inode* dir, dentry* de, uint32 mode); - int (*rmdir)(inode* dir, dentry* de); - int (*mknod)(inode* dir, dentry* de, uint32 mode, uint32 rdev); - int (*rename)(inode* old_dir, dentry* old_de, - inode* new_dir, dentry* new_de, unsigned int flags); - int (*setattr)(dentry* de, iattr* attr); - int (*getattr)(const path* p, kstat* stat, uint32 request_mask, - unsigned int query_flags); - const char* (*get_link)(dentry* de, inode* ip); - int (*permission)(inode* ip, int mask); -}; - -/* ================================================================== */ -/* Inode API */ -/* ================================================================== */ - -inode* new_inode(super_block* sb); -inode* iget(super_block* sb, uint64 ino); -void iput(inode* ip); -void ihold(inode* ip); -void mark_inode_dirty(inode* ip); - -void set_nlink(inode* ip, uint32 nlink); -void inc_nlink(inode* ip); -void drop_nlink(inode* ip); -void clear_nlink(inode* ip); - -void inode_init_owner(inode* ip, const inode* dir, uint32 mode); - -/* Generic helpers used by simple filesystems */ -void generic_fillattr(inode* ip, kstat* stat); - -/* ================================================================== */ -/* kstat (matches oscomp ABI layout exactly) */ -/* ================================================================== */ - -struct kstat { - uint64 st_dev; - uint64 st_ino; - uint32 st_mode; - uint32 st_nlink; - uint32 st_uid; - uint32 st_gid; - uint64 st_rdev; - uint64 __pad; - int64 st_size; - int32 st_blksize; - int32 __pad2; - int64 st_blocks; - int64 st_atime_sec; - int64 st_atime_nsec; - int64 st_mtime_sec; - int64 st_mtime_nsec; - int64 st_ctime_sec; - int64 st_ctime_nsec; - uint32 __unused[2]; -}; - -struct kstatfs { - int64 f_type; - int64 f_bsize; - uint64 f_blocks; - uint64 f_bfree; - uint64 f_bavail; - uint64 f_files; - uint64 f_ffree; - int32 f_fsid[2]; - int64 f_namelen; - int64 f_frsize; - int64 f_flags; - int64 f_spare[4]; -}; - -/* ================================================================== */ -/* iattr (for setattr) */ -/* ================================================================== */ - -struct iattr { - unsigned int ia_valid; /* ATTR_* flags */ - uint32 ia_mode; - uint32 ia_uid; - uint32 ia_gid; - int64 ia_size; - int64 ia_atime_sec; - int64 ia_atime_nsec; - int64 ia_mtime_sec; - int64 ia_mtime_nsec; - int64 ia_ctime_sec; - int64 ia_ctime_nsec; -}; - -/* ATTR_* flags */ -inline constexpr uint32 ATTR_MODE = (1U << 0); -inline constexpr uint32 ATTR_UID = (1U << 1); -inline constexpr uint32 ATTR_GID = (1U << 2); -inline constexpr uint32 ATTR_SIZE = (1U << 3); -inline constexpr uint32 ATTR_ATIME = (1U << 4); -inline constexpr uint32 ATTR_MTIME = (1U << 5); -inline constexpr uint32 ATTR_CTIME = (1U << 6); - -/* ================================================================== */ -/* VFS init */ -/* ================================================================== */ - -void vfs_init(); diff --git a/na/include/lume/fs_struct.h b/na/include/lume/fs_struct.h deleted file mode 100644 index ba643b5..0000000 --- a/na/include/lume/fs_struct.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs_struct.h — Per-task filesystem context (cwd, root) - * - * Modeled after Linux include/linux/fs_struct.h. - * Reference: docs/vfs/05_fdtable.md - */ - -#include -#include -#include /* struct path */ -#include - -/* ================================================================== */ -/* fs_struct */ -/* ================================================================== */ - -struct fs_struct { - lume::atomic count; /* Reference count (CLONE_FS) */ - Spinlock lock; - int umask; /* File creation mask */ - path root; /* Root directory */ - path pwd; /* Current working directory */ -}; - -/* ================================================================== */ -/* API */ -/* ================================================================== */ - -fs_struct* alloc_fs_struct(); -void put_fs_struct(fs_struct* fs); -fs_struct* copy_fs_struct(const fs_struct* old_fs); - -void set_fs_root(fs_struct* fs, const path* p); -void set_fs_pwd(fs_struct* fs, const path* p); diff --git a/na/include/lume/fs_types.h b/na/include/lume/fs_types.h deleted file mode 100644 index 767f37c..0000000 --- a/na/include/lume/fs_types.h +++ /dev/null @@ -1,197 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs_types.h — File mode constants, open flags, seek constants - * - * All values match Linux asm-generic so musl/busybox binaries work. - * Reference: docs/vfs/02_inode.md, docs/vfs/04_file.md - */ - -#include - -/* ================================================================== */ -/* S_IF* — file type in inode::i_mode upper bits */ -/* ================================================================== */ - -inline constexpr uint32 S_IFMT = 0170000; -inline constexpr uint32 S_IFSOCK = 0140000; -inline constexpr uint32 S_IFLNK = 0120000; -inline constexpr uint32 S_IFREG = 0100000; -inline constexpr uint32 S_IFBLK = 0060000; -inline constexpr uint32 S_IFDIR = 0040000; -inline constexpr uint32 S_IFCHR = 0020000; -inline constexpr uint32 S_IFIFO = 0010000; - -inline constexpr uint32 S_ISUID = 04000; -inline constexpr uint32 S_ISGID = 02000; -inline constexpr uint32 S_ISVTX = 01000; - -inline constexpr uint32 S_IRWXU = 00700; -inline constexpr uint32 S_IRUSR = 00400; -inline constexpr uint32 S_IWUSR = 00200; -inline constexpr uint32 S_IXUSR = 00100; -inline constexpr uint32 S_IRWXG = 00070; -inline constexpr uint32 S_IRGRP = 00040; -inline constexpr uint32 S_IWGRP = 00020; -inline constexpr uint32 S_IXGRP = 00010; -inline constexpr uint32 S_IRWXO = 00007; -inline constexpr uint32 S_IROTH = 00004; -inline constexpr uint32 S_IWOTH = 00002; -inline constexpr uint32 S_IXOTH = 00001; - -inline bool S_ISREG(uint32 m) { return (m & S_IFMT) == S_IFREG; } -inline bool S_ISDIR(uint32 m) { return (m & S_IFMT) == S_IFDIR; } -inline bool S_ISCHR(uint32 m) { return (m & S_IFMT) == S_IFCHR; } -inline bool S_ISBLK(uint32 m) { return (m & S_IFMT) == S_IFBLK; } -inline bool S_ISFIFO(uint32 m) { return (m & S_IFMT) == S_IFIFO; } -inline bool S_ISLNK(uint32 m) { return (m & S_IFMT) == S_IFLNK; } -inline bool S_ISSOCK(uint32 m) { return (m & S_IFMT) == S_IFSOCK; } - -/* ================================================================== */ -/* O_* — open(2) flags */ -/* ================================================================== */ - -inline constexpr int O_RDONLY = 00; -inline constexpr int O_WRONLY = 01; -inline constexpr int O_RDWR = 02; -inline constexpr int O_ACCMODE = 03; -inline constexpr int O_CREAT = 0100; -inline constexpr int O_EXCL = 0200; -inline constexpr int O_NOCTTY = 0400; -inline constexpr int O_TRUNC = 01000; -inline constexpr int O_APPEND = 02000; -inline constexpr int O_NONBLOCK = 04000; -inline constexpr int O_DSYNC = 010000; -inline constexpr int O_DIRECTORY = 0200000; -inline constexpr int O_NOFOLLOW = 0400000; -inline constexpr int O_CLOEXEC = 02000000; - -/* ================================================================== */ -/* AT_* — *at() syscall dirfd constants */ -/* ================================================================== */ - -inline constexpr int AT_FDCWD = -100; -inline constexpr int AT_REMOVEDIR = 0x200; -inline constexpr int AT_SYMLINK_NOFOLLOW = 0x100; -inline constexpr int AT_EMPTY_PATH = 0x1000; - -/* ================================================================== */ -/* SEEK_* — lseek(2) whence */ -/* ================================================================== */ - -inline constexpr int SEEK_SET = 0; -inline constexpr int SEEK_CUR = 1; -inline constexpr int SEEK_END = 2; - -/* ================================================================== */ -/* FMODE_* — internal file mode flags */ -/* ================================================================== */ - -inline constexpr uint32 FMODE_READ = (1U << 0); -inline constexpr uint32 FMODE_WRITE = (1U << 1); -inline constexpr uint32 FMODE_LSEEK = (1U << 2); -inline constexpr uint32 FMODE_PREAD = (1U << 3); -inline constexpr uint32 FMODE_PWRITE = (1U << 4); -inline constexpr uint32 FMODE_EXEC = (1U << 5); -inline constexpr uint32 FMODE_CAN_READ = (1U << 17); -inline constexpr uint32 FMODE_CAN_WRITE = (1U << 18); -inline constexpr uint32 FMODE_OPENED = (1U << 19); -inline constexpr uint32 FMODE_CREATED = (1U << 20); -inline constexpr uint32 FMODE_STREAM = (1U << 21); - -/* ================================================================== */ -/* MAY_* — permission check flags */ -/* ================================================================== */ - -inline constexpr int MAY_EXEC = 0x00000001; -inline constexpr int MAY_WRITE = 0x00000002; -inline constexpr int MAY_READ = 0x00000004; -inline constexpr int MAY_OPEN = 0x00000020; -inline constexpr int MAY_CHDIR = 0x00000040; - -/* ================================================================== */ -/* DT_* — directory entry types (for getdents64) */ -/* ================================================================== */ - -inline constexpr uint8 DT_UNKNOWN = 0; -inline constexpr uint8 DT_FIFO = 1; -inline constexpr uint8 DT_CHR = 2; -inline constexpr uint8 DT_DIR = 4; -inline constexpr uint8 DT_BLK = 6; -inline constexpr uint8 DT_REG = 8; -inline constexpr uint8 DT_LNK = 10; -inline constexpr uint8 DT_SOCK = 12; - -/* ================================================================== */ -/* SB_* — super_block flags */ -/* ================================================================== */ - -inline constexpr uint64 SB_RDONLY = (1ULL << 0); -inline constexpr uint64 SB_NOSUID = (1ULL << 1); -inline constexpr uint64 SB_NODEV = (1ULL << 2); -inline constexpr uint64 SB_NOEXEC = (1ULL << 3); -inline constexpr uint64 SB_SYNCHRONOUS = (1ULL << 4); -inline constexpr uint64 SB_NOATIME = (1ULL << 10); -inline constexpr uint64 SB_NODIRATIME = (1ULL << 11); -inline constexpr uint64 SB_SILENT = (1ULL << 15); -inline constexpr uint64 SB_KERNMOUNT = (1ULL << 22); -inline constexpr uint64 SB_LAZYTIME = (1ULL << 25); -inline constexpr uint64 SB_ACTIVE = (1ULL << 30); - -/* ================================================================== */ -/* MNT_* — mount flags */ -/* ================================================================== */ - -inline constexpr int MNT_NOSUID = 0x01; -inline constexpr int MNT_NODEV = 0x02; -inline constexpr int MNT_NOEXEC = 0x04; -inline constexpr int MNT_NOATIME = 0x08; -inline constexpr int MNT_RELATIME = 0x20; -inline constexpr int MNT_READONLY = 0x40; -inline constexpr int MNT_INTERNAL = 0x4000; - -/* ================================================================== */ -/* Inode state flags */ -/* ================================================================== */ - -inline constexpr uint32 I_NEW = (1U << 0); -inline constexpr uint32 I_DIRTY_SYNC = (1U << 4); -inline constexpr uint32 I_DIRTY_DATASYNC = (1U << 5); -inline constexpr uint32 I_DIRTY_PAGES = (1U << 6); -inline constexpr uint32 I_WILL_FREE = (1U << 7); -inline constexpr uint32 I_FREEING = (1U << 8); -inline constexpr uint32 I_CLEAR = (1U << 9); -inline constexpr uint32 I_REFERENCED = (1U << 10); -inline constexpr uint32 I_DIRTY_TIME = (1U << 12); -inline constexpr uint32 I_CREATING = (1U << 15); - -inline constexpr uint32 I_DIRTY_INODE = I_DIRTY_SYNC | I_DIRTY_DATASYNC; -inline constexpr uint32 I_DIRTY = I_DIRTY_INODE | I_DIRTY_PAGES; - -/* ================================================================== */ -/* DCACHE_* — dentry flags */ -/* ================================================================== */ - -inline constexpr uint32 DCACHE_OP_HASH = (1U << 0); -inline constexpr uint32 DCACHE_OP_COMPARE = (1U << 1); -inline constexpr uint32 DCACHE_OP_REVALIDATE = (1U << 2); -inline constexpr uint32 DCACHE_DISCONNECTED = (1U << 5); -inline constexpr uint32 DCACHE_REFERENCED = (1U << 6); -inline constexpr uint32 DCACHE_CANT_MOUNT = (1U << 8); -inline constexpr uint32 DCACHE_MOUNTED = (1U << 15); -inline constexpr uint32 DCACHE_ENTRY_TYPE = (7U << 19); -inline constexpr uint32 DCACHE_MISS_TYPE = (0U << 19); -inline constexpr uint32 DCACHE_DIRECTORY_TYPE = (2U << 19); -inline constexpr uint32 DCACHE_REGULAR_TYPE = (4U << 19); -inline constexpr uint32 DCACHE_SPECIAL_TYPE = (5U << 19); -inline constexpr uint32 DCACHE_SYMLINK_TYPE = (6U << 19); - -/* ================================================================== */ -/* FS_* — file_system_type flags */ -/* ================================================================== */ - -inline constexpr int FS_REQUIRES_DEV = 1; -inline constexpr int FS_NO_DEVICE = 2; diff --git a/na/include/lume/init.h b/na/include/lume/init.h deleted file mode 100644 index 541ab96..0000000 --- a/na/include/lume/init.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_INIT_H -#define _LUME_INIT_H - -/* - * Boot-time section markers. - * - * Mirrors the subset of Linux include/linux/init.h that LumeOS uses: - * - * __init mark a function as boot-only — emitted into .init.text, - * which the kernel can later free once setup_arch / - * start_kernel have finished (LumeOS doesn't yet reclaim - * this memory, but the segment lives so future versions - * can without source changes). - * __initdata same for data, emitted into .init.data. - * asmlinkage ABI marker for C functions called from assembly. On - * RISC-V the default C ABI matches the assembly calling - * convention, so this is empty (matches Linux RISC-V). - * - * No attempt is made to mirror Linux's COMMAND_LINE_SIZE / saved_command_line - * machinery — that belongs in a future . - */ - -#define __init __attribute__((section(".init.text"))) -#define __initdata __attribute__((section(".init.data"))) - -#ifndef asmlinkage -#define asmlinkage /* RISC-V: empty, ABI already matches */ -#endif - -#endif /* _LUME_INIT_H */ diff --git a/na/include/lume/irq.h b/na/include/lume/irq.h deleted file mode 100644 index f5f34e4..0000000 --- a/na/include/lume/irq.h +++ /dev/null @@ -1,84 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * IRQ Manager — Dynamic Interrupt Dispatch - * - * Drivers register ISR callbacks at init time via register_handler(). - * The MI trap layer calls dispatch() when an external interrupt arrives. - * dispatch() runs in interrupt context — handlers must be short and - * non-blocking. - * - * Lock contract: - * - lock_ protects register/unregister (cold path). - * - dispatch() is lock-free: handler pointers are published with RELEASE - * semantics and read with ACQUIRE in dispatch(). - * - Handlers must not acquire VmSpace::lock or sleep. - * - * Reference: docs/specs/trap.md §3.3 - */ - -#include -#include - -/* IRQ handler callback signature */ -using IrqHandler = void (*)(uint32 irq, void *arg); - -/* Abstract Interrupt Controller Interface */ -struct IrqChip { - const char* name; - uint32 (*claim)(); - void (*complete)(uint32 irq); - void (*init_ap)(); - void (*enable)(uint32 irq); - void (*disable)(uint32 irq); - void (*set_priority)(uint32 irq, uint32 priority); -}; - -class IrqManager { -public: - void init(); - - /* Register an ISR for the given IRQ number. - * Returns 0 on success, -EINVAL if irq out of range, -EBUSY if already - * registered. */ - int register_handler(uint32 irq, IrqHandler handler, void *arg); - - /* Unregister the ISR for the given IRQ number. */ - void unregister_handler(uint32 irq); - - /* Dispatch an interrupt to the registered handler. - * Called from MI trap layer in interrupt context. Lock-free. */ - void dispatch(uint32 irq); - - /* Set the active interrupt controller chip */ - void set_chip(const IrqChip* chip); - const IrqChip* get_chip() const { return chip_; } - - /* Architecture-independent claim and complete for the active IRQ chip */ - uint32 claim() const; - void complete(uint32 irq) const; - void init_ap() const; - void enable(uint32 irq) const; - void disable(uint32 irq) const; - void set_priority(uint32 irq, uint32 priority) const; - - /* Check if a handler is registered for the given IRQ. */ - bool has_handler(uint32 irq) const; - - static constexpr uint32 kMaxIrqs = 64; - -private: - struct IrqEntry { - lume::atomic handler_ptr; // Stored as uint64 for atomic access - void *arg = nullptr; - }; - - const IrqChip* chip_ = nullptr; - IrqEntry entries_[kMaxIrqs]; -}; - -/* Global IRQ manager singleton */ -extern IrqManager g_irq_manager; diff --git a/na/include/lume/kbuild.h b/na/include/lume/kbuild.h deleted file mode 100644 index 4c3e12a..0000000 --- a/na/include/lume/kbuild.h +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_KBUILD_H -#define _LUME_KBUILD_H - -/* - * Helpers for arch//kernel/asm-offsets.c — 1:1 with Linux - * include/linux/kbuild.h. - * - * Trick: each DEFINE/OFFSET emits an inline-asm ".ascii" directive - * containing a literal "->" prefix. When the file is compiled with - * - * $(CC) -S asm-offsets.c -o asm-offsets.s - * - * the resulting assembly contains lines like - * - * ->KERNEL_MAP_VIRT_ADDR $0 offsetof(struct kernel_mapping, virt_addr) - * - * scripts/extract-asm-offsets.sed then rewrites those into proper - * `#define KERNEL_MAP_VIRT_ADDR 0` lines for inclusion from head.S. - * - * The benefit over hand-written #defines is that struct layout changes - * are caught at build time: rearrange a field and the asm offset - * changes automatically. - */ - -#define DEFINE(sym, val) \ - asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val)) - -#define BLANK() \ - asm volatile("\n.ascii \"->\"" : : ) - -#define OFFSET(sym, str, mem) \ - DEFINE(sym, offsetof(struct str, mem)) - -#define COMMENT(x) \ - asm volatile("\n.ascii \"->#" x "\"") - -#endif /* _LUME_KBUILD_H */ diff --git a/na/include/lume/kernel_map.h b/na/include/lume/kernel_map.h deleted file mode 100644 index 7eafe88..0000000 --- a/na/include/lume/kernel_map.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_KERNEL_MAP_H -#define _LUME_KERNEL_MAP_H - -/* - * struct kernel_mapping — where the kernel image lives in physical and - * virtual memory. Field names and order match Linux's - * arch/riscv/include/asm/page.h struct kernel_mapping so that: - * - * 1. arch/riscv/kernel/head.S can do `la a1, kernel_map` then - * `REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)` and read the virtual - * address. The KERNEL_MAP_* asm constants come from - * , produced by arch/riscv/kernel/ - * asm-offsets.c at build time — re-order the struct and the - * asm offsets follow automatically. - * - * 2. arch/riscv/mm/init.c (Linux origin) populates the same fields - * with the same semantics so no source edits are needed. - */ - -#ifndef __ASSEMBLY__ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct kernel_mapping { - unsigned long virt_addr; - unsigned long virt_offset; - uintptr_t phys_addr; - uintptr_t size; - unsigned long va_pa_offset; - unsigned long page_offset; - unsigned long va_kernel_pa_offset; -}; - -extern struct kernel_mapping kernel_map; - -#ifdef __cplusplus -} -#endif - -#else /* __ASSEMBLY__ */ - -/* Assembly sees only the field offsets, picked up from the generated - * header. Including the C struct above would be a parse error. */ -#include - -#endif /* __ASSEMBLY__ */ - -#endif /* _LUME_KERNEL_MAP_H */ diff --git a/na/include/lume/klog.h b/na/include/lume/klog.h deleted file mode 100644 index 69ab269..0000000 --- a/na/include/lume/klog.h +++ /dev/null @@ -1,200 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Kernel Logging Subsystem - * - * All kernel output goes through klog(), which formats messages into - * a fixed-size ring buffer and optionally emits them to the console. - * - * Architecture: - * vsnprintf_() ← pure formatting (mpaland/printf) - * ↓ - * KlogBuffer (ring) ← all messages stored here - * ↓ - * early_putc() (sync) ← current: synchronous UART output - * klogd thread (async) ← future: background flush thread - * /proc/kmsg ← future: userspace dmesg interface - * - * Reference: docs/specs/klog.md - */ - -#include - -/* ======================================================================== - * Log Levels (matches Linux kern_levels.h ordering) - * ======================================================================== */ - -enum KlogLevel : uint8 { - KLOG_EMERG = 0, /* System is unusable */ - KLOG_ALERT = 1, /* Action must be taken immediately */ - KLOG_CRIT = 2, /* Critical conditions */ - KLOG_ERR = 3, /* Error conditions */ - KLOG_WARN = 4, /* Warning conditions */ - KLOG_NOTICE = 5, /* Normal but significant condition */ - KLOG_INFO = 6, /* Informational */ - KLOG_DEBUG = 7, /* Debug-level messages */ -}; - -/* Log subsystems (bitmask-friendly) */ -enum KlogSubsys : uint8 { - KLOG_SUBSYS_GENERIC = 0, - KLOG_SUBSYS_SCHED = 1, - KLOG_SUBSYS_TRAP = 2, - KLOG_SUBSYS_IRQ = 3, - KLOG_SUBSYS_TIMER = 4, - KLOG_SUBSYS_VMM = 5, - KLOG_SUBSYS_PMM = 6, - KLOG_SUBSYS_SLAB = 7, - KLOG_SUBSYS_FS = 8, - KLOG_SUBSYS_DRIVER = 9, - KLOG_SUBSYS_TEST = 10, - KLOG_SUBSYS_SYSCALL = 11, /* per-syscall entry trace */ - KLOG_SUBSYS_EXEC = 12, /* ELF load / exec internals */ - KLOG_SUBSYS_SIGNAL = 13, /* signal delivery trace */ - KLOG_SUBSYS_TTY = 14, /* tty / job-control trace */ - KLOG_SUBSYS_MAX = 15, -}; - -/* Default: show everything up to INFO. DEBUG is compile-gated. */ -#ifndef KLOG_DEFAULT_LEVEL -#define KLOG_DEFAULT_LEVEL KLOG_INFO -#endif - -/* Ring buffer size: 2^KLOG_BUF_SHIFT bytes (default 64KB) */ -#ifndef KLOG_BUF_SHIFT -#define KLOG_BUF_SHIFT 16 -#endif - -#define KLOG_BUF_SIZE (1U << KLOG_BUF_SHIFT) - -/* Maximum length of a single log message (stack-allocated) */ -#define KLOG_LINE_MAX 256 - -struct KlogStats { - uint32 head; - uint32 tail; - uint32 console_tail; - uint32 dropped; - uint32 seq; -}; - -struct KlogRateLimit { - uint64 last_ts; - uint32 interval; - uint32 burst; - uint32 printed; - uint32 suppressed; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -/* ======================================================================== - * Initialization - * ======================================================================== */ - -/* Initialize the ring buffer and console backend. Call once from BSP. */ -void klog_init(void); - -/* ======================================================================== - * Core API - * ======================================================================== */ - -/* Write a leveled log message. Formats into ring buffer, then - * synchronously flushes to console if level <= current threshold. */ -__attribute__((format(printf, 2, 3))) -void klog(KlogLevel level, const char* fmt, ...); - -__attribute__((format(printf, 3, 4))) -void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...); - -/* Linux-style printk interface */ -__attribute__((format(printf, 1, 2))) -int printk(const char* fmt, ...); -int vprintk(const char* fmt, __builtin_va_list va); - -/* Set runtime log level filter. Messages above this level are - * still stored in the ring buffer but not printed to console. */ -void klog_set_level(KlogLevel level); - -void klog_set_subsys_mask(uint64 mask); -void klog_enable_subsys(KlogSubsys subsys); -void klog_disable_subsys(KlogSubsys subsys); - -/* ======================================================================== - * Ring Buffer Read (for future dmesg / /proc/kmsg) - * ======================================================================== */ - -/* Read up to 'size' bytes from the ring buffer into 'buf'. - * Returns number of bytes read. Non-destructive peek. */ -int klog_read(char* buf, int size); - -/* Read and consume entries (advances tail). */ -int klog_read_consume(char* buf, int size); - -/* Flush pending entries to console (advances console tail). */ -void klog_flush(void); - -bool klog_ratelimit(KlogRateLimit* rl, uint64 now); - -void klog_stats(KlogStats* out); - -/* ======================================================================== - * Convenience Macros - * ======================================================================== */ - -#define pr_emerg(fmt, ...) klog_ex(KLOG_EMERG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_alert(fmt, ...) klog_ex(KLOG_ALERT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_crit(fmt, ...) klog_ex(KLOG_CRIT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_err(fmt, ...) klog_ex(KLOG_ERR, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_warn(fmt, ...) klog_ex(KLOG_WARN, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_notice(fmt, ...) klog_ex(KLOG_NOTICE, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_info(fmt, ...) klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) - -#define pr_emerg_s(subsys, fmt, ...) klog_ex(KLOG_EMERG, subsys, fmt, ##__VA_ARGS__) -#define pr_alert_s(subsys, fmt, ...) klog_ex(KLOG_ALERT, subsys, fmt, ##__VA_ARGS__) -#define pr_crit_s(subsys, fmt, ...) klog_ex(KLOG_CRIT, subsys, fmt, ##__VA_ARGS__) -#define pr_err_s(subsys, fmt, ...) klog_ex(KLOG_ERR, subsys, fmt, ##__VA_ARGS__) -#define pr_warn_s(subsys, fmt, ...) klog_ex(KLOG_WARN, subsys, fmt, ##__VA_ARGS__) -#define pr_notice_s(subsys, fmt, ...) klog_ex(KLOG_NOTICE, subsys, fmt, ##__VA_ARGS__) -#define pr_info_s(subsys, fmt, ...) klog_ex(KLOG_INFO, subsys, fmt, ##__VA_ARGS__) - -#ifdef KLOG_ENABLE_DEBUG -#define pr_debug(fmt, ...) klog_ex(KLOG_DEBUG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#else -#define pr_debug(fmt, ...) do {} while (0) -#endif - -/* pr_debug_s: subsystem-tagged debug. Always compiled in (subsys is the gate); - * level KLOG_DEBUG ensures messages stay in the ring buffer but are filtered - * out of the console by default (console_level = KLOG_INFO). Toggle a subsys - * with klog_enable_subsys()/klog_disable_subsys() at runtime — or, to make - * the messages also reach the console for debugging, raise console_level - * via klog_set_level(KLOG_DEBUG). */ -#define pr_debug_s(subsys, fmt, ...) \ - klog_ex(KLOG_DEBUG, subsys, fmt, ##__VA_ARGS__) - -/* Linux kernel loglevel prefixes */ -#define KERN_SOH "\001" -#define KERN_EMERG KERN_SOH "0" -#define KERN_ALERT KERN_SOH "1" -#define KERN_CRIT KERN_SOH "2" -#define KERN_ERR KERN_SOH "3" -#define KERN_WARNING KERN_SOH "4" -#define KERN_NOTICE KERN_SOH "5" -#define KERN_INFO KERN_SOH "6" -#define KERN_DEBUG KERN_SOH "7" - -/* Boot stage log (thin wrapper over printk) */ -#define BOOT_STAGE(n, desc) \ - printk(KERN_INFO "STAGE%u %s\n", (unsigned)(n), (desc)) - -extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/kobject.h b/na/include/lume/kobject.h deleted file mode 100644 index 8581d6e..0000000 --- a/na/include/lume/kobject.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * kobject — generic refcounted object with parent/child relationship. - * - * Mirrors Linux include/linux/kobject.h. Embedded into device, gendisk, - * request_queue, block_device, etc. Each kobject has a kobj_type that - * provides the release callback called when the last reference drops. - * - * sysfs integration is not yet implemented — the sd/sysfs_ops fields are - * reserved for future use. - * - * Reference: docs/specs/storage_stack.md §4.2 - */ - -#include -#include -#include -#include - -struct kobj_type; - -struct kobject { - const char* name; - kobject* parent; - kref refcount; - const kobj_type* ktype; - list_node entry; // node in parent->children - list_node children; // head of children list - Spinlock lock; - bool state_in_sysfs; // reserved, always false for now -}; - -struct kobj_type { - void (*release)(kobject*); - /* sysfs_ops + default_attrs: reserved for future sysfs implementation. */ -}; - -/* Initialize a kobject in place; refcount starts at 1. - * Caller must provide a non-null ktype with a valid release(). */ -void kobject_init(kobject* k, const kobj_type* type); - -/* Link `k` under `parent` (which may be null for a detached root). - * Caller passes a stable `name` pointer (e.g. a static string or an - * already-strdup'd buffer); kobject does not own the name string. */ -int kobject_add(kobject* k, kobject* parent, const char* name); - -/* Unlink from parent. Does NOT drop the final ref — caller decides - * when to kobject_put(). */ -void kobject_del(kobject* k); - -/* Bump / drop reference. */ -void kobject_get(kobject* k); -void kobject_put(kobject* k); diff --git a/na/include/lume/kprintf.h b/na/include/lume/kprintf.h deleted file mode 100644 index b158602..0000000 --- a/na/include/lume/kprintf.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Kernel Formatted Output — kprintf - * - * Provides printf-style formatted output for the kernel. - * Built on top of stb_sprintf for full format string support - * without any libc dependency. - * - * Output is synchronous and directed to the console (UART). - * Safe to call from any context (interrupt or process). - * - * Usage: - * kprintf("Hello %s, addr=0x%lx\n", name, addr); - */ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Formatted print to console (like printf). */ -__attribute__((format(printf, 1, 2))) -int kprintf(const char* fmt, ...); - -/* Formatted print with va_list. */ -int kvprintf(const char* fmt, va_list va); - -/* Formatted snprintf into buffer. */ -__attribute__((format(printf, 3, 4))) -int ksnprintf(char* buf, int count, const char* fmt, ...); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/kref.h b/na/include/lume/kref.h deleted file mode 100644 index 13d53f3..0000000 --- a/na/include/lume/kref.h +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * kref — atomic reference counting. - * - * Mirrors Linux include/linux/kref.h. Use kref_init() to set count=1, - * kref_get() / kref_put() to add/drop refs. kref_put() invokes the - * release callback when the final ref is dropped and returns 1 in that - * case. - * - * Reference: docs/specs/storage_stack.md §4.1 - */ - -#include -#include - -struct kref { - lume::atomic refcount; -}; - -inline void kref_init(kref* k) -{ - k->refcount.store(1, __ATOMIC_RELEASE); -} - -inline void kref_get(kref* k) -{ - k->refcount.fetch_add(1, __ATOMIC_RELAXED); -} - -inline int kref_put(kref* k, void (*release)(kref*)) -{ - if (k->refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) { - release(k); - return 1; - } - return 0; -} - -inline int32 kref_read(const kref* k) -{ - return k->refcount.load(__ATOMIC_RELAXED); -} diff --git a/na/include/lume/libfdt.h b/na/include/lume/libfdt.h deleted file mode 100644 index e3496e9..0000000 --- a/na/include/lume/libfdt.h +++ /dev/null @@ -1,8 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _INCLUDE_LIBFDT_H_ -#define _INCLUDE_LIBFDT_H_ - -#include -#include "../../scripts/dtc/libfdt/libfdt.h" - -#endif /* _INCLUDE_LIBFDT_H_ */ diff --git a/na/include/lume/libfdt_env.h b/na/include/lume/libfdt_env.h deleted file mode 100644 index a95fb1d..0000000 --- a/na/include/lume/libfdt_env.h +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef LIBFDT_ENV_H -#define LIBFDT_ENV_H - -/* - * Kernel-side adapter for the standalone libfdt sources in - * scripts/dtc/libfdt/. libfdt's *.c files start with - * #include "libfdt_env.h" - * and expect this header to provide: - * - byte-swap helpers (cpu_to_fdt16/32/64, fdt16/32/64_to_cpu) - * - fdt16/32/64_t typedefs (just plain uint types — endianness is - * handled in the helpers above) - * - string / mem helpers (memcpy, memmove, memcmp, strlen, strcmp, - * strchr, strnlen, ...) - * - INT_MAX, UINT32_MAX (libfdt bounds-checks props with these) - * - size_t / uintptr_t (everywhere) - * - * LumeOS-native: no Linux includes. Provides the surface - * libfdt needs and nothing else. - */ - -#include /* uint16_t / uint32_t / uint64_t / size_t */ -#include /* INT_MAX */ -#include /* libc-shim memcpy / memcmp / strlen ... */ - -/* libfdt uses these names; both come from . */ -#define INT32_MAX 2147483647 -#define UINT32_MAX 4294967295U - -/* libfdt's "FDT word" types — opaque big-endian-stored u16/u32/u64. - * The byte-swap helpers below are the only access path. */ -typedef uint16_t fdt16_t; -typedef uint32_t fdt32_t; -typedef uint64_t fdt64_t; - -/* - * Endianness helpers. FDT (Device Tree Blob) is big-endian by spec. - * LumeOS runs on RISC-V (little-endian) and x86 (little-endian); we - * unconditionally byte-swap. - * - * Open-coded shifts rather than __builtin_bswapN — on RISC-V without - * the Zbb extension, gcc lowers __builtin_bswap32 to a libgcc call - * (__bswapsi2 / __bswapdi2) which isn't present in a freestanding - * kernel link. Mirrors Linux's scripts/dtc/libfdt/libfdt_env.h - * which uses ___constant_swab32-style explicit shifts. */ -static inline uint16_t __lumefdt_bswap16(uint16_t x) -{ - return (uint16_t)((x << 8) | (x >> 8)); -} -static inline uint32_t __lumefdt_bswap32(uint32_t x) -{ - return ((x & 0x000000FFu) << 24) | - ((x & 0x0000FF00u) << 8) | - ((x & 0x00FF0000u) >> 8) | - ((x & 0xFF000000u) >> 24); -} -static inline uint64_t __lumefdt_bswap64(uint64_t x) -{ - return ((x & 0x00000000000000FFull) << 56) | - ((x & 0x000000000000FF00ull) << 40) | - ((x & 0x0000000000FF0000ull) << 24) | - ((x & 0x00000000FF000000ull) << 8) | - ((x & 0x000000FF00000000ull) >> 8) | - ((x & 0x0000FF0000000000ull) >> 24) | - ((x & 0x00FF000000000000ull) >> 40) | - ((x & 0xFF00000000000000ull) >> 56); -} - -#define fdt16_to_cpu(x) __lumefdt_bswap16((uint16_t)(x)) -#define cpu_to_fdt16(x) ((fdt16_t)__lumefdt_bswap16((uint16_t)(x))) -#define fdt32_to_cpu(x) __lumefdt_bswap32((uint32_t)(x)) -#define cpu_to_fdt32(x) ((fdt32_t)__lumefdt_bswap32((uint32_t)(x))) -#define fdt64_to_cpu(x) __lumefdt_bswap64((uint64_t)(x)) -#define cpu_to_fdt64(x) ((fdt64_t)__lumefdt_bswap64((uint64_t)(x))) - -#endif /* LIBFDT_ENV_H */ diff --git a/na/include/lume/limits.h b/na/include/lume/limits.h deleted file mode 100644 index a275a71..0000000 --- a/na/include/lume/limits.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_LIMITS_H -#define _LUME_LIMITS_H - -/* - * Numeric limit constants — minimal subset needed by LumeOS kernel code. - * - * Values match the standard on all 64-bit LP64 targets, which - * is the only ABI LumeOS supports. Defined as plain integer literals so - * this header is usable from C, C++, and assembly without dragging in - * the toolchain's (which on hosted builds references libc - * internals we don't link against). - */ - -/* Signed/unsigned char */ -#define SCHAR_MIN (-128) -#define SCHAR_MAX 127 -#define UCHAR_MAX 255 -#define CHAR_MIN SCHAR_MIN -#define CHAR_MAX SCHAR_MAX - -/* Short */ -#define SHRT_MIN (-32768) -#define SHRT_MAX 32767 -#define USHRT_MAX 65535 - -/* Int */ -#define INT_MIN (-INT_MAX - 1) -#define INT_MAX 2147483647 -#define UINT_MAX 4294967295U - -/* Long (LP64: 64-bit) */ -#define LONG_MIN (-LONG_MAX - 1L) -#define LONG_MAX 9223372036854775807L -#define ULONG_MAX 18446744073709551615UL - -/* Long long */ -#define LLONG_MIN (-LLONG_MAX - 1LL) -#define LLONG_MAX 9223372036854775807LL -#define ULLONG_MAX 18446744073709551615ULL - -/* size_t (LP64) */ -#define SIZE_MAX ULONG_MAX -#define SSIZE_MAX LONG_MAX - -#endif /* _LUME_LIMITS_H */ diff --git a/na/include/lume/list.h b/na/include/lume/list.h deleted file mode 100644 index ed1202a..0000000 --- a/na/include/lume/list.h +++ /dev/null @@ -1,346 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include -#include - -/** - * @brief The node to be embedded in a struct to make it linkable. - * - * An object can be on multiple lists simultaneously by embedding multiple - * list_node members with different names. - */ -struct list_node { - list_node* prev; - list_node* next; - - /** - * @brief Initializes the node to point to itself, forming an empty list head. - */ - void init() { - prev = this; - next = this; - } - - /** - * @brief Checks if the list this node is the head of is empty. - * @return True if the list is empty, false otherwise. - */ - bool is_empty() const { - return next == this; - } -}; - -/* - * Type-safe container_of: given a pointer to a list_node member, - * recover a pointer to the enclosing object. - * Uses pointer-to-member arithmetic — no __builtin_offsetof needed. - */ -template -static inline T *list_entry(list_node *node) -{ - const size_t off = (size_t)(uintptr) & (((T *)nullptr)->*Member); - return reinterpret_cast(reinterpret_cast(node) - off); -} - -/** - * @brief A type-safe, intrusive, doubly-linked list. - * - * This class manages objects of type T that have a `list_node` member. - * It has zero runtime overhead compared to a manual C implementation and - * provides compile-time type safety. - * - * @tparam T The type of the object in the list. - * @tparam NodeMember A pointer-to-member specifying which `list_node` in T to use. - */ -template -class List { -private: - static list_node* get_node(T* obj) { - return &(obj->*NodeMember); - } - - // NOTE: `((T*)nullptr)->*NodeMember` is technically undefined behavior in - // standard C++. However, it's a common idiom in low-level programming - // (similar to `offsetof`) that compilers typically handle correctly, - // especially with flags like `-fno-delete-null-pointer-checks`. - // The alternative (using `offsetof`) would sacrifice the compile-time - // type safety of the pointer-to-member template parameter. - static T* get_obj(list_node* node) { - constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); - return reinterpret_cast(reinterpret_cast(node) - offset); - } - - static const T* get_obj(const list_node* node) { - constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); - return reinterpret_cast(reinterpret_cast(node) - offset); - } - -public: - class iterator; - class const_iterator; - - class iterator { - friend class List; - friend class const_iterator; - - public: - iterator(list_node* node) : node_(node) {} - - T& operator*() const { return *get_obj(node_); } - T* operator->() const { return get_obj(node_); } - - iterator& operator++() { - node_ = node_->next; - return *this; - } - iterator operator++(int) { - iterator tmp = *this; - ++(*this); - return tmp; - } - - iterator& operator--() { - node_ = node_->prev; - return *this; - } - iterator operator--(int) { - iterator tmp = *this; - --(*this); - return tmp; - } - - bool operator==(const iterator& other) const { return node_ == other.node_; } - bool operator!=(const iterator& other) const { return !(*this == other); } - - private: - list_node* node_; - }; - - class const_iterator { - public: - const_iterator(const list_node* node) : node_(node) {} - const_iterator(const iterator& other) : node_(other.node_) {} - - const T& operator*() const { return *get_obj(node_); } - const T* operator->() const { return get_obj(node_); } - - const_iterator& operator++() { - node_ = node_->next; - return *this; - } - const_iterator operator++(int) { - const_iterator tmp = *this; - ++(*this); - return tmp; - } - - const_iterator& operator--() { - node_ = node_->prev; - return *this; - } - const_iterator operator--(int) { - const_iterator tmp = *this; - --(*this); - return tmp; - } - - bool operator==(const const_iterator& other) const { return node_ == other.node_; } - bool operator!=(const const_iterator& other) const { return !(*this == other); } - - private: - const list_node* node_; - }; - - List() { head_.init(); } - ~List() = default; - - // This container is intrusive and does not own the objects. - // Copying or moving it is a logical error. - List(const List&) = delete; - List& operator=(const List&) = delete; - - bool empty() const { return head_.is_empty(); } - - void push_front(T* obj) { - list_node* node = get_node(obj); - node->next = head_.next; - node->prev = &head_; - head_.next->prev = node; - head_.next = node; - } - - void push_back(T* obj) { - list_node* node = get_node(obj); - node->prev = head_.prev; - node->next = &head_; - head_.prev->next = node; - head_.prev = node; - } - - /** - * @brief Splices all elements from another list to the front of this list. - * - * Moves all elements from `other` to the beginning of `this` list in O(1) - * time. The `other` list becomes empty after the operation. - * - * @param other The list to splice from. - */ - void splice_front(List& other) { - if (other.empty()) { - return; - } - list_node* this_first = head_.next; - list_node* other_first = other.head_.next; - list_node* other_last = other.head_.prev; - - // Link other's last to this's original first - other_last->next = this_first; - this_first->prev = other_last; - - // Link this's head to other's first - head_.next = other_first; - other_first->prev = &head_; - - // Reset the other list - other.head_.init(); - } - - /** - * @brief Splices all elements from another list to the back of this list. - * - * Moves all elements from `other` to the end of `this` list in O(1) - * time. The `other` list becomes empty after the operation. - * - * @param other The list to splice from. - */ - void splice_tail(List& other) { - if (other.empty()) { - return; - } - list_node* this_last = head_.prev; - list_node* other_first = other.head_.next; - list_node* other_last = other.head_.prev; - - // Link this's last to other's first - this_last->next = other_first; - other_first->prev = this_last; - - // Link other's last to this's head - other_last->next = &head_; - head_.prev = other_last; - - // Reset the other list - other.head_.init(); - } - - T* pop_front() { - if (empty()) { - return nullptr; - } - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - return get_obj(node); - } - - T* pop_back() { - if (empty()) { - return nullptr; - } - list_node* node = head_.prev; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - return get_obj(node); - } - - void remove(T* obj) { - list_node* node = get_node(obj); - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - } - - T* front() { - return empty() ? nullptr : get_obj(head_.next); - } - - const T* front() const { - return empty() ? nullptr : get_obj(head_.next); - } - - T* back() { - return empty() ? nullptr : get_obj(head_.prev); - } - - const T* back() const { - return empty() ? nullptr : get_obj(head_.prev); - } - - iterator begin() { return iterator(head_.next); } - iterator end() { return iterator(&head_); } - const_iterator begin() const { return const_iterator(head_.next); } - const_iterator end() const { return const_iterator(&head_); } - const_iterator cbegin() const { return const_iterator(head_.next); } - const_iterator cend() const { return const_iterator(&head_); } - -private: - list_node head_; -}; - -/* ======================================================================== - * Linux-compatible C-style list macros (for VFS subsystem) - * ======================================================================== */ - -using list_head = list_node; - -inline void list_init(list_node* head) { - head->prev = head; - head->next = head; -} - -inline bool list_empty(const list_node* head) { - return head->next == head; -} - -inline void __list_add(list_node* node, list_node* p, list_node* n) { - n->prev = node; - node->next = n; - node->prev = p; - p->next = node; -} - -inline void list_add(list_node* node, list_node* head) { - __list_add(node, head, head->next); -} - -inline void list_add_tail(list_node* node, list_node* head) { - __list_add(node, head->prev, head); -} - -inline void list_del(list_node* node) { - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = nullptr; - node->prev = nullptr; -} - -inline void list_del_init(list_node* node) { - node->prev->next = node->next; - node->next->prev = node->prev; - list_init(node); -} - -#define list_entry(ptr, type, member) container_of(ptr, type, member) - -#define list_for_each(pos, head) \ - for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) - -#define list_for_each_safe(pos, tmp, head) \ - for ((pos) = (head)->next, (tmp) = (pos)->next; \ - (pos) != (head); \ - (pos) = (tmp), (tmp) = (pos)->next) diff --git a/na/include/lume/lumeos.lds.h b/na/include/lume/lumeos.lds.h deleted file mode 100644 index 58d2f72..0000000 --- a/na/include/lume/lumeos.lds.h +++ /dev/null @@ -1,193 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Common Linker Script Macros (Machine-Independent) - * - * Shared section definitions that every architecture must emit. - * Architecture-specific lumeos.lds.S files #include this header - * and use these macros to avoid duplicating common boilerplate. - * - * Modeled after Linux include/asm-generic/vmlinux.lds.h - * Reference: docs/specs/linker_and_entry.md - */ - -#ifndef _LUME_LUMEOS_LDS_H -#define _LUME_LUMEOS_LDS_H - -/* ===== Text Section: executable code ===== - * - * .init.text is merged into the main .text segment so PC-relative `la` - * from regular code can call __init helpers without a thunk. Linux - * keeps a separate .init.text only to reclaim the pages after boot — - * LumeOS doesn't yet reclaim, so the merge is harmless and avoids an - * extra PT_LOAD entry. */ -#define TEXT_SECTION \ - .text : AT(ADDR(.text) - PAGE_OFFSET) { \ - _stext = .; \ - *(.text .text.*) \ - *(.init.text .init.text.*) \ - *(.fixup) \ - _etext = .; \ - } = 0 - -/* ===== Read-Only Data + C++ init_array + initramfs ===== - * - * Layout invariants: - * - extable / init_array / driver_array / initramfs all live INSIDE - * .rodata between _srodata and _erodata so the VMM has one R-only - * mapping to manage. - * - The relocation table (.lume.reloc) is NOT in here — it lives in - * its own padded output section (see RELOC_SECTION) so that its - * two-pass-build size variance can't shift downstream offsets. - * (Putting it in .rodata caused a heisenbug where pre-link offsets - * used by gen_reloc.py didn't match post-link offsets after the - * reloc.o blob was inserted.) - */ -#define RODATA_SECTION \ - .rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { \ - _srodata = .; \ - *(.rodata .rodata.*) \ - *(.srodata .srodata.* .srodata.cst*) \ - . = ALIGN(8); \ - __extable_start = .; \ - KEEP(*(.extable)) \ - __extable_end = .; \ - INIT_ARRAY \ - DRIVER_ARRAY \ - /* Embedded initramfs CPIO archive */ \ - . = ALIGN(8); \ - __initramfs_start = .; \ - KEEP(*(.init.ramfs)) \ - __initramfs_end = .; \ - . = ALIGN(4096); \ - _erodata = .; \ - } - -/* ===== .rel.dyn / .rela.dyn — Linker-emitted RELA relocations ===== - * - * Mirrors arch/riscv/kernel/vmlinux.lds.S §101-109: the .rela.dyn - * section sits BETWEEN init data and .rodata so it lands in a - * read-only PT_LOAD segment alongside .text/.rodata. The companion - * dynamic-linker bookkeeping (.dynamic, .dynsym, .dynstr, .hash, - * .gnu.hash) lives in the RW segment AFTER .data — Linux does the - * same under #ifdef CONFIG_RELOCATABLE (§139-147). */ -#define RELA_DYN_SECTION \ - .rel.dyn : AT(ADDR(.rel.dyn) - PAGE_OFFSET) { \ - *(.rel.dyn*) \ - } \ - .rela.dyn : AT(ADDR(.rela.dyn) - PAGE_OFFSET) ALIGN(8) {\ - __rela_dyn_start = .; \ - *(.rela .rela*) \ - __rela_dyn_end = .; \ - } - -/* ===== GOT — kept; everything else dynamic-linker is discarded ===== - * - * Strict mirror of arch/riscv/kernel/vmlinux.lds.S in Linux: the only - * dynamic-linker artefact the kernel keeps is `.got` (it's referenced - * by every -fPIE compiled global access). `.plt .dynamic .dynsym - * .dynstr .hash .gnu.hash .interp .dynbss` are all sent to /DISCARD/ - * via the DISCARDS macro below — the kernel never invokes a dynamic - * linker (head.S walks .rela.dyn directly). - * - * Keeping .plt was the cause of two distinct bugs: - * 1. PLT trampoline target for an undefined symbol (selftest_reloc - * after we removed test_reloc.cc) was unresolved → jumps to a - * slot that re-jumps to itself → instruction page fault. - * 2. .plt carries X permission; living in the same PT_LOAD as .data - * forces the load segment to RWX, which `ld` warns about. - * - * Sit in the same PT_LOAD as .data because .got is RW. */ -#define GOT_SECTION \ - .got : AT(ADDR(.got) - PAGE_OFFSET) { *(.got*) } - -/* ===== Embedded initramfs (CPIO archive) ===== */ -#define INITRAMFS_SECTION \ - .init.ramfs : AT(ADDR(.init.ramfs) - PAGE_OFFSET) { \ - . = ALIGN(8); \ - __initramfs_start = .; \ - KEEP(*(.init.ramfs)) \ - __initramfs_end = .; \ - } - -/* ===== C++ Global Constructor Table ===== */ -#define INIT_ARRAY \ - . = ALIGN(8); \ - __init_array_start = .; \ - KEEP(*(.init_array .init_array.*)) \ - __init_array_end = .; - -/* ===== Driver Registration Table ===== */ -#define DRIVER_ARRAY \ - . = ALIGN(8); \ - PROVIDE(__drivers_start = .); \ - KEEP(*(.drivers)) \ - PROVIDE(__drivers_end = .); - -/* ===== Initialized Read-Write Data ===== - * - * .init.data is merged into .data for the same reason TEXT_SECTION - * absorbs .init.text — LumeOS doesn't yet reclaim __init pages, so - * splitting them gains nothing while costing a PT_LOAD entry. */ -#define DATA_SECTION \ - .data : AT(ADDR(.data) - PAGE_OFFSET) { \ - _sdata = .; \ - *(.data .data.*) \ - *(.init.data .init.data.*) \ - ARCH_DATA_EXTRA \ - _edata = .; \ - } - -/* ===== BSS: Uninitialized Data ===== */ -#define BSS_SECTION \ - .bss : AT(ADDR(.bss) - PAGE_OFFSET) { \ - _sbss = .; \ - *(.bss .bss.*) \ - *(.sbss .sbss.*) \ - ARCH_BSS_EXTRA \ - _ebss = .; \ - } - -/* ===== Discard Unwanted Sections ===== - * - * Mirrors arch/riscv/kernel/vmlinux.lds.S in Linux: - * /DISCARD/ : { - * *(.dynbss) - * *(.dynstr*) - * *(.dynamic*) - * *(.plt*) - * *(.interp*) - * *(.eh_frame) - * *(.note.GNU-stack) - * } - * - * Why each is safe to discard: - * .plt — -Bsymbolic resolves all internal refs at link time; - * any remaining PLT entry is an undefined symbol that - * the kernel would have crashed on anyway. - * .dynamic etc. — kernel doesn't invoke a dynamic linker; head.S - * walks __rela_dyn_start..__rela_dyn_end manually. - * .interp — no PT_INTERP at runtime. - * .eh_frame* — no C++ exceptions in kernel (-fno-exceptions). - * .note.* — boot-time identification metadata, unused. */ -#define DISCARDS \ - /DISCARD/ : { \ - *(.dynbss) \ - *(.dynstr*) \ - *(.dynamic*) \ - *(.dynsym*) \ - *(.hash*) *(.gnu.hash*) \ - *(.plt*) \ - *(.interp*) \ - *(.eh_frame .eh_frame_hdr) \ - *(.note .note.*) \ - *(.comment) \ - } - -/* ===== Kernel Image Boundary ===== */ -#define KERNEL_END \ - . = ALIGN(4096); \ - _kernel_end = .; - -#endif /* _LUME_LUMEOS_LDS_H */ diff --git a/na/include/lume/memblock.h b/na/include/lume/memblock.h deleted file mode 100644 index cb9a76b..0000000 --- a/na/include/lume/memblock.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * memblock — Boot-time physical memory allocator - * - * A simple region-based allocator active from early boot until - * the buddy system (PMM) takes over. Inspired by Linux's memblock. - * - * Lifetime: - * 1. memblock_init() — discovers RAM via FDT, reserves kernel image - * 2. vmm_init() — uses memblock_alloc for page table pages & page_map - * 3. pmm_init() — absorbs remaining free regions into buddy - * 4. memblock_retire() — marks memblock inactive; all alloc via PMM - * - * This eliminates the need for post-init pointer relocation when - * rebinding page_map to a VMEMMAP virtual address. - */ - -#include - -inline constexpr int kMemblockMaxRegions = 128; - -/* Initialize memblock: scan FDT for RAM, reserve kernel image + firmware. */ -void memblock_init(uint64 fdt_paddr); - -/* Mark a physical range as reserved (will not be handed to buddy). */ -void memblock_reserve(uint64 base, uint64 size); - -/* Allocate `size` bytes aligned to `align` from top of free memory. - * Returns PA, or 0 on failure. Used for page_map. */ -uint64 memblock_alloc_top(uint64 size, uint64 align); - -/* Allocate `size` bytes aligned to `align` from bottom of free memory. - * Returns PA, or 0 on failure. Used for page table pages. */ -uint64 memblock_alloc(uint64 size, uint64 align); - -/* Query memory layout (same info as FDT). */ -uint64 memblock_get_mem_base(); -uint64 memblock_get_mem_size(); - -/* Is memblock still the active allocator? */ -bool memblock_is_active(); - -/* Mark memblock as retired (PMM takes over). */ -void memblock_retire(); - -/* Iterate all free (unreserved) regions in ascending order. - * Used by pmm_init() to feed free pages into the buddy system. */ -void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), - void* ctx); diff --git a/na/include/lume/mm_layout.h b/na/include/lume/mm_layout.h deleted file mode 100644 index 7744de0..0000000 --- a/na/include/lume/mm_layout.h +++ /dev/null @@ -1,73 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * mm_layout.h — User/Kernel address space boundary definitions - * - * Single source of truth for the user/kernel VA split. - * All modules (VMM, uaccess, page fault, tests) MUST include this - * header instead of defining their own constants. - * - * Layout (SV39, RISC-V): - * [0x0000_0000_0000, kUserVaLimit) — User space - * [kUserVaLimit, 0xFFFF_FFFF_FFFF) — Kernel space (direct map, vmemmap, etc.) - * - * Reference: docs/specs/linux_abi_mm_plan.md §5 - */ - -#include -#include - -/* User VA range: [kUserVaBase, kUserVaLimit) - * kUserVaBase avoids mapping the null page (catch NULL derefs). - * - * SV39: user space is the lower half [0, 2^38). The upper half - * [0xFFFFFFC000000000, 0xFFFFFFFFFFFFFFFF] is kernel space. - * Addresses between 2^38 and 0xFFFFFFC000000000 are non-canonical - * and will always fault — they must never be used. - * - * This value MUST match the page table mode selected by the MD layer. - * If we ever support SV48/SV57, update this accordingly. */ -inline constexpr uint64 kUserVaBase = kPageSize; -inline constexpr uint64 kUserVaLimit = 0x4000000000ULL; /* 2^38 = 256 GB */ - -/* Kernel VA starts at the sign-extended upper half */ -inline constexpr uint64 kKernelVaBase = 0xFFFFFFC000000000ULL; - -/* User stack: sits at the top of the user VA range. - * Matches Linux STACK_TOP = TASK_SIZE = kUserVaLimit. - * One guard page below the top, 8 MB stack. */ -inline constexpr uint64 kUserStackSize = 8ULL * 1024 * 1024; -inline constexpr uint64 kUserStackTop = kUserVaLimit - kPageSize; /* guard page */ -inline constexpr uint64 kUserStackBase = kUserStackTop - kUserStackSize; - -/* - * Check whether an address range [addr, addr+len) is entirely - * within user space. Returns true if valid. - */ -inline bool is_user_range(uint64 addr, uint64 len) -{ - if (addr < kUserVaBase) { - return false; - } - if (addr >= kUserVaLimit) { - return false; - } - if (addr + len < addr) { - return false; // Overflow - } - if (addr + len > kUserVaLimit) { - return false; - } - return true; -} - -/* - * Check whether a single address is in user space. - */ -inline bool is_user_address(uint64 addr) -{ - return addr >= kUserVaBase && addr < kUserVaLimit; -} diff --git a/na/include/lume/mman.h b/na/include/lume/mman.h deleted file mode 100644 index d952419..0000000 --- a/na/include/lume/mman.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * POSIX Memory Management Constants - * Reference: docs/specs/syscall.md §9 - */ - -/* mmap / mprotect protection flags */ -inline constexpr int PROT_NONE = 0x0; -inline constexpr int PROT_READ = 0x1; -inline constexpr int PROT_WRITE = 0x2; -inline constexpr int PROT_EXEC = 0x4; - -/* mmap flags */ -inline constexpr int MAP_SHARED = 0x01; -inline constexpr int MAP_PRIVATE = 0x02; -inline constexpr int MAP_FIXED = 0x10; -inline constexpr int MAP_ANONYMOUS = 0x20; diff --git a/na/include/lume/mount.h b/na/include/lume/mount.h deleted file mode 100644 index 13f8e62..0000000 --- a/na/include/lume/mount.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * mount.h — Internal mount structure - */ - -#include -#include -#include -#include - -struct dentry; -struct super_block; -struct file_system_type; - -/* ================================================================== */ -/* mount (VFS-internal) */ -/* ================================================================== */ - -struct mount { - vfsmount mnt; - mount* mnt_parent; - dentry* mnt_mountpoint; - list_node mnt_child; - list_head mnt_mounts; - list_node mnt_list; - lume::atomic mnt_count; -}; - -inline mount* real_mount(vfsmount* m) { - return reinterpret_cast(m); -} - -/* ================================================================== */ -/* Mount API */ -/* ================================================================== */ - -int do_mount(const char* dev, const char* dir, - const char* fstype, uint64 flags, void* data); -int do_umount(vfsmount* mnt, int flags); - -vfsmount* vfs_kern_mount(file_system_type* type, int flags, - const char* name, void* data); - -/* Global root */ -extern vfsmount* g_root_mnt; -extern dentry* g_root_dentry; - -void mount_init(); diff --git a/na/include/lume/mutex.h b/na/include/lume/mutex.h deleted file mode 100644 index 9719aa6..0000000 --- a/na/include/lume/mutex.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Mutex — Linux-style sleeping mutex (no priority inheritance). - * - * Semantics mirror Linux kernel/locking/mutex.c: - * - Single owner (nullptr when free). - * - FIFO wait list — first waiter is the next owner. - * - Recursive acquire by the same task is FORBIDDEN (panic). - * - Owner MUST be the one to unlock. - * - lock() may sleep; MUST NOT be called from IRQ context, with - * preemption disabled, or while holding a spinlock. - * - try_lock() never sleeps; may be called from any context. - * - * Priority inheritance lives in a separate primitive (RtMutex, future) - * just like Linux splits struct mutex / struct rt_mutex. - * - * Reference: docs/specs/mutex.md - */ - -#include -#include -#include - -struct TaskControlBlock; - -class Mutex { -public: - void init(const char* name); - - /* Blocking acquire (uninterruptible). */ - void lock(); - - /* Non-blocking attempt. Returns true if acquired. */ - bool try_lock(); - - /* Release; only the owner may call (else panic). */ - void unlock(); - - bool is_locked() const { return owner_ != nullptr; } - bool is_owned_by_current() const; - TaskControlBlock* owner() const { return owner_; } - -private: - TaskControlBlock* owner_ = nullptr; - Spinlock wait_lock_; - list_node wait_list_{}; - const char* name_ = nullptr; -}; - -/* RAII guard. */ -class MutexGuard { -public: - explicit MutexGuard(Mutex& m) : m_(m) { m_.lock(); } - ~MutexGuard() { m_.unlock(); } - MutexGuard(const MutexGuard&) = delete; - MutexGuard& operator=(const MutexGuard&) = delete; -private: - Mutex& m_; -}; diff --git a/na/include/lume/namei.h b/na/include/lume/namei.h deleted file mode 100644 index 1764693..0000000 --- a/na/include/lume/namei.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * namei.h — Pathname lookup (path resolution) - */ - -#include -#include /* struct path */ -#include /* struct qstr */ - -/* ================================================================== */ -/* LOOKUP_* flags */ -/* ================================================================== */ - -inline constexpr uint32 LOOKUP_FOLLOW = 0x0001; -inline constexpr uint32 LOOKUP_DIRECTORY = 0x0002; -inline constexpr uint32 LOOKUP_CREATE = 0x0200; -inline constexpr uint32 LOOKUP_EXCL = 0x0400; -inline constexpr uint32 LOOKUP_PARENT = 0x0010; - -/* ================================================================== */ -/* Path lookup API */ -/* ================================================================== */ - -int kern_path(const char* name, unsigned int flags, path* result); -int filename_lookup(int dfd, const char* name, unsigned int flags, path* result); -dentry* lookup_one_len(const char* name, dentry* parent, int len); - -void namei_init(); diff --git a/na/include/lume/new.h b/na/include/lume/new.h deleted file mode 100644 index cdcacb8..0000000 --- a/na/include/lume/new.h +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/page.h b/na/include/lume/page.h deleted file mode 100644 index 61bff95..0000000 --- a/na/include/lume/page.h +++ /dev/null @@ -1,151 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Page — Physical page descriptor - * - * Each Page describes one 4KB physical page. A flat array of Page - * structs (page_map) covers all physical memory discovered via FDT. - * - * Memory layout is compressed using a union: only one of free/slab - * metadata is valid at a time, determined by `type`. - * - * Fields: - * refcount — lifecycle reference count (atomic). Page cannot be - * freed while refcount > 0. - * mapcount — PTE mapping count (atomic). Number of page table - * entries pointing to this page. Used by COW and - * future reclaim. - * flags — page state bits (PG_*). Also serves as a bit-lock - * via PG_LOCKED for short critical sections. - * type — discriminant for the union (Free/Slab/Anon/File). - * mapping — pointer to reverse-mapping context. For anonymous - * pages, points to AnonVma (low bit set). For file - * pages, points to FileMapping (low bit clear). - * index — page offset within the mapping object. - * - * Reference: docs/specs/pmm.md §3.1 - */ - -#include -#include -#include - -/* Forward declarations */ -class KmemCache; - -/* ================================================================== */ -/* Page type discriminant */ -/* ================================================================== */ - -enum class PageType : uint8 { - Free, // In Buddy free list - PcpCached, // In Per-CPU cache (invisible to buddy coalescing) - Allocated, // Handed out but not yet typed (PMM default) - Slab, // Backing a Slab cache - Anon, // Anonymous user page - File, // File-backed page (page cache) -}; - -/* ================================================================== */ -/* Page flags (stored as atomic uint32) */ -/* ================================================================== */ - -inline constexpr uint32 kPgLocked = (1U << 0); // Bit-lock for short critical sections -inline constexpr uint32 kPgReferenced = (1U << 1); // Accessed since last check -inline constexpr uint32 kPgDirty = (1U << 2); // Modified, needs writeback -inline constexpr uint32 kPgWriteback = (1U << 3); // IO writeback in progress -inline constexpr uint32 kPgLru = (1U << 4); // On an LRU list -inline constexpr uint32 kPgMapped = (1U << 5); // Has at least one PTE mapping -inline constexpr uint32 kPgSwapcache = (1U << 6); // In swap cache (future) -inline constexpr uint32 kPgCow = (1U << 7); // Involved in COW sharing - -/* ================================================================== */ -/* Page descriptor */ -/* ================================================================== */ - -struct Page { - /* --- Counts (atomic, lock-free) --- */ - lume::atomic refcount; // Lifecycle ref: 0 = reclaimable - lume::atomic mapcount; // PTE mapping count: 0 = no user mapping - - /* --- State --- */ - lume::atomic flags; // PG_* bits (also serves as bit-lock) - PageType type; // Discriminant for union - uint8 order; // Buddy order (0–10, i.e. 4KB–4MB) - - /* --- Reverse mapping --- */ - void* mapping; // AnonVma* (low bit set) or FileMapping* - uint64 index; // Page offset within mapping object - - /* --- LRU (future) --- */ - list_node lru; // LRU list linkage (for reclaim) - - /* --- Subsystem-specific metadata --- */ - union { - /* Valid when type == Free */ - struct { - list_node free_link; // Link into Buddy or PCP free list - } free; - - /* Valid when type == Slab */ - struct { - KmemCache* cache; // Owning slab cache - uint32 obj_count; // Number of allocated objects on this page - void* freelist; // Head of in-page free object linked list - } slab; - - /* type == Anon / File: no extra union metadata (rmap via mapping) */ - }; -}; - -/* ================================================================== */ -/* Page bit-lock (lightweight, no sleep, used for rmap critical */ -/* sections and short metadata updates) */ -/* ================================================================== */ - -inline bool page_trylock(Page* page) -{ - uint32 old = page->flags.fetch_or(kPgLocked); - return (old & kPgLocked) == 0; -} - -inline void page_lock(Page* page) -{ - while (!page_trylock(page)) { - /* spin — acceptable for short critical sections only */ - } -} - -inline void page_unlock(Page* page) -{ - page->flags.fetch_and(~kPgLocked); -} - -/* ================================================================== */ -/* Mapping pointer helpers (Linux convention: low bit = anon) */ -/* ================================================================== */ - -inline bool page_is_anon(const Page* page) -{ - return (reinterpret_cast(page->mapping) & 1ULL) != 0; -} - -inline void* page_mapping_raw(const Page* page) -{ - return reinterpret_cast( - reinterpret_cast(page->mapping) & ~1ULL); -} - -inline void page_set_anon_mapping(Page* page, void* anon_vma) -{ - page->mapping = reinterpret_cast( - reinterpret_cast(anon_vma) | 1ULL); -} - -inline void page_set_file_mapping(Page* page, void* file_mapping) -{ - page->mapping = file_mapping; -} diff --git a/na/include/lume/page_cache.h b/na/include/lume/page_cache.h deleted file mode 100644 index abdc535..0000000 --- a/na/include/lume/page_cache.h +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * page_cache.h — Page Cache interface - * - * Provides a stable abstraction for caching physical pages indexed by - * (mapping, index) pairs. All file-backed fault paths should go - * through this interface so that upgrading the backing implementation - * (e.g. LRU eviction, writeback, radix tree) does not require - * changing any caller. - * - * Current implementation: simple open-addressing hash table. - * Future: radix tree + LRU lists + writeback integration. - * - * Terminology: - * mapping — opaque pointer identifying the backing object - * (FileMapping*, or any future cache-able object). - * index — page-granularity offset within the mapping. - * - * Reference: docs/specs/linux_abi_mm_plan.md §2.9, §3 - */ - -#include - -struct Page; - -/* - * Look up a cached page by (mapping, index). - * Returns the Page* if found (with refcount incremented), or nullptr. - * Caller must call page_cache_put() when done. - */ -Page* page_cache_get(void* mapping, uint64 index); - -/* - * Insert a page into the cache, associated with (mapping, index). - * The page's refcount is incremented by the cache. - * If a page already exists at (mapping, index), returns -EEXIST. - * Returns 0 on success, negative errno on failure. - */ -int page_cache_insert(void* mapping, uint64 index, Page* page); - -/* - * Release a reference obtained via page_cache_get(). - * This decrements the page's refcount. If the page was removed - * from the cache and refcount drops to 0, the page is freed. - */ -void page_cache_put(Page* page); - -/* - * Remove a page from the cache by (mapping, index). - * Decrements the cache's reference on the page. - * Returns the removed Page* (caller does NOT own a new ref), - * or nullptr if not found. - */ -Page* page_cache_remove(void* mapping, uint64 index); - -/* - * Remove all pages associated with a given mapping. - * Used during file close / inode eviction / truncate. - * Each removed page has its cache reference decremented. - */ -void page_cache_invalidate(void* mapping); - -/* - * Look up a page; if not found, allocate one, insert it, and return it. - * The returned page has its refcount incremented (caller must put). - * Returns nullptr on OOM. - * - * This is the primary interface for file-backed fault paths: - * the caller gets a page and then fills it with file data if kPgUptodate - * is not set. - */ -Page* page_cache_get_or_alloc(void* mapping, uint64 index); - -/* - * Initialize the page cache subsystem. - * Called once during boot after slab_init(). - */ -void page_cache_init(); diff --git a/na/include/lume/pgrp.h b/na/include/lume/pgrp.h deleted file mode 100644 index 127f3f8..0000000 --- a/na/include/lume/pgrp.h +++ /dev/null @@ -1,125 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Process Group / Session infrastructure. - * - * Linux models pgrp/session via struct pid + PIDTYPE_PGID/PIDTYPE_SID hlists. - * We use a simpler design that supports the same operations cheaply: - * - * - Each thread group owns one signal_struct (refcount-shared with - * CLONE_THREAD siblings). - * - signal_struct carries pgid/sid and is the linked-list node on a - * fixed-size pgrp hash table. - * - kill(pid<=0), tty_signal_pgrp, orphan checks all walk one bucket. - * - * Concurrency: g_pgrp_lock protects every bucket and the pgrp_link of - * every signal_struct on a bucket. signal_struct::lock is a finer-grain - * lock for fields that change without re-hashing (e.g. tty pointer). - * - * Reference: Linux include/linux/pid.h, kernel/pid.c - */ - -#include -#include -#include - -struct signal_struct; -struct TaskControlBlock; - -/* ================================================================== */ -/* Subsystem init */ -/* ================================================================== */ - -void pgrp_init(); - -/* ================================================================== */ -/* signal_struct lifecycle */ -/* ================================================================== */ - -/* - * Allocate a new signal_struct for `leader`, with sid=pgid=leader_pid, - * refcount=1, attached to pgrp `leader_pid`. Returns nullptr on OOM. - */ -signal_struct* alloc_signal_struct(int32 leader_pid, - TaskControlBlock* leader); - -/* - * Allocate a child signal_struct inheriting pgid/sid/tty from `parent`. - * The new struct is attached to the parent's pgrp and starts with - * refcount=1. Used by fork (non-CLONE_THREAD). - */ -signal_struct* fork_signal_struct(signal_struct* parent, - TaskControlBlock* child_leader); - -/* Increment refcount (CLONE_THREAD share). */ -void get_signal_struct(signal_struct* sig); - -/* - * Decrement refcount; on last drop, detach from pgrp hash and free. - * Pass the task that's dropping the reference so we can manage - * leader pointers if the leader exits while sub-threads remain. - */ -void put_signal_struct(signal_struct* sig); - -/* ================================================================== */ -/* Pgrp operations (assume signal_struct is allocated) */ -/* ================================================================== */ - -/* - * Move a signal_struct from its current pgrp to `new_pgid`. - * Used by setpgid / setsid. Takes g_pgrp_lock internally. - */ -void change_pgid(signal_struct* sig, int32 new_pgid); - -/* - * Invoke `fn(sig)` for every signal_struct whose pgid == target. - * Holds g_pgrp_lock for the entire walk — fn must not block, sleep, - * or take locks that order before g_pgrp_lock. - * - * Template-y but kept in header to avoid a function-ptr indirection; - * keep callsites lean. - */ -template -void for_each_pgrp_member(int32 target_pgid, F&& fn); - -/* - * Count the number of signal_structs in `pgid`. Used by setsid - * preflight (can't create a session if you're already a pgrp leader - * with members) and orphan checks. - */ -int pgrp_member_count(int32 pgid); - -/* ================================================================== */ -/* Implementation details exposed for the template above */ -/* ================================================================== */ - -constexpr int kPgrpHashShift = 6; -constexpr int kPgrpHashSize = 1 << kPgrpHashShift; /* 64 buckets */ -constexpr int kPgrpHashMask = kPgrpHashSize - 1; - -extern list_node g_pgrp_hash[kPgrpHashSize]; -extern Spinlock g_pgrp_lock; - -inline int pgrp_hash_idx(int32 pgid) { - return static_cast(static_cast(pgid)) & kPgrpHashMask; -} - -#include - -template -void for_each_pgrp_member(int32 target_pgid, F&& fn) -{ - LockGuard guard(g_pgrp_lock); - list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(target_pgid)]; - list_node* node = bucket->next; - while (node != bucket) { - list_node* nxt = node->next; /* fn may not unlink, but be safe */ - auto* sig = list_entry(node); - if (sig->pgid == target_pgid) - fn(sig); - node = nxt; - } -} diff --git a/na/include/lume/pgtable_types.h b/na/include/lume/pgtable_types.h deleted file mode 100644 index e309a82..0000000 --- a/na/include/lume/pgtable_types.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Page Table Entry Types — strongly typed wrappers. - * - * Layout and macro names mirror Linux's arch/riscv/include/asm/page.h - * and pgtable-64.h so that arch/riscv/mm/init.c can be copied from - * Linux without source edits. The wrapped struct prevents accidental - * mixing of a pte_t with a pgd_t or a raw physical address, which the - * previous bare-uint64 design in pmap.cc could not catch. - * - * Underlying width on RV64 LP64 is `unsigned long` = 64 bits, matching - * Linux exactly — assembly references (e.g. REG_S/REG_L on a pte_t*) - * see the same byte layout. - * - * SV39 uses 3 levels (pgd -> pmd -> pte); SV48 uses 4 (pgd -> pud -> - * pmd -> pte). p4d_t/pud_t are emitted unconditionally so that the - * higher-level abstractions in asm-generic/pgtable-nopud.h and friends - * (which Linux uses to collapse unused levels) work without #ifdef. - */ - -#include - -typedef struct { - unsigned long pte; -} pte_t; - -typedef struct { - unsigned long pmd; -} pmd_t; - -typedef struct { - unsigned long pud; -} pud_t; - -typedef struct { - unsigned long p4d; -} p4d_t; - -typedef struct { - unsigned long pgd; -} pgd_t; - -typedef struct { - unsigned long pgprot; -} pgprot_t; - -typedef struct page *pgtable_t; - -/* Field accessors */ -#define pte_val(x) ((x).pte) -#define pmd_val(x) ((x).pmd) -#define pud_val(x) ((x).pud) -#define p4d_val(x) ((x).p4d) -#define pgd_val(x) ((x).pgd) -#define pgprot_val(x) ((x).pgprot) - -/* Constructors (cast raw uint64 to wrapped type) */ -#define __pte(x) ((pte_t) { (x) }) -#define __pmd(x) ((pmd_t) { (x) }) -#define __pud(x) ((pud_t) { (x) }) -#define __p4d(x) ((p4d_t) { (x) }) -#define __pgd(x) ((pgd_t) { (x) }) -#define __pgprot(x) ((pgprot_t) { (x) }) - -#define PTE_FMT "%016lx" diff --git a/na/include/lume/pid.h b/na/include/lume/pid.h deleted file mode 100644 index 8503750..0000000 --- a/na/include/lume/pid.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * PID Allocator — Bitmap-based O(1) block search - * Reference: docs/specs/task.md §3.2 - */ - -#include -#include - -inline constexpr int32 kMaxPid = 32768; - -class PidAllocator { -public: - void init(); - int32 alloc(); - void free(int32 pid); - -private: - static constexpr int32 kBitmapWords = kMaxPid / 64; - Spinlock lock_; - uint64 bitmap_[kBitmapWords]; - int32 next_hint_ = 2; -}; - -extern PidAllocator g_pid_alloc; diff --git a/na/include/lume/pipe.h b/na/include/lume/pipe.h deleted file mode 100644 index c905c9a..0000000 --- a/na/include/lume/pipe.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * pipe.h — Pipe data structure and API - * - * A pipe is a unidirectional byte stream backed by a fixed-size - * circular buffer. Two file structs (reader + writer) share a - * single pipe_inode_info. - */ - -#include -#include -#include -#include - -struct file; -struct files_struct; - -/* ================================================================== */ -/* pipe_inode_info */ -/* ================================================================== */ - -inline constexpr int PIPE_BUF_SIZE = 4096; - -struct pipe_inode_info { - char buf[PIPE_BUF_SIZE]; - int rd_pos; /* Read position in circular buffer */ - int wr_pos; /* Write position in circular buffer */ - int len; /* Bytes currently in buffer */ - Spinlock lock; - WaitQueue rd_wait; /* Readers sleep here when empty */ - WaitQueue wr_wait; /* Writers sleep here when full */ - - lume::atomic readers; /* Number of open read ends */ - lume::atomic writers; /* Number of open write ends */ - lume::atomic refcount; /* Total references */ -}; - -/* ================================================================== */ -/* API */ -/* ================================================================== */ - -/* Create a pipe, install read/write fds into the given files_struct. - * Returns 0 on success, negative errno on failure. */ -int do_pipe2(files_struct* files, int fds[2], int flags); - -/* Allocate a fresh pipe_inode_info */ -pipe_inode_info* alloc_pipe_info(); -void free_pipe_info(pipe_inode_info* pipe); diff --git a/na/include/lume/platform_device.h b/na/include/lume/platform_device.h deleted file mode 100644 index ac3e527..0000000 --- a/na/include/lume/platform_device.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * platform_device / platform_driver — FDT-described memory-mapped devices. - * - * Mirrors Linux include/linux/platform_device.h. The platform bus is - * the catch-all bus for devices that don't live on a real bus like PCI - * or USB — every device discovered via the FDT lands here. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include -#include - -class DeviceNode; - -/* Compatible-string -> driver-private data table (Linux's of_device_id). */ -struct of_device_id { - const char* compatible; - const void* data; -}; - -struct platform_device { - device dev; - DeviceNode* of_node; // FDT node this device came from - uint64 mmio_base; // first `reg` cell from FDT (0 if none) - uint64 mmio_size; // second `reg` cell (0 if none) - int irq; // first interrupt (-1 if none / not parsed) -}; - -struct platform_driver { - device_driver driver; - int (*probe)(platform_device*); - void (*remove)(platform_device*); - const of_device_id* of_match_table; // NULL-terminated array -}; - -extern bus_type platform_bus; - -void platform_bus_init(); -int platform_driver_register(platform_driver* drv); -int platform_device_register(platform_device* pdev); - -/* Helper: pull current driver out of a probed device. */ -inline platform_driver* to_platform_driver(device_driver* drv) -{ - return container_of(drv, platform_driver, driver); -} -inline platform_device* to_platform_device(device* dev) -{ - return container_of(dev, platform_device, dev); -} - -/* Linker-set registration macro — drop a pointer to your platform_driver - * into the .drivers section. driver_probe_all() iterates this set at - * boot. Use a unique token in CONCAT to avoid name collisions. */ -#define PLATFORM_DRIVER(_drv) \ - extern platform_driver _drv; \ - static platform_driver* const __pdrv_ptr_##_drv \ - __attribute__((used, section(".drivers"))) = &_drv diff --git a/na/include/lume/pmm.h b/na/include/lume/pmm.h deleted file mode 100644 index 3e4b819..0000000 --- a/na/include/lume/pmm.h +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Physical Memory Manager (PMM) — Buddy System + Per-CPU Cache - * - * Manages all physical pages discovered via FDT. Built on a - * power-of-two buddy allocator for multi-page requests, with a - * Per-CPU single-page cache (PCP) to reduce lock contention on the - * hot single-page alloc/free path. - * - * Concurrency model: - * - PCP operations run with interrupts disabled (same-CPU safety). - * - Global buddy operations are protected by a single Spinlock. - * - Reference counts use __atomic builtins (lock-free). - * - * Reference: docs/specs/pmm.md - */ - -#include -#include -#include -#include - -/* Buddy system constants */ -inline constexpr int kMaxOrder = CONFIG_MAX_ORDER; // Orders 0..(N-1) - -/* Per-CPU cache dynamic parameters (calculated in pmm_init) */ -extern uint32 g_pcp_high_watermark; -extern uint32 g_pcp_batch_size; - -/* ================================================================== */ -/* Public free-function API */ -/* ================================================================== */ - -/* - * Initialize the PMM. Called by BSP after vmm_init() + memblock. - * page_map is already mapped at VMEMMAP VA by vmm_init. - * Populates buddy lists from memblock's free regions, then retires memblock. - * Panics on failure. - */ -void pmm_init(); - -/* --- Single-page fast path (uses PCP) --- */ - -// Allocate one 4KB page. Returns nullptr on OOM. -Page* pmm_alloc_page(); - -// Free one 4KB page. -void pmm_free_page(Page* page); - -/* --- Multi-page slow path (direct buddy) --- */ - -// Allocate 2^order contiguous pages. Returns nullptr on OOM. -Page* pmm_alloc_pages(uint8 order); - -// Free 2^order contiguous pages. -void pmm_free_pages(Page* page, uint8 order); - -/* --- Reference counting (atomic, lock-free) --- */ - -void page_incref(Page* page); -void page_decref(Page* page); - -/* --- Map counting (atomic, lock-free) --- */ - -inline void page_inc_mapcount(Page* page) { - page->mapcount.fetch_add(1); -} - -inline uint32 page_dec_mapcount(Page* page) { - return page->mapcount.fetch_sub(1) - 1; // Returns new value -} - -inline uint32 page_mapcount(const Page* page) { - return page->mapcount.load(__ATOMIC_RELAXED); -} - -/* --- Address conversion --- */ - -// Convert a Page* to its physical address. -PhysAddr page_to_pa(const Page* page); - -// Convert a physical address to its Page*. -Page* pa_to_page(PhysAddr pa); - -// Convert a Page* to a kernel virtual address. -VirtAddr page_to_va(const Page* page); - -/* --- PFN (Page Frame Number) abstraction --- */ -/* - * PFN is the fundamental unit for PA↔Page conversion. - * PA↔PFN converters already exist in addr.h (pa_to_pfn / pfn_to_pa). - * When sparse vmemmap lands, only pfn_to_page/page_to_pfn - * need to change — no callers are affected. - */ - -// Convert PFN to Page*. PFN = pa / PAGE_SIZE. -Page* pfn_to_page(uint64 pfn); - -// Convert Page* to PFN. -uint64 page_to_pfn(const Page* page); - -// Query PMM memory range (for VMM kernel mapping) -PhysAddr pmm_get_mem_base(); -uint64 pmm_get_mem_size(); diff --git a/na/include/lume/poll.h b/na/include/lume/poll.h deleted file mode 100644 index 7d5bca2..0000000 --- a/na/include/lume/poll.h +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * poll.h — POSIX poll() / ppoll() infrastructure. - * - * Linux pattern (include/linux/poll.h): - * - file_operations->poll(file, poll_table) returns a bitmask of - * POLL* events that are currently true on this file. - * - As a side effect, the poll callback calls poll_wait(file, wq, pt) - * to register the caller on every wait queue that might fire when - * state changes. sys_ppoll() then sleeps on those queues until - * one of them fires or a signal/timeout arrives. - * - * Our minimal implementation supports a SINGLE wait queue per poll call - * (sufficient for nfds==1, which is the BusyBox interactive shell case). - * Multi-fd polls work but fall back to busy re-checking; a proper - * per-fd waiter-list upgrade can come later. - */ - -#include - -struct file; -class WaitQueue; - -/* ================================================================== */ -/* UAPI: pollfd struct + POLL* event bits (Linux asm-generic) */ -/* ================================================================== */ - -struct pollfd { - int32 fd; - int16 events; - int16 revents; -}; - -inline constexpr int16 POLLIN = 0x0001; -inline constexpr int16 POLLPRI = 0x0002; -inline constexpr int16 POLLOUT = 0x0004; -inline constexpr int16 POLLERR = 0x0008; -inline constexpr int16 POLLHUP = 0x0010; -inline constexpr int16 POLLNVAL = 0x0020; -inline constexpr int16 POLLRDNORM = 0x0040; -inline constexpr int16 POLLRDBAND = 0x0080; -inline constexpr int16 POLLWRNORM = 0x0100; -inline constexpr int16 POLLWRBAND = 0x0200; - -/* ================================================================== */ -/* Kernel-side poll_table */ -/* ================================================================== */ - -/* - * Carried into file_operations->poll(). Drivers call poll_wait() with - * the wait queue they'd like the poller to sleep on; we record the - * first one. When `wq == nullptr` on entry, no real registration is - * needed (the caller is just probing readiness). - */ -struct poll_table { - WaitQueue* wq; /* first wait queue registered (single-wq design) */ -}; - -/* - * Called from f_op->poll() implementations to register a wait queue. - * If `pt` is null we're in a probe-only pass — caller doesn't want to - * sleep, just to read current revents. - */ -void poll_wait(file* fp, WaitQueue* wq, poll_table* pt); diff --git a/na/include/lume/ptrace.h b/na/include/lume/ptrace.h deleted file mode 100644 index 687d103..0000000 --- a/na/include/lume/ptrace.h +++ /dev/null @@ -1,76 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * PtRegs — Linux-compatible user register context - * - * This structure MUST match the Linux RISC-V pt_regs layout exactly. - * It is the canonical representation for: - * - Signal frame construction (ucontext / sigcontext) - * - ptrace(GETREGS/SETREGS) - * - /proc//regs - * - * The internal TrapFrame (arch/trapframe.h) has a DIFFERENT layout - * optimized for fast trap entry/exit. Conversion between the two - * is done via trapframe_to_pt_regs() / pt_regs_to_trapframe(). - * - * Reference: Linux arch/riscv/include/asm/ptrace.h - */ - -#include - -struct PtRegs { - uint64 epc; // Program counter at trap - uint64 ra; // x1 - uint64 sp; // x2 - uint64 gp; // x3 - uint64 tp; // x4 - uint64 t0; // x5 - uint64 t1; // x6 - uint64 t2; // x7 - uint64 s0; // x8 - uint64 s1; // x9 - uint64 a0; // x10 - uint64 a1; // x11 - uint64 a2; // x12 - uint64 a3; // x13 - uint64 a4; // x14 - uint64 a5; // x15 - uint64 a6; // x16 - uint64 a7; // x17 - uint64 s2; // x18 - uint64 s3; // x19 - uint64 s4; // x20 - uint64 s5; // x21 - uint64 s6; // x22 - uint64 s7; // x23 - uint64 s8; // x24 - uint64 s9; // x25 - uint64 s10; // x26 - uint64 s11; // x27 - uint64 t3; // x28 - uint64 t4; // x29 - uint64 t5; // x30 - uint64 t6; // x31 - /* CSRs */ - uint64 status; // sstatus - uint64 badaddr; // stval - uint64 cause; // scause - /* Syscall bookkeeping */ - uint64 orig_a0; // a0 value before syscall (for restart) -}; - -static_assert(sizeof(PtRegs) == 36 * 8, "PtRegs must be 288 bytes (Linux ABI)"); - -/* - * Convert between internal TrapFrame and Linux-ABI PtRegs. - * These are the ONLY places that know both layouts. - * Future TrapFrame restructuring only needs to update these two functions. - */ -namespace arch { struct TrapFrame; } - -void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src, - uint64 scause, uint64 stval); -void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src); diff --git a/na/include/lume/random.h b/na/include/lume/random.h deleted file mode 100644 index 740b060..0000000 --- a/na/include/lume/random.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include - -enum class RandomSeedSource : uint8 { - Unknown = 0, - FdtProvided, - HwRandom, - CycleFallback, -}; - -/* - * MI: Machine-Independent Random Number Generator API - * - * Provides early PRNG and eventually a full CSPRNG. - */ - -/* - * Initialize early PRNG with a seed (e.g., from FDT). - * If seed is 0, attempts to use hardware TRNG or falls back to timer jitter. - */ -void random_early_init(uint64 seed); - -/* Consume random bits */ -uint64 get_random_u64(); -uint32 get_random_u32(); -void get_random_bytes(void* buf, size_t len); - -/* Inject entropy (for future interrupt/device mixing) */ -void add_device_randomness(const void* buf, size_t len); - -/* Early-seed metadata (for boot diagnostics/KASLR logs). */ -RandomSeedSource random_early_seed_source(); -uint64 random_early_seed_value(); -const char* random_seed_source_name(RandomSeedSource source); diff --git a/na/include/lume/rbtree.h b/na/include/lume/rbtree.h deleted file mode 100644 index 80b0c6c..0000000 --- a/na/include/lume/rbtree.h +++ /dev/null @@ -1,207 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Trees — public API - * Adapted from Linux kernel include/linux/rbtree.h - * - * (C) 1999 Andrea Arcangeli - */ - -#pragma once - -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Core macros */ -/* ------------------------------------------------------------------ */ - -#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3UL)) - -#define rb_entry(ptr, type, member) container_of(ptr, type, member) - -#define RB_EMPTY_ROOT(root) (READ_ONCE((root)->rb_node) == nullptr) - -#define RB_EMPTY_NODE(node) \ - ((node)->__rb_parent_color == (unsigned long)(node)) -#define RB_CLEAR_NODE(node) \ - ((node)->__rb_parent_color = (unsigned long)(node)) - -/* ------------------------------------------------------------------ */ -/* Core API (implemented in lib/rbtree.cc) */ -/* ------------------------------------------------------------------ */ - -#ifdef __cplusplus -extern "C" { -#endif - -void rb_insert_color(struct rb_node *, struct rb_root *); -void rb_erase(struct rb_node *, struct rb_root *); - -struct rb_node *rb_next(const struct rb_node *); -struct rb_node *rb_prev(const struct rb_node *); -struct rb_node *rb_first(const struct rb_root *); -struct rb_node *rb_last(const struct rb_root *); - -struct rb_node *rb_first_postorder(const struct rb_root *); -struct rb_node *rb_next_postorder(const struct rb_node *); - -void rb_replace_node(struct rb_node *victim, struct rb_node *_new, - struct rb_root *root); - -#ifdef __cplusplus -} -#endif - -/* ------------------------------------------------------------------ */ -/* Inline helpers */ -/* ------------------------------------------------------------------ */ - -static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, - struct rb_node **rb_link) -{ - node->__rb_parent_color = (unsigned long)parent; - node->rb_left = node->rb_right = nullptr; - *rb_link = node; -} - -#define rb_entry_safe(ptr, type, member) \ - ({ __typeof__(ptr) ____ptr = (ptr); \ - ____ptr ? rb_entry(____ptr, type, member) : nullptr; \ - }) - -/* ------------------------------------------------------------------ */ -/* Cached (leftmost) tree helpers */ -/* ------------------------------------------------------------------ */ - -#define rb_first_cached(root) (root)->rb_leftmost - -static inline void rb_insert_color_cached(struct rb_node *node, - struct rb_root_cached *root, - bool leftmost) -{ - if (leftmost) - root->rb_leftmost = node; - rb_insert_color(node, &root->rb_root); -} - -static inline void rb_erase_cached(struct rb_node *node, - struct rb_root_cached *root) -{ - if (root->rb_leftmost == node) - root->rb_leftmost = rb_next(node); - rb_erase(node, &root->rb_root); -} - -static inline void rb_replace_node_cached(struct rb_node *victim, - struct rb_node *_new, - struct rb_root_cached *root) -{ - if (root->rb_leftmost == victim) - root->rb_leftmost = _new; - rb_replace_node(victim, _new, &root->rb_root); -} - -static inline void rb_erase_init(struct rb_node *n, struct rb_root *root) -{ - rb_erase(n, root); - RB_CLEAR_NODE(n); -} - -/* ------------------------------------------------------------------ */ -/* Generic insert / find helpers (inline, using function pointers) */ -/* ------------------------------------------------------------------ */ - -static inline void -rb_add(struct rb_node *node, struct rb_root *tree, - bool (*less)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_node; - struct rb_node *parent = nullptr; - - while (*link) { - parent = *link; - if (less(node, parent)) - link = &parent->rb_left; - else - link = &parent->rb_right; - } - - rb_link_node(node, parent, link); - rb_insert_color(node, tree); -} - -static inline void -rb_add_cached(struct rb_node *node, struct rb_root_cached *tree, - bool (*less)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_root.rb_node; - struct rb_node *parent = nullptr; - bool leftmost = true; - - while (*link) { - parent = *link; - if (less(node, parent)) { - link = &parent->rb_left; - } else { - link = &parent->rb_right; - leftmost = false; - } - } - - rb_link_node(node, parent, link); - rb_insert_color_cached(node, tree, leftmost); -} - -static inline struct rb_node * -rb_find(const void *key, const struct rb_root *tree, - int (*cmp)(const void *key, const struct rb_node *)) -{ - struct rb_node *node = tree->rb_node; - - while (node) { - int c = cmp(key, node); - if (c < 0) - node = node->rb_left; - else if (c > 0) - node = node->rb_right; - else - return node; - } - return nullptr; -} - -static inline struct rb_node * -rb_find_add(struct rb_node *node, struct rb_root *tree, - int (*cmp)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_node; - struct rb_node *parent = nullptr; - int c; - - while (*link) { - parent = *link; - c = cmp(node, parent); - if (c < 0) - link = &parent->rb_left; - else if (c > 0) - link = &parent->rb_right; - else - return parent; - } - - rb_link_node(node, parent, link); - rb_insert_color(node, tree); - return nullptr; -} - -/* ------------------------------------------------------------------ */ -/* Iteration macros */ -/* ------------------------------------------------------------------ */ - -#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ - for (pos = rb_entry_safe(rb_first_postorder(root), __typeof__(*pos), field); \ - pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ - __typeof__(*pos), field); 1; }); \ - pos = n) - diff --git a/na/include/lume/rbtree_augmented.h b/na/include/lume/rbtree_augmented.h deleted file mode 100644 index d4559c4..0000000 --- a/na/include/lume/rbtree_augmented.h +++ /dev/null @@ -1,252 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Trees — augmented operations & internal helpers - * Adapted from Linux kernel include/linux/rbtree_augmented.h - * - * (C) 1999 Andrea Arcangeli - * (C) 2002 David Woodhouse - * (C) 2012 Michel Lespinasse - */ - -#pragma once - -#include - -/* ------------------------------------------------------------------ */ -/* Augmented callback structure */ -/* ------------------------------------------------------------------ */ - -struct rb_augment_callbacks { - void (*propagate)(struct rb_node *node, struct rb_node *stop); - void (*copy)(struct rb_node *old, struct rb_node *_new); - void (*rotate)(struct rb_node *old, struct rb_node *_new); -}; - -extern "C" void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); - -extern "C" void __rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); - -static inline void -rb_insert_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - __rb_insert_augmented(node, root, augment->rotate); -} - -static inline void -rb_insert_augmented_cached(struct rb_node *node, - struct rb_root_cached *root, bool newleft, - const struct rb_augment_callbacks *augment) -{ - if (newleft) - root->rb_leftmost = node; - rb_insert_augmented(node, &root->rb_root, augment); -} - -/* ------------------------------------------------------------------ */ -/* Color / parent encoding helpers */ -/* ------------------------------------------------------------------ */ - -#define RB_RED 0 -#define RB_BLACK 1 - -#define __rb_parent(pc) ((struct rb_node *)(pc & ~3UL)) - -#define __rb_color(pc) ((pc) & 1) -#define __rb_is_black(pc) __rb_color(pc) -#define __rb_is_red(pc) (!__rb_color(pc)) -#define rb_color(rb) __rb_color((rb)->__rb_parent_color) -#define rb_is_red(rb) __rb_is_red((rb)->__rb_parent_color) -#define rb_is_black(rb) __rb_is_black((rb)->__rb_parent_color) - -static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) -{ - rb->__rb_parent_color = rb_color(rb) + (unsigned long)p; -} - -static inline void rb_set_parent_color(struct rb_node *rb, - struct rb_node *p, int color) -{ - rb->__rb_parent_color = (unsigned long)p + color; -} - -static inline void -__rb_change_child(struct rb_node *old, struct rb_node *_new, - struct rb_node *parent, struct rb_root *root) -{ - if (parent) { - if (parent->rb_left == old) - WRITE_ONCE(parent->rb_left, _new); - else - WRITE_ONCE(parent->rb_right, _new); - } else - WRITE_ONCE(root->rb_node, _new); -} - -/* ------------------------------------------------------------------ */ -/* Augmented erase (inline) */ -/* ------------------------------------------------------------------ */ - -static __always_inline struct rb_node * -__rb_erase_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - struct rb_node *child = node->rb_right; - struct rb_node *tmp = node->rb_left; - struct rb_node *parent, *rebalance; - unsigned long pc; - - if (!tmp) { - /* - * Case 1: node to erase has no more than 1 child (easy!) - */ - pc = node->__rb_parent_color; - parent = __rb_parent(pc); - __rb_change_child(node, child, parent, root); - if (child) { - child->__rb_parent_color = pc; - rebalance = nullptr; - } else - rebalance = __rb_is_black(pc) ? parent : nullptr; - tmp = parent; - } else if (!child) { - /* Still case 1, but this time the child is node->rb_left */ - tmp->__rb_parent_color = pc = node->__rb_parent_color; - parent = __rb_parent(pc); - __rb_change_child(node, tmp, parent, root); - rebalance = nullptr; - tmp = parent; - } else { - struct rb_node *successor = child, *child2; - - tmp = child->rb_left; - if (!tmp) { - /* - * Case 2: node's successor is its right child - */ - parent = successor; - child2 = successor->rb_right; - - augment->copy(node, successor); - } else { - /* - * Case 3: node's successor is leftmost under - * node's right child subtree - */ - do { - parent = successor; - successor = tmp; - tmp = tmp->rb_left; - } while (tmp); - child2 = successor->rb_right; - WRITE_ONCE(parent->rb_left, child2); - WRITE_ONCE(successor->rb_right, child); - rb_set_parent(child, successor); - - augment->copy(node, successor); - augment->propagate(parent, successor); - } - - tmp = node->rb_left; - WRITE_ONCE(successor->rb_left, tmp); - rb_set_parent(tmp, successor); - - pc = node->__rb_parent_color; - tmp = __rb_parent(pc); - __rb_change_child(node, successor, tmp, root); - - if (child2) { - rb_set_parent_color(child2, parent, RB_BLACK); - rebalance = nullptr; - } else { - rebalance = rb_is_black(successor) ? parent : nullptr; - } - successor->__rb_parent_color = pc; - tmp = successor; - } - - augment->propagate(tmp, nullptr); - return rebalance; -} - -static __always_inline void -rb_erase_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - struct rb_node *rebalance = __rb_erase_augmented(node, root, augment); - if (rebalance) - __rb_erase_color(rebalance, root, augment->rotate); -} - -static __always_inline void -rb_erase_augmented_cached(struct rb_node *node, struct rb_root_cached *root, - const struct rb_augment_callbacks *augment) -{ - if (root->rb_leftmost == node) - root->rb_leftmost = rb_next(node); - rb_erase_augmented(node, &root->rb_root, augment); -} - -/* ------------------------------------------------------------------ */ -/* Augmented callback declaration macros */ -/* ------------------------------------------------------------------ */ - -#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ -static inline void \ -RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ -{ \ - while (rb != stop) { \ - RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ - if (RBCOMPUTE(node, true)) \ - break; \ - rb = rb_parent(&node->RBFIELD); \ - } \ -} \ -static inline void \ -RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ -{ \ - RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ - RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new_->RBAUGMENTED = old_->RBAUGMENTED; \ -} \ -static void \ -RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ -{ \ - RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ - RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new_->RBAUGMENTED = old_->RBAUGMENTED; \ - RBCOMPUTE(old_, false); \ -} \ -RBSTATIC const struct rb_augment_callbacks RBNAME = { \ - RBNAME ## _propagate, \ - RBNAME ## _copy, \ - RBNAME ## _rotate \ -}; - -#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ - RBTYPE, RBAUGMENTED, RBCOMPUTE) \ -static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit_early) \ -{ \ - RBSTRUCT *child; \ - RBTYPE max = RBCOMPUTE(node); \ - if (node->RBFIELD.rb_left) { \ - child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (node->RBFIELD.rb_right) { \ - child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (exit_early && node->RBAUGMENTED == max) \ - return true; \ - node->RBAUGMENTED = max; \ - return false; \ -} \ -RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max) - diff --git a/na/include/lume/rbtree_types.h b/na/include/lume/rbtree_types.h deleted file mode 100644 index 688ade4..0000000 --- a/na/include/lume/rbtree_types.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Tree type definitions - * Adapted from Linux kernel include/linux/rbtree_types.h - */ - -#pragma once - -#include - -struct rb_node { - unsigned long __rb_parent_color; - struct rb_node *rb_right; - struct rb_node *rb_left; -} __attribute__((aligned(sizeof(long)))); - -struct rb_root { - struct rb_node *rb_node; -}; - -struct rb_root_cached { - struct rb_root rb_root; - struct rb_node *rb_leftmost; -}; - -#define RB_ROOT (struct rb_root) { nullptr, } -#define RB_ROOT_CACHED (struct rb_root_cached) { {nullptr, }, nullptr } - diff --git a/na/include/lume/sched.h b/na/include/lume/sched.h deleted file mode 100644 index ab26bac..0000000 --- a/na/include/lume/sched.h +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Scheduler Public Interface - * - * Linux-style sched_class core with per-CPU runqueues. - * Phase 2: O(1) bitmap class is the default scheduling class. - * - * Reference: docs/specs/scheduler.md - */ - -#include -#include - -struct TaskControlBlock; - -/* ======================================================================== - * Scheduler Constants — driven by CONFIG_xxx (see scripts/Kconfig.mk) - * ======================================================================== */ - -inline constexpr int kNumPriorities = CONFIG_NR_PRIORITIES; -inline constexpr int kDefaultPriority = CONFIG_DEFAULT_PRIORITY; -inline constexpr int kDefaultTimeSlice = CONFIG_DEFAULT_TIMESLICE; -inline constexpr int kIdlePriority = CONFIG_IDLE_PRIORITY; - -/* ======================================================================== - * Initialization - * ======================================================================== */ - -/* BSP: initialize all RunQueues, create BSP idle thread and boot TCB. */ -void sched_init(); - -/* AP: create per-CPU idle thread and boot TCB. */ -void sched_init_ap(); - -/* ======================================================================== - * Core Scheduling - * ======================================================================== */ - -/* Main scheduling entry point. Picks the next task and context-switches. - * Called voluntarily (yield, sleep, exit) or from trap return path. */ -void schedule(); - -/* Timer tick handler. Delegates to current task's sched_class. - * Called from MI trap layer in interrupt context. */ -void sched_tick(); - -/* Check if rescheduling is needed (called before returning to user mode). */ -void sched_check_preempt(); - -/* Voluntarily yield the current time slice. */ -void sched_yield(); - -/* ======================================================================== - * Task Queue Management (used by task.cc, waitqueue.cc) - * ======================================================================== */ - -/* Make a task runnable: add it to its CPU's RunQueue. */ -void sched_enqueue(TaskControlBlock* task); - -/* Mark a task as sleeping: remove it from the RunQueue. */ -void sched_dequeue(TaskControlBlock* task); - -/* ======================================================================== - * Time Slice Policy - * ======================================================================== */ - -/* Convert priority level to time slice length. - * Higher priority (lower number) → longer time slice. */ -int32 priority_to_timeslice(uint8 priority); - -/* ======================================================================== - * Task Migration & Load Balancing - * ======================================================================== */ - -/* - * sched_migrate_task — Move a task from its current CPU to target_cpu. - * - * The task must NOT be the currently running task. It is dequeued from - * its old runqueue and enqueued onto the target. Both runqueue locks - * are acquired in cpu_id order to prevent deadlock. - * - * Returns 0 on success, -EINVAL if task is current or target is invalid. - */ -int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu); - -/* - * sched_balance — Periodic load balancer. - * - * Called from sched_tick(). Checks if any CPU has significantly more - * runnable tasks and pulls work to the local runqueue. This is a - * simplified version of Linux's load_balance(). - */ -void sched_balance(); - -/* - * sched_nr_running — Get the number of runnable tasks on a CPU. - */ -uint32 sched_nr_running(uint32 cpu_id); diff --git a/na/include/lume/sched_class.h b/na/include/lume/sched_class.h deleted file mode 100644 index 08686c1..0000000 --- a/na/include/lume/sched_class.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Scheduler Class Interface (Linux-style) - * - * Each scheduling algorithm implements a sched_class and is chained - * in priority order. The scheduler core walks the chain to select - * the next runnable task. - */ - -#include - -struct RunQueue; -struct TaskControlBlock; - -struct SchedClass { - const char* name; - const SchedClass* next; // Linked list order: higher priority first - - void (*init_rq)(RunQueue* rq); - - void (*enqueue_task)(RunQueue* rq, TaskControlBlock* task); - void (*dequeue_task)(RunQueue* rq, TaskControlBlock* task); - TaskControlBlock* (*pick_next_task)(RunQueue* rq); - void (*put_prev_task)(RunQueue* rq, TaskControlBlock* task); - - void (*task_tick)(RunQueue* rq, TaskControlBlock* curr); - void (*yield_task)(RunQueue* rq, TaskControlBlock* curr); -}; - -extern const SchedClass g_sched_class_o1; -extern const SchedClass g_sched_class_idle; diff --git a/na/include/lume/selftest.h b/na/include/lume/selftest.h deleted file mode 100644 index 7040986..0000000 --- a/na/include/lume/selftest.h +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Boot Self-Test Framework - * - * Minimal assertion-based test harness that runs during kernel_main() - * after all subsystems are initialized. Each test is a void function - * that calls ST_ASSERT / ST_ASSERT_EQ macros. On failure, prints - * the failing expression and panics. - * - * Usage: - * void selftest_pmm(); // defined in test/test_pmm.cc - * void selftest_slab(); // defined in test/test_slab.cc - * - * All self-tests run only on BSP in single-core mode. - */ - -#include -#include -#include - -#define ST_ASSERT(cond) \ - do { \ - if (!(cond)) { \ - kprintf("[FAIL] %s:%d: assertion failed: %s\n", \ - __FILE__, __LINE__, #cond); \ - kernel_panic("selftest failed", #cond); \ - } \ - } while (0) - -#define ST_ASSERT_EQ(a, b) \ - do { \ - auto _a = (a); auto _b = (b); \ - if (_a != _b) { \ - kprintf("[FAIL] %s:%d: %s != %s (0x%llx != 0x%llx)\n", \ - __FILE__, __LINE__, #a, #b, \ - (uint64)(uintptr)(_a), (uint64)(uintptr)(_b)); \ - kernel_panic("selftest failed", #a " != " #b); \ - } \ - } while (0) - -#define ST_ASSERT_NE(a, b) \ - do { \ - auto _a = (a); auto _b = (b); \ - if (_a == _b) { \ - kprintf("[FAIL] %s:%d: %s == %s (both 0x%llx)\n", \ - __FILE__, __LINE__, #a, #b, \ - (uint64)(uintptr)(_a)); \ - kernel_panic("selftest failed", #a " == " #b); \ - } \ - } while (0) - -/* Announce a test suite beginning/end */ -inline void st_begin(const char* name) -{ - kprintf("[test] %s ... ", name); -} - -inline void st_pass() -{ - kprintf("PASS\n"); -} diff --git a/na/include/lume/setup.h b/na/include/lume/setup.h deleted file mode 100644 index f8e6cb6..0000000 --- a/na/include/lume/setup.h +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_SETUP_H -#define _LUME_SETUP_H - -/* - * Boot-time interfaces called between assembly and C. - * - * Mirrors the subset of Linux include/linux/setup.h relevant to LumeOS's - * Linux-aligned RISC-V boot flow: - * - * setup_vm Build early_pg_dir / trampoline_pg_dir before - * the MMU is enabled. Filled kernel_map for - * all subsequent code to consult. - * relocate_enable_mmu Switch SATP, jump to higher-half, apply - * .rela.dyn via relocate_kernel. - * relocate_kernel Walk .rela.dyn, patch R_RISCV_RELATIVE - * slots with addend + kaslr_offset. - * - * kernel_main (LumeOS's equivalent of Linux's start_kernel) is declared - * in kernel/main.cc and re-declared in arch-side glue — kept local to - * that file to avoid creating an artificial public API for the lone - * call site in head.S. - */ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -void setup_vm(uintptr_t load_pa, uintptr_t dtb_pa); -void relocate_enable_mmu(pgd_t *pgdir); -void relocate_kernel(void); - -#ifdef __cplusplus -} -#endif - -#endif /* _LUME_SETUP_H */ diff --git a/na/include/lume/signal.h b/na/include/lume/signal.h deleted file mode 100644 index 05b7542..0000000 --- a/na/include/lume/signal.h +++ /dev/null @@ -1,60 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Signal Infrastructure — Core signal delivery API - * - * Provides the interface between trap handling, signal delivery, - * and user-space signal handler execution. - * - * Reference: Linux include/linux/sched/signal.h - */ - -#include - -struct TaskControlBlock; -struct sighand_struct; -namespace arch { struct TrapFrame; } - -/* ------------------------------------------------------------------ - * sighand_struct lifecycle (Linux kernel/fork.c:copy_sighand pattern) - * ------------------------------------------------------------------ */ - -sighand_struct* alloc_sighand(); -sighand_struct* copy_sighand(sighand_struct* parent); -void get_sighand(sighand_struct* sh); -void put_sighand(sighand_struct* sh); - -/* - * force_sig_fault — Deliver a fault-induced signal to the current task. - * - * Called from trap handler when a user-mode exception cannot be resolved. - * Bypasses the blocked mask and enqueues the signal. - * - * @signo: Signal number (SIGSEGV, SIGBUS, SIGILL, SIGFPE, etc.) - * @code: si_code for siginfo_t (e.g., SEGV_MAPERR, SEGV_ACCERR) - * @addr: Fault address (stval / badaddr) - */ -void force_sig_fault(int signo, int code, uint64 addr); - -/* - * do_signal — Process pending signals before returning to user mode. - * - * Called at the end of trap_handle() when origin == FromUser. - * If a signal is pending and a handler is registered, constructs - * a signal frame on the user stack and redirects epc. - * - * @tf: TrapFrame to modify if signal delivery is needed. - */ -void do_signal(arch::TrapFrame* tf); - -/* - * signal_pending — Check if the task has unblocked pending signals. - * - * Used by blocking syscalls (e.g., n_tty_read) to detect whether - * they should return -EINTR. - */ -bool signal_pending(TaskControlBlock* task); - diff --git a/na/include/lume/signal_nr.h b/na/include/lume/signal_nr.h deleted file mode 100644 index 8e595d7..0000000 --- a/na/include/lume/signal_nr.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Linux-compatible signal numbers and related ABI constants. - * - * This mirrors the common Linux UAPI numbering used by asm-generic/signal.h. - * Keep the values stable so user-space binaries see the same numbers. - */ - -inline constexpr int _NSIG = 64; -inline constexpr int _NSIG_BPW = sizeof(unsigned long) * 8; -inline constexpr int _NSIG_WORDS = _NSIG / _NSIG_BPW; - -inline constexpr int SIGHUP = 1; -inline constexpr int SIGINT = 2; -inline constexpr int SIGQUIT = 3; -inline constexpr int SIGILL = 4; -inline constexpr int SIGTRAP = 5; -inline constexpr int SIGABRT = 6; -inline constexpr int SIGIOT = SIGABRT; -inline constexpr int SIGBUS = 7; -inline constexpr int SIGFPE = 8; -inline constexpr int SIGKILL = 9; -inline constexpr int SIGUSR1 = 10; -inline constexpr int SIGSEGV = 11; -inline constexpr int SIGUSR2 = 12; -inline constexpr int SIGPIPE = 13; -inline constexpr int SIGALRM = 14; -inline constexpr int SIGTERM = 15; -inline constexpr int SIGSTKFLT = 16; -inline constexpr int SIGCHLD = 17; -inline constexpr int SIGCONT = 18; -inline constexpr int SIGSTOP = 19; -inline constexpr int SIGTSTP = 20; -inline constexpr int SIGTTIN = 21; -inline constexpr int SIGTTOU = 22; -inline constexpr int SIGURG = 23; -inline constexpr int SIGXCPU = 24; -inline constexpr int SIGXFSZ = 25; -inline constexpr int SIGVTALRM = 26; -inline constexpr int SIGPROF = 27; -inline constexpr int SIGWINCH = 28; -inline constexpr int SIGIO = 29; -inline constexpr int SIGPOLL = SIGIO; -inline constexpr int SIGPWR = 30; -inline constexpr int SIGSYS = 31; -inline constexpr int SIGUNUSED = 31; - -/* Real-time signals start here in Linux UAPI. */ -inline constexpr int SIGRTMIN = 32; -inline constexpr int SIGRTMAX = _NSIG; diff --git a/na/include/lume/signal_types.h b/na/include/lume/signal_types.h deleted file mode 100644 index f66231b..0000000 --- a/na/include/lume/signal_types.h +++ /dev/null @@ -1,263 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Signal Types — Linux-compatible structures for signal delivery - * - * Follows the RISC-V LP64 ABI layout so that user-space programs - * compiled with musl / glibc see the correct struct sizes. - * - * Reference: Linux include/uapi/asm-generic/signal.h - * Linux include/uapi/asm-generic/siginfo.h - */ - -#include -#include -#include -#include - -/* ================================================================== */ -/* Signal set (64-bit bitmap) */ -/* ================================================================== */ - -struct sigset_t { - uint64 sig[_NSIG_WORDS]; /* _NSIG_WORDS = 1 on LP64 */ - - void clear() { for (auto& s : sig) s = 0; } - void fill() { for (auto& s : sig) s = ~0ULL; } - void add(int signo) { sig[(signo-1)/64] |= (1ULL << ((signo-1)%64)); } - void del(int signo) { sig[(signo-1)/64] &= ~(1ULL << ((signo-1)%64)); } - bool has(int signo) const { return (sig[(signo-1)/64] >> ((signo-1)%64)) & 1; } - bool is_empty() const { for (auto s : sig) if (s) return false; return true; } - - sigset_t operator&(const sigset_t& o) const { - sigset_t r; - for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] & o.sig[i]; - return r; - } - sigset_t operator|(const sigset_t& o) const { - sigset_t r; - for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] | o.sig[i]; - return r; - } - sigset_t operator~() const { - sigset_t r; - for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = ~sig[i]; - return r; - } -}; - -/* ================================================================== */ -/* siginfo_t (simplified, Linux UAPI compatible layout) */ -/* ================================================================== */ - -struct kernel_siginfo { - int si_signo; - int si_errno; - int si_code; - int _pad0; /* alignment */ - - union { - /* kill() */ - struct { - int32 _pid; - int32 _uid; - } _kill; - - /* SIGCHLD */ - struct { - int32 _pid; - int32 _uid; - int _status; - long _utime; - long _stime; - } _sigchld; - - /* SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP */ - struct { - uint64 _addr; - } _sigfault; - - /* Pad to 128 bytes total (Linux SI_MAX_SIZE) */ - char _pad[128 - 4*sizeof(int)]; - } _sifields; -}; - -/* ================================================================== */ -/* Signal action (kernel representation) */ -/* ================================================================== */ - -/* sa_flags bits (Linux UAPI) */ -inline constexpr uint64 SA_NOCLDSTOP = 0x00000001; -inline constexpr uint64 SA_NOCLDWAIT = 0x00000002; -inline constexpr uint64 SA_SIGINFO = 0x00000004; -inline constexpr uint64 SA_RESTORER = 0x04000000; -inline constexpr uint64 SA_ONSTACK = 0x08000000; -inline constexpr uint64 SA_RESTART = 0x10000000; -inline constexpr uint64 SA_NODEFER = 0x40000000; -inline constexpr uint64 SA_RESETHAND = 0x80000000; - -/* Special handler values */ -inline constexpr uint64 SIG_DFL = 0; -inline constexpr uint64 SIG_IGN = 1; -inline constexpr uint64 SIG_ERR = static_cast(-1); - -struct kernel_sigaction { - uint64 sa_handler; /* handler address or SIG_DFL/SIG_IGN */ - uint64 sa_flags; - uint64 sa_restorer; /* sigreturn trampoline (SA_RESTORER) */ - sigset_t sa_mask; /* signals blocked during handler */ -}; - -/* ================================================================== */ -/* Per-task signal state */ -/* ================================================================== */ - -/* Queued signal entry */ -struct sigqueue { - list_node list; - kernel_siginfo info; -}; - -/* Pending signal set + queue */ -struct sigpending { - list_node list; /* list of sigqueue entries */ - sigset_t signal; /* bitmap of pending signals */ - - void init() { - list.init(); - signal.clear(); - } -}; - -/* Signal handler table (shared between threads via CLONE_SIGHAND). - * - * Linux pattern (kernel/fork.c:copy_sighand): one sighand_struct per - * thread group by default, shared across CLONE_SIGHAND siblings, - * refcounted, freed when the last referencing task exits. Its - * `siglock` guards BOTH the action table and every per-task `pending` - * list of every task that references this sighand. That single-lock - * model is what lets send/dequeue/discard race-free without per-list - * locks. */ -struct sighand_struct { - Spinlock siglock; - uint32 refcount; /* # tasks sharing this sighand (≥1) */ - kernel_sigaction action[_NSIG]; - - void init() { - siglock.init("sighand"); - refcount = 1; - for (auto& a : action) { - a.sa_handler = SIG_DFL; - a.sa_flags = 0; - a.sa_restorer = 0; - a.sa_mask.clear(); - } - } -}; - -/* ================================================================== */ -/* signal_struct — per-thread-group shared state */ -/* ================================================================== */ - -struct TaskControlBlock; -struct tty_struct; - -/* - * Linux's signal_struct is the per-thread-group container holding: - * - Process group / session identity (pgid, sid) - * - Controlling terminal (tty) - * - Misc state shared across CLONE_THREAD siblings - * - * Refcount tracks the number of tasks in the thread group; when the last - * task in the group exits, the signal_struct is freed. Detachment from - * the pgrp hash also happens at that point. - * - * `pgrp_link` makes this signal_struct a member of one pgrp bucket; the - * pgrp subsystem walks the bucket to enumerate all thread groups whose - * pgid hashes there. - */ -struct signal_struct { - Spinlock lock; - uint32 refcount; /* # tasks sharing this (≥1) */ - int32 pgid; /* process group id */ - int32 sid; /* session id */ - tty_struct* tty; /* controlling tty (or nullptr) */ - int32 tty_old_pgrp; /* SIGHUP target on TIOCNOTTY */ - TaskControlBlock* leader; /* thread-group leader (pid==tgid) */ - list_node pgrp_link; /* on pgrp hash bucket */ - - void init(int32 leader_pid, TaskControlBlock* tg_leader) { - lock.init("signal"); - refcount = 1; - pgid = leader_pid; - sid = leader_pid; - tty = nullptr; - tty_old_pgrp = 0; - leader = tg_leader; - pgrp_link.init(); - } -}; - -/* ================================================================== */ -/* Default signal dispositions */ -/* ================================================================== */ - -enum SigDefaultAction { - SIG_ACT_TERM, /* Terminate */ - SIG_ACT_IGN, /* Ignore */ - SIG_ACT_CORE, /* Terminate + core dump */ - SIG_ACT_STOP, /* Stop process */ - SIG_ACT_CONT, /* Continue if stopped */ -}; - -/* Returns the default action for a standard signal */ -inline SigDefaultAction sig_default_action(int sig) -{ - switch (sig) { - case SIGCHLD: - case SIGURG: - case SIGWINCH: - return SIG_ACT_IGN; - - case SIGCONT: - return SIG_ACT_CONT; - - case SIGSTOP: - case SIGTSTP: - case SIGTTIN: - case SIGTTOU: - return SIG_ACT_STOP; - - case SIGQUIT: - case SIGILL: - case SIGTRAP: - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGSEGV: - case SIGXCPU: - case SIGXFSZ: - case SIGSYS: - return SIG_ACT_CORE; - - default: - return SIG_ACT_TERM; - } -} - -/* SIG_KERNEL_ONLY: cannot be caught or ignored */ -inline bool sig_kernel_only(int sig) -{ - return sig == SIGKILL || sig == SIGSTOP; -} - -/* ================================================================== */ -/* SigprocMask how values (Linux UAPI) */ -/* ================================================================== */ - -inline constexpr int SIG_BLOCK = 0; -inline constexpr int SIG_UNBLOCK = 1; -inline constexpr int SIG_SETMASK = 2; diff --git a/na/include/lume/slab.h b/na/include/lume/slab.h deleted file mode 100644 index 4ebade1..0000000 --- a/na/include/lume/slab.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * SLUB public API - * - * This header exposes the allocator entry points to the rest of the kernel. - * Implementation details stay in mm/slab.h and mm/slab.cc. - */ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initialize global slab caches. */ -void slab_init(); - -/* Allocate/free kernel memory. */ -void* kmalloc(uint32 size); -void kfree(void* ptr); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/smp.h b/na/include/lume/smp.h deleted file mode 100644 index a5292b6..0000000 --- a/na/include/lume/smp.h +++ /dev/null @@ -1,74 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * smp.h — Machine-Independent SMP primitives - * - * Provides cross-CPU communication (IPI) and TLB coordination. - * The MI interface calls through to arch-specific backends - * (e.g. SBI on RISC-V, APIC on x86). - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP - */ - -#include -#include -#include - -/* - * IPI reasons — each bit in the per-CPU pending mask - * corresponds to one reason. Multiple reasons can be - * pending simultaneously. - */ -enum IpiReason : uint32 { - IPI_RESCHEDULE = (1U << 0), /* Target should call schedule() */ - IPI_TLB_SHOOTDOWN = (1U << 1), /* Target should flush TLB */ - IPI_CALL_FUNCTION = (1U << 2), /* Target should run a function */ - IPI_HALT = (1U << 3), /* Target should halt */ -}; - -/* - * Per-CPU IPI pending state. - * Written by sender (atomically), consumed by receiver in IRQ context. - */ -struct IpiData { - lume::atomic pending; /* Bitmask of IpiReason */ - - /* TLB shootdown payload */ - uint64 tlb_start; - uint64 tlb_end; -}; - -/* Per-CPU IPI data array (BSS zero-initialized) */ -extern IpiData g_ipi_data[kMaxCpus]; - -/* - * smp_send_ipi — Send an IPI to a set of CPUs - * - * @cpu_mask: bitmask of target CPU IDs (bit N = CPU N) - * @reason: reason bits to set in the target's pending mask - * - * Safe to call from any context (interrupt or process). - */ -void smp_send_ipi(uint64 cpu_mask, uint32 reason); - -/* - * smp_send_reschedule — Convenience: send reschedule IPI to one CPU - */ -inline void smp_send_reschedule(uint8 cpu_id) -{ - smp_send_ipi(1ULL << cpu_id, IPI_RESCHEDULE); -} - -/* - * smp_init — Initialize SMP subsystem (called from BSP after trap_init) - */ -void smp_init(); - -/* - * smp_handle_ipi — Called from trap handler on receiving S-mode - * software interrupt. Processes all pending IPI reasons. - */ -void smp_handle_ipi(); diff --git a/na/include/lume/stdarg.h b/na/include/lume/stdarg.h deleted file mode 100644 index c8dc7f4..0000000 --- a/na/include/lume/stdarg.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef _LINUX_STDARG_H -#define _LINUX_STDARG_H - -typedef __builtin_va_list va_list; -#define va_start(v, l) __builtin_va_start(v, l) -#define va_end(v) __builtin_va_end(v) -#define va_arg(v, T) __builtin_va_arg(v, T) -#define va_copy(d, s) __builtin_va_copy(d, s) - -#endif diff --git a/na/include/lume/stringify.h b/na/include/lume/stringify.h deleted file mode 100644 index 0e84cbe..0000000 --- a/na/include/lume/stringify.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __LINUX_STRINGIFY_H -#define __LINUX_STRINGIFY_H - -/* Indirect stringification. Doing two levels allows the parameter to be a - * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) - * converts to "bar". - */ - -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) - -#define FILE_LINE __FILE__ ":" __stringify(__LINE__) - -#endif /* !__LINUX_STRINGIFY_H */ diff --git a/na/include/lume/syscall.h b/na/include/lume/syscall.h deleted file mode 100644 index 8185096..0000000 --- a/na/include/lume/syscall.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * System Call Interface — Machine-Independent Layer - * - * Defines the unified syscall handler signature and the MI dispatch function. - * MD layer (arch/riscv/syscall/syscall_md.cc) extracts registers into args[6] - * and calls syscall_dispatch(). - * - * Reference: docs/specs/syscall.md §4 - */ - -#include - -/* Unified syscall handler signature. - * All arguments and return value are register-width (uint64). - * Individual sys_* functions cast internally. */ -using SyscallHandler = int64 (*)(uint64, uint64, uint64, - uint64, uint64, uint64); - -/* MI-layer syscall dispatcher. - * Called by MD layer after extracting syscall number and arguments. - * Returns the syscall result (negative errno on error). */ -int64 syscall_dispatch(uint64 nr, uint64 args[6]); - -/* Register all implemented syscall handlers into the jump table. - * Called from kernel_main() after sched_init(). */ -void syscall_init(); - -/* MD-layer entry point — called from trap handler on TrapCause::Syscall. - * Extracts registers from TrapFrame, calls syscall_dispatch, writes result. */ -namespace arch { -struct TrapFrame; -} -void syscall_dispatch_md(arch::TrapFrame* tf); diff --git a/na/include/lume/syscall_nr.h b/na/include/lume/syscall_nr.h deleted file mode 100644 index 29e3904..0000000 --- a/na/include/lume/syscall_nr.h +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * System Call Numbers — RISC-V LP64 (asm-generic/unistd.h compatible) - * - * Numbering strictly follows Linux asm-generic so that musl libc / Busybox - * binaries can be run without modification. - * - * Reference: docs/specs/syscall.md §4.3 - */ - -inline constexpr int SYS_exit = 93; -inline constexpr int SYS_exit_group = 94; -inline constexpr int SYS_set_tid_address = 96; -inline constexpr int SYS_sched_yield = 124; -inline constexpr int SYS_uname = 160; -inline constexpr int SYS_getpid = 172; -inline constexpr int SYS_getppid = 173; -inline constexpr int SYS_getuid = 174; -inline constexpr int SYS_getgid = 176; -inline constexpr int SYS_gettid = 178; -inline constexpr int SYS_brk = 214; -inline constexpr int SYS_munmap = 215; -inline constexpr int SYS_clone = 220; -inline constexpr int SYS_execve = 221; -inline constexpr int SYS_mmap = 222; -inline constexpr int SYS_mprotect = 226; -inline constexpr int SYS_wait4 = 260; - -inline constexpr int SYS_getcwd = 17; -inline constexpr int SYS_dup = 23; -inline constexpr int SYS_dup3 = 24; -inline constexpr int SYS_ioctl = 29; -inline constexpr int SYS_mkdirat = 34; -inline constexpr int SYS_unlinkat = 35; -inline constexpr int SYS_chdir = 49; -inline constexpr int SYS_openat = 56; -inline constexpr int SYS_close = 57; -inline constexpr int SYS_pipe2 = 59; -inline constexpr int SYS_lseek = 62; -inline constexpr int SYS_read = 63; -inline constexpr int SYS_write = 64; -inline constexpr int SYS_writev = 66; -inline constexpr int SYS_ppoll = 73; -inline constexpr int SYS_fstatat = 79; -inline constexpr int SYS_fstat = 80; -inline constexpr int SYS_getdents64 = 61; - -inline constexpr int SYS_fcntl = 25; -inline constexpr int SYS_linkat = 37; -inline constexpr int SYS_mount = 40; -inline constexpr int SYS_umount2 = 39; - -inline constexpr int SYS_kill = 129; -inline constexpr int SYS_tkill = 130; -inline constexpr int SYS_tgkill = 131; -inline constexpr int SYS_sigaltstack = 132; -inline constexpr int SYS_rt_sigaction = 134; -inline constexpr int SYS_rt_sigprocmask = 135; -inline constexpr int SYS_rt_sigreturn = 139; -inline constexpr int SYS_reboot = 142; -inline constexpr int SYS_setpgid = 154; -inline constexpr int SYS_getpgid = 155; -inline constexpr int SYS_getsid = 156; -inline constexpr int SYS_setsid = 157; -inline constexpr int SYS_prctl = 167; -inline constexpr int SYS_geteuid = 175; -inline constexpr int SYS_getegid = 177; - -inline constexpr int SYS_futex = 98; -inline constexpr int SYS_nanosleep = 101; -inline constexpr int SYS_clock_gettime = 113; -inline constexpr int SYS_epoll_create1 = 20; -inline constexpr int SYS_epoll_ctl = 21; -inline constexpr int SYS_epoll_pwait = 22; - -inline constexpr int SYS_rt_sigtimedwait = 137; -inline constexpr int SYS_socket = 198; -inline constexpr int SYS_sendto = 206; diff --git a/na/include/lume/task.h b/na/include/lume/task.h deleted file mode 100644 index e959147..0000000 --- a/na/include/lume/task.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Task Control Block & Task State - * - * Central representation of every execution context in LumeOS, - * whether kernel thread or (future) user process. - * - * Reference: docs/specs/task.md §3.1, docs/specs/scheduler.md §3.1 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* Forward declarations */ -struct VmSpace; -struct SchedClass; - -/* ======================================================================== - * Task State Machine - * ======================================================================== */ - -enum class TaskState : uint8 { - Runnable = 0, // In a Per-CPU ready queue - Running = 1, // Currently executing on a CPU - Sleeping = 2, // Blocked on a WaitQueue - Zombie = 3, // Exited, waiting for parent wait() - Dead = 4, // Fully reclaimed, TCB can be freed - Stopped = 5, // Job control: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU -}; - -/* ======================================================================== - * Task Flags - * ======================================================================== */ - -inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user mode) -inline constexpr uint8 TF_IDLE = 0x02; // Idle thread -inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1) - -/* ======================================================================== - * Kernel Stack - * ======================================================================== */ - -inline constexpr uint64 kKernelStackSize = CONFIG_KERNEL_STACK_SIZE; -inline constexpr int kKernelStackOrder = (kKernelStackSize <= 4096) ? 0 - : (kKernelStackSize <= 8192) ? 1 - : (kKernelStackSize <= 16384) ? 2 - : (kKernelStackSize <= 32768) ? 3 : 4; -inline constexpr uint64 kStackCanary = 0xDEAD'BEEF'CAFE'BABEULL; - -/* ======================================================================== - * Task Control Block - * ======================================================================== */ - -struct TaskControlBlock { - /* ===== Scheduler hot fields (cache-line optimized) ===== */ - TaskState state; // Current state - uint8 priority; // Static priority [0, 31] - uint8 cpu_id; // Pinned CPU (Phase 2: no migration) - uint8 flags; // TF_KTHREAD | TF_IDLE | TF_INIT - - int32 time_slice; // Remaining time slice (ticks) - int32 pid; // Unique task ID (Linux: tid) - int32 tgid; // Thread group ID (Linux: pid) - int32 preempt_count; // >0: preemption disabled (holding locks etc.) - - list_node run_link; // RunQueue list node - list_node all_link; // Global task list node - - /* ===== Context ===== */ - arch::Context context; // Callee-saved registers (scheduler switch) - arch::TrapFrame* trapframe; // Trap frame (user-mode entry/exit) - arch::FpState fpstate; // FPU register state (f0-f31 + fcsr) - - /* ===== Kernel Stack ===== */ - uint64 kstack_pa; // Physical address - uint64 kstack_va; // Virtual address (stack bottom) - - /* ===== Address Space ===== */ - VmSpace* mm; // nullptr = kernel thread - - /* ===== Per-task lock ===== - * Protects: parent, children, sibling, exit_code, state transitions. - * Lock ordering: task->lock -> g_task_list_lock (never reverse). */ - Spinlock lock; - - /* ===== Task Hierarchy ===== */ - TaskControlBlock* parent; - list_node children; // Children list head - list_node sibling; // Sibling list node - WaitQueue wait_chld; // Parent waits on this for children to exit - - /* ===== Exit ===== */ - int32 exit_code; - int32 exit_signal; // Linux: signal sent to parent on exit. - // SIGCHLD = notify parent, become zombie - // -1 = auto-reap (kthreads) - uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: write 0 here on exit - - /* ===== Job control reporting ===== - * stopped_code: 0 means "no pending stop notification"; non-zero is the - * Linux wstatus value to hand back from wait4(WUNTRACED). Cleared - * after the parent consumes it via wait4 (or task termination). - * notify_continued: set when SIGCONT resumes us; consumed by - * wait4(WCONTINUED). */ - int32 stopped_code; - bool notify_continued; - - /* ===== Scheduler class metadata ===== */ - const SchedClass* sched_class; - void* sched_entity; - - /* ===== VFS ===== */ - struct files_struct* files; // Per-task fd table (shared via CLONE_FILES) - struct fs_struct* fs; // cwd/root (shared via CLONE_FS) - - /* ===== Signals ===== */ - struct sigpending pending; // Per-task pending signal queue - sigset_t blocked; // Blocked signal mask - struct sighand_struct* sighand; // Signal handler table (shared via CLONE_SIGHAND) - struct signal_struct* signal; // Thread-group identity: pgid/sid/ctty (shared via CLONE_THREAD) - - /* ===== Debug ===== */ - const char* name; -}; - -/* ======================================================================== - * current_task() — fast access to the running task on this CPU - * - * Must be called with interrupts disabled or while holding a spinlock. - * ======================================================================== */ -TaskControlBlock* current_task(); - -/* Terminate the current task with a signal. */ -void task_kill_current(int sig); diff --git a/na/include/lume/timer.h b/na/include/lume/timer.h deleted file mode 100644 index 9bdddd5..0000000 --- a/na/include/lume/timer.h +++ /dev/null @@ -1,43 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include - -/* - * MI Timer Subsystem - */ - -#include - -struct TimerList { - list_node entry; - uint64 expires; - void (*function)(TimerList*); - void* data; - - void init(void (*cb)(TimerList*)) { - entry.init(); - function = cb; - data = nullptr; - expires = 0; - } -}; - -extern uint64 g_jiffies; - -void timer_init(); - -// AP per-CPU initialization -void timer_init_ap(); - -// Handle timer interrupt tick (called from TrapHandler) -void timer_tick(); - -// Get configured timebase frequency -uint64 timer_get_freq(); - -void add_timer(TimerList* timer); -int del_timer(TimerList* timer); -int mod_timer(TimerList* timer, uint64 expires); diff --git a/na/include/lume/tlb.h b/na/include/lume/tlb.h deleted file mode 100644 index b4d51ea..0000000 --- a/na/include/lume/tlb.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tlb.h — Machine-Independent TLB management interface - * - * All TLB invalidation in the kernel MUST go through these functions. - * Direct use of arch::mmu::flush_tlb_*() is prohibited outside of - * this file's implementation. - * - * Current implementation: local CPU sfence.vma only (single-core). - * When SMP IPI infrastructure is available, tlb_shootdown_range() - * and tlb_shootdown_all() will broadcast invalidation to remote - * CPUs via IPI — no caller changes required. - * - * Reference: docs/specs/linux_abi_mm_plan.md §4 - */ - -#include - -struct VmSpace; - -/* - * Invalidate TLB entries for a VA range [start, end) on ALL CPUs - * that may have cached translations for the given address space. - * - * @mm: address space (used to determine which CPUs to IPI; - * nullptr means kernel address space = all CPUs). - * @start: first VA to invalidate (page-aligned). - * @end: one-past-last VA to invalidate (page-aligned). - * - * If (end - start) exceeds a threshold, falls back to full flush. - */ -void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end); - -/* - * Invalidate a single page's TLB entry on ALL CPUs. - * Convenience wrapper around tlb_shootdown_range(). - */ -void tlb_shootdown_page(VmSpace* mm, uint64 va); - -/* - * Full TLB flush on ALL CPUs. - * Used after page table switch, large-scale munmap, etc. - */ -void tlb_shootdown_all(); diff --git a/na/include/lume/trap.h b/na/include/lume/trap.h deleted file mode 100644 index 61a5054..0000000 --- a/na/include/lume/trap.h +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Trap Subsystem — Machine-Independent Interface - * - * Defines TrapCause enum and MI-layer declarations. - * MD layer (arch/riscv/trap/trap_md.cc) translates scause → TrapCause. - * MI layer (kernel/trap.cc) dispatches based on TrapCause alone. - * - * Reference: docs/specs/trap.md §3.2 - */ - -#include - -/* MI-layer trap cause classification. - * MD layer is responsible for translating architecture-specific cause codes - * (e.g., RISC-V scause) into these semantic values. MI layer never reads CSRs. */ -enum class TrapCause : uint32 { - /* --- Exceptions (Synchronous) --- */ - PageFaultLoad = 0, // Load page fault - PageFaultStore = 1, // Store/AMO page fault - PageFaultExec = 2, // Instruction page fault - Syscall = 3, // User-mode ecall - IllegalInst = 4, // Illegal instruction - Breakpoint = 5, // Breakpoint (ebreak) - AccessFault = 6, // Bus access fault (not page fault) - AlignFault = 7, // Address misalignment - - /* --- Interrupts (Asynchronous) --- */ - IrqTimer = 16, // S-mode timer interrupt - IrqSoftware = 17, // S-mode software interrupt (IPI) - IrqExternal = 18, // S-mode external interrupt (PLIC) - - /* --- Unknown --- */ - Unknown = 255, -}; - -/* High-level trap class for routing and stats. */ -enum class TrapClass : uint8 { - Exception = 0, - Interrupt = 1, - Syscall = 2, - Unknown = 3, -}; - -/* Origin privilege of the trap. */ -enum class TrapOrigin : uint8 { - FromUser = 0, - FromKernel = 1, -}; - -/* Unified trap context passed from MD to MI. */ -struct TrapInfo { - TrapClass cls; - TrapCause cause; - TrapOrigin origin; - uint64 val; // stval (faulting address or instruction) - uint64 epc; // sepc at trap entry (may be adjusted by MD) - uint64 scause; // raw scause for diagnostics -}; - -/* Trap statistics for debugging/testing. */ -inline constexpr uint32 kTrapCauseCount = 12; - -struct TrapStats { - uint64 total; - uint64 by_cause[kTrapCauseCount]; - TrapCause last_cause; - TrapClass last_class; - TrapOrigin last_origin; - uint64 last_val; - uint64 last_epc; - uint64 last_scause; -}; - -namespace arch { -struct TrapFrame; // Forward declaration (defined in arch/trapframe.h) -} - -/* - * trap_init() — BSP initialization - * Sets stvec to kernel_trap_entry (handles traps while in S-mode). - * Initializes the global IrqManager. - * Called from kernel_main() after VMM and before PLIC. - */ -void trap_init(); - -/* - * trap_init_ap() — AP per-CPU initialization - * Sets this CPU's stvec. - */ -void trap_init_ap(); - -/* - * trap_handle() — MI-layer semantic dispatcher - * Called by MD layer after scause → TrapCause translation. - * Routes to page fault handler, syscall dispatcher, IRQ manager, etc. - * - * tf: pointer to the current TrapFrame - * info: translated trap context from MD layer - */ -void trap_handle(arch::TrapFrame* tf, const TrapInfo& info); - -/* Trap statistics helpers (MI layer). */ -void trap_stats_get(TrapStats* out); -void trap_stats_reset(); diff --git a/na/include/lume/tty.h b/na/include/lume/tty.h deleted file mode 100644 index 34cf7bb..0000000 --- a/na/include/lume/tty.h +++ /dev/null @@ -1,165 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tty.h — TTY subsystem core structures - * - * tty_struct is the central object representing an open terminal. - * It connects a hardware driver (tty_driver) with a line discipline - * (tty_ldisc) to implement the full terminal abstraction. - * - * Architecture: - * user read/write → tty_core → tty_ldisc (n_tty) → tty_driver (uart/pty) - * - * Reference: Linux include/linux/tty.h, include/linux/tty_ldisc.h - */ - -#include -#include -#include -#include - -struct tty_struct; -struct file; -struct file_operations; -struct inode; - -/* ================================================================== */ -/* tty_driver — hardware/pseudo-terminal driver interface */ -/* ================================================================== */ - -struct tty_driver_ops { - int (*open)(tty_struct* tty); - void (*close)(tty_struct* tty); - int (*write)(tty_struct* tty, const char* buf, int count); - int (*write_room)(tty_struct* tty); - void (*put_char)(tty_struct* tty, char ch); - void (*flush_chars)(tty_struct* tty); - int (*chars_in_buffer)(tty_struct* tty); - void (*flush_buffer)(tty_struct* tty); - int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg); - void (*throttle)(tty_struct* tty); - void (*unthrottle)(tty_struct* tty); - void (*hangup)(tty_struct* tty); -}; - -struct tty_driver { - const char* name; /* "ttyS", "ttySIF", "pts" */ - int major; - int minor_start; - int num; /* number of devices */ - const tty_driver_ops* ops; -}; - -/* ================================================================== */ -/* tty_ldisc — line discipline interface */ -/* ================================================================== */ - -struct tty_ldisc_ops { - const char* name; - int num; /* N_TTY = 0 */ - - int (*open)(tty_struct* tty); - void (*close)(tty_struct* tty); - int (*read)(tty_struct* tty, file* fp, char* buf, uint64 count); - int (*write)(tty_struct* tty, file* fp, const char* buf, uint64 count); - void (*receive_buf)(tty_struct* tty, const char* buf, int count); - int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg); - void (*flush_buffer)(tty_struct* tty); - int (*chars_in_buffer)(tty_struct* tty); - void (*set_termios)(tty_struct* tty, const termios* old); - void (*hangup)(tty_struct* tty); -}; - -struct tty_ldisc { - const tty_ldisc_ops* ops; - void* data; /* ldisc private data (n_tty_data*) */ -}; - -/* ================================================================== */ -/* tty_struct — central TTY object */ -/* ================================================================== */ - -inline constexpr int TTY_BUF_SIZE = 4096; - -/* TTY flags */ -inline constexpr uint32 TTY_THROTTLED = (1 << 0); -inline constexpr uint32 TTY_IO_ERROR = (1 << 1); -inline constexpr uint32 TTY_CLOSING = (1 << 2); -inline constexpr uint32 TTY_HUPPING = (1 << 3); - -struct tty_struct { - int index; /* device index */ - const tty_driver* driver; /* hardware driver */ - tty_ldisc ldisc; /* current line discipline */ - tty_struct* link; /* PTY peer (master ↔ slave) */ - - termios termios_data; /* terminal settings */ - winsize ws; /* window size */ - - int32 pgrp; /* foreground process group */ - int32 session; /* controlling session */ - - /* Output buffer (tty_core → driver) */ - char write_buf[TTY_BUF_SIZE]; - int write_cnt; - - /* Synchronization */ - WaitQueue read_wait; - WaitQueue write_wait; - Spinlock lock; - - uint32 flags; - int open_count; - - /* Private driver data */ - void* driver_data; -}; - -/* ================================================================== */ -/* TTY core API */ -/* ================================================================== */ - -/* Initialize the TTY subsystem (creates /dev/console TTY) */ -void tty_init(); - -/* Allocate and initialize a new tty_struct */ -tty_struct* tty_alloc(const tty_driver* driver, int index); - -/* Open/close a TTY (called from chardev open/close) */ -int tty_open(tty_struct* tty); -void tty_close(tty_struct* tty); - -/* Core I/O (called from file_operations) */ -int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count); -int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count); -long tty_ioctl(tty_struct* tty, file* fp, unsigned int cmd, uint64 arg); - -/* Called by hardware driver when bytes arrive */ -void tty_receive_buf(tty_struct* tty, const char* buf, int count); - -/* Signal helpers */ -void tty_signal_pgrp(tty_struct* tty, int sig); - -/* file_operations for TTY devices */ -extern file_operations tty_fops; - -/* Global console TTY */ -extern tty_struct* g_console_tty; - -/* ================================================================== */ -/* N_TTY line discipline */ -/* ================================================================== */ - -extern const tty_ldisc_ops n_tty_ops; - -/* ================================================================== */ -/* UART TTY driver */ -/* ================================================================== */ - -extern const tty_driver uart_tty_driver; - -/* Initialize UART as a TTY driver (registers RX IRQ) */ -void uart_tty_init(tty_struct* tty); diff --git a/na/include/lume/tty_ioctl.h b/na/include/lume/tty_ioctl.h deleted file mode 100644 index 87ba59b..0000000 --- a/na/include/lume/tty_ioctl.h +++ /dev/null @@ -1,202 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tty_ioctl.h — Terminal I/O control constants and structures - * - * Linux-compatible termios, winsize, and TIOC* ioctl definitions. - * Values match the RISC-V/generic Linux ABI. - * - * Reference: include/uapi/asm-generic/termbits.h, - * include/uapi/asm-generic/ioctls.h - */ - -#include - -/* ================================================================== */ -/* termios c_cc indices */ -/* ================================================================== */ - -inline constexpr int VINTR = 0; -inline constexpr int VQUIT = 1; -inline constexpr int VERASE = 2; -inline constexpr int VKILL = 3; -inline constexpr int VEOF = 4; -inline constexpr int VTIME = 5; -inline constexpr int VMIN = 6; -inline constexpr int VSWTC = 7; -inline constexpr int VSTART = 8; -inline constexpr int VSTOP = 9; -inline constexpr int VSUSP = 10; -inline constexpr int VEOL = 11; -inline constexpr int VREPRINT = 12; -inline constexpr int VDISCARD = 13; -inline constexpr int VWERASE = 14; -inline constexpr int VLNEXT = 15; -inline constexpr int VEOL2 = 16; -inline constexpr int NCCS = 19; - -/* ================================================================== */ -/* c_iflag — input mode flags */ -/* ================================================================== */ - -inline constexpr uint32 IGNBRK = 0000001; -inline constexpr uint32 BRKINT = 0000002; -inline constexpr uint32 IGNPAR = 0000004; -inline constexpr uint32 PARMRK = 0000010; -inline constexpr uint32 INPCK = 0000020; -inline constexpr uint32 ISTRIP = 0000040; -inline constexpr uint32 INLCR = 0000100; -inline constexpr uint32 IGNCR = 0000200; -inline constexpr uint32 ICRNL = 0000400; -inline constexpr uint32 IUCLC = 0001000; -inline constexpr uint32 IXON = 0002000; -inline constexpr uint32 IXANY = 0004000; -inline constexpr uint32 IXOFF = 0010000; -inline constexpr uint32 IMAXBEL = 0020000; -inline constexpr uint32 IUTF8 = 0040000; - -/* ================================================================== */ -/* c_oflag — output mode flags */ -/* ================================================================== */ - -inline constexpr uint32 OPOST = 0000001; -inline constexpr uint32 OLCUC = 0000002; -inline constexpr uint32 ONLCR = 0000004; -inline constexpr uint32 OCRNL = 0000010; -inline constexpr uint32 ONOCR = 0000020; -inline constexpr uint32 ONLRET = 0000040; -inline constexpr uint32 OFILL = 0000100; -inline constexpr uint32 OFDEL = 0000200; - -/* ================================================================== */ -/* c_cflag — control mode flags */ -/* ================================================================== */ - -inline constexpr uint32 CSIZE = 0000060; -inline constexpr uint32 CS5 = 0000000; -inline constexpr uint32 CS6 = 0000020; -inline constexpr uint32 CS7 = 0000040; -inline constexpr uint32 CS8 = 0000060; -inline constexpr uint32 CSTOPB = 0000100; -inline constexpr uint32 CREAD = 0000200; -inline constexpr uint32 PARENB = 0000400; -inline constexpr uint32 PARODD = 0001000; -inline constexpr uint32 HUPCL = 0002000; -inline constexpr uint32 CLOCAL = 0004000; - -/* Baud rates (stored in c_cflag masked with CBAUD) */ -inline constexpr uint32 CBAUD = 0010017; -inline constexpr uint32 B0 = 0000000; -inline constexpr uint32 B9600 = 0000015; -inline constexpr uint32 B19200 = 0000016; -inline constexpr uint32 B38400 = 0000017; -inline constexpr uint32 B115200 = 0010002; - -/* ================================================================== */ -/* c_lflag — local mode flags */ -/* ================================================================== */ - -inline constexpr uint32 ISIG = 0000001; -inline constexpr uint32 ICANON = 0000002; -inline constexpr uint32 XCASE = 0000004; -inline constexpr uint32 LECHO = 0000010; /* Named LECHO to avoid macro conflict */ -inline constexpr uint32 ECHOE = 0000020; -inline constexpr uint32 ECHOK = 0000040; -inline constexpr uint32 ECHONL = 0000100; -inline constexpr uint32 NOFLSH = 0000200; -inline constexpr uint32 TOSTOP = 0000400; -inline constexpr uint32 ECHOCTL = 0001000; -inline constexpr uint32 ECHOPRT = 0002000; -inline constexpr uint32 ECHOKE = 0004000; -inline constexpr uint32 IEXTEN = 0100000; - -/* Linux uses ECHO = 0x0008, we use LECHO to avoid reserved-name issues. - * Provide the standard name as well for ioctl compatibility. */ -inline constexpr uint32 ECHO = LECHO; - -/* ================================================================== */ -/* termios structure */ -/* ================================================================== */ - -struct termios { - uint32 c_iflag; /* input mode flags */ - uint32 c_oflag; /* output mode flags */ - uint32 c_cflag; /* control mode flags */ - uint32 c_lflag; /* local mode flags */ - uint8 c_line; /* line discipline */ - uint8 c_cc[NCCS]; /* control characters */ -}; - -/* ================================================================== */ -/* winsize structure */ -/* ================================================================== */ - -struct winsize { - uint16 ws_row; - uint16 ws_col; - uint16 ws_xpixel; - uint16 ws_ypixel; -}; - -/* ================================================================== */ -/* ioctl command numbers (Linux asm-generic/ioctls.h) */ -/* ================================================================== */ - -inline constexpr uint32 TCGETS = 0x5401; -inline constexpr uint32 TCSETS = 0x5402; -inline constexpr uint32 TCSETSW = 0x5403; -inline constexpr uint32 TCSETSF = 0x5404; -inline constexpr uint32 TCGETA = 0x5405; -inline constexpr uint32 TCSETA = 0x5406; -inline constexpr uint32 TCSETAW = 0x5407; -inline constexpr uint32 TCSETAF = 0x5408; -inline constexpr uint32 TCSBRK = 0x5409; -inline constexpr uint32 TCXONC = 0x540A; -inline constexpr uint32 TCFLSH = 0x540B; -inline constexpr uint32 TIOCEXCL = 0x540C; -inline constexpr uint32 TIOCNXCL = 0x540D; -inline constexpr uint32 TIOCSCTTY = 0x540E; -inline constexpr uint32 TIOCGPGRP = 0x540F; -inline constexpr uint32 TIOCSPGRP = 0x5410; -inline constexpr uint32 TIOCOUTQ = 0x5411; -inline constexpr uint32 TIOCSTI = 0x5412; -inline constexpr uint32 TIOCGWINSZ = 0x5413; -inline constexpr uint32 TIOCSWINSZ = 0x5414; -inline constexpr uint32 TIOCMGET = 0x5415; -inline constexpr uint32 TIOCMSET = 0x5418; -inline constexpr uint32 FIONREAD = 0x541B; -inline constexpr uint32 TIOCNOTTY = 0x5422; -inline constexpr uint32 TIOCGPTLCK = 0x80045430; -inline constexpr uint32 TIOCSPTLCK = 0x40045431; - -/* Default termios for a new TTY */ -inline void tty_default_termios(termios* t) -{ - t->c_iflag = ICRNL | IXON | IUTF8; - t->c_oflag = OPOST | ONLCR; - t->c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; - t->c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; - t->c_line = 0; /* N_TTY */ - - for (int i = 0; i < NCCS; i++) - t->c_cc[i] = 0; - - t->c_cc[VINTR] = 003; /* Ctrl+C */ - t->c_cc[VQUIT] = 034; /* Ctrl+\ */ - t->c_cc[VERASE] = 0177; /* DEL */ - t->c_cc[VKILL] = 025; /* Ctrl+U */ - t->c_cc[VEOF] = 004; /* Ctrl+D */ - t->c_cc[VSTART] = 021; /* Ctrl+Q */ - t->c_cc[VSTOP] = 023; /* Ctrl+S */ - t->c_cc[VSUSP] = 032; /* Ctrl+Z */ - t->c_cc[VREPRINT] = 022; /* Ctrl+R */ - t->c_cc[VWERASE] = 027; /* Ctrl+W */ - t->c_cc[VLNEXT] = 026; /* Ctrl+V */ - t->c_cc[VEOL] = 0; - t->c_cc[VEOL2] = 0; - t->c_cc[VMIN] = 1; - t->c_cc[VTIME] = 0; -} diff --git a/na/include/lume/types.h b/na/include/lume/types.h deleted file mode 100644 index 2342359..0000000 --- a/na/include/lume/types.h +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_TYPES_H -#define _LUME_TYPES_H - -/* - * LumeOS Base Type Definitions - * - * Three naming layers coexist so that source files imported from - * upstream projects (Linux kernel, libfdt) compile unchanged: - * - * LumeOS native uint8 / int8 / ... / uint64 / int64 - * Linux kernel short names u8 / s8 / ... / u64 / s64 - * POSIX / C99 (with _t) uint8_t / ... / uint64_t / intptr_t - * - * All three resolve to the same underlying compiler builtin, so they - * are binary-compatible. - * - * Declared with C `typedef` (not C++ `using`) so this header may be - * included from .c sources — required for libfdt shims and the - * arch/riscv/kernel/pi/ subsystem, which are pure C imports from - * Linux. C++ accepts `typedef` syntax identically. - */ - -/* ===== LumeOS native names ===== */ -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; -typedef unsigned long long uint64; - -typedef signed char int8; -typedef signed short int16; -typedef signed int int32; -typedef signed long long int64; - -typedef __SIZE_TYPE__ size_t; -typedef __PTRDIFF_TYPE__ ptrdiff_t; -typedef int64 ssize_t; -typedef uint64 uintptr; - -/* ===== Linux kernel short names ===== */ -typedef uint8 u8; typedef int8 s8; -typedef uint16 u16; typedef int16 s16; -typedef uint32 u32; typedef int32 s32; -typedef uint64 u64; typedef int64 s64; - -/* ===== POSIX / C99 aliases ===== - * Guarded so a later #include from libc-shim during musl - * userspace builds is a no-op rather than a redefinition error. */ -#ifndef _STDINT_H -# define _STDINT_H -typedef uint8 uint8_t; -typedef uint16 uint16_t; -typedef uint32 uint32_t; -typedef uint64 uint64_t; -typedef int8 int8_t; -typedef int16 int16_t; -typedef int32 int32_t; -typedef int64 int64_t; -typedef uintptr uintptr_t; -typedef int64 intptr_t; -#endif - -/* ===== Linux domain typedefs needed by RISC-V boot path ===== */ -typedef uint64 phys_addr_t; - -/* ===== libfdt on-wire types (big-endian). Real byte-swap helpers - * live in . ===== */ -typedef uint16 fdt16_t; -typedef uint32 fdt32_t; -typedef uint64 fdt64_t; - -#ifndef offsetof -# define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER) -#endif - -#endif /* _LUME_TYPES_H */ diff --git a/na/include/lume/uaccess.h b/na/include/lume/uaccess.h deleted file mode 100644 index 5a9ed71..0000000 --- a/na/include/lume/uaccess.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * User-space memory access — safe copy routines. - * - * Phase 2: Software page-table validation. - * Phase 3+: Exception Table (zero-cost happy path). - * - * Reference: docs/specs/syscall.md §8 - */ - -#include - -/* Copy len bytes from user address to kernel buffer. - * Returns 0 on success, -EFAULT if user address is invalid. */ -int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len); - -/* Copy len bytes from kernel buffer to user address. - * Returns 0 on success, -EFAULT if user address is invalid. */ -int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len); - -/* Copy NUL-terminated string from user space. - * Returns string length (excluding NUL) or -EFAULT. */ -int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len); diff --git a/na/include/lume/uuid.h b/na/include/lume/uuid.h deleted file mode 100644 index fcbd4a3..0000000 --- a/na/include/lume/uuid.h +++ /dev/null @@ -1,207 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * UUID/GUID definition - * - * Copyright (C) 2010, 2016 Intel Corp. - * Huang Ying - */ - -#pragma once - -#include -#include - -constexpr uint32 UUID_SIZE = 16; -constexpr uint32 UUID_STRING_LEN = 36; - -static inline int uuid_hex_value(char c) -{ - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } - if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } - return -1; -} - -static inline bool uuid_parse_raw(const char* uuid, uint8 out[UUID_SIZE]) -{ - if (!uuid) { - return false; - } - if (strlen(uuid) != UUID_STRING_LEN) { - return false; - } - if (uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || uuid[23] != '-') { - return false; - } - - int out_index = 0; - for (int i = 0; i < static_cast(UUID_STRING_LEN); ) { - if (uuid[i] == '-') { - ++i; - continue; - } - - int hi = uuid_hex_value(uuid[i]); - int lo = uuid_hex_value(uuid[i + 1]); - if (hi < 0 || lo < 0) { - return false; - } - - out[out_index++] = static_cast((hi << 4) | lo); - i += 2; - } - - return out_index == static_cast(UUID_SIZE); -} - -struct uuid_base { - uint8 b[UUID_SIZE]; - - bool is_null() const - { - for (uint32 i = 0; i < UUID_SIZE; ++i) { - if (b[i] != 0) { - return false; - } - } - return true; - } - - bool operator==(const uuid_base& other) const - { - return memcmp(b, other.b, UUID_SIZE) == 0; - } - - bool operator!=(const uuid_base& other) const - { - return !(*this == other); - } -}; - -struct uuid_t : public uuid_base { - explicit uuid_t(const uint8 in_b[UUID_SIZE]) - { - memcpy(b, in_b, UUID_SIZE); - } - - explicit constexpr uuid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1, - uint8 d2, uint8 d3, uint8 d4, uint8 d5, - uint8 d6, uint8 d7) - : uuid_base{{ - static_cast((a >> 24) & 0xff), - static_cast((a >> 16) & 0xff), - static_cast((a >> 8) & 0xff), - static_cast(a & 0xff), - static_cast((b >> 8) & 0xff), - static_cast(b & 0xff), - static_cast((c >> 8) & 0xff), - static_cast(c & 0xff), - d0, d1, d2, d3, d4, d5, d6, d7 - }} - { - } - - constexpr uuid_t() : uuid_base{{0}} {} - - static bool parse(const char* uuid, uuid_t& out) - { - uint8 tmp[UUID_SIZE]; - if (!uuid_parse_raw(uuid, tmp)) { - return false; - } - - memcpy(out.b, tmp, UUID_SIZE); - return true; - } - - static bool is_valid(const char* uuid) - { - uint8 tmp[UUID_SIZE]; - return uuid_parse_raw(uuid, tmp); - } - - static constexpr uuid_t null() - { - return uuid_t(); - } - - void import_bytes(const uint8* src) - { - memcpy(b, src, UUID_SIZE); - } - - void export_bytes(uint8* dst) const - { - memcpy(dst, b, UUID_SIZE); - } -}; - -struct guid_t : public uuid_base { - explicit guid_t(const uint8 in_b[UUID_SIZE]) - { - memcpy(b, in_b, UUID_SIZE); - } - - explicit constexpr guid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1, - uint8 d2, uint8 d3, uint8 d4, uint8 d5, - uint8 d6, uint8 d7) - : uuid_base{{ - static_cast(a & 0xff), - static_cast((a >> 8) & 0xff), - static_cast((a >> 16) & 0xff), - static_cast((a >> 24) & 0xff), - static_cast(b & 0xff), - static_cast((b >> 8) & 0xff), - static_cast(c & 0xff), - static_cast((c >> 8) & 0xff), - d0, d1, d2, d3, d4, d5, d6, d7 - }} - { - } - - constexpr guid_t() : uuid_base{{0}} {} - static bool parse(const char* uuid, guid_t& out) - { - uint8 tmp[UUID_SIZE]; - if (!uuid_parse_raw(uuid, tmp)) { - return false; - } - - static constexpr uint8 guid_index[UUID_SIZE] = { - 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15, - }; - - for (int i = 0; i < static_cast(UUID_SIZE); ++i) { - out.b[guid_index[i]] = tmp[i]; - } - return true; - } - - static bool is_valid(const char* uuid) - { - uint8 tmp[UUID_SIZE]; - return uuid_parse_raw(uuid, tmp); - } - - static constexpr guid_t null() - { - return guid_t(); - } - - void import_bytes(const uint8* src) - { - memcpy(b, src, UUID_SIZE); - } - - void export_bytes(uint8* dst) const - { - memcpy(dst, b, UUID_SIZE); - } -}; - diff --git a/na/include/lume/vmm.h b/na/include/lume/vmm.h deleted file mode 100644 index 9cbebf1..0000000 --- a/na/include/lume/vmm.h +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * vmm.h — Virtual Memory Manager - * - * Managing kernel and user virtual address spaces. - * - * Reference: docs/specs/vmm.md - */ - -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* MI-layer permission flags (architecture-independent) */ -/* */ -/* These are used by VMA and vmm_map_user / vmm_handle_page_fault. */ -/* The pmap MD layer translates them to hardware PTE bits. */ -/* ------------------------------------------------------------------ */ -inline constexpr uint64 VM_READ = (1ULL << 0); -inline constexpr uint64 VM_WRITE = (1ULL << 1); -inline constexpr uint64 VM_EXEC = (1ULL << 2); -inline constexpr uint64 VM_USER = (1ULL << 3); - -enum class VmFaultCause : uint8 { - Read = 0, - Write = 1, - Exec = 2, -}; - -/* Forward declarations */ -struct VmSpace; -struct VmAreaStruct; - -/* - * Virtual Memory Area (VMA) - * Represents a contiguous range [start, end) of uniform permissions. - */ -struct VmAreaStruct { - uint64 start; // Start VA (page aligned) - uint64 end; // End VA (exclusive, page aligned) - uint64 perm; // VM_READ | VM_WRITE | VM_EXEC | VM_USER - struct rb_node rb; // Linkage in VmSpace::vma_tree - VmSpace* mm; // Back pointer to the parent address space -}; - -/* - * Address Space (VmSpace) - * Represents a user process's complete virtual memory mapping. - * - * Lock contract: - * Holds: lock (internal spinlock) - * May acquire: PMM global lock (via pmm_alloc_page inside page fault) - * Callers must NOT hold: PMM lock (lock ordering: VmSpace -> PMM) - */ -struct VmSpace { - uint64 root_pa; // SV39 root page table physical address - struct rb_root vma_tree; // Red-black tree of VmAreaStructs - Spinlock lock; // Protects vma_tree mutations - lume::atomic refcount; // Reference count (for CLONE_VM sharing) - lume::atomic cpu_mask; // Bitmask of CPUs with this mm in satp - - uint64 brk_start; // Start of heap (page-aligned) - uint64 brk_end; // Current heap end (page-aligned) - uint64 mmap_base; // Base for mmap (Linux: TASK_UNMAPPED_BASE = TASK_SIZE/3) - - // Create a brand new VmSpace (allocates root pagetable, refcount=1) - static VmSpace* create(); - - // Increment reference count (for CLONE_VM) - void incref(); - - // Decrement reference count; destroys VmSpace when it reaches 0. - // Call this instead of destroy() directly. - void put(); - - // Destroy VmSpace, unmapping all VMAs and freeing pagetables. - // Internal — called by put() when refcount drops to 0. - void destroy(); -}; - -/* Initialize kernel page table (Phase 1) */ -void vmm_init(); - -/* Switch AP to the kernel page table built by BSP (Phase 1) */ -void vmm_init_ap(); - -/* Map MMIO device region to the active kernel page table */ -void vmm_map_kernel_mmio(uint64 pa, uint64 size); - -/* Query page_map allocation (set by vmm_init, read by pmm_init). - * Returns the PA, byte size, and page count of the vmemmap region. */ -uint64 vmm_get_page_map_pa(); -uint64 vmm_get_page_map_size(); -uint64 vmm_get_num_pages(); - -/* User space memory management (VMA & Demand Paging) */ -int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); -int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len); -int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); - -/* VMA helpers for mmap/mprotect implementations */ -uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align); -bool vma_range_covered(VmSpace* space, uint64 start, uint64 end); - -/* Handle user page fault (lookup VMA -> allocate page -> map) */ -int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause); - -/* Clone an address space for fork() */ -VmSpace* vmm_clone(VmSpace* parent_space); diff --git a/na/include/lume/wait.h b/na/include/lume/wait.h deleted file mode 100644 index 865c1f2..0000000 --- a/na/include/lume/wait.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * POSIX waitpid / wait4 Constants - * Reference: docs/specs/syscall.md §9 - */ - -/* waitpid options */ -inline constexpr int WNOHANG = 1; -inline constexpr int WUNTRACED = 2; -inline constexpr int WCONTINUED = 8; -inline constexpr int WSTOPPED = WUNTRACED; /* POSIX alias */ -inline constexpr int WEXITED = 4; /* POSIX */ -inline constexpr int WNOWAIT = 0x01000000; /* POSIX: peek, don't reap */ - -/* Exit status encoding (Linux-compatible) */ -static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; } -static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; } -static inline bool WIFSIGNALED(int s) { return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; } -static inline int WTERMSIG(int s) { return s & 0x7F; } -static inline bool WIFSTOPPED(int s) { return (s & 0xFF) == 0x7F; } -static inline int WSTOPSIG(int s) { return (s >> 8) & 0xFF; } -static inline bool WIFCONTINUED(int s){ return s == 0xFFFF; } - -/* Encode exit code into wstatus (normal exit) */ -static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; } -static inline int W_STOPCODE(int sig) { return ((sig & 0xFF) << 8) | 0x7F; } -static inline int W_CONTINUED() { return 0xFFFF; } diff --git a/na/include/lume/waitqueue.h b/na/include/lume/waitqueue.h deleted file mode 100644 index 3720cb7..0000000 --- a/na/include/lume/waitqueue.h +++ /dev/null @@ -1,79 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * WaitQueue — blocking synchronization primitive - * - * Supports two usage patterns: - * - * 1. Simple (legacy): wq.sleep() — combines prepare/schedule/finish. - * Suitable for simple cases where no external condition is checked. - * - * 2. Linux-style two-phase (recommended for correctness): - * - * wq.prepare_to_wait(curr, TaskState::Sleeping); - * if (!condition) { - * schedule(); - * } - * wq.finish_wait(curr); - * - * This pattern prevents the "lost wakeup" race: the task is already - * on the wait list (and marked Sleeping) BEFORE checking the condition. - * If a wake_one/wake_all fires between the check and schedule(), the - * task's state will already be set back to Runnable, and schedule() - * will see state == Runnable and simply re-run the task. - * - * Reference: docs/specs/scheduler.md, docs/specs/task.md §5 - */ - -#include -#include -#include - -struct TaskControlBlock; -enum class TaskState : uint8; - -class WaitQueue { -public: - void init(const char* name = "wq") { - head_.init(); - lock_.init(name); - } - - /* - * Phase 1: Add the task to the wait queue and set its state. - * The task is NOT yet descheduled — the caller must check the - * condition and call schedule() if the condition is still false. - * - * Must be called with interrupts disabled or preemption disabled. - */ - void prepare_to_wait(TaskControlBlock* task, TaskState new_state); - - /* - * Phase 3: Remove the task from the wait queue (if still on it) - * and ensure its state is Runnable. Called after schedule() returns - * (i.e. after the task has been woken up, or if the condition was - * true and schedule() was skipped). - */ - void finish_wait(TaskControlBlock* task); - - /* Legacy convenience: combines prepare_to_wait + schedule + finish_wait. - * If external_lock is provided, it is released AFTER the task is - * added to the wait queue but BEFORE schedule() is called. */ - void sleep(Spinlock* external_lock = nullptr); - - /* Wake the first (oldest) sleeping task. Returns true if a task was woken. */ - bool wake_one(); - - /* Wake all sleeping tasks. Returns the number of tasks woken. */ - int wake_all(); - - /* Check if any tasks are waiting. */ - bool is_empty() const { return head_.is_empty(); } - -private: - list_node head_; - Spinlock lock_; -}; diff --git a/na/kernel/exec.cc b/na/kernel/exec.cc deleted file mode 100644 index 616c0da..0000000 --- a/na/kernel/exec.cc +++ /dev/null @@ -1,659 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * exec.cc — ELF64 binary loader and execve implementation - * - * Loads a static ELF64 executable into a fresh address space, - * constructs the user stack (argc/argv/envp/auxv), and redirects - * the current task's trapframe to begin executing at the ELF entry. - * - * Reference: Linux fs/binfmt_elf.c, fs/exec.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Assembly entry points (trap_vector.S) */ -extern "C" void trap_vector(); -extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); -extern "C" void userret(arch::TrapFrame* tf); - -/* ================================================================== */ -/* Constants */ -/* ================================================================== */ - -/* User stack layout comes from mm_layout.h (MI layer). - * kUserStackTop, kUserStackBase, kUserStackSize are defined there, - * derived from kUserVaLimit so they automatically adapt to the - * page table mode (SV39/SV48). */ - -/* ================================================================== */ -/* Helper: kernel_read — read from a file into a kernel buffer */ -/* ================================================================== */ - -static int64 kernel_read(file* fp, void* buf, uint64 count, int64 offset) -{ - int64 pos = offset; - if (!fp->f_op || !fp->f_op->read) return -EINVAL; - return fp->f_op->read(fp, static_cast(buf), count, &pos); -} - -/* ================================================================== */ -/* Helper: copy data to a user page via kernel direct map */ -/* ================================================================== */ - -static void memcpy_kernel(void* dst, const void* src, uint64 n) -{ - auto* d = static_cast(dst); - auto* s = static_cast(src); - for (uint64 i = 0; i < n; i++) - d[i] = s[i]; -} - -static void memset_kernel(void* dst, uint8 val, uint64 n) -{ - auto* d = static_cast(dst); - for (uint64 i = 0; i < n; i++) - d[i] = val; -} - -static uint64 strlen_kernel(const char* s) -{ - uint64 n = 0; - while (s[n]) n++; - return n; -} - -/* ================================================================== */ -/* Helper: allocate and map a page at a specific user VA */ -/* ================================================================== */ - -static int map_page_at(VmSpace* mm, uint64 va, uint64 pte_perm) -{ - Page* pg = pmm_alloc_page(); - if (!pg) return -ENOMEM; - - uint64 pa = page_to_pa(pg).raw; - - /* Zero the page */ - void* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - memset_kernel(kva, 0, kPageSize); - - int ret = pmap::map(mm->root_pa, va, pa, pte_perm); - if (ret != 0) { - pmm_free_page(pg); - return -ENOMEM; - } - page_inc_mapcount(pg); - return 0; -} - -/* ================================================================== */ -/* load_elf_binary — parse ELF and map segments */ -/* ================================================================== */ - -struct elf_load_info { - uint64 entry; /* ELF entry point (after base offset) */ - uint64 phdr_addr; /* User VA where phdr was loaded */ - uint16 phnum; /* Number of program headers */ - uint16 phentsize; /* Size of each phdr */ - uint64 brk_start; /* End of highest PT_LOAD segment (for brk) */ - uint64 load_bias; /* PIE load bias (0 for ET_EXEC) */ -}; - -static int load_elf_binary(file* fp, VmSpace* mm, elf_load_info* info) -{ - /* Read ELF header */ - Elf64_Ehdr ehdr{}; - int64 n = kernel_read(fp, &ehdr, sizeof(ehdr), 0); - if (n < static_cast(sizeof(ehdr))) { - printk(KERN_ERR "exec: failed to read ELF header (got %lld)\n", n); - return -ENOEXEC; - } - - /* Validate magic */ - if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || - ehdr.e_ident[EI_MAG1] != ELFMAG1 || - ehdr.e_ident[EI_MAG2] != ELFMAG2 || - ehdr.e_ident[EI_MAG3] != ELFMAG3) { - printk(KERN_ERR "exec: bad ELF magic\n"); - return -ENOEXEC; - } - - /* Validate class (64-bit) and endianness (little-endian) */ - if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) return -ENOEXEC; - if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) return -ENOEXEC; - - /* Validate type and machine */ - if (ehdr.e_type != ET_EXEC && ehdr.e_type != ET_DYN) return -ENOEXEC; - if (ehdr.e_machine != EM_RISCV) return -ENOEXEC; - - if (ehdr.e_phnum == 0 || ehdr.e_phentsize < sizeof(Elf64_Phdr)) - return -ENOEXEC; - - /* Read program headers */ - uint64 phdr_total = static_cast(ehdr.e_phnum) * ehdr.e_phentsize; - if (phdr_total > kPageSize) return -ENOEXEC; /* Sanity limit */ - - /* Allocate kernel buffer for program headers */ - uint8 phdr_buf[512]; /* Max ~8 phdrs * 56 bytes each */ - if (phdr_total > sizeof(phdr_buf)) return -ENOEXEC; - - n = kernel_read(fp, phdr_buf, phdr_total, static_cast(ehdr.e_phoff)); - if (n < static_cast(phdr_total)) return -ENOEXEC; - - info->phnum = ehdr.e_phnum; - info->phentsize = ehdr.e_phentsize; - info->phdr_addr = 0; - info->brk_start = 0; - info->load_bias = 0; - - /* - * PIE (ET_DYN): segments have p_vaddr starting near 0. - * We must choose a load base and add it to all addresses. - * Linux uses ELF_ET_DYN_BASE (~2/3 of TASK_SIZE); we use 0x400000. - */ - constexpr uint64 kPieLoadBase = 0x400000; - uint64 load_bias = (ehdr.e_type == ET_DYN) ? kPieLoadBase : 0; - info->load_bias = load_bias; - info->entry = ehdr.e_entry + load_bias; - - /* Process each PT_LOAD segment */ - for (uint16 i = 0; i < ehdr.e_phnum; i++) { - auto* phdr = reinterpret_cast( - phdr_buf + i * ehdr.e_phentsize); - - if (phdr->p_type == PT_PHDR) { - /* Remember where program headers are mapped */ - info->phdr_addr = phdr->p_vaddr + load_bias; - continue; - } - - if (phdr->p_type != PT_LOAD) continue; - if (phdr->p_memsz == 0) continue; - - /* Compute permissions */ - uint64 perm = pmap::PTE_V | pmap::PTE_U; - if (phdr->p_flags & PF_R) perm |= pmap::PTE_R; - if (phdr->p_flags & PF_W) perm |= pmap::PTE_W; - if (phdr->p_flags & PF_X) perm |= pmap::PTE_X; - - /* Page-align the segment boundaries (apply load bias for PIE) */ - uint64 seg_start = page_align_down(phdr->p_vaddr + load_bias); - uint64 seg_end = page_align_up(phdr->p_vaddr + phdr->p_memsz + load_bias); - - /* Create VMA for this segment */ - uint64 vm_perm = VM_USER; - if (phdr->p_flags & PF_R) vm_perm |= VM_READ; - if (phdr->p_flags & PF_W) vm_perm |= VM_WRITE; - if (phdr->p_flags & PF_X) vm_perm |= VM_EXEC; - - int err = vmm_map_user(mm, seg_start, seg_end - seg_start, vm_perm); - if (err) { - printk(KERN_ERR "exec: vmm_map_user failed for segment %d: %d\n", - i, err); - return err; - } - - /* Map and fill pages */ - for (uint64 va = seg_start; va < seg_end; va += kPageSize) { - err = map_page_at(mm, va, perm); - if (err) return err; - - /* Determine how much file data goes into this page */ - uint64 file_seg_start = phdr->p_vaddr + load_bias; - uint64 file_seg_end = phdr->p_vaddr + phdr->p_filesz + load_bias; - - uint64 page_start = va; - uint64 page_end = va + kPageSize; - - /* Overlap between [page_start, page_end) and [file_seg_start, file_seg_end) */ - if (page_start < file_seg_end && page_end > file_seg_start) { - uint64 copy_start = (page_start > file_seg_start) ? page_start : file_seg_start; - uint64 copy_end = (page_end < file_seg_end) ? page_end : file_seg_end; - uint64 copy_len = copy_end - copy_start; - - /* File offset for this data */ - uint64 file_off = phdr->p_offset + (copy_start - phdr->p_vaddr - load_bias); - - /* Get kernel VA of the mapped page */ - uint64 pa; - if (!pmap::lookup(mm->root_pa, va, &pa)) return -EFAULT; - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - - /* Read file data into the page */ - uint64 page_offset = copy_start - va; - n = kernel_read(fp, kva + page_offset, copy_len, - static_cast(file_off)); - if (n < static_cast(copy_len)) { - printk(KERN_ERR "exec: short read at offset %llu\n", file_off); - return -EIO; - } - } - /* BSS region (memsz > filesz) is already zeroed by map_page_at */ - } - - /* Track highest loaded address for brk */ - if (seg_end > info->brk_start) - info->brk_start = seg_end; - - /* Detect which PT_LOAD contains the program header table. - * Linux: fs/binfmt_elf.c line ~1209 */ - if (info->phdr_addr == 0 && - phdr->p_offset <= ehdr.e_phoff && - ehdr.e_phoff < phdr->p_offset + phdr->p_filesz) { - info->phdr_addr = ehdr.e_phoff - phdr->p_offset - + phdr->p_vaddr + load_bias; - } - } - - /* phdr_addr should have been set by the PT_LOAD loop above - * or by a PT_PHDR entry. If still zero, something is wrong. */ - if (info->phdr_addr == 0) { - printk(KERN_ERR "exec: unable to determine AT_PHDR address\n"); - return -ENOEXEC; - } - - return 0; -} - -/* ================================================================== */ -/* setup_user_stack — build argc/argv/envp/auxv on the user stack */ -/* ================================================================== */ - -static int setup_user_stack(VmSpace* mm, const char* const* argv, - const char* const* envp, - const elf_load_info* info, - uint64* out_sp) -{ - /* Create stack VMA */ - uint64 vm_perm = VM_USER | VM_READ | VM_WRITE; - int err = vmm_map_user(mm, kUserStackBase, kUserStackSize, vm_perm); - if (err) return err; - - /* We only need the top few pages for initial stack content. - * Map 2 pages at the top for argc/argv/envp/auxv. */ - uint64 perm = pmap::PTE_V | pmap::PTE_U | pmap::PTE_R | pmap::PTE_W; - for (uint64 va = kUserStackTop - 2 * kPageSize; va < kUserStackTop; va += kPageSize) { - err = map_page_at(mm, va, perm); - if (err) return err; - } - - /* Count arguments and environment */ - int argc = 0; - int envc = 0; - if (argv) { while (argv[argc]) argc++; } - if (envp) { while (envp[envc]) envc++; } - - /* - * Build the stack from top down using kernel VA access. - * Layout (top to bottom): - * 1. String data (argv strings, envp strings) - * 2. Padding (16-byte align) - * 3. auxv[] - * 4. NULL (end of envp) - * 5. envp[] pointers - * 6. NULL (end of argv) - * 7. argv[] pointers - * 8. argc - */ - - /* First pass: calculate total string space */ - uint64 str_total = 0; - for (int i = 0; i < argc; i++) - str_total += strlen_kernel(argv[i]) + 1; - for (int i = 0; i < envc; i++) - str_total += strlen_kernel(envp[i]) + 1; - - /* String area starts at top of stack */ - uint64 str_pos = kUserStackTop - str_total; - str_pos &= ~15ULL; /* 16-byte align */ - - /* Copy strings and record user addresses */ - uint64 argv_addrs[32]; /* Max 32 args */ - uint64 envp_addrs[32]; - if (argc > 32) argc = 32; - if (envc > 32) envc = 32; - - uint64 cur = str_pos; - for (int i = 0; i < argc; i++) { - uint64 len = strlen_kernel(argv[i]) + 1; - argv_addrs[i] = cur; - - /* Copy string bytes to user stack via kernel VA */ - uint64 pa; - if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa)) - return -EFAULT; - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - uint64 off = cur - page_align_down(cur); - memcpy_kernel(kva + off, argv[i], len); - cur += len; - } - for (int i = 0; i < envc; i++) { - uint64 len = strlen_kernel(envp[i]) + 1; - envp_addrs[i] = cur; - - uint64 pa; - if (!pmap::lookup(mm->root_pa, page_align_down(cur), &pa)) - return -EFAULT; - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - uint64 off = cur - page_align_down(cur); - memcpy_kernel(kva + off, envp[i], len); - cur += len; - } - - /* Build auxv, envp[], argv[], argc below the strings */ - /* Count entries: argc + argv_ptrs + NULL + envp_ptrs + NULL + auxv (6 pairs + terminator) */ - int auxv_count = 8; /* AT_PHDR, AT_PHENT, AT_PHNUM, AT_PAGESZ, AT_BASE, AT_ENTRY, AT_RANDOM, AT_NULL */ - uint64 table_size = sizeof(uint64) * (1 + argc + 1 + envc + 1 + auxv_count * 2); - uint64 table_start = str_pos - table_size; - table_start &= ~15ULL; - - /* Write the table entries. - * We need to handle page boundaries, but for simplicity we assume - * the table fits in the already-mapped pages. */ - uint64 sp = table_start; - - /* Helper: write a uint64 to user stack */ - auto write_u64 = [&](uint64 addr, uint64 val) -> int { - uint64 pa; - if (!pmap::lookup(mm->root_pa, page_align_down(addr), &pa)) - return -EFAULT; - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - uint64 off = addr - page_align_down(addr); - *reinterpret_cast(kva + off) = val; - return 0; - }; - - /* argc */ - err = write_u64(sp, static_cast(argc)); - if (err) return err; - sp += 8; - - /* argv[] */ - for (int i = 0; i < argc; i++) { - err = write_u64(sp, argv_addrs[i]); - if (err) return err; - sp += 8; - } - err = write_u64(sp, 0); /* NULL terminator */ - if (err) return err; - sp += 8; - - /* envp[] */ - for (int i = 0; i < envc; i++) { - err = write_u64(sp, envp_addrs[i]); - if (err) return err; - sp += 8; - } - err = write_u64(sp, 0); /* NULL terminator */ - if (err) return err; - sp += 8; - - /* auxv[] */ - auto write_auxv = [&](uint64 type, uint64 val) -> int { - int r = write_u64(sp, type); - if (r) return r; - sp += 8; - r = write_u64(sp, val); - if (r) return r; - sp += 8; - return 0; - }; - - write_auxv(AT_PHDR, info->phdr_addr); - write_auxv(AT_PHENT, info->phentsize); - write_auxv(AT_PHNUM, info->phnum); - write_auxv(AT_PAGESZ, kPageSize); - write_auxv(AT_BASE, info->load_bias); - write_auxv(AT_ENTRY, info->entry); - write_auxv(AT_RANDOM, str_pos - 16); /* Point to 16 bytes of zeros for now */ - write_auxv(AT_NULL, 0); - - *out_sp = table_start; - return 0; -} - -/* ================================================================== */ -/* do_execve — main entry point */ -/* ================================================================== */ - -int do_execve(const char* pathname, const char* const* argv, - const char* const* envp) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - /* 1. Look up the file */ - path p{}; - int err = filename_lookup(AT_FDCWD, pathname, 0, &p); - if (err) { - printk(KERN_ERR "exec: file not found: %s (err=%d)\n", pathname, err); - return err; - } - - inode* ip = p.de->d_inode; - if (!ip || !S_ISREG(ip->i_mode)) { - printk(KERN_ERR "exec: not a regular file: %s\n", pathname); - return -EACCES; - } - - /* Open a kernel file handle */ - file* fp = alloc_empty_file(O_RDONLY); - if (!fp) { - return -ENOMEM; - } - fp->f_inode = ip; - fp->f_path = p; - fp->f_op = ip->i_fop; - fp->f_pos = 0; - - if (fp->f_op && fp->f_op->open) { - err = fp->f_op->open(ip, fp); - if (err) { - fput(fp); - return err; - } - } - - /* 2. Create new address space */ - VmSpace* new_mm = VmSpace::create(); - if (!new_mm) { - fput(fp); - return -ENOMEM; - } - - /* 3. Load ELF segments */ - elf_load_info info{}; - err = load_elf_binary(fp, new_mm, &info); - if (err) { - printk(KERN_ERR "exec: load_elf_binary failed: %d\n", err); - new_mm->put(); - fput(fp); - dput(p.de); - return err; - } - - /* 4. Set up user stack */ - uint64 user_sp = 0; - err = setup_user_stack(new_mm, argv, envp, &info, &user_sp); - if (err) { - printk(KERN_ERR "exec: setup_user_stack failed: %d\n", err); - new_mm->put(); - fput(fp); - return err; - } - - /* Set brk */ - new_mm->brk_start = page_align_up(info.brk_start); - new_mm->brk_end = new_mm->brk_start; - - /* Set mmap base — Linux: arch_pick_mmap_layout() sets mm->mmap_base - * to TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3). - * This ensures mmap(NULL) returns addresses well above the ELF - * text/data segments. */ - new_mm->mmap_base = page_align_up(kUserVaLimit / 3); - - fput(fp); - - /* =============================================================== */ - /* Point of no return — replace the current process */ - /* =============================================================== */ - - /* 5a. Replace address space */ - VmSpace* old_mm = curr->mm; - curr->mm = new_mm; - pmap::activate(new_mm->root_pa); - if (old_mm) old_mm->put(); - - /* 5a'. Transition from kernel thread to user process. - * Linux: begin_new_exec() in fs/exec.c: - * current->flags &= ~PF_KTHREAD; (line 1173) - * tsk->exit_signal = SIGCHLD; (line 1021) - * Without this, a kernel_execve'd task stays marked as a kthread - * and would be auto-reaped (exit_signal=-1) instead of becoming - * a proper zombie for parent wait(). */ - curr->flags &= ~TF_KTHREAD; - curr->exit_signal = SIGCHLD; - - /* 5b. Reset signal handlers to SIG_DFL (POSIX requirement). - * - * Linux fs/exec.c:flush_signal_handlers() runs under sighand->siglock. - * Even though after fork our sighand is no longer shared with the - * parent (we copy in task_clone), Linux still locks here — and so - * must we, because the same sighand may be shared with CLONE_SIGHAND - * sibling threads of the execing task. Holding siglock here also - * keeps the per-task blocked mask reset atomic w.r.t. concurrent - * sigprocmask / signal delivery on this very task. */ - if (curr->sighand) { - LockGuard guard(curr->sighand->siglock); - for (int i = 0; i < _NSIG; i++) { - if (curr->sighand->action[i].sa_handler != SIG_IGN) - curr->sighand->action[i].sa_handler = SIG_DFL; - curr->sighand->action[i].sa_flags = 0; - curr->sighand->action[i].sa_mask.clear(); - } - curr->blocked.clear(); - } else { - curr->blocked.clear(); - } - - /* 5c. Close O_CLOEXEC file descriptors */ - /* TODO: implement close-on-exec */ - - /* 6. Set up trapframe to enter user space */ - arch::TrapFrame* tf = curr->trapframe; - if (!tf) { - printk(KERN_ERR "exec: no trapframe!\n"); - task_kill_current(SIGKILL); - return -EINVAL; /* not reached */ - } - - /* Initialize trapframe for user-mode return. - * This is the MI equivalent of Linux's start_thread() + kernel - * metadata setup. All arch-specific details (sstatus, FP state, - * kernel_satp, kernel_sp, trap handler address, etc.) are handled - * by the MD layer. MI exec never touches arch registers. */ - arch::trapframe_init_user(tf, info.entry, user_sp); - - pr_debug_s(KLOG_SUBSYS_EXEC, - "exec: pid %d entry=%llx sp=%llx phdr=%llx\n", - curr->pid, info.entry, user_sp, info.phdr_addr); - - /* Verify loaded data at entry point — debug-level, ring buffer only. */ - { - uint64 pa; - if (pmap::lookup(curr->mm->root_pa, page_align_down(info.entry), &pa)) { - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - uint64 off = info.entry - page_align_down(info.entry); - pr_debug_s(KLOG_SUBSYS_EXEC, - "exec: entry bytes @%llx: %02x %02x %02x %02x %02x %02x %02x %02x\n", - info.entry, - kva[off+0], kva[off+1], kva[off+2], kva[off+3], - kva[off+4], kva[off+5], kva[off+6], kva[off+7]); - } - for (uint64 va = 0x10000; va < 0x14000; va += kPageSize) { - if (pmap::lookup(curr->mm->root_pa, va, &pa)) { - uint8* kva2 = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - pr_debug_s(KLOG_SUBSYS_EXEC, - "exec: page %llx: %02x%02x%02x%02x %02x%02x%02x%02x\n", - va, kva2[0], kva2[1], kva2[2], kva2[3], - kva2[4], kva2[5], kva2[6], kva2[7]); - } else { - pr_debug_s(KLOG_SUBSYS_EXEC, - "exec: page %llx: NOT MAPPED\n", va); - } - } - } - - /* Flush instruction cache after loading executable code into memory. - * Without this, the I-cache may contain stale data for the pages - * we just loaded, causing the CPU to execute garbage. */ - arch::mmu::flush_icache(); - - return 0; /* Will return to user mode via trap return */ -} - -/* ================================================================== */ -/* sys_execve_wrapper — syscall entry point */ -/* ================================================================== */ - -int64 sys_execve_wrapper(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - const char* pathname = reinterpret_cast(a0); - const char* const* argv = reinterpret_cast(a1); - const char* const* envp = reinterpret_cast(a2); - - return do_execve(pathname, argv, envp); -} - -/* ================================================================== */ -/* kernel_execve — call execve from kernel context (e.g. init) */ -/* ================================================================== */ - -int kernel_execve(const char* pathname, const char* const* argv, - const char* const* envp) -{ - TaskControlBlock* curr = current_task(); - - int ret = do_execve(pathname, argv, envp); - if (ret != 0) - return ret; - - /* - * do_execve succeeded: the trapframe is set up with entry point, - * user stack, and sstatus (SPP=0 for U-mode). We must NOT return - * normally — that would go back through the kthread return path - * and jump to address 0. Instead, jump directly to user mode via - * the same userret() path that fork_child_entry uses. - * - * Switch stvec from kernelvec to trap_vector so that traps from - * U-mode are handled by the user trap entry path (which loads - * kernel context from the trapframe via sscratch). - */ - arch::trap::set_vector(reinterpret_cast(trap_vector)); - userret(curr->trapframe); - __builtin_unreachable(); -} diff --git a/na/kernel/irq.cc b/na/kernel/irq.cc deleted file mode 100644 index 4de468a..0000000 --- a/na/kernel/irq.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * IRQ Manager Implementation - * - * Lock-free dispatch path for interrupt context safety. - * Registration/unregistration is protected by atomic publish. - * - * Reference: docs/specs/trap.md §3.3 - */ - -#include -#include -#include - -/* Global singleton */ -IrqManager g_irq_manager; - -void IrqManager::init() { - for (uint32 i = 0; i < kMaxIrqs; i++) { - entries_[i].handler_ptr.store(0, __ATOMIC_RELAXED); - entries_[i].arg = nullptr; - } -} - -int IrqManager::register_handler(uint32 irq, IrqHandler handler, void *arg) { - if (irq >= kMaxIrqs) - return -EINVAL; - - if (!handler) - return -EINVAL; - - /* Check if already registered (CAS from 0 to handler) */ - uint64 expected = 0; - uint64 desired = reinterpret_cast(handler); - - /* Set arg first (before publishing handler) so dispatch sees valid arg */ - entries_[irq].arg = arg; - - if (!entries_[irq].handler_ptr.compare_exchange(expected, desired, - __ATOMIC_ACQ_REL)) { - /* Already registered by someone else */ - return -EBUSY; - } - - return 0; -} - -void IrqManager::unregister_handler(uint32 irq) { - if (irq >= kMaxIrqs) - return; - - /* Clear handler with release semantics */ - entries_[irq].handler_ptr.store(0, __ATOMIC_RELEASE); - entries_[irq].arg = nullptr; -} - -void IrqManager::dispatch(uint32 irq) { - if (irq >= kMaxIrqs) - return; - - /* Load handler with acquire semantics — pairs with register's release */ - uint64 h = entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE); - if (h == 0) { - /* Spurious or unregistered IRQ — silently ignore */ - return; - } - - auto handler = reinterpret_cast(h); - handler(irq, entries_[irq].arg); -} - -bool IrqManager::has_handler(uint32 irq) const { - if (irq >= kMaxIrqs) - return false; - return entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE) != 0; -} - -void IrqManager::set_chip(const IrqChip* chip) { - chip_ = chip; -} - -uint32 IrqManager::claim() const { - return chip_ ? chip_->claim() : 0; -} - -void IrqManager::complete(uint32 irq) const { - if (chip_) chip_->complete(irq); -} - -void IrqManager::init_ap() const { - if (chip_ && chip_->init_ap) chip_->init_ap(); -} - -void IrqManager::enable(uint32 irq) const { - if (chip_ && chip_->enable) chip_->enable(irq); -} - -void IrqManager::disable(uint32 irq) const { - if (chip_ && chip_->disable) chip_->disable(irq); -} - -void IrqManager::set_priority(uint32 irq, uint32 priority) const { - if (chip_ && chip_->set_priority) chip_->set_priority(irq, priority); -} diff --git a/na/kernel/main.cc b/na/kernel/main.cc deleted file mode 100644 index db5843c..0000000 --- a/na/kernel/main.cc +++ /dev/null @@ -1,468 +0,0 @@ -/* 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. - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// 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. -// 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 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(); - -// AP per-CPU init -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(-100), /* AT_FDCWD */ - reinterpret_cast("/dev/console"), O_RDWR, 0, 0, 0); - if (fd0 < 0) { - printk(KERN_ERR "[init] open /dev/console failed: %d\n", - static_cast(fd0)); - } else { - sys_dup(static_cast(fd0), 0, 0, 0, 0, 0); /* fd 1 */ - sys_dup(static_cast(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. -// ============================================================ -static lume::atomic ap_boot_sync; - -// ============================================================ -// kernel_main — C++ entry point for all CPUs -// ============================================================ -extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { - - if (cpu_id == bsp_hart_id) { - // ======================================================== - // BSP: Absolute single-core period — no locks needed. - // APs are spinning on ap_boot_sync. - // ======================================================== - - uint64 fdt_pa = fdt_paddr; - - // ---- Phase 1: Console + Exception Safety Net ---- - - // 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=%llu\n", cpu_id); - printk(KERN_INFO "boot: higher-half kernel active\n"); - - /* 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(kernel_map.phys_addr), - static_cast(kernel_map.virt_addr), - static_cast(kernel_map.size)); - - // --- Subsystem init DAG (strict top-down order) --- - - // 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"); - - // 4. KASLR — seed PRNG and randomise VA layout. - // MUST happen before vmm_init() builds the kernel page table. - { - uint64 kaslr_seed = 0; - fdt_early_get_kaslr_seed(&kaslr_seed); - random_early_init(kaslr_seed); - arch::kaslr_init(); - } - - // Sort exception table before setting RO permissions in vmm_init() - extable_init(); - - // ---- 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"); - - // 6. Virtual memory manager — builds page table with KASLR'd bases - vmm_init(); - printk(KERN_INFO "init: vmm_init done\n"); - - // 7. Physical memory manager (Buddy) - pmm_init(); - printk(KERN_INFO "init: pmm_init done\n"); - - // 8. Slab allocator — operator new usable after this - slab_init(); - printk(KERN_INFO "init: slab_init done\n"); - - // 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"); - - // ---- Phase 3: Task & Scheduler (software state before hardware) ---- - - // 11. Task management (PID allocator + global task list) - task_init(); - printk(KERN_INFO "init: task_init done\n"); - - 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"); - - // 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"); - - // 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) - - // ---- 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(); - - // 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"); - - // Release APs with RELEASE semantics: - // 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(_start))).raw; - } - uint32 num_harts = 4; // TODO: read from FDT /cpus - for (uint32 h = 0; h < num_harts; h++) { - if (h == static_cast(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, which will pick up PID 1. - arch::cpu::intr_on(); - - printk(KERN_INFO "boot: BSP entering idle loop\n"); - - } else { - // ======================================================== - // 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); - - 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 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) - } - - // 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(); - } -} diff --git a/na/kernel/module.mk b/na/kernel/module.mk deleted file mode 100644 index 754a14c..0000000 --- a/na/kernel/module.mk +++ /dev/null @@ -1,26 +0,0 @@ -# kernel/module.mk -SRCS_CC += kernel/main.cc -SRCS_CC += kernel/sync/spinlock.cc -SRCS_CC += kernel/sync/mutex.cc -SRCS_CC += kernel/trap.cc -SRCS_CC += kernel/irq.cc -SRCS_CC += kernel/sched_core.cc -SRCS_CC += kernel/sched_class_o1.cc -SRCS_CC += kernel/timer.cc -SRCS_CC += kernel/pid.cc -SRCS_CC += kernel/pgrp.cc -SRCS_CC += kernel/task.cc -SRCS_CC += kernel/syscall.cc -SRCS_CC += kernel/sys_process.cc -SRCS_CC += kernel/sys_mm.cc -SRCS_CC += kernel/sys_info.cc -SRCS_CC += kernel/sys_fs.cc -SRCS_CC += kernel/uaccess.cc -SRCS_CC += kernel/waitqueue.cc -SRCS_CC += kernel/signal.cc -SRCS_CC += kernel/sys_signal.cc -SRCS_CC += kernel/sys_misc.cc -SRCS_CC += kernel/exec.cc -SRCS_CC += kernel/poll.cc -SRCS_CC += kernel/ptrace.cc -SRCS_CC += kernel/smp.cc diff --git a/na/kernel/pgrp.cc b/na/kernel/pgrp.cc deleted file mode 100644 index e5b382c..0000000 --- a/na/kernel/pgrp.cc +++ /dev/null @@ -1,148 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Process Group / Session Implementation - * - * Hash table of signal_struct objects keyed by pgid. Lifecycle of - * signal_struct mirrors Linux: one per thread group, refcounted across - * CLONE_THREAD siblings, freed when the last task in the group exits. - * - * Reference: Linux kernel/pid.c, kernel/exit.c - */ - -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* Hash table state */ -/* ================================================================== */ - -list_node g_pgrp_hash[kPgrpHashSize]; -Spinlock g_pgrp_lock; - -void pgrp_init() -{ - for (auto& bucket : g_pgrp_hash) bucket.init(); - g_pgrp_lock.init("pgrp"); -} - -/* ================================================================== */ -/* Internal helpers (must be called with g_pgrp_lock held) */ -/* ================================================================== */ - -static void pgrp_attach_locked(signal_struct* sig, int32 pgid) -{ - sig->pgid = pgid; - list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)]; - sig->pgrp_link.prev = bucket->prev; - sig->pgrp_link.next = bucket; - bucket->prev->next = &sig->pgrp_link; - bucket->prev = &sig->pgrp_link; -} - -static void pgrp_detach_locked(signal_struct* sig) -{ - /* Unlink only if currently on a list — init() makes prev/next point - * to self, which we treat as "not linked". */ - if (sig->pgrp_link.next == &sig->pgrp_link) return; - sig->pgrp_link.prev->next = sig->pgrp_link.next; - sig->pgrp_link.next->prev = sig->pgrp_link.prev; - sig->pgrp_link.init(); -} - -/* ================================================================== */ -/* signal_struct allocation / refcount */ -/* ================================================================== */ - -signal_struct* alloc_signal_struct(int32 leader_pid, TaskControlBlock* leader) -{ - auto* sig = new signal_struct; - if (!sig) return nullptr; - sig->init(leader_pid, leader); - - LockGuard guard(g_pgrp_lock); - pgrp_attach_locked(sig, leader_pid); - return sig; -} - -signal_struct* fork_signal_struct(signal_struct* parent, - TaskControlBlock* child_leader) -{ - if (!parent) return nullptr; - - auto* sig = new signal_struct; - if (!sig) return nullptr; - - /* Copy identity from parent, then override leader and attach. */ - sig->lock.init("signal"); - sig->refcount = 1; - sig->leader = child_leader; - sig->tty_old_pgrp = 0; - sig->pgrp_link.init(); - - { - LockGuard pg(parent->lock); - sig->pgid = parent->pgid; - sig->sid = parent->sid; - sig->tty = parent->tty; - } - - LockGuard guard(g_pgrp_lock); - pgrp_attach_locked(sig, sig->pgid); - return sig; -} - -void get_signal_struct(signal_struct* sig) -{ - if (!sig) return; - LockGuard guard(sig->lock); - sig->refcount++; -} - -void put_signal_struct(signal_struct* sig) -{ - if (!sig) return; - - uint32 remaining; - { - LockGuard guard(sig->lock); - remaining = --sig->refcount; - } - if (remaining != 0) return; - - /* Last reference — detach from pgrp hash and free. */ - { - LockGuard guard(g_pgrp_lock); - pgrp_detach_locked(sig); - } - delete sig; -} - -/* ================================================================== */ -/* Pgrp membership change */ -/* ================================================================== */ - -void change_pgid(signal_struct* sig, int32 new_pgid) -{ - if (!sig) return; - LockGuard guard(g_pgrp_lock); - if (sig->pgid == new_pgid) return; - pgrp_detach_locked(sig); - pgrp_attach_locked(sig, new_pgid); -} - -int pgrp_member_count(int32 pgid) -{ - LockGuard guard(g_pgrp_lock); - int n = 0; - list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(pgid)]; - for (list_node* node = bucket->next; node != bucket; node = node->next) { - auto* sig = list_entry(node); - if (sig->pgid == pgid) n++; - } - return n; -} diff --git a/na/kernel/pid.cc b/na/kernel/pid.cc deleted file mode 100644 index 04b846b..0000000 --- a/na/kernel/pid.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * PID Allocator — Bitmap-based O(1) block search - * Reference: docs/specs/task.md §3.2 - */ - -#include - -void PidAllocator::init() -{ - for (int i = 0; i < kBitmapWords; i++) - bitmap_[i] = 0; - next_hint_ = 1; - bitmap_[0] |= 0x1ULL; /* Reserve PID 0 (idle/swapper) only. - * PID 1 (init) is allocated normally — the first - * kthread_create() call gets it, matching Linux. */ -} - -int32 PidAllocator::alloc() -{ - LockGuard guard(lock_); - - int32 word_start = next_hint_ / 64; - - for (int32 passes = 0; passes < kBitmapWords; passes++) { - int32 w = (word_start + passes) % kBitmapWords; - uint64 free_bits = ~bitmap_[w]; - if (free_bits == 0) - continue; - - int bit = 0; - uint64 v = free_bits; - while ((v & 1ULL) == 0) { v >>= 1; bit++; } - int32 pid = w * 64 + bit; - - if (pid >= kMaxPid) - continue; - - bitmap_[w] |= (1ULL << bit); - next_hint_ = pid + 1; - if (next_hint_ >= kMaxPid) - next_hint_ = 2; - - return pid; - } - - return -1; -} - -void PidAllocator::free(int32 pid) -{ - if (pid < 0 || pid >= kMaxPid) - return; - - LockGuard guard(lock_); - int32 w = pid / 64; - int32 bit = pid % 64; - bitmap_[w] &= ~(1ULL << bit); -} - -PidAllocator g_pid_alloc; diff --git a/na/kernel/poll.cc b/na/kernel/poll.cc deleted file mode 100644 index 3674491..0000000 --- a/na/kernel/poll.cc +++ /dev/null @@ -1,250 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_ppoll — POSIX ppoll() implementation. - * - * Linux fs/select.c:do_sys_poll() pattern, simplified for our needs: - * - Two-phase loop: "register-then-check". First pass calls every - * f_op->poll() with a real poll_table so drivers can register their - * wait queues; if any fd is already ready we return immediately. - * Otherwise schedule() until a wq fires (or timeout/signal), then - * re-check with poll_table=nullptr (probe-only). - * - * Limitations vs. Linux: - * - Only a single wait queue per ppoll call is tracked (the first one - * a driver hands to poll_wait). Multi-fd polls work, but only the - * first wq's wake actually re-runs the loop; other fds becoming - * ready won't be noticed until the timer fires or another wake. - * BusyBox interactive shell polls one fd (stdin), so this matches. - * - * Reference: Linux fs/select.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* poll_wait — called by file_operations->poll() implementations */ -/* ================================================================== */ - -void poll_wait(file* /*fp*/, WaitQueue* wq, poll_table* pt) -{ - /* Probe-only pass: pt is null, drivers skip registration. */ - if (!pt || !wq) return; - /* Single-wq design: keep the first one we see. A more complete - * implementation would chain entries onto a list. */ - if (!pt->wq) pt->wq = wq; -} - -/* ================================================================== */ -/* Linux-compatible timespec */ -/* ================================================================== */ - -struct linux_timespec_pp { - int64 tv_sec; - int64 tv_nsec; -}; - -/* - * Convert a timespec into a deadline in jiffies (g_jiffies clock). - * Returns 0 if the input is null (= infinite wait), or a deadline - * jiffy value otherwise. Caller treats 0 deadline as "no timeout". - */ -static uint64 timespec_to_deadline(const linux_timespec_pp* ts) -{ - if (!ts) return 0; - /* g_jiffies advances at 100 Hz (HZ=100) — 1 jiffy = 10 ms */ - uint64 ticks = static_cast(ts->tv_sec) * 100ULL; - ticks += ts->tv_nsec / 10'000'000; /* 10 ms granularity */ - /* Round up sub-jiffy nsec so a tiny timeout doesn't truncate to 0. */ - if (ts->tv_nsec % 10'000'000 != 0) ticks++; - return g_jiffies + ticks; -} - -/* ================================================================== */ -/* do_poll — one scan over all fds */ -/* ================================================================== */ - -/* - * For each fd: look up the file, call its poll() callback. If `pt` is - * non-null, drivers register their wait queues onto it. Returns the - * number of fds that have any non-zero revents. - */ -static int do_poll_scan(pollfd* fds, uint32 nfds, poll_table* pt) -{ - TaskControlBlock* c = current_task(); - files_struct* files = c ? c->files : nullptr; - if (!files) return 0; - int ready = 0; - - for (uint32 i = 0; i < nfds; i++) { - if (fds[i].fd < 0) { - fds[i].revents = 0; - continue; - } - file* fp = fcheck(files, fds[i].fd); - if (!fp) { - fds[i].revents = POLLNVAL; - ready++; - continue; - } - uint32 mask = 0; - if (fp->f_op && fp->f_op->poll) { - mask = fp->f_op->poll(fp, pt); - } else { - /* No poll handler — treat as always readable/writable so - * a missing implementation doesn't permanently block sh. */ - mask = POLLIN | POLLOUT; - } - /* Filter: revents always include error/hup; userland event bits - * only set if requested in events. */ - uint32 always = POLLERR | POLLHUP | POLLNVAL; - uint32 want = static_cast(fds[i].events) | always; - fds[i].revents = static_cast(mask & want); - if (fds[i].revents) ready++; - } - return ready; -} - -/* ================================================================== */ -/* sys_ppoll */ -/* ================================================================== */ - -/* - * a0 = struct pollfd __user *ufds - * a1 = nfds_t nfds - * a2 = const struct __kernel_timespec __user *tsp (nullable = infinite) - * a3 = const sigset_t __user *sigmask (nullable = no override) - * a4 = size_t sigsetsize - */ -int64 sys_ppoll(uint64 a0, uint64 a1, uint64 a2, uint64 a3, - uint64 a4, uint64 /*a5*/) -{ - uint64 ufds = a0; - uint32 nfds = static_cast(a1); - uint64 utsp = a2; - uint64 usigmask = a3; - uint64 sigsetsize = a4; - - if (nfds > 1024) return -EINVAL; /* Linux RLIMIT_NOFILE-ish cap */ - if (usigmask && sigsetsize != sizeof(sigset_t)) return -EINVAL; - - /* Copy timespec from user. Null pointer means "wait forever". */ - bool have_timeout = (utsp != 0); - linux_timespec_pp ts{}; - if (have_timeout) { - if (copy_from_user(&ts, utsp, sizeof(ts)) < 0) return -EFAULT; - if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= 1'000'000'000) - return -EINVAL; - } - uint64 deadline = have_timeout ? timespec_to_deadline(&ts) : 0; - - /* Apply temporary sigmask if requested. Linux semantics: the new - * mask is in effect for the duration of the call; on return we - * restore the old mask. If a signal interrupts us mid-wait, the - * original mask is restored before returning -EINTR. */ - TaskControlBlock* curr = current_task(); - sigset_t saved_mask{}; - bool mask_swapped = false; - if (usigmask && curr && curr->sighand) { - sigset_t newmask{}; - if (copy_from_user(&newmask, usigmask, sizeof(newmask)) < 0) - return -EFAULT; - newmask.del(SIGKILL); - newmask.del(SIGSTOP); - LockGuard guard(curr->sighand->siglock); - saved_mask = curr->blocked; - curr->blocked = newmask; - mask_swapped = true; - } - - /* Copy pollfd array from user. Allocate a kernel buffer; for the - * common nfds<=8 case this is one slab object. */ - pollfd* kfds = nullptr; - uint64 fds_bytes = static_cast(nfds) * sizeof(pollfd); - int64 ret = 0; - - if (nfds > 0) { - kfds = static_cast(operator new[](fds_bytes)); - if (!kfds) { ret = -ENOMEM; goto out_restore_mask; } - if (copy_from_user(kfds, ufds, fds_bytes) < 0) { - ret = -EFAULT; goto out_free; - } - } - - /* The main poll loop. */ - { - poll_table pt{}; - pt.wq = nullptr; - int n_ready = do_poll_scan(kfds, nfds, &pt); - - while (n_ready == 0) { - /* Timeout==0 means non-blocking probe. */ - if (have_timeout && deadline <= g_jiffies) break; - - /* No registered wait queue? Nothing to sleep on. This - * happens when no fd has a poll handler that calls - * poll_wait — fall back to a short reschedule loop so we - * don't burn the CPU. */ - if (!pt.wq) { - schedule(); - if (signal_pending(curr)) { ret = -EINTR; goto out_free; } - pt.wq = nullptr; - n_ready = do_poll_scan(kfds, nfds, &pt); - continue; - } - - /* Standard prepare-check-schedule pattern. */ - pt.wq->prepare_to_wait(curr, TaskState::Sleeping); - - /* Re-probe with pt=nullptr (no extra registration) so we - * see any state change that arrived between the previous - * scan and prepare_to_wait. */ - n_ready = do_poll_scan(kfds, nfds, nullptr); - if (n_ready > 0) { - pt.wq->finish_wait(curr); - break; - } - if (signal_pending(curr)) { - pt.wq->finish_wait(curr); - ret = -EINTR; goto out_free; - } - - schedule(); - pt.wq->finish_wait(curr); - - /* Reset the registration and rescan — drivers may register - * fresh wait queues this pass. */ - pt.wq = nullptr; - n_ready = do_poll_scan(kfds, nfds, &pt); - } - ret = n_ready; - } - - /* Copy revents back to user (events bits stay as user wrote them). */ - if (nfds > 0) { - if (copy_to_user(ufds, kfds, fds_bytes) < 0) ret = -EFAULT; - } - -out_free: - if (kfds) operator delete[](kfds); - -out_restore_mask: - if (mask_swapped && curr && curr->sighand) { - LockGuard guard(curr->sighand->siglock); - curr->blocked = saved_mask; - } - return ret; -} diff --git a/na/kernel/ptrace.cc b/na/kernel/ptrace.cc deleted file mode 100644 index 3280c07..0000000 --- a/na/kernel/ptrace.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * PtRegs ↔ TrapFrame conversion - * - * This is the ONLY file that knows both the internal TrapFrame layout - * and the Linux-ABI PtRegs layout. When TrapFrame is restructured - * (e.g., to embed pt_regs directly), only this file needs to change. - */ - -#include -#include - -void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src, - uint64 scause, uint64 stval) -{ - dst->epc = src->epc; - dst->ra = src->ra; - dst->sp = src->sp; - dst->gp = src->gp; - dst->tp = src->tp; - dst->t0 = src->t[0]; - dst->t1 = src->t[1]; - dst->t2 = src->t[2]; - dst->s0 = src->s[0]; - dst->s1 = src->s[1]; - dst->a0 = src->a[0]; - dst->a1 = src->a[1]; - dst->a2 = src->a[2]; - dst->a3 = src->a[3]; - dst->a4 = src->a[4]; - dst->a5 = src->a[5]; - dst->a6 = src->a[6]; - dst->a7 = src->a[7]; - dst->s2 = src->s[2]; - dst->s3 = src->s[3]; - dst->s4 = src->s[4]; - dst->s5 = src->s[5]; - dst->s6 = src->s[6]; - dst->s7 = src->s[7]; - dst->s8 = src->s[8]; - dst->s9 = src->s[9]; - dst->s10 = src->s[10]; - dst->s11 = src->s[11]; - dst->t3 = src->t[3]; - dst->t4 = src->t[4]; - dst->t5 = src->t[5]; - dst->t6 = src->t[6]; - dst->status = src->sstatus; - dst->badaddr = stval; - dst->cause = scause; - dst->orig_a0 = src->a[0]; /* Caller should set this before syscall dispatch */ -} - -void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src) -{ - dst->epc = src->epc; - dst->ra = src->ra; - dst->sp = src->sp; - dst->gp = src->gp; - dst->tp = src->tp; - dst->t[0] = src->t0; - dst->t[1] = src->t1; - dst->t[2] = src->t2; - dst->s[0] = src->s0; - dst->s[1] = src->s1; - dst->a[0] = src->a0; - dst->a[1] = src->a1; - dst->a[2] = src->a2; - dst->a[3] = src->a3; - dst->a[4] = src->a4; - dst->a[5] = src->a5; - dst->a[6] = src->a6; - dst->a[7] = src->a7; - dst->s[2] = src->s2; - dst->s[3] = src->s3; - dst->s[4] = src->s4; - dst->s[5] = src->s5; - dst->s[6] = src->s6; - dst->s[7] = src->s7; - dst->s[8] = src->s8; - dst->s[9] = src->s9; - dst->s[10] = src->s10; - dst->s[11] = src->s11; - dst->t[3] = src->t3; - dst->t[4] = src->t4; - dst->t[5] = src->t5; - dst->t[6] = src->t6; - dst->sstatus = src->status; - /* badaddr and cause are CSR values, not written back to TrapFrame */ -} diff --git a/na/kernel/sched_class_o1.cc b/na/kernel/sched_class_o1.cc deleted file mode 100644 index 28c693c..0000000 --- a/na/kernel/sched_class_o1.cc +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * O(1) Priority Bitmap Scheduling Class - */ - -#include -#include -#include -#include -#include - -static void o1_init_rq(RunQueue* rq) -{ - rq->o1().init(); -} - -static void o1_enqueue_task(RunQueue* rq, TaskControlBlock* task) -{ - rq->o1().enqueue(task); - rq->inc_running(); -} - -static void o1_dequeue_task(RunQueue* rq, TaskControlBlock* task) -{ - rq->o1().dequeue(task); - rq->dec_running(); -} - -static TaskControlBlock* o1_pick_next_task(RunQueue* rq) -{ - return rq->o1().pick_next(); -} - -static void o1_put_prev_task(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} - -int32 priority_to_timeslice(uint8 priority) -{ - int32 ts = 20 - static_cast(priority) * 19 / 31; - if (ts < 1) ts = 1; - return ts; -} - -static void o1_task_tick(RunQueue* rq, TaskControlBlock* curr) -{ - if (curr == rq->idle()) { - /* Idle task: if anything is runnable, yield immediately */ - if (rq->nr_running() > 0) { - rq->set_need_resched(true); - } - return; - } - - curr->time_slice--; - if (curr->time_slice <= 0) { - curr->time_slice = priority_to_timeslice(curr->priority); - rq->set_need_resched(true); - } -} - -static void o1_yield_task(RunQueue* rq, TaskControlBlock* curr) -{ - if (curr == rq->idle()) - return; - - curr->time_slice = 0; - rq->set_need_resched(true); -} - -const SchedClass g_sched_class_o1 = { - .name = "o1", - .next = &g_sched_class_idle, - .init_rq = o1_init_rq, - .enqueue_task = o1_enqueue_task, - .dequeue_task = o1_dequeue_task, - .pick_next_task = o1_pick_next_task, - .put_prev_task = o1_put_prev_task, - .task_tick = o1_task_tick, - .yield_task = o1_yield_task, -}; diff --git a/na/kernel/sched_core.cc b/na/kernel/sched_core.cc deleted file mode 100644 index c0e068e..0000000 --- a/na/kernel/sched_core.cc +++ /dev/null @@ -1,571 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Scheduler Core - * - * The core walks sched_class chain to pick the next task. Each class - * owns its own runqueue data and policy. This file owns the per-CPU - * runqueue array and the scheduling state machine. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Assembly trampoline symbol (MD layer) */ -extern "C" void kthread_entry_trampoline(); - -/* Per-CPU runqueue array */ -static RunQueue g_rq[kMaxCpus]; - -/* sched_class chain head (highest priority first) */ -static const SchedClass* g_sched_class_head = &g_sched_class_o1; - -/* ======================================================================== - * Idle thread - * ======================================================================== */ - -static void idle_thread_func(void* /*arg*/) -{ - while (true) { - if (g_rq[arch::cpu::id()].need_resched()) { - schedule(); - } - arch::cpu::intr_on(); - arch::cpu::halt_until_interrupt(); - } -} - - -/* ======================================================================== - * Idle class (always returns idle task) - * ======================================================================== */ - -static void idle_init_rq(RunQueue* /*rq*/) {} -static void idle_enqueue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static void idle_dequeue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static TaskControlBlock* idle_pick_next(RunQueue* rq) { return rq->idle(); } -static void idle_put_prev(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static void idle_task_tick(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} -static void idle_yield(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} - -const SchedClass g_sched_class_idle = { - .name = "idle", - .next = nullptr, - .init_rq = idle_init_rq, - .enqueue_task = idle_enqueue, - .dequeue_task = idle_dequeue, - .pick_next_task = idle_pick_next, - .put_prev_task = idle_put_prev, - .task_tick = idle_task_tick, - .yield_task = idle_yield, -}; - -/* ======================================================================== - * Internal helpers - * ======================================================================== */ - -static RunQueue* rq_for_cpu(uint32 cpu_id) -{ - return &g_rq[cpu_id]; -} - -static RunQueue* this_rq() -{ - return rq_for_cpu(arch::cpu::id()); -} - -static TaskControlBlock* pick_next_task(RunQueue* rq) -{ - for (const SchedClass* c = g_sched_class_head; c; c = c->next) { - TaskControlBlock* t = c->pick_next_task(rq); - if (t) { - return t; - } - } - return rq->idle(); -} - -static const char* idle_name_for_cpu(uint32 cpu) -{ - static char names[kMaxCpus][16]; - if (cpu >= kMaxCpus) { - kernel_panic("sched: cpu_id out of range"); - } - - if (names[cpu][0] == '\0') { - ksnprintf(names[cpu], sizeof(names[cpu]), "idle/%u", cpu); - } - return names[cpu]; -} - -/* ======================================================================== - * Kernel thread entry — MI-layer function called by MD trampoline - * ======================================================================== */ - -/* Forward declaration — C++ linkage (task.cc) */ -void task_exit(int code); - -extern "C" void kthread_entry(void (*entry)(void*), void* arg) -{ - RunQueue* rq = this_rq(); - rq->lock().release(); - - arch::cpu::intr_on(); - - entry(arg); - - /* Entry function returned — clean exit (release PID, stack, TCB) */ - task_exit(0); - __builtin_unreachable(); -} - -/* ======================================================================== - * fork_child_entry — called from ret_from_fork assembly - * ======================================================================== */ -extern "C" void userret(arch::TrapFrame* tf); -extern "C" void trap_vector(); - -extern "C" void fork_child_entry(arch::TrapFrame* tf) -{ - RunQueue* rq = this_rq(); - rq->lock().release(); - - /* Set stvec to trap_vector for user-mode traps, then return to user */ - arch::trap::set_vector(reinterpret_cast(trap_vector)); - userret(tf); - - __builtin_unreachable(); -} - -/* ======================================================================== - * alloc_task — canonical TCB allocator (defined in task.cc) - * - * Single point of initialization for TaskControlBlock. Ensures all - * fields are properly zeroed when the struct gains new members. - * ======================================================================== */ - -extern TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags, uint8 cpu); - -/* ======================================================================== - * Public API: current_task() - * ======================================================================== */ - -TaskControlBlock* current_task() -{ - return this_rq()->current(); -} - -/* ======================================================================== - * Public API: sched_enqueue / sched_dequeue - * ======================================================================== */ - -void sched_enqueue(TaskControlBlock* task) -{ - RunQueue* rq = rq_for_cpu(task->cpu_id); - LockGuard guard(rq->lock()); - - if (!task->sched_class) { - task->sched_class = &g_sched_class_o1; - } - if (!task->sched_entity) { - task->sched_entity = &task->run_link; - } - - task->sched_class->enqueue_task(rq, task); - - if (rq->current() == rq->idle() || task->priority < rq->current()->priority) { - rq->set_need_resched(true); - } -} - -void sched_dequeue(TaskControlBlock* task) -{ - RunQueue* rq = rq_for_cpu(task->cpu_id); - LockGuard guard(rq->lock()); - - if (!task->sched_class) { - task->sched_class = &g_sched_class_o1; - } - task->sched_class->dequeue_task(rq, task); -} - -/* ======================================================================== - * schedule() — main scheduling entry point - * ======================================================================== */ - -void schedule() -{ - RunQueue* rq = this_rq(); - rq->lock().acquire(); - - TaskControlBlock* prev = rq->current(); - - if (prev && prev->state == TaskState::Running) { - prev->state = TaskState::Runnable; - prev->sched_class->enqueue_task(rq, prev); - } else if (prev) { - prev->sched_class->put_prev_task(rq, prev); - } - - TaskControlBlock* next = pick_next_task(rq); - - if (prev == next) { - /* We picked the same task. It must be Running again. */ - next->state = TaskState::Running; - next->sched_class->dequeue_task(rq, next); - rq->clear_need_resched(); - rq->lock().release(); - return; - } - - if (next != rq->idle()) { - next->sched_class->dequeue_task(rq, next); - } - - next->state = TaskState::Running; - rq->set_current(next); - rq->clear_need_resched(); - - /* Switch address space (satp) if the new task has a different mm. - * Kernel threads (mm == nullptr) borrow the previous task's mm, - * so no satp switch is needed — this matches Linux's lazy TLB behavior. */ - { - VmSpace* prev_mm = prev ? prev->mm : nullptr; - VmSpace* next_mm = next->mm; - uint32 cpu = static_cast(arch::cpu::id()); - - if (next_mm && next_mm != prev_mm) { - /* Switching TO a user process with a different address space. - * Linux: switch_mm_irqs_off() in arch/riscv/mm/context.c - * sets satp and flushes TLB. */ - arch::mmu::set_page_table(next_mm->root_pa); - arch::mmu::flush_tlb_all(); - next_mm->cpu_mask.fetch_or(1ULL << cpu, __ATOMIC_RELAXED); - - /* Critical: update TrapFrame's kernel_satp so that trap_vector.S - * restores the correct page table on next trap entry. - * Without this, a process migrated to a new CPU would have its - * trap entry use a stale satp from when trapframe_init_user() - * was called, pointing to whatever page table was active then - * on the ORIGINAL CPU. */ - if (next->trapframe) { - next->trapframe->kernel_satp = arch::mmu::read_satp(); - } - } - - if (prev_mm && prev_mm != next_mm) { - /* Leaving prev's address space — clear our bit */ - prev_mm->cpu_mask.fetch_and(~(1ULL << cpu), __ATOMIC_RELAXED); - } - } - - /* FPU lazy save/restore. - * Save prev's FP registers only if they were modified (FS=Dirty). - * Restore next's FP state for user tasks; disable FPU for kthreads. */ - if (arch::fpu_state_dirty(arch::fpu_get_fs())) { - fpu_save(&prev->fpstate); - } - if (next->mm != nullptr) { - arch::fpu_set_fs(arch::kSstatusFS_Clean); - fpu_restore(&next->fpstate); - } else { - arch::fpu_set_fs(arch::kSstatusFS_Off); - } - - arch::context_switch(&prev->context, &next->context); - - rq->lock().release(); -} - -/* ======================================================================== - * sched_tick() — called from timer interrupt (MI trap layer) - * ======================================================================== */ - -void sched_tick() -{ - RunQueue* rq = this_rq(); - { - LockGuard guard(rq->lock()); - TaskControlBlock* curr = rq->current(); - if (!curr) return; - curr->sched_class->task_tick(rq, curr); - } - - /* Periodic load balancing — runs without holding rq lock */ - sched_balance(); -} - -/* ======================================================================== - * sched_check_preempt() — called from trap return path - * - * Only preempts if: - * 1. need_resched flag is set (a higher-priority task is ready) - * 2. preempt_count == 0 (no spinlocks held, safe to context switch) - * ======================================================================== */ - -void sched_check_preempt() -{ - RunQueue* rq = this_rq(); - if (!rq->need_resched()) return; - - TaskControlBlock* curr = current_task(); - if (curr && curr->preempt_count > 0) { - /* Cannot preempt — caller is in a critical section. - * The preemption will fire when the last lock is released. */ - return; - } - schedule(); -} - -/* ======================================================================== - * sched_yield() — voluntary yield - * ======================================================================== */ - -void sched_yield() -{ - RunQueue* rq = this_rq(); - { - LockGuard guard(rq->lock()); - TaskControlBlock* curr = rq->current(); - if (curr && curr != rq->idle()) { - curr->sched_class->yield_task(rq, curr); - rq->set_need_resched(true); - } - } - schedule(); -} - -/* ======================================================================== - * sched_init() — BSP initialization - * ======================================================================== */ - -void sched_init() -{ - for (int i = 0; i < kMaxCpus; i++) { - RunQueue* rq = &g_rq[i]; - rq->init(static_cast(i)); - - g_sched_class_o1.init_rq(rq); - g_sched_class_idle.init_rq(rq); - } - - g_sched_class_head = &g_sched_class_o1; - - uint64 bsp_cpu = arch::cpu::id(); - uint8 bsp_cpu8 = static_cast(bsp_cpu); - - TaskControlBlock* idle0 = alloc_task(idle_name_for_cpu(static_cast(bsp_cpu)), - kIdlePriority, - TF_KTHREAD | TF_IDLE, bsp_cpu8); - if (!idle0) kernel_panic("sched_init: failed to create idle/bsp"); - - idle0->pid = 0; - idle0->tgid = 0; - idle0->sched_class = &g_sched_class_idle; - idle0->sched_entity = nullptr; - - uint64 stack_top = idle0->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - idle0->context = {}; - idle0->context.ra = reinterpret_cast(kthread_entry_trampoline); - idle0->context.sp = stack_top; - idle0->context.s[0] = reinterpret_cast(idle_thread_func); - idle0->context.s[1] = 0; - - g_rq[bsp_cpu].set_idle(idle0); - - TaskControlBlock* bsp_task = alloc_task("kernel_main", 0, TF_KTHREAD, bsp_cpu8); - if (!bsp_task) kernel_panic("sched_init: failed to create bsp_task"); - - bsp_task->pid = -1; - bsp_task->tgid = -1; - bsp_task->state = TaskState::Running; - - g_rq[bsp_cpu].set_current(bsp_task); -} - -/* ======================================================================== - * sched_init_ap() — AP per-CPU initialization - * ======================================================================== */ - -void sched_init_ap() -{ - uint64 cpu = arch::cpu::id(); - - const char* name = idle_name_for_cpu(static_cast(cpu)); - - TaskControlBlock* idle = alloc_task(name, kIdlePriority, - TF_KTHREAD | TF_IDLE, - static_cast(cpu)); - if (!idle) kernel_panic("sched_init_ap: failed to create idle"); - - idle->pid = 0; - idle->tgid = 0; - idle->sched_class = &g_sched_class_idle; - idle->sched_entity = nullptr; - - uint64 stack_top = idle->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - idle->context = {}; - idle->context.ra = reinterpret_cast(kthread_entry_trampoline); - idle->context.sp = stack_top; - idle->context.s[0] = reinterpret_cast(idle_thread_func); - idle->context.s[1] = 0; - - g_rq[cpu].set_idle(idle); - - TaskControlBlock* ap_task = alloc_task("ap_boot", 0, TF_KTHREAD, - static_cast(cpu)); - if (!ap_task) kernel_panic("sched_init_ap: failed to create ap_task"); - - ap_task->pid = -1; - ap_task->tgid = -1; - ap_task->state = TaskState::Running; - - g_rq[cpu].set_current(ap_task); -} - -/* ======================================================================== - * sched_nr_running() — runnable task count for a CPU - * ======================================================================== */ - -uint32 sched_nr_running(uint32 cpu_id) -{ - if (cpu_id >= kMaxCpus) return 0; - return g_rq[cpu_id].o1().nr_running(); -} - -/* ======================================================================== - * sched_migrate_task() — move a task between CPUs - * - * Locks are acquired in cpu_id order to prevent ABBA deadlock. - * The task must be Runnable (in a runqueue) and not currently running. - * ======================================================================== */ - -int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu) -{ - if (!task || target_cpu >= kMaxCpus) return -22; /* -EINVAL */ - - uint8 src_cpu = task->cpu_id; - if (src_cpu == target_cpu) return 0; /* already there */ - - /* Cannot migrate idle or currently-running tasks */ - if (task->flags & TF_IDLE) return -22; - - RunQueue* src_rq = rq_for_cpu(src_cpu); - RunQueue* dst_rq = rq_for_cpu(target_cpu); - - /* Ordered lock acquisition: always lock lower cpu_id first */ - if (src_cpu < target_cpu) { - src_rq->lock().acquire(); - dst_rq->lock().acquire(); - } else { - dst_rq->lock().acquire(); - src_rq->lock().acquire(); - } - - int ret = -22; - - /* Double-check: task must not be current on either CPU */ - if (task != src_rq->current() && task->state == TaskState::Runnable) { - /* Dequeue from source */ - task->sched_class->dequeue_task(src_rq, task); - - /* Update cpu_id and enqueue on destination */ - task->cpu_id = target_cpu; - task->sched_class->enqueue_task(dst_rq, task); - - /* Signal target CPU to reschedule */ - dst_rq->set_need_resched(true); - ret = 0; - } - - /* Release in reverse order */ - if (src_cpu < target_cpu) { - dst_rq->lock().release(); - src_rq->lock().release(); - } else { - src_rq->lock().release(); - dst_rq->lock().release(); - } - - /* If migration succeeded, send reschedule IPI to target */ - if (ret == 0) { - smp_send_reschedule(target_cpu); - } - - return ret; -} - -/* ======================================================================== - * sched_balance() — periodic load balancer (pull model) - * - * Called from sched_tick() context. Looks at all other CPUs and if any - * has 2+ more runnable tasks than the local CPU, pulls one task. - * This is a simplified Linux-style load_balance(). - * ======================================================================== */ - -void sched_balance() -{ - uint32 this_cpu = static_cast(arch::cpu::id()); - uint32 local_load = g_rq[this_cpu].o1().nr_running(); - - /* Only balance if we're idle or lightly loaded */ - if (local_load > 1) return; - - uint32 busiest_cpu = this_cpu; - uint32 busiest_load = local_load; - - /* Find the busiest CPU */ - for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) { - if (cpu == this_cpu) continue; - uint32 load = g_rq[cpu].o1().nr_running(); - if (load > busiest_load) { - busiest_load = load; - busiest_cpu = cpu; - } - } - - /* Only pull if imbalance is >= 2 */ - if (busiest_cpu == this_cpu || busiest_load < local_load + 2) return; - - /* Try to pull one task from the busiest CPU */ - RunQueue* busy_rq = rq_for_cpu(busiest_cpu); - - /* Lock ordering: lower cpu_id first */ - RunQueue* first = (this_cpu < busiest_cpu) - ? &g_rq[this_cpu] : busy_rq; - RunQueue* second = (this_cpu < busiest_cpu) - ? busy_rq : &g_rq[this_cpu]; - - first->lock().acquire(); - second->lock().acquire(); - - /* Re-check after acquiring locks */ - TaskControlBlock* victim = busy_rq->o1().pick_next(); - if (victim && victim != busy_rq->current() - && !(victim->flags & TF_IDLE)) { - victim->sched_class->dequeue_task(busy_rq, victim); - victim->cpu_id = static_cast(this_cpu); - victim->sched_class->enqueue_task(&g_rq[this_cpu], victim); - g_rq[this_cpu].set_need_resched(true); - } - - second->lock().release(); - first->lock().release(); -} diff --git a/na/kernel/sched_internal.h b/na/kernel/sched_internal.h deleted file mode 100644 index 514b613..0000000 --- a/na/kernel/sched_internal.h +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -class O1RunQueue { -public: - void init() { - bitmap_ = 0; - nr_running_ = 0; - for (int p = 0; p < kNumPriorities; p++) { - queues_[p].init(); - } - } - - void enqueue(TaskControlBlock* task) { - int prio = task->priority; - - list_node* head = &queues_[prio]; - task->run_link.prev = head->prev; - task->run_link.next = head; - head->prev->next = &task->run_link; - head->prev = &task->run_link; - - bitmap_ |= (1U << prio); - nr_running_++; - - task->state = TaskState::Runnable; - } - - void dequeue(TaskControlBlock* task) { - int prio = task->priority; - - task->run_link.prev->next = task->run_link.next; - task->run_link.next->prev = task->run_link.prev; - task->run_link.init(); - - if (queues_[prio].is_empty()) { - bitmap_ &= ~(1U << prio); - } - - if (nr_running_ > 0) { - nr_running_--; - } - } - - TaskControlBlock* pick_next() const { - if (bitmap_ == 0) { - return nullptr; - } - - int prio = lume::ctz32(bitmap_); - list_node* node = queues_[prio].next; - return list_entry(node); - } - - uint32 nr_running() const { return nr_running_; } - -private: - uint32 bitmap_ = 0; - list_node queues_[kNumPriorities]; - uint32 nr_running_ = 0; -}; - -class RunQueue { -public: - void init(uint32 cpu_id) { - lock_.init("RunQueue"); - cpu_id_ = cpu_id; - nr_running_ = 0; - need_resched_ = false; - current_ = nullptr; - idle_ = nullptr; - } - - Spinlock& lock() { return lock_; } - - uint32 cpu_id() const { return cpu_id_; } - - uint32 nr_running() const { return nr_running_; } - void inc_running() { nr_running_++; } - void dec_running() { if (nr_running_ > 0) nr_running_--; } - - bool need_resched() const { return need_resched_; } - void set_need_resched(bool v) { need_resched_ = v; } - void clear_need_resched() { need_resched_ = false; } - - TaskControlBlock* current() const { return current_; } - void set_current(TaskControlBlock* task) { current_ = task; } - - TaskControlBlock* idle() const { return idle_; } - void set_idle(TaskControlBlock* task) { idle_ = task; } - - O1RunQueue& o1() { return o1_; } - const O1RunQueue& o1() const { return o1_; } - -private: - Spinlock lock_; - uint32 cpu_id_ = 0; - uint32 nr_running_ = 0; - bool need_resched_ = false; - - TaskControlBlock* current_ = nullptr; - TaskControlBlock* idle_ = nullptr; - - O1RunQueue o1_; -}; diff --git a/na/kernel/signal.cc b/na/kernel/signal.cc deleted file mode 100644 index 53d26da..0000000 --- a/na/kernel/signal.cc +++ /dev/null @@ -1,442 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Signal Delivery — Queue, Dequeue, Default Actions, and do_signal - * - * Implements the core signal infrastructure: - * - send_signal(): enqueue a signal to a task's pending set - * - force_sig_fault(): fault-induced signal (trap handler API) - * - do_signal(): called on return-to-user, delivers pending signals - * - next_signal(): dequeue the highest-priority deliverable signal - * - * Signal frame construction (arch-specific) is in arch/riscv/signal.cc. - * - * Reference: Linux kernel/signal.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Forward declaration: arch-specific signal frame setup */ -int setup_signal_frame(arch::TrapFrame* tf, int signo, - kernel_sigaction* ka, kernel_siginfo* info); - -/* ================================================================== */ -/* sighand_struct lifecycle (refcount-shared, Linux copy_sighand) */ -/* ================================================================== */ - -sighand_struct* alloc_sighand() -{ - auto* sh = new sighand_struct; - if (sh) sh->init(); - return sh; -} - -sighand_struct* copy_sighand(sighand_struct* parent) -{ - /* Linux: kernel/fork.c:copy_sighand() when !CLONE_SIGHAND copies - * the action table verbatim so the child inherits dispositions - * without sharing future modifications. */ - if (!parent) return alloc_sighand(); - auto* sh = new sighand_struct; - if (!sh) return nullptr; - sh->init(); - LockGuard guard(parent->siglock); - for (int i = 0; i < _NSIG; i++) sh->action[i] = parent->action[i]; - return sh; -} - -void get_sighand(sighand_struct* sh) -{ - if (!sh) return; - LockGuard guard(sh->siglock); - sh->refcount++; -} - -void put_sighand(sighand_struct* sh) -{ - if (!sh) return; - uint32 remaining; - { - LockGuard guard(sh->siglock); - remaining = --sh->refcount; - } - if (remaining == 0) delete sh; -} - -/* ================================================================== */ -/* next_signal — find lowest-numbered deliverable signal */ -/* ================================================================== */ - -/* Manual count-trailing-zeros (no libgcc __ctzdi2 dependency) */ -static inline int sig_ctz64(uint64 x) -{ - if (x == 0) return 64; - int n = 0; - if ((x & 0xFFFFFFFF) == 0) { n += 32; x >>= 32; } - if ((x & 0x0000FFFF) == 0) { n += 16; x >>= 16; } - if ((x & 0x000000FF) == 0) { n += 8; x >>= 8; } - if ((x & 0x0000000F) == 0) { n += 4; x >>= 4; } - if ((x & 0x00000003) == 0) { n += 2; x >>= 2; } - if ((x & 0x00000001) == 0) { n += 1; } - return n; -} - -static int next_signal(sigpending* pending, const sigset_t* mask) -{ - /* Standard signals: check bitmap & ~blocked */ - for (int i = 0; i < _NSIG_WORDS; i++) { - uint64 bits = pending->signal.sig[i] & ~mask->sig[i]; - if (bits != 0) { - int sig = i * 64 + sig_ctz64(bits) + 1; - return sig; - } - } - return 0; -} - -/* ================================================================== */ -/* dequeue_signal — remove a signal from the pending set */ -/* ================================================================== */ - -/* - * Dequeue the next deliverable signal. MUST be called with - * task->sighand->siglock held. Mirrors Linux's __dequeue_signal() - * (kernel/signal.c) which requires the same lock. - */ -static int dequeue_signal_locked(TaskControlBlock* task, kernel_siginfo* info) -{ - int sig = next_signal(&task->pending, &task->blocked); - if (sig == 0) return 0; - - /* Clear the bitmap bit */ - task->pending.signal.del(sig); - - /* Search the queue for a matching sigqueue entry */ - list_node* node = task->pending.list.next; - while (node != &task->pending.list) { - auto* q = list_entry(node); - if (q->info.si_signo == sig) { - /* Remove from queue */ - node->prev->next = node->next; - node->next->prev = node->prev; - if (info) *info = q->info; - delete q; - return sig; - } - node = node->next; - } - - /* No queued entry — fill in minimal info */ - if (info) { - info->si_signo = sig; - info->si_errno = 0; - info->si_code = 0; /* SI_USER */ - } - return sig; -} - -/* ================================================================== */ -/* send_signal — enqueue a signal to a task */ -/* ================================================================== */ - -/* - * Drop a queued sigqueue entry with the given signo from `task`. - * - * MUST be called with task->sighand->siglock held — Linux's - * flush_sigqueue_mask() has the same precondition (kernel/signal.c). - */ -static bool discard_pending_locked(TaskControlBlock* task, int signo) -{ - bool any = false; - list_node* node = task->pending.list.next; - while (node != &task->pending.list) { - list_node* nxt = node->next; - auto* q = list_entry(node); - if (q->info.si_signo == signo) { - node->prev->next = node->next; - node->next->prev = node->prev; - delete q; - any = true; - } - node = nxt; - } - task->pending.signal.del(signo); - return any; -} - -static inline bool is_stop_sig(int sig) -{ - return sig == SIGSTOP || sig == SIGTSTP || - sig == SIGTTIN || sig == SIGTTOU; -} - -/* - * send_signal — queue a signal to a task. - * - * Acquires task->sighand->siglock for the full update (bitmap, queue, - * stop/cont side-effects, scheduler wake). Mirrors Linux's - * __send_signal_locked() which also requires this lock (see - * kernel/signal.c:1048 lockdep_assert_held). - * - * Returns 0 on success or a negative errno. - */ -int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task) -{ - if (sig < 1 || sig > _NSIG) return -EINVAL; - if (!task) return -ESRCH; - if (!task->sighand) return -ESRCH; /* eager-alloc invariant; tasks - * without sighand can't receive - * signals (idle / early kthreads) */ - - LockGuard guard(task->sighand->siglock); - - /* - * Linux's prepare_signal(): a stop signal cancels any pending - * SIGCONT and vice versa, so the most recent decision wins. - */ - if (is_stop_sig(sig)) { - discard_pending_locked(task, SIGCONT); - } else if (sig == SIGCONT) { - discard_pending_locked(task, SIGSTOP); - discard_pending_locked(task, SIGTSTP); - discard_pending_locked(task, SIGTTIN); - discard_pending_locked(task, SIGTTOU); - - /* SIGCONT's resume effect happens atomically with delivery. */ - if (task->state == TaskState::Stopped) { - task->state = TaskState::Runnable; - task->notify_continued = true; - task->stopped_code = 0; - sched_enqueue(task); - if (task->parent) task->parent->wait_chld.wake_all(); - } - } - - /* Already pending? For standard signals, just mark the bitmap */ - if (sig < SIGRTMIN && task->pending.signal.has(sig)) - return 0; - - /* Enqueue a sigqueue entry */ - auto* q = new sigqueue; - if (!q) { - /* Can't allocate — just set the bitmap (signal still delivered) */ - task->pending.signal.add(sig); - return 0; - } - - q->list.init(); - if (info) { - q->info = *info; - } else { - q->info.si_signo = sig; - q->info.si_errno = 0; - q->info.si_code = 0; /* SI_USER */ - } - q->info.si_signo = sig; /* ensure consistent */ - - /* Add to tail of queue */ - list_node* head = &task->pending.list; - q->list.prev = head->prev; - q->list.next = head; - head->prev->next = &q->list; - head->prev = &q->list; - - /* Set bitmap */ - task->pending.signal.add(sig); - - /* Wake the task if it's sleeping in an interruptible wait. */ - if (task->state == TaskState::Sleeping) { - task->state = TaskState::Runnable; - sched_enqueue(task); - } - - /* SIGKILL must run even if the target is Stopped. */ - if (sig == SIGKILL && task->state == TaskState::Stopped) { - task->state = TaskState::Runnable; - task->stopped_code = 0; - sched_enqueue(task); - } - - return 0; -} - -/* ================================================================== */ -/* force_sig_fault — trap handler API */ -/* ================================================================== */ - -void force_sig_fault(int signo, int code, uint64 addr) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return; - - /* Kernel threads cannot handle signals — kill immediately */ - if (curr->flags & TF_KTHREAD) { - pr_err("signal: kernel thread %s received signal %d\n", - curr->name ? curr->name : "(noname)", signo); - task_kill_current(signo); - return; - } - - /* Sighand must already exist for user-mode tasks (allocated at - * fork / kernel_execve time). If it's somehow missing, the only - * safe action is to terminate. */ - if (!curr->sighand) { - pr_err("signal: pid %d has no sighand, killing\n", curr->pid); - task_kill_current(signo); - return; - } - - kernel_siginfo info{}; - info.si_signo = signo; - info.si_code = code; - info._sifields._sigfault._addr = addr; - - /* Force: bypass blocked mask for fatal signals (lock to keep - * blocked-mask updates consistent with sigprocmask). */ - { - LockGuard guard(curr->sighand->siglock); - curr->blocked.del(signo); - } - - send_signal(signo, &info, curr); -} - -/* ================================================================== */ -/* signal_pending — check if unblocked signals are pending */ -/* ================================================================== */ - -bool signal_pending(TaskControlBlock* task) -{ - /* Advisory check (matches Linux's recalc_sigpending() usage): - * callers re-validate under siglock before acting. The bitmap is - * a uint64 word; a torn read can only produce false-positives or - * false-negatives transiently, never a corrupt pointer. */ - if (!task) return false; - sigset_t unblocked = task->pending.signal & ~task->blocked; - return !unblocked.is_empty(); -} - -/* ================================================================== */ -/* do_signal — called before returning to user mode */ -/* ================================================================== */ - -void do_signal(arch::TrapFrame* tf) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return; - - /* Kernel threads never deliver signals via frames */ - if (curr->flags & TF_KTHREAD) return; - - /* No sighand means no signal infrastructure for this task */ - if (!curr->sighand) return; - - while (true) { - kernel_siginfo info{}; - kernel_sigaction ka_copy; - int sig; - - /* - * Take siglock for dequeue + action snapshot. Action processing - * (default actions like SIG_ACT_TERM, or constructing a signal - * frame for a user handler) is done OUTSIDE the lock — it can - * touch user memory and even call schedule(). This matches - * Linux's get_signal() loop in kernel/signal.c: it takes - * siglock around dequeue_signal+ka_copy, drops it before - * dispatching, and re-acquires next iteration. - */ - { - LockGuard guard(curr->sighand->siglock); - sig = dequeue_signal_locked(curr, &info); - if (sig == 0) break; - ka_copy = curr->sighand->action[sig - 1]; - } - kernel_sigaction* ka = &ka_copy; - - /* SIG_IGN: discard */ - if (ka->sa_handler == SIG_IGN && !sig_kernel_only(sig)) - continue; - - /* SIG_DFL: apply default action */ - if (ka->sa_handler == SIG_DFL) { - SigDefaultAction act = sig_default_action(sig); - switch (act) { - case SIG_ACT_IGN: - continue; - case SIG_ACT_TERM: - case SIG_ACT_CORE: - pr_debug_s(KLOG_SUBSYS_SIGNAL, - "signal: pid %d killed by signal %d\n", - curr->pid, sig); - task_kill_current(sig); - return; /* never reached */ - case SIG_ACT_STOP: { - /* - * Enter Stopped state. Record the encoded wstatus so - * the parent's wait4(WUNTRACED) can observe and report. - * The schedule() below does NOT re-enqueue Stopped - * tasks — when SIGCONT eventually arrives, send_signal - * flips us back to Runnable and re-enqueues. - */ - curr->stopped_code = W_STOPCODE(sig); - curr->state = TaskState::Stopped; - if (curr->parent) - curr->parent->wait_chld.wake_all(); - pr_debug_s(KLOG_SUBSYS_SIGNAL, - "signal: pid %d stopped by sig %d\n", - curr->pid, sig); - schedule(); - /* - * Resumed (either by SIGCONT or SIGKILL). Loop back to - * deliver any remaining pending signals before returning - * to user mode. - */ - continue; - } - case SIG_ACT_CONT: - /* SIGCONT's resume side-effect already happened in - * send_signal(). The only thing left at handler time - * is to consume the signal (no-op for SIG_DFL). */ - continue; - } - } - - /* User handler: construct signal frame */ - pr_debug_s(KLOG_SUBSYS_SIGNAL, - "signal: pid %d delivering sig %d handler=%llx\n", - curr->pid, sig, reinterpret_cast(ka->sa_handler)); - int ret = setup_signal_frame(tf, sig, ka, &info); - if (ret < 0) { - pr_err("signal: failed to set up frame for sig %d\n", sig); - task_kill_current(sig); - return; - } - - /* Post-delivery bookkeeping under siglock — both the per-task - * blocked mask and the shared action table (SA_RESETHAND) are - * touched, so we must hold the same lock the senders use. */ - { - LockGuard guard(curr->sighand->siglock); - if (!(ka->sa_flags & SA_NODEFER)) curr->blocked.add(sig); - curr->blocked = curr->blocked | ka->sa_mask; - if (ka->sa_flags & SA_RESETHAND) { - curr->sighand->action[sig - 1].sa_handler = SIG_DFL; - } - } - - /* Only deliver one signal per return-to-user */ - break; - } -} diff --git a/na/kernel/smp.cc b/na/kernel/smp.cc deleted file mode 100644 index 1a79ac3..0000000 --- a/na/kernel/smp.cc +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * smp.cc — SMP IPI implementation - * - * Sending side: atomically set reason bits in target CPU's IpiData, - * then invoke arch-specific IPI delivery (SBI ecall on RISC-V). - * - * Receiving side: smp_handle_ipi() is called from the trap handler - * when an S-mode software interrupt arrives. It atomically consumes - * all pending reason bits and dispatches to the appropriate handler. - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.2 - */ - -#include -#include -#include -#include -#include -#include - -/* Per-CPU IPI data — BSS zero-initialized */ -IpiData g_ipi_data[kMaxCpus]; - -/* ------------------------------------------------------------------ */ -/* smp_init */ -/* ------------------------------------------------------------------ */ - -void smp_init() -{ - for (uint32 i = 0; i < kMaxCpus; i++) { - g_ipi_data[i].pending.store(0, __ATOMIC_RELAXED); - g_ipi_data[i].tlb_start = 0; - g_ipi_data[i].tlb_end = 0; - } - - printk(KERN_INFO "smp: IPI framework initialized (%u CPUs max)\n", - static_cast(kMaxCpus)); -} - -/* ------------------------------------------------------------------ */ -/* smp_send_ipi — send IPI to target CPUs */ -/* ------------------------------------------------------------------ */ - -void smp_send_ipi(uint64 cpu_mask, uint32 reason) -{ - if (cpu_mask == 0) return; - - /* Set pending bits on each target CPU's IpiData */ - for (uint32 cpu = 0; cpu < kMaxCpus; cpu++) { - if (cpu_mask & (1ULL << cpu)) { - g_ipi_data[cpu].pending.fetch_or(reason, __ATOMIC_RELEASE); - } - } - - /* Deliver hardware interrupt via SBI. - * RISC-V SBI IPI extension sends S-mode software interrupt - * to the specified harts. hart_mask maps 1:1 to cpu_mask - * (assuming hartid == cpu_id, which holds for QEMU virt). */ - arch::sbi::sbi_send_ipi(cpu_mask, 0); -} - -/* ------------------------------------------------------------------ */ -/* smp_handle_ipi — called from trap handler on software interrupt */ -/* ------------------------------------------------------------------ */ - -void smp_handle_ipi() -{ - uint32 cpu = static_cast(arch::cpu::id()); - - /* Clear the S-mode software interrupt pending bit (sip.SSIP). - * On RISC-V, we must clear this in software, otherwise the - * interrupt will keep firing. */ - arch::cpu::clear_sip_ssip(); - - /* Atomically consume all pending reasons */ - uint32 reasons = g_ipi_data[cpu].pending.exchange(0, __ATOMIC_ACQUIRE); - - if (reasons == 0) return; /* Spurious */ - - /* Dispatch each reason */ - - if (reasons & IPI_RESCHEDULE) { - /* Just need to return from interrupt — schedule() will be - * called on the return-from-trap path if need_resched is set. - * For now, set the flag by yielding on next opportunity. */ - /* TODO: set need_resched flag when preemption tracking is added */ - } - - if (reasons & IPI_TLB_SHOOTDOWN) { - /* Execute local TLB flush with the parameters the sender stored */ - uint64 start = g_ipi_data[cpu].tlb_start; - uint64 end = g_ipi_data[cpu].tlb_end; - if (start == 0 && end == 0) { - arch::mmu::flush_tlb_all(); - } else { - for (uint64 va = start; va < end; va += kPageSize) { - arch::mmu::flush_tlb_page(va); - } - } - } - - if (reasons & IPI_HALT) { - printk(KERN_INFO "smp: CPU %u halting by IPI\n", cpu); - arch::cpu::intr_off(); - while (true) { - arch::cpu::halt_until_interrupt(); - } - } -} diff --git a/na/kernel/sync/cpu_state.h b/na/kernel/sync/cpu_state.h deleted file mode 100644 index 1f7dde0..0000000 --- a/na/kernel/sync/cpu_state.h +++ /dev/null @@ -1,35 +0,0 @@ -/* 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 -#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/mutex.cc b/na/kernel/sync/mutex.cc deleted file mode 100644 index c4d3e9d..0000000 --- a/na/kernel/sync/mutex.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Mutex — Linux-style sleeping mutex (no PI). - * - * Implementation notes (mirroring kernel/locking/mutex.c, simplified): - * - * - Fast path (uncontended): wait_lock_ -> see owner_==null -> claim -> unlock. - * - Slow path (contended): enqueue MutexWaiter on stack, sleep, race for - * ownership after each wake. unlock() wakes only the head waiter. - * - No optimistic spin, no handoff anti-starvation, no interruptible wait. - * These belong to follow-up work (see mutex.md §5). - * - * Reference: docs/specs/mutex.md - */ - -#include -#include -#include -#include -#include - -namespace { - -struct MutexWaiter { - list_node link; - TaskControlBlock* task; -}; - -} // namespace - -/* ------------------------------------------------------------------ */ -/* init */ -/* ------------------------------------------------------------------ */ - -void Mutex::init(const char* name) -{ - owner_ = nullptr; - name_ = name; - wait_lock_.init(name != nullptr ? name : "mutex"); - wait_list_.init(); -} - -/* ------------------------------------------------------------------ */ -/* try_lock — non-blocking, safe from any context */ -/* ------------------------------------------------------------------ */ - -bool Mutex::try_lock() -{ - LockGuard g(wait_lock_); - if (owner_ != nullptr) { - return false; - } - owner_ = current_task(); - return true; -} - -/* ------------------------------------------------------------------ */ -/* lock — blocking, must be called in schedulable context */ -/* ------------------------------------------------------------------ */ - -void Mutex::lock() -{ - TaskControlBlock* curr = current_task(); - - if (curr == nullptr) { - kernel_panic("Mutex::lock: no current task"); - } - if (curr->preempt_count > 0) { - kernel_panic("Mutex::lock: preemption disabled (holding spinlock?)"); - } - - wait_lock_.acquire(); - - if (owner_ == nullptr) { - owner_ = curr; - wait_lock_.release(); - return; - } - - if (owner_ == curr) { - wait_lock_.release(); - kernel_panic("Mutex::lock: recursive acquire by same task"); - } - - /* Slow path: enqueue ourselves and sleep until we are head + lock free. */ - MutexWaiter waiter; - waiter.link.init(); - waiter.task = curr; - list_add_tail(&waiter.link, &wait_list_); - - for (;;) { - /* Set state inside wait_lock_ so unlock-side sees a coherent view: - * either we are on the list AND Sleeping (will be woken), or we - * already took ownership above (not in this loop). */ - curr->state = TaskState::Sleeping; - wait_lock_.release(); - - schedule(); - - wait_lock_.acquire(); - - if (owner_ == nullptr && wait_list_.next == &waiter.link) { - owner_ = curr; - list_del_init(&waiter.link); - wait_lock_.release(); - return; - } - /* Spurious wake / lost race — keep waiting. */ - } -} - -/* ------------------------------------------------------------------ */ -/* unlock */ -/* ------------------------------------------------------------------ */ - -void Mutex::unlock() -{ - TaskControlBlock* curr = current_task(); - - wait_lock_.acquire(); - - if (owner_ != curr) { - wait_lock_.release(); - kernel_panic("Mutex::unlock: not the owner"); - } - - owner_ = nullptr; - - if (!wait_list_.is_empty()) { - list_node* first = wait_list_.next; - MutexWaiter* w = list_entry(first); - TaskControlBlock* victim = w->task; - - /* Race-tolerant wake: only enqueue if still sleeping. If the - * waiter is already Runnable (e.g. it was already woken by a - * timer-driven schedule reentry and didn't yet retry the lock), - * sched_enqueue would double-link it. */ - if (victim->state == TaskState::Sleeping) { - victim->state = TaskState::Runnable; - sched_enqueue(victim); - } - } - - wait_lock_.release(); -} - -/* ------------------------------------------------------------------ */ -/* Debug accessors */ -/* ------------------------------------------------------------------ */ - -bool Mutex::is_owned_by_current() const -{ - return owner_ == current_task(); -} diff --git a/na/kernel/sync/spinlock.cc b/na/kernel/sync/spinlock.cc deleted file mode 100644 index f649fe7..0000000 --- a/na/kernel/sync/spinlock.cc +++ /dev/null @@ -1,120 +0,0 @@ -/* 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 -#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_.store(0, __ATOMIC_RELAXED); - 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 (locked_.load(__ATOMIC_RELAXED)) - ; - // Inner: attempt atomic exchange with acquire semantics - if (locked_.exchange(1, __ATOMIC_ACQUIRE) == 0) - break; - } - - // Step 4: record owner - cpu_id_ = arch::cpu::id(); - - // Step 5: bump preempt_count — prevents schedule() from preempting - TaskControlBlock* curr = current_task(); - if (curr) curr->preempt_count++; -} - -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 - locked_.store(0, __ATOMIC_RELEASE); - - // Step 4: drop preempt_count - TaskControlBlock* curr = current_task(); - if (curr) curr->preempt_count--; - - // Step 5: 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 deleted file mode 100644 index a1369d1..0000000 --- a/na/kernel/sync/spinlock.h +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 -#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: - lume::atomic locked_; // 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/kernel/sys_fs.cc b/na/kernel/sys_fs.cc deleted file mode 100644 index 1d26fdd..0000000 --- a/na/kernel/sys_fs.cc +++ /dev/null @@ -1,679 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_fs.cc — Filesystem-related syscall handlers - * - * Implements: openat, close, read, write, lseek, fstat, getdents64, - * dup, dup3, getcwd, chdir, mkdirat, unlinkat - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ================================================================== */ -/* Helpers */ -/* ================================================================== */ - -static files_struct* current_files() -{ - TaskControlBlock* t = current_task(); - return t ? t->files : nullptr; -} - -static fs_struct* current_fs() -{ - TaskControlBlock* t = current_task(); - return t ? t->fs : nullptr; -} - -/* ================================================================== */ -/* sys_openat(dirfd, pathname, flags, mode) */ -/* ================================================================== */ - -int64 sys_openat(uint64 a0, uint64 a1, uint64 a2, - uint64 a3, uint64 /*a4*/, uint64 /*a5*/) -{ - int dirfd = static_cast(a0); - const char* pathname = reinterpret_cast(a1); - int flags = static_cast(a2); - uint32 mode = static_cast(a3); - - files_struct* files = current_files(); - if (!files) return -EMFILE; - - - /* Pathname lookup */ - path p{}; - int err = filename_lookup(dirfd, pathname, 0, &p); - - if (err == -ENOENT && (flags & O_CREAT)) { - /* Create: look up the parent directory */ - /* Find last '/' to split parent path / filename */ - const char* last_slash = nullptr; - for (const char* s = pathname; *s; s++) { - if (*s == '/') last_slash = s; - } - - if (!last_slash || last_slash == pathname) { - /* File in root or current directory */ - path parent{}; - if (last_slash == pathname) { - /* Absolute: parent is root */ - parent.de = g_root_dentry; - parent.mnt = g_root_mnt; - } else { - /* Relative: parent is cwd */ - fs_struct* fs = current_fs(); - if (!fs) return -ENOENT; - parent = fs->pwd; - } - - const char* name = last_slash ? last_slash + 1 : pathname; - inode* dir_ip = parent.de->d_inode; - if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->create) { - return -EACCES; - } - - /* Create the inode via the directory's i_op->create */ - qstr q{}; - q.name = name; - q.len = 0; - for (const char* s = name; *s; s++) q.len++; - dentry* new_de = d_alloc(parent.de, &q); - if (!new_de) return -ENOMEM; - - - err = dir_ip->i_op->create(dir_ip, new_de, mode & ~S_IFMT, false); - if (err) { - /* TODO: free new_de */ - return err; - } - - p.de = new_de; - p.mnt = parent.mnt; - } else { - /* TODO: multi-component parent lookup */ - return -ENOENT; - } - } else if (err) { - return err; - } - - /* Allocate fd */ - int fd = alloc_fd(files, 0); - if (fd < 0) { - return fd; - } - - /* Allocate file object */ - file* fp = alloc_empty_file(flags); - if (!fp) { - /* TODO: release fd */ - return -ENFILE; - } - - fp->f_inode = p.de->d_inode; - fp->f_path = p; - fp->f_op = p.de->d_inode->i_fop; - fp->f_pos = 0; - - /* Chardev: look up cdev by i_rdev and replace f_op */ - if (S_ISCHR(fp->f_inode->i_mode)) { - extern int devfs_chrdev_open(inode* ip, file* fp); - err = devfs_chrdev_open(fp->f_inode, fp); - if (err) { - fput(fp); - return err; - } - } else if (fp->f_op && fp->f_op->open) { - err = fp->f_op->open(fp->f_inode, fp); - if (err) { - fput(fp); - return err; - } - } - - /* O_TRUNC for regular files */ - if ((flags & O_TRUNC) && S_ISREG(fp->f_inode->i_mode)) { - fp->f_inode->i_size = 0; - } - - fd_install(files, fd, fp); - return fd; -} - -/* ================================================================== */ -/* sys_close(fd) */ -/* ================================================================== */ - -int64 sys_close(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - files_struct* files = current_files(); - if (!files) return -EBADF; - return close_fd(files, fd); -} - -/* ================================================================== */ -/* sys_read(fd, buf, count) */ -/* ================================================================== */ - -int64 sys_read(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - char* buf = reinterpret_cast(a1); - uint64 count = a2; - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - if (!(fp->f_mode & FMODE_READ)) return -EBADF; - - return vfs_read(fp, buf, count, &fp->f_pos); -} - -/* ================================================================== */ -/* sys_write(fd, buf, count) */ -/* ================================================================== */ - -int64 sys_write(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - const char* buf = reinterpret_cast(a1); - uint64 count = a2; - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - if (!(fp->f_mode & FMODE_WRITE)) return -EBADF; - - return vfs_write(fp, buf, count, &fp->f_pos); -} - -/* ================================================================== */ -/* sys_lseek(fd, offset, whence) */ -/* ================================================================== */ - -int64 sys_lseek(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - int64 offset = static_cast(a1); - int whence = static_cast(a2); - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - return vfs_llseek(fp, offset, whence); -} - -/* ================================================================== */ -/* sys_fstat(fd, statbuf) */ -/* ================================================================== */ - -int64 sys_fstat_wrapper(uint64 a0, uint64 a1, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - auto* statbuf = reinterpret_cast(a1); - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - return vfs_fstat(fp, statbuf); -} - -/* ================================================================== */ -/* sys_getdents64(fd, dirp, count) */ -/* ================================================================== */ - -struct linux_dirent64 { - uint64 d_ino; - int64 d_off; - uint16 d_reclen; - uint8 d_type; - char d_name[]; -}; - -struct getdents_ctx { - dir_context ctx; - char* buf; - int count; - int written; - int error; -}; - -static bool filldir64(dir_context* ctx, const char* name, int namelen, - int64 offset, uint64 ino, unsigned int d_type) -{ - auto* gctx = container_of(ctx, getdents_ctx, ctx); - - /* Calculate record length (rounded up to 8 bytes) */ - int reclen = static_cast( - (sizeof(linux_dirent64) + namelen + 1 + 7) & ~7ULL); - - if (gctx->written + reclen > gctx->count) { - return false; /* Buffer full */ - } - - auto* dirent = reinterpret_cast( - gctx->buf + gctx->written); - - dirent->d_ino = ino; - dirent->d_off = offset + 1; - dirent->d_reclen = static_cast(reclen); - dirent->d_type = static_cast(d_type); - - /* Copy name */ - for (int i = 0; i < namelen; i++) { - dirent->d_name[i] = name[i]; - } - dirent->d_name[namelen] = '\0'; - - gctx->written += reclen; - return true; -} - -int64 sys_getdents64(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - char* buf = reinterpret_cast(a1); - int count = static_cast(a2); - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - if (!fp->f_op || !fp->f_op->iterate_shared) { - return -ENOTDIR; - } - - getdents_ctx gctx{}; - gctx.ctx.actor = filldir64; - gctx.ctx.pos = fp->f_pos; - gctx.buf = buf; - gctx.count = count; - gctx.written = 0; - gctx.error = 0; - - fp->f_op->iterate_shared(fp, &gctx.ctx); - fp->f_pos = gctx.ctx.pos; - - return gctx.written; -} - -/* ================================================================== */ -/* sys_dup(oldfd) */ -/* ================================================================== */ - -int64 sys_dup(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int oldfd = static_cast(a0); - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, oldfd); - if (!fp) return -EBADF; - - int newfd = alloc_fd(files, 0); - if (newfd < 0) return newfd; - - get_file(fp); - fd_install(files, newfd, fp); - return newfd; -} - -/* ================================================================== */ -/* sys_dup3(oldfd, newfd, flags) */ -/* ================================================================== */ - -int64 sys_dup3(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int oldfd = static_cast(a0); - int newfd = static_cast(a1); - int flags = static_cast(a2); - - files_struct* files = current_files(); - if (!files) return -EBADF; - - return do_dup2(files, oldfd, newfd, flags); -} - -/* ================================================================== */ -/* sys_getcwd(buf, size) */ -/* ================================================================== */ - -int64 sys_getcwd(uint64 a0, uint64 a1, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - char* buf = reinterpret_cast(a0); - uint64 size = a1; - - if (!buf || size == 0) return -EINVAL; - - fs_struct* fs = current_fs(); - if (!fs) return -ENOENT; - - char tmp[256]; - char* result = d_path(&fs->pwd, tmp, sizeof(tmp)); - if (!result) return -ERANGE; - - /* Calculate length */ - int len = 0; - for (const char* s = result; *s; s++) len++; - len++; /* Include NUL */ - - if (static_cast(len) > size) return -ERANGE; - - for (int i = 0; i < len; i++) { - buf[i] = result[i]; - } - - return len; -} - -/* ================================================================== */ -/* sys_chdir(pathname) */ -/* ================================================================== */ - -int64 sys_chdir(uint64 a0, uint64 /*a1*/, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - const char* pathname = reinterpret_cast(a0); - - path p{}; - int err = filename_lookup(AT_FDCWD, pathname, LOOKUP_DIRECTORY, &p); - if (err) return err; - - if (!S_ISDIR(p.de->d_inode->i_mode)) { - path_put(&p); - return -ENOTDIR; - } - - fs_struct* fs = current_fs(); - if (!fs) { - path_put(&p); - return -ENOENT; - } - - set_fs_pwd(fs, &p); - path_put(&p); - return 0; -} - -/* ================================================================== */ -/* sys_mkdirat(dirfd, pathname, mode) */ -/* ================================================================== */ - -int64 sys_mkdirat(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int dirfd = static_cast(a0); - const char* pathname = reinterpret_cast(a1); - uint32 mode = static_cast(a2); - - /* Walk to the parent directory */ - /* For simplicity, support only "single component" paths for now */ - path parent{}; - int err; - - if (pathname[0] == '/') { - parent.de = g_root_dentry; - parent.mnt = g_root_mnt; - pathname++; /* skip leading / */ - } else { - fs_struct* fs = current_fs(); - if (!fs) return -ENOENT; - parent = fs->pwd; - (void)dirfd; /* TODO: respect dirfd for AT_FDCWD check */ - } - - inode* dir_ip = parent.de->d_inode; - if (!dir_ip || !dir_ip->i_op || !dir_ip->i_op->mkdir) { - return -EPERM; - } - - qstr q{}; - q.name = pathname; - q.len = 0; - for (const char* s = pathname; *s; s++) q.len++; - dentry* new_de = d_alloc(parent.de, &q); - if (!new_de) return -ENOMEM; - - err = dir_ip->i_op->mkdir(dir_ip, new_de, mode | S_IFDIR); - if (err) { - return err; - } - - return 0; -} - -/* ================================================================== */ -/* sys_unlinkat(dirfd, pathname, flags) */ -/* ================================================================== */ - -int64 sys_unlinkat(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - (void)a0; (void)a1; (void)a2; - /* TODO: implement unlink/rmdir */ - return -ENOSYS; -} - -/* ================================================================== */ -/* sys_writev(fd, iov, iovcnt) */ -/* ================================================================== */ - -struct iovec { - void* iov_base; - uint64 iov_len; -}; - -int64 sys_writev(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - const iovec* iov = reinterpret_cast(a1); - int iovcnt = static_cast(a2); - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - if (!(fp->f_mode & FMODE_WRITE)) return -EBADF; - - int64 total = 0; - for (int i = 0; i < iovcnt; i++) { - if (iov[i].iov_len == 0) continue; - int64 ret = vfs_write(fp, static_cast(iov[i].iov_base), - iov[i].iov_len, &fp->f_pos); - if (ret < 0) { - return total > 0 ? total : ret; - } - total += ret; - if (static_cast(ret) < iov[i].iov_len) break; - } - return total; -} - -/* ================================================================== */ -/* sys_ioctl — dispatch to file_operations */ -/* ================================================================== */ - -int64 sys_ioctl(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - unsigned int cmd = static_cast(a1); - uint64 arg = a2; - - TaskControlBlock* curr = current_task(); - if (!curr || !curr->files) return -EBADF; - - file* fp = fcheck(curr->files, fd); - if (!fp) return -EBADF; - - if (fp->f_op && fp->f_op->unlocked_ioctl) - return fp->f_op->unlocked_ioctl(fp, cmd, arg); - - return -ENOTTY; -} - -/* ================================================================== */ -/* sys_pipe2(fds, flags) */ -/* ================================================================== */ - -#include - -int64 sys_pipe2(uint64 a0, uint64 a1, uint64 /*a2*/, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int* user_fds = reinterpret_cast(a0); - int flags = static_cast(a1); - - files_struct* files = current_files(); - if (!files) return -EMFILE; - - int fds[2]; - int err = do_pipe2(files, fds, flags); - if (err) return err; - - user_fds[0] = fds[0]; - user_fds[1] = fds[1]; - return 0; -} - -/* ================================================================== */ -/* sys_fcntl(fd, cmd, arg) */ -/* ================================================================== */ - -/* fcntl commands — Linux ABI values */ -inline constexpr int F_DUPFD = 0; -inline constexpr int F_GETFD = 1; -inline constexpr int F_SETFD = 2; -inline constexpr int F_GETFL = 3; -inline constexpr int F_SETFL = 4; -inline constexpr int F_DUPFD_CLOEXEC = 1030; - -inline constexpr int FD_CLOEXEC = 1; - -/* Bitmap helpers (duplicated from fd.cc — TODO: move to shared header) */ -static inline bool fcntl_bit_test(const uint64* bmap, int bit) { - return (bmap[bit / 64] >> (bit % 64)) & 1; -} -static inline void fcntl_bit_set(uint64* bmap, int bit) { - bmap[bit / 64] |= (1ULL << (bit % 64)); -} -static inline void fcntl_bit_clear(uint64* bmap, int bit) { - bmap[bit / 64] &= ~(1ULL << (bit % 64)); -} - -int64 sys_fcntl(uint64 a0, uint64 a1, uint64 a2, - uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) -{ - int fd = static_cast(a0); - int cmd = static_cast(a1); - uint64 arg = a2; - - files_struct* files = current_files(); - if (!files) return -EBADF; - - file* fp = fcheck(files, fd); - if (!fp) return -EBADF; - - switch (cmd) { - case F_DUPFD: - case F_DUPFD_CLOEXEC: { - int new_fd = alloc_fd(files, static_cast(arg)); - if (new_fd < 0) return new_fd; - get_file(fp); - fd_install(files, new_fd, fp); - if (cmd == F_DUPFD_CLOEXEC) { - LockGuard guard(files->file_lock); - fcntl_bit_set(files->close_on_exec_init, new_fd); - } - return new_fd; - } - - case F_GETFD: { - LockGuard guard(files->file_lock); - return fcntl_bit_test(files->close_on_exec_init, fd) ? FD_CLOEXEC : 0; - } - - case F_SETFD: { - LockGuard guard(files->file_lock); - if (arg & FD_CLOEXEC) - fcntl_bit_set(files->close_on_exec_init, fd); - else - fcntl_bit_clear(files->close_on_exec_init, fd); - return 0; - } - - case F_GETFL: - return fp->f_flags; - - case F_SETFL: - /* Only allow changing O_APPEND, O_NONBLOCK, O_ASYNC */ - fp->f_flags = (fp->f_flags & ~(O_APPEND | O_NONBLOCK)) | - (static_cast(arg) & (O_APPEND | O_NONBLOCK)); - return 0; - - default: - return -EINVAL; - } -} - -/* ================================================================== */ -/* sys_socket (SYS_socket = 198) & sys_sendto (SYS_sendto = 206) */ -/* ================================================================== */ - -int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64) -{ - /* - * COMPATIBILITY STUB: - * LumeOS currently lacks a networking stack and Unix Domain Sockets (AF_UNIX). - * We return -EAFNOSUPPORT (Address family not supported by protocol) as expected - * by Linux applications (like BusyBox syslogd connecting to /dev/log). - */ - return -EAFNOSUPPORT; -} - -int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64) -{ - /* COMPATIBILITY STUB */ - return -EAFNOSUPPORT; -} diff --git a/na/kernel/sys_info.cc b/na/kernel/sys_info.cc deleted file mode 100644 index 79baab0..0000000 --- a/na/kernel/sys_info.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * System Information Calls - * - * Implements: uname - * - * Reference: docs/specs/syscall.md §7.3 - */ - -#include -#include -#include - -/* Linux-compatible utsname structure (65-byte fields) */ -struct utsname { - char sysname[65]; - char nodename[65]; - char release[65]; - char version[65]; - char machine[65]; - char domainname[65]; -}; - -/* Simple safe string copy */ -static void safe_strncpy(char* dst, const char* src, uint64 n) -{ - uint64 i = 0; - for (; i < n - 1 && src[i] != '\0'; i++) - dst[i] = src[i]; - dst[i] = '\0'; -} - -int64 sys_uname(uint64 buf_ptr, uint64, uint64, uint64, uint64, uint64) -{ - utsname info = {}; - safe_strncpy(info.sysname, "LumeOS", sizeof(info.sysname)); - safe_strncpy(info.nodename, "lume", sizeof(info.nodename)); - safe_strncpy(info.release, "0.1.0", sizeof(info.release)); - safe_strncpy(info.version, "Phase 2", sizeof(info.version)); - safe_strncpy(info.machine, "riscv64", sizeof(info.machine)); - safe_strncpy(info.domainname, "(none)", sizeof(info.domainname)); - - if (copy_to_user(buf_ptr, &info, sizeof(info)) < 0) - return -EFAULT; - - return 0; -} diff --git a/na/kernel/sys_misc.cc b/na/kernel/sys_misc.cc deleted file mode 100644 index 48eb47b..0000000 --- a/na/kernel/sys_misc.cc +++ /dev/null @@ -1,360 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Miscellaneous System Calls — busybox bootstrap set - * - * Each syscall follows the Linux kernel semantics exactly. - * Reference: Linux kernel/sys.c, kernel/signal.c, kernel/time/posix-timers.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ====================================================================== - * sys_reboot — Linux kernel/reboot.c - * - * Validates magic numbers and performs the requested action. - * For now, we only support LINUX_REBOOT_CMD_CAD_ON/OFF (ignore) - * and reject everything else with -EPERM (no CAP_SYS_BOOT). - * ====================================================================== */ - -static constexpr uint32 LINUX_REBOOT_MAGIC1 = 0xfee1dead; -static constexpr uint32 LINUX_REBOOT_MAGIC2 = 672274793; /* 0x28121969 */ -static constexpr uint32 LINUX_REBOOT_MAGIC2A = 85072278; -static constexpr uint32 LINUX_REBOOT_MAGIC2B = 369367448; -static constexpr uint32 LINUX_REBOOT_MAGIC2C = 537993216; - -int64 sys_reboot(uint64 magic1, uint64 magic2, uint64 cmd, - uint64, uint64, uint64) -{ - /* Linux: kernel/reboot.c — validate magic numbers first */ - if (static_cast(magic1) != LINUX_REBOOT_MAGIC1) - return -EINVAL; - if (static_cast(magic2) != LINUX_REBOOT_MAGIC2 && - static_cast(magic2) != LINUX_REBOOT_MAGIC2A && - static_cast(magic2) != LINUX_REBOOT_MAGIC2B && - static_cast(magic2) != LINUX_REBOOT_MAGIC2C) - return -EINVAL; - - /* We don't have CAP_SYS_BOOT yet — reject with -EPERM. - * This matches Linux behavior for unprivileged callers. - * Busybox handles this gracefully and continues. */ - (void)cmd; - printk(KERN_INFO "reboot: cmd=%llu (not implemented, returning -EPERM)\n", cmd); - return -EPERM; -} - -/* ====================================================================== - * sys_prctl — Linux kernel/sys.c - * - * Process control. Most options are optional for early boot. - * We implement the minimum set busybox needs. - * ====================================================================== */ - -/* prctl option constants (from ) */ -static constexpr int PR_SET_NAME = 15; -static constexpr int PR_GET_NAME = 16; -static constexpr int PR_SET_PDEATHSIG = 1; -static constexpr int PR_GET_PDEATHSIG = 2; - -int64 sys_prctl(uint64 option, uint64 arg2, uint64 arg3, - uint64 arg4, uint64 arg5, uint64) -{ - switch (static_cast(option)) { - case PR_SET_PDEATHSIG: - /* Linux: set signal delivered when parent dies. - * Silently accept — we don't track pdeath_signal yet. */ - return 0; - case PR_GET_PDEATHSIG: { - /* Linux: return current pdeath_signal (0 = none) */ - int32 zero = 0; - if (copy_to_user(arg2, &zero, sizeof(zero)) != 0) - return -EFAULT; - return 0; - } - case PR_SET_NAME: { - /* Linux: set comm (task name), max 16 chars */ - /* TODO: copy_from_user the string */ - return 0; - } - case PR_GET_NAME: { - /* Linux: get comm (task name) */ - TaskControlBlock* curr = current_task(); - if (!curr) return -ESRCH; - const char* name = curr->name ? curr->name : ""; - uint64 len = 0; - while (name[len] && len < 15) len++; - char buf[16] = {}; - for (uint64 i = 0; i < len; i++) buf[i] = name[i]; - if (copy_to_user(arg2, buf, 16) != 0) - return -EFAULT; - return 0; - } - default: - /* Unknown prctl — Linux returns -EINVAL for unrecognized options */ - return -EINVAL; - } -} - -/* ====================================================================== - * Session / Process-Group syscalls - * - * All four operate on the caller's signal_struct (or a target task's). - * Identity lives in signal_struct, so these become straightforward reads - * and pgrp-hash updates. We mirror Linux kernel/sys.c semantics closely. - * ====================================================================== */ - -static TaskControlBlock* find_task_by_pid(int32 pid) -{ - extern list_node g_all_tasks; - extern Spinlock g_task_list_lock; - LockGuard guard(g_task_list_lock); - list_node* node = g_all_tasks.next; - while (node != &g_all_tasks) { - auto* task = list_entry(node); - if (task->pid == pid) return task; - node = node->next; - } - return nullptr; -} - -/* - * sys_setsid — Linux kernel/sys.c:ksys_setsid() - * - * Caller becomes leader of a new session+pgrp. Refused if a pgrp with - * id == caller pid already exists with members other than the caller — - * that's Linux's "process group id already exists" check, which prevents - * collisions when a non-leader tries to setsid. - */ -int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ESRCH; - - /* Detect collision: another pgrp already uses our pid as pgid. */ - bool conflict = false; - for_each_pgrp_member(curr->pid, [&](signal_struct* sig) { - if (sig != curr->signal) conflict = true; - }); - if (conflict) return -EPERM; - - /* Move to new pgrp (creates one if missing), update sid, drop ctty. */ - change_pgid(curr->signal, curr->pid); - - /* Detach the controlling terminal. If this task is the session - * leader that owns the tty (tty->session == caller's old sid), - * also clear the tty's session/pgrp so the next session-leader - * that opens the device can claim it. Without this, tty->session - * permanently sticks to the abandoned sid and every subsequent - * tty_acquire_ctty() bails with "owned by another session" — - * which is exactly what kept BusyBox's child sh from picking the - * console back up after init's setsid(). - * - * Linux's behavior is similar in spirit (disassociate_ctty when - * the leader exits), but it triggers from exit, not setsid. Our - * simpler "clear-on-give-up" matches the way our acquisition path - * is written. */ - tty_struct* old_tty = nullptr; - int32 old_sid = 0; - { - LockGuard guard(curr->signal->lock); - old_tty = curr->signal->tty; - old_sid = curr->signal->sid; - curr->signal->sid = curr->pid; - curr->signal->tty = nullptr; - curr->signal->tty_old_pgrp = 0; - } - if (old_tty) { - LockGuard guard(old_tty->lock); - if (old_tty->session == old_sid) { - old_tty->session = 0; - old_tty->pgrp = 0; - } - } - return curr->pid; -} - -/* - * sys_setpgid — Linux kernel/sys.c:ksys_setpgid() - * - * Move a process between groups within the same session. Allowed: - * - caller targeting self or a child - * - target is not a session leader - * - target stays in caller's session - * - target hasn't exec()'d (we don't track did_exec yet, so skip) - * - new pgid is either == target's pid (new group) or names a pgrp - * that already exists in the same session - */ -int64 sys_setpgid(uint64 pid, uint64 pgid, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ESRCH; - - int32 target_pid = static_cast(pid); - int32 target_pgid = static_cast(pgid); - - if (target_pid == 0) target_pid = curr->pid; - if (target_pgid == 0) target_pgid = target_pid; - if (target_pgid < 0) return -EINVAL; - - TaskControlBlock* target = (target_pid == curr->pid) - ? curr - : find_task_by_pid(target_pid); - if (!target || !target->signal) return -ESRCH; - - /* Target must be caller or a child of caller. */ - if (target != curr && target->parent != curr) return -ESRCH; - - /* Same session as caller. */ - if (target->signal->sid != curr->signal->sid) return -EPERM; - - /* Cannot move a session leader into a different pgrp. */ - if (target->signal->sid == target->pid) return -EPERM; - - /* If target_pgid != target_pid, the pgrp must already exist in - * the same session. pgrp existence == any signal_struct with - * pgid == target_pgid AND sid == caller's sid. */ - if (target_pgid != target->pid) { - bool found = false; - for_each_pgrp_member(target_pgid, [&](signal_struct* sig) { - if (sig->sid == curr->signal->sid) found = true; - }); - if (!found) return -EPERM; - } - - change_pgid(target->signal, target_pgid); - return 0; -} - -/* - * sys_getpgid — return process group ID of `pid` (0 = self). - */ -int64 sys_getpgid(uint64 pid, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ESRCH; - - if (pid == 0) return curr->signal->pgid; - - TaskControlBlock* target = find_task_by_pid(static_cast(pid)); - if (!target || !target->signal) return -ESRCH; - return target->signal->pgid; -} - -/* - * sys_getsid — return session ID of `pid` (0 = self). - */ -int64 sys_getsid(uint64 pid, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->signal) return -ESRCH; - - if (pid == 0) return curr->signal->sid; - - TaskControlBlock* target = find_task_by_pid(static_cast(pid)); - if (!target || !target->signal) return -ESRCH; - - /* Linux: -EPERM if target's session differs from caller's and we're - * in a different namespace. We have one namespace, so always allow. */ - return target->signal->sid; -} - -/* ====================================================================== - * sys_geteuid / sys_getegid — Linux kernel/sys.c - * - * Return effective UID/GID. We run everything as root (0). - * ====================================================================== */ - -int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - /* Linux: return current_euid(). We are always root. */ - return 0; -} - -int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - /* Linux: return current_egid(). We are always root. */ - return 0; -} - -/* ====================================================================== - * sys_sigaltstack — Linux kernel/signal.c - * - * Set/get alternate signal stack. For now, accept and ignore - * (busybox doesn't critically need it for shell operation). - * ====================================================================== */ - -int64 sys_sigaltstack(uint64 uss_ptr, uint64 uoss_ptr, - uint64, uint64, uint64, uint64) -{ - /* Linux: if uoss != NULL, return current alt stack info. - * We don't have alt stacks yet — report SS_DISABLE. */ - if (uoss_ptr != 0) { - /* stack_t { void* ss_sp; int ss_flags; size_t ss_size; } - * On RISC-V LP64: 8 + 4 + padding + 8 = 24 bytes typically, - * but the ABI packs it as {uint64, int32, pad, uint64}. */ - struct { - uint64 ss_sp; - int32 ss_flags; - int32 _pad; - uint64 ss_size; - } oss = { 0, 2 /* SS_DISABLE */, 0, 0 }; - if (copy_to_user(uoss_ptr, &oss, sizeof(oss)) != 0) - return -EFAULT; - } - - /* uss: silently accept (we'll need it for proper signal handling later) */ - (void)uss_ptr; - return 0; -} - -/* ====================================================================== - * sys_clock_gettime — Linux kernel/time/posix-timers.c - * - * Get the current time for a given clock. - * We use the RISC-V cycle counter as a monotonic source. - * ====================================================================== */ - -static constexpr int CLOCK_REALTIME = 0; -static constexpr int CLOCK_MONOTONIC = 1; - -int64 sys_clock_gettime(uint64 clk_id, uint64 tp_ptr, - uint64, uint64, uint64, uint64) -{ - if (tp_ptr == 0) return -EFAULT; - - /* Read RISC-V time CSR (mtime mapped to SBI) */ - uint64 ticks = arch::cpu::read_time(); - - /* Convert to seconds + nanoseconds. - * QEMU virt timebase-frequency is 10MHz. */ - constexpr uint64 kTimebaseHz = 10'000'000ULL; - uint64 sec = ticks / kTimebaseHz; - uint64 nsec = (ticks % kTimebaseHz) * (1'000'000'000ULL / kTimebaseHz); - - struct timespec64 { - int64 tv_sec; - int64 tv_nsec; - }; - - timespec64 ts; - ts.tv_sec = static_cast(sec); - ts.tv_nsec = static_cast(nsec); - - (void)clk_id; /* We treat all clocks as monotonic for now */ - - if (copy_to_user(tp_ptr, &ts, sizeof(ts)) != 0) - return -EFAULT; - - return 0; -} diff --git a/na/kernel/sys_mm.cc b/na/kernel/sys_mm.cc deleted file mode 100644 index bfa067c..0000000 --- a/na/kernel/sys_mm.cc +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Memory Management System Calls - * - * Implements: brk, mmap (anonymous only), munmap, mprotect. - * - * Reference: docs/specs/syscall.md §7.2 - */ - -#include -#include -#include -#include -#include -#include -#include - - -/* Page alignment helpers */ -static inline uint64 page_align_up(uint64 x) -{ - return (x + kPageSize - 1) & ~(kPageSize - 1); -} - -static inline uint64 page_align_down(uint64 x) -{ - return x & ~(kPageSize - 1); -} - -/* ====================================================================== - * sys_brk - * ====================================================================== */ - -int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64) -{ - VmSpace* mm = current_task()->mm; - if (!mm) - return -ENOMEM; /* kernel threads have no user address space */ - - if (new_brk == 0) { - return static_cast(mm->brk_end); - } - - if (mm->brk_start == 0) { - uint64 init_brk = page_align_up(new_brk); - mm->brk_start = init_brk; - mm->brk_end = init_brk; - } - - if (new_brk < mm->brk_start) { - return -EINVAL; - } - - uint64 new_end = page_align_up(new_brk); - if (new_end > mm->brk_end) { - uint64 grow = new_end - mm->brk_end; - int ret = vmm_map_user(mm, mm->brk_end, grow, VM_READ | VM_WRITE); - if (ret < 0) { - return ret; - } - } else if (new_end < mm->brk_end) { - uint64 shrink = mm->brk_end - new_end; - int ret = vmm_unmap_user(mm, new_end, shrink); - if (ret < 0) { - return ret; - } - } - - mm->brk_end = new_end; - return static_cast(new_brk); -} - -/* ====================================================================== - * sys_mmap - * ====================================================================== */ - -int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, - uint64 flags, uint64 fd, uint64 offset) -{ - printk("[mmap] addr=%llx len=%llx prot=%llx flags=%llx fd=%lld off=%llx\n", - addr, length, prot, flags, static_cast(fd), offset); - - if (length == 0) return -EINVAL; - length = page_align_up(length); - if (offset % kPageSize != 0) return -EINVAL; - - if ((flags & MAP_PRIVATE) && (flags & MAP_SHARED)) return -EINVAL; - if (!(flags & MAP_PRIVATE) && !(flags & MAP_SHARED)) return -EINVAL; - - VmSpace* mm = current_task()->mm; - if (!mm) return -ENOMEM; - - /* Phase 2: only anonymous private mappings */ - if (!(flags & MAP_ANONYMOUS)) { - printk("[mmap] REJECTED: file-backed mmap not implemented\n"); - return -ENOSYS; /* File-backed mmap is Phase 3 */ - } - if (flags & MAP_SHARED) - return -ENOSYS; /* Shared mappings are Phase 3 */ - - (void)fd; - - /* Convert POSIX prot → MI permission flags */ - uint64 vm_perm = 0; - if (prot & PROT_READ) vm_perm |= VM_READ; - if (prot & PROT_WRITE) vm_perm |= VM_WRITE; - if (prot & PROT_EXEC) vm_perm |= VM_EXEC; - vm_perm |= VM_USER; - - uint64 va = addr; - if (flags & MAP_FIXED) { - if (va % kPageSize != 0) return -EINVAL; - } else if (va != 0) { - va = page_align_down(va); - } - - if (va == 0) { - va = mm_find_free_va(mm, length, kPageSize); - if (!va) return -ENOMEM; - } - - if (flags & MAP_FIXED) { - int ret = vmm_unmap_user(mm, va, length); - if (ret < 0) return ret; - } - - int ret = vmm_map_user(mm, va, length, vm_perm); - if (ret < 0 && !(flags & MAP_FIXED) && addr != 0) { - uint64 fallback = mm_find_free_va(mm, length, kPageSize); - if (!fallback) return ret; - ret = vmm_map_user(mm, fallback, length, vm_perm); - if (ret < 0) return ret; - va = fallback; - } else if (ret < 0) { - return ret; - } - - return static_cast(va); -} - -/* ====================================================================== - * sys_munmap - * ====================================================================== */ - -int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64) -{ - if (length == 0) return -EINVAL; - if (addr % kPageSize != 0) return -EINVAL; - length = page_align_up(length); - - VmSpace* mm = current_task()->mm; - if (!mm) return -EINVAL; - - return vmm_unmap_user(mm, addr, length); -} - -/* ====================================================================== - * sys_mprotect — Phase 2 stub - * ====================================================================== */ - -int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, - uint64, uint64, uint64) -{ - if (len == 0) return -EINVAL; - if (addr % kPageSize != 0) return -EINVAL; - - VmSpace* mm = current_task()->mm; - if (!mm) return -ENOMEM; - - uint64 vm_perm = 0; - if (prot & PROT_READ) vm_perm |= VM_READ; - if (prot & PROT_WRITE) vm_perm |= VM_WRITE; - if (prot & PROT_EXEC) vm_perm |= VM_EXEC; - - return vmm_protect_user(mm, addr, len, vm_perm); -} diff --git a/na/kernel/sys_process.cc b/na/kernel/sys_process.cc deleted file mode 100644 index 438ad1b..0000000 --- a/na/kernel/sys_process.cc +++ /dev/null @@ -1,185 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Process Control System Calls - * - * Implements: exit, exit_group, getpid, gettid, getppid, - * sched_yield, clone (stub), wait4 (stub), - * set_tid_address, getuid, getgid. - * - * Reference: docs/specs/syscall.md §7.1 - */ - -#include "lume/klog.h" -#include -#include -#include -#include -#include -#include - -/* External: task lifecycle (kernel/task.cc) */ -extern void task_exit(int code); -extern int32 do_wait4(int32 wait_pid, int options, int32* exit_code); -extern int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, - uint64 child_stack, uint64 parent_tidptr, - uint64 child_tidptr, uint64 tls); -extern int32 task_fork(arch::TrapFrame* tf); - -/* ====================================================================== - * sys_exit / sys_exit_group - * ====================================================================== */ - -int64 sys_exit(uint64 code, uint64, uint64, uint64, uint64, uint64) -{ - task_exit(static_cast(code & 0xFF)); - __builtin_unreachable(); -} - -int64 sys_exit_group(uint64 code, uint64, uint64, uint64, uint64, uint64) -{ - /* Phase 2: single-threaded, equivalent to sys_exit. - * Phase 3: kill all threads in tgid then exit. */ - task_exit(static_cast(code & 0xFF)); - __builtin_unreachable(); -} - -/* ====================================================================== - * sys_getpid / sys_gettid / sys_getppid - * ====================================================================== */ - -int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return current_task()->tgid; /* Linux: getpid() returns tgid */ -} - -int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return current_task()->pid; /* Linux: gettid() returns real tid */ -} - -int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* parent = current_task()->parent; - return parent ? parent->tgid : 0; -} - -/* ====================================================================== - * sys_sched_yield - * ====================================================================== */ - -int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64) -{ - sched_yield(); - return 0; -} - -int64 sys_clone_wrapper(uint64 flags, uint64 child_stack, - uint64 ptid, uint64 ctid, uint64 tls, uint64) -{ - arch::TrapFrame* tf = current_task()->trapframe; - if (!tf) return -EINVAL; - - return task_clone(tf, flags, child_stack, ptid, ctid, tls); -} - -/* ======================================================================== - * sys_wait4 — Wait for child to exit - * ======================================================================== */ - -int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr, - uint64 options, uint64, uint64, uint64) -{ - int32 exit_code = 0; - int32 child_pid = do_wait4(static_cast(pid), - static_cast(options), - &exit_code); - - if (child_pid < 0) return child_pid; - - /* exit_code is already in Linux wstatus format */ - if (wstatus_ptr != 0) { - if (copy_to_user(wstatus_ptr, &exit_code, sizeof(int)) < 0) - return -EFAULT; - } - - return child_pid; -} - -/* ====================================================================== - * sys_set_tid_address — musl libc calls this very early - * ====================================================================== */ - -int64 sys_set_tid_address(uint64 tidptr, uint64, uint64, - uint64, uint64, uint64) -{ - /* Store clear_child_tid pointer — on exit, kernel writes 0 here - * and does futex_wake (enables pthread_join). */ - current_task()->clear_child_tid = tidptr; - return current_task()->pid; -} - -/* ====================================================================== - * sys_getuid / sys_getgid — Phase 2: always root - * ====================================================================== */ - -int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return 0; /* root */ -} - -int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return 0; /* root */ -} - -/* ====================================================================== - * sys_nanosleep (SYS_nanosleep = 101) - * ====================================================================== */ -#include -#include -#include - -struct linux_timespec { - int64 tv_sec; - int64 tv_nsec; -}; - -static void nanosleep_wakeup(TimerList* t) { - TaskControlBlock* task = static_cast(t->data); - task->state = TaskState::Runnable; - sched_enqueue(task); -} - -int64 sys_nanosleep(uint64 req_ptr, uint64 rem_ptr, uint64, uint64, uint64, uint64) { - if (req_ptr == 0) return -EINVAL; - - linux_timespec req; - if (copy_from_user(&req, req_ptr, sizeof(req)) != 0) { - return -EFAULT; - } - - uint64 target = *(volatile uint64*)&g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000); - - TaskControlBlock* curr = current_task(); - curr->state = TaskState::Sleeping; - - TimerList t; - t.init(nanosleep_wakeup); - t.expires = target; - t.data = curr; - - add_timer(&t); - - if (*(volatile uint64*)&g_jiffies < target) { - schedule(); - } else { - curr->state = TaskState::Running; - } - - - del_timer(&t); - - return 0; -} diff --git a/na/kernel/sys_signal.cc b/na/kernel/sys_signal.cc deleted file mode 100644 index 8b459d5..0000000 --- a/na/kernel/sys_signal.cc +++ /dev/null @@ -1,351 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_signal.cc — Signal-related system calls - * - * Implements: - * sys_kill (SYS_kill = 129) - * sys_rt_sigaction (SYS_rt_sigaction = 134) - * sys_rt_sigprocmask (SYS_rt_sigprocmask = 135) - * sys_rt_sigreturn (SYS_rt_sigreturn = 139) - * - * Reference: Linux kernel/signal.c, arch/riscv/kernel/signal.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* From signal.cc */ -extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); - -/* From task.cc */ -extern list_node g_all_tasks; -extern Spinlock g_task_list_lock; - -/* ================================================================== */ -/* Helper: find task by PID */ -/* ================================================================== */ - -static TaskControlBlock* find_task_by_pid(int32 pid) -{ - LockGuard guard(g_task_list_lock); - list_node* node = g_all_tasks.next; - while (node != &g_all_tasks) { - auto* task = list_entry(node); - if (task->pid == pid) return task; - node = node->next; - } - return nullptr; -} - -/* ================================================================== */ -/* sys_kill — send a signal to a process */ -/* ================================================================== */ - -/* - * Deliver `sig` to the thread-group leader represented by `sig_st`. - * sighand is allocated eagerly at fork/exec time, so we just trust - * that the leader has one; send_signal returns -ESRCH if not. - */ -static int deliver_to_group(signal_struct* sig_st, int sig, - kernel_siginfo* info) -{ - if (!sig_st || !sig_st->leader) return 0; - if (send_signal(sig, info, sig_st->leader) == 0) return 1; - return 0; -} - -int64 sys_kill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64) -{ - int32 pid = static_cast(a0); - int sig = static_cast(a1); - - if (sig < 0 || sig > _NSIG) return -EINVAL; - - TaskControlBlock* curr = current_task(); - if (!curr) return -ESRCH; - - kernel_siginfo info{}; - info.si_signo = sig; - info.si_code = 0; /* SI_USER */ - info._sifields._kill._pid = curr->pid; - info._sifields._kill._uid = 0; - - /* pid > 0: deliver to that thread group's leader. */ - if (pid > 0) { - TaskControlBlock* target = find_task_by_pid(pid); - if (!target) return -ESRCH; - /* Group signal: prefer the thread-group leader if available. */ - if (target->signal && target->signal->leader) - target = target->signal->leader; - if (sig == 0) return 0; - return send_signal(sig, &info, target); - } - - /* pid == 0: every member of caller's process group. */ - if (pid == 0) { - if (!curr->signal) return -ESRCH; - if (sig == 0) return 0; - int delivered = 0; - int32 target_pgid = curr->signal->pgid; - for_each_pgrp_member(target_pgid, [&](signal_struct* s) { - delivered += deliver_to_group(s, sig, &info); - }); - return delivered > 0 ? 0 : -ESRCH; - } - - /* pid == -1: every process the caller may signal, except init. - * We approximate "every" with "every thread-group leader on every - * pgrp bucket". This is rare in practice (BusyBox doesn't use it - * during normal shell operation). */ - if (pid == -1) { - if (sig == 0) return 0; - int delivered = 0; - LockGuard guard(g_pgrp_lock); - for (auto& bucket : g_pgrp_hash) { - list_node* node = bucket.next; - while (node != &bucket) { - list_node* next = node->next; - auto* s = list_entry(node); - if (s->leader && s->leader->pid != 1) - delivered += deliver_to_group(s, sig, &info); - node = next; - } - } - return delivered > 0 ? 0 : -ESRCH; - } - - /* pid < -1: every member of pgrp |pid|. */ - int32 target_pgid = -pid; - if (sig == 0) { - bool any = false; - for_each_pgrp_member(target_pgid, [&](signal_struct*) { any = true; }); - return any ? 0 : -ESRCH; - } - int delivered = 0; - for_each_pgrp_member(target_pgid, [&](signal_struct* s) { - delivered += deliver_to_group(s, sig, &info); - }); - return delivered > 0 ? 0 : -ESRCH; -} - -/* ================================================================== */ -/* sys_tkill / sys_tgkill — send a signal to a specific thread */ -/* ================================================================== */ - -static int64 do_tkill(int32 tgid, int32 tid, int sig) -{ - if (sig < 0 || sig > _NSIG) return -EINVAL; - if (tid <= 0) return -EINVAL; - TaskControlBlock* target = find_task_by_pid(tid); - if (!target) return -ESRCH; - /* tgid == -1 from tkill means "any tgid"; otherwise enforce match. */ - if (tgid != -1 && target->tgid != tgid) return -ESRCH; - - if (sig == 0) return 0; - - kernel_siginfo info{}; - info.si_signo = sig; - info.si_code = 0; /* SI_TKILL would be -6; userland rarely checks */ - info._sifields._kill._pid = current_task() ? current_task()->pid : 0; - info._sifields._kill._uid = 0; - return send_signal(sig, &info, target); -} - -int64 sys_tkill(uint64 a0, uint64 a1, uint64, uint64, uint64, uint64) -{ - return do_tkill(-1, - static_cast(a0), - static_cast(a1)); -} - -int64 sys_tgkill(uint64 a0, uint64 a1, uint64 a2, uint64, uint64, uint64) -{ - return do_tkill(static_cast(a0), - static_cast(a1), - static_cast(a2)); -} - -/* ================================================================== */ -/* sys_rt_sigaction — get/set signal disposition */ -/* ================================================================== */ - -/* - * User-space sigaction struct layout (RISC-V LP64): - * uint64 sa_handler; - * uint64 sa_flags; - * uint64 sa_restorer; (SA_RESTORER) - * sigset_t sa_mask; (8 bytes on LP64) - */ -struct user_sigaction { - uint64 sa_handler; - uint64 sa_flags; - uint64 sa_restorer; - sigset_t sa_mask; -}; - -int64 sys_rt_sigaction(uint64 a0, uint64 a1, uint64 a2, uint64 a3, - uint64, uint64) -{ - int sig = static_cast(a0); - uint64 act_uaddr = a1; /* const struct sigaction __user * */ - uint64 oact_uaddr = a2; /* struct sigaction __user * */ - uint64 sigsetsize = a3; - - if (sig < 1 || sig > _NSIG) return -EINVAL; - if (sig_kernel_only(sig)) return -EINVAL; - if (sigsetsize != sizeof(sigset_t)) return -EINVAL; - - TaskControlBlock* curr = current_task(); - if (!curr || !curr->sighand) return -EINVAL; - - /* All reads/writes of the action table happen under siglock — - * Linux's do_sigaction() also takes the same lock (kernel/signal.c). */ - user_sigaction old{}; - user_sigaction act{}; - bool have_new = (act_uaddr != 0); - - if (have_new) { - int err = copy_from_user(&act, act_uaddr, sizeof(act)); - if (err < 0) return -EFAULT; - } - - { - LockGuard guard(curr->sighand->siglock); - kernel_sigaction* ksa = &curr->sighand->action[sig - 1]; - old.sa_handler = ksa->sa_handler; - old.sa_flags = ksa->sa_flags; - old.sa_restorer = ksa->sa_restorer; - old.sa_mask = ksa->sa_mask; - - if (have_new) { - ksa->sa_handler = act.sa_handler; - ksa->sa_flags = act.sa_flags; - ksa->sa_restorer = act.sa_restorer; - ksa->sa_mask = act.sa_mask; - ksa->sa_mask.del(SIGKILL); - ksa->sa_mask.del(SIGSTOP); - } - } - - if (oact_uaddr != 0) { - int err = copy_to_user(oact_uaddr, &old, sizeof(old)); - if (err < 0) return -EFAULT; - } - return 0; -} - -/* ================================================================== */ -/* sys_rt_sigprocmask — change signal blocked mask */ -/* ================================================================== */ - -int64 sys_rt_sigprocmask(uint64 a0, uint64 a1, uint64 a2, uint64 a3, - uint64, uint64) -{ - int how = static_cast(a0); - uint64 set_uaddr = a1; - uint64 oset_uaddr = a2; - uint64 sigsetsize = a3; - - if (sigsetsize != sizeof(sigset_t)) return -EINVAL; - - TaskControlBlock* curr = current_task(); - if (!curr || !curr->sighand) return -EINVAL; - - sigset_t newset{}; - bool have_new = (set_uaddr != 0); - if (have_new) { - int err = copy_from_user(&newset, set_uaddr, sizeof(sigset_t)); - if (err < 0) return -EFAULT; - newset.del(SIGKILL); - newset.del(SIGSTOP); - } - - sigset_t old{}; - { - /* Linux: kernel/signal.c:do_sigprocmask() also holds siglock - * across read+update so the snapshot is atomic vs. concurrent - * signal delivery. */ - LockGuard guard(curr->sighand->siglock); - old = curr->blocked; - if (have_new) { - switch (how) { - case SIG_BLOCK: - curr->blocked = curr->blocked | newset; - break; - case SIG_UNBLOCK: - curr->blocked = curr->blocked & ~newset; - break; - case SIG_SETMASK: - curr->blocked = newset; - break; - default: - return -EINVAL; - } - } - } - - if (oset_uaddr != 0) { - int err = copy_to_user(oset_uaddr, &old, sizeof(sigset_t)); - if (err < 0) return -EFAULT; - } - return 0; -} - -/* ================================================================== */ -/* sys_rt_sigreturn — restore context after signal handler */ -/* ================================================================== */ - -/* Arch-specific: implemented in arch/riscv/signal.cc */ -extern int64 arch_sigreturn(arch::TrapFrame* tf); - -int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->trapframe) return -EINVAL; - - return arch_sigreturn(curr->trapframe); -} - -/* ================================================================== */ -/* sys_rt_sigtimedwait (SYS_rt_sigtimedwait = 137) */ -/* ================================================================== */ -#include -#include - -struct linux_timespec { - int64 tv_sec; - int64 tv_nsec; -}; - -int64 sys_rt_sigtimedwait(uint64 /* set_ptr */, uint64 /* info_ptr */, uint64 timeout_ptr, uint64 /* sigsetsize */, uint64, uint64) -{ - /* - * COMPATIBILITY STUB: - * LumeOS lacks full real-time signal queues and timed waiting mechanism. - * When BusyBox init runs out of children to reap, it calls this to wait - * for incoming signals (like SIGCHLD) with a timeout. - * We simulate the timeout by yielding the CPU, then return -EAGAIN (Try again) - * which tells user space that the timeout expired without receiving a signal. - */ - if (timeout_ptr != 0) { - linux_timespec req; - if (copy_from_user(&req, timeout_ptr, sizeof(req)) == 0) { - uint64 target = g_jiffies + (req.tv_sec * 100) + (req.tv_nsec / 10000000); - while (g_jiffies < target) { - schedule(); - } - } - } - return -EAGAIN; -} diff --git a/na/kernel/syscall.cc b/na/kernel/syscall.cc deleted file mode 100644 index a5b80ec..0000000 --- a/na/kernel/syscall.cc +++ /dev/null @@ -1,208 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * System Call MI Layer — Jump Table & Dispatcher - * - * O(1) function pointer array dispatch. No switch-case. - * syscall_init() registers all implemented handlers. - * - * Reference: docs/specs/syscall.md §6 - */ - -#include -#include -#include -#include -#include -#include -#include - -/* ====================================================================== - * Jump table - * ====================================================================== */ - -inline constexpr int kNrSyscalls = 512; - -static SyscallHandler sys_call_table[kNrSyscalls]; - -/* ====================================================================== - * MI Dispatcher - * ====================================================================== */ - -int64 syscall_dispatch(uint64 nr, uint64 args[6]) -{ - if (nr >= static_cast(kNrSyscalls)) { - kprintf("[syscall] nr=%llu out of range\n", nr); - return -ENOSYS; - } - - /* Per-syscall entry trace. KLOG_DEBUG keeps it out of the console - * by default; enable via klog_set_level(KLOG_DEBUG) or read the - * ring buffer to investigate. Subsys mask gates this further. */ - pr_debug_s(KLOG_SUBSYS_SYSCALL, - "syscall: nr=%llu a0=%llx a1=%llx a2=%llx a3=%llx\n", - nr, args[0], args[1], args[2], args[3]); - - SyscallHandler handler = sys_call_table[nr]; - if (!handler) { - kprintf("[syscall] unimplemented nr=%llu\n", nr); - return -ENOSYS; - } - - return handler(args[0], args[1], args[2], - args[3], args[4], args[5]); -} - -/* ====================================================================== - * Forward declarations of sys_* handlers - * ====================================================================== */ - -/* sys_process.cc */ -int64 sys_exit(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_exit_group(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_clone_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_wait4_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_nanosleep(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_mm.cc */ -int64 sys_brk(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mmap(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_munmap(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mprotect(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_info.cc */ -int64 sys_uname(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_fs.cc */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_ppoll(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_signal.cc */ -int64 sys_kill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_tkill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_tgkill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigaction(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigprocmask(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigtimedwait(uint64, uint64, uint64, uint64, uint64, uint64); - -/* exec.cc */ -int64 sys_execve_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_misc.cc */ -int64 sys_reboot(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_prctl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getsid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_setpgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getpgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sigaltstack(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_clock_gettime(uint64, uint64, uint64, uint64, uint64, uint64); - -/* ====================================================================== - * syscall_init — register all handlers - * ====================================================================== */ - -void syscall_init() -{ - /* Zero the table (redundant for BSS, but explicit) */ - for (int i = 0; i < kNrSyscalls; i++) - sys_call_table[i] = nullptr; - - /* Phase 2: minimal runnable set */ - sys_call_table[SYS_exit] = sys_exit; - sys_call_table[SYS_exit_group] = sys_exit_group; - sys_call_table[SYS_getpid] = sys_getpid; - sys_call_table[SYS_gettid] = sys_gettid; - sys_call_table[SYS_getppid] = sys_getppid; - sys_call_table[SYS_sched_yield] = sys_sched_yield_wrapper; - sys_call_table[SYS_clone] = sys_clone_wrapper; - sys_call_table[SYS_wait4] = sys_wait4_wrapper; - sys_call_table[SYS_set_tid_address] = sys_set_tid_address; - sys_call_table[SYS_getuid] = sys_getuid; - sys_call_table[SYS_getgid] = sys_getgid; - sys_call_table[SYS_brk] = sys_brk; - sys_call_table[SYS_mmap] = sys_mmap; - sys_call_table[SYS_munmap] = sys_munmap; - sys_call_table[SYS_mprotect] = sys_mprotect; - sys_call_table[SYS_uname] = sys_uname; - - /* Phase 3: filesystem */ - sys_call_table[SYS_openat] = sys_openat; - sys_call_table[SYS_close] = sys_close; - sys_call_table[SYS_read] = sys_read; - sys_call_table[SYS_write] = sys_write; - sys_call_table[SYS_lseek] = sys_lseek; - sys_call_table[SYS_fstat] = sys_fstat_wrapper; - sys_call_table[SYS_getdents64] = sys_getdents64; - sys_call_table[SYS_dup] = sys_dup; - sys_call_table[SYS_dup3] = sys_dup3; - sys_call_table[SYS_getcwd] = sys_getcwd; - sys_call_table[SYS_chdir] = sys_chdir; - sys_call_table[SYS_mkdirat] = sys_mkdirat; - sys_call_table[SYS_unlinkat] = sys_unlinkat; - sys_call_table[SYS_writev] = sys_writev; - sys_call_table[SYS_ioctl] = sys_ioctl; - sys_call_table[SYS_ppoll] = sys_ppoll; - - /* Phase 4: signals */ - sys_call_table[SYS_kill] = sys_kill; - sys_call_table[SYS_tkill] = sys_tkill; - sys_call_table[SYS_tgkill] = sys_tgkill; - sys_call_table[SYS_rt_sigaction] = sys_rt_sigaction; - sys_call_table[SYS_rt_sigprocmask] = sys_rt_sigprocmask; - sys_call_table[SYS_rt_sigreturn] = sys_rt_sigreturn; - sys_call_table[SYS_rt_sigtimedwait] = sys_rt_sigtimedwait; - - /* Phase 5: exec */ - sys_call_table[SYS_execve] = sys_execve_wrapper; - - /* Phase 6: pipe + fcntl */ - sys_call_table[SYS_pipe2] = sys_pipe2; - sys_call_table[SYS_fcntl] = sys_fcntl; - - /* Phase 7: busybox bootstrap */ - sys_call_table[SYS_reboot] = sys_reboot; - sys_call_table[SYS_prctl] = sys_prctl; - sys_call_table[SYS_setsid] = sys_setsid; - sys_call_table[SYS_getsid] = sys_getsid; - sys_call_table[SYS_setpgid] = sys_setpgid; - sys_call_table[SYS_getpgid] = sys_getpgid; - sys_call_table[SYS_geteuid] = sys_geteuid; - sys_call_table[SYS_getegid] = sys_getegid; - sys_call_table[SYS_sigaltstack] = sys_sigaltstack; - sys_call_table[SYS_clock_gettime] = sys_clock_gettime; - sys_call_table[SYS_nanosleep] = sys_nanosleep; - sys_call_table[SYS_socket] = sys_socket; - sys_call_table[SYS_sendto] = sys_sendto; - - printk(KERN_INFO "init: syscall_init done (%d handlers registered)\n", 46); -} diff --git a/na/kernel/task.cc b/na/kernel/task.cc deleted file mode 100644 index 9f9b043..0000000 --- a/na/kernel/task.cc +++ /dev/null @@ -1,780 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Task Management — create, exit, destroy, kthread - * - * Implements task lifecycle management: kernel thread creation, - * task exit (zombie transition), and resource reclamation. - * - * Reference: docs/specs/task.md §4-§6 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ====================================================================== - * Forward declarations - * ====================================================================== */ - -extern "C" void kthread_entry_trampoline(); -extern "C" void ret_from_fork(); - -/* ====================================================================== - * Global task list - * ====================================================================== */ - -list_node g_all_tasks; -Spinlock g_task_list_lock; -int32 g_nr_tasks = 0; - -/* Special tasks */ -TaskControlBlock* g_kernel_task = nullptr; /* PID 0 container */ -TaskControlBlock* g_init_task = nullptr; /* PID 1 (future) */ - -/* ====================================================================== - * task_init() — called once from kernel_main before sched_init - * ====================================================================== */ - -void task_init() -{ - g_all_tasks.init(); - g_task_list_lock.init("task_list"); - g_pid_alloc.init(); - - printk(KERN_INFO "init: task_init done (pid_alloc ready)\n"); -} - -/* ====================================================================== - * alloc_task — allocate TCB + kernel stack - * - * This is the SINGLE canonical TCB initializer. Both kthread_create - * (task.cc) and sched_init (sched_core.cc) must use this function. - * NEVER duplicate this logic — adding fields to TaskControlBlock - * requires updating only this function. - * ====================================================================== */ - -TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags, uint8 cpu) -{ - auto* task = new TaskControlBlock; - if (!task) return nullptr; - - task->context = {}; - - /* Allocate 8KB kernel stack (order-1 = 2 pages) */ - Page* stack_page = pmm_alloc_pages(1); - if (!stack_page) { - delete task; - return nullptr; - } - task->kstack_pa = page_to_pa(stack_page).raw; - task->kstack_va = pa_to_va(phys_addr(task->kstack_pa)).raw; - - /* Write stack canary at bottom */ - *reinterpret_cast(task->kstack_va) = kStackCanary; - - /* Place TrapFrame at the top of the kernel stack (like Linux pt_regs). - * ALL tasks — kernel threads and user processes — have a TrapFrame - * here. kthread_create sets the initial SP below the TrapFrame so - * the kthread function never touches this reserved area. - * kernel_execve / do_execve can later fill it in for user-mode return. */ - uint64 tf_addr = task->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - task->trapframe = reinterpret_cast(tf_addr); - __builtin_memset(task->trapframe, 0, sizeof(arch::TrapFrame)); - - /* Basic fields */ - task->name = name; - task->priority = priority; - task->time_slice = priority_to_timeslice(priority); - task->state = TaskState::Runnable; - task->cpu_id = cpu; - task->flags = flags; - task->exit_code = 0; - task->exit_signal = 0; - task->preempt_count = 0; - task->clear_child_tid = 0; - task->stopped_code = 0; - task->notify_continued = false; - task->parent = nullptr; - task->mm = nullptr; - task->fpstate = {}; - task->files = nullptr; - task->fs = nullptr; - task->pending.init(); - task->blocked.clear(); - task->sighand = nullptr; - task->signal = nullptr; - - /* Init all list nodes */ - task->run_link.init(); - task->all_link.init(); - task->children.init(); - task->sibling.init(); - task->lock.init("task"); - task->wait_chld.init("wait_chld"); - - /* Default sched class */ - extern const SchedClass g_sched_class_o1; - task->sched_class = &g_sched_class_o1; - task->sched_entity = &task->run_link; - - return task; -} - -/* Add task to global task list */ -static void task_list_add(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - list_node* head = &g_all_tasks; - task->all_link.prev = head->prev; - task->all_link.next = head; - head->prev->next = &task->all_link; - head->prev = &task->all_link; - - g_nr_tasks++; -} - -/* Remove task from global task list */ -static void task_list_remove(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - task->all_link.prev->next = task->all_link.next; - task->all_link.next->prev = task->all_link.prev; - task->all_link.init(); - - if (g_nr_tasks > 0) - g_nr_tasks--; -} - -/* ====================================================================== - * kthread_create — create a kernel thread - * ====================================================================== */ - -TaskControlBlock* kthread_create(const char* name, - void (*entry)(void*), - void* arg, - uint8 priority) -{ - TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD, - static_cast(arch::cpu::id())); - if (!task) return nullptr; - - /* Allocate PID */ - task->pid = g_pid_alloc.alloc(); - if (task->pid < 0) { - /* PID exhausted — clean up */ - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - delete task; - return nullptr; - } - task->tgid = task->pid; - - /* Kernel threads: exit_signal = -1 → auto-reap on exit. - * Linux: kernel_thread() passes (flags & ~CSIGNAL) → exit_signal = 0, - * but kthreads' parent (kthreadd) reaps them. We have no kthreadd, - * so we use -1 which makes exit_notify() call release_task() directly, - * identical to Linux's CLONE_THREAD autoreap path. */ - task->exit_signal = -1; - - /* Set up context for kthread_entry_trampoline. - * SP starts BELOW the TrapFrame reserved at the stack top. - * This ensures the kthread function never clobbers the TrapFrame area. */ - uint64 stack_top = task->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - task->context = {}; - task->context.ra = reinterpret_cast(kthread_entry_trampoline); - task->context.sp = stack_top; - task->context.s[0] = reinterpret_cast(entry); - task->context.s[1] = reinterpret_cast(arg); - - /* Parent is kernel_task (PID 0) or current caller */ - TaskControlBlock* parent = g_kernel_task ? g_kernel_task : current_task(); - task->parent = parent; - if (parent) { - LockGuard guard(parent->lock); - list_node* head = &parent->children; - task->sibling.prev = head->prev; - task->sibling.next = head; - head->prev->next = &task->sibling; - head->prev = &task->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(task); - sched_enqueue(task); - - return task; -} - -/* ====================================================================== - * task_exit — transition current task to Zombie - * ====================================================================== */ - -void task_exit(int code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_exit: no current task"); - - /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */ - curr->exit_code = (code & 0xFF) << 8; - - /* CLONE_CHILD_CLEARTID / set_tid_address: write 0 to user tidptr. - * This is how pthread_join detects thread termination. - * TODO: futex_wake(clear_child_tid, 1) after writing 0. */ - if (curr->clear_child_tid != 0 && curr->mm != nullptr) { - int32 zero = 0; - copy_to_user(curr->clear_child_tid, &zero, sizeof(zero)); - curr->clear_child_tid = 0; - } - - /* Release user address space (refcount-safe). - * MUST switch to kernel page table FIRST — otherwise we're running - * on a freed page table after put() deallocates it. */ - if (curr->mm != nullptr) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children to init (or kernel_task if no init yet) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry(child_node); - child->parent = foster; - - /* Move sibling link to foster's children */ - child_node->prev->next = child_node->next; - child_node->next->prev = child_node->prev; - - list_node* fhead = &foster->children; - child_node->prev = fhead->prev; - child_node->next = fhead; - fhead->prev->next = child_node; - fhead->prev = child_node; - - child_node = next_sib; - } - curr->children.init(); - } - - - /* ---- exit_notify: Linux kernel/exit.c:exit_notify() ---- - * Determine whether this task should be auto-reaped or become zombie. - * - * Linux logic (simplified): - * - exit_signal == -1 (kthreads, sub-threads): autoreap = true - * - exit_signal >= 1 (user processes): notify parent, become zombie - * - * If autoreap, we call release_task() equivalent inline: - * free PID, remove from task list, then deschedule to Dead. */ - bool autoreap = (curr->exit_signal <= 0); - - if (autoreap) { - /* Auto-reap: release resources immediately (no zombie state). - * Linux: exit_notify() → tsk->exit_state = EXIT_DEAD → release_task() */ - task_list_remove(curr); - g_pid_alloc.free(curr->pid); - - /* Remove from parent's children list */ - curr->sibling.prev->next = curr->sibling.next; - curr->sibling.next->prev = curr->sibling.prev; - curr->sibling.init(); - - /* Drop signal_struct ref: last task in the thread group also - * detaches us from the pgrp hash. */ - if (curr->signal) { - put_signal_struct(curr->signal); - curr->signal = nullptr; - } - if (curr->sighand) { - put_sighand(curr->sighand); - curr->sighand = nullptr; - } - - curr->state = TaskState::Dead; - schedule(); - __builtin_unreachable(); - } - - /* User process path: become Zombie for parent wait() */ - curr->state = TaskState::Zombie; - - /* Wake parent->wait_chld queue */ - if (curr->parent) { - curr->parent->wait_chld.wake_all(); - } - - /* Yield CPU — never returns */ - schedule(); - - __builtin_unreachable(); -} - -void task_kill_current(int sig) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_kill_current: no current task"); - - /* Linux ABI: signal-killed encodes status as (signo & 0x7F) */ - curr->exit_code = sig & 0x7F; - - /* Release user address space (refcount-safe). - * Switch to kernel page table first (same as task_exit). */ - if (curr->mm) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children (same as task_exit) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry(child_node); - child->parent = foster; - - child_node->prev->next = child_node->next; - child_node->next->prev = child_node->prev; - - list_node* fhead = &foster->children; - child_node->prev = fhead->prev; - child_node->next = fhead; - fhead->prev->next = child_node; - fhead->prev = child_node; - - child_node = next_sib; - } - curr->children.init(); - } - - curr->state = TaskState::Zombie; - - if (curr->parent) { - curr->parent->wait_chld.wake_all(); - } - - schedule(); - __builtin_unreachable(); -} - -/* ====================================================================== - * task_destroy — fully reclaim a Zombie task (called by waitpid) - * ====================================================================== */ - -void task_destroy(TaskControlBlock* task) -{ - /* Remove from global list */ - task_list_remove(task); - - /* Remove from parent's children */ - task->sibling.prev->next = task->sibling.next; - task->sibling.next->prev = task->sibling.prev; - task->sibling.init(); - - /* Free PID */ - g_pid_alloc.free(task->pid); - - /* Drop signal_struct + sighand refs. For zombies these are delayed - * until reap (here) so a waitpid() walker can still inspect - * pgid/sid; Linux defers __exit_signal/__cleanup_sighand to - * release_task() for the same reason. */ - if (task->signal) { - put_signal_struct(task->signal); - task->signal = nullptr; - } - if (task->sighand) { - put_sighand(task->sighand); - task->sighand = nullptr; - } - - /* Free kernel stack */ - if (task->kstack_pa != 0) { - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - } - - /* Free TCB */ - delete task; -} - -/* ====================================================================== - * do_wait4 — wait for a child to exit (Linux-compatible) - * - * @wait_pid: >0 = specific child PID - * -1 = any child - * 0 = any child in same process group (TODO) - * <-1 = any child in specific process group (TODO) - * @options: WNOHANG = return immediately if no zombie - * @exit_code: output parameter (already in Linux wstatus format) - * - * Returns: PID of exited child, -ECHILD, or -EAGAIN (WNOHANG). - * ====================================================================== */ - -int32 do_wait4(int32 wait_pid, int options, int32* exit_code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - while (true) { - TaskControlBlock* zombie = nullptr; - TaskControlBlock* report = nullptr; /* Stopped/Continued report */ - int32 report_status = 0; - bool has_children = false; - - /* - * Two-phase wait: put ourselves on the wait queue BEFORE - * checking the condition. This closes the race window where - * a child exits between our check and schedule(). - * - * If a child exits and calls wake_all() while we're checking, - * our state will be flipped back to Runnable, and schedule() - * will simply return immediately. - */ - curr->wait_chld.prepare_to_wait(curr, TaskState::Sleeping); - - /* Scan children */ - curr->lock.acquire(); - - if (curr->children.is_empty()) { - curr->lock.release(); - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - has_children = true; - - list_node* node = curr->children.next; - while (node != &curr->children) { - auto* child = list_entry(node); - bool matches = (wait_pid == -1) || (wait_pid == child->pid); - if (!matches) { - node = node->next; - continue; - } - - /* Zombie reaping has priority — it frees resources. */ - if (child->state == TaskState::Zombie) { - zombie = child; - break; - } - - /* WUNTRACED: report Stopped child once per stop event. */ - if ((options & WUNTRACED) && child->stopped_code != 0) { - report = child; - report_status = child->stopped_code; - break; - } - - /* WCONTINUED: report a one-shot SIGCONT resume. */ - if ((options & WCONTINUED) && child->notify_continued) { - report = child; - report_status = W_CONTINUED(); - break; - } - - node = node->next; - } - curr->lock.release(); - - if (zombie) { - curr->wait_chld.finish_wait(curr); - int32 pid = zombie->pid; - if (exit_code) *exit_code = zombie->exit_code; - task_destroy(zombie); - return pid; - } - - if (report) { - /* Status-only report: clear the one-shot flag, leave the - * child alive. WNOWAIT (peek) would skip the clear, but - * we don't yet honour it. */ - curr->wait_chld.finish_wait(curr); - int32 pid = report->pid; - if (exit_code) *exit_code = report_status; - if (W_CONTINUED() == report_status) { - report->notify_continued = false; - } else { - report->stopped_code = 0; - } - return pid; - } - - /* No event to report */ - if (!has_children) { - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - if (options & WNOHANG) { - curr->wait_chld.finish_wait(curr); - return 0; - } - - /* Condition still false — deschedule. - * We are already on the wait queue and marked Sleeping. */ - schedule(); - - /* Woken up — finish_wait removes us from queue, restores Runnable */ - curr->wait_chld.finish_wait(curr); - - /* Loop back to re-check the condition */ - } -} - -/* Legacy wrapper for internal callers */ -int32 task_wait(int32* exit_code) -{ - return do_wait4(-1, 0, exit_code); -} - -/* ====================================================================== - * task_clone — clone current task with Linux-compatible flags - * - * @tf: trap frame to copy to child - * @clone_flags: CLONE_VM | CLONE_THREAD | CSIGNAL etc. - * @child_stack: if non-zero, set as child's user SP (for pthread) - * @parent_tidptr: write child tid here in parent (CLONE_PARENT_SETTID) - * @child_tidptr: write child tid here in child (CLONE_CHILD_SETTID) - * @tls: TLS pointer (CLONE_SETTLS → tp register) - * - * Returns: child PID in parent, 0 in child, or negative errno. - * ====================================================================== */ - -int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, - uint64 child_stack, uint64 parent_tidptr, - uint64 child_tidptr, uint64 tls) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - TaskControlBlock* child = alloc_task("cloned", curr->priority, 0, - static_cast(arch::cpu::id())); - if (!child) return -ENOMEM; - - /* Allocate PID */ - child->pid = g_pid_alloc.alloc(); - if (child->pid < 0) { - pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); - delete child; - return -ENOMEM; - } - - /* exit_signal + tgid: from clone_flags. - * Linux: fork.c line 2418/2424 */ - if (clone_flags & CLONE_THREAD) { - child->tgid = curr->tgid; - child->exit_signal = -1; /* Linux: sub-threads auto-reap */ - } else { - child->tgid = child->pid; - child->exit_signal = static_cast(clone_flags & 0xFF); /* CSIGNAL */ - if (child->exit_signal == 0) { - child->exit_signal = SIGCHLD; /* default for fork() */ - } - } - - /* Address space: CLONE_VM shares, otherwise deep copy */ - if (clone_flags & CLONE_VM) { - if (curr->mm) { - curr->mm->incref(); - child->mm = curr->mm; - } - } else { - if (curr->mm) { - child->mm = vmm_clone(curr->mm); - if (!child->mm) { - g_pid_alloc.free(child->pid); - pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); - delete child; - return -ENOMEM; - } - } - } - - /* Copy TrapFrame to the child's kernel stack */ - uint64 tf_addr = child->kstack_va + kKernelStackSize - sizeof(arch::TrapFrame); - child->trapframe = reinterpret_cast(tf_addr); - *child->trapframe = *tf; - - /* Set Kernel SP */ - child->trapframe->kernel_sp = reinterpret_cast(child->trapframe); - child->trapframe->kernel_cpu_id = child->cpu_id; - - /* Child returns 0 from fork/clone */ - child->trapframe->a[0] = 0; - - /* If caller specified a child stack, use it (pthread_create) */ - if (child_stack != 0) { - child->trapframe->sp = child_stack; - } - - /* CLONE_SETTLS: set thread pointer register */ - if (clone_flags & CLONE_SETTLS) { - child->trapframe->tp = tls; - } - - /* Set up context for ret_from_fork */ - uint64 stack_top = tf_addr; - child->context = {}; - child->context.ra = reinterpret_cast(ret_from_fork); - child->context.sp = stack_top; - child->context.s[0] = tf_addr; - - /* ---- Copy fs_struct (Linux: copy_fs in fork.c) ---- - * CLONE_FS: share the same fs_struct (increment refcount). - * Otherwise: allocate a new fs_struct copying root/pwd. */ - if (curr->fs != nullptr) { - if (clone_flags & CLONE_FS) { - curr->fs->count.fetch_add(1, __ATOMIC_RELAXED); - child->fs = curr->fs; - } else { - child->fs = copy_fs_struct(curr->fs); - } - } - - /* ---- Copy files_struct (Linux: copy_files in fork.c) ---- - * CLONE_FILES: share the same fd table. - * Otherwise: duplicate the fd table. */ - if (curr->files != nullptr) { - if (clone_flags & CLONE_FILES) { - curr->files->count.fetch_add(1, __ATOMIC_RELAXED); - child->files = curr->files; - } else { - child->files = dup_fd(curr->files); - } - } - - /* ---- Copy sighand (Linux kernel/fork.c:copy_sighand) ---- - * CLONE_SIGHAND: share signal handlers (incref parent's). - * Otherwise: deep copy of the action table; child gets its own - * sighand_struct (and its own siglock). Both branches MUST yield - * a non-null sighand — every user task needs one so signal paths - * can take the siglock without lazy-allocation races. */ - if (clone_flags & CLONE_SIGHAND) { - if (!curr->sighand) { - /* CLONE_SIGHAND requires the parent to have a sighand. */ - return -EINVAL; - } - get_sighand(curr->sighand); - child->sighand = curr->sighand; - } else { - child->sighand = copy_sighand(curr->sighand); - if (!child->sighand) { - /* OOM — same best-effort policy as fs/files/signal above. - * A separate cleanup-on-OOM pass is on the to-do list. */ - } - } - - /* ---- Copy signal_struct (Linux: copy_signal in fork.c) ---- - * CLONE_THREAD shares the parent's signal_struct (incref). - * Otherwise allocate a new one that inherits pgid/sid/tty from the - * parent so the child stays in the same process group and session — - * this is what makes fork() participate in the existing job-control - * identity without explicit setpgid() calls. */ - if (curr->signal != nullptr) { - if (clone_flags & CLONE_THREAD) { - get_signal_struct(curr->signal); - child->signal = curr->signal; - } else { - child->signal = fork_signal_struct(curr->signal, child); - } - } - - /* Add to task tree */ - child->parent = curr; - { - LockGuard guard(curr->lock); - list_node* head = &curr->children; - child->sibling.prev = head->prev; - child->sibling.next = head; - head->prev->next = &child->sibling; - head->prev = &child->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(child); - sched_enqueue(child); - - /* CLONE_PARENT_SETTID: write child tid to parent's user address */ - if ((clone_flags & CLONE_PARENT_SETTID) && parent_tidptr != 0) { - copy_to_user(parent_tidptr, &child->pid, sizeof(child->pid)); - } - - /* CLONE_CHILD_SETTID: write child tid into child's user address. - * Note: this runs in parent context, but the address is in the - * child's mm. For CLONE_VM (threads), parent and child share mm - * so this works directly. For fork, the page was just CoW-copied. */ - if ((clone_flags & CLONE_CHILD_SETTID) && child_tidptr != 0) { - if (clone_flags & CLONE_VM) { - /* Same address space — write directly */ - copy_to_user(child_tidptr, &child->pid, sizeof(child->pid)); - } - /* For non-CLONE_VM fork, the child's mm is separate. - * The write would need to go into the child's pagetable. - * Linux handles this in the child's first schedule via - * a special flag. For now, threads (CLONE_VM) are the - * primary consumer. */ - } - - /* CLONE_CHILD_CLEARTID: kernel writes 0 to child_tidptr on exit */ - if (clone_flags & CLONE_CHILD_CLEARTID) { - child->clear_child_tid = child_tidptr; - } - - return child->pid; -} - -/* Legacy wrapper — plain fork() semantics */ -int32 task_fork(arch::TrapFrame* tf) -{ - return task_clone(tf, SIGCHLD, 0, 0, 0, 0); -} - - -/* ====================================================================== - * task_check_stack_canary - * ====================================================================== */ - -void task_check_stack_canary(TaskControlBlock* task) -{ - if (!task || task->kstack_va == 0) return; - - auto* canary = reinterpret_cast(task->kstack_va); - if (*canary != kStackCanary) { - kernel_panic("kernel stack overflow detected"); - } -} diff --git a/na/kernel/timer.cc b/na/kernel/timer.cc deleted file mode 100644 index 3cf03cf..0000000 --- a/na/kernel/timer.cc +++ /dev/null @@ -1,118 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include -#include -#include - -#include - -uint64 g_jiffies = 0; -static uint64 g_timer_freq = 10000000; -static uint64 g_tick_interval = 0; - -static list_node g_timer_list; -static Spinlock g_timer_lock; - - -void add_timer(TimerList* timer) { - LockGuard guard(g_timer_lock); - - list_node* pos; - list_for_each(pos, &g_timer_list) { - TimerList* t = list_entry(pos); - if (t->expires > timer->expires) { - break; - } - } - - timer->entry.prev = pos->prev; - timer->entry.next = pos; - pos->prev->next = &timer->entry; - pos->prev = &timer->entry; -} - -int del_timer(TimerList* timer) { - LockGuard guard(g_timer_lock); - if (timer->entry.next != nullptr && timer->entry.next != &timer->entry) { - timer->entry.prev->next = timer->entry.next; - timer->entry.next->prev = timer->entry.prev; - timer->entry.init(); - return 1; - } - return 0; -} - -int mod_timer(TimerList* timer, uint64 expires) { - int ret = del_timer(timer); - timer->expires = expires; - add_timer(timer); - return ret; -} - -static void run_timers() { - g_timer_lock.acquire(); - - while (!g_timer_list.is_empty()) { - TimerList* t = list_entry(g_timer_list.next); - if (t->expires > g_jiffies) { - break; - } - - t->entry.prev->next = t->entry.next; - t->entry.next->prev = t->entry.prev; - t->entry.init(); - - g_timer_lock.release(); - t->function(t); - g_timer_lock.acquire(); - } - - g_timer_lock.release(); -} - -static void timer_set_next() { - arch::sbi::set_timer(arch::cpu::read_time() + g_tick_interval); -} - -void timer_init() { - DeviceNode* cpus = fdt_get_node_by_path("/cpus"); - if (cpus) { - g_timer_freq = cpus->get_prop_u32("timebase-frequency", g_timer_freq); - } - - // Default 100 Hz (10ms per tick) - g_tick_interval = g_timer_freq / 100; - - g_timer_lock.init("timer_list"); - g_timer_list.init(); - - timer_set_next(); - - arch::cpu::timer_intr_on(); -} - - -void timer_init_ap() { - timer_set_next(); - arch::cpu::timer_intr_on(); -} - -extern "C" uint32 bsp_hart_id; - -void timer_tick() { - if (arch::cpu::id() == bsp_hart_id) { - g_jiffies++; - run_timers(); - } - - timer_set_next(); - sched_tick(); -} - - -uint64 timer_get_freq() { - return g_timer_freq; -} diff --git a/na/kernel/trap.cc b/na/kernel/trap.cc deleted file mode 100644 index 2116d91..0000000 --- a/na/kernel/trap.cc +++ /dev/null @@ -1,258 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Trap MI Layer — Machine-Independent Semantic Dispatcher - * - * Receives a translated TrapCause from the MD layer and routes to - * the appropriate handler. - * - * *** MI INVARIANT: This file contains ZERO inline assembly, - * ZERO arch-specific constants (no CSR reads, no scause codes). *** - * arch/trapframe.h is included because TrapFrame is the cross-layer - * data contract defined in docs/specs/arch_abstraction.md §3.2. - * - * Reference: docs/specs/trap.md §4 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* TrapFrame: cross-layer data contract */ -#include /* search_extable: kernel fault recovery */ - -/* ======================================================================== - * trap_handle() — MI-layer dispatcher for traps - * - * Called by MD layer after scause → TrapInfo translation. - * ======================================================================== */ - -static TrapStats g_trap_stats = {}; - -static uint32 trap_cause_index(TrapCause cause) -{ - uint32 raw = static_cast(cause); - - /* Exceptions 0-7 map directly to indices 0-7 */ - if (raw <= 7) { - return raw; - } - /* IRQs 16-18 map to indices 8-10 */ - if (raw >= 16 && raw <= 18) { - return raw - 16 + 8; - } - /* Unknown / out-of-range */ - return 11; -} - -static void trap_record(const TrapInfo& info) -{ - uint32 idx = trap_cause_index(info.cause); - __atomic_fetch_add(&g_trap_stats.total, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&g_trap_stats.by_cause[idx], 1, __ATOMIC_RELAXED); - g_trap_stats.last_cause = info.cause; - g_trap_stats.last_class = info.cls; - g_trap_stats.last_origin = info.origin; - g_trap_stats.last_val = info.val; - g_trap_stats.last_epc = info.epc; - g_trap_stats.last_scause = info.scause; -} - -static VmFaultCause vm_fault_cause_from_trap(TrapCause cause) -{ - switch (cause) { - case TrapCause::PageFaultStore: - return VmFaultCause::Write; - case TrapCause::PageFaultExec: - return VmFaultCause::Exec; - case TrapCause::PageFaultLoad: - default: - return VmFaultCause::Read; - } -} - -void trap_stats_get(TrapStats* out) -{ - if (!out) { - return; - } - *out = g_trap_stats; -} - -void trap_stats_reset() -{ - g_trap_stats = {}; -} - -void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) -{ - trap_record(info); - - /* ---- Kernel exception handling ---- - * Interrupts are always allowed through. For exceptions: - * - Page faults: handled in the switch below (extable fixup path) - * - Other exceptions: try extable, then panic. - * - * This replaces the old blanket kernel_panic, enabling - * copy_from_user/copy_to_user to safely fault on user pages. */ - if (info.origin == TrapOrigin::FromKernel && - info.cls != TrapClass::Interrupt) { - - /* Page faults are dispatched in the switch — let them through */ - bool is_page_fault = - (info.cause == TrapCause::PageFaultLoad || - info.cause == TrapCause::PageFaultStore || - info.cause == TrapCause::PageFaultExec); - - if (!is_page_fault) { - /* Non-page-fault kernel exception: try extable fixup */ - uint64 fixup = search_extable(info.epc); - if (fixup != 0) { - tf->epc = fixup; - return; - } - kprintf("[trap] fatal kernel exception: cause=%u epc=0x%llx val=0x%llx\n", - static_cast(info.cause), info.epc, info.val); - kernel_panic("unrecoverable kernel exception"); - } - } - - switch (info.cause) { - /* ---- Page Faults ---- */ - case TrapCause::PageFaultLoad: - case TrapCause::PageFaultStore: - case TrapCause::PageFaultExec: - if (info.origin == TrapOrigin::FromUser) { - TaskControlBlock* curr = current_task(); - if (!curr || !curr->mm) { - kernel_panic("user page fault with no current mm"); - } - - int ret = vmm_handle_page_fault(curr->mm, info.val, - vm_fault_cause_from_trap(info.cause)); - if (ret == -EFAULT) { - kprintf("[trap] user SIGSEGV (MAPERR) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGSEGV, 1 /* SEGV_MAPERR */, info.val); - } else if (ret == -ENOMEM) { - kprintf("[trap] user SIGKILL (OOM) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGKILL, 0, info.val); - } else if (ret < 0) { - kprintf("[trap] user SIGSEGV (ACCERR) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGSEGV, 2 /* SEGV_ACCERR */, info.val); - } - break; - } - - /* Kernel page fault — try exception table fixup. - * This is the path used by copy_from_user/copy_to_user when - * accessing unmapped or faulting user addresses from S-mode. */ - { - uint64 fixup = search_extable(info.epc); - if (fixup != 0) { - tf->epc = fixup; - break; - } - } - kprintf("[trap] kernel page fault: epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - { - TaskControlBlock* curr = current_task(); - if (curr) { - kprintf("[trap] pid=%d state=%d\n", - curr->pid, static_cast(curr->state)); - kprintf("[trap] tf->epc=%llx tf->ra=%llx tf->sp=%llx\n", - tf->epc, tf->ra, tf->sp); - } - } - kernel_panic("unhandled kernel page fault (no extable entry)"); - break; - - /* ---- System Call ---- */ - case TrapCause::Syscall: - syscall_dispatch_md(tf); - break; - - /* ---- Interrupts ---- */ - case TrapCause::IrqTimer: - /* Timer tick */ - timer_tick(); - break; - - case TrapCause::IrqExternal: { - /* External interrupt: generic claim → dispatch → complete */ - uint32 irq = g_irq_manager.claim(); - if (irq != 0) { - g_irq_manager.dispatch(irq); - g_irq_manager.complete(irq); - } - break; - } - - case TrapCause::IrqSoftware: - /* S-mode software interrupt = IPI from another hart */ - smp_handle_ipi(); - break; - - /* ---- Fatal exceptions ---- */ - case TrapCause::IllegalInst: - if (info.origin == TrapOrigin::FromUser) { - kprintf("[trap] user illegal instruction at epc=0x%llx stval=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGILL, 1 /* ILL_ILLOPC */, info.epc); - } else { - kprintf("[trap] illegal instruction at epc=0x%llx\n", info.epc); - kernel_panic("illegal instruction in kernel mode"); - } - break; - - case TrapCause::AccessFault: - kprintf("[trap] access fault\n"); - kernel_panic("access fault"); - break; - - case TrapCause::AlignFault: - kprintf("[trap] alignment fault\n"); - kernel_panic("alignment fault"); - break; - - case TrapCause::Breakpoint: - kprintf("[trap] breakpoint\n"); - /* ebreak advance is handled by MD layer (arch-specific instruction size) */ - break; - - case TrapCause::Unknown: - default: - kprintf("[trap] unknown trap\n"); - kernel_panic("unknown trap"); - break; - } - - /* Process pending signals before returning to user mode */ - if (info.origin == TrapOrigin::FromUser) { - do_signal(tf); - } - - /* Preemption check on return from any interrupt. - * This is the kernel's equivalent of Linux's retint_kernel path: - * if a timer tick (or IPI) set need_resched, we must call schedule() - * before returning — otherwise the idle loop never yields to - * runnable tasks like PID 1. */ - if (info.cls == TrapClass::Interrupt) { - sched_check_preempt(); - } -} diff --git a/na/kernel/uaccess.cc b/na/kernel/uaccess.cc deleted file mode 100644 index 14c5fb7..0000000 --- a/na/kernel/uaccess.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * User-Kernel Memory Copy — Phase 3 (Exception Tables) - * - * Validates user pointers by checking they fall within user address space - * (below KERNEL_VA_BASE). Uses assembly-level safe copy functions that - * integrate with the kernel's Exception Table mechanism. - * - * Reference: docs/specs/syscall.md §8 - */ - -#include -#include -#include -#include -#include - -extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len); -extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len); - -int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len) -{ - if (len == 0) return 0; - if (!is_user_range(user_src, len)) return -EFAULT; - - return __uaccess_memcpy(kernel_dst, reinterpret_cast(user_src), len); -} - -int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len) -{ - if (len == 0) return 0; - if (!is_user_range(user_dst, len)) return -EFAULT; - - return __uaccess_memcpy(reinterpret_cast(user_dst), kernel_src, len); -} - -int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len) -{ - if (!is_user_range(user_src, 1)) return -EFAULT; - - /* We must also check that the string does not cross into kernel space. - This is safely handled by the assembly routine which will fault if it hits - an unmapped page. However, to strictly respect kKernelVaBase, we should - restrict max_len to the distance to kKernelVaBase. */ - uint64 max_allowed = kKernelVaBase - user_src; - if (max_len > max_allowed) { - max_len = max_allowed; - } - - return __uaccess_strncpy(kernel_dst, reinterpret_cast(user_src), max_len); -} diff --git a/na/kernel/waitqueue.cc b/na/kernel/waitqueue.cc deleted file mode 100644 index 9689417..0000000 --- a/na/kernel/waitqueue.cc +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * WaitQueue — blocking synchronization implementation - * - * Two-phase wait protocol: - * - * prepare_to_wait(): task → Sleeping, link into queue - * [caller checks condition — if still false, calls schedule()] - * finish_wait(): remove from queue, restore Runnable - * - * Legacy sleep() wraps the two-phase pattern for simple callers. - * - * wake_one()/wake_all(): dequeue waiters → Runnable, sched_enqueue() - * - * The internal spinlock protects the wait list only. - * schedule() is called AFTER releasing all spinlocks to avoid - * holding a lock across a context switch. - */ - -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Two-phase wait API */ -/* ------------------------------------------------------------------ */ - -void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state) -{ - LockGuard guard(lock_); - - /* Set state BEFORE linking — this is the key to preventing lost wakeups. - * If wake_one fires between now and schedule(), it will set state back - * to Runnable, and schedule() will see Runnable and not block. */ - task->state = new_state; - - /* Only add to queue if not already linked (idempotent) */ - if (task->run_link.next == &task->run_link) { - /* Link into wait queue tail */ - list_node* node = &task->run_link; - node->prev = head_.prev; - node->next = &head_; - head_.prev->next = node; - head_.prev = node; - } -} - -void WaitQueue::finish_wait(TaskControlBlock* task) -{ - LockGuard guard(lock_); - - /* Remove from wait queue if still linked */ - if (task->run_link.next != &task->run_link) { - list_node* node = &task->run_link; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - } - - /* Ensure task is Runnable (it may have been woken by wake_one/wake_all - * already, or it may never have slept if the condition was true). */ - if (task->state == TaskState::Sleeping) { - task->state = TaskState::Runnable; - } -} - -/* ------------------------------------------------------------------ */ -/* Legacy convenience API */ -/* ------------------------------------------------------------------ */ - -void WaitQueue::sleep(Spinlock* external_lock) -{ - TaskControlBlock* curr = current_task(); - if (!curr) { - if (external_lock) external_lock->release(); - return; - } - - /* Phase 1: add to queue, mark Sleeping */ - prepare_to_wait(curr, TaskState::Sleeping); - - /* Release external lock AFTER we are on the queue (critical ordering) */ - if (external_lock) { - external_lock->release(); - } - - /* Phase 2: context switch away — will return here when woken */ - schedule(); - - /* Phase 3: clean up */ - finish_wait(curr); -} - -/* ------------------------------------------------------------------ */ -/* Wakeup */ -/* ------------------------------------------------------------------ */ - -bool WaitQueue::wake_one() -{ - LockGuard guard(lock_); - - if (head_.is_empty()) - return false; - - /* Dequeue the first (oldest) waiter */ - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - - TaskControlBlock* task = - list_entry(node); - - task->state = TaskState::Runnable; - sched_enqueue(task); - - return true; -} - -int WaitQueue::wake_all() -{ - LockGuard guard(lock_); - - int count = 0; - while (!head_.is_empty()) { - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - - TaskControlBlock* task = - list_entry(node); - - task->state = TaskState::Runnable; - sched_enqueue(task); - count++; - } - - return count; -} diff --git a/na/lib/cxx_abi.cc b/na/lib/cxx_abi.cc deleted file mode 100644 index 872b4a2..0000000 --- a/na/lib/cxx_abi.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * cxx_abi.cc — Minimal C++ ABI stubs for freestanding kernel - * - * Provides symbols that the compiler/linker expects from the C++ - * runtime library. All error paths are fatal (kernel_panic). - * - * Reference: docs/specs/cpp_runtime.md - */ - -#include - -/* Pure virtual function call trap. - * Generated by the compiler in vtable entries for pure virtual methods. - * Reaching this means a derived class failed to implement a pure virtual. */ -extern "C" void __cxa_pure_virtual() -{ - kernel_panic("pure virtual function call"); -} - -/* Global destructor registration. - * The kernel does not support global object destruction — all global - * objects use Placement New with explicit lifetime management. - * If the compiler emits a call to this, it indicates a code violation. */ -extern "C" int __cxa_atexit(void (*)(void*), void*, void*) -{ - kernel_panic("__cxa_atexit called in kernel"); - return 0; // unreachable -} - -/* DSO handle — required by __cxa_atexit ABI. - * The kernel is statically linked; this symbol just needs to exist. */ -extern "C" { -void* __dso_handle __attribute__((visibility("hidden"))) = nullptr; -} diff --git a/na/lib/fdt.c b/na/lib/fdt.c deleted file mode 100644 index 1b20383..0000000 --- a/na/lib/fdt.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt.c" diff --git a/na/lib/fdt_addresses.c b/na/lib/fdt_addresses.c deleted file mode 100644 index d58f896..0000000 --- a/na/lib/fdt_addresses.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_addresses.c" diff --git a/na/lib/fdt_empty_tree.c b/na/lib/fdt_empty_tree.c deleted file mode 100644 index 37f8ef8..0000000 --- a/na/lib/fdt_empty_tree.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_empty_tree.c" diff --git a/na/lib/fdt_ro.c b/na/lib/fdt_ro.c deleted file mode 100644 index 3599d49..0000000 --- a/na/lib/fdt_ro.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_ro.c" diff --git a/na/lib/fdt_rw.c b/na/lib/fdt_rw.c deleted file mode 100644 index 65d7a71..0000000 --- a/na/lib/fdt_rw.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_rw.c" diff --git a/na/lib/fdt_strerror.c b/na/lib/fdt_strerror.c deleted file mode 100644 index dae35ea..0000000 --- a/na/lib/fdt_strerror.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_strerror.c" diff --git a/na/lib/fdt_sw.c b/na/lib/fdt_sw.c deleted file mode 100644 index d04f220..0000000 --- a/na/lib/fdt_sw.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_sw.c" diff --git a/na/lib/fdt_wip.c b/na/lib/fdt_wip.c deleted file mode 100644 index ebfb3c1..0000000 --- a/na/lib/fdt_wip.c +++ /dev/null @@ -1,2 +0,0 @@ -#include -#include "../scripts/dtc/libfdt/fdt_wip.c" diff --git a/na/lib/klog.cc b/na/lib/klog.cc deleted file mode 100644 index 86c67f8..0000000 --- a/na/lib/klog.cc +++ /dev/null @@ -1,574 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * klog.cc — Kernel logging subsystem implementation - * - * Implements the ring buffer, spinlock-protected writes, log level - * filtering, and synchronous console output. - * - * Phase 2 TODO: async flush via kernel thread (see docs/specs/klog.md) - */ - -#include -#include -#include -#include -#include -#include - -/* Forward declaration — mpaland/printf */ -extern "C" int vsnprintf_(char* buffer, size_t count, - const char* format, __builtin_va_list va); - -/* ======================================================================== - * Ring Buffer State - * ======================================================================== */ - -struct KlogEntryHdr { - uint32 seq; - uint32 len; - uint64 ts; - uint16 cpu; - uint8 level; - uint8 subsys; - uint16 reserved; -}; - -static struct { - char data[KLOG_BUF_SIZE]; - uint32 head; /* Next write position */ - uint32 tail; /* Next read position (for /proc/kmsg) */ - uint32 console_tail; /* Next position to flush to console */ - uint32 dropped; - lume::atomic seq; - uint64 subsys_mask; - Spinlock lock; - KlogLevel console_level; /* Messages <= this level go to UART */ - bool initialized; - bool panic_mode; -} g_klog; - -/* ======================================================================== - * Level name table - * ======================================================================== */ - -static const char* klog_level_name(KlogLevel level) -{ - static const char* names[] = { - "EMERG", "ALERT", "CRIT", "ERR", - "WARN", "NOTICE", "INFO", "DEBUG" - }; - if (level <= KLOG_DEBUG) { - return names[level]; - } - return "?"; -} - -static const char* klog_subsys_name(KlogSubsys subsys) -{ - static const char* names[] = { - "GEN", - "SCHED", - "TRAP", - "IRQ", - "TIMER", - "VMM", - "PMM", - "SLAB", - "FS", - "DRV", - "TEST", - }; - if (subsys < KLOG_SUBSYS_MAX) { - return names[subsys]; - } - return "?"; -} - -static const char* parse_kern_level(const char* fmt, KlogLevel* level) -{ - if (!fmt || !level) { - return fmt; - } - - /* Linux-style "\001n" prefix (KERN_SOH) */ - if (fmt[0] == '\001' && fmt[1] >= '0' && fmt[1] <= '7') { - *level = static_cast(fmt[1] - '0'); - return fmt + 2; - } - - /* Alternate "" prefix */ - if (fmt[0] == '<' && fmt[1] >= '0' && fmt[1] <= '7' && fmt[2] == '>') { - *level = static_cast(fmt[1] - '0'); - return fmt + 3; - } - - return fmt; -} - -/* ======================================================================== - * Ring Buffer Operations - * ======================================================================== */ - -/* Write a byte to the ring buffer. Overwrites oldest data on overflow. */ -static inline uint32 min_u32(uint32 a, uint32 b) -{ - return (a < b) ? a : b; -} - -static void ring_write_bytes(const void* src, uint32 len) -{ - const char* p = reinterpret_cast(src); - for (uint32 i = 0; i < len; ++i) { - g_klog.data[(g_klog.head + i) & (KLOG_BUF_SIZE - 1)] = p[i]; - } - g_klog.head += len; -} - -static void ring_read_bytes(uint32 pos, void* dst, uint32 len) -{ - char* out = reinterpret_cast(dst); - for (uint32 i = 0; i < len; ++i) { - out[i] = g_klog.data[(pos + i) & (KLOG_BUF_SIZE - 1)]; - } -} - -static void ring_drop_oldest(uint32 needed) -{ - uint32 min_tail = min_u32(g_klog.tail, g_klog.console_tail); - - while (KLOG_BUF_SIZE - (g_klog.head - min_tail) < needed) { - KlogEntryHdr hdr; - ring_read_bytes(min_tail, &hdr, sizeof(hdr)); - - uint32 entry = sizeof(hdr) + hdr.len; - if (entry == 0 || entry > KLOG_BUF_SIZE) { - min_tail++; - g_klog.dropped++; - continue; - } - min_tail += entry; - g_klog.dropped++; - } - - if (g_klog.tail < min_tail) { - g_klog.tail = min_tail; - } - if (g_klog.console_tail < min_tail) { - g_klog.console_tail = min_tail; - } -} - -/* ======================================================================== - * Console Output (synchronous) - * ======================================================================== */ - -static void console_emit(const char* s, int len) -{ - console_write_all(s, len); -} - -static int klog_format_line(char* out, int out_size, - const KlogEntryHdr& hdr, - const char* msg, int msg_len) -{ - const char* lvl = klog_level_name(static_cast(hdr.level)); - const char* subsys = klog_subsys_name(static_cast(hdr.subsys)); - - int prefix = ksnprintf(out, out_size, - "[%s][cpu%u][%llu][%s] ", - lvl, (uint32)hdr.cpu, - (unsigned long long)hdr.ts, subsys); - if (prefix < 0) { - return 0; - } - - int total = prefix; - int avail = out_size - 1 - total; - if (avail > 0) { - int to_copy = (msg_len < avail) ? msg_len : avail; - for (int i = 0; i < to_copy; ++i) { - out[total + i] = msg[i]; - } - total += to_copy; - } - - if (total > 0 && out[total - 1] != '\n' && total < out_size - 1) { - out[total++] = '\n'; - } - - out[total] = '\0'; - return total; -} - -static int klog_read_internal(char* buf, int size, uint32* tail_ptr, bool consume) -{ - if (size <= 0) { - return 0; - } - - int written = 0; - uint32 pos = *tail_ptr; - - while (pos != g_klog.head) { - KlogEntryHdr hdr; - ring_read_bytes(pos, &hdr, sizeof(hdr)); - uint32 raw_len = hdr.len; - if (raw_len > KLOG_BUF_SIZE) { - break; - } - uint32 copy_len = raw_len; - if (copy_len > KLOG_LINE_MAX) { - copy_len = KLOG_LINE_MAX; - } - - char msg[KLOG_LINE_MAX + 1]; - ring_read_bytes(pos + sizeof(hdr), msg, copy_len); - msg[copy_len] = '\0'; - - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), hdr, msg, - static_cast(copy_len)); - - if (line_len <= 0 || written + line_len > size) { - break; - } - - for (int i = 0; i < line_len; ++i) { - buf[written + i] = line[i]; - } - written += line_len; - - pos += sizeof(hdr) + raw_len; - } - - if (consume) { - *tail_ptr = pos; - } - - return written; -} - -static void klog_vex(KlogLevel level, KlogSubsys subsys, - const char* fmt, __builtin_va_list va); - -/* ======================================================================== - * Public API - * ======================================================================== */ - -void klog_init(void) -{ - g_klog.head = 0; - g_klog.tail = 0; - g_klog.console_tail = 0; - g_klog.dropped = 0; - g_klog.seq.store(0, __ATOMIC_RELAXED); - g_klog.subsys_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; - g_klog.console_level = static_cast(KLOG_DEFAULT_LEVEL); - g_klog.initialized = true; - g_klog.panic_mode = false; - g_klog.lock.init("klog"); -} - -void klog(KlogLevel level, const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - klog_vex(level, KLOG_SUBSYS_GENERIC, fmt, va); - __builtin_va_end(va); -} - -void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - klog_vex(level, subsys, fmt, va); - __builtin_va_end(va); -} - -static void klog_vex(KlogLevel level, KlogSubsys subsys, - const char* fmt, __builtin_va_list va) -{ - if (subsys >= KLOG_SUBSYS_MAX) { - subsys = KLOG_SUBSYS_GENERIC; - } - - if (g_klog.initialized && ((g_klog.subsys_mask & (1ULL << subsys)) == 0)) { - return; - } - - char msg[KLOG_LINE_MAX + 1]; - int msg_len = vsnprintf_(msg, KLOG_LINE_MAX, fmt, va); - - if (msg_len < 0) { - return; - } - if (msg_len > KLOG_LINE_MAX) { - msg_len = KLOG_LINE_MAX; - } - msg[msg_len] = '\0'; - - KlogEntryHdr hdr; - hdr.seq = g_klog.seq.fetch_add(1, __ATOMIC_RELAXED); - hdr.len = static_cast(msg_len); - hdr.ts = arch::cpu::read_time(); - hdr.cpu = static_cast(arch::cpu::id()); - hdr.level = static_cast(level); - hdr.subsys = static_cast(subsys); - hdr.reserved = 0; - - bool do_console = (!g_klog.initialized || level <= g_klog.console_level); - - if (g_klog.initialized && !g_klog.panic_mode) { - LockGuard guard(g_klog.lock); - ring_drop_oldest(sizeof(hdr) + hdr.len); - ring_write_bytes(&hdr, sizeof(hdr)); - ring_write_bytes(msg, hdr.len); - if (do_console) { - g_klog.console_tail = g_klog.head; - /* Emit to UART while holding the lock to prevent - * interleaved output from concurrent CPUs. */ - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), - hdr, msg, hdr.len); - if (line_len > 0) { - console_emit(line, line_len); - } - } - } else if (do_console) { - /* Pre-init or panic mode: emit without lock */ - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), - hdr, msg, hdr.len); - if (line_len > 0) { - console_emit(line, line_len); - } - } -} - -int vprintk(const char* fmt, __builtin_va_list va) -{ - KlogLevel level = KLOG_INFO; - const char* real_fmt = parse_kern_level(fmt, &level); - - __builtin_va_list va_copy; - __builtin_va_copy(va_copy, va); - - char tmp[KLOG_LINE_MAX + 1]; - int len = vsnprintf_(tmp, KLOG_LINE_MAX, real_fmt, va_copy); - __builtin_va_end(va_copy); - - klog_vex(level, KLOG_SUBSYS_GENERIC, real_fmt, va); - - if (len < 0) { - return 0; - } - return len; -} - -int printk(const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - int ret = vprintk(fmt, va); - __builtin_va_end(va); - return ret; -} - -void klog_set_level(KlogLevel level) -{ - g_klog.console_level = level; -} - -void klog_set_subsys_mask(uint64 mask) -{ - g_klog.subsys_mask = mask; -} - -void klog_enable_subsys(KlogSubsys subsys) -{ - if (subsys < KLOG_SUBSYS_MAX) { - g_klog.subsys_mask |= (1ULL << subsys); - } -} - -void klog_disable_subsys(KlogSubsys subsys) -{ - if (subsys < KLOG_SUBSYS_MAX) { - g_klog.subsys_mask &= ~(1ULL << subsys); - } -} - -int klog_read(char* buf, int size) -{ - if (!g_klog.initialized || size <= 0) { - return 0; - } - - LockGuard guard(g_klog.lock); - return klog_read_internal(buf, size, &g_klog.tail, false); -} - -int klog_read_consume(char* buf, int size) -{ - if (!g_klog.initialized || size <= 0) { - return 0; - } - - LockGuard guard(g_klog.lock); - return klog_read_internal(buf, size, &g_klog.tail, true); -} - -void klog_flush(void) -{ - if (!g_klog.initialized) { - return; - } - - LockGuard guard(g_klog.lock); - - uint32 pos = g_klog.console_tail; - while (pos != g_klog.head) { - KlogEntryHdr hdr; - ring_read_bytes(pos, &hdr, sizeof(hdr)); - uint32 raw_len = hdr.len; - if (raw_len > KLOG_BUF_SIZE) { - break; - } - uint32 copy_len = raw_len; - if (copy_len > KLOG_LINE_MAX) { - copy_len = KLOG_LINE_MAX; - } - - char msg[KLOG_LINE_MAX + 1]; - ring_read_bytes(pos + sizeof(hdr), msg, copy_len); - msg[copy_len] = '\0'; - - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), hdr, msg, - static_cast(copy_len)); - if (line_len > 0) { - console_emit(line, line_len); - } - - pos += sizeof(hdr) + raw_len; - } - - g_klog.console_tail = pos; -} - -bool klog_ratelimit(KlogRateLimit* rl, uint64 now) -{ - if (!rl) { - return true; - } - - if (rl->interval == 0 || rl->burst == 0) { - return true; - } - - if (now - rl->last_ts >= rl->interval) { - rl->last_ts = now; - rl->printed = 0; - rl->suppressed = 0; - } - - if (rl->printed < rl->burst) { - rl->printed++; - return true; - } - - rl->suppressed++; - return false; -} - -void klog_stats(KlogStats* out) -{ - if (!out) { - return; - } - - out->head = g_klog.head; - out->tail = g_klog.tail; - out->console_tail = g_klog.console_tail; - out->dropped = g_klog.dropped; - out->seq = g_klog.seq.load(__ATOMIC_RELAXED); -} - -/* ======================================================================== - * Kernel Panic — Fatal Error Handler - * - * Dumps all critical S-mode CSRs and the stack pointer to the console, - * then halts the CPU. Uses kprintf (NOT klog) to avoid deadlocking - * if the panic was triggered while holding the klog spinlock. - * ======================================================================== */ - -extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) { - /* Disable interrupts immediately */ - arch::cpu::intr_off(); - - g_klog.panic_mode = true; - - /* ---- Banner ---- */ - kprintf("\n\n"); - kprintf("========================================\n"); - kprintf(" *** KERNEL PANIC ***\n"); - kprintf("========================================\n"); - - if (msg != nullptr) { - kprintf(" Reason : %s", msg); - if (detail != nullptr) { - kprintf(": %s", detail); - } - kprintf("\n"); - } - - /* ---- Read S-mode CSRs ---- */ - uint64 sepc, scause, stval, sstatus, satp, stvec, sscratch, sp_val; - - __asm__ volatile("csrr %0, sepc" : "=r"(sepc)); - __asm__ volatile("csrr %0, scause" : "=r"(scause)); - __asm__ volatile("csrr %0, stval" : "=r"(stval)); - __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); - __asm__ volatile("csrr %0, satp" : "=r"(satp)); - __asm__ volatile("csrr %0, stvec" : "=r"(stvec)); - __asm__ volatile("csrr %0, sscratch" : "=r"(sscratch)); - __asm__ volatile("mv %0, sp" : "=r"(sp_val)); - - kprintf("\n--- S-mode CSR Dump ---\n"); - kprintf(" sepc = 0x%016llx\n", sepc); - kprintf(" scause = 0x%016llx", scause); - - /* Decode scause for convenience */ - bool is_interrupt = (scause >> 63) != 0; - uint64 cause_code = scause & 0x7FFFFFFFFFFFFFFFULL; - if (is_interrupt) { - kprintf(" (interrupt, code=%llu)\n", cause_code); - } else { - kprintf(" (exception, code=%llu)\n", cause_code); - } - - kprintf(" stval = 0x%016llx\n", stval); - kprintf(" sstatus = 0x%016llx", sstatus); - kprintf(" [SIE=%llu SPP=%llu SPIE=%llu]\n", - (sstatus >> 1) & 1, /* SIE */ - (sstatus >> 8) & 1, /* SPP */ - (sstatus >> 5) & 1); /* SPIE */ - kprintf(" satp = 0x%016llx", satp); - uint64 satp_mode = satp >> 60; - kprintf(" [mode=%llu]\n", satp_mode); - kprintf(" stvec = 0x%016llx\n", stvec); - kprintf(" sscratch = 0x%016llx\n", sscratch); - kprintf(" sp = 0x%016llx\n", sp_val); - - kprintf(" cpu_id = %llu\n", arch::cpu::id()); - kprintf(" time = %llu\n", arch::cpu::read_time()); - - kprintf("========================================\n\n"); - - /* Halt — spin forever with WFI to save power */ - while (true) { - arch::cpu::halt_until_interrupt(); - } -} diff --git a/na/lib/kobject.cc b/na/lib/kobject.cc deleted file mode 100644 index 8e8329e..0000000 --- a/na/lib/kobject.cc +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * kobject — implementation. - * - * Lives in lib/ (mirroring Linux's lib/kobject.c) because kobject is a - * generic primitive, not tied to the driver model. - * - * Reference: docs/specs/storage_stack.md §4.2 - */ - -#include -#include -#include - -void kobject_init(kobject* k, const kobj_type* type) -{ - if (k == nullptr) { - return; - } - k->name = nullptr; - k->parent = nullptr; - k->ktype = type; - k->state_in_sysfs = false; - k->entry.init(); - k->children.init(); - k->lock.init("kobject"); - kref_init(&k->refcount); -} - -int kobject_add(kobject* k, kobject* parent, const char* name) -{ - if (k == nullptr) { - return -1; - } - k->name = name; - if (parent != nullptr) { - kobject_get(parent); - k->parent = parent; - LockGuard g(parent->lock); - list_add_tail(&k->entry, &parent->children); - } - return 0; -} - -void kobject_del(kobject* k) -{ - if (k == nullptr || k->parent == nullptr) { - return; - } - kobject* parent = k->parent; - { - LockGuard g(parent->lock); - list_del_init(&k->entry); - } - k->parent = nullptr; - kobject_put(parent); -} - -void kobject_get(kobject* k) -{ - if (k != nullptr) { - kref_get(&k->refcount); - } -} - -static void kobject_release(kref* r) -{ - kobject* k = container_of(r, kobject, refcount); - if (k->ktype != nullptr && k->ktype->release != nullptr) { - k->ktype->release(k); - } -} - -void kobject_put(kobject* k) -{ - if (k == nullptr) { - return; - } - kref_put(&k->refcount, kobject_release); -} diff --git a/na/lib/kprintf.cc b/na/lib/kprintf.cc deleted file mode 100644 index a98de71..0000000 --- a/na/lib/kprintf.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * kprintf.cc — Kernel formatted output - * - * Uses mpaland/printf's vsnprintf_ as the core formatting engine. - * - * Architecture: - * vsnprintf_() ← pure formatting, writes to buffer - * ↓ - * kprintf() ← format + emit via console_write_all() - * kvprintf() ← va_list variant - * ksnprintf() ← format into caller's buffer - * - * Output path: - * Pre-registration: console_write_all() → early_putc() fallback - * Post-registration: console_write_all() → struct console chain - */ - -#include -#include -#include -#include -#include "printf.h" - -/* ------------------------------------------------------------------ */ -/* Console spinlock (raw, no scheduler dependency — safe in panic) */ -/* ------------------------------------------------------------------ */ - -static lume::atomic g_console_lock{0}; - -static inline void console_lock_acquire() -{ - while (g_console_lock.exchange(1, __ATOMIC_ACQUIRE) != 0) { - /* spin */ - } -} - -static inline void console_lock_release() -{ - g_console_lock.store(0, __ATOMIC_RELEASE); -} - -/* ------------------------------------------------------------------ */ -/* _putchar: required by mpaland/printf for printf_() / vprintf_() */ -/* ------------------------------------------------------------------ */ - -void _putchar(char character) -{ - early_putc(character); -} - -/* ------------------------------------------------------------------ */ -/* Public API */ -/* ------------------------------------------------------------------ */ - -extern "C" int kprintf(const char* fmt, ...) -{ - char buf[256]; - va_list va; - va_start(va, fmt); - int len = vsnprintf_(buf, sizeof(buf), fmt, va); - va_end(va); - - /* Emit to console under lock */ - console_lock_acquire(); - console_write_all(buf, len); - console_lock_release(); - return len; -} - -extern "C" int kvprintf(const char* fmt, va_list va) -{ - char buf[256]; - int len = vsnprintf_(buf, sizeof(buf), fmt, va); - - console_lock_acquire(); - console_write_all(buf, len); - console_lock_release(); - return len; -} - -extern "C" int ksnprintf(char* buf, int count, const char* fmt, ...) -{ - va_list va; - va_start(va, fmt); - int ret = vsnprintf_(buf, (size_t)count, fmt, va); - va_end(va); - return ret; -} diff --git a/na/lib/module.mk b/na/lib/module.mk deleted file mode 100644 index 0b178f8..0000000 --- a/na/lib/module.mk +++ /dev/null @@ -1,66 +0,0 @@ -# lib/module.mk -SRCS_CC += lib/cxx_abi.cc -SRCS_CC += lib/rbtree.cc -SRCS_CC += lib/kobject.cc -SRCS_CC += lib/kprintf.cc -SRCS_CC += lib/klog.cc -SRCS_CC += lib/random.cc -SRCS_C += lib/printf.c - -# ============================================================ -# musl string library (cross-platform with arch asm overrides) -# ============================================================ - -# Map LumeOS ARCH to musl string arch directory -# riscv has no musl asm optimizations → pure C fallback -MUSL_STRING_ARCH_riscv := -MUSL_STRING_ARCH_x86_64 := x86_64 -MUSL_STRING_ARCH_i386 := i386 -MUSL_STRING_ARCH_aarch64:= aarch64 -MUSL_STRING_ARCH_arm := arm - -MUSL_STRING_ARCH := $(MUSL_STRING_ARCH_$(ARCH)) - -# Collect arch-specific asm sources (if any for this arch) -MUSL_ASM_SRCS := -ifneq ($(MUSL_STRING_ARCH),) - MUSL_ASM_SRCS += $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.S) \ - $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.s) -endif - -# Derive which C files are overridden by asm (strip path+ext to get base name) -MUSL_ASM_BASENAMES := $(sort $(basename $(notdir $(MUSL_ASM_SRCS)))) - -# All C sources, then filter out those with asm replacements -MUSL_ALL_C := $(wildcard lib/string/*.c) -MUSL_EXCLUDED_C := $(foreach name,$(MUSL_ASM_BASENAMES),lib/string/$(name).c) -MUSL_C_SRCS := $(filter-out $(MUSL_EXCLUDED_C),$(MUSL_ALL_C)) - -SRCS_C += $(MUSL_C_SRCS) -SRCS_S += $(filter %.S,$(MUSL_ASM_SRCS)) -SRCS_s += $(filter %.s,$(MUSL_ASM_SRCS)) - -# ============================================================ -# libfdt — kernel adapter shims around scripts/dtc/libfdt/. -# -# Each shim is two lines: -# #include -# #include "../scripts/dtc/libfdt/fdt*.c" -# matching Linux lib/fdt*.c verbatim. The compile recipe needs: -# - reachable via the default -Iinclude path -# - -I scripts/dtc/libfdt so the upstream source's #include -# and friends resolve to scripts/dtc/libfdt/libfdt.h -# Overriding CFLAGS_LIBC (which the %.o: %.c rule uses) avoids dragging -# in libc-shim headers — libfdt is freestanding-compatible and uses -# only the string functions forward-declared in . -# ============================================================ -LIBFDT_FILES := fdt.c fdt_ro.c fdt_wip.c fdt_rw.c fdt_sw.c \ - fdt_strerror.c fdt_empty_tree.c fdt_addresses.c -SRCS_C += $(addprefix lib/,$(LIBFDT_FILES)) - -# Per-target CFLAGS override: plain CFLAGS plus the upstream include dir. -# Use `=` (recursive) not `:=` because $(CFLAGS) is defined in the top -# Makefile AFTER this file is included — `:=` would expand it now (empty) -# and lose -Iinclude, breaking . -$(addprefix $(BUILD)/lib/,$(LIBFDT_FILES:.c=.o)): CFLAGS_LIBC = \ - $(CFLAGS) -I scripts/dtc/libfdt diff --git a/na/lib/printf.c b/na/lib/printf.c deleted file mode 100644 index 8a700ad..0000000 --- a/na/lib/printf.c +++ /dev/null @@ -1,914 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// \author (c) Marco Paland (info@paland.com) -// 2014-2019, PALANDesign Hannover, Germany -// -// \license The MIT License (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on -// embedded systems with a very limited resources. These routines are thread -// safe and reentrant! -// Use this instead of the bloated standard/newlib printf cause these use -// malloc for printf (and may not be thread safe). -// -/////////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "printf.h" - - -// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the -// printf_config.h header file -// default: undefined -#ifdef PRINTF_INCLUDE_CONFIG_H -#include "printf_config.h" -#endif - - -// 'ntoa' conversion buffer size, this must be big enough to hold one converted -// numeric number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_NTOA_BUFFER_SIZE -#define PRINTF_NTOA_BUFFER_SIZE 32U -#endif - -// 'ftoa' conversion buffer size, this must be big enough to hold one converted -// float number including padded zeros (dynamically created on stack) -// default: 32 byte -#ifndef PRINTF_FTOA_BUFFER_SIZE -#define PRINTF_FTOA_BUFFER_SIZE 32U -#endif - -// support for the floating point type (%f) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_FLOAT -#define PRINTF_SUPPORT_FLOAT -#endif - -// support for exponential floating point notation (%e/%g) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL -#define PRINTF_SUPPORT_EXPONENTIAL -#endif - -// define the default floating point precision -// default: 6 digits -#ifndef PRINTF_DEFAULT_FLOAT_PRECISION -#define PRINTF_DEFAULT_FLOAT_PRECISION 6U -#endif - -// define the largest float suitable to print with %f -// default: 1e9 -#ifndef PRINTF_MAX_FLOAT -#define PRINTF_MAX_FLOAT 1e9 -#endif - -// support for the long long types (%llu or %p) -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG -#define PRINTF_SUPPORT_LONG_LONG -#endif - -// support for the ptrdiff_t type (%t) -// ptrdiff_t is normally defined in as long or long long type -// default: activated -#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T -#define PRINTF_SUPPORT_PTRDIFF_T -#endif - -/////////////////////////////////////////////////////////////////////////////// - -// internal flag definitions -#define FLAGS_ZEROPAD (1U << 0U) -#define FLAGS_LEFT (1U << 1U) -#define FLAGS_PLUS (1U << 2U) -#define FLAGS_SPACE (1U << 3U) -#define FLAGS_HASH (1U << 4U) -#define FLAGS_UPPERCASE (1U << 5U) -#define FLAGS_CHAR (1U << 6U) -#define FLAGS_SHORT (1U << 7U) -#define FLAGS_LONG (1U << 8U) -#define FLAGS_LONG_LONG (1U << 9U) -#define FLAGS_PRECISION (1U << 10U) -#define FLAGS_ADAPT_EXP (1U << 11U) - - -// import float.h for DBL_MAX -#if defined(PRINTF_SUPPORT_FLOAT) -#include -#endif - - -// output function type -typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); - - -// wrapper (used as buffer) for output function type -typedef struct { - void (*fct)(char character, void* arg); - void* arg; -} out_fct_wrap_type; - - -// internal buffer output -static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) -{ - if (idx < maxlen) { - ((char*)buffer)[idx] = character; - } -} - - -// internal null output -static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)character; (void)buffer; (void)idx; (void)maxlen; -} - - -// internal _putchar wrapper -static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)buffer; (void)idx; (void)maxlen; - if (character) { - _putchar(character); - } -} - - -// internal output function wrapper -static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) -{ - (void)idx; (void)maxlen; - if (character) { - // buffer is the output fct pointer - ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); - } -} - - -// internal secure strlen -// \return The length of the string (excluding the terminating 0) limited by 'maxsize' -static inline unsigned int _strnlen_s(const char* str, size_t maxsize) -{ - const char* s; - for (s = str; *s && maxsize--; ++s); - return (unsigned int)(s - str); -} - - -// internal test if char is a digit (0-9) -// \return true if char is a digit -static inline bool _is_digit(char ch) -{ - return (ch >= '0') && (ch <= '9'); -} - - -// internal ASCII string to unsigned int conversion -static unsigned int _atoi(const char** str) -{ - unsigned int i = 0U; - while (_is_digit(**str)) { - i = i * 10U + (unsigned int)(*((*str)++) - '0'); - } - return i; -} - - -// output the specified string in reverse, taking care of any zero-padding -static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) -{ - const size_t start_idx = idx; - - // pad spaces up to given width - if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { - for (size_t i = len; i < width; i++) { - out(' ', buffer, idx++, maxlen); - } - } - - // reverse string - while (len) { - out(buf[--len], buffer, idx++, maxlen); - } - - // append pad spaces up to given width - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) { - out(' ', buffer, idx++, maxlen); - } - } - - return idx; -} - - -// internal itoa format -static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) -{ - // pad leading zeros - if (!(flags & FLAGS_LEFT)) { - if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - // handle hash - if (flags & FLAGS_HASH) { - if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { - len--; - if (len && (base == 16U)) { - len--; - } - } - if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'x'; - } - else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'X'; - } - else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { - buf[len++] = 'b'; - } - if (len < PRINTF_NTOA_BUFFER_SIZE) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_NTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -// internal itoa for 'long' type -static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} - - -// internal itoa for 'long long' type -#if defined(PRINTF_SUPPORT_LONG_LONG) -static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_NTOA_BUFFER_SIZE]; - size_t len = 0U; - - // no hash for 0 values - if (!value) { - flags &= ~FLAGS_HASH; - } - - // write if precision != 0 and value is != 0 - if (!(flags & FLAGS_PRECISION) || value) { - do { - const char digit = (char)(value % base); - buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; - value /= base; - } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); - } - - return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); -} -#endif // PRINTF_SUPPORT_LONG_LONG - - -#if defined(PRINTF_SUPPORT_FLOAT) - -#if defined(PRINTF_SUPPORT_EXPONENTIAL) -// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT -static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); -#endif - - -// internal ftoa for fixed decimal floating point -static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) -{ - char buf[PRINTF_FTOA_BUFFER_SIZE]; - size_t len = 0U; - double diff = 0.0; - - // powers of 10 - static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; - - // test for special values - if (value != value) - return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); - if (value < -DBL_MAX) - return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); - if (value > DBL_MAX) - return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); - - // test for very large values - // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad - if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { -#if defined(PRINTF_SUPPORT_EXPONENTIAL) - return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); -#else - return 0U; -#endif - } - - // test for negative - bool negative = false; - if (value < 0) { - negative = true; - value = 0 - value; - } - - // set default precision, if not set explicitly - if (!(flags & FLAGS_PRECISION)) { - prec = PRINTF_DEFAULT_FLOAT_PRECISION; - } - // limit precision to 9, cause a prec >= 10 can lead to overflow errors - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { - buf[len++] = '0'; - prec--; - } - - int whole = (int)value; - double tmp = (value - whole) * pow10[prec]; - unsigned long frac = (unsigned long)tmp; - diff = tmp - frac; - - if (diff > 0.5) { - ++frac; - // handle rollover, e.g. case 0.99 with prec 1 is 1.0 - if (frac >= pow10[prec]) { - frac = 0; - ++whole; - } - } - else if (diff < 0.5) { - } - else if ((frac == 0U) || (frac & 1U)) { - // if halfway, round up if odd OR if last digit is 0 - ++frac; - } - - if (prec == 0U) { - diff = value - (double)whole; - if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { - // exactly 0.5 and ODD, then round up - // 1.5 -> 2, but 2.5 -> 2 - ++whole; - } - } - else { - unsigned int count = prec; - // now do fractional part, as an unsigned number - while (len < PRINTF_FTOA_BUFFER_SIZE) { - --count; - buf[len++] = (char)(48U + (frac % 10U)); - if (!(frac /= 10U)) { - break; - } - } - // add extra 0s - while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { - buf[len++] = '0'; - } - if (len < PRINTF_FTOA_BUFFER_SIZE) { - // add decimal - buf[len++] = '.'; - } - } - - // do whole part, number is reversed - while (len < PRINTF_FTOA_BUFFER_SIZE) { - buf[len++] = (char)(48 + (whole % 10)); - if (!(whole /= 10)) { - break; - } - } - - // pad leading zeros - if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { - if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { - width--; - } - while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { - buf[len++] = '0'; - } - } - - if (len < PRINTF_FTOA_BUFFER_SIZE) { - if (negative) { - buf[len++] = '-'; - } - else if (flags & FLAGS_PLUS) { - buf[len++] = '+'; // ignore the space if the '+' exists - } - else if (flags & FLAGS_SPACE) { - buf[len++] = ' '; - } - } - - return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); -} - - -#if defined(PRINTF_SUPPORT_EXPONENTIAL) -// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse -static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) -{ - // check for NaN and special values - if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { - return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); - } - - // determine the sign - const bool negative = value < 0; - if (negative) { - value = -value; - } - - // default precision - if (!(flags & FLAGS_PRECISION)) { - prec = PRINTF_DEFAULT_FLOAT_PRECISION; - } - - // determine the decimal exponent - // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) - union { - uint64_t U; - double F; - } conv; - - conv.F = value; - int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 - conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) - // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 - int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); - // now we want to compute 10^expval but we want to be sure it won't overflow - exp2 = (int)(expval * 3.321928094887362 + 0.5); - const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; - const double z2 = z * z; - conv.U = (uint64_t)(exp2 + 1023) << 52U; - // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex - conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); - // correct for rounding errors - if (value < conv.F) { - expval--; - conv.F /= 10; - } - - // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters - unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; - - // in "%g" mode, "prec" is the number of *significant figures* not decimals - if (flags & FLAGS_ADAPT_EXP) { - // do we want to fall-back to "%f" mode? - if ((value >= 1e-4) && (value < 1e6)) { - if ((int)prec > expval) { - prec = (unsigned)((int)prec - expval - 1); - } - else { - prec = 0; - } - flags |= FLAGS_PRECISION; // make sure _ftoa respects precision - // no characters in exponent - minwidth = 0U; - expval = 0; - } - else { - // we use one sigfig for the whole part - if ((prec > 0) && (flags & FLAGS_PRECISION)) { - --prec; - } - } - } - - // will everything fit? - unsigned int fwidth = width; - if (width > minwidth) { - // we didn't fall-back so subtract the characters required for the exponent - fwidth -= minwidth; - } else { - // not enough characters, so go back to default sizing - fwidth = 0U; - } - if ((flags & FLAGS_LEFT) && minwidth) { - // if we're padding on the right, DON'T pad the floating part - fwidth = 0U; - } - - // rescale the float value - if (expval) { - value /= conv.F; - } - - // output the floating part - const size_t start_idx = idx; - idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); - - // output the exponent part - if (minwidth) { - // output the exponential symbol - out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); - // output the exponent value - idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); - // might need to right-pad spaces - if (flags & FLAGS_LEFT) { - while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); - } - } - return idx; -} -#endif // PRINTF_SUPPORT_EXPONENTIAL -#endif // PRINTF_SUPPORT_FLOAT - - -// internal vsnprintf -static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) -{ - unsigned int flags, width, precision, n; - size_t idx = 0U; - - if (!buffer) { - // use null output function - out = _out_null; - } - - while (*format) - { - // format specifier? %[flags][width][.precision][length] - if (*format != '%') { - // no - out(*format, buffer, idx++, maxlen); - format++; - continue; - } - else { - // yes, evaluate it - format++; - } - - // evaluate flags - flags = 0U; - do { - switch (*format) { - case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; - case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; - case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; - case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; - case '#': flags |= FLAGS_HASH; format++; n = 1U; break; - default : n = 0U; break; - } - } while (n); - - // evaluate width field - width = 0U; - if (_is_digit(*format)) { - width = _atoi(&format); - } - else if (*format == '*') { - const int w = va_arg(va, int); - if (w < 0) { - flags |= FLAGS_LEFT; // reverse padding - width = (unsigned int)-w; - } - else { - width = (unsigned int)w; - } - format++; - } - - // evaluate precision field - precision = 0U; - if (*format == '.') { - flags |= FLAGS_PRECISION; - format++; - if (_is_digit(*format)) { - precision = _atoi(&format); - } - else if (*format == '*') { - const int prec = (int)va_arg(va, int); - precision = prec > 0 ? (unsigned int)prec : 0U; - format++; - } - } - - // evaluate length field - switch (*format) { - case 'l' : - flags |= FLAGS_LONG; - format++; - if (*format == 'l') { - flags |= FLAGS_LONG_LONG; - format++; - } - break; - case 'h' : - flags |= FLAGS_SHORT; - format++; - if (*format == 'h') { - flags |= FLAGS_CHAR; - format++; - } - break; -#if defined(PRINTF_SUPPORT_PTRDIFF_T) - case 't' : - flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; -#endif - case 'j' : - flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - case 'z' : - flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); - format++; - break; - default : - break; - } - - // evaluate specifier - switch (*format) { - case 'd' : - case 'i' : - case 'u' : - case 'x' : - case 'X' : - case 'o' : - case 'b' : { - // set the base - unsigned int base; - if (*format == 'x' || *format == 'X') { - base = 16U; - } - else if (*format == 'o') { - base = 8U; - } - else if (*format == 'b') { - base = 2U; - } - else { - base = 10U; - flags &= ~FLAGS_HASH; // no hash for dec format - } - // uppercase - if (*format == 'X') { - flags |= FLAGS_UPPERCASE; - } - - // no plus or space flag for u, x, X, o, b - if ((*format != 'i') && (*format != 'd')) { - flags &= ~(FLAGS_PLUS | FLAGS_SPACE); - } - - // ignore '0' flag when precision is given - if (flags & FLAGS_PRECISION) { - flags &= ~FLAGS_ZEROPAD; - } - - // convert the integer - if ((*format == 'i') || (*format == 'd')) { - // signed - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - const long long value = va_arg(va, long long); - idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - const long value = va_arg(va, long); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - else { - const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); - } - } - else { - // unsigned - if (flags & FLAGS_LONG_LONG) { -#if defined(PRINTF_SUPPORT_LONG_LONG) - idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); -#endif - } - else if (flags & FLAGS_LONG) { - idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); - } - else { - const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); - idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); - } - } - format++; - break; - } -#if defined(PRINTF_SUPPORT_FLOAT) - case 'f' : - case 'F' : - if (*format == 'F') flags |= FLAGS_UPPERCASE; - idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); - format++; - break; -#if defined(PRINTF_SUPPORT_EXPONENTIAL) - case 'e': - case 'E': - case 'g': - case 'G': - if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; - if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; - idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); - format++; - break; -#endif // PRINTF_SUPPORT_EXPONENTIAL -#endif // PRINTF_SUPPORT_FLOAT - case 'c' : { - unsigned int l = 1U; - // pre padding - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // char output - out((char)va_arg(va, int), buffer, idx++, maxlen); - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 's' : { - const char* p = va_arg(va, char*); - unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); - // pre padding - if (flags & FLAGS_PRECISION) { - l = (l < precision ? l : precision); - } - if (!(flags & FLAGS_LEFT)) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - // string output - while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { - out(*(p++), buffer, idx++, maxlen); - } - // post padding - if (flags & FLAGS_LEFT) { - while (l++ < width) { - out(' ', buffer, idx++, maxlen); - } - } - format++; - break; - } - - case 'p' : { - width = sizeof(void*) * 2U; - flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; -#if defined(PRINTF_SUPPORT_LONG_LONG) - const bool is_ll = sizeof(uintptr_t) == sizeof(long long); - if (is_ll) { - idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); - } - else { -#endif - idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); -#if defined(PRINTF_SUPPORT_LONG_LONG) - } -#endif - format++; - break; - } - - case '%' : - out('%', buffer, idx++, maxlen); - format++; - break; - - default : - out(*format, buffer, idx++, maxlen); - format++; - break; - } - } - - // termination - out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); - - // return written chars without terminating \0 - return (int)idx; -} - - -/////////////////////////////////////////////////////////////////////////////// - -int printf_(const char* format, ...) -{ - va_list va; - va_start(va, format); - char buffer[1]; - const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int sprintf_(char* buffer, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); - va_end(va); - return ret; -} - - -int snprintf_(char* buffer, size_t count, const char* format, ...) -{ - va_list va; - va_start(va, format); - const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); - va_end(va); - return ret; -} - - -int vprintf_(const char* format, va_list va) -{ - char buffer[1]; - return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); -} - - -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) -{ - return _vsnprintf(_out_buffer, buffer, count, format, va); -} - - -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) -{ - va_list va; - va_start(va, format); - const out_fct_wrap_type out_fct_wrap = { out, arg }; - const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); - va_end(va); - return ret; -} diff --git a/na/lib/printf.h b/na/lib/printf.h deleted file mode 100644 index 8aa23cf..0000000 --- a/na/lib/printf.h +++ /dev/null @@ -1,129 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// \author (c) Marco Paland (info@paland.com) -// 2014-2019, PALANDesign Hannover, Germany -// -// \license The MIT License (MIT) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on -// embedded systems with a very limited resources. -// Use this instead of bloated standard/newlib printf. -// These routines are thread safe and reentrant. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _PRINTF_H_ -#define _PRINTF_H_ - -#include - -/* LumeOS: avoid stddef.h — define types from compiler builtins directly. - * This works in both C and C++ and matches our types.h definition. */ -#ifndef _SIZE_T_DEFINED -#define _SIZE_T_DEFINED -typedef __SIZE_TYPE__ size_t; -typedef __PTRDIFF_TYPE__ ptrdiff_t; -typedef __INTMAX_TYPE__ intmax_t; -#endif - -/* LumeOS: kernel does not need floating-point formatted output */ -#define PRINTF_DISABLE_SUPPORT_FLOAT -#define PRINTF_DISABLE_SUPPORT_EXPONENTIAL - - -#ifdef __cplusplus -extern "C" { -#endif - - -/** - * Output a character to a custom device like UART, used by the printf() function - * This function is declared here only. You have to write your custom implementation somewhere - * \param character Character to output - */ -void _putchar(char character); - - -/** - * Tiny printf implementation - * You have to implement _putchar if you use printf() - * To avoid conflicts with the regular printf() API it is overridden by macro defines - * and internal underscore-appended functions like printf_() are used - * \param format A string that specifies the format of the output - * \return The number of characters that are written into the array, not counting the terminating null character - */ -#define printf printf_ -int printf_(const char* format, ...); - - -/** - * Tiny sprintf implementation - * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! - * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! - * \param format A string that specifies the format of the output - * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -#define sprintf sprintf_ -int sprintf_(char* buffer, const char* format, ...); - - -/** - * Tiny snprintf/vsnprintf implementation - * \param buffer A pointer to the buffer where to store the formatted string - * \param count The maximum number of characters to store in the buffer, including a terminating null character - * \param format A string that specifies the format of the output - * \param va A value identifying a variable arguments list - * \return The number of characters that COULD have been written into the buffer, not counting the terminating - * null character. A value equal or larger than count indicates truncation. Only when the returned value - * is non-negative and less than count, the string has been completely written. - */ -#define snprintf snprintf_ -#define vsnprintf vsnprintf_ -int snprintf_(char* buffer, size_t count, const char* format, ...); -int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); - - -/** - * Tiny vprintf implementation - * \param format A string that specifies the format of the output - * \param va A value identifying a variable arguments list - * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character - */ -#define vprintf vprintf_ -int vprintf_(const char* format, va_list va); - - -/** - * printf with output function - * You may use this as dynamic alternative to printf() with its fixed _putchar() output - * \param out An output function which takes one character and an argument pointer - * \param arg An argument pointer for user data passed to output function - * \param format A string that specifies the format of the output - * \return The number of characters that are sent to the output function, not counting the terminating null character - */ -int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); - - -#ifdef __cplusplus -} -#endif - - -#endif // _PRINTF_H_ diff --git a/na/lib/random.cc b/na/lib/random.cc deleted file mode 100644 index 428c75c..0000000 --- a/na/lib/random.cc +++ /dev/null @@ -1,152 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include - -#include - -/* - * Early Boot PRNG (xoshiro256**) - * State size: 256 bits (4 * 64-bit words) - * Extremely fast, excellent statistical properties. - */ - -static uint64 g_state[4]; -static bool g_initialized = false; -static uint64 g_init_seed = 0; -static RandomSeedSource g_seed_source = RandomSeedSource::Unknown; - -static inline uint64 rotl(const uint64 x, int k) { - return (x << k) | (x >> (64 - k)); -} - -/* SplitMix64 used to initialize xoshiro state from a single 64-bit seed */ -static uint64 splitmix64(uint64& state) { - uint64 z = (state += 0x9e3779b97f4a7c15ULL); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; - z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; - return z ^ (z >> 31); -} - -void random_early_init(uint64 seed) -{ - if (g_initialized) return; - - RandomSeedSource source = RandomSeedSource::Unknown; - - /* If seed is 0 (not provided by bootloader), try hardware */ - if (seed == 0) { - uint64 hw_val; - if (arch::get_hw_random(hw_val)) { - seed = hw_val; - source = RandomSeedSource::HwRandom; - } else { - /* Fallback: use boot cycles mixed with a constant */ - seed = arch::get_cycles() ^ 0xdeadbeefbadc0ffeULL; - source = RandomSeedSource::CycleFallback; - } - } else { - source = RandomSeedSource::FdtProvided; - } - - uint64 sm_state = seed; - g_state[0] = splitmix64(sm_state); - g_state[1] = splitmix64(sm_state); - g_state[2] = splitmix64(sm_state); - g_state[3] = splitmix64(sm_state); - - g_init_seed = seed; - g_seed_source = source; - g_initialized = true; - kprintf("[random] early PRNG initialized, seed=0x%llx, source=%s\n", - seed, random_seed_source_name(source)); -} - -uint64 get_random_u64() -{ - if (!g_initialized) { - random_early_init(0); - } - - const uint64 result = rotl(g_state[1] * 5, 7) * 9; - - const uint64 t = g_state[1] << 17; - - g_state[2] ^= g_state[0]; - g_state[3] ^= g_state[1]; - g_state[1] ^= g_state[2]; - g_state[0] ^= g_state[3]; - - g_state[2] ^= t; - g_state[3] = rotl(g_state[3], 45); - - /* Mix with current cycles for slight additional unpredictability */ - return result ^ arch::get_cycles(); -} - -uint32 get_random_u32() -{ - return static_cast(get_random_u64() >> 32); -} - -void get_random_bytes(void* buf, size_t len) -{ - uint8* p = static_cast(buf); - while (len >= 8) { - uint64 val = get_random_u64(); - for (int i = 0; i < 8; i++) { - p[i] = (val >> (i * 8)) & 0xFF; - } - p += 8; - len -= 8; - } - if (len > 0) { - uint64 val = get_random_u64(); - for (size_t i = 0; i < len; i++) { - p[i] = (val >> (i * 8)) & 0xFF; - } - } -} - -void add_device_randomness(const void* buf, size_t len) -{ - /* - * For now, just mix into the xoshiro state using a simple XOR. - * In the future, this will feed the CSPRNG entropy pool. - */ - const uint8* p = static_cast(buf); - uint64 mix = arch::get_cycles(); - for (size_t i = 0; i < len; i++) { - mix = (mix << 5) ^ (mix >> 59) ^ p[i]; - } - - g_state[0] ^= mix; - g_state[1] ^= arch::get_cycles(); -} - -RandomSeedSource random_early_seed_source() -{ - return g_seed_source; -} - -uint64 random_early_seed_value() -{ - return g_init_seed; -} - -const char* random_seed_source_name(RandomSeedSource source) -{ - switch (source) { - case RandomSeedSource::FdtProvided: - return "fdt"; - case RandomSeedSource::HwRandom: - return "hw"; - case RandomSeedSource::CycleFallback: - return "cycle"; - case RandomSeedSource::Unknown: - default: - return "unknown"; - } -} diff --git a/na/lib/rbtree.cc b/na/lib/rbtree.cc deleted file mode 100644 index 6718a1a..0000000 --- a/na/lib/rbtree.cc +++ /dev/null @@ -1,596 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - Red Black Trees - (C) 1999 Andrea Arcangeli - (C) 2002 David Woodhouse - (C) 2012 Michel Lespinasse - - - linux/lib/rbtree.c -*/ - -#include - - -/* - * red-black trees properties: https://en.wikipedia.org/wiki/Rbtree - * - * 1) A node is either red or black - * 2) The root is black - * 3) All leaves (nullptr) are black - * 4) Both children of every red node are black - * 5) Every simple path from root to leaves contains the same number - * of black nodes. - * - * 4 and 5 give the O(log n) guarantee, since 4 implies you cannot have two - * consecutive red nodes in a path and every red node is therefore followed by - * a black. So if B is the number of black nodes on every simple path (as per - * 5), then the longest possible path due to 4 is 2B. - * - * We shall indicate color with case, where black nodes are uppercase and red - * nodes will be lowercase. Unknown color nodes shall be drawn as red within - * parentheses and have some accompanying text comment. - */ - -/* - * Notes on lockless lookups: - * - * All stores to the tree structure (rb_left and rb_right) must be done using - * WRITE_ONCE(). And we must not inadvertently cause (temporary) loops in the - * tree structure as seen in program order. - * - * These two requirements will allow lockless iteration of the tree -- not - * correct iteration mind you, tree rotations are not atomic so a lookup might - * miss entire subtrees. - * - * But they do guarantee that any such traversal will only see valid elements - * and that it will indeed complete -- does not get stuck in a loop. - * - * It also guarantees that if the lookup returns an element it is the 'correct' - * one. But not returning an element does _NOT_ mean it's not present. - * - * NOTE: - * - * Stores to __rb_parent_color are not important for simple lookups so those - * are left undone as of now. Nor did I check for loops involving parent - * pointers. - */ - -static inline void rb_set_black(struct rb_node *rb) -{ - rb->__rb_parent_color += RB_BLACK; -} - -static inline struct rb_node *rb_red_parent(struct rb_node *red) -{ - return (struct rb_node *)red->__rb_parent_color; -} - -/* - * Helper function for rotations: - * - old's parent and color get assigned to _new - * - old gets assigned _new as a parent and 'color' as a color. - */ -static inline void -__rb_rotate_set_parents(struct rb_node *old, struct rb_node *_new, - struct rb_root *root, int color) -{ - struct rb_node *parent = rb_parent(old); - _new->__rb_parent_color = old->__rb_parent_color; - rb_set_parent_color(old, _new, color); - __rb_change_child(old, _new, parent, root); -} - -static __always_inline void -__rb_insert(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; - - while (true) { - /* - * Loop invariant: node is red. - */ - if (unlikely(!parent)) { - /* - * The inserted node is root. Either this is the - * first node, or we recursed at Case 1 below and - * are no longer violating 4). - */ - rb_set_parent_color(node, nullptr, RB_BLACK); - break; - } - - /* - * If there is a black parent, we are done. - * Otherwise, take some corrective action as, - * per 4), we don't want a red root or two - * consecutive red nodes. - */ - if(rb_is_black(parent)) - break; - - gparent = rb_red_parent(parent); - - tmp = gparent->rb_right; - if (parent != tmp) { /* parent == gparent->rb_left */ - if (tmp && rb_is_red(tmp)) { - /* - * Case 1 - node's uncle is red (color flips). - * - * G g - * / \ / \ - * p u --> P U - * / / - * n n - * - * However, since g's parent might be red, and - * 4) does not allow this, we need to recurse - * at g. - */ - rb_set_parent_color(tmp, gparent, RB_BLACK); - rb_set_parent_color(parent, gparent, RB_BLACK); - node = gparent; - parent = rb_parent(node); - rb_set_parent_color(node, parent, RB_RED); - continue; - } - - tmp = parent->rb_right; - if (node == tmp) { - /* - * Case 2 - node's uncle is black and node is - * the parent's right child (left rotate at parent). - * - * G G - * / \ / \ - * p U --> n U - * \ / - * n p - * - * This still leaves us in violation of 4), the - * continuation into Case 3 will fix that. - */ - tmp = node->rb_left; - WRITE_ONCE(parent->rb_right, tmp); - WRITE_ONCE(node->rb_left, parent); - if (tmp) - rb_set_parent_color(tmp, parent, - RB_BLACK); - rb_set_parent_color(parent, node, RB_RED); - augment_rotate(parent, node); - parent = node; - tmp = node->rb_right; - } - - /* - * Case 3 - node's uncle is black and node is - * the parent's left child (right rotate at gparent). - * - * G P - * / \ / \ - * p U --> n g - * / \ - * n U - */ - WRITE_ONCE(gparent->rb_left, tmp); /* == parent->rb_right */ - WRITE_ONCE(parent->rb_right, gparent); - if (tmp) - rb_set_parent_color(tmp, gparent, RB_BLACK); - __rb_rotate_set_parents(gparent, parent, root, RB_RED); - augment_rotate(gparent, parent); - break; - } else { - tmp = gparent->rb_left; - if (tmp && rb_is_red(tmp)) { - /* Case 1 - color flips */ - rb_set_parent_color(tmp, gparent, RB_BLACK); - rb_set_parent_color(parent, gparent, RB_BLACK); - node = gparent; - parent = rb_parent(node); - rb_set_parent_color(node, parent, RB_RED); - continue; - } - - tmp = parent->rb_left; - if (node == tmp) { - /* Case 2 - right rotate at parent */ - tmp = node->rb_right; - WRITE_ONCE(parent->rb_left, tmp); - WRITE_ONCE(node->rb_right, parent); - if (tmp) - rb_set_parent_color(tmp, parent, - RB_BLACK); - rb_set_parent_color(parent, node, RB_RED); - augment_rotate(parent, node); - parent = node; - tmp = node->rb_left; - } - - /* Case 3 - left rotate at gparent */ - WRITE_ONCE(gparent->rb_right, tmp); /* == parent->rb_left */ - WRITE_ONCE(parent->rb_left, gparent); - if (tmp) - rb_set_parent_color(tmp, gparent, RB_BLACK); - __rb_rotate_set_parents(gparent, parent, root, RB_RED); - augment_rotate(gparent, parent); - break; - } - } -} - -/* - * Inline version for rb_erase() use - we want to be able to inline - * and eliminate the dummy_rotate callback there - */ -static __always_inline void -____rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - struct rb_node *node = nullptr, *sibling, *tmp1, *tmp2; - - while (true) { - /* - * Loop invariants: - * - node is black (or nullptr on first iteration) - * - node is not the root (parent is not nullptr) - * - All leaf paths going through parent and node have a - * black node count that is 1 lower than other leaf paths. - */ - sibling = parent->rb_right; - if (node != sibling) { /* node == parent->rb_left */ - if (rb_is_red(sibling)) { - /* - * Case 1 - left rotate at parent - * - * P S - * / \ / \ - * N s --> p Sr - * / \ / \ - * Sl Sr N Sl - */ - tmp1 = sibling->rb_left; - WRITE_ONCE(parent->rb_right, tmp1); - WRITE_ONCE(sibling->rb_left, parent); - rb_set_parent_color(tmp1, parent, RB_BLACK); - __rb_rotate_set_parents(parent, sibling, root, - RB_RED); - augment_rotate(parent, sibling); - sibling = tmp1; - } - tmp1 = sibling->rb_right; - if (!tmp1 || rb_is_black(tmp1)) { - tmp2 = sibling->rb_left; - if (!tmp2 || rb_is_black(tmp2)) { - /* - * Case 2 - sibling color flip - * (p could be either color here) - * - * (p) (p) - * / \ / \ - * N S --> N s - * / \ / \ - * Sl Sr Sl Sr - * - * This leaves us violating 5) which - * can be fixed by flipping p to black - * if it was red, or by recursing at p. - * p is red when coming from Case 1. - */ - rb_set_parent_color(sibling, parent, - RB_RED); - if (rb_is_red(parent)) - rb_set_black(parent); - else { - node = parent; - parent = rb_parent(node); - if (parent) - continue; - } - break; - } - /* - * Case 3 - right rotate at sibling - * (p could be either color here) - * - * (p) (p) - * / \ / \ - * N S --> N sl - * / \ \ - * sl Sr S - * \ - * Sr - * - * Note: p might be red, and then both - * p and sl are red after rotation(which - * breaks property 4). This is fixed in - * Case 4 (in __rb_rotate_set_parents() - * which set sl the color of p - * and set p RB_BLACK) - * - * (p) (sl) - * / \ / \ - * N sl --> P S - * \ / \ - * S N Sr - * \ - * Sr - */ - tmp1 = tmp2->rb_right; - WRITE_ONCE(sibling->rb_left, tmp1); - WRITE_ONCE(tmp2->rb_right, sibling); - WRITE_ONCE(parent->rb_right, tmp2); - if (tmp1) - rb_set_parent_color(tmp1, sibling, - RB_BLACK); - augment_rotate(sibling, tmp2); - tmp1 = sibling; - sibling = tmp2; - } - /* - * Case 4 - left rotate at parent + color flips - * (p and sl could be either color here. - * After rotation, p becomes black, s acquires - * p's color, and sl keeps its color) - * - * (p) (s) - * / \ / \ - * N S --> P Sr - * / \ / \ - * (sl) sr N (sl) - */ - tmp2 = sibling->rb_left; - WRITE_ONCE(parent->rb_right, tmp2); - WRITE_ONCE(sibling->rb_left, parent); - rb_set_parent_color(tmp1, sibling, RB_BLACK); - if (tmp2) - rb_set_parent(tmp2, parent); - __rb_rotate_set_parents(parent, sibling, root, - RB_BLACK); - augment_rotate(parent, sibling); - break; - } else { - sibling = parent->rb_left; - if (rb_is_red(sibling)) { - /* Case 1 - right rotate at parent */ - tmp1 = sibling->rb_right; - WRITE_ONCE(parent->rb_left, tmp1); - WRITE_ONCE(sibling->rb_right, parent); - rb_set_parent_color(tmp1, parent, RB_BLACK); - __rb_rotate_set_parents(parent, sibling, root, - RB_RED); - augment_rotate(parent, sibling); - sibling = tmp1; - } - tmp1 = sibling->rb_left; - if (!tmp1 || rb_is_black(tmp1)) { - tmp2 = sibling->rb_right; - if (!tmp2 || rb_is_black(tmp2)) { - /* Case 2 - sibling color flip */ - rb_set_parent_color(sibling, parent, - RB_RED); - if (rb_is_red(parent)) - rb_set_black(parent); - else { - node = parent; - parent = rb_parent(node); - if (parent) - continue; - } - break; - } - /* Case 3 - left rotate at sibling */ - tmp1 = tmp2->rb_left; - WRITE_ONCE(sibling->rb_right, tmp1); - WRITE_ONCE(tmp2->rb_left, sibling); - WRITE_ONCE(parent->rb_left, tmp2); - if (tmp1) - rb_set_parent_color(tmp1, sibling, - RB_BLACK); - augment_rotate(sibling, tmp2); - tmp1 = sibling; - sibling = tmp2; - } - /* Case 4 - right rotate at parent + color flips */ - tmp2 = sibling->rb_right; - WRITE_ONCE(parent->rb_left, tmp2); - WRITE_ONCE(sibling->rb_right, parent); - rb_set_parent_color(tmp1, sibling, RB_BLACK); - if (tmp2) - rb_set_parent(tmp2, parent); - __rb_rotate_set_parents(parent, sibling, root, - RB_BLACK); - augment_rotate(parent, sibling); - break; - } - } -} - -/* Non-inline version for rb_erase_augmented() use */ -void __rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - ____rb_erase_color(parent, root, augment_rotate); -} - -/* - * Non-augmented rbtree manipulation functions. - * - * We use dummy augmented callbacks here, and have the compiler optimize them - * out of the rb_insert_color() and rb_erase() function definitions. - */ - -static inline void dummy_propagate(struct rb_node *node, struct rb_node *stop) {} -static inline void dummy_copy(struct rb_node *old, struct rb_node *_new) {} -static inline void dummy_rotate(struct rb_node *old, struct rb_node *_new) {} - -static const struct rb_augment_callbacks dummy_callbacks = { - .propagate = dummy_propagate, - .copy = dummy_copy, - .rotate = dummy_rotate -}; - -void rb_insert_color(struct rb_node *node, struct rb_root *root) -{ - __rb_insert(node, root, dummy_rotate); -} - -void rb_erase(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *rebalance; - rebalance = __rb_erase_augmented(node, root, &dummy_callbacks); - if (rebalance) - ____rb_erase_color(rebalance, root, dummy_rotate); -} - - -/* - * Augmented rbtree manipulation functions. - * - * This instantiates the same __always_inline functions as in the non-augmented - * case, but this time with user-defined callbacks. - */ - -void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - __rb_insert(node, root, augment_rotate); -} - -struct rb_node *rb_next(const struct rb_node *node) -{ - struct rb_node *parent; - - if (RB_EMPTY_NODE(node)) - return nullptr; - - /* - * If we have a right-hand child, go down and then left as far - * as we can. - */ - if (node->rb_right) { - node = node->rb_right; - while (node->rb_left) - node = node->rb_left; - return (struct rb_node *)node; - } - - /* - * No right-hand children. Everything down and left is smaller than us, - * so any 'next' node must be in the general direction of our parent. - * Go up the tree; any time the ancestor is a right-hand child of its - * parent, keep going up. First time it's a left-hand child of its - * parent, said parent is our 'next' node. - */ - while ((parent = rb_parent(node)) && node == parent->rb_right) - node = parent; - - return parent; -} - -struct rb_node *rb_prev(const struct rb_node *node) -{ - struct rb_node *parent; - - if (RB_EMPTY_NODE(node)) - return nullptr; - - /* - * If we have a left-hand child, go down and then right as far - * as we can. - */ - if (node->rb_left) { - node = node->rb_left; - while (node->rb_right) - node = node->rb_right; - return (struct rb_node *)node; - } - - /* - * No left-hand children. Go up till we find an ancestor which - * is a right-hand child of its parent. - */ - while ((parent = rb_parent(node)) && node == parent->rb_left) - node = parent; - - return parent; -} - -void rb_replace_node(struct rb_node *victim, struct rb_node *_new, - struct rb_root *root) -{ - struct rb_node *parent = rb_parent(victim); - - /* Copy the pointers/colour from the victim to the replacement */ - *_new = *victim; - - /* Set the surrounding nodes to point to the replacement */ - if (victim->rb_left) - rb_set_parent(victim->rb_left, _new); - if (victim->rb_right) - rb_set_parent(victim->rb_right, _new); - __rb_change_child(victim, _new, parent, root); -} - - -static struct rb_node *rb_left_deepest_node(const struct rb_node *node) -{ - for (;;) { - if (node->rb_left) - node = node->rb_left; - else if (node->rb_right) - node = node->rb_right; - else - return (struct rb_node *)node; - } -} - -struct rb_node *rb_next_postorder(const struct rb_node *node) -{ - const struct rb_node *parent; - if (!node) - return nullptr; - parent = rb_parent(node); - - /* If we're sitting on node, we've already seen our children */ - if (parent && node == parent->rb_left && parent->rb_right) { - /* If we are the parent's left node, go to the parent's right - * node then all the way down to the left */ - return rb_left_deepest_node(parent->rb_right); - } else - /* Otherwise we are the parent's right node, and the parent - * should be next */ - return (struct rb_node *)parent; -} - -struct rb_node *rb_first_postorder(const struct rb_root *root) -{ - if (!root->rb_node) - return nullptr; - - return rb_left_deepest_node(root->rb_node); -} - -struct rb_node *rb_first(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return nullptr; - while (n->rb_left) - n = n->rb_left; - return n; -} - -struct rb_node *rb_last(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return nullptr; - while (n->rb_right) - n = n->rb_right; - return n; -} diff --git a/na/lib/string/COPYRIGHT b/na/lib/string/COPYRIGHT deleted file mode 100644 index 2f15edc..0000000 --- a/na/lib/string/COPYRIGHT +++ /dev/null @@ -1,193 +0,0 @@ -musl as a whole is licensed under the following standard MIT license: - ----------------------------------------------------------------------- -Copyright © 2005-2020 Rich Felker, et al. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------- - -Authors/contributors include: - -A. Wilcox -Ada Worcester -Alex Dowad -Alex Suykov -Alexander Monakov -Andre McCurdy -Andrew Kelley -Anthony G. Basile -Aric Belsito -Arvid Picciani -Bartosz Brachaczek -Benjamin Peterson -Bobby Bingham -Boris Brezillon -Brent Cook -Chris Spiegel -Clément Vasseur -Daniel Micay -Daniel Sabogal -Daurnimator -David Carlier -David Edelsohn -Denys Vlasenko -Dmitry Ivanov -Dmitry V. Levin -Drew DeVault -Emil Renner Berthing -Fangrui Song -Felix Fietkau -Felix Janda -Gianluca Anzolin -Hauke Mehrtens -He X -Hiltjo Posthuma -Isaac Dunham -Jaydeep Patil -Jens Gustedt -Jeremy Huntwork -Jo-Philipp Wich -Joakim Sindholt -John Spencer -Julien Ramseier -Justin Cormack -Kaarle Ritvanen -Khem Raj -Kylie McClain -Leah Neukirchen -Luca Barbato -Luka Perkov -Lynn Ochs -M Farkas-Dyck (Strake) -Mahesh Bodapati -Markus Wichmann -Masanori Ogino -Michael Clark -Michael Forney -Mikhail Kremnyov -Natanael Copa -Nicholas J. Kain -orc -Pascal Cuoq -Patrick Oppenlander -Petr Hosek -Petr Skocik -Pierre Carrier -Reini Urban -Rich Felker -Richard Pennington -Ryan Fairfax -Samuel Holland -Segev Finer -Shiz -sin -Solar Designer -Stefan Kristiansson -Stefan O'Rear -Szabolcs Nagy -Timo Teräs -Trutz Behn -Will Dietz -William Haddon -William Pitcock - -Portions of this software are derived from third-party works licensed -under terms compatible with the above MIT license: - -The TRE regular expression implementation (src/regex/reg* and -src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed -under a 2-clause BSD license (license text in the source files). The -included version has been heavily modified by Rich Felker in 2012, in -the interests of size, simplicity, and namespace cleanliness. - -Much of the math library code (src/math/* and src/complex/*) is -Copyright © 1993,2004 Sun Microsystems or -Copyright © 2003-2011 David Schultz or -Copyright © 2003-2009 Steven G. Kargl or -Copyright © 2003-2009 Bruce D. Evans or -Copyright © 2008 Stephen L. Moshier or -Copyright © 2017-2018 Arm Limited -and labelled as such in comments in the individual source files. All -have been licensed under extremely permissive terms. - -The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 -The Android Open Source Project and is licensed under a two-clause BSD -license. It was taken from Bionic libc, used on Android. - -The AArch64 memcpy and memset code (src/string/aarch64/*) are -Copyright © 1999-2019, Arm Limited. - -The implementation of DES for crypt (src/crypt/crypt_des.c) is -Copyright © 1994 David Burren. It is licensed under a BSD license. - -The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was -originally written by Solar Designer and placed into the public -domain. The code also comes with a fallback permissive license for use -in jurisdictions that may not recognize the public domain. - -The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 -Lynn Ochs and is licensed under an MIT-style license. - -The x86_64 port was written by Nicholas J. Kain and is licensed under -the standard MIT terms. - -The mips and microblaze ports were originally written by Richard -Pennington for use in the ellcc project. The original code was adapted -by Rich Felker for build system and code conventions during upstream -integration. It is licensed under the standard MIT terms. - -The mips64 port was contributed by Imagination Technologies and is -licensed under the standard MIT terms. - -The powerpc port was also originally written by Richard Pennington, -and later supplemented and integrated by John Spencer. It is licensed -under the standard MIT terms. - -All other files which have no copyright comments are original works -produced specifically for use as part of this library, written either -by Rich Felker, the main author of the library, or by one or more -contibutors listed above. Details on authorship of individual files -can be found in the git version control history of the project. The -omission of copyright and license comments in each file is in the -interest of source tree size. - -In addition, permission is hereby granted for all public header files -(include/* and arch/*/bits/*) and crt files intended to be linked into -applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit -the copyright notice and permission notice otherwise required by the -license, and to use these files without any requirement of -attribution. These files include substantial contributions from: - -Bobby Bingham -John Spencer -Nicholas J. Kain -Rich Felker -Richard Pennington -Stefan Kristiansson -Szabolcs Nagy - -all of whom have explicitly granted such permission. - -This file previously contained text expressing a belief that most of -the files covered by the above exception were sufficiently trivial not -to be subject to copyright, resulting in confusion over whether it -negated the permissions granted in the license. In the spirit of -permissive licensing, and of not having licensing issues being an -obstacle to adoption, that text has been removed. diff --git a/na/lib/string/aarch64/memcpy.S b/na/lib/string/aarch64/memcpy.S deleted file mode 100644 index 48bb8a8..0000000 --- a/na/lib/string/aarch64/memcpy.S +++ /dev/null @@ -1,186 +0,0 @@ -/* - * memcpy - copy memory area - * - * Copyright (c) 2012-2020, Arm Limited. - * SPDX-License-Identifier: MIT - */ - -/* Assumptions: - * - * ARMv8-a, AArch64, unaligned accesses. - * - */ - -#define dstin x0 -#define src x1 -#define count x2 -#define dst x3 -#define srcend x4 -#define dstend x5 -#define A_l x6 -#define A_lw w6 -#define A_h x7 -#define B_l x8 -#define B_lw w8 -#define B_h x9 -#define C_l x10 -#define C_lw w10 -#define C_h x11 -#define D_l x12 -#define D_h x13 -#define E_l x14 -#define E_h x15 -#define F_l x16 -#define F_h x17 -#define G_l count -#define G_h dst -#define H_l src -#define H_h srcend -#define tmp1 x14 - -/* This implementation of memcpy uses unaligned accesses and branchless - sequences to keep the code small, simple and improve performance. - - Copies are split into 3 main cases: small copies of up to 32 bytes, medium - copies of up to 128 bytes, and large copies. The overhead of the overlap - check is negligible since it is only required for large copies. - - Large copies use a software pipelined loop processing 64 bytes per iteration. - The destination pointer is 16-byte aligned to minimize unaligned accesses. - The loop tail is handled by always copying 64 bytes from the end. -*/ - -.global memcpy -.type memcpy,%function -memcpy: - add srcend, src, count - add dstend, dstin, count - cmp count, 128 - b.hi .Lcopy_long - cmp count, 32 - b.hi .Lcopy32_128 - - /* Small copies: 0..32 bytes. */ - cmp count, 16 - b.lo .Lcopy16 - ldp A_l, A_h, [src] - ldp D_l, D_h, [srcend, -16] - stp A_l, A_h, [dstin] - stp D_l, D_h, [dstend, -16] - ret - - /* Copy 8-15 bytes. */ -.Lcopy16: - tbz count, 3, .Lcopy8 - ldr A_l, [src] - ldr A_h, [srcend, -8] - str A_l, [dstin] - str A_h, [dstend, -8] - ret - - .p2align 3 - /* Copy 4-7 bytes. */ -.Lcopy8: - tbz count, 2, .Lcopy4 - ldr A_lw, [src] - ldr B_lw, [srcend, -4] - str A_lw, [dstin] - str B_lw, [dstend, -4] - ret - - /* Copy 0..3 bytes using a branchless sequence. */ -.Lcopy4: - cbz count, .Lcopy0 - lsr tmp1, count, 1 - ldrb A_lw, [src] - ldrb C_lw, [srcend, -1] - ldrb B_lw, [src, tmp1] - strb A_lw, [dstin] - strb B_lw, [dstin, tmp1] - strb C_lw, [dstend, -1] -.Lcopy0: - ret - - .p2align 4 - /* Medium copies: 33..128 bytes. */ -.Lcopy32_128: - ldp A_l, A_h, [src] - ldp B_l, B_h, [src, 16] - ldp C_l, C_h, [srcend, -32] - ldp D_l, D_h, [srcend, -16] - cmp count, 64 - b.hi .Lcopy128 - stp A_l, A_h, [dstin] - stp B_l, B_h, [dstin, 16] - stp C_l, C_h, [dstend, -32] - stp D_l, D_h, [dstend, -16] - ret - - .p2align 4 - /* Copy 65..128 bytes. */ -.Lcopy128: - ldp E_l, E_h, [src, 32] - ldp F_l, F_h, [src, 48] - cmp count, 96 - b.ls .Lcopy96 - ldp G_l, G_h, [srcend, -64] - ldp H_l, H_h, [srcend, -48] - stp G_l, G_h, [dstend, -64] - stp H_l, H_h, [dstend, -48] -.Lcopy96: - stp A_l, A_h, [dstin] - stp B_l, B_h, [dstin, 16] - stp E_l, E_h, [dstin, 32] - stp F_l, F_h, [dstin, 48] - stp C_l, C_h, [dstend, -32] - stp D_l, D_h, [dstend, -16] - ret - - .p2align 4 - /* Copy more than 128 bytes. */ -.Lcopy_long: - - /* Copy 16 bytes and then align dst to 16-byte alignment. */ - - ldp D_l, D_h, [src] - and tmp1, dstin, 15 - bic dst, dstin, 15 - sub src, src, tmp1 - add count, count, tmp1 /* Count is now 16 too large. */ - ldp A_l, A_h, [src, 16] - stp D_l, D_h, [dstin] - ldp B_l, B_h, [src, 32] - ldp C_l, C_h, [src, 48] - ldp D_l, D_h, [src, 64]! - subs count, count, 128 + 16 /* Test and readjust count. */ - b.ls .Lcopy64_from_end - -.Lloop64: - stp A_l, A_h, [dst, 16] - ldp A_l, A_h, [src, 16] - stp B_l, B_h, [dst, 32] - ldp B_l, B_h, [src, 32] - stp C_l, C_h, [dst, 48] - ldp C_l, C_h, [src, 48] - stp D_l, D_h, [dst, 64]! - ldp D_l, D_h, [src, 64]! - subs count, count, 64 - b.hi .Lloop64 - - /* Write the last iteration and copy 64 bytes from the end. */ -.Lcopy64_from_end: - ldp E_l, E_h, [srcend, -64] - stp A_l, A_h, [dst, 16] - ldp A_l, A_h, [srcend, -48] - stp B_l, B_h, [dst, 32] - ldp B_l, B_h, [srcend, -32] - stp C_l, C_h, [dst, 48] - ldp C_l, C_h, [srcend, -16] - stp D_l, D_h, [dst, 64] - stp E_l, E_h, [dstend, -64] - stp A_l, A_h, [dstend, -48] - stp B_l, B_h, [dstend, -32] - stp C_l, C_h, [dstend, -16] - ret - -.size memcpy,.-memcpy diff --git a/na/lib/string/aarch64/memset.S b/na/lib/string/aarch64/memset.S deleted file mode 100644 index f0d29b7..0000000 --- a/na/lib/string/aarch64/memset.S +++ /dev/null @@ -1,115 +0,0 @@ -/* - * memset - fill memory with a constant byte - * - * Copyright (c) 2012-2020, Arm Limited. - * SPDX-License-Identifier: MIT - */ - -/* Assumptions: - * - * ARMv8-a, AArch64, Advanced SIMD, unaligned accesses. - * - */ - -#define dstin x0 -#define val x1 -#define valw w1 -#define count x2 -#define dst x3 -#define dstend x4 -#define zva_val x5 - -.global memset -.type memset,%function -memset: - - dup v0.16B, valw - add dstend, dstin, count - - cmp count, 96 - b.hi .Lset_long - cmp count, 16 - b.hs .Lset_medium - mov val, v0.D[0] - - /* Set 0..15 bytes. */ - tbz count, 3, 1f - str val, [dstin] - str val, [dstend, -8] - ret - nop -1: tbz count, 2, 2f - str valw, [dstin] - str valw, [dstend, -4] - ret -2: cbz count, 3f - strb valw, [dstin] - tbz count, 1, 3f - strh valw, [dstend, -2] -3: ret - - /* Set 17..96 bytes. */ -.Lset_medium: - str q0, [dstin] - tbnz count, 6, .Lset96 - str q0, [dstend, -16] - tbz count, 5, 1f - str q0, [dstin, 16] - str q0, [dstend, -32] -1: ret - - .p2align 4 - /* Set 64..96 bytes. Write 64 bytes from the start and - 32 bytes from the end. */ -.Lset96: - str q0, [dstin, 16] - stp q0, q0, [dstin, 32] - stp q0, q0, [dstend, -32] - ret - - .p2align 4 -.Lset_long: - and valw, valw, 255 - bic dst, dstin, 15 - str q0, [dstin] - cmp count, 160 - ccmp valw, 0, 0, hs - b.ne .Lno_zva - -#ifndef SKIP_ZVA_CHECK - mrs zva_val, dczid_el0 - and zva_val, zva_val, 31 - cmp zva_val, 4 /* ZVA size is 64 bytes. */ - b.ne .Lno_zva -#endif - str q0, [dst, 16] - stp q0, q0, [dst, 32] - bic dst, dst, 63 - sub count, dstend, dst /* Count is now 64 too large. */ - sub count, count, 128 /* Adjust count and bias for loop. */ - - .p2align 4 -.Lzva_loop: - add dst, dst, 64 - dc zva, dst - subs count, count, 64 - b.hi .Lzva_loop - stp q0, q0, [dstend, -64] - stp q0, q0, [dstend, -32] - ret - -.Lno_zva: - sub count, dstend, dst /* Count is 16 too large. */ - sub dst, dst, 16 /* Dst is biased by -32. */ - sub count, count, 64 + 16 /* Adjust count and bias for loop. */ -.Lno_zva_loop: - stp q0, q0, [dst, 32] - stp q0, q0, [dst, 64]! - subs count, count, 64 - b.hi .Lno_zva_loop - stp q0, q0, [dstend, -64] - stp q0, q0, [dstend, -32] - ret - -.size memset,.-memset - diff --git a/na/lib/string/arm/__aeabi_memcpy.s b/na/lib/string/arm/__aeabi_memcpy.s deleted file mode 100644 index 3a527e4..0000000 --- a/na/lib/string/arm/__aeabi_memcpy.s +++ /dev/null @@ -1,45 +0,0 @@ -.syntax unified - -.global __aeabi_memcpy8 -.global __aeabi_memcpy4 -.global __aeabi_memcpy -.global __aeabi_memmove8 -.global __aeabi_memmove4 -.global __aeabi_memmove - -.type __aeabi_memcpy8,%function -.type __aeabi_memcpy4,%function -.type __aeabi_memcpy,%function -.type __aeabi_memmove8,%function -.type __aeabi_memmove4,%function -.type __aeabi_memmove,%function - -__aeabi_memmove8: -__aeabi_memmove4: -__aeabi_memmove: - cmp r0, r1 - bls 3f - cmp r2, #0 - beq 2f - adds r0, r0, r2 - adds r2, r1, r2 -1: subs r2, r2, #1 - ldrb r3, [r2] - subs r0, r0, #1 - strb r3, [r0] - cmp r1, r2 - bne 1b -2: bx lr -__aeabi_memcpy8: -__aeabi_memcpy4: -__aeabi_memcpy: -3: cmp r2, #0 - beq 2f - adds r2, r1, r2 -1: ldrb r3, [r1] - adds r1, r1, #1 - strb r3, [r0] - adds r0, r0, #1 - cmp r1, r2 - bne 1b -2: bx lr diff --git a/na/lib/string/arm/__aeabi_memset.s b/na/lib/string/arm/__aeabi_memset.s deleted file mode 100644 index f9f6058..0000000 --- a/na/lib/string/arm/__aeabi_memset.s +++ /dev/null @@ -1,31 +0,0 @@ -.syntax unified - -.global __aeabi_memclr8 -.global __aeabi_memclr4 -.global __aeabi_memclr -.global __aeabi_memset8 -.global __aeabi_memset4 -.global __aeabi_memset - -.type __aeabi_memclr8,%function -.type __aeabi_memclr4,%function -.type __aeabi_memclr,%function -.type __aeabi_memset8,%function -.type __aeabi_memset4,%function -.type __aeabi_memset,%function - -__aeabi_memclr8: -__aeabi_memclr4: -__aeabi_memclr: - movs r2, #0 -__aeabi_memset8: -__aeabi_memset4: -__aeabi_memset: - cmp r1, #0 - beq 2f - adds r1, r0, r1 -1: strb r2, [r0] - adds r0, r0, #1 - cmp r1, r0 - bne 1b -2: bx lr diff --git a/na/lib/string/arm/memcpy.S b/na/lib/string/arm/memcpy.S deleted file mode 100644 index 869e344..0000000 --- a/na/lib/string/arm/memcpy.S +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - - -/* - * Optimized memcpy() for ARM. - * - * note that memcpy() always returns the destination pointer, - * so we have to preserve R0. - */ - -/* - * This file has been modified from the original for use in musl libc. - * The main changes are: addition of .type memcpy,%function to make the - * code safely callable from thumb mode, adjusting the return - * instructions to be compatible with pre-thumb ARM cpus, removal of - * prefetch code that is not compatible with older cpus and support for - * building as thumb 2 and big-endian. - */ - -.syntax unified - -.global memcpy -.type memcpy,%function -memcpy: - /* The stack must always be 64-bits aligned to be compliant with the - * ARM ABI. Since we have to save R0, we might as well save R4 - * which we can use for better pipelining of the reads below - */ - .fnstart - .save {r0, r4, lr} - stmfd sp!, {r0, r4, lr} - /* Making room for r5-r11 which will be spilled later */ - .pad #28 - sub sp, sp, #28 - - /* it simplifies things to take care of len<4 early */ - cmp r2, #4 - blo copy_last_3_and_return - - /* compute the offset to align the source - * offset = (4-(src&3))&3 = -src & 3 - */ - rsb r3, r1, #0 - ands r3, r3, #3 - beq src_aligned - - /* align source to 32 bits. We need to insert 2 instructions between - * a ldr[b|h] and str[b|h] because byte and half-word instructions - * stall 2 cycles. - */ - movs r12, r3, lsl #31 - sub r2, r2, r3 /* we know that r3 <= r2 because r2 >= 4 */ - ldrbmi r3, [r1], #1 - ldrbcs r4, [r1], #1 - ldrbcs r12,[r1], #1 - strbmi r3, [r0], #1 - strbcs r4, [r0], #1 - strbcs r12,[r0], #1 - -src_aligned: - - /* see if src and dst are aligned together (congruent) */ - eor r12, r0, r1 - tst r12, #3 - bne non_congruent - - /* Use post-incriment mode for stm to spill r5-r11 to reserved stack - * frame. Don't update sp. - */ - stmea sp, {r5-r11} - - /* align the destination to a cache-line */ - rsb r3, r0, #0 - ands r3, r3, #0x1C - beq congruent_aligned32 - cmp r3, r2 - andhi r3, r2, #0x1C - - /* conditionnaly copies 0 to 7 words (length in r3) */ - movs r12, r3, lsl #28 - ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ - ldmmi r1!, {r8, r9} /* 8 bytes */ - stmcs r0!, {r4, r5, r6, r7} - stmmi r0!, {r8, r9} - tst r3, #0x4 - ldrne r10,[r1], #4 /* 4 bytes */ - strne r10,[r0], #4 - sub r2, r2, r3 - -congruent_aligned32: - /* - * here source is aligned to 32 bytes. - */ - -cached_aligned32: - subs r2, r2, #32 - blo less_than_32_left - - /* - * We preload a cache-line up to 64 bytes ahead. On the 926, this will - * stall only until the requested world is fetched, but the linefill - * continues in the the background. - * While the linefill is going, we write our previous cache-line - * into the write-buffer (which should have some free space). - * When the linefill is done, the writebuffer will - * start dumping its content into memory - * - * While all this is going, we then load a full cache line into - * 8 registers, this cache line should be in the cache by now - * (or partly in the cache). - * - * This code should work well regardless of the source/dest alignment. - * - */ - - /* Align the preload register to a cache-line because the cpu does - * "critical word first" (the first word requested is loaded first). - */ - @ bic r12, r1, #0x1F - @ add r12, r12, #64 - -1: ldmia r1!, { r4-r11 } - subs r2, r2, #32 - - /* - * NOTE: if r12 is more than 64 ahead of r1, the following ldrhi - * for ARM9 preload will not be safely guarded by the preceding subs. - * When it is safely guarded the only possibility to have SIGSEGV here - * is because the caller overstates the length. - */ - @ ldrhi r3, [r12], #32 /* cheap ARM9 preload */ - stmia r0!, { r4-r11 } - bhs 1b - - add r2, r2, #32 - -less_than_32_left: - /* - * less than 32 bytes left at this point (length in r2) - */ - - /* skip all this if there is nothing to do, which should - * be a common case (if not executed the code below takes - * about 16 cycles) - */ - tst r2, #0x1F - beq 1f - - /* conditionnaly copies 0 to 31 bytes */ - movs r12, r2, lsl #28 - ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ - ldmmi r1!, {r8, r9} /* 8 bytes */ - stmcs r0!, {r4, r5, r6, r7} - stmmi r0!, {r8, r9} - movs r12, r2, lsl #30 - ldrcs r3, [r1], #4 /* 4 bytes */ - ldrhmi r4, [r1], #2 /* 2 bytes */ - strcs r3, [r0], #4 - strhmi r4, [r0], #2 - tst r2, #0x1 - ldrbne r3, [r1] /* last byte */ - strbne r3, [r0] - - /* we're done! restore everything and return */ -1: ldmfd sp!, {r5-r11} - ldmfd sp!, {r0, r4, lr} - bx lr - - /********************************************************************/ - -non_congruent: - /* - * here source is aligned to 4 bytes - * but destination is not. - * - * in the code below r2 is the number of bytes read - * (the number of bytes written is always smaller, because we have - * partial words in the shift queue) - */ - cmp r2, #4 - blo copy_last_3_and_return - - /* Use post-incriment mode for stm to spill r5-r11 to reserved stack - * frame. Don't update sp. - */ - stmea sp, {r5-r11} - - /* compute shifts needed to align src to dest */ - rsb r5, r0, #0 - and r5, r5, #3 /* r5 = # bytes in partial words */ - mov r12, r5, lsl #3 /* r12 = right */ - rsb lr, r12, #32 /* lr = left */ - - /* read the first word */ - ldr r3, [r1], #4 - sub r2, r2, #4 - - /* write a partial word (0 to 3 bytes), such that destination - * becomes aligned to 32 bits (r5 = nb of words to copy for alignment) - */ - movs r5, r5, lsl #31 - -#if __ARMEB__ - movmi r3, r3, ror #24 - strbmi r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 -#else - strbmi r3, [r0], #1 - movmi r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 -#endif - - cmp r2, #4 - blo partial_word_tail - -#if __ARMEB__ - mov r3, r3, lsr r12 - mov r3, r3, lsl r12 -#endif - - /* Align destination to 32 bytes (cache line boundary) */ -1: tst r0, #0x1c - beq 2f - ldr r5, [r1], #4 - sub r2, r2, #4 -#if __ARMEB__ - mov r4, r5, lsr lr - orr r4, r4, r3 - mov r3, r5, lsl r12 -#else - mov r4, r5, lsl lr - orr r4, r4, r3 - mov r3, r5, lsr r12 -#endif - str r4, [r0], #4 - cmp r2, #4 - bhs 1b - blo partial_word_tail - - /* copy 32 bytes at a time */ -2: subs r2, r2, #32 - blo less_than_thirtytwo - - /* Use immediate mode for the shifts, because there is an extra cycle - * for register shifts, which could account for up to 50% of - * performance hit. - */ - - cmp r12, #24 - beq loop24 - cmp r12, #8 - beq loop8 - -loop16: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #16 - mov r4, r4, lsl #16 - orr r4, r4, r5, lsr #16 - mov r5, r5, lsl #16 - orr r5, r5, r6, lsr #16 - mov r6, r6, lsl #16 - orr r6, r6, r7, lsr #16 - mov r7, r7, lsl #16 - orr r7, r7, r8, lsr #16 - mov r8, r8, lsl #16 - orr r8, r8, r9, lsr #16 - mov r9, r9, lsl #16 - orr r9, r9, r10, lsr #16 - mov r10, r10, lsl #16 - orr r10, r10, r11, lsr #16 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #16 -#else - orr r3, r3, r4, lsl #16 - mov r4, r4, lsr #16 - orr r4, r4, r5, lsl #16 - mov r5, r5, lsr #16 - orr r5, r5, r6, lsl #16 - mov r6, r6, lsr #16 - orr r6, r6, r7, lsl #16 - mov r7, r7, lsr #16 - orr r7, r7, r8, lsl #16 - mov r8, r8, lsr #16 - orr r8, r8, r9, lsl #16 - mov r9, r9, lsr #16 - orr r9, r9, r10, lsl #16 - mov r10, r10, lsr #16 - orr r10, r10, r11, lsl #16 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #16 -#endif - bhs 1b - b less_than_thirtytwo - -loop8: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #24 - mov r4, r4, lsl #8 - orr r4, r4, r5, lsr #24 - mov r5, r5, lsl #8 - orr r5, r5, r6, lsr #24 - mov r6, r6, lsl #8 - orr r6, r6, r7, lsr #24 - mov r7, r7, lsl #8 - orr r7, r7, r8, lsr #24 - mov r8, r8, lsl #8 - orr r8, r8, r9, lsr #24 - mov r9, r9, lsl #8 - orr r9, r9, r10, lsr #24 - mov r10, r10, lsl #8 - orr r10, r10, r11, lsr #24 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #8 -#else - orr r3, r3, r4, lsl #24 - mov r4, r4, lsr #8 - orr r4, r4, r5, lsl #24 - mov r5, r5, lsr #8 - orr r5, r5, r6, lsl #24 - mov r6, r6, lsr #8 - orr r6, r6, r7, lsl #24 - mov r7, r7, lsr #8 - orr r7, r7, r8, lsl #24 - mov r8, r8, lsr #8 - orr r8, r8, r9, lsl #24 - mov r9, r9, lsr #8 - orr r9, r9, r10, lsl #24 - mov r10, r10, lsr #8 - orr r10, r10, r11, lsl #24 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #8 -#endif - bhs 1b - b less_than_thirtytwo - -loop24: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #8 - mov r4, r4, lsl #24 - orr r4, r4, r5, lsr #8 - mov r5, r5, lsl #24 - orr r5, r5, r6, lsr #8 - mov r6, r6, lsl #24 - orr r6, r6, r7, lsr #8 - mov r7, r7, lsl #24 - orr r7, r7, r8, lsr #8 - mov r8, r8, lsl #24 - orr r8, r8, r9, lsr #8 - mov r9, r9, lsl #24 - orr r9, r9, r10, lsr #8 - mov r10, r10, lsl #24 - orr r10, r10, r11, lsr #8 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #24 -#else - orr r3, r3, r4, lsl #8 - mov r4, r4, lsr #24 - orr r4, r4, r5, lsl #8 - mov r5, r5, lsr #24 - orr r5, r5, r6, lsl #8 - mov r6, r6, lsr #24 - orr r6, r6, r7, lsl #8 - mov r7, r7, lsr #24 - orr r7, r7, r8, lsl #8 - mov r8, r8, lsr #24 - orr r8, r8, r9, lsl #8 - mov r9, r9, lsr #24 - orr r9, r9, r10, lsl #8 - mov r10, r10, lsr #24 - orr r10, r10, r11, lsl #8 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #24 -#endif - bhs 1b - -less_than_thirtytwo: - /* copy the last 0 to 31 bytes of the source */ - rsb r12, lr, #32 /* we corrupted r12, recompute it */ - add r2, r2, #32 - cmp r2, #4 - blo partial_word_tail - -1: ldr r5, [r1], #4 - sub r2, r2, #4 -#if __ARMEB__ - mov r4, r5, lsr lr - orr r4, r4, r3 - mov r3, r5, lsl r12 -#else - mov r4, r5, lsl lr - orr r4, r4, r3 - mov r3, r5, lsr r12 -#endif - str r4, [r0], #4 - cmp r2, #4 - bhs 1b - -partial_word_tail: - /* we have a partial word in the input buffer */ - movs r5, lr, lsl #(31-3) -#if __ARMEB__ - movmi r3, r3, ror #24 - strbmi r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 -#else - strbmi r3, [r0], #1 - movmi r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 - strbcs r3, [r0], #1 -#endif - - /* Refill spilled registers from the stack. Don't update sp. */ - ldmfd sp, {r5-r11} - -copy_last_3_and_return: - movs r2, r2, lsl #31 /* copy remaining 0, 1, 2 or 3 bytes */ - ldrbmi r2, [r1], #1 - ldrbcs r3, [r1], #1 - ldrbcs r12,[r1] - strbmi r2, [r0], #1 - strbcs r3, [r0], #1 - strbcs r12,[r0] - - /* we're done! restore sp and spilled registers and return */ - add sp, sp, #28 - ldmfd sp!, {r0, r4, lr} - bx lr - diff --git a/na/lib/string/bcmp.c b/na/lib/string/bcmp.c deleted file mode 100644 index d4c093f..0000000 --- a/na/lib/string/bcmp.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include - -int bcmp(const void *s1, const void *s2, size_t n) -{ - return memcmp(s1, s2, n); -} diff --git a/na/lib/string/bcopy.c b/na/lib/string/bcopy.c deleted file mode 100644 index 25e98a2..0000000 --- a/na/lib/string/bcopy.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include - -void bcopy(const void *s1, void *s2, size_t n) -{ - memmove(s2, s1, n); -} diff --git a/na/lib/string/bzero.c b/na/lib/string/bzero.c deleted file mode 100644 index 08f1398..0000000 --- a/na/lib/string/bzero.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include - -void bzero(void *s, size_t n) -{ - memset(s, 0, n); -} diff --git a/na/lib/string/explicit_bzero.c b/na/lib/string/explicit_bzero.c deleted file mode 100644 index c94f25f..0000000 --- a/na/lib/string/explicit_bzero.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include - -void explicit_bzero(void *d, size_t n) -{ - d = memset(d, 0, n); - __asm__ __volatile__ ("" : : "r"(d) : "memory"); -} diff --git a/na/lib/string/i386/memcpy.s b/na/lib/string/i386/memcpy.s deleted file mode 100644 index 0608dd8..0000000 --- a/na/lib/string/i386/memcpy.s +++ /dev/null @@ -1,32 +0,0 @@ -.global memcpy -.global __memcpy_fwd -.hidden __memcpy_fwd -.type memcpy,@function -memcpy: -__memcpy_fwd: - push %esi - push %edi - mov 12(%esp),%edi - mov 16(%esp),%esi - mov 20(%esp),%ecx - mov %edi,%eax - cmp $4,%ecx - jc 1f - test $3,%edi - jz 1f -2: movsb - dec %ecx - test $3,%edi - jnz 2b -1: mov %ecx,%edx - shr $2,%ecx - rep - movsl - and $3,%edx - jz 1f -2: movsb - dec %edx - jnz 2b -1: pop %edi - pop %esi - ret diff --git a/na/lib/string/i386/memmove.s b/na/lib/string/i386/memmove.s deleted file mode 100644 index 2a6a504..0000000 --- a/na/lib/string/i386/memmove.s +++ /dev/null @@ -1,22 +0,0 @@ -.global memmove -.type memmove,@function -memmove: - mov 4(%esp),%eax - sub 8(%esp),%eax - cmp 12(%esp),%eax -.hidden __memcpy_fwd - jae __memcpy_fwd - push %esi - push %edi - mov 12(%esp),%edi - mov 16(%esp),%esi - mov 20(%esp),%ecx - lea -1(%edi,%ecx),%edi - lea -1(%esi,%ecx),%esi - std - rep movsb - cld - lea 1(%edi),%eax - pop %edi - pop %esi - ret diff --git a/na/lib/string/i386/memset.s b/na/lib/string/i386/memset.s deleted file mode 100644 index d00422c..0000000 --- a/na/lib/string/i386/memset.s +++ /dev/null @@ -1,76 +0,0 @@ -.global memset -.type memset,@function -memset: - mov 12(%esp),%ecx - cmp $62,%ecx - ja 2f - - mov 8(%esp),%dl - mov 4(%esp),%eax - test %ecx,%ecx - jz 1f - - mov %dl,%dh - - mov %dl,(%eax) - mov %dl,-1(%eax,%ecx) - cmp $2,%ecx - jbe 1f - - mov %dx,1(%eax) - mov %dx,(-1-2)(%eax,%ecx) - cmp $6,%ecx - jbe 1f - - shl $16,%edx - mov 8(%esp),%dl - mov 8(%esp),%dh - - mov %edx,(1+2)(%eax) - mov %edx,(-1-2-4)(%eax,%ecx) - cmp $14,%ecx - jbe 1f - - mov %edx,(1+2+4)(%eax) - mov %edx,(1+2+4+4)(%eax) - mov %edx,(-1-2-4-8)(%eax,%ecx) - mov %edx,(-1-2-4-4)(%eax,%ecx) - cmp $30,%ecx - jbe 1f - - mov %edx,(1+2+4+8)(%eax) - mov %edx,(1+2+4+8+4)(%eax) - mov %edx,(1+2+4+8+8)(%eax) - mov %edx,(1+2+4+8+12)(%eax) - mov %edx,(-1-2-4-8-16)(%eax,%ecx) - mov %edx,(-1-2-4-8-12)(%eax,%ecx) - mov %edx,(-1-2-4-8-8)(%eax,%ecx) - mov %edx,(-1-2-4-8-4)(%eax,%ecx) - -1: ret - -2: movzbl 8(%esp),%eax - mov %edi,12(%esp) - imul $0x1010101,%eax - mov 4(%esp),%edi - test $15,%edi - mov %eax,-4(%edi,%ecx) - jnz 2f - -1: shr $2, %ecx - rep - stosl - mov 4(%esp),%eax - mov 12(%esp),%edi - ret - -2: xor %edx,%edx - sub %edi,%edx - and $15,%edx - mov %eax,(%edi) - mov %eax,4(%edi) - mov %eax,8(%edi) - mov %eax,12(%edi) - sub %edx,%ecx - add %edx,%edi - jmp 1b diff --git a/na/lib/string/index.c b/na/lib/string/index.c deleted file mode 100644 index 8f6894c..0000000 --- a/na/lib/string/index.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include - -char *index(const char *s, int c) -{ - return strchr(s, c); -} diff --git a/na/lib/string/memccpy.c b/na/lib/string/memccpy.c deleted file mode 100644 index 99f146f..0000000 --- a/na/lib/string/memccpy.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -void *memccpy(void *restrict dest, const void *restrict src, int c, size_t n) -{ - unsigned char *d = dest; - const unsigned char *s = src; - - c = (unsigned char)c; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s)!=c; n--, s++, d++); - if ((uintptr_t)s & ALIGN) goto tail; - size_t k = ONES * c; - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws^k); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; n && (*d=*s)!=c; n--, s++, d++); -tail: - if (n) return d+1; - return 0; -} diff --git a/na/lib/string/memchr.c b/na/lib/string/memchr.c deleted file mode 100644 index d0f670f..0000000 --- a/na/lib/string/memchr.c +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define SS (sizeof(size_t)) -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -void *memchr(const void *src, int c, size_t n) -{ - const unsigned char *s = src; - c = (unsigned char)c; -#ifdef __GNUC__ - for (; ((uintptr_t)s & ALIGN) && n && *s != c; s++, n--); - if (n && *s != c) { - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - size_t k = ONES * c; - for (w = (const void *)s; n>=SS && !HASZERO(*w^k); w++, n-=SS); - s = (const void *)w; - } -#endif - for (; n && *s != c; s++, n--); - return n ? (void *)s : 0; -} diff --git a/na/lib/string/memcmp.c b/na/lib/string/memcmp.c deleted file mode 100644 index 15606f4..0000000 --- a/na/lib/string/memcmp.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -int memcmp(const void *vl, const void *vr, size_t n) -{ - const unsigned char *l=vl, *r=vr; - for (; n && *l == *r; n--, l++, r++); - return n ? *l-*r : 0; -} diff --git a/na/lib/string/memcpy.c b/na/lib/string/memcpy.c deleted file mode 100644 index de26a00..0000000 --- a/na/lib/string/memcpy.c +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -void *memcpy(void *restrict dest, const void *restrict src, size_t n) -{ - unsigned char *d = dest; - const unsigned char *s = src; - -#ifdef __GNUC__ - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define LS >> -#define RS << -#else -#define LS << -#define RS >> -#endif - - typedef uint32_t __attribute__((__may_alias__)) u32; - uint32_t w, x; - - for (; (uintptr_t)s % 4 && n; n--) *d++ = *s++; - - if ((uintptr_t)d % 4 == 0) { - for (; n>=16; s+=16, d+=16, n-=16) { - *(u32 *)(d+0) = *(u32 *)(s+0); - *(u32 *)(d+4) = *(u32 *)(s+4); - *(u32 *)(d+8) = *(u32 *)(s+8); - *(u32 *)(d+12) = *(u32 *)(s+12); - } - if (n&8) { - *(u32 *)(d+0) = *(u32 *)(s+0); - *(u32 *)(d+4) = *(u32 *)(s+4); - d += 8; s += 8; - } - if (n&4) { - *(u32 *)(d+0) = *(u32 *)(s+0); - d += 4; s += 4; - } - if (n&2) { - *d++ = *s++; *d++ = *s++; - } - if (n&1) { - *d = *s; - } - return dest; - } - - if (n >= 32) switch ((uintptr_t)d % 4) { - case 1: - w = *(u32 *)s; - *d++ = *s++; - *d++ = *s++; - *d++ = *s++; - n -= 3; - for (; n>=17; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+1); - *(u32 *)(d+0) = (w LS 24) | (x RS 8); - w = *(u32 *)(s+5); - *(u32 *)(d+4) = (x LS 24) | (w RS 8); - x = *(u32 *)(s+9); - *(u32 *)(d+8) = (w LS 24) | (x RS 8); - w = *(u32 *)(s+13); - *(u32 *)(d+12) = (x LS 24) | (w RS 8); - } - break; - case 2: - w = *(u32 *)s; - *d++ = *s++; - *d++ = *s++; - n -= 2; - for (; n>=18; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+2); - *(u32 *)(d+0) = (w LS 16) | (x RS 16); - w = *(u32 *)(s+6); - *(u32 *)(d+4) = (x LS 16) | (w RS 16); - x = *(u32 *)(s+10); - *(u32 *)(d+8) = (w LS 16) | (x RS 16); - w = *(u32 *)(s+14); - *(u32 *)(d+12) = (x LS 16) | (w RS 16); - } - break; - case 3: - w = *(u32 *)s; - *d++ = *s++; - n -= 1; - for (; n>=19; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+3); - *(u32 *)(d+0) = (w LS 8) | (x RS 24); - w = *(u32 *)(s+7); - *(u32 *)(d+4) = (x LS 8) | (w RS 24); - x = *(u32 *)(s+11); - *(u32 *)(d+8) = (w LS 8) | (x RS 24); - w = *(u32 *)(s+15); - *(u32 *)(d+12) = (x LS 8) | (w RS 24); - } - break; - } - if (n&16) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&8) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&4) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&2) { - *d++ = *s++; *d++ = *s++; - } - if (n&1) { - *d = *s; - } - return dest; -#endif - - for (; n; n--) *d++ = *s++; - return dest; -} diff --git a/na/lib/string/memmem.c b/na/lib/string/memmem.c deleted file mode 100644 index cfd23bb..0000000 --- a/na/lib/string/memmem.c +++ /dev/null @@ -1,152 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include -#include - -static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; - for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-2; - return hw == nw ? (char *)h-2 : 0; -} - -static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; - for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) - if (hw == nw) return (char *)h-3; - return hw == nw ? (char *)h-3 : 0; -} - -static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; - for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-4; - return hw == nw ? (char *)h-4 : 0; -} - -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) -{ - size_t i, ip, jp, k, p, ms, p0, mem, mem0; - size_t byteset[32 / sizeof(size_t)] = { 0 }; - size_t shift[256]; - - /* Computing length of needle and fill shift table */ - for (i=0; i n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - ms = ip; - p0 = p; - - /* And with the opposite comparison */ - ip = -1; jp = 0; k = p = 1; - while (jp+k ms+1) ms = ip; - else p = p0; - - /* Periodic needle? */ - if (memcmp(n, n+p, ms+1)) { - mem0 = 0; - p = MAX(ms, l-ms-1) + 1; - } else mem0 = l-p; - mem = 0; - - /* Search loop */ - for (;;) { - /* If remainder of haystack is shorter than needle, done */ - if (z-h < l) return 0; - - /* Check last byte first; advance by shift on mismatch */ - if (BITOP(byteset, h[l-1], &)) { - k = l-shift[h[l-1]]; - if (k) { - if (k < mem) k = mem; - h += k; - mem = 0; - continue; - } - } else { - h += l; - mem = 0; - continue; - } - - /* Compare right half */ - for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); - if (k <= mem) return (char *)h; - h += p; - mem = mem0; - } -} - -void *memmem(const void *h0, size_t k, const void *n0, size_t l) -{ - const unsigned char *h = h0, *n = n0; - - /* Return immediately on empty needle */ - if (!l) return (void *)h; - - /* Return immediately when needle is longer than haystack */ - if (k -#include - -#ifdef __GNUC__ -typedef __attribute__((__may_alias__)) size_t WT; -#define WS (sizeof(WT)) -#endif - -void *memmove(void *dest, const void *src, size_t n) -{ - char *d = dest; - const char *s = src; - - if (d==s) return d; - if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n); - - if (d=WS; n-=WS, d+=WS, s+=WS) *(WT *)d = *(WT *)s; - } -#endif - for (; n; n--) *d++ = *s++; - } else { -#ifdef __GNUC__ - if ((uintptr_t)s % WS == (uintptr_t)d % WS) { - while ((uintptr_t)(d+n) % WS) { - if (!n--) return dest; - d[n] = s[n]; - } - while (n>=WS) n-=WS, *(WT *)(d+n) = *(WT *)(s+n); - } -#endif - while (n) n--, d[n] = s[n]; - } - - return dest; -} diff --git a/na/lib/string/mempcpy.c b/na/lib/string/mempcpy.c deleted file mode 100644 index 1f33e45..0000000 --- a/na/lib/string/mempcpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include - -void *mempcpy(void *dest, const void *src, size_t n) -{ - return (char *)memcpy(dest, src, n) + n; -} diff --git a/na/lib/string/memrchr.c b/na/lib/string/memrchr.c deleted file mode 100644 index 8d5c742..0000000 --- a/na/lib/string/memrchr.c +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -void *__memrchr(const void *m, int c, size_t n) -{ - const unsigned char *s = m; - c = (unsigned char)c; - while (n--) if (s[n]==c) return (void *)(s+n); - return 0; -} - -weak_alias(__memrchr, memrchr); diff --git a/na/lib/string/memset.c b/na/lib/string/memset.c deleted file mode 100644 index e780c71..0000000 --- a/na/lib/string/memset.c +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include - -void *memset(void *dest, int c, size_t n) -{ - unsigned char *s = dest; - size_t k; - - /* Fill head and tail with minimal branching. Each - * conditional ensures that all the subsequently used - * offsets are well-defined and in the dest region. */ - - if (!n) return dest; - s[0] = c; - s[n-1] = c; - if (n <= 2) return dest; - s[1] = c; - s[2] = c; - s[n-2] = c; - s[n-3] = c; - if (n <= 6) return dest; - s[3] = c; - s[n-4] = c; - if (n <= 8) return dest; - - /* Advance pointer to align it at a 4-byte boundary, - * and truncate n to a multiple of 4. The previous code - * already took care of any head/tail that get cut off - * by the alignment. */ - - k = -(uintptr_t)s & 3; - s += k; - n -= k; - n &= -4; - -#ifdef __GNUC__ - typedef uint32_t __attribute__((__may_alias__)) u32; - typedef uint64_t __attribute__((__may_alias__)) u64; - - u32 c32 = ((u32)-1)/255 * (unsigned char)c; - - /* In preparation to copy 32 bytes at a time, aligned on - * an 8-byte bounary, fill head/tail up to 28 bytes each. - * As in the initial byte-based head/tail fill, each - * conditional below ensures that the subsequent offsets - * are valid (e.g. !(n<=24) implies n>=28). */ - - *(u32 *)(s+0) = c32; - *(u32 *)(s+n-4) = c32; - if (n <= 8) return dest; - *(u32 *)(s+4) = c32; - *(u32 *)(s+8) = c32; - *(u32 *)(s+n-12) = c32; - *(u32 *)(s+n-8) = c32; - if (n <= 24) return dest; - *(u32 *)(s+12) = c32; - *(u32 *)(s+16) = c32; - *(u32 *)(s+20) = c32; - *(u32 *)(s+24) = c32; - *(u32 *)(s+n-28) = c32; - *(u32 *)(s+n-24) = c32; - *(u32 *)(s+n-20) = c32; - *(u32 *)(s+n-16) = c32; - - /* Align to a multiple of 8 so we can fill 64 bits at a time, - * and avoid writing the same bytes twice as much as is - * practical without introducing additional branching. */ - - k = 24 + ((uintptr_t)s & 4); - s += k; - n -= k; - - /* If this loop is reached, 28 tail bytes have already been - * filled, so any remainder when n drops below 32 can be - * safely ignored. */ - - u64 c64 = c32 | ((u64)c32 << 32); - for (; n >= 32; n-=32, s+=32) { - *(u64 *)(s+0) = c64; - *(u64 *)(s+8) = c64; - *(u64 *)(s+16) = c64; - *(u64 *)(s+24) = c64; - } -#else - /* Pure C fallback with no aliasing violations. */ - for (; n; n--, s++) *s = c; -#endif - - return dest; -} diff --git a/na/lib/string/rindex.c b/na/lib/string/rindex.c deleted file mode 100644 index 897e19e..0000000 --- a/na/lib/string/rindex.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include - -char *rindex(const char *s, int c) -{ - return strrchr(s, c); -} diff --git a/na/lib/string/stpcpy.c b/na/lib/string/stpcpy.c deleted file mode 100644 index 0c1f1b4..0000000 --- a/na/lib/string/stpcpy.c +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__stpcpy(char *restrict d, const char *restrict s) -{ -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { - for (; (uintptr_t)s % ALIGN; s++, d++) - if (!(*d=*s)) return d; - wd=(void *)d; ws=(const void *)s; - for (; !HASZERO(*ws); *wd++ = *ws++); - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; (*d=*s); s++, d++); - - return d; -} - -weak_alias(__stpcpy, stpcpy); diff --git a/na/lib/string/stpncpy.c b/na/lib/string/stpncpy.c deleted file mode 100644 index ca2ef78..0000000 --- a/na/lib/string/stpncpy.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__stpncpy(char *restrict d, const char *restrict s, size_t n) -{ -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); - if (!n || !*s) goto tail; - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; n && (*d=*s); n--, s++, d++); -tail: - memset(d, 0, n); - return d; -} - -weak_alias(__stpncpy, stpncpy); - diff --git a/na/lib/string/strcat.c b/na/lib/string/strcat.c deleted file mode 100644 index 723c2ff..0000000 --- a/na/lib/string/strcat.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strcat(char *restrict dest, const char *restrict src) -{ - strcpy(dest + strlen(dest), src); - return dest; -} diff --git a/na/lib/string/strchr.c b/na/lib/string/strchr.c deleted file mode 100644 index ddeca01..0000000 --- a/na/lib/string/strchr.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strchr(const char *s, int c) -{ - char *r = __strchrnul(s, c); - return *(unsigned char *)r == (unsigned char)c ? r : 0; -} diff --git a/na/lib/string/strchrnul.c b/na/lib/string/strchrnul.c deleted file mode 100644 index 6fc86d1..0000000 --- a/na/lib/string/strchrnul.c +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__strchrnul(const char *s, int c) -{ - c = (unsigned char)c; - if (!c) return (char *)s + strlen(s); - -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - for (; (uintptr_t)s % ALIGN; s++) - if (!*s || *(unsigned char *)s == c) return (char *)s; - size_t k = ONES * c; - for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); - s = (void *)w; -#endif - for (; *s && *(unsigned char *)s != c; s++); - return (char *)s; -} - -weak_alias(__strchrnul, strchrnul); diff --git a/na/lib/string/strcmp.c b/na/lib/string/strcmp.c deleted file mode 100644 index afcb1b7..0000000 --- a/na/lib/string/strcmp.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -int strcmp(const char *l, const char *r) -{ - for (; *l==*r && *l; l++, r++); - return *(unsigned char *)l - *(unsigned char *)r; -} diff --git a/na/lib/string/strcpy.c b/na/lib/string/strcpy.c deleted file mode 100644 index a761577..0000000 --- a/na/lib/string/strcpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strcpy(char *restrict dest, const char *restrict src) -{ - __stpcpy(dest, src); - return dest; -} diff --git a/na/lib/string/strcspn.c b/na/lib/string/strcspn.c deleted file mode 100644 index ba55fe6..0000000 --- a/na/lib/string/strcspn.c +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -size_t strcspn(const char *s, const char *c) -{ - const char *a = s; - size_t byteset[32/sizeof(size_t)]; - - if (!c[0] || !c[1]) return __strchrnul(s, *c)-a; - - memset(byteset, 0, sizeof byteset); - for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); - for (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++); - return s-a; -} diff --git a/na/lib/string/strlcat.c b/na/lib/string/strlcat.c deleted file mode 100644 index a61821d..0000000 --- a/na/lib/string/strlcat.c +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include - -size_t strlcat(char *d, const char *s, size_t n) -{ - size_t l = strnlen(d, n); - if (l == n) return l + strlen(s); - return l + strlcpy(d+l, s, n-l); -} diff --git a/na/lib/string/strlcpy.c b/na/lib/string/strlcpy.c deleted file mode 100644 index c2a5142..0000000 --- a/na/lib/string/strlcpy.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include -#include -#include - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -size_t strlcpy(char *d, const char *s, size_t n) -{ - char *d0 = d; - size_t *wd; - - if (!n--) goto finish; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); - if (n && *s) { - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } - } -#endif - for (; n && (*d=*s); n--, s++, d++); - *d = 0; -finish: - return d-d0 + strlen(s); -} diff --git a/na/lib/string/strlen.c b/na/lib/string/strlen.c deleted file mode 100644 index 76e14f2..0000000 --- a/na/lib/string/strlen.c +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include -#include - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -size_t strlen(const char *s) -{ - const char *a = s; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a; - for (w = (const void *)s; !HASZERO(*w); w++); - s = (const void *)w; -#endif - for (; *s; s++); - return s-a; -} diff --git a/na/lib/string/strncat.c b/na/lib/string/strncat.c deleted file mode 100644 index a144e37..0000000 --- a/na/lib/string/strncat.c +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strncat(char *restrict d, const char *restrict s, size_t n) -{ - char *a = d; - d += strlen(d); - while (n && *s) n--, *d++ = *s++; - *d++ = 0; - return a; -} diff --git a/na/lib/string/strncmp.c b/na/lib/string/strncmp.c deleted file mode 100644 index b98473b..0000000 --- a/na/lib/string/strncmp.c +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -int strncmp(const char *_l, const char *_r, size_t n) -{ - const unsigned char *l=(void *)_l, *r=(void *)_r; - if (!n--) return 0; - for (; *l && *r && n && *l == *r ; l++, r++, n--); - return *l - *r; -} diff --git a/na/lib/string/strncpy.c b/na/lib/string/strncpy.c deleted file mode 100644 index a828465..0000000 --- a/na/lib/string/strncpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strncpy(char *restrict d, const char *restrict s, size_t n) -{ - __stpncpy(d, s, n); - return d; -} diff --git a/na/lib/string/strnlen.c b/na/lib/string/strnlen.c deleted file mode 100644 index 8931c52..0000000 --- a/na/lib/string/strnlen.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -size_t strnlen(const char *s, size_t n) -{ - const char *p = memchr(s, 0, n); - return p ? p-s : n; -} diff --git a/na/lib/string/strpbrk.c b/na/lib/string/strpbrk.c deleted file mode 100644 index 82fbc92..0000000 --- a/na/lib/string/strpbrk.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strpbrk(const char *s, const char *b) -{ - s += strcspn(s, b); - return *s ? (char *)s : 0; -} diff --git a/na/lib/string/strrchr.c b/na/lib/string/strrchr.c deleted file mode 100644 index a45b459..0000000 --- a/na/lib/string/strrchr.c +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strrchr(const char *s, int c) -{ - return __memrchr(s, c, strlen(s) + 1); -} diff --git a/na/lib/string/strsep.c b/na/lib/string/strsep.c deleted file mode 100644 index 2d513f4..0000000 --- a/na/lib/string/strsep.c +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include - -char *strsep(char **str, const char *sep) -{ - char *s = *str, *end; - if (!s) return NULL; - end = s + strcspn(s, sep); - if (*end) *end++ = 0; - else end = 0; - *str = end; - return s; -} diff --git a/na/lib/string/strspn.c b/na/lib/string/strspn.c deleted file mode 100644 index f27710d..0000000 --- a/na/lib/string/strspn.c +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -size_t strspn(const char *s, const char *c) -{ - const char *a = s; - size_t byteset[32/sizeof(size_t)] = { 0 }; - - if (!c[0]) return 0; - if (!c[1]) { - for (; *s == *c; s++); - return s-a; - } - - for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); - for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++); - return s-a; -} diff --git a/na/lib/string/strstr.c b/na/lib/string/strstr.c deleted file mode 100644 index 07e2982..0000000 --- a/na/lib/string/strstr.c +++ /dev/null @@ -1,157 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include -#include - -static char *twobyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; - for (h++; *h && hw != nw; hw = hw<<8 | *++h); - return *h ? (char *)h-1 : 0; -} - -static char *threebyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; - for (h+=2; *h && hw != nw; hw = (hw|*++h)<<8); - return *h ? (char *)h-2 : 0; -} - -static char *fourbyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; - for (h+=3; *h && hw != nw; hw = hw<<8 | *++h); - return *h ? (char *)h-3 : 0; -} - -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -static char *twoway_strstr(const unsigned char *h, const unsigned char *n) -{ - const unsigned char *z; - size_t l, ip, jp, k, p, ms, p0, mem, mem0; - size_t byteset[32 / sizeof(size_t)] = { 0 }; - size_t shift[256]; - - /* Computing length of needle and fill shift table */ - for (l=0; n[l] && h[l]; l++) - BITOP(byteset, n[l], |=), shift[n[l]] = l+1; - if (n[l]) return 0; /* hit the end of h */ - - /* Compute maximal suffix */ - ip = -1; jp = 0; k = p = 1; - while (jp+k n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - ms = ip; - p0 = p; - - /* And with the opposite comparison */ - ip = -1; jp = 0; k = p = 1; - while (jp+k ms+1) ms = ip; - else p = p0; - - /* Periodic needle? */ - if (memcmp(n, n+p, ms+1)) { - mem0 = 0; - p = MAX(ms, l-ms-1) + 1; - } else mem0 = l-p; - mem = 0; - - /* Initialize incremental end-of-haystack pointer */ - z = h; - - /* Search loop */ - for (;;) { - /* Update incremental end-of-haystack pointer */ - if (z-h < l) { - /* Fast estimate for MAX(l,63) */ - size_t grow = l | 63; - const unsigned char *z2 = memchr(z, 0, grow); - if (z2) { - z = z2; - if (z-h < l) return 0; - } else z += grow; - } - - /* Check last byte first; advance by shift on mismatch */ - if (BITOP(byteset, h[l-1], &)) { - k = l-shift[h[l-1]]; - if (k) { - if (k < mem) k = mem; - h += k; - mem = 0; - continue; - } - } else { - h += l; - mem = 0; - continue; - } - - /* Compare right half */ - for (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++); - if (n[k]) { - h += k-ms; - mem = 0; - continue; - } - /* Compare left half */ - for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); - if (k <= mem) return (char *)h; - h += p; - mem = mem0; - } -} - -char *strstr(const char *h, const char *n) -{ - /* Return immediately on empty needle */ - if (!n[0]) return (char *)h; - - /* Use faster algorithms for short needles */ - h = strchr(h, *n); - if (!h || !n[1]) return (char *)h; - if (!h[1]) return 0; - if (!n[2]) return twobyte_strstr((void *)h, (void *)n); - if (!h[2]) return 0; - if (!n[3]) return threebyte_strstr((void *)h, (void *)n); - if (!h[3]) return 0; - if (!n[4]) return fourbyte_strstr((void *)h, (void *)n); - - return twoway_strstr((void *)h, (void *)n); -} diff --git a/na/lib/string/strtok.c b/na/lib/string/strtok.c deleted file mode 100644 index 86d9c97..0000000 --- a/na/lib/string/strtok.c +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strtok(char *restrict s, const char *restrict sep) -{ - static char *p; - if (!s && !(s = p)) return NULL; - s += strspn(s, sep); - if (!*s) return p = 0; - p = s + strcspn(s, sep); - if (*p) *p++ = 0; - else p = 0; - return s; -} diff --git a/na/lib/string/strtok_r.c b/na/lib/string/strtok_r.c deleted file mode 100644 index a702703..0000000 --- a/na/lib/string/strtok_r.c +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include - -char *strtok_r(char *restrict s, const char *restrict sep, char **restrict p) -{ - if (!s && !(s = *p)) return NULL; - s += strspn(s, sep); - if (!*s) return *p = 0; - *p = s + strcspn(s, sep); - if (**p) *(*p)++ = 0; - else *p = 0; - return s; -} diff --git a/na/lib/string/x86_64/memcpy.s b/na/lib/string/x86_64/memcpy.s deleted file mode 100644 index 3d960ef..0000000 --- a/na/lib/string/x86_64/memcpy.s +++ /dev/null @@ -1,25 +0,0 @@ -.global memcpy -.global __memcpy_fwd -.hidden __memcpy_fwd -.type memcpy,@function -memcpy: -__memcpy_fwd: - mov %rdi,%rax - cmp $8,%rdx - jc 1f - test $7,%edi - jz 1f -2: movsb - dec %rdx - test $7,%edi - jnz 2b -1: mov %rdx,%rcx - shr $3,%rcx - rep - movsq - and $7,%edx - jz 1f -2: movsb - dec %edx - jnz 2b -1: ret diff --git a/na/lib/string/x86_64/memmove.s b/na/lib/string/x86_64/memmove.s deleted file mode 100644 index 172c025..0000000 --- a/na/lib/string/x86_64/memmove.s +++ /dev/null @@ -1,16 +0,0 @@ -.global memmove -.type memmove,@function -memmove: - mov %rdi,%rax - sub %rsi,%rax - cmp %rdx,%rax -.hidden __memcpy_fwd - jae __memcpy_fwd - mov %rdx,%rcx - lea -1(%rdi,%rdx),%rdi - lea -1(%rsi,%rdx),%rsi - std - rep movsb - cld - lea 1(%rdi),%rax - ret diff --git a/na/lib/string/x86_64/memset.s b/na/lib/string/x86_64/memset.s deleted file mode 100644 index 2d3f5e5..0000000 --- a/na/lib/string/x86_64/memset.s +++ /dev/null @@ -1,72 +0,0 @@ -.global memset -.type memset,@function -memset: - movzbq %sil,%rax - mov $0x101010101010101,%r8 - imul %r8,%rax - - cmp $126,%rdx - ja 2f - - test %edx,%edx - jz 1f - - mov %sil,(%rdi) - mov %sil,-1(%rdi,%rdx) - cmp $2,%edx - jbe 1f - - mov %ax,1(%rdi) - mov %ax,(-1-2)(%rdi,%rdx) - cmp $6,%edx - jbe 1f - - mov %eax,(1+2)(%rdi) - mov %eax,(-1-2-4)(%rdi,%rdx) - cmp $14,%edx - jbe 1f - - mov %rax,(1+2+4)(%rdi) - mov %rax,(-1-2-4-8)(%rdi,%rdx) - cmp $30,%edx - jbe 1f - - mov %rax,(1+2+4+8)(%rdi) - mov %rax,(1+2+4+8+8)(%rdi) - mov %rax,(-1-2-4-8-16)(%rdi,%rdx) - mov %rax,(-1-2-4-8-8)(%rdi,%rdx) - cmp $62,%edx - jbe 1f - - mov %rax,(1+2+4+8+16)(%rdi) - mov %rax,(1+2+4+8+16+8)(%rdi) - mov %rax,(1+2+4+8+16+16)(%rdi) - mov %rax,(1+2+4+8+16+24)(%rdi) - mov %rax,(-1-2-4-8-16-32)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-24)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-16)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-8)(%rdi,%rdx) - -1: mov %rdi,%rax - ret - -2: test $15,%edi - mov %rdi,%r8 - mov %rax,-8(%rdi,%rdx) - mov %rdx,%rcx - jnz 2f - -1: shr $3,%rcx - rep - stosq - mov %r8,%rax - ret - -2: xor %edx,%edx - sub %edi,%edx - and $15,%edx - mov %rax,(%rdi) - mov %rax,8(%rdi) - sub %rdx,%rcx - add %rdx,%rdi - jmp 1b diff --git a/na/mm/memblock.cc b/na/mm/memblock.cc deleted file mode 100644 index aee0245..0000000 --- a/na/mm/memblock.cc +++ /dev/null @@ -1,257 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * memblock.cc — Boot-time physical memory allocator - * - * Simple region tracker: one memory region (from FDT) plus an array - * of reserved regions. Allocation searches for gaps between reserved - * regions. All operations are O(n_reserved), which is fine for the - * ~20 allocations that happen during early boot. - * - * Reference: inspired by Linux kernel's mm/memblock.c - */ - -#include -#include -#include -#include -#include -#include - -extern "C" char _stext[], _kernel_end[]; - -/* ------------------------------------------------------------------ */ -/* Module state */ -/* ------------------------------------------------------------------ */ - -namespace { - -constexpr uint64 kMB = 1024ULL * 1024ULL; - -struct { - uint64 mem_base; - uint64 mem_size; - - struct { - uint64 base; - uint64 size; - } reserved[kMemblockMaxRegions]; - int reserved_count; - bool active; -} g_mb; - -/* ------------------------------------------------------------------ */ -/* Internal helpers */ -/* ------------------------------------------------------------------ */ - -/* Insert a reserved region, keeping the array sorted by base. */ -void insert_reserved(uint64 base, uint64 size) -{ - if (g_mb.reserved_count >= kMemblockMaxRegions) { - kernel_panic("memblock: too many reserved regions"); - } - - int i = g_mb.reserved_count; - while (i > 0 && g_mb.reserved[i - 1].base > base) { - g_mb.reserved[i] = g_mb.reserved[i - 1]; - i--; - } - g_mb.reserved[i] = {base, size}; - g_mb.reserved_count++; -} - -/* Check if [base, base+size) overlaps any reserved region. */ -bool overlaps_reserved(uint64 base, uint64 size) -{ - uint64 end = base + size; - for (int i = 0; i < g_mb.reserved_count; i++) { - uint64 rb = g_mb.reserved[i].base; - uint64 re = rb + g_mb.reserved[i].size; - if (base < re && end > rb) { - return true; - } - } - return false; -} - -/* Find the end of the first reserved region that covers `addr`. */ -uint64 reserved_end_covering(uint64 addr) -{ - for (int i = 0; i < g_mb.reserved_count; i++) { - uint64 rb = g_mb.reserved[i].base; - uint64 re = rb + g_mb.reserved[i].size; - if (addr >= rb && addr < re) { - return re; - } - } - return addr; -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Public API */ -/* ------------------------------------------------------------------ */ - -void memblock_init(uint64 fdt_paddr) -{ - - /* 1. Get memory layout from FDT */ - uint64 mem_base = 0; - uint64 mem_size = 0; - fdt_early_get_mem_info(&mem_base, &mem_size); - if (mem_size == 0) { - kernel_panic("memblock_init: FDT reports zero memory"); - } - - g_mb.mem_base = mem_base; - g_mb.mem_size = mem_size; - g_mb.reserved_count = 0; - g_mb.active = true; - - /* 2. Reserve firmware region [mem_base, kPhysStart). - * - * Everything below the kernel's linked load address is firmware - * territory (OpenSBI), which is PMP-protected. */ - { - uint64 fw_end = kPhysStart; - if (fw_end > mem_base) { - memblock_reserve(mem_base, fw_end - mem_base); - } - } - - /* 2b. Reserve stale pre-relocation kernel image. - * - * After physical KASLR, the kernel is relocated from its original - * load address (kPhysStart) to a new PA. The old copy at - * [kPhysStart, kPhysStart + kernel_size) must not be allocated — - * it contains stale code/data that would corrupt page tables or - * other structures if reused. */ - { - uint64 ks_pa = boot_va_to_pa(virt_addr( - reinterpret_cast(_stext))).raw; - if (ks_pa != kPhysStart) { - /* Kernel was relocated — reserve the original location */ - uint64 kernel_size = boot_va_to_pa(virt_addr( - reinterpret_cast(_kernel_end))).raw - ks_pa; - memblock_reserve(kPhysStart, kernel_size); - } - } - - /* 3. Reserve kernel image [_stext, _kernel_end) */ - uint64 ks_pa = boot_va_to_pa(virt_addr( - reinterpret_cast(_stext))).raw; - uint64 ke_pa = boot_va_to_pa(virt_addr( - reinterpret_cast(_kernel_end))).raw; - memblock_reserve(ks_pa, ke_pa - ks_pa); - - /* 4. Reserve FDT blob — it lives in RAM and must not be overwritten - * by vmemmap/page_map or buddy before fdt_unflatten() copies it. */ - { - struct fdt_hdr { uint32 magic; uint32 totalsize; }; - const auto* fh = reinterpret_cast(fdt_paddr); - uint32 fdt_size = ((fh->totalsize & 0xFF) << 24) | - (((fh->totalsize >> 8) & 0xFF) << 16) | - (((fh->totalsize >> 16) & 0xFF) << 8) | - ((fh->totalsize >> 24) & 0xFF); - memblock_reserve(fdt_paddr, fdt_size); - kprintf("[memblock] FDT reserved [0x%llx, 0x%llx)\n", - fdt_paddr, fdt_paddr + fdt_size); - } - - kprintf("[memblock] RAM [0x%llx, 0x%llx) = %llu MB\n", - mem_base, mem_base + mem_size, mem_size / kMB); - kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa); -} - -void memblock_reserve(uint64 base, uint64 size) -{ - uint64 aligned_base = page_align_down(base); - uint64 aligned_end = page_align_up(base + size); - insert_reserved(aligned_base, aligned_end - aligned_base); -} - -uint64 memblock_alloc_top(uint64 size, uint64 align) -{ - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - - /* Search from top of RAM downward */ - uint64 end = page_align_down(mem_end); - uint64 start = (end - size) & ~(align - 1); - - while (start >= g_mb.mem_base && start + size <= mem_end) { - if (!overlaps_reserved(start, size)) { - memblock_reserve(start, size); - return start; - } - if (start < align) { - break; - } - start -= align; - } - - kernel_panic("memblock_alloc_top: out of memory"); - return 0; -} - -uint64 memblock_alloc(uint64 size, uint64 align) -{ - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - uint64 start = page_align_up(g_mb.mem_base); - - while (start + size <= mem_end) { - start = (start + align - 1) & ~(align - 1); - if (start + size > mem_end) { - break; - } - - if (!overlaps_reserved(start, size)) { - memblock_reserve(start, size); - return start; - } - - /* Skip past the overlapping reserved region */ - uint64 skip = reserved_end_covering(start); - if (skip <= start) { - start += align; - } else { - start = skip; - } - } - - kernel_panic("memblock_alloc: out of memory"); - return 0; -} - -uint64 memblock_get_mem_base() { return g_mb.mem_base; } -uint64 memblock_get_mem_size() { return g_mb.mem_size; } - -bool memblock_is_active() { return g_mb.active; } -void memblock_retire() { g_mb.active = false; } - -void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), - void* ctx) -{ - uint64 pos = g_mb.mem_base; - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - - for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) { - uint64 rb = g_mb.reserved[i].base; - if (rb < pos) { - uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size; - if (re > pos) { - pos = re; - } - continue; - } - if (rb > pos) { - cb(pos, rb - pos, ctx); - } - pos = g_mb.reserved[i].base + g_mb.reserved[i].size; - } - - if (pos < mem_end) { - cb(pos, mem_end - pos, ctx); - } -} diff --git a/na/mm/module.mk b/na/mm/module.mk deleted file mode 100644 index 93cef80..0000000 --- a/na/mm/module.mk +++ /dev/null @@ -1,7 +0,0 @@ -SRCS_CC += mm/memblock.cc -SRCS_CC += mm/pmm.cc -SRCS_CC += mm/slab.cc -SRCS_CC += mm/vmm.cc -SRCS_CC += mm/vma.cc -SRCS_CC += mm/page_cache.cc -SRCS_CC += mm/tlb.cc diff --git a/na/mm/page_cache.cc b/na/mm/page_cache.cc deleted file mode 100644 index 25c6a60..0000000 --- a/na/mm/page_cache.cc +++ /dev/null @@ -1,252 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * page_cache.cc — Minimal page cache implementation - * - * Open-addressing hash table indexed by (mapping, index) pairs. - * Fixed-size bucket array allocated at init time via slab. - * - * This is intentionally simple: no LRU, no writeback, no eviction. - * The interface in page_cache.h is designed so that upgrading to a - * radix tree + LRU scheme later only changes this file. - * - * Concurrency: protected by a single spinlock. Acceptable for - * initial bring-up; will be replaced by per-bucket or RCU-based - * locking when contention becomes measurable. - */ - -#include -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Hash table internals */ -/* ------------------------------------------------------------------ */ - -struct CacheEntry { - void* mapping; // nullptr = empty slot - uint64 index; - Page* page; -}; - -namespace { - -constexpr uint64 kCacheBuckets = 1024; - -CacheEntry g_buckets[kCacheBuckets]; -Spinlock g_cache_lock; - -uint64 hash_key(void* mapping, uint64 index) -{ - uint64 m = reinterpret_cast(mapping); - /* FNV-1a inspired mixing */ - uint64 h = m ^ (index * 0x517cc1b727220a95ULL); - h ^= h >> 17; - h *= 0xbf58476d1ce4e5b9ULL; - h ^= h >> 31; - return h % kCacheBuckets; -} - -/* Linear probe to find an entry or an empty slot. */ -CacheEntry* find_slot(void* mapping, uint64 index) -{ - uint64 start = hash_key(mapping, index); - for (uint64 i = 0; i < kCacheBuckets; i++) { - uint64 idx = (start + i) % kCacheBuckets; - CacheEntry* e = &g_buckets[idx]; - if (e->mapping == nullptr) { - return e; // Empty slot - } - if (e->mapping == mapping && e->index == index) { - return e; // Found - } - } - return nullptr; // Table full -} - -/* Find an existing entry (not an empty slot). */ -CacheEntry* find_entry(void* mapping, uint64 index) -{ - uint64 start = hash_key(mapping, index); - for (uint64 i = 0; i < kCacheBuckets; i++) { - uint64 idx = (start + i) % kCacheBuckets; - CacheEntry* e = &g_buckets[idx]; - if (e->mapping == nullptr) { - return nullptr; // Empty = not found - } - if (e->mapping == mapping && e->index == index) { - return e; - } - } - return nullptr; -} - -/* - * Remove an entry and fix up the linear-probe chain. - * Standard open-addressing deletion with backward shift. - */ -void remove_entry(CacheEntry* entry) -{ - uint64 removed = static_cast(entry - g_buckets); - entry->mapping = nullptr; - entry->page = nullptr; - - /* Backward-shift deletion to maintain probe chain integrity */ - uint64 cur = (removed + 1) % kCacheBuckets; - while (g_buckets[cur].mapping != nullptr) { - uint64 ideal = hash_key(g_buckets[cur].mapping, g_buckets[cur].index); - /* Check if cur's ideal position is "before" removed in probe order */ - bool needs_shift = false; - if (removed <= cur) { - needs_shift = (ideal <= removed || ideal > cur); - } else { - needs_shift = (ideal <= removed && ideal > cur); - } - - if (needs_shift) { - g_buckets[removed] = g_buckets[cur]; - g_buckets[cur].mapping = nullptr; - g_buckets[cur].page = nullptr; - removed = cur; - } - cur = (cur + 1) % kCacheBuckets; - } -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Public API */ -/* ------------------------------------------------------------------ */ - -void page_cache_init() -{ - g_cache_lock.init("page_cache"); - for (uint64 i = 0; i < kCacheBuckets; i++) { - g_buckets[i].mapping = nullptr; - g_buckets[i].index = 0; - g_buckets[i].page = nullptr; - } -} - -Page* page_cache_get(void* mapping, uint64 index) -{ - LockGuard guard(g_cache_lock); - CacheEntry* e = find_entry(mapping, index); - if (!e) { - return nullptr; - } - page_incref(e->page); - return e->page; -} - -int page_cache_insert(void* mapping, uint64 index, Page* page) -{ - if (!mapping || !page) { - return -EINVAL; - } - - LockGuard guard(g_cache_lock); - - /* Check for existing entry */ - CacheEntry* existing = find_entry(mapping, index); - if (existing) { - return -EEXIST; - } - - /* Find an empty slot */ - CacheEntry* slot = find_slot(mapping, index); - if (!slot || slot->mapping != nullptr) { - return -ENOMEM; // Table full - } - - slot->mapping = mapping; - slot->index = index; - slot->page = page; - page_incref(page); // Cache holds a reference - - return 0; -} - -void page_cache_put(Page* page) -{ - if (!page) { - return; - } - page_decref(page); -} - -Page* page_cache_remove(void* mapping, uint64 index) -{ - LockGuard guard(g_cache_lock); - CacheEntry* e = find_entry(mapping, index); - if (!e) { - return nullptr; - } - - Page* page = e->page; - remove_entry(e); - page_decref(page); // Release cache's reference - return page; -} - -void page_cache_invalidate(void* mapping) -{ - if (!mapping) { - return; - } - - LockGuard guard(g_cache_lock); - - /* Scan all buckets — acceptable for current table size */ - for (uint64 i = 0; i < kCacheBuckets; i++) { - if (g_buckets[i].mapping == mapping) { - Page* page = g_buckets[i].page; - remove_entry(&g_buckets[i]); - page_decref(page); - /* After removal, re-check this index since backward shift - * may have moved an entry into this slot. */ - i--; - } - } -} - -Page* page_cache_get_or_alloc(void* mapping, uint64 index) -{ - /* Fast path: already cached */ - Page* page = page_cache_get(mapping, index); - if (page) { - return page; - } - - /* Slow path: allocate and insert */ - page = pmm_alloc_page(); - if (!page) { - return nullptr; - } - - int ret = page_cache_insert(mapping, index, page); - if (ret == -EEXIST) { - /* Race: another thread inserted between our get and insert. - * Free our allocation and return the existing page. */ - pmm_free_page(page); - return page_cache_get(mapping, index); - } - if (ret < 0) { - pmm_free_page(page); - return nullptr; - } - - /* Caller gets a ref (insert already added cache's ref, - * we need one more for the caller). */ - /* Actually, insert did incref for cache. The page already has - * refcount=1 from alloc + 1 from insert = 2. - * We return it to caller who must put() when done. - * So we incref once more for the caller's reference. */ - page_incref(page); - return page; -} diff --git a/na/mm/pmm.cc b/na/mm/pmm.cc deleted file mode 100644 index b7cf611..0000000 --- a/na/mm/pmm.cc +++ /dev/null @@ -1,528 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pmm.cc — Physical Memory Manager implementation - * - * Buddy allocator with Per-CPU single-page caches (PCP). - * - * Initialization sequence (called by BSP in single-core period): - * 1. memblock + vmm_init allocate and map the page_map at VMEMMAP. - * 2. Zero and mark all Page descriptors reserved by default. - * 3. Carve free regions from memblock into the buddy system. - * - * The page_map is a flat array indexed by physical page frame number (PFN). - * PFN = (pa - mem_base) / PAGE_SIZE. This keeps address conversion O(1). - * - * Reference: docs/specs/pmm.md - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Per-CPU page cache */ -/* ------------------------------------------------------------------ */ - -struct PerCpuCache { - list_node free_list; // Single-page free list - uint32 count; // Number of cached pages -}; - -/* ------------------------------------------------------------------ */ -/* Module-private state */ -/* ------------------------------------------------------------------ */ - -namespace { - -/* PCP Cache tuning parameters */ -constexpr uint32 kPcpBatchDivisor = 1024; -constexpr uint32 kPcpMinBatchSize = 16; -constexpr uint64 kMB = 1024ULL * 1024ULL; - -Spinlock g_pmm_lock; -list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10] -PerCpuCache g_pcp[kMaxCpus]; - -Page *g_page_map = nullptr; // Flat array of Page descriptors -uint64 g_page_map_pa = 0; // Physical base of page_map storage -uint64 g_page_map_bytes = 0; // Byte size of page_map storage -uint64 g_mem_base = 0; // Physical memory base (from FDT) -uint64 g_mem_size = 0; // Total physical memory size -uint64 g_num_pages = 0; // Total managed pages - -} // namespace - -uint32 g_pcp_high_watermark = 0; -uint32 g_pcp_batch_size = 0; - -/* ------------------------------------------------------------------ */ -/* Address conversion helpers */ -/* ------------------------------------------------------------------ */ - -/* - * PFN (Page Frame Number) — fundamental conversion unit. - * When sparse vmemmap is implemented, only these two functions change. - * All other PA↔Page conversions go through them. - */ -uint64 page_to_pfn(const Page *page) { - uintptr base = reinterpret_cast(g_page_map); - uintptr addr = reinterpret_cast(page); - uintptr end = base + g_num_pages * sizeof(Page); - - if (addr < base || addr >= end) { - kernel_panic("page_to_pfn: page outside page_map bounds"); - } - - uint64 idx = (addr - base) / sizeof(Page); - return (g_mem_base / kPageSize) + idx; -} - -Page *pfn_to_page(uint64 pfn) { - uint64 base_pfn = g_mem_base / kPageSize; - if (pfn < base_pfn || pfn >= base_pfn + g_num_pages) { - kernel_panic("pfn_to_page: PFN outside managed range"); - } - return &g_page_map[pfn - base_pfn]; -} - -PhysAddr page_to_pa(const Page *page) { - return pfn_to_pa(Pfn{page_to_pfn(page)}); -} - -Page *pa_to_page(PhysAddr pa) { - return pfn_to_page(pa_to_pfn(pa).raw); -} - -VirtAddr page_to_va(const Page *page) { - return pa_to_va(page_to_pa(page)); -} - - -/* ------------------------------------------------------------------ */ -/* Buddy system internals (caller must hold g_pmm_lock) */ -/* ------------------------------------------------------------------ */ - -namespace { - -/* - * Find a page's buddy at a given order. - * Buddy PFN = PFN ^ (1 << order). - */ -inline Page *buddy_of(Page *page, uint8 order) { - uint64 pfn = static_cast(page - g_page_map); - uint64 buddy_pfn = pfn ^ (1ULL << order); - if (buddy_pfn >= g_num_pages) { - return nullptr; - } - return &g_page_map[buddy_pfn]; -} - -/* - * Insert a free block into the buddy free list at the given order. - */ -void buddy_list_add(Page *page, uint8 order) { - page->type = PageType::Free; - page->order = order; - list_node *head = &g_free_areas[order]; - list_node *node = &page->free.free_link; - node->next = head->next; - node->prev = head; - head->next->prev = node; - head->next = node; -} - -/* - * Remove a block from whatever buddy free list it is on. - */ -void buddy_list_del(Page *page) { - list_node *node = &page->free.free_link; - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = node; - node->prev = node; -} - -/* - * Allocate a block of 2^order contiguous pages from the buddy system. - * Splits higher-order blocks if no exact match is available. - * Returns nullptr if no memory available. - */ -Page *buddy_alloc(uint8 order) { - for (int cur = order; cur < kMaxOrder; cur++) { - list_node *head = &g_free_areas[cur]; - if (head->next == head) { - continue; // Empty at this order - } - - // Pop the first block from this order's free list - list_node *node = head->next; - // Compute Page* from the free_link member offset - Page *page = - reinterpret_cast(reinterpret_cast(node) - - __builtin_offsetof(Page, free.free_link)); - buddy_list_del(page); - - // Split down to the requested order - while (cur > order) { - cur--; - // The upper half becomes a free buddy - Page *split = &g_page_map[(page - g_page_map) + (1ULL << cur)]; - buddy_list_add(split, static_cast(cur)); - } - - page->type = PageType::Allocated; - page->order = order; - page->refcount.store(1, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->mapping = nullptr; - page->index = 0; - return page; - } - return nullptr; -} - -/* - * Return a block of 2^order pages to the buddy system. - * Coalesces with its buddy recursively up to kMaxOrder-1. - */ -void buddy_free(Page *page, uint8 order) { - uint64 pfn = static_cast(page - g_page_map); - - while (order < kMaxOrder - 1) { - Page *buddy = buddy_of(page, order); - if (!buddy) { - break; - } - // Buddy must be free AND at the same order to coalesce - if (buddy->type != PageType::Free || buddy->order != order) { - break; - } - - // Remove buddy from its free list and merge - buddy_list_del(buddy); - - // The merged block starts at the lower PFN - uint64 buddy_pfn = static_cast(buddy - g_page_map); - if (buddy_pfn < pfn) { - page = buddy; - pfn = buddy_pfn; - } - order++; - } - - buddy_list_add(page, order); -} - -/* ------------------------------------------------------------------ */ -/* Per-CPU cache operations (interrupts must be disabled by caller) */ -/* ------------------------------------------------------------------ */ - -/* - * Refill the PCP from the global buddy system. - * Acquires g_pmm_lock internally. - */ -void refill_pcp(PerCpuCache *pcp) { - LockGuard guard(g_pmm_lock); - for (uint32 i = 0; i < g_pcp_batch_size; i++) { - Page *page = buddy_alloc(0); - if (!page) { - break; - } - // Put onto PCP list (reuse the free_link for PCP chain) - page->type = PageType::PcpCached; - list_node *node = &page->free.free_link; - node->next = pcp->free_list.next; - node->prev = &pcp->free_list; - pcp->free_list.next->prev = node; - pcp->free_list.next = node; - pcp->count++; - } -} - -/* - * Drain half the PCP back to the global buddy system. - * Acquires g_pmm_lock internally. - */ -void drain_pcp(PerCpuCache *pcp) { - LockGuard guard(g_pmm_lock); - int to_drain = pcp->count / 2; - for (int i = 0; i < to_drain; i++) { - list_node *head = &pcp->free_list; - if (head->next == head) { - break; - } - list_node *node = head->next; - // Unlink from PCP - node->prev->next = node->next; - node->next->prev = node->prev; - pcp->count--; - - Page *page = - reinterpret_cast(reinterpret_cast(node) - - __builtin_offsetof(Page, free.free_link)); - buddy_free(page, 0); - } -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Public API: single-page (fast path via PCP) */ -/* ------------------------------------------------------------------ */ - -Page *pmm_alloc_page() { - /* - * Disable interrupts to prevent reentry on the same CPU - * (e.g. timer IRQ handler calling pmm_alloc_page while we - * are mid-way through manipulating the PCP list). - */ - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - PerCpuCache *pcp = &g_pcp[cpu]; - - if (pcp->count == 0) { - refill_pcp(pcp); - } - - Page *page = nullptr; - if (pcp->count > 0) { - list_node *node = pcp->free_list.next; - // Unlink - node->prev->next = node->next; - node->next->prev = node->prev; - pcp->count--; - - page = - reinterpret_cast(reinterpret_cast(node) - - __builtin_offsetof(Page, free.free_link)); - - // Validate the page is within the page_map bounds - if (page < g_page_map || page >= g_page_map + g_num_pages) { - kernel_panic("pmm_alloc_page: PCP returned out-of-bounds page"); - } - - page->type = PageType::Allocated; - page->order = 0; - page->refcount.store(1, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->mapping = nullptr; - page->index = 0; - } - - if (was_on) { - arch::cpu::intr_on(); - } - return page; -} - -void pmm_free_page(Page *page) { - if (!page) { - kernel_panic("pmm_free_page: null page"); - } - - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - PerCpuCache *pcp = &g_pcp[cpu]; - - // Push onto PCP - page->type = PageType::PcpCached; - page->order = 0; - list_node *node = &page->free.free_link; - node->next = pcp->free_list.next; - node->prev = &pcp->free_list; - pcp->free_list.next->prev = node; - pcp->free_list.next = node; - pcp->count++; - - // Drain if over high watermark - if (pcp->count >= g_pcp_high_watermark) { - drain_pcp(pcp); - } - - if (was_on) { - arch::cpu::intr_on(); - } -} - -/* ------------------------------------------------------------------ */ -/* Public API: multi-page (slow path, direct buddy) */ -/* ------------------------------------------------------------------ */ - -Page *pmm_alloc_pages(uint8 order) { - if (order >= kMaxOrder) { - return nullptr; - } - LockGuard guard(g_pmm_lock); - return buddy_alloc(order); -} - -void pmm_free_pages(Page *page, uint8 order) { - if (!page) { - kernel_panic("pmm_free_pages: null page"); - } - if (order >= kMaxOrder) { - kernel_panic("pmm_free_pages: order out of range"); - } - LockGuard guard(g_pmm_lock); - buddy_free(page, order); -} - -/* ------------------------------------------------------------------ */ -/* Public API: reference counting */ -/* ------------------------------------------------------------------ */ - -void page_incref(Page *page) { page->refcount.fetch_add(1); } - -void page_decref(Page *page) { - uint32 old = page->refcount.fetch_sub(1); - if (old == 1) { - // Refcount dropped to 0 — return to pool based on originally allocated - // order - if (page->order == 0) { - pmm_free_page(page); - } else { - pmm_free_pages(page, page->order); - } - } -} - -/* ------------------------------------------------------------------ */ -/* Initialization */ -/* ------------------------------------------------------------------ */ - -/* - * Helper: zero out a range of bytes. We cannot use memset here - * because lib/string may not be linked yet. - */ -namespace { -void zero_range(void *start, uint64 bytes) { - uint8 *p = static_cast(start); - for (uint64 i = 0; i < bytes; i++) { - p[i] = 0; - } -} -} // namespace - -void pmm_init() { - /* 1. Get memory layout from memblock (already scanned FDT) */ - g_mem_base = memblock_get_mem_base(); - g_mem_size = memblock_get_mem_size(); - g_num_pages = vmm_get_num_pages(); - - if (g_mem_size == 0) { - kernel_panic("pmm_init: memblock reports zero memory"); - } - - /* 2. PCP parameters */ - uint32 calc_batch = static_cast(g_num_pages / (kMaxCpus * kPcpBatchDivisor)); - g_pcp_batch_size = (calc_batch > kPcpMinBatchSize) ? calc_batch : kPcpMinBatchSize; - g_pcp_high_watermark = g_pcp_batch_size * 4; - - /* 3. page_map was allocated by vmm_init at vmemmap VA. - * No relocation needed — page_map starts life at the right VA. */ - g_page_map_pa = vmm_get_page_map_pa(); - g_page_map_bytes = vmm_get_page_map_size(); - g_page_map = reinterpret_cast(arch::g_vmemmap_base); - - /* 4. Zero out the entire page_map */ - zero_range(g_page_map, g_page_map_bytes); - - /* 4b. Default every page descriptor to reserved/non-free. */ - for (uint64 i = 0; i < g_num_pages; i++) { - Page *page = &g_page_map[i]; - page->refcount.store(0, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->type = PageType::Allocated; - page->order = 0; - page->mapping = nullptr; - page->index = 0; - page->lru.init(); - page->free.free_link.init(); - } - - /* 5. Initialize buddy free list heads */ - g_pmm_lock.init("pmm"); - for (int i = 0; i < kMaxOrder; i++) { - g_free_areas[i].init(); - } - - /* 6. Initialize PCP list heads */ - for (int i = 0; i < kMaxCpus; i++) { - g_pcp[i].free_list.init(); - g_pcp[i].count = 0; - } - - /* 7. Feed free regions from memblock into the buddy system. - * memblock knows about all reserved regions (firmware, kernel, - * page table pages, page_map). Everything else is free. */ - struct FeedCtx { - uint64 total_pages; - }; - FeedCtx ctx{0}; - - memblock_for_each_free( - [](uint64 base, uint64 size, void *arg) { - auto *c = static_cast(arg); - uint64 start_pfn = (base - memblock_get_mem_base()) / kPageSize; - uint64 end_pfn = start_pfn + size / kPageSize; - - /* Access page_map via the globals directly (they are in anon namespace now) */ - - uint64 pfn = start_pfn; - while (pfn < end_pfn) { - uint8 order = 0; - for (int o = kMaxOrder - 1; o >= 0; o--) { - uint64 block_size = 1ULL << o; - if ((pfn & (block_size - 1)) == 0 && pfn + block_size <= end_pfn) { - order = static_cast(o); - break; - } - } - - Page *page = &g_page_map[pfn]; - page->type = PageType::Free; - page->order = order; - page->refcount.store(0, __ATOMIC_RELAXED); - page->free.free_link.init(); - - /* Inline buddy_list_add since we're inside a lambda */ - list_node *head = &g_free_areas[order]; - list_node *node = &page->free.free_link; - node->next = head->next; - node->prev = head; - head->next->prev = node; - head->next = node; - - pfn += (1ULL << order); - c->total_pages += (1ULL << order); - } - }, - &ctx); - - kprintf("[pmm] buddy: %llu free pages (%llu MB)\n", ctx.total_pages, - (ctx.total_pages * kPageSize) / kMB); - - /* 8. Retire memblock — all future allocations go through PMM */ - memblock_retire(); -} - -PhysAddr pmm_get_mem_base() { return phys_addr(g_mem_base); } -uint64 pmm_get_mem_size() { return g_mem_size; } diff --git a/na/mm/slab.cc b/na/mm/slab.cc deleted file mode 100644 index 43d8c4a..0000000 --- a/na/mm/slab.cc +++ /dev/null @@ -1,321 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * slab.cc — SLUB allocator implementation - * - * Key design points: - * - Free object list is embedded in the objects themselves (first 8 bytes). - * - Per-page metadata lives in Page.slab (cache, obj_count, freelist). - * - Per-CPU "active page" avoids lock contention on the fast path. - * - partial_ list protected by per-KmemCache Spinlock. - * - PMM calls happen outside the lock to avoid lock ordering issues. - * - * Address note: the kernel may be running at physical addresses via - * identity mapping (before vmm_init). We handle PA/VA carefully: - * PMM gives us Page*, page_to_pa() gives PA, and we access page - * contents via pa_to_va() (higher-half VA, always mapped). - * - * Reference: docs/specs/slab.md - */ - -#include "slab.h" -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Helpers */ -/* ------------------------------------------------------------------ */ - -/* - * Convert a Page* to the usable virtual address of the page's contents. - * Uses the higher-half mapping which is always active. - */ -static inline void* page_to_data_va(Page* page) -{ - return reinterpret_cast(page_to_va(page).raw); -} - -/* Round up x to the next multiple of align (align must be power of 2) */ -static inline uint32 align_up(uint32 x, uint32 align) -{ - return (x + align - 1) & ~(align - 1); -} - -/* ------------------------------------------------------------------ */ -/* Global size-class caches */ -/* ------------------------------------------------------------------ */ - -KmemCache g_size_caches[kNumSizeClasses]; - -/* ------------------------------------------------------------------ */ -/* KmemCache::init */ -/* ------------------------------------------------------------------ */ - -void KmemCache::init(const char* name, uint32 obj_size, uint32 obj_align) -{ - name_ = name; - obj_align_ = (obj_align < 8) ? 8 : obj_align; // minimum 8 for freelist ptr - obj_size_ = align_up((obj_size < 8) ? 8 : obj_size, obj_align_); - objs_per_slab_ = kPageSize / obj_size_; - - lock_.init(name); - partial_.init(); - - for (int i = 0; i < kMaxCpus; i++) - cpu_slab_[i].active = nullptr; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::new_slab — allocate and format a fresh slab page */ -/* ------------------------------------------------------------------ */ - -Page* KmemCache::new_slab() -{ - Page* page = pmm_alloc_page(); - if (!page) - return nullptr; - - page->type = PageType::Slab; - page->slab.cache = this; - page->slab.obj_count = 0; - - /* Build the embedded free list across all object slots */ - uint8* base = static_cast(page_to_data_va(page)); - void* head = nullptr; - - /* Chain objects from last to first so freelist order = low addr first */ - for (int i = static_cast(objs_per_slab_) - 1; i >= 0; i--) { - void* obj = base + i * obj_size_; - *reinterpret_cast(obj) = head; - head = obj; - } - page->slab.freelist = head; - - return page; -} - -/* ------------------------------------------------------------------ */ -/* Partial list management (lock must be held) */ -/* ------------------------------------------------------------------ */ - -Page* KmemCache::get_partial() -{ - /* - * Phase 1 simplification: no partial list tracking. - * The slow path always allocates a fresh page from PMM. - * Full pages stay alive (Page.slab.cache is valid for kfree). - * Empty pages are returned to PMM immediately. - * - * TODO: add list_node to Page.slab and implement partial tracking - * to avoid wasting partially-filled pages. - */ - (void)partial_; - return nullptr; -} - -void KmemCache::put_partial(Page* page) -{ - (void)page; -} - -void KmemCache::remove_partial(Page* page) -{ - (void)page; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::alloc */ -/* ------------------------------------------------------------------ */ - -void* KmemCache::alloc() -{ - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - Page* active = cpu_slab_[cpu].active; - - /* Fast path: pop from active page's freelist */ - if (active && active->slab.freelist) { - void* obj = active->slab.freelist; - active->slab.freelist = *reinterpret_cast(obj); - active->slab.obj_count++; - if (was_on) arch::cpu::intr_on(); - return obj; - } - - /* Slow path: need a new active page */ - - /* If current active is full, detach it (it stays as Slab state, - * cache pointer still valid for kfree to find it) */ - if (active && !active->slab.freelist) { - cpu_slab_[cpu].active = nullptr; - } - - if (was_on) arch::cpu::intr_on(); - - /* Try to get a partial page or allocate a fresh one. - * PMM call happens without any lock held. */ - Page* new_page = new_slab(); - if (!new_page) - return nullptr; - - /* Install as new active page */ - was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - /* Another interrupt handler might have set a new active while we - * were allocating. If so, put ours aside (simplified: just use ours). */ - cpu_slab_[cpu].active = new_page; - - void* obj = new_page->slab.freelist; - new_page->slab.freelist = *reinterpret_cast(obj); - new_page->slab.obj_count++; - - if (was_on) arch::cpu::intr_on(); - return obj; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::free */ -/* ------------------------------------------------------------------ */ - -void KmemCache::free(void* obj) -{ - if (!obj) - return; - - /* Find the Page for this object's page */ - uint64 obj_addr = reinterpret_cast(obj); - - /* Convert VA to PA. Like pmm.cc, handle both PA and VA cases. */ - uint64 obj_pa = va_to_pa(virt_addr(obj_addr)).raw; - uint64 page_pa = obj_pa & ~(kPageSize - 1); - Page* page = pa_to_page(phys_addr(page_pa)); - - if (page->type != PageType::Slab || page->slab.cache != this) - kernel_panic("slab free: object does not belong to this cache"); - - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - /* Push object back onto the page's freelist */ - *reinterpret_cast(obj) = page->slab.freelist; - page->slab.freelist = obj; - page->slab.obj_count--; - - /* If page is now completely empty, return it to PMM */ - if (page->slab.obj_count == 0) { - /* If this page is our active, clear it */ - uint64 cpu = arch::cpu::id(); - if (cpu_slab_[cpu].active == page) - cpu_slab_[cpu].active = nullptr; - - if (was_on) arch::cpu::intr_on(); - - /* Return page to PMM */ - page->slab.cache = nullptr; - page->slab.freelist = nullptr; - pmm_free_page(page); - return; - } - - if (was_on) arch::cpu::intr_on(); -} - -/* ------------------------------------------------------------------ */ -/* Public free-function API */ -/* ------------------------------------------------------------------ */ - -/* Find the smallest size class >= size */ -static KmemCache* find_cache(uint32 size) -{ - for (int i = 0; i < kNumSizeClasses; i++) { - if (kSizeClasses[i] >= size) - return &g_size_caches[i]; - } - return nullptr; // Too large for slab -} - -void* kmalloc(uint32 size) -{ - if (size == 0) - return nullptr; - - /* Large allocation: fall back to PMM directly */ - if (size > kSizeClasses[kNumSizeClasses - 1]) { - uint8 order = 0; - uint64 needed = (size + kPageSize - 1) / kPageSize; - while ((1ULL << order) < needed) - order++; - Page* page = pmm_alloc_pages(order); - if (!page) - return nullptr; - return reinterpret_cast(pa_to_va(page_to_pa(page)).raw); - } - - KmemCache* cache = find_cache(size); - if (!cache) - return nullptr; - return cache->alloc(); -} - -void kfree(void* ptr) -{ - if (!ptr) - return; - - uint64 addr = reinterpret_cast(ptr); - uint64 pa = va_to_pa(virt_addr(addr)).raw; - uint64 page_pa = pa & ~(kPageSize - 1); - Page* page = pa_to_page(phys_addr(page_pa)); - - if (page->type == PageType::Slab) { - KmemCache* cache = page->slab.cache; - if (!cache) - kernel_panic("kfree: slab page has null cache"); - cache->free(ptr); - } else if (page->type == PageType::Allocated) { - /* Large allocation — return directly to PMM */ - pmm_free_pages(page, page->order); - } else { - kernel_panic("kfree: invalid page state"); - } -} - -/* ------------------------------------------------------------------ */ -/* C++ operator new / delete */ -/* ------------------------------------------------------------------ */ - -/* Use decltype(sizeof(0)) instead of size_t to match the compiler's - * built-in expectation for operator new (unsigned long vs unsigned long long). */ -using __sz = decltype(sizeof(0)); - -void* operator new(__sz size) { return kmalloc(static_cast(size)); } -void* operator new[](__sz size) { return kmalloc(static_cast(size)); } - -void operator delete(void* ptr) noexcept { kfree(ptr); } -void operator delete[](void* ptr) noexcept { kfree(ptr); } -void operator delete(void* ptr, __sz) noexcept { kfree(ptr); } -void operator delete[](void* ptr, __sz) noexcept { kfree(ptr); } - -/* ------------------------------------------------------------------ */ -/* Initialization */ -/* ------------------------------------------------------------------ */ - -void slab_init() -{ - for (int i = 0; i < kNumSizeClasses; i++) { - /* Build a name string like "kmalloc-32" */ - static const char* names[kNumSizeClasses] = { - "kmalloc-8", "kmalloc-16", "kmalloc-32", - "kmalloc-64", "kmalloc-128", "kmalloc-256", - "kmalloc-512", "kmalloc-1024","kmalloc-2048" - }; - g_size_caches[i].init(names[i], kSizeClasses[i], 8); - } -} diff --git a/na/mm/slab.h b/na/mm/slab.h deleted file mode 100644 index 909ee62..0000000 --- a/na/mm/slab.h +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * SLUB Allocator — module-private header - * - * SLUB = Simple List of Unfull Blocks. - * Metadata lives in Page descriptors (no per-page Slab header). - * Free object list is embedded inside the objects themselves. - * Per-CPU "active page" eliminates lock contention on the fast path. - * - * Reference: docs/specs/slab.md - */ - -#include -#include -#include -#include -#include - -/* Size classes: 8, 16, 32, 64, 128, 256, 512, 1024, 2048 */ -inline constexpr int kNumSizeClasses = 9; -inline constexpr uint32 kSizeClasses[kNumSizeClasses] = { - 8, 16, 32, 64, 128, 256, 512, 1024, 2048 -}; - -/* Per-CPU active slab page pointer */ -struct PerCpuSlab { - Page* active; // Currently used slab page (nullptr if none) -}; - -/* - * KmemCache — manages a pool of fixed-size objects. - * - * Lock contract: - * lock_ protects partial_ list only. - * Per-CPU cpu_slab_ access requires interrupts disabled. - * PMM calls happen outside lock_ to avoid lock ordering issues. - */ -class KmemCache { -public: - KmemCache() = default; - - // Explicit init (Placement New / BSS pattern) - void init(const char* name, uint32 obj_size, uint32 obj_align); - - // Allocate one object. Returns nullptr on OOM. - void* alloc(); - - // Free one object back to its owning page. - void free(void* obj); - - const char* name() const { return name_; } - uint32 obj_size() const { return obj_size_; } - -private: - // Get a partially-filled page from the partial list (lock must be held) - Page* get_partial(); - - // Put a page onto the partial list (lock must be held) - void put_partial(Page* page); - - // Remove a page from the partial list (lock must be held) - void remove_partial(Page* page); - - // Allocate a new page from PMM and format it as a slab - Page* new_slab(); - - Spinlock lock_; // Protects partial_ only - list_node partial_; // Partially-filled slab pages - - PerCpuSlab cpu_slab_[kMaxCpus]; // Per-CPU active page - - const char* name_ = "uninit"; - uint32 obj_size_ = 0; // Aligned object size (bytes) - uint32 obj_align_ = 0; // Alignment requirement - uint32 objs_per_slab_ = 0; // Objects per 4KB page -}; - -/* Global size-class caches */ -extern KmemCache g_size_caches[kNumSizeClasses]; diff --git a/na/mm/tlb.cc b/na/mm/tlb.cc deleted file mode 100644 index b7a8ba7..0000000 --- a/na/mm/tlb.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tlb.cc — TLB shootdown implementation - * - * Uses RISC-V SBI Remote Fence extension to perform cross-hart - * TLB invalidation. SBI firmware handles the IPI delivery and - * synchronization — the call blocks until all target harts have - * completed their local sfence.vma. - * - * When a VmSpace is provided, we use its cpu_mask to target only - * the CPUs that have this address space loaded in satp. This avoids - * unnecessary IPI storms on unrelated cores. - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.1 - */ - -#include -#include -#include -#include -#include -#include - -/* - * If a shootdown range exceeds this many pages, do a full flush - * instead of per-page invalidation. - */ -static constexpr uint64 kShootdownPageThreshold = 32; - -/* - * Build a cpu mask for all online CPUs (fallback when mm is null). - */ -namespace { -uint64 all_cpus_mask() -{ - uint64 mask = 0; - for (uint32 i = 0; i < kMaxCpus; i++) { - mask |= (1ULL << i); - } - return mask; -} - -/* - * Get the target cpu mask: use mm->cpu_mask when available, - * fall back to all_cpus_mask for kernel address space (mm == nullptr). - */ -uint64 target_cpu_mask(VmSpace* mm) -{ - if (mm) { - uint64 mask = mm->cpu_mask.load(__ATOMIC_RELAXED); - /* Always include our own CPU — we may still have stale TLB */ - mask |= (1ULL << arch::cpu::id()); - return mask; - } - return all_cpus_mask(); -} -} // namespace - -void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end) -{ - if (start >= end) { - return; - } - - uint64 num_pages = (end - start) / kPageSize; - uint64 cpu_mask = target_cpu_mask(mm); - - if (num_pages > kShootdownPageThreshold) { - /* Full flush on target CPUs via arch backend */ - arch::tlb::flush_remote(cpu_mask, 0, ~0ULL); - return; - } - - /* Range flush on target CPUs via arch backend */ - arch::tlb::flush_remote(cpu_mask, start, end - start); -} - -void tlb_shootdown_page(VmSpace* mm, uint64 va) -{ - uint64 cpu_mask = target_cpu_mask(mm); - arch::tlb::flush_remote(cpu_mask, va, kPageSize); -} - -void tlb_shootdown_all() -{ - uint64 cpu_mask = all_cpus_mask(); - arch::tlb::flush_remote(cpu_mask, 0, ~0ULL); -} diff --git a/na/mm/vma.cc b/na/mm/vma.cc deleted file mode 100644 index da89e08..0000000 --- a/na/mm/vma.cc +++ /dev/null @@ -1,640 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vma.cc — Virtual Memory Area management (MI layer) - * - * Manages user address spaces using a red-black tree of VMA regions. - * Implements demand paging: vmm_map_user records intent only; - * physical pages are allocated on page fault via vmm_handle_page_fault. - * - * MI/MD separation: this file NEVER references pmap::PTE_* constants. - * Permission translation is delegated to pmap::vm_perm_to_pte(). - * - * Reference: docs/specs/vmm.md - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Rbtree callbacks for VMA interval tree */ -/* ------------------------------------------------------------------ */ - -static bool vma_less(struct rb_node* node, const struct rb_node* parent) { - VmAreaStruct* n = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); - const VmAreaStruct* p = reinterpret_cast( - reinterpret_cast(parent) - __builtin_offsetof(VmAreaStruct, rb)); - return n->start < p->start; -} - -static int vma_cmp(const void* key, const struct rb_node* node) { - uint64 addr = *static_cast(key); - const VmAreaStruct* n = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); - if (addr < n->start) return -1; - if (addr >= n->end) return 1; - return 0; -} - -static VmAreaStruct* node_to_vma(struct rb_node* node) -{ - return reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); -} - -static uint64 align_up_any(uint64 value, uint64 align) -{ - if (align == 0) { - return value; - } - return ((value + align - 1) / align) * align; -} - -static bool range_overlaps(uint64 a_start, uint64 a_end, - uint64 b_start, uint64 b_end) -{ - return a_start < b_end && b_start < a_end; -} - - - -static bool vma_overlaps_locked(VmSpace* space, uint64 start, uint64 end) -{ - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (range_overlaps(start, end, vma->start, vma->end)) { - return true; - } - node = rb_next(node); - } - return false; -} - -static bool vma_range_covered_locked(VmSpace* space, uint64 start, uint64 end) -{ - uint64 cursor = start; - struct rb_node* node = rb_first(&space->vma_tree); - while (node && cursor < end) { - VmAreaStruct* vma = node_to_vma(node); - if (vma->end <= cursor) { - node = rb_next(node); - continue; - } - if (vma->start > cursor) { - return false; - } - if (vma->end > cursor) { - cursor = vma->end; - } - node = rb_next(node); - } - return cursor >= end; -} - -static void vma_try_merge_neighbors_locked(VmSpace* space, VmAreaStruct* vma) -{ - struct rb_node* prev_node = rb_prev(&vma->rb); - if (prev_node) { - VmAreaStruct* prev = node_to_vma(prev_node); - if (prev->end == vma->start && prev->perm == vma->perm) { - prev->end = vma->end; - rb_erase(&vma->rb, &space->vma_tree); - delete vma; - vma = prev; - } - } - - struct rb_node* next_node = rb_next(&vma->rb); - if (next_node) { - VmAreaStruct* next = node_to_vma(next_node); - if (vma->end == next->start && vma->perm == next->perm) { - vma->end = next->end; - rb_erase(&next->rb, &space->vma_tree); - delete next; - } - } -} - -static int vma_split_locked(VmSpace* space, VmAreaStruct* vma, uint64 split) -{ - if (split <= vma->start || split >= vma->end) { - return 0; - } - - VmAreaStruct* right = new VmAreaStruct; - if (!right) { - return -ENOMEM; - } - - right->start = split; - right->end = vma->end; - right->perm = vma->perm; - right->mm = space; - - vma->end = split; - rb_add(&right->rb, &space->vma_tree, vma_less); - return 0; -} - -static int vma_split_range_locked(VmSpace* space, uint64 start, uint64 end) -{ - if (end <= start) { - return 0; - } - - struct rb_node* start_node = rb_find(&start, &space->vma_tree, vma_cmp); - if (!start_node) { - return -ENOMEM; - } - VmAreaStruct* start_vma = node_to_vma(start_node); - int ret = vma_split_locked(space, start_vma, start); - if (ret < 0) { - return ret; - } - - uint64 end_key = end - 1; - struct rb_node* end_node = rb_find(&end_key, &space->vma_tree, vma_cmp); - if (!end_node) { - return -ENOMEM; - } - VmAreaStruct* end_vma = node_to_vma(end_node); - return vma_split_locked(space, end_vma, end); -} - -/* ------------------------------------------------------------------ */ -/* VmSpace lifecycle */ -/* ------------------------------------------------------------------ */ - -VmSpace* VmSpace::create() { - VmSpace* space = new VmSpace; - if (!space) return nullptr; - - space->root_pa = pmap::create(); - if (!space->root_pa) { - delete space; - return nullptr; - } - - space->vma_tree = RB_ROOT; - space->lock.init("VmSpace"); - space->refcount.store(1, __ATOMIC_RELAXED); - space->brk_start = 0; - space->brk_end = 0; - return space; -} - -void VmSpace::incref() { - refcount.fetch_add(1, __ATOMIC_RELAXED); -} - -void VmSpace::put() { - if (refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) { - destroy(); - } -} - -void VmSpace::destroy() { - /* Drain VMA tree under lock, then release BEFORE delete this */ - lock.acquire(); - uint64 root = root_pa; - struct rb_node* node = rb_first(&vma_tree); - while (node) { - VmAreaStruct* vma = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); - - /* Decref and dec mapcount for all mapped physical pages */ - for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { - uint64 pa; - if (pmap::lookup(root, addr, &pa)) { - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - } - } - - node = rb_next(node); - rb_erase(&vma->rb, &vma_tree); - delete vma; - } - lock.release(); - - /* Destroy hardware page tables outside the lock */ - pmap::destroy(root); - - /* Finally free VmSpace itself */ - delete this; -} - -/* ------------------------------------------------------------------ */ -/* Public API: VMA manipulation */ -/* ------------------------------------------------------------------ */ - -int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) { - if (!space) return -EINVAL; - - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return 0; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - - VmAreaStruct* vma = new VmAreaStruct; - if (!vma) return -ENOMEM; - vma->start = va; - vma->end = va + len; - vma->perm = perm | VM_USER; // Enforce user-mode access (MI flag) - vma->mm = space; - - LockGuard guard(space->lock); - if (vma_overlaps_locked(space, vma->start, vma->end)) { - delete vma; - return -EINVAL; - } - rb_add(&vma->rb, &space->vma_tree, vma_less); - vma_try_merge_neighbors_locked(space, vma); - return 0; -} - -int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) { - if (!space) return -EINVAL; - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return 0; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - uint64 end = va + len; - - LockGuard guard(space->lock); - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - struct rb_node* next = rb_next(node); - - if (vma->end <= va) { - node = next; - continue; - } - if (vma->start >= end) { - break; - } - - uint64 unmap_start = (vma->start > va) ? vma->start : va; - uint64 unmap_end = (vma->end < end) ? vma->end : end; - - if (unmap_start < unmap_end) { - for (uint64 addr = unmap_start; addr < unmap_end; addr += kPageSize) { - uint64 pa; - if (pmap::lookup(space->root_pa, addr, &pa)) { - pmap::unmap(space->root_pa, addr); - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - } - } - } - - if (unmap_start == vma->start && unmap_end == vma->end) { - rb_erase(&vma->rb, &space->vma_tree); - delete vma; - } else if (unmap_start == vma->start) { - rb_erase(&vma->rb, &space->vma_tree); - vma->start = unmap_end; - rb_add(&vma->rb, &space->vma_tree, vma_less); - } else if (unmap_end == vma->end) { - vma->end = unmap_start; - } else { - VmAreaStruct* right = new VmAreaStruct; - if (!right) { - return -ENOMEM; - } - right->start = unmap_end; - right->end = vma->end; - right->perm = vma->perm; - right->mm = space; - - vma->end = unmap_start; - rb_add(&right->rb, &space->vma_tree, vma_less); - break; - } - - node = next; - } - return 0; -} - -int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) -{ - if (!space) return -EINVAL; - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return -EINVAL; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - - uint64 end = va + len; - - LockGuard guard(space->lock); - if (!vma_range_covered_locked(space, va, end)) { - return -ENOMEM; - } - int ret = vma_split_range_locked(space, va, end); - if (ret < 0) { - return ret; - } - - bool tlb_flush_needed = false; - struct rb_node* node = rb_find(&va, &space->vma_tree, vma_cmp); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (vma->start >= end) { - break; - } - if (vma->end <= va) { - node = rb_next(node); - continue; - } - - vma->perm = (perm | VM_USER); - - for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { - uint64 pa; - if (!pmap::lookup(space->root_pa, addr, &pa)) { - continue; - } - - if (perm == 0) { - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - pmap::unmap(space->root_pa, addr); - tlb_flush_needed = true; - continue; - } - - uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); - if ((vma->perm & VM_WRITE) && pmap::is_cow(space->root_pa, addr)) { - pte_perm &= ~pmap::PTE_W; - pte_perm |= pmap::PTE_COW; - } - - int ret = pmap::protect(space->root_pa, addr, pte_perm); - if (ret < 0) { - return ret; - } - tlb_flush_needed = true; - } - - node = rb_next(node); - } - - if (tlb_flush_needed) { - tlb_shootdown_range(space, va, end); - } - return 0; -} - -uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align) -{ - if (!space) return 0; - length = page_align_up(length); - if (length == 0) return 0; - if (align < kPageSize) align = kPageSize; - - LockGuard guard(space->lock); - - /* Linux: arch_get_unmapped_area() starts from mm->mmap_base. - * RISC-V: TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3). - * Fall back to kUserVaBase if mmap_base not set (kernel VmSpaces). */ - uint64 base = space->mmap_base ? space->mmap_base : kUserVaBase; - uint64 cursor = align_up_any(base, align); - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (cursor <= vma->start) { - if (cursor > kUserVaLimit - length) { - return 0; - } - if (cursor + length <= vma->start) { - return cursor; - } - } - if (cursor < vma->end) { - cursor = vma->end; - } - cursor = align_up_any(cursor, align); - node = rb_next(node); - } - - if (cursor > kUserVaLimit - length) { - return 0; - } - return cursor; -} - -bool vma_range_covered(VmSpace* space, uint64 start, uint64 end) -{ - if (!space) return false; - start = page_align_down(start); - end = page_align_up(end); - if (end <= start) return true; - - LockGuard guard(space->lock); - return vma_range_covered_locked(space, start, end); -} - -/* ------------------------------------------------------------------ */ -/* Page fault handler (demand paging) */ -/* ------------------------------------------------------------------ */ - -static bool vma_allows(const VmAreaStruct* vma, VmFaultCause cause) -{ - switch (cause) { - case VmFaultCause::Read: - return (vma->perm & VM_READ) != 0; - case VmFaultCause::Write: - return (vma->perm & VM_WRITE) != 0; - case VmFaultCause::Exec: - return (vma->perm & VM_EXEC) != 0; - default: - return false; - } -} - -int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause) { - if (!space) return -EINVAL; - - uint64 aligned_addr = page_align_down(fault_addr); - - LockGuard guard(space->lock); - struct rb_node* node = rb_find(&fault_addr, &space->vma_tree, vma_cmp); - if (!node) { - return -EFAULT; // No VMA covers this address → segfault - } - - VmAreaStruct* vma = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); - - if (!vma_allows(vma, cause)) { - return -EFAULT; - } - - /* Check if page is already mapped */ - uint64 paddr; - if (pmap::lookup(space->root_pa, aligned_addr, &paddr)) { - if (cause == VmFaultCause::Write && pmap::is_cow(space->root_pa, aligned_addr)) { - /* Store Page Fault on a COW page */ - Page* page = pmm_alloc_page(); - if (!page) return -ENOMEM; - uint64 new_pa = page_to_pa(page).raw; - - /* Copy page contents */ - uint8* dst = reinterpret_cast(pa_to_va(phys_addr(new_pa)).raw); - const uint8* src = reinterpret_cast(pa_to_va(phys_addr(paddr)).raw); - for (uint64 i = 0; i < kPageSize; i++) { - dst[i] = src[i]; - } - - /* Remap with write permission, clearing COW */ - uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); - if (pmap::map(space->root_pa, aligned_addr, new_pa, pte_perm) != 0) { - pmm_free_page(page); - return -ENOMEM; - } - - /* New page gets mapcount = 1 (we just mapped it) */ - page_inc_mapcount(page); - - /* Decrement mapcount and refcount of old page */ - Page* old_page = pa_to_page(phys_addr(paddr)); - page_dec_mapcount(old_page); - page_decref(old_page); - - /* Flush TLB to remove old read-only cached translation */ - tlb_shootdown_page(space, aligned_addr); - return 0; - } - /* DEBUG: page already mapped but still faulting — dump PTE */ - { - uint64 cur_satp; - asm volatile("csrr %0, satp" : "=r"(cur_satp)); - printk(KERN_ERR "fault: va=%llx pa=%llx cause=%d pte=%llx satp=%llx root=%llx\n", - aligned_addr, paddr, static_cast(cause), - pmap::lookup_pte(space->root_pa, aligned_addr), - cur_satp, space->root_pa); - } - return 0; // Already mapped, maybe read fault on existing page - } - - /* Allocate physical page and zero it. - * Linux: do_anonymous_page() uses alloc_zeroed_user_highpage_movable(). - * Anonymous pages MUST be zeroed — userspace expects MAP_ANONYMOUS - * pages to contain all zeros (POSIX requirement). */ - Page* page = pmm_alloc_page(); - if (!page) return -ENOMEM; - - uint64 pa = page_to_pa(page).raw; - - /* Zero the page before mapping it to userspace */ - /* Zero the page before mapping it to userspace */ - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - for (uint64 i = 0; i < kPageSize; i++) kva[i] = 0; - - /* Translate MI permission flags to hardware PTE bits (MD layer) */ - uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); - - if (pmap::map(space->root_pa, aligned_addr, pa, pte_perm) != 0) { - pmm_free_page(page); - return -ENOMEM; - } - - /* Initial mapping: refcount is already 1 from pmm_alloc_page. - * Set mapcount = 1 to track this PTE mapping. */ - page_inc_mapcount(page); - - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Fork / Clone (COW infrastructure) */ -/* ------------------------------------------------------------------ */ - -VmSpace* vmm_clone(VmSpace* parent) { - if (!parent) return nullptr; - - VmSpace* child = VmSpace::create(); - if (!child) return nullptr; - - child->brk_start = parent->brk_start; - child->brk_end = parent->brk_end; - - LockGuard parent_guard(parent->lock); - - struct rb_node* node = rb_first(&parent->vma_tree); - while (node) { - VmAreaStruct* parent_vma = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(VmAreaStruct, rb)); - - VmAreaStruct* child_vma = new VmAreaStruct; - if (!child_vma) { - child->destroy(); - return nullptr; - } - child_vma->start = parent_vma->start; - child_vma->end = parent_vma->end; - child_vma->perm = parent_vma->perm; - child_vma->mm = child; - - /* Copy pages and mark COW if writable */ - for (uint64 va = child_vma->start; va < child_vma->end; va += kPageSize) { - uint64 pa; - if (pmap::lookup(parent->root_pa, va, &pa)) { - /* Make parent COW if it was writable */ - if (child_vma->perm & VM_WRITE) { - pmap::make_cow(parent->root_pa, va); - /* Flush TLB for parent since we reduced permissions */ - tlb_shootdown_page(parent, va); - } - - /* Map in child as COW or read-only */ - uint64 pte_perm = pmap::vm_perm_to_pte(child_vma->perm); - if (child_vma->perm & VM_WRITE) { - pte_perm &= ~pmap::PTE_W; - pte_perm |= pmap::PTE_COW; - } - - if (pmap::map(child->root_pa, va, pa, pte_perm) != 0) { - /* If map fails, we leak what we did in this loop for now, - * but child->destroy() will clean it up safely since - * we increment refcounts as we go. */ - delete child_vma; - child->destroy(); - return nullptr; - } - - /* Increment physical page refcount and mapcount */ - Page* shared_page = pa_to_page(phys_addr(pa)); - page_incref(shared_page); - page_inc_mapcount(shared_page); - } - } - - rb_add(&child_vma->rb, &child->vma_tree, vma_less); - node = rb_next(node); - } - - return child; -} diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc deleted file mode 100644 index 51617e3..0000000 --- a/na/mm/vmm.cc +++ /dev/null @@ -1,235 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vmm.cc — Virtual Memory Manager (Phase 1: Kernel Page Table) - * - * Builds a fine-grained kernel page table, activates it, and retires - * the early_pgdir identity mapping from entry.S. - * - * With memblock integration, the initialization order is: - * memblock_init → vmm_init (allocates page_map from memblock, - * maps vmemmap, activates page table) → pmm_init (buddy system - * starts with page_map already at vmemmap VA — no relocation). - * - * Reference: docs/specs/vmm.md - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Linker-exported section boundaries (virtual addresses) */ -extern "C" char _stext[], _etext[]; -extern "C" char _srodata[], _erodata[]; -extern "C" char _sdata[], _edata[]; -extern "C" char _sbss[], _ebss[]; -extern "C" char _kernel_end[]; - -/* Global kernel page table root PA — external linkage for task_exit */ -uint64 g_kernel_pgtbl = 0; - -namespace { - -/* VMM exports for PMM: page_map location after vmemmap mapping. */ -uint64 g_vmemmap_page_map_pa = 0; -uint64 g_vmemmap_page_map_size = 0; -uint64 g_vmemmap_num_pages = 0; - -/* Convert a linker symbol to PA (kernel is executing in higher-half VA). */ -inline uint64 sym_to_pa(const void *sym) { - return boot_va_to_pa(virt_addr(reinterpret_cast(sym))).raw; -} - -/* Map a PA range into the kernel page table via the direct map. */ -void map_range(uint64 root, uint64 pa_start, uint64 pa_end, - uint64 perm) { - for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { - uint64 va = pa_to_va(phys_addr(pa)).raw; - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map failed"); - } - } -} - -/* Map a PA range into the kernel page table at the fixed linked VA. */ -void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end, - uint64 perm) { - for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { - uint64 va = boot_pa_to_va(phys_addr(pa)).raw; - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map (linked) failed"); - } - } -} - -/* Map a PA range to an explicit VA base, using 2MB pages where possible. */ -void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start, - uint64 pa_end, uint64 perm) { - constexpr uint64 k2MB = 2ULL * 1024 * 1024; - uint64 pa = pa_start; - uint64 va = va_base; - - /* Map 2MB-aligned chunks with mega pages */ - while (pa + k2MB <= pa_end) { - if ((va & (k2MB - 1)) == 0 && (pa & (k2MB - 1)) == 0) { - int ret = pmap::map_2mb(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map_2mb failed"); - } - pa += k2MB; - va += k2MB; - } else { - break; - } - } - - /* Map remaining 4KB tail */ - while (pa < pa_end) { - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: vmemmap 4KB pmap::map failed"); - } - pa += kPageSize; - va += kPageSize; - } -} - -} // namespace - -uint64 vmm_get_page_map_pa() { return g_vmemmap_page_map_pa; } -uint64 vmm_get_page_map_size() { return g_vmemmap_page_map_size; } -uint64 vmm_get_num_pages() { return g_vmemmap_num_pages; } - -void vmm_init() { - /* 1. Allocate new root page table (from memblock) */ - uint64 root = pmap::create(); - if (!root) { - kernel_panic("vmm_init: failed to create root page table"); - } - - /* 2. Compute PA boundaries of each kernel section */ - uint64 text_start = page_align_down(sym_to_pa(_stext)); - uint64 text_end = page_align_up(sym_to_pa(_etext)); - uint64 ro_start = page_align_down(sym_to_pa(_srodata)); - uint64 ro_end = page_align_up(sym_to_pa(_erodata)); - uint64 data_start = page_align_down(sym_to_pa(_sdata)); - uint64 bss_end = page_align_up(sym_to_pa(_ebss)); - - /* Memory extent from memblock */ - uint64 mem_base = memblock_get_mem_base(); - uint64 mem_size = memblock_get_mem_size(); - uint64 mem_end = mem_base + mem_size; - - /* 3. Map kernel image at FIXED linked VA. - * - * Kernel text/data are linked at the bootstrap higher-half addresses - * (kDirectMapBaseDefault + PA). They must remain executable/readable at - * those addresses until full kernel-image relocation is implemented. - */ - map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X); - - /* 4. Map .rodata (R) at fixed linked VA. - * We map [_srodata, _sdata) so that the .lume.reloc section (which - * sits in its own padded output section between _erodata and _sdata) - * is also covered. Relocator only reads it during early boot, but - * mapping it keeps post-vmm accesses to __lume_reloc_* symbols sane. */ - map_range_linked(root, ro_start, page_align_down(sym_to_pa(_sdata)), - pmap::PTE_R); - - /* 5. Map .data + .bss (RW) at fixed linked VA */ - map_range_linked(root, data_start, bss_end, pmap::PTE_R | pmap::PTE_W); - - /* 6. Map usable physical RAM into the KASLR-slid direct map. - * - * The direct map provides pa_to_va() / va_to_pa() for the physical - * address space. We map from kPhysStart (the kernel's linked load - * address), skipping [mem_base, kPhysStart) because that region is - * PMP-protected firmware (OpenSBI) — S-mode cannot access it. - * - * Linux does the same on RISC-V: the linear map covers only the - * usable RAM range, not firmware-reserved regions. - * - * Uses 2MB huge pages for efficiency (64 entries for 128MB vs 32K). - */ - uint64 dm_start = kPhysStart; - if (dm_start < mem_base) { - dm_start = mem_base; - } - uint64 dm_va_base = pa_to_va(phys_addr(dm_start)).raw; - map_range_at_huge(root, dm_va_base, dm_start, mem_end, - pmap::PTE_R | pmap::PTE_W); - - /* 7. Allocate page_map from memblock (top of RAM) and map at VMEMMAP. - * - * Since memblock is active (PMM not yet initialized), pmap::map - * allocates page table pages from memblock too. page_map starts - * life at the vmemmap VA — no relocation ever needed. - */ - uint64 num_pages = mem_size / kPageSize; - uint64 page_map_bytes = num_pages * sizeof(Page); - uint64 page_map_span = page_align_up(page_map_bytes); - uint64 page_map_pa = memblock_alloc_top(page_map_span, kPageSize); - - if (!page_map_pa) { - kernel_panic("vmm_init: memblock failed to allocate page_map"); - } - - kprintf("[vmm] vmemmap: %llu pages, %llu KB at PA 0x%llx\n", num_pages, - page_map_span / 1024, page_map_pa); - - /* Map page_map at vmemmap_base from KaslrLayout (randomised by KASLR), using 2MB pages where possible */ - map_range_at_huge(root, arch::kaslr_layout_get().vmemmap_base, page_map_pa, - page_map_pa + page_map_span, pmap::PTE_R | pmap::PTE_W); - - /* Export for pmm_init() */ - g_vmemmap_page_map_pa = page_map_pa; - g_vmemmap_page_map_size = page_map_bytes; - g_vmemmap_num_pages = num_pages; - - /* 8. Pre-map console MMIO before page table switch */ - g_kernel_pgtbl = root; - console_init(); - - /* 9. Activate the new page table */ - pmap::activate(root); - - /* Console MMIO access must now use runtime (KASLR) direct-map base. */ - console_use_runtime_mapping(); - - kprintf("[vmm] vmm_init complete (vmemmap at 0x%llx)\n", arch::kaslr_layout_get().vmemmap_base); -} - -void vmm_init_ap() { - if (!g_kernel_pgtbl) { - kernel_panic("vmm_init_ap: kernel page table not ready"); - } - pmap::activate(g_kernel_pgtbl); -} - -void vmm_map_kernel_mmio(uint64 pa, uint64 size) { - if (!g_kernel_pgtbl) { - kernel_panic("vmm_map_kernel_mmio: kernel page table not ready"); - } - - uint64 aligned_pa = page_align_down(pa); - uint64 aligned_size = page_align_up(pa + size) - aligned_pa; - - map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size, - pmap::PTE_R | pmap::PTE_W | pmap::PTE_G); - tlb_shootdown_all(); -} diff --git a/na/scripts/Kbuild.include b/na/scripts/Kbuild.include deleted file mode 100644 index 76466bd..0000000 --- a/na/scripts/Kbuild.include +++ /dev/null @@ -1,110 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# scripts/Kbuild.include — shared Make helpers for LumeOS. -# -# Mirrors a small, useful subset of Linux's scripts/Kbuild.include. -# Provides: -# -# - Q / KBUILD_VERBOSE : `make V=1` shows full commands, -# default V=0 prints a one-line summary -# - $(call cmd,) : echo a quiet summary, then run cmd_ -# - $(call if_changed,) : currently a thin alias for $(cmd) — -# no .cmd-file hashing yet (Linux's full -# if_changed uses fixdep to detect both -# recipe-text and header changes; LumeOS -# relies on `-MMD -MP` for headers and -# on `make clean` for flag changes). -# - $(call kbuild-objs,...) : translate Kbuild-style `obj-y := foo.o` -# entries into SRCS_C / SRCS_CC. -# -# Naming convention adopted from Linux: each command has both a -# `quiet_cmd_` (single-line summary) and a `cmd_` (full -# command line). $(quiet) selects which prefix to display: -# V=0 -> quiet := quiet_ -> echoes " CC foo.c" -# V=1 -> quiet := -> echoes the full $(CC) $(CFLAGS) ... -# ---------------------------------------------------------------------- - -# --- Verbosity --------------------------------------------------------- -# Override at the command line: `make V=1`. Default V=0 = quiet. -KBUILD_VERBOSE ?= $(V) -KBUILD_VERBOSE ?= 0 - -ifeq ($(KBUILD_VERBOSE),1) - Q := - quiet := -else - Q := @ - quiet := quiet_ -endif - -# --- echo-cmd / cmd / if_changed --------------------------------------- -# -# echo-cmd prints the recipe's one-line summary (when quiet) or its full -# command line (when verbose). Implementation uses $($(quiet)cmd_) -# so the right form is picked up automatically. -# -# cmd is the workhorse: print summary, then execute the real command. -echo-cmd = $(if $($(quiet)cmd_$(1)),echo ' $($(quiet)cmd_$(1))';) - -cmd = @$(echo-cmd) $(cmd_$(1)) - -# if_changed currently degenerates to `cmd` — full Linux semantics -# require tracking the command-line via per-target ..cmd files -# and comparing on every rebuild, which needs fixdep. Until LumeOS -# grows that infrastructure, `make clean` after editing CFLAGS suffices. -if_changed = $(cmd) - -# --- Standard quiet_cmd_* / cmd_* templates --------------------------- -# -# Subdir Makefiles and the top-level Makefile can reference these by -# name via $(call cmd,). Add new pairs here as new recipe shapes -# show up (e.g., cmd_lz / cmd_gz / cmd_zstd for compressed Image targets). - - quiet_cmd_cc_o_c = CC $< - cmd_cc_o_c = $(CC) $(CFLAGS_LIBC) -c $< -o $@ - - quiet_cmd_cxx_o_cc = CXX $< - cmd_cxx_o_cc = $(CXX) $(CXXFLAGS) -c $< -o $@ - - quiet_cmd_as_o_S = AS $< - cmd_as_o_S = $(CC) $(ASFLAGS) -c $< -o $@ - - quiet_cmd_cc_s_c = CC -S $< - cmd_cc_s_c = $(CC) $(CFLAGS_LIBC) -S $< -o $@ - - quiet_cmd_objcopy_bin = OBJCOPY $@ - cmd_objcopy_bin = $(OBJCOPY) -O binary $< $@ - -# Objcopy with custom flags via per-target OBJCOPYFLAGS variable — -# used by the .pi.o post-processing rule (--prefix-symbols=__pi_ etc.). - quiet_cmd_objcopy = OBJCOPY $@ - cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $< $@ - - quiet_cmd_ld_kernel = LD $@ - cmd_ld_kernel = $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $^ - -# --- kbuild-objs: obj-y -> SRCS_C / SRCS_CC --------------------------- -# -# Usage from a subdir Makefile (e.g. arch/riscv/kernel/pi/Makefile): -# -# obj-dir := arch/riscv/kernel/pi -# obj-y := fdt_early.pi.o cmdline_early.pi.o archrandom_early.pi.o -# $(eval $(call kbuild-objs,$(obj-dir),$(obj-y:.pi.o=.o))) -# -# For each .o entry, looks up /.cc first, then -# /.c, and appends to SRCS_CC / SRCS_C respectively. -# Pure .pi.o entries should be passed through `:.pi.o=.o` first by the -# caller so the .pi suffix doesn't confuse the source-file lookup. - -define _kbuild-find-src -$(if $(wildcard $(1)/$(basename $(2)).cc),\ - $(eval SRCS_CC += $(1)/$(basename $(2)).cc),\ - $(if $(wildcard $(1)/$(basename $(2)).c),\ - $(eval SRCS_C += $(1)/$(basename $(2)).c),\ - $(error kbuild-objs: no $(1)/$(basename $(2)).{c,cc} for obj $(2)))) -endef - -define kbuild-objs -$(foreach _obj,$(2),$(call _kbuild-find-src,$(1),$(_obj))) -endef diff --git a/na/scripts/Kconfig.mk b/na/scripts/Kconfig.mk deleted file mode 100644 index 2bdc934..0000000 --- a/na/scripts/Kconfig.mk +++ /dev/null @@ -1,238 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# scripts/Kconfig.mk — Kernel Configuration Infrastructure -# -# This file is the heart of the LumeOS build-time configuration system. -# It mirrors the role of Linux's Kconfig + auto.conf pipeline: -# -# 1. Developer edits `.config` (or copies from arch/$(ARCH)/defconfig) -# 2. This file reads `.config`, applies defaults for anything missing, -# validates constraints, and exports two things: -# a) KCPPFLAGS — a set of -DCONFIG_xxx=value flags for the compiler -# b) $(BUILD)/generated/autoconf.h — the same values as C #defines -# -# Design Principles (matching Linux conventions): -# - Every CONFIG_ symbol has a default defined HERE, so a bare `make` -# with no .config still builds a sane kernel. -# - Boolean options use 'y' / 'n'. Numeric options are plain integers. -# - The generated autoconf.h uses the exact same names so that -# `#ifdef CONFIG_DEBUG` works identically in Makefile and C. -# ====================================================================== - -# ------------------------------------------------------------------ -# §1 Read user configuration (if it exists) -# ------------------------------------------------------------------ --include .config - -# ------------------------------------------------------------------ -# §2 Provide defaults for every CONFIG_ symbol -# -# ?= means "set only if not already set (by .config or CLI)". -# This is the single source of truth for default values. -# ------------------------------------------------------------------ - -# --- Platform ------------------------------------------------------- -CONFIG_ARCH ?= riscv - -# --- CPU / SMP ------------------------------------------------------ -CONFIG_NR_CPUS ?= 8 - -# --- Scheduler ------------------------------------------------------ -CONFIG_HZ ?= 100 -CONFIG_NR_PRIORITIES ?= 32 - -# --- Memory Management ---------------------------------------------- -CONFIG_PAGE_SIZE ?= 4096 -CONFIG_MAX_ORDER ?= 11 -CONFIG_KERNEL_STACK_SIZE ?= 8192 - -# --- Security / Hardening ------------------------------------------- -CONFIG_KASLR ?= y -CONFIG_KASLR_POLICY_ID ?= 0 -CONFIG_KASLR_LOG_LEVEL ?= 1 -CONFIG_STACK_CANARY ?= y -CONFIG_KERNEL_COMPRESSED ?= y - -# --- Debug ----------------------------------------------------------- -CONFIG_DEBUG ?= y -CONFIG_DEBUG_LOCKS ?= y -CONFIG_SELFTEST ?= y -CONFIG_KLOG ?= y - -# --- Initramfs ------------------------------------------------------ -# Source for the initramfs image. Accepted forms (auto-detected by -# scripts/gen_initramfs.sh): -# - a listing file (gen_init_cpio format) → built into a cpio -# - a directory → packed recursively -# - a *.cpio file → used as-is -# Override on the command line or pin in .config: -# make all CONFIG_INITRAMFS_SOURCE=/path/to/rootfs -CONFIG_INITRAMFS_SOURCE ?= usr/default_cpio_list -CONFIG_INITRAMFS_ROOT_UID ?= 0 -CONFIG_INITRAMFS_ROOT_GID ?= 0 - -# ------------------------------------------------------------------ -# §3 Derived constants (computed from CONFIG_ symbols) -# -# These follow Linux's pattern of expressing dependent values -# so that C code never has to do its own derivation at runtime. -# ------------------------------------------------------------------ - -# Default priority = middle of the range -CONFIG_DEFAULT_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) / 2 ))) - -# Idle priority = lowest = NR_PRIORITIES - 1 -CONFIG_IDLE_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) - 1 ))) - -# Default time slice in ticks: 100ms worth of ticks -# (same as Linux CFS's sysctl_sched_min_granularity default) -CONFIG_DEFAULT_TIMESLICE = $(shell echo $$(( $(CONFIG_HZ) / 10 ))) - -# ------------------------------------------------------------------ -# §4 Export to C/C++ via -D flags -# -# KCPPFLAGS is appended to CFLAGS/CXXFLAGS in the main Makefile. -# Every CONFIG_xxx= becomes -DCONFIG_xxx=. -# Boolean 'y' options become -DCONFIG_xxx=1. -# ------------------------------------------------------------------ -KCPPFLAGS := - -# Numeric options — pass through as-is -KCPPFLAGS += -DCONFIG_NR_CPUS=$(CONFIG_NR_CPUS) -KCPPFLAGS += -DCONFIG_HZ=$(CONFIG_HZ) -KCPPFLAGS += -DCONFIG_NR_PRIORITIES=$(CONFIG_NR_PRIORITIES) -KCPPFLAGS += -DCONFIG_DEFAULT_PRIORITY=$(CONFIG_DEFAULT_PRIORITY) -KCPPFLAGS += -DCONFIG_IDLE_PRIORITY=$(CONFIG_IDLE_PRIORITY) -KCPPFLAGS += -DCONFIG_DEFAULT_TIMESLICE=$(CONFIG_DEFAULT_TIMESLICE) -KCPPFLAGS += -DCONFIG_PAGE_SIZE=$(CONFIG_PAGE_SIZE) -KCPPFLAGS += -DCONFIG_MAX_ORDER=$(CONFIG_MAX_ORDER) -KCPPFLAGS += -DCONFIG_KERNEL_STACK_SIZE=$(CONFIG_KERNEL_STACK_SIZE) - -# Boolean options — translate 'y' → 1 -ifeq ($(CONFIG_KASLR),y) -KCPPFLAGS += -DCONFIG_KASLR=1 -endif -KCPPFLAGS += -DCONFIG_KASLR_POLICY_ID=$(CONFIG_KASLR_POLICY_ID) -KCPPFLAGS += -DCONFIG_KASLR_LOG_LEVEL=$(CONFIG_KASLR_LOG_LEVEL) -ifeq ($(CONFIG_STACK_CANARY),y) -KCPPFLAGS += -DCONFIG_STACK_CANARY=1 -endif -ifeq ($(CONFIG_DEBUG),y) -KCPPFLAGS += -DCONFIG_DEBUG=1 -endif -ifeq ($(CONFIG_DEBUG_LOCKS),y) -KCPPFLAGS += -DCONFIG_DEBUG_LOCKS=1 -endif -ifeq ($(CONFIG_SELFTEST),y) -KCPPFLAGS += -DCONFIG_SELFTEST=1 -endif -ifeq ($(CONFIG_KLOG),y) -KCPPFLAGS += -DCONFIG_KLOG=1 -endif -ifeq ($(CONFIG_KERNEL_COMPRESSED),y) -KCPPFLAGS += -DCONFIG_KERNEL_COMPRESSED=1 -endif - -# ------------------------------------------------------------------ -# §5 Generate autoconf.h (analogous to Linux's include/generated/autoconf.h) -# -# This header is auto-generated at build time so that C code can -# #include and see CONFIG_xxx macros without -# relying on -D flags alone. This is important for IDE integration. -# ------------------------------------------------------------------ -AUTOCONF_H := $(BUILD)/generated/autoconf.h - -define generate-autoconf - @mkdir -p $(dir $(AUTOCONF_H)) - @echo " GEN $(AUTOCONF_H)" - @echo "/* AUTO-GENERATED — DO NOT EDIT */" > $(AUTOCONF_H) - @echo "/* Generated by scripts/Kconfig.mk */" >> $(AUTOCONF_H) - @echo "#pragma once" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Platform */" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_CPUS $(CONFIG_NR_CPUS)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Scheduler */" >> $(AUTOCONF_H) - @echo "#define CONFIG_HZ $(CONFIG_HZ)" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_PRIORITIES $(CONFIG_NR_PRIORITIES)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_PRIORITY $(CONFIG_DEFAULT_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_IDLE_PRIORITY $(CONFIG_IDLE_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_TIMESLICE $(CONFIG_DEFAULT_TIMESLICE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Memory Management */" >> $(AUTOCONF_H) - @echo "#define CONFIG_PAGE_SIZE $(CONFIG_PAGE_SIZE)" >> $(AUTOCONF_H) - @echo "#define CONFIG_MAX_ORDER $(CONFIG_MAX_ORDER)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KERNEL_STACK_SIZE $(CONFIG_KERNEL_STACK_SIZE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Security */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_KASLR)), @echo "#define CONFIG_KASLR 1" >> $(AUTOCONF_H)) - @echo "#define CONFIG_KASLR_POLICY_ID $(CONFIG_KASLR_POLICY_ID)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KASLR_LOG_LEVEL $(CONFIG_KASLR_LOG_LEVEL)" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_STACK_CANARY)), @echo "#define CONFIG_STACK_CANARY 1" >> $(AUTOCONF_H)) - @echo "" >> $(AUTOCONF_H) - @echo "/* Debug */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_DEBUG)), @echo "#define CONFIG_DEBUG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_DEBUG_LOCKS)), @echo "#define CONFIG_DEBUG_LOCKS 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_SELFTEST)), @echo "#define CONFIG_SELFTEST 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KLOG)), @echo "#define CONFIG_KLOG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KERNEL_COMPRESSED)), @echo "#define CONFIG_KERNEL_COMPRESSED 1" >> $(AUTOCONF_H)) -endef - -# ------------------------------------------------------------------ -# §6 Utility targets -# ------------------------------------------------------------------ - -# 'make defconfig' — reset .config to the arch default -.PHONY: defconfig savedefconfig showconfig - -defconfig: - @echo " DEFCFG arch/$(CONFIG_ARCH)/defconfig → .config" - @cp arch/$(CONFIG_ARCH)/defconfig .config - -# 'make savedefconfig' — save current effective config -savedefconfig: - @echo " SAVE .config" - @echo "# LumeOS Kernel Configuration (saved $$(date +%Y-%m-%d))" > .config - @echo "CONFIG_ARCH = $(CONFIG_ARCH)" >> .config - @echo "CONFIG_NR_CPUS = $(CONFIG_NR_CPUS)" >> .config - @echo "CONFIG_HZ = $(CONFIG_HZ)" >> .config - @echo "CONFIG_NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" >> .config - @echo "CONFIG_PAGE_SIZE = $(CONFIG_PAGE_SIZE)" >> .config - @echo "CONFIG_MAX_ORDER = $(CONFIG_MAX_ORDER)" >> .config - @echo "CONFIG_KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" >> .config - @echo "CONFIG_KASLR = $(CONFIG_KASLR)" >> .config - @echo "CONFIG_KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" >> .config - @echo "CONFIG_KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" >> .config - @echo "CONFIG_STACK_CANARY = $(CONFIG_STACK_CANARY)" >> .config - @echo "CONFIG_DEBUG = $(CONFIG_DEBUG)" >> .config - @echo "CONFIG_DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" >> .config - @echo "CONFIG_SELFTEST = $(CONFIG_SELFTEST)" >> .config - @echo "CONFIG_KLOG = $(CONFIG_KLOG)" >> .config - @echo "CONFIG_KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" >> .config - -# 'make showconfig' — print effective configuration -showconfig: - @echo "========================================" - @echo " LumeOS Effective Configuration" - @echo "========================================" - @echo " ARCH = $(CONFIG_ARCH)" - @echo " NR_CPUS = $(CONFIG_NR_CPUS)" - @echo " HZ = $(CONFIG_HZ)" - @echo " NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" - @echo " DEFAULT_PRIORITY = $(CONFIG_DEFAULT_PRIORITY)" - @echo " IDLE_PRIORITY = $(CONFIG_IDLE_PRIORITY)" - @echo " DEFAULT_TIMESLICE = $(CONFIG_DEFAULT_TIMESLICE)" - @echo " PAGE_SIZE = $(CONFIG_PAGE_SIZE)" - @echo " MAX_ORDER = $(CONFIG_MAX_ORDER)" - @echo " KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" - @echo " KASLR = $(CONFIG_KASLR)" - @echo " KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" - @echo " KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" - @echo " STACK_CANARY = $(CONFIG_STACK_CANARY)" - @echo " DEBUG = $(CONFIG_DEBUG)" - @echo " DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" - @echo " SELFTEST = $(CONFIG_SELFTEST)" - @echo " KLOG = $(CONFIG_KLOG)" - @echo " KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" - @echo "========================================" diff --git a/na/scripts/dtc/libfdt/fdt.c b/na/scripts/dtc/libfdt/fdt.c deleted file mode 100644 index 56d4dcb..0000000 --- a/na/scripts/dtc/libfdt/fdt.c +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -/* - * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks - * that the given buffer contains what appears to be a flattened - * device tree with sane information in its header. - */ -int32_t fdt_ro_probe_(const void *fdt) -{ - uint32_t totalsize = fdt_totalsize(fdt); - - if (can_assume(VALID_DTB)) - return totalsize; - - /* The device tree must be at an 8-byte aligned address */ - if ((uintptr_t)fdt & 7) - return -FDT_ERR_ALIGNMENT; - - if (fdt_magic(fdt) == FDT_MAGIC) { - /* Complete tree */ - if (!can_assume(LATEST)) { - if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) - return -FDT_ERR_BADVERSION; - if (fdt_last_comp_version(fdt) > - FDT_LAST_SUPPORTED_VERSION) - return -FDT_ERR_BADVERSION; - } - } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { - /* Unfinished sequential-write blob */ - if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0) - return -FDT_ERR_BADSTATE; - } else { - return -FDT_ERR_BADMAGIC; - } - - if (totalsize < INT32_MAX) - return totalsize; - else - return -FDT_ERR_TRUNCATED; -} - -static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) -{ - return (off >= hdrsize) && (off <= totalsize); -} - -static int check_block_(uint32_t hdrsize, uint32_t totalsize, - uint32_t base, uint32_t size) -{ - if (!check_off_(hdrsize, totalsize, base)) - return 0; /* block start out of bounds */ - if ((base + size) < base) - return 0; /* overflow */ - if (!check_off_(hdrsize, totalsize, base + size)) - return 0; /* block end out of bounds */ - return 1; -} - -size_t fdt_header_size_(uint32_t version) -{ - if (version <= 1) - return FDT_V1_SIZE; - else if (version <= 2) - return FDT_V2_SIZE; - else if (version <= 3) - return FDT_V3_SIZE; - else if (version <= 16) - return FDT_V16_SIZE; - else - return FDT_V17_SIZE; -} - -size_t fdt_header_size(const void *fdt) -{ - return can_assume(LATEST) ? FDT_V17_SIZE : - fdt_header_size_(fdt_version(fdt)); -} - -int fdt_check_header(const void *fdt) -{ - size_t hdrsize; - - /* The device tree must be at an 8-byte aligned address */ - if ((uintptr_t)fdt & 7) - return -FDT_ERR_ALIGNMENT; - - if (fdt_magic(fdt) != FDT_MAGIC) - return -FDT_ERR_BADMAGIC; - if (!can_assume(LATEST)) { - if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) - || (fdt_last_comp_version(fdt) > - FDT_LAST_SUPPORTED_VERSION)) - return -FDT_ERR_BADVERSION; - if (fdt_version(fdt) < fdt_last_comp_version(fdt)) - return -FDT_ERR_BADVERSION; - } - hdrsize = fdt_header_size(fdt); - if (!can_assume(VALID_DTB)) { - if ((fdt_totalsize(fdt) < hdrsize) - || (fdt_totalsize(fdt) > INT_MAX)) - return -FDT_ERR_TRUNCATED; - - /* memrsv block must be 8 byte aligned */ - if (fdt_off_mem_rsvmap(fdt) % sizeof(uint64_t)) - return -FDT_ERR_ALIGNMENT; - - /* Structure block must be 4 byte aligned */ - if (fdt_off_dt_struct(fdt) % FDT_TAGSIZE) - return -FDT_ERR_ALIGNMENT; - - /* Bounds check memrsv block */ - if (!check_off_(hdrsize, fdt_totalsize(fdt), - fdt_off_mem_rsvmap(fdt))) - return -FDT_ERR_TRUNCATED; - - /* Bounds check structure block */ - if (!can_assume(LATEST) && fdt_version(fdt) < 17) { - if (!check_off_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_struct(fdt))) - return -FDT_ERR_TRUNCATED; - } else { - if (!check_block_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_struct(fdt), - fdt_size_dt_struct(fdt))) - return -FDT_ERR_TRUNCATED; - } - - /* Bounds check strings block */ - if (!check_block_(hdrsize, fdt_totalsize(fdt), - fdt_off_dt_strings(fdt), - fdt_size_dt_strings(fdt))) - return -FDT_ERR_TRUNCATED; - } - - return 0; -} - -const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) -{ - unsigned int uoffset = offset; - unsigned int absoffset = offset + fdt_off_dt_struct(fdt); - - if (offset < 0) - return NULL; - - if (!can_assume(VALID_INPUT)) - if ((absoffset < uoffset) - || ((absoffset + len) < absoffset) - || (absoffset + len) > fdt_totalsize(fdt)) - return NULL; - - if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) - if (((uoffset + len) < uoffset) - || ((offset + len) > fdt_size_dt_struct(fdt))) - return NULL; - - return fdt_offset_ptr_(fdt, offset); -} - -uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) -{ - const fdt32_t *tagp, *lenp; - uint32_t tag, len, sum; - int offset = startoffset; - const char *p; - - *nextoffset = -FDT_ERR_TRUNCATED; - tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); - if (!can_assume(VALID_DTB) && !tagp) - return FDT_END; /* premature end */ - tag = fdt32_to_cpu(*tagp); - offset += FDT_TAGSIZE; - - *nextoffset = -FDT_ERR_BADSTRUCTURE; - switch (tag) { - case FDT_BEGIN_NODE: - /* skip name */ - do { - p = fdt_offset_ptr(fdt, offset++, 1); - } while (p && (*p != '\0')); - if (!can_assume(VALID_DTB) && !p) - return FDT_END; /* premature end */ - break; - - case FDT_PROP: - lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); - if (!can_assume(VALID_DTB) && !lenp) - return FDT_END; /* premature end */ - - len = fdt32_to_cpu(*lenp); - sum = len + offset; - if (!can_assume(VALID_DTB) && - (INT_MAX <= sum || sum < (uint32_t) offset)) - return FDT_END; /* premature end */ - - /* skip-name offset, length and value */ - offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len; - - if (!can_assume(LATEST) && - fdt_version(fdt) < 0x10 && len >= 8 && - ((offset - len) % 8) != 0) - offset += 4; - break; - - case FDT_END: - case FDT_END_NODE: - case FDT_NOP: - break; - - default: - return FDT_END; - } - - if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) - return FDT_END; /* premature end */ - - *nextoffset = FDT_TAGALIGN(offset); - return tag; -} - -int fdt_check_node_offset_(const void *fdt, int offset) -{ - if (!can_assume(VALID_INPUT) - && ((offset < 0) || (offset % FDT_TAGSIZE))) - return -FDT_ERR_BADOFFSET; - - if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) - return -FDT_ERR_BADOFFSET; - - return offset; -} - -int fdt_check_prop_offset_(const void *fdt, int offset) -{ - if (!can_assume(VALID_INPUT) - && ((offset < 0) || (offset % FDT_TAGSIZE))) - return -FDT_ERR_BADOFFSET; - - if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) - return -FDT_ERR_BADOFFSET; - - return offset; -} - -int fdt_next_node(const void *fdt, int offset, int *depth) -{ - int nextoffset = 0; - uint32_t tag; - - if (offset >= 0) - if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) - return nextoffset; - - do { - offset = nextoffset; - tag = fdt_next_tag(fdt, offset, &nextoffset); - - switch (tag) { - case FDT_PROP: - case FDT_NOP: - break; - - case FDT_BEGIN_NODE: - if (depth) - (*depth)++; - break; - - case FDT_END_NODE: - if (depth && ((--(*depth)) < 0)) - return nextoffset; - break; - - case FDT_END: - if ((nextoffset >= 0) - || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) - return -FDT_ERR_NOTFOUND; - else - return nextoffset; - } - } while (tag != FDT_BEGIN_NODE); - - return offset; -} - -int fdt_first_subnode(const void *fdt, int offset) -{ - int depth = 0; - - offset = fdt_next_node(fdt, offset, &depth); - if (offset < 0 || depth != 1) - return -FDT_ERR_NOTFOUND; - - return offset; -} - -int fdt_next_subnode(const void *fdt, int offset) -{ - int depth = 1; - - /* - * With respect to the parent, the depth of the next subnode will be - * the same as the last. - */ - do { - offset = fdt_next_node(fdt, offset, &depth); - if (offset < 0 || depth < 1) - return -FDT_ERR_NOTFOUND; - } while (depth > 1); - - return offset; -} - -const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, - int slen) -{ - const char *last = strtab + tabsize - (slen + 1); - const char *p; - - for (p = strtab; p <= last; p++) - if (memcmp(p, s, slen) == 0 && p[slen] == '\0') - return p; - return NULL; -} - -int fdt_move(const void *fdt, void *buf, int bufsize) -{ - if (!can_assume(VALID_INPUT) && bufsize < 0) - return -FDT_ERR_NOSPACE; - - FDT_RO_PROBE(fdt); - - if (fdt_totalsize(fdt) > (unsigned int)bufsize) - return -FDT_ERR_NOSPACE; - - memmove(buf, fdt, fdt_totalsize(fdt)); - return 0; -} diff --git a/na/scripts/dtc/libfdt/fdt.h b/na/scripts/dtc/libfdt/fdt.h deleted file mode 100644 index a07abfc..0000000 --- a/na/scripts/dtc/libfdt/fdt.h +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef FDT_H -#define FDT_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * Copyright 2012 Kim Phillips, Freescale Semiconductor. - */ - -#ifndef __ASSEMBLER__ - -struct fdt_header { - fdt32_t magic; /* magic word FDT_MAGIC */ - fdt32_t totalsize; /* total size of DT block */ - fdt32_t off_dt_struct; /* offset to structure */ - fdt32_t off_dt_strings; /* offset to strings */ - fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ - fdt32_t version; /* format version */ - fdt32_t last_comp_version; /* last compatible version */ - - /* version 2 fields below */ - fdt32_t boot_cpuid_phys; /* Which physical CPU id we're - booting on */ - /* version 3 fields below */ - fdt32_t size_dt_strings; /* size of the strings block */ - - /* version 17 fields below */ - fdt32_t size_dt_struct; /* size of the structure block */ -}; - -struct fdt_reserve_entry { - fdt64_t address; - fdt64_t size; -}; - -struct fdt_node_header { - fdt32_t tag; - char name[]; -}; - -struct fdt_property { - fdt32_t tag; - fdt32_t len; - fdt32_t nameoff; - char data[]; -}; - -#endif /* !__ASSEMBLER__ */ - -#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ -#define FDT_TAGSIZE sizeof(fdt32_t) - -#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ -#define FDT_END_NODE 0x2 /* End node */ -#define FDT_PROP 0x3 /* Property: name off, - size, content */ -#define FDT_NOP 0x4 /* nop */ -#define FDT_END 0x9 - -#define FDT_V1_SIZE (7*sizeof(fdt32_t)) -#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) -#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) -#define FDT_V16_SIZE FDT_V3_SIZE -#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) - -#endif /* FDT_H */ diff --git a/na/scripts/dtc/libfdt/fdt_addresses.c b/na/scripts/dtc/libfdt/fdt_addresses.c deleted file mode 100644 index c40ba09..0000000 --- a/na/scripts/dtc/libfdt/fdt_addresses.c +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2014 David Gibson - * Copyright (C) 2018 embedded brains GmbH - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -static int fdt_cells(const void *fdt, int nodeoffset, const char *name) -{ - const fdt32_t *c; - uint32_t val; - int len; - - c = fdt_getprop(fdt, nodeoffset, name, &len); - if (!c) - return len; - - if (len != sizeof(*c)) - return -FDT_ERR_BADNCELLS; - - val = fdt32_to_cpu(*c); - if (val > FDT_MAX_NCELLS) - return -FDT_ERR_BADNCELLS; - - return (int)val; -} - -int fdt_address_cells(const void *fdt, int nodeoffset) -{ - int val; - - val = fdt_cells(fdt, nodeoffset, "#address-cells"); - if (val == 0) - return -FDT_ERR_BADNCELLS; - if (val == -FDT_ERR_NOTFOUND) - return 2; - return val; -} - -int fdt_size_cells(const void *fdt, int nodeoffset) -{ - int val; - - val = fdt_cells(fdt, nodeoffset, "#size-cells"); - if (val == -FDT_ERR_NOTFOUND) - return 1; - return val; -} - -/* This function assumes that [address|size]_cells is 1 or 2 */ -int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, - const char *name, uint64_t addr, uint64_t size) -{ - int addr_cells, size_cells, ret; - uint8_t data[sizeof(fdt64_t) * 2], *prop; - - ret = fdt_address_cells(fdt, parent); - if (ret < 0) - return ret; - addr_cells = ret; - - ret = fdt_size_cells(fdt, parent); - if (ret < 0) - return ret; - size_cells = ret; - - /* check validity of address */ - prop = data; - if (addr_cells == 1) { - if ((addr > UINT32_MAX) || (((uint64_t) UINT32_MAX + 1 - addr) < size)) - return -FDT_ERR_BADVALUE; - - fdt32_st(prop, (uint32_t)addr); - } else if (addr_cells == 2) { - fdt64_st(prop, addr); - } else { - return -FDT_ERR_BADNCELLS; - } - - /* check validity of size */ - prop += addr_cells * sizeof(fdt32_t); - if (size_cells == 1) { - if (size > UINT32_MAX) - return -FDT_ERR_BADVALUE; - - fdt32_st(prop, (uint32_t)size); - } else if (size_cells == 2) { - fdt64_st(prop, size); - } else { - return -FDT_ERR_BADNCELLS; - } - - return fdt_appendprop(fdt, nodeoffset, name, data, - (addr_cells + size_cells) * sizeof(fdt32_t)); -} diff --git a/na/scripts/dtc/libfdt/fdt_empty_tree.c b/na/scripts/dtc/libfdt/fdt_empty_tree.c deleted file mode 100644 index 49d54d4..0000000 --- a/na/scripts/dtc/libfdt/fdt_empty_tree.c +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2012 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -int fdt_create_empty_tree(void *buf, int bufsize) -{ - int err; - - err = fdt_create(buf, bufsize); - if (err) - return err; - - err = fdt_finish_reservemap(buf); - if (err) - return err; - - err = fdt_begin_node(buf, ""); - if (err) - return err; - - err = fdt_end_node(buf); - if (err) - return err; - - err = fdt_finish(buf); - if (err) - return err; - - return fdt_open_into(buf, buf, bufsize); -} diff --git a/na/scripts/dtc/libfdt/fdt_overlay.c b/na/scripts/dtc/libfdt/fdt_overlay.c deleted file mode 100644 index 51a3859..0000000 --- a/na/scripts/dtc/libfdt/fdt_overlay.c +++ /dev/null @@ -1,1101 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2016 Free Electrons - * Copyright (C) 2016 NextThing Co. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -/** - * overlay_get_target_phandle - retrieves the target phandle of a fragment - * @fdto: pointer to the device tree overlay blob - * @fragment: node offset of the fragment in the overlay - * - * overlay_get_target_phandle() retrieves the target phandle of an - * overlay fragment when that fragment uses a phandle (target - * property) instead of a path (target-path property). - * - * returns: - * the phandle pointed by the target property - * 0, if the phandle was not found - * -1, if the phandle was malformed - */ -static uint32_t overlay_get_target_phandle(const void *fdto, int fragment) -{ - const fdt32_t *val; - int len; - - val = fdt_getprop(fdto, fragment, "target", &len); - if (!val) - return 0; - - if ((len != sizeof(*val)) || (fdt32_to_cpu(*val) == (uint32_t)-1)) - return (uint32_t)-1; - - return fdt32_to_cpu(*val); -} - -int fdt_overlay_target_offset(const void *fdt, const void *fdto, - int fragment_offset, char const **pathp) -{ - uint32_t phandle; - const char *path = NULL; - int path_len = 0, ret; - - /* Try first to do a phandle based lookup */ - phandle = overlay_get_target_phandle(fdto, fragment_offset); - if (phandle == (uint32_t)-1) - return -FDT_ERR_BADPHANDLE; - - /* no phandle, try path */ - if (!phandle) { - /* And then a path based lookup */ - path = fdt_getprop(fdto, fragment_offset, "target-path", &path_len); - if (path) - ret = fdt_path_offset(fdt, path); - else - ret = path_len; - } else - ret = fdt_node_offset_by_phandle(fdt, phandle); - - /* - * If we haven't found either a target or a - * target-path property in a node that contains a - * __overlay__ subnode (we wouldn't be called - * otherwise), consider it a improperly written - * overlay - */ - if (ret < 0 && path_len == -FDT_ERR_NOTFOUND) - ret = -FDT_ERR_BADOVERLAY; - - /* return on error */ - if (ret < 0) - return ret; - - /* return pointer to path (if available) */ - if (pathp) - *pathp = path ? path : NULL; - - return ret; -} - -/** - * overlay_phandle_add_offset - Increases a phandle by an offset - * @fdt: Base device tree blob - * @node: Device tree overlay blob - * @name: Name of the property to modify (phandle or linux,phandle) - * @delta: offset to apply - * - * overlay_phandle_add_offset() increments a node phandle by a given - * offset. - * - * returns: - * 0 on success. - * Negative error code on error - */ -static int overlay_phandle_add_offset(void *fdt, int node, - const char *name, uint32_t delta) -{ - fdt32_t *valp, val; - int len; - - valp = fdt_getprop_w(fdt, node, name, &len); - if (!valp) - return len; - - if (len != sizeof(val)) - return -FDT_ERR_BADPHANDLE; - - val = fdt32_ld(valp); - if (val + delta < val || val + delta == (uint32_t)-1) - return -FDT_ERR_NOPHANDLES; - - fdt32_st(valp, val + delta); - return 0; -} - -/** - * overlay_adjust_node_phandles - Offsets the phandles of a node - * @fdto: Device tree overlay blob - * @node: Offset of the node we want to adjust - * @delta: Offset to shift the phandles of - * - * overlay_adjust_node_phandles() adds a constant to all the phandles - * of a given node. This is mainly use as part of the overlay - * application process, when we want to update all the overlay - * phandles to not conflict with the overlays of the base device tree. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_adjust_node_phandles(void *fdto, int node, - uint32_t delta) -{ - int child; - int ret; - - ret = overlay_phandle_add_offset(fdto, node, "phandle", delta); - if (ret && ret != -FDT_ERR_NOTFOUND) - return ret; - - ret = overlay_phandle_add_offset(fdto, node, "linux,phandle", delta); - if (ret && ret != -FDT_ERR_NOTFOUND) - return ret; - - fdt_for_each_subnode(child, fdto, node) { - ret = overlay_adjust_node_phandles(fdto, child, delta); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_adjust_local_phandles - Adjust the phandles of a whole overlay - * @fdto: Device tree overlay blob - * @delta: Offset to shift the phandles of - * - * overlay_adjust_local_phandles() adds a constant to all the - * phandles of an overlay. This is mainly use as part of the overlay - * application process, when we want to update all the overlay - * phandles to not conflict with the overlays of the base device tree. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_adjust_local_phandles(void *fdto, uint32_t delta) -{ - /* - * Start adjusting the phandles from the overlay root - */ - return overlay_adjust_node_phandles(fdto, 0, delta); -} - -/** - * overlay_update_local_node_references - Adjust the overlay references - * @fdto: Device tree overlay blob - * @tree_node: Node offset of the node to operate on - * @fixup_node: Node offset of the matching local fixups node - * @delta: Offset to shift the phandles of - * - * overlay_update_local_nodes_references() update the phandles - * pointing to a node within the device tree overlay by adding a - * constant delta. - * - * This is mainly used as part of a device tree application process, - * where you want the device tree overlays phandles to not conflict - * with the ones from the base device tree before merging them. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_update_local_node_references(void *fdto, - int tree_node, - int fixup_node, - uint32_t delta) -{ - int fixup_prop; - int fixup_child; - int ret; - - fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { - const fdt32_t *fixup_val; - const char *name; - char *tree_val; - int fixup_len; - int tree_len; - int i; - - fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, - &name, &fixup_len); - if (!fixup_val) - return fixup_len; - - if (fixup_len % sizeof(uint32_t)) - return -FDT_ERR_BADOVERLAY; - fixup_len /= sizeof(uint32_t); - - tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); - if (!tree_val) { - if (tree_len == -FDT_ERR_NOTFOUND) - return -FDT_ERR_BADOVERLAY; - - return tree_len; - } - - for (i = 0; i < fixup_len; i++) { - fdt32_t *refp; - - refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); - - /* - * phandles to fixup can be unaligned, so use - * fdt32_{ld,st}() to read/write them. - */ - fdt32_st(refp, fdt32_ld(refp) + delta); - } - } - - fdt_for_each_subnode(fixup_child, fdto, fixup_node) { - const char *fixup_child_name = fdt_get_name(fdto, fixup_child, - NULL); - int tree_child; - - tree_child = fdt_subnode_offset(fdto, tree_node, - fixup_child_name); - if (tree_child == -FDT_ERR_NOTFOUND) - return -FDT_ERR_BADOVERLAY; - if (tree_child < 0) - return tree_child; - - ret = overlay_update_local_node_references(fdto, - tree_child, - fixup_child, - delta); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_update_local_references - Adjust the overlay references - * @fdto: Device tree overlay blob - * @delta: Offset to shift the phandles of - * - * overlay_update_local_references() update all the phandles pointing - * to a node within the device tree overlay by adding a constant - * delta to not conflict with the base overlay. - * - * This is mainly used as part of a device tree application process, - * where you want the device tree overlays phandles to not conflict - * with the ones from the base device tree before merging them. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_update_local_references(void *fdto, uint32_t delta) -{ - int fixups; - - fixups = fdt_path_offset(fdto, "/__local_fixups__"); - if (fixups < 0) { - /* There's no local phandles to adjust, bail out */ - if (fixups == -FDT_ERR_NOTFOUND) - return 0; - - return fixups; - } - - /* - * Update our local references from the root of the tree - */ - return overlay_update_local_node_references(fdto, 0, fixups, - delta); -} - -/** - * overlay_fixup_one_phandle - Set an overlay phandle to the base one - * @fdto: Device tree overlay blob - * @symbols_off: Node offset of the symbols node in the base device tree - * @path: Path to a node holding a phandle in the overlay - * @path_len: number of path characters to consider - * @name: Name of the property holding the phandle reference in the overlay - * @name_len: number of name characters to consider - * @poffset: Offset within the overlay property where the phandle is stored - * @phandle: Phandle referencing the node - * - * overlay_fixup_one_phandle() resolves an overlay phandle pointing to - * a node in the base device tree. - * - * This is part of the device tree overlay application process, when - * you want all the phandles in the overlay to point to the actual - * base dt nodes. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_fixup_one_phandle(void *fdto, int symbols_off, - const char *path, uint32_t path_len, - const char *name, uint32_t name_len, - int poffset, uint32_t phandle) -{ - fdt32_t phandle_prop; - int fixup_off; - - if (symbols_off < 0) - return symbols_off; - - fixup_off = fdt_path_offset_namelen(fdto, path, path_len); - if (fixup_off == -FDT_ERR_NOTFOUND) - return -FDT_ERR_BADOVERLAY; - if (fixup_off < 0) - return fixup_off; - - phandle_prop = cpu_to_fdt32(phandle); - return fdt_setprop_inplace_namelen_partial(fdto, fixup_off, - name, name_len, poffset, - &phandle_prop, - sizeof(phandle_prop)); -} - -/** - * overlay_fixup_phandle - Set an overlay phandle to the base one - * @fdt: Base Device Tree blob - * @fdto: Device tree overlay blob - * @symbols_off: Node offset of the symbols node in the base device tree - * @property: Property offset in the overlay holding the list of fixups - * - * overlay_fixup_phandle() resolves all the overlay phandles pointed - * to in a __fixups__ property, and updates them to match the phandles - * in use in the base device tree. - * - * This is part of the device tree overlay application process, when - * you want all the phandles in the overlay to point to the actual - * base dt nodes. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, - int property) -{ - const char *value; - const char *label; - int len; - const char *symbol_path; - int prop_len; - int symbol_off; - uint32_t phandle; - - value = fdt_getprop_by_offset(fdto, property, - &label, &len); - if (!value) { - if (len == -FDT_ERR_NOTFOUND) - return -FDT_ERR_INTERNAL; - - return len; - } - - symbol_path = fdt_getprop(fdt, symbols_off, label, &prop_len); - if (!symbol_path) - return prop_len; - - symbol_off = fdt_path_offset(fdt, symbol_path); - if (symbol_off < 0) - return symbol_off; - - phandle = fdt_get_phandle(fdt, symbol_off); - if (!phandle) - return -FDT_ERR_NOTFOUND; - - do { - const char *path, *name, *fixup_end; - const char *fixup_str = value; - uint32_t path_len, name_len; - uint32_t fixup_len; - const char *sep; - char *endptr; - int poffset, ret; - - fixup_end = memchr(value, '\0', len); - if (!fixup_end) - return -FDT_ERR_BADOVERLAY; - fixup_len = fixup_end - fixup_str; - - len -= fixup_len + 1; - value += fixup_len + 1; - - path = fixup_str; - sep = memchr(fixup_str, ':', fixup_len); - if (!sep || *sep != ':') - return -FDT_ERR_BADOVERLAY; - - path_len = sep - path; - if (path_len == (fixup_len - 1)) - return -FDT_ERR_BADOVERLAY; - - fixup_len -= path_len + 1; - name = sep + 1; - sep = memchr(name, ':', fixup_len); - if (!sep || *sep != ':') - return -FDT_ERR_BADOVERLAY; - - name_len = sep - name; - if (!name_len) - return -FDT_ERR_BADOVERLAY; - - poffset = strtoul(sep + 1, &endptr, 10); - if ((*endptr != '\0') || (endptr <= (sep + 1))) - return -FDT_ERR_BADOVERLAY; - - ret = overlay_fixup_one_phandle(fdto, symbols_off, - path, path_len, name, name_len, - poffset, phandle); - if (ret) - return ret; - } while (len > 0); - - return 0; -} - -/** - * overlay_fixup_phandles - Resolve the overlay phandles to the base - * device tree - * @fdt: Base Device Tree blob - * @fdto: Device tree overlay blob - * - * overlay_fixup_phandles() resolves all the overlay phandles pointing - * to nodes in the base device tree. - * - * This is one of the steps of the device tree overlay application - * process, when you want all the phandles in the overlay to point to - * the actual base dt nodes. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_fixup_phandles(void *fdt, void *fdto) -{ - int fixups_off, symbols_off; - int property; - - /* We can have overlays without any fixups */ - fixups_off = fdt_path_offset(fdto, "/__fixups__"); - if (fixups_off == -FDT_ERR_NOTFOUND) - return 0; /* nothing to do */ - if (fixups_off < 0) - return fixups_off; - - /* And base DTs without symbols */ - symbols_off = fdt_path_offset(fdt, "/__symbols__"); - if ((symbols_off < 0 && (symbols_off != -FDT_ERR_NOTFOUND))) - return symbols_off; - - fdt_for_each_property_offset(property, fdto, fixups_off) { - int ret; - - ret = overlay_fixup_phandle(fdt, fdto, symbols_off, property); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_adjust_local_conflicting_phandle: Changes a phandle value - * @fdto: Device tree overlay - * @node: The node the phandle is set for - * @fdt_phandle: The new value for the phandle - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_adjust_local_conflicting_phandle(void *fdto, int node, - uint32_t fdt_phandle) -{ - const fdt32_t *php; - int len, ret; - - php = fdt_getprop(fdto, node, "phandle", &len); - if (php && len == sizeof(*php)) { - ret = fdt_setprop_inplace_u32(fdto, node, "phandle", fdt_phandle); - if (ret) - return ret; - } - - php = fdt_getprop(fdto, node, "linux,phandle", &len); - if (php && len == sizeof(*php)) { - ret = fdt_setprop_inplace_u32(fdto, node, "linux,phandle", fdt_phandle); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_update_node_conflicting_references - Recursively replace phandle values - * @fdto: Device tree overlay blob - * @tree_node: Node to recurse into - * @fixup_node: Node offset of the matching local fixups node - * @fdt_phandle: Value to replace phandles with - * @fdto_phandle: Value to be replaced - * - * Replaces all phandles with value @fdto_phandle by @fdt_phandle. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_update_node_conflicting_references(void *fdto, int tree_node, - int fixup_node, - uint32_t fdt_phandle, - uint32_t fdto_phandle) -{ - int fixup_prop; - int fixup_child; - int ret; - - fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { - const fdt32_t *fixup_val; - const char *name; - char *tree_val; - int fixup_len; - int tree_len; - int i; - - fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, - &name, &fixup_len); - if (!fixup_val) - return fixup_len; - - if (fixup_len % sizeof(uint32_t)) - return -FDT_ERR_BADOVERLAY; - fixup_len /= sizeof(uint32_t); - - tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); - if (!tree_val) { - if (tree_len == -FDT_ERR_NOTFOUND) - return -FDT_ERR_BADOVERLAY; - - return tree_len; - } - - for (i = 0; i < fixup_len; i++) { - fdt32_t *refp; - uint32_t valp; - - refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); - valp = fdt32_ld(refp); - - if (valp == fdto_phandle) - fdt32_st(refp, fdt_phandle); - } - } - - fdt_for_each_subnode(fixup_child, fdto, fixup_node) { - const char *fixup_child_name = fdt_get_name(fdto, fixup_child, NULL); - int tree_child; - - tree_child = fdt_subnode_offset(fdto, tree_node, fixup_child_name); - - if (tree_child == -FDT_ERR_NOTFOUND) - return -FDT_ERR_BADOVERLAY; - if (tree_child < 0) - return tree_child; - - ret = overlay_update_node_conflicting_references(fdto, tree_child, - fixup_child, - fdt_phandle, - fdto_phandle); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_update_local_conflicting_references - Recursively replace phandle values - * @fdto: Device tree overlay blob - * @fdt_phandle: Value to replace phandles with - * @fdto_phandle: Value to be replaced - * - * Replaces all phandles with value @fdto_phandle by @fdt_phandle. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_update_local_conflicting_references(void *fdto, - uint32_t fdt_phandle, - uint32_t fdto_phandle) -{ - int fixups; - - fixups = fdt_path_offset(fdto, "/__local_fixups__"); - if (fixups == -FDT_ERR_NOTFOUND) - return 0; - if (fixups < 0) - return fixups; - - return overlay_update_node_conflicting_references(fdto, 0, fixups, - fdt_phandle, - fdto_phandle); -} - -/** - * overlay_prevent_phandle_overwrite_node - Helper function for overlay_prevent_phandle_overwrite - * @fdt: Base Device tree blob - * @fdtnode: Node in fdt that is checked for an overwrite - * @fdto: Device tree overlay blob - * @fdtonode: Node in fdto matching @fdtnode - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_prevent_phandle_overwrite_node(void *fdt, int fdtnode, - void *fdto, int fdtonode) -{ - uint32_t fdt_phandle, fdto_phandle; - int fdtochild; - - fdt_phandle = fdt_get_phandle(fdt, fdtnode); - fdto_phandle = fdt_get_phandle(fdto, fdtonode); - - if (fdt_phandle && fdto_phandle) { - int ret; - - ret = overlay_adjust_local_conflicting_phandle(fdto, fdtonode, - fdt_phandle); - if (ret) - return ret; - - ret = overlay_update_local_conflicting_references(fdto, - fdt_phandle, - fdto_phandle); - if (ret) - return ret; - } - - fdt_for_each_subnode(fdtochild, fdto, fdtonode) { - const char *name = fdt_get_name(fdto, fdtochild, NULL); - int fdtchild; - int ret; - - fdtchild = fdt_subnode_offset(fdt, fdtnode, name); - if (fdtchild == -FDT_ERR_NOTFOUND) - /* - * no further overwrites possible here as this node is - * new - */ - continue; - - ret = overlay_prevent_phandle_overwrite_node(fdt, fdtchild, - fdto, fdtochild); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_prevent_phandle_overwrite - Fixes overlay phandles to not overwrite base phandles - * @fdt: Base Device Tree blob - * @fdto: Device tree overlay blob - * - * Checks recursively if applying fdto overwrites phandle values in the base - * dtb. When such a phandle is found, the fdto is changed to use the fdt's - * phandle value to not break references in the base. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_prevent_phandle_overwrite(void *fdt, void *fdto) -{ - int fragment; - - fdt_for_each_subnode(fragment, fdto, 0) { - int overlay; - int target; - int ret; - - overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); - if (overlay == -FDT_ERR_NOTFOUND) - continue; - - if (overlay < 0) - return overlay; - - target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); - if (target == -FDT_ERR_NOTFOUND) - /* - * The subtree doesn't exist in the base, so nothing - * will be overwritten. - */ - continue; - else if (target < 0) - return target; - - ret = overlay_prevent_phandle_overwrite_node(fdt, target, - fdto, overlay); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_apply_node - Merges a node into the base device tree - * @fdt: Base Device Tree blob - * @target: Node offset in the base device tree to apply the fragment to - * @fdto: Device tree overlay blob - * @node: Node offset in the overlay holding the changes to merge - * - * overlay_apply_node() merges a node into a target base device tree - * node pointed. - * - * This is part of the final step in the device tree overlay - * application process, when all the phandles have been adjusted and - * resolved and you just have to merge overlay into the base device - * tree. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_apply_node(void *fdt, int target, - void *fdto, int node) -{ - int property; - int subnode; - - fdt_for_each_property_offset(property, fdto, node) { - const char *name; - const void *prop; - int prop_len; - int ret; - - prop = fdt_getprop_by_offset(fdto, property, &name, - &prop_len); - if (prop_len == -FDT_ERR_NOTFOUND) - return -FDT_ERR_INTERNAL; - if (prop_len < 0) - return prop_len; - - ret = fdt_setprop(fdt, target, name, prop, prop_len); - if (ret) - return ret; - } - - fdt_for_each_subnode(subnode, fdto, node) { - const char *name = fdt_get_name(fdto, subnode, NULL); - int nnode; - int ret; - - nnode = fdt_add_subnode(fdt, target, name); - if (nnode == -FDT_ERR_EXISTS) { - nnode = fdt_subnode_offset(fdt, target, name); - if (nnode == -FDT_ERR_NOTFOUND) - return -FDT_ERR_INTERNAL; - } - - if (nnode < 0) - return nnode; - - ret = overlay_apply_node(fdt, nnode, fdto, subnode); - if (ret) - return ret; - } - - return 0; -} - -/** - * overlay_merge - Merge an overlay into its base device tree - * @fdt: Base Device Tree blob - * @fdto: Device tree overlay blob - * - * overlay_merge() merges an overlay into its base device tree. - * - * This is the next to last step in the device tree overlay application - * process, when all the phandles have been adjusted and resolved and - * you just have to merge overlay into the base device tree. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_merge(void *fdt, void *fdto) -{ - int fragment; - - fdt_for_each_subnode(fragment, fdto, 0) { - int overlay; - int target; - int ret; - - /* - * Each fragments will have an __overlay__ node. If - * they don't, it's not supposed to be merged - */ - overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); - if (overlay == -FDT_ERR_NOTFOUND) - continue; - - if (overlay < 0) - return overlay; - - target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); - if (target < 0) - return target; - - ret = overlay_apply_node(fdt, target, fdto, overlay); - if (ret) - return ret; - } - - return 0; -} - -static int get_path_len(const void *fdt, int nodeoffset) -{ - int len = 0, namelen; - const char *name; - - FDT_RO_PROBE(fdt); - - for (;;) { - name = fdt_get_name(fdt, nodeoffset, &namelen); - if (!name) - return namelen; - - /* root? we're done */ - if (namelen == 0) - break; - - nodeoffset = fdt_parent_offset(fdt, nodeoffset); - if (nodeoffset < 0) - return nodeoffset; - len += namelen + 1; - } - - /* in case of root pretend it's "/" */ - if (len == 0) - len++; - return len; -} - -/** - * overlay_symbol_update - Update the symbols of base tree after a merge - * @fdt: Base Device Tree blob - * @fdto: Device tree overlay blob - * - * overlay_symbol_update() updates the symbols of the base tree with the - * symbols of the applied overlay - * - * This is the last step in the device tree overlay application - * process, allowing the reference of overlay symbols by subsequent - * overlay operations. - * - * returns: - * 0 on success - * Negative error code on failure - */ -static int overlay_symbol_update(void *fdt, void *fdto) -{ - int root_sym, ov_sym, prop, path_len, fragment, target; - int len, frag_name_len, ret, rel_path_len; - const char *s, *e; - const char *path; - const char *name; - const char *frag_name; - const char *rel_path; - const char *target_path; - char *buf; - void *p; - - ov_sym = fdt_subnode_offset(fdto, 0, "__symbols__"); - - /* if no overlay symbols exist no problem */ - if (ov_sym < 0) - return 0; - - root_sym = fdt_subnode_offset(fdt, 0, "__symbols__"); - - /* it no root symbols exist we should create them */ - if (root_sym == -FDT_ERR_NOTFOUND) - root_sym = fdt_add_subnode(fdt, 0, "__symbols__"); - - /* any error is fatal now */ - if (root_sym < 0) - return root_sym; - - /* iterate over each overlay symbol */ - fdt_for_each_property_offset(prop, fdto, ov_sym) { - path = fdt_getprop_by_offset(fdto, prop, &name, &path_len); - if (!path) - return path_len; - - /* verify it's a string property (terminated by a single \0) */ - if (path_len < 1 || memchr(path, '\0', path_len) != &path[path_len - 1]) - return -FDT_ERR_BADVALUE; - - /* keep end marker to avoid strlen() */ - e = path + path_len; - - if (*path != '/') - return -FDT_ERR_BADVALUE; - - /* get fragment name first */ - s = strchr(path + 1, '/'); - if (!s) { - /* Symbol refers to something that won't end - * up in the target tree */ - continue; - } - - frag_name = path + 1; - frag_name_len = s - path - 1; - - /* verify format; safe since "s" lies in \0 terminated prop */ - len = sizeof("/__overlay__/") - 1; - if ((e - s) > len && (memcmp(s, "/__overlay__/", len) == 0)) { - /* //__overlay__/ */ - rel_path = s + len; - rel_path_len = e - rel_path - 1; - } else if ((e - s) == len - && (memcmp(s, "/__overlay__", len - 1) == 0)) { - /* //__overlay__ */ - rel_path = ""; - rel_path_len = 0; - } else { - /* Symbol refers to something that won't end - * up in the target tree */ - continue; - } - - /* find the fragment index in which the symbol lies */ - ret = fdt_subnode_offset_namelen(fdto, 0, frag_name, - frag_name_len); - /* not found? */ - if (ret < 0) - return -FDT_ERR_BADOVERLAY; - fragment = ret; - - /* an __overlay__ subnode must exist */ - ret = fdt_subnode_offset(fdto, fragment, "__overlay__"); - if (ret < 0) - return -FDT_ERR_BADOVERLAY; - - /* get the target of the fragment */ - ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); - if (ret < 0) - return ret; - target = ret; - - /* if we have a target path use */ - if (!target_path) { - ret = get_path_len(fdt, target); - if (ret < 0) - return ret; - len = ret; - } else { - len = strlen(target_path); - } - - ret = fdt_setprop_placeholder(fdt, root_sym, name, - len + (len > 1) + rel_path_len + 1, &p); - if (ret < 0) - return ret; - - if (!target_path) { - /* again in case setprop_placeholder changed it */ - ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); - if (ret < 0) - return ret; - target = ret; - } - - buf = p; - if (len > 1) { /* target is not root */ - if (!target_path) { - ret = fdt_get_path(fdt, target, buf, len + 1); - if (ret < 0) - return ret; - } else - memcpy(buf, target_path, len + 1); - - } else - len--; - - buf[len] = '/'; - memcpy(buf + len + 1, rel_path, rel_path_len); - buf[len + 1 + rel_path_len] = '\0'; - } - - return 0; -} - -int fdt_overlay_apply(void *fdt, void *fdto) -{ - uint32_t delta; - int ret; - - FDT_RO_PROBE(fdt); - FDT_RO_PROBE(fdto); - - ret = fdt_find_max_phandle(fdt, &delta); - if (ret) - goto err; - - /* Increase all phandles in the fdto by delta */ - ret = overlay_adjust_local_phandles(fdto, delta); - if (ret) - goto err; - - /* Adapt the phandle values in fdto to the above increase */ - ret = overlay_update_local_references(fdto, delta); - if (ret) - goto err; - - /* Update fdto's phandles using symbols from fdt */ - ret = overlay_fixup_phandles(fdt, fdto); - if (ret) - goto err; - - /* Don't overwrite phandles in fdt */ - ret = overlay_prevent_phandle_overwrite(fdt, fdto); - if (ret) - goto err; - - ret = overlay_merge(fdt, fdto); - if (ret) - goto err; - - ret = overlay_symbol_update(fdt, fdto); - if (ret) - goto err; - - /* - * The overlay has been damaged, erase its magic. - */ - fdt_set_magic(fdto, ~0); - - return 0; - -err: - /* - * The overlay might have been damaged, erase its magic. - */ - fdt_set_magic(fdto, ~0); - - /* - * The base device tree might have been damaged, erase its - * magic. - */ - fdt_set_magic(fdt, ~0); - - return ret; -} diff --git a/na/scripts/dtc/libfdt/fdt_ro.c b/na/scripts/dtc/libfdt/fdt_ro.c deleted file mode 100644 index 63494fb..0000000 --- a/na/scripts/dtc/libfdt/fdt_ro.c +++ /dev/null @@ -1,888 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -static int fdt_nodename_eq_(const void *fdt, int offset, - const char *s, int len) -{ - int olen; - const char *p = fdt_get_name(fdt, offset, &olen); - - if (!p || olen < len) - /* short match */ - return 0; - - if (memcmp(p, s, len) != 0) - return 0; - - if (p[len] == '\0') - return 1; - else if (!memchr(s, '@', len) && (p[len] == '@')) - return 1; - else - return 0; -} - -const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) -{ - int32_t totalsize; - uint32_t absoffset; - size_t len; - int err; - const char *s, *n; - - if (can_assume(VALID_INPUT)) { - s = (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; - - if (lenp) - *lenp = strlen(s); - return s; - } - totalsize = fdt_ro_probe_(fdt); - err = totalsize; - if (totalsize < 0) - goto fail; - - err = -FDT_ERR_BADOFFSET; - absoffset = stroffset + fdt_off_dt_strings(fdt); - if (absoffset >= (unsigned)totalsize) - goto fail; - len = totalsize - absoffset; - - if (fdt_magic(fdt) == FDT_MAGIC) { - if (stroffset < 0) - goto fail; - if (can_assume(LATEST) || fdt_version(fdt) >= 17) { - if ((unsigned)stroffset >= fdt_size_dt_strings(fdt)) - goto fail; - if ((fdt_size_dt_strings(fdt) - stroffset) < len) - len = fdt_size_dt_strings(fdt) - stroffset; - } - } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { - unsigned int sw_stroffset = -stroffset; - - if ((stroffset >= 0) || - (sw_stroffset > fdt_size_dt_strings(fdt))) - goto fail; - if (sw_stroffset < len) - len = sw_stroffset; - } else { - err = -FDT_ERR_INTERNAL; - goto fail; - } - - s = (const char *)fdt + absoffset; - n = memchr(s, '\0', len); - if (!n) { - /* missing terminating NULL */ - err = -FDT_ERR_TRUNCATED; - goto fail; - } - - if (lenp) - *lenp = n - s; - return s; - -fail: - if (lenp) - *lenp = err; - return NULL; -} - -const char *fdt_string(const void *fdt, int stroffset) -{ - return fdt_get_string(fdt, stroffset, NULL); -} - -static int fdt_string_eq_(const void *fdt, int stroffset, - const char *s, int len) -{ - int slen; - const char *p = fdt_get_string(fdt, stroffset, &slen); - - return p && (slen == len) && (memcmp(p, s, len) == 0); -} - -int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) -{ - uint32_t max = 0; - int offset = -1; - - while (true) { - uint32_t value; - - offset = fdt_next_node(fdt, offset, NULL); - if (offset < 0) { - if (offset == -FDT_ERR_NOTFOUND) - break; - - return offset; - } - - value = fdt_get_phandle(fdt, offset); - - if (value > max) - max = value; - } - - if (phandle) - *phandle = max; - - return 0; -} - -int fdt_generate_phandle(const void *fdt, uint32_t *phandle) -{ - uint32_t max; - int err; - - err = fdt_find_max_phandle(fdt, &max); - if (err < 0) - return err; - - if (max == FDT_MAX_PHANDLE) - return -FDT_ERR_NOPHANDLES; - - if (phandle) - *phandle = max + 1; - - return 0; -} - -static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) -{ - unsigned int offset = n * sizeof(struct fdt_reserve_entry); - unsigned int absoffset = fdt_off_mem_rsvmap(fdt) + offset; - - if (!can_assume(VALID_INPUT)) { - if (absoffset < fdt_off_mem_rsvmap(fdt)) - return NULL; - if (absoffset > fdt_totalsize(fdt) - - sizeof(struct fdt_reserve_entry)) - return NULL; - } - return fdt_mem_rsv_(fdt, n); -} - -int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) -{ - const struct fdt_reserve_entry *re; - - FDT_RO_PROBE(fdt); - re = fdt_mem_rsv(fdt, n); - if (!can_assume(VALID_INPUT) && !re) - return -FDT_ERR_BADOFFSET; - - *address = fdt64_ld_(&re->address); - *size = fdt64_ld_(&re->size); - return 0; -} - -int fdt_num_mem_rsv(const void *fdt) -{ - int i; - const struct fdt_reserve_entry *re; - - for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { - if (fdt64_ld_(&re->size) == 0) - return i; - } - return -FDT_ERR_TRUNCATED; -} - -static int nextprop_(const void *fdt, int offset) -{ - uint32_t tag; - int nextoffset; - - do { - tag = fdt_next_tag(fdt, offset, &nextoffset); - - switch (tag) { - case FDT_END: - if (nextoffset >= 0) - return -FDT_ERR_BADSTRUCTURE; - else - return nextoffset; - - case FDT_PROP: - return offset; - } - offset = nextoffset; - } while (tag == FDT_NOP); - - return -FDT_ERR_NOTFOUND; -} - -int fdt_subnode_offset_namelen(const void *fdt, int offset, - const char *name, int namelen) -{ - int depth; - - FDT_RO_PROBE(fdt); - - for (depth = 0; - (offset >= 0) && (depth >= 0); - offset = fdt_next_node(fdt, offset, &depth)) - if ((depth == 1) - && fdt_nodename_eq_(fdt, offset, name, namelen)) - return offset; - - if (depth < 0) - return -FDT_ERR_NOTFOUND; - return offset; /* error */ -} - -int fdt_subnode_offset(const void *fdt, int parentoffset, - const char *name) -{ - return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); -} - -int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) -{ - const char *end = path + namelen; - const char *p = path; - int offset = 0; - - FDT_RO_PROBE(fdt); - - if (!can_assume(VALID_INPUT) && namelen <= 0) - return -FDT_ERR_BADPATH; - - /* see if we have an alias */ - if (*path != '/') { - const char *q = memchr(path, '/', end - p); - - if (!q) - q = end; - - p = fdt_get_alias_namelen(fdt, p, q - p); - if (!p) - return -FDT_ERR_BADPATH; - offset = fdt_path_offset(fdt, p); - - p = q; - } - - while (p < end) { - const char *q; - - while (*p == '/') { - p++; - if (p == end) - return offset; - } - q = memchr(p, '/', end - p); - if (! q) - q = end; - - offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); - if (offset < 0) - return offset; - - p = q; - } - - return offset; -} - -int fdt_path_offset(const void *fdt, const char *path) -{ - return fdt_path_offset_namelen(fdt, path, strlen(path)); -} - -const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) -{ - const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); - const char *nameptr; - int err; - - if (!can_assume(VALID_DTB) && (((err = fdt_ro_probe_(fdt)) < 0) - || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0))) - goto fail; - - nameptr = nh->name; - - if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { - /* - * For old FDT versions, match the naming conventions of V16: - * give only the leaf name (after all /). The actual tree - * contents are loosely checked. - */ - const char *leaf; - leaf = strrchr(nameptr, '/'); - if (leaf == NULL) { - err = -FDT_ERR_BADSTRUCTURE; - goto fail; - } - nameptr = leaf+1; - } - - if (len) - *len = strlen(nameptr); - - return nameptr; - - fail: - if (len) - *len = err; - return NULL; -} - -int fdt_first_property_offset(const void *fdt, int nodeoffset) -{ - int offset; - - if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) - return offset; - - return nextprop_(fdt, offset); -} - -int fdt_next_property_offset(const void *fdt, int offset) -{ - if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) - return offset; - - return nextprop_(fdt, offset); -} - -static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, - int offset, - int *lenp) -{ - int err; - const struct fdt_property *prop; - - if (!can_assume(VALID_INPUT) && - (err = fdt_check_prop_offset_(fdt, offset)) < 0) { - if (lenp) - *lenp = err; - return NULL; - } - - prop = fdt_offset_ptr_(fdt, offset); - - if (lenp) - *lenp = fdt32_ld_(&prop->len); - - return prop; -} - -const struct fdt_property *fdt_get_property_by_offset(const void *fdt, - int offset, - int *lenp) -{ - /* Prior to version 16, properties may need realignment - * and this API does not work. fdt_getprop_*() will, however. */ - - if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { - if (lenp) - *lenp = -FDT_ERR_BADVERSION; - return NULL; - } - - return fdt_get_property_by_offset_(fdt, offset, lenp); -} - -static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, - int offset, - const char *name, - int namelen, - int *lenp, - int *poffset) -{ - for (offset = fdt_first_property_offset(fdt, offset); - (offset >= 0); - (offset = fdt_next_property_offset(fdt, offset))) { - const struct fdt_property *prop; - - prop = fdt_get_property_by_offset_(fdt, offset, lenp); - if (!can_assume(LIBFDT_FLAWLESS) && !prop) { - offset = -FDT_ERR_INTERNAL; - break; - } - if (fdt_string_eq_(fdt, fdt32_ld_(&prop->nameoff), - name, namelen)) { - if (poffset) - *poffset = offset; - return prop; - } - } - - if (lenp) - *lenp = offset; - return NULL; -} - - -const struct fdt_property *fdt_get_property_namelen(const void *fdt, - int offset, - const char *name, - int namelen, int *lenp) -{ - /* Prior to version 16, properties may need realignment - * and this API does not work. fdt_getprop_*() will, however. */ - if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { - if (lenp) - *lenp = -FDT_ERR_BADVERSION; - return NULL; - } - - return fdt_get_property_namelen_(fdt, offset, name, namelen, lenp, - NULL); -} - - -const struct fdt_property *fdt_get_property(const void *fdt, - int nodeoffset, - const char *name, int *lenp) -{ - return fdt_get_property_namelen(fdt, nodeoffset, name, - strlen(name), lenp); -} - -const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, - const char *name, int namelen, int *lenp) -{ - int poffset; - const struct fdt_property *prop; - - prop = fdt_get_property_namelen_(fdt, nodeoffset, name, namelen, lenp, - &poffset); - if (!prop) - return NULL; - - /* Handle realignment */ - if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && - (poffset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) - return prop->data + 4; - return prop->data; -} - -const void *fdt_getprop_by_offset(const void *fdt, int offset, - const char **namep, int *lenp) -{ - const struct fdt_property *prop; - - prop = fdt_get_property_by_offset_(fdt, offset, lenp); - if (!prop) - return NULL; - if (namep) { - const char *name; - int namelen; - - if (!can_assume(VALID_INPUT)) { - name = fdt_get_string(fdt, fdt32_ld_(&prop->nameoff), - &namelen); - *namep = name; - if (!name) { - if (lenp) - *lenp = namelen; - return NULL; - } - } else { - *namep = fdt_string(fdt, fdt32_ld_(&prop->nameoff)); - } - } - - /* Handle realignment */ - if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && - (offset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) - return prop->data + 4; - return prop->data; -} - -const void *fdt_getprop(const void *fdt, int nodeoffset, - const char *name, int *lenp) -{ - return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); -} - -uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) -{ - const fdt32_t *php; - int len; - - /* FIXME: This is a bit sub-optimal, since we potentially scan - * over all the properties twice. */ - php = fdt_getprop(fdt, nodeoffset, "phandle", &len); - if (!php || (len != sizeof(*php))) { - php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); - if (!php || (len != sizeof(*php))) - return 0; - } - - return fdt32_ld_(php); -} - -static const void *fdt_path_getprop_namelen(const void *fdt, const char *path, - const char *propname, int propnamelen, - int *lenp) -{ - int offset = fdt_path_offset(fdt, path); - - if (offset < 0) - return NULL; - - return fdt_getprop_namelen(fdt, offset, propname, propnamelen, lenp); -} - -const char *fdt_get_alias_namelen(const void *fdt, - const char *name, int namelen) -{ - int len; - const char *alias; - - alias = fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, &len); - - if (!can_assume(VALID_DTB) && - !(alias && len > 0 && alias[len - 1] == '\0' && *alias == '/')) - return NULL; - - return alias; -} - -const char *fdt_get_alias(const void *fdt, const char *name) -{ - return fdt_get_alias_namelen(fdt, name, strlen(name)); -} - -const char *fdt_get_symbol_namelen(const void *fdt, - const char *name, int namelen) -{ - return fdt_path_getprop_namelen(fdt, "/__symbols__", name, namelen, NULL); -} - -const char *fdt_get_symbol(const void *fdt, const char *name) -{ - return fdt_get_symbol_namelen(fdt, name, strlen(name)); -} - -int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) -{ - int pdepth = 0, p = 0; - int offset, depth, namelen; - const char *name; - - FDT_RO_PROBE(fdt); - - if (buflen < 2) - return -FDT_ERR_NOSPACE; - - for (offset = 0, depth = 0; - (offset >= 0) && (offset <= nodeoffset); - offset = fdt_next_node(fdt, offset, &depth)) { - while (pdepth > depth) { - do { - p--; - } while (buf[p-1] != '/'); - pdepth--; - } - - if (pdepth >= depth) { - name = fdt_get_name(fdt, offset, &namelen); - if (!name) - return namelen; - if ((p + namelen + 1) <= buflen) { - memcpy(buf + p, name, namelen); - p += namelen; - buf[p++] = '/'; - pdepth++; - } - } - - if (offset == nodeoffset) { - if (pdepth < (depth + 1)) - return -FDT_ERR_NOSPACE; - - if (p > 1) /* special case so that root path is "/", not "" */ - p--; - buf[p] = '\0'; - return 0; - } - } - - if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) - return -FDT_ERR_BADOFFSET; - else if (offset == -FDT_ERR_BADOFFSET) - return -FDT_ERR_BADSTRUCTURE; - - return offset; /* error from fdt_next_node() */ -} - -int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, - int supernodedepth, int *nodedepth) -{ - int offset, depth; - int supernodeoffset = -FDT_ERR_INTERNAL; - - FDT_RO_PROBE(fdt); - - if (supernodedepth < 0) - return -FDT_ERR_NOTFOUND; - - for (offset = 0, depth = 0; - (offset >= 0) && (offset <= nodeoffset); - offset = fdt_next_node(fdt, offset, &depth)) { - if (depth == supernodedepth) - supernodeoffset = offset; - - if (offset == nodeoffset) { - if (nodedepth) - *nodedepth = depth; - - if (supernodedepth > depth) - return -FDT_ERR_NOTFOUND; - else - return supernodeoffset; - } - } - - if (!can_assume(VALID_INPUT)) { - if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) - return -FDT_ERR_BADOFFSET; - else if (offset == -FDT_ERR_BADOFFSET) - return -FDT_ERR_BADSTRUCTURE; - } - - return offset; /* error from fdt_next_node() */ -} - -int fdt_node_depth(const void *fdt, int nodeoffset) -{ - int nodedepth; - int err; - - err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); - if (err) - return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err : - -FDT_ERR_INTERNAL; - return nodedepth; -} - -int fdt_parent_offset(const void *fdt, int nodeoffset) -{ - int nodedepth = fdt_node_depth(fdt, nodeoffset); - - if (nodedepth < 0) - return nodedepth; - return fdt_supernode_atdepth_offset(fdt, nodeoffset, - nodedepth - 1, NULL); -} - -int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, - const char *propname, - const void *propval, int proplen) -{ - int offset; - const void *val; - int len; - - FDT_RO_PROBE(fdt); - - /* FIXME: The algorithm here is pretty horrible: we scan each - * property of a node in fdt_getprop(), then if that didn't - * find what we want, we scan over them again making our way - * to the next node. Still it's the easiest to implement - * approach; performance can come later. */ - for (offset = fdt_next_node(fdt, startoffset, NULL); - offset >= 0; - offset = fdt_next_node(fdt, offset, NULL)) { - val = fdt_getprop(fdt, offset, propname, &len); - if (val && (len == proplen) - && (memcmp(val, propval, len) == 0)) - return offset; - } - - return offset; /* error from fdt_next_node() */ -} - -int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) -{ - int offset; - - if ((phandle == 0) || (phandle == ~0U)) - return -FDT_ERR_BADPHANDLE; - - FDT_RO_PROBE(fdt); - - /* FIXME: The algorithm here is pretty horrible: we - * potentially scan each property of a node in - * fdt_get_phandle(), then if that didn't find what - * we want, we scan over them again making our way to the next - * node. Still it's the easiest to implement approach; - * performance can come later. */ - for (offset = fdt_next_node(fdt, -1, NULL); - offset >= 0; - offset = fdt_next_node(fdt, offset, NULL)) { - if (fdt_get_phandle(fdt, offset) == phandle) - return offset; - } - - return offset; /* error from fdt_next_node() */ -} - -int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) -{ - int len = strlen(str); - const char *p; - - while (listlen >= len) { - if (memcmp(str, strlist, len+1) == 0) - return 1; - p = memchr(strlist, '\0', listlen); - if (!p) - return 0; /* malformed strlist.. */ - listlen -= (p-strlist) + 1; - strlist = p + 1; - } - return 0; -} - -int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) -{ - const char *list, *end; - int length, count = 0; - - list = fdt_getprop(fdt, nodeoffset, property, &length); - if (!list) - return length; - - end = list + length; - - while (list < end) { - length = strnlen(list, end - list) + 1; - - /* Abort if the last string isn't properly NUL-terminated. */ - if (list + length > end) - return -FDT_ERR_BADVALUE; - - list += length; - count++; - } - - return count; -} - -int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, - const char *string) -{ - int length, len, idx = 0; - const char *list, *end; - - list = fdt_getprop(fdt, nodeoffset, property, &length); - if (!list) - return length; - - len = strlen(string) + 1; - end = list + length; - - while (list < end) { - length = strnlen(list, end - list) + 1; - - /* Abort if the last string isn't properly NUL-terminated. */ - if (list + length > end) - return -FDT_ERR_BADVALUE; - - if (length == len && memcmp(list, string, length) == 0) - return idx; - - list += length; - idx++; - } - - return -FDT_ERR_NOTFOUND; -} - -const char *fdt_stringlist_get(const void *fdt, int nodeoffset, - const char *property, int idx, - int *lenp) -{ - const char *list, *end; - int length; - - list = fdt_getprop(fdt, nodeoffset, property, &length); - if (!list) { - if (lenp) - *lenp = length; - - return NULL; - } - - end = list + length; - - while (list < end) { - length = strnlen(list, end - list) + 1; - - /* Abort if the last string isn't properly NUL-terminated. */ - if (list + length > end) { - if (lenp) - *lenp = -FDT_ERR_BADVALUE; - - return NULL; - } - - if (idx == 0) { - if (lenp) - *lenp = length - 1; - - return list; - } - - list += length; - idx--; - } - - if (lenp) - *lenp = -FDT_ERR_NOTFOUND; - - return NULL; -} - -int fdt_node_check_compatible(const void *fdt, int nodeoffset, - const char *compatible) -{ - const void *prop; - int len; - - prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); - if (!prop) - return len; - - return !fdt_stringlist_contains(prop, len, compatible); -} - -int fdt_node_offset_by_compatible(const void *fdt, int startoffset, - const char *compatible) -{ - int offset, err; - - FDT_RO_PROBE(fdt); - - /* FIXME: The algorithm here is pretty horrible: we scan each - * property of a node in fdt_node_check_compatible(), then if - * that didn't find what we want, we scan over them again - * making our way to the next node. Still it's the easiest to - * implement approach; performance can come later. */ - for (offset = fdt_next_node(fdt, startoffset, NULL); - offset >= 0; - offset = fdt_next_node(fdt, offset, NULL)) { - err = fdt_node_check_compatible(fdt, offset, compatible); - if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) - return err; - else if (err == 0) - return offset; - } - - return offset; /* error from fdt_next_node() */ -} diff --git a/na/scripts/dtc/libfdt/fdt_rw.c b/na/scripts/dtc/libfdt/fdt_rw.c deleted file mode 100644 index 90ea14e..0000000 --- a/na/scripts/dtc/libfdt/fdt_rw.c +++ /dev/null @@ -1,512 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -static int fdt_blocks_misordered_(const void *fdt, - int mem_rsv_size, int struct_size) -{ - return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8)) - || (fdt_off_dt_struct(fdt) < - (fdt_off_mem_rsvmap(fdt) + mem_rsv_size)) - || (fdt_off_dt_strings(fdt) < - (fdt_off_dt_struct(fdt) + struct_size)) - || (fdt_totalsize(fdt) < - (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); -} - -static void fdt_downgrade_version(void *fdt) -{ - if (!can_assume(LATEST) && fdt_version(fdt) > FDT_LAST_SUPPORTED_VERSION) - fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); -} - -static int fdt_rw_probe_(void *fdt) -{ - if (can_assume(VALID_DTB)) - return 0; - FDT_RO_PROBE(fdt); - - if (!can_assume(LATEST) && fdt_version(fdt) < 17) - return -FDT_ERR_BADVERSION; - if (fdt_blocks_misordered_(fdt, sizeof(struct fdt_reserve_entry), - fdt_size_dt_struct(fdt))) - return -FDT_ERR_BADLAYOUT; - - fdt_downgrade_version(fdt); - return 0; -} - -#define FDT_RW_PROBE(fdt) \ - { \ - int err_; \ - if ((err_ = fdt_rw_probe_(fdt)) != 0) \ - return err_; \ - } - -static inline unsigned int fdt_data_size_(void *fdt) -{ - return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); -} - -static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen) -{ - char *p = splicepoint; - unsigned int dsize = fdt_data_size_(fdt); - size_t soff = p - (char *)fdt; - - if ((oldlen < 0) || (soff + oldlen < soff) || (soff + oldlen > dsize)) - return -FDT_ERR_BADOFFSET; - if ((p < (char *)fdt) || (dsize + newlen < (unsigned)oldlen)) - return -FDT_ERR_BADOFFSET; - if (dsize - oldlen + newlen > fdt_totalsize(fdt)) - return -FDT_ERR_NOSPACE; - memmove(p + newlen, p + oldlen, ((char *)fdt + dsize) - (p + oldlen)); - return 0; -} - -static int fdt_splice_mem_rsv_(void *fdt, struct fdt_reserve_entry *p, - int oldn, int newn) -{ - int delta = (newn - oldn) * sizeof(*p); - int err; - err = fdt_splice_(fdt, p, oldn * sizeof(*p), newn * sizeof(*p)); - if (err) - return err; - fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta); - fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); - return 0; -} - -static int fdt_splice_struct_(void *fdt, void *p, - int oldlen, int newlen) -{ - int delta = newlen - oldlen; - int err; - - if ((err = fdt_splice_(fdt, p, oldlen, newlen))) - return err; - - fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); - fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); - return 0; -} - -/* Must only be used to roll back in case of error */ -static void fdt_del_last_string_(void *fdt, const char *s) -{ - int newlen = strlen(s) + 1; - - fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); -} - -static int fdt_splice_string_(void *fdt, int newlen) -{ - void *p = (char *)fdt - + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); - int err; - - if ((err = fdt_splice_(fdt, p, 0, newlen))) - return err; - - fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); - return 0; -} - -/** - * fdt_find_add_string_() - Find or allocate a string - * - * @fdt: pointer to the device tree to check/adjust - * @s: string to find/add - * @allocated: Set to 0 if the string was found, 1 if not found and so - * allocated. Ignored if can_assume(NO_ROLLBACK) - * @return offset of string in the string table (whether found or added) - */ -static int fdt_find_add_string_(void *fdt, const char *s, int slen, - int *allocated) -{ - char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); - const char *p; - char *new; - int err; - - if (!can_assume(NO_ROLLBACK)) - *allocated = 0; - - p = fdt_find_string_len_(strtab, fdt_size_dt_strings(fdt), s, slen); - if (p) - /* found it */ - return (p - strtab); - - new = strtab + fdt_size_dt_strings(fdt); - err = fdt_splice_string_(fdt, slen + 1); - if (err) - return err; - - if (!can_assume(NO_ROLLBACK)) - *allocated = 1; - - memcpy(new, s, slen); - new[slen] = '\0'; - - return (new - strtab); -} - -int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) -{ - struct fdt_reserve_entry *re; - int err; - - FDT_RW_PROBE(fdt); - - re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt)); - err = fdt_splice_mem_rsv_(fdt, re, 0, 1); - if (err) - return err; - - re->address = cpu_to_fdt64(address); - re->size = cpu_to_fdt64(size); - return 0; -} - -int fdt_del_mem_rsv(void *fdt, int n) -{ - struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n); - - FDT_RW_PROBE(fdt); - - if (n >= fdt_num_mem_rsv(fdt)) - return -FDT_ERR_NOTFOUND; - - return fdt_splice_mem_rsv_(fdt, re, 1, 0); -} - -static int fdt_resize_property_(void *fdt, int nodeoffset, - const char *name, int namelen, - int len, struct fdt_property **prop) -{ - int oldlen; - int err; - - *prop = fdt_get_property_namelen_w(fdt, nodeoffset, name, namelen, - &oldlen); - if (!*prop) - return oldlen; - - if ((err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), - FDT_TAGALIGN(len)))) - return err; - - (*prop)->len = cpu_to_fdt32(len); - return 0; -} - -static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, - int namelen, int len, struct fdt_property **prop) -{ - int proplen; - int nextoffset; - int namestroff; - int err; - int allocated; - - if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) - return nextoffset; - - namestroff = fdt_find_add_string_(fdt, name, namelen, &allocated); - if (namestroff < 0) - return namestroff; - - *prop = fdt_offset_ptr_w_(fdt, nextoffset); - proplen = sizeof(**prop) + FDT_TAGALIGN(len); - - err = fdt_splice_struct_(fdt, *prop, 0, proplen); - if (err) { - /* Delete the string if we failed to add it */ - if (!can_assume(NO_ROLLBACK) && allocated) - fdt_del_last_string_(fdt, name); - return err; - } - - (*prop)->tag = cpu_to_fdt32(FDT_PROP); - (*prop)->nameoff = cpu_to_fdt32(namestroff); - (*prop)->len = cpu_to_fdt32(len); - return 0; -} - -int fdt_set_name(void *fdt, int nodeoffset, const char *name) -{ - char *namep; - int oldlen, newlen; - int err; - - FDT_RW_PROBE(fdt); - - namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); - if (!namep) - return oldlen; - - newlen = strlen(name); - - err = fdt_splice_struct_(fdt, namep, FDT_TAGALIGN(oldlen+1), - FDT_TAGALIGN(newlen+1)); - if (err) - return err; - - memcpy(namep, name, newlen+1); - return 0; -} - -int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, int len, void **prop_data) -{ - struct fdt_property *prop; - int err; - - FDT_RW_PROBE(fdt); - - err = fdt_resize_property_(fdt, nodeoffset, name, namelen, len, &prop); - if (err == -FDT_ERR_NOTFOUND) - err = fdt_add_property_(fdt, nodeoffset, name, namelen, len, - &prop); - if (err) - return err; - - *prop_data = prop->data; - return 0; -} - -int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, const void *val, int len) -{ - void *prop_data; - int err; - - err = fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, namelen, - len, &prop_data); - if (err) - return err; - - if (len) - memcpy(prop_data, val, len); - return 0; -} - -int fdt_appendprop(void *fdt, int nodeoffset, const char *name, - const void *val, int len) -{ - struct fdt_property *prop; - int err, oldlen, newlen; - - FDT_RW_PROBE(fdt); - - prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); - if (prop) { - newlen = len + oldlen; - err = fdt_splice_struct_(fdt, prop->data, - FDT_TAGALIGN(oldlen), - FDT_TAGALIGN(newlen)); - if (err) - return err; - prop->len = cpu_to_fdt32(newlen); - memcpy(prop->data + oldlen, val, len); - } else { - err = fdt_add_property_(fdt, nodeoffset, name, strlen(name), - len, &prop); - if (err) - return err; - memcpy(prop->data, val, len); - } - return 0; -} - -int fdt_delprop(void *fdt, int nodeoffset, const char *name) -{ - struct fdt_property *prop; - int len, proplen; - - FDT_RW_PROBE(fdt); - - prop = fdt_get_property_w(fdt, nodeoffset, name, &len); - if (!prop) - return len; - - proplen = sizeof(*prop) + FDT_TAGALIGN(len); - return fdt_splice_struct_(fdt, prop, proplen, 0); -} - -int fdt_add_subnode_namelen(void *fdt, int parentoffset, - const char *name, int namelen) -{ - struct fdt_node_header *nh; - int offset, nextoffset; - int nodelen; - int err; - uint32_t tag; - fdt32_t *endtag; - - FDT_RW_PROBE(fdt); - - offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); - if (offset >= 0) - return -FDT_ERR_EXISTS; - else if (offset != -FDT_ERR_NOTFOUND) - return offset; - - /* Try to place the new node after the parent's properties */ - tag = fdt_next_tag(fdt, parentoffset, &nextoffset); - /* the fdt_subnode_offset_namelen() should ensure this never hits */ - if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE)) - return -FDT_ERR_INTERNAL; - do { - offset = nextoffset; - tag = fdt_next_tag(fdt, offset, &nextoffset); - } while ((tag == FDT_PROP) || (tag == FDT_NOP)); - - nh = fdt_offset_ptr_w_(fdt, offset); - nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; - - err = fdt_splice_struct_(fdt, nh, 0, nodelen); - if (err) - return err; - - nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); - memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); - memcpy(nh->name, name, namelen); - endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE); - *endtag = cpu_to_fdt32(FDT_END_NODE); - - return offset; -} - -int fdt_add_subnode(void *fdt, int parentoffset, const char *name) -{ - return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name)); -} - -int fdt_del_node(void *fdt, int nodeoffset) -{ - int endoffset; - - FDT_RW_PROBE(fdt); - - endoffset = fdt_node_end_offset_(fdt, nodeoffset); - if (endoffset < 0) - return endoffset; - - return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset), - endoffset - nodeoffset, 0); -} - -static void fdt_packblocks_(const char *old, char *new, - int mem_rsv_size, - int struct_size, - int strings_size) -{ - int mem_rsv_off, struct_off, strings_off; - - mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8); - struct_off = mem_rsv_off + mem_rsv_size; - strings_off = struct_off + struct_size; - - memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size); - fdt_set_off_mem_rsvmap(new, mem_rsv_off); - - memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size); - fdt_set_off_dt_struct(new, struct_off); - fdt_set_size_dt_struct(new, struct_size); - - memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size); - fdt_set_off_dt_strings(new, strings_off); - fdt_set_size_dt_strings(new, fdt_size_dt_strings(old)); -} - -int fdt_open_into(const void *fdt, void *buf, int bufsize) -{ - int err; - int mem_rsv_size, struct_size; - int newsize; - const char *fdtstart = fdt; - const char *fdtend = fdtstart + fdt_totalsize(fdt); - char *tmp; - - FDT_RO_PROBE(fdt); - - mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) - * sizeof(struct fdt_reserve_entry); - - if (can_assume(LATEST) || fdt_version(fdt) >= 17) { - struct_size = fdt_size_dt_struct(fdt); - } else if (fdt_version(fdt) == 16) { - struct_size = 0; - while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END) - ; - if (struct_size < 0) - return struct_size; - } else { - return -FDT_ERR_BADVERSION; - } - - if (can_assume(LIBFDT_ORDER) || - !fdt_blocks_misordered_(fdt, mem_rsv_size, struct_size)) { - /* no further work necessary */ - err = fdt_move(fdt, buf, bufsize); - if (err) - return err; - fdt_set_version(buf, 17); - fdt_set_size_dt_struct(buf, struct_size); - fdt_set_totalsize(buf, bufsize); - return 0; - } - - /* Need to reorder */ - newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size - + struct_size + fdt_size_dt_strings(fdt); - - if (bufsize < newsize) - return -FDT_ERR_NOSPACE; - - /* First attempt to build converted tree at beginning of buffer */ - tmp = buf; - /* But if that overlaps with the old tree... */ - if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) { - /* Try right after the old tree instead */ - tmp = (char *)(uintptr_t)fdtend; - if ((tmp + newsize) > ((char *)buf + bufsize)) - return -FDT_ERR_NOSPACE; - } - - fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size, - fdt_size_dt_strings(fdt)); - memmove(buf, tmp, newsize); - - fdt_set_magic(buf, FDT_MAGIC); - fdt_set_totalsize(buf, bufsize); - fdt_set_version(buf, 17); - fdt_set_last_comp_version(buf, 16); - fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt)); - - return 0; -} - -int fdt_pack(void *fdt) -{ - int mem_rsv_size; - - FDT_RW_PROBE(fdt); - - mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) - * sizeof(struct fdt_reserve_entry); - fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt), - fdt_size_dt_strings(fdt)); - fdt_set_totalsize(fdt, fdt_data_size_(fdt)); - - return 0; -} diff --git a/na/scripts/dtc/libfdt/fdt_strerror.c b/na/scripts/dtc/libfdt/fdt_strerror.c deleted file mode 100644 index d852b77..0000000 --- a/na/scripts/dtc/libfdt/fdt_strerror.c +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -struct fdt_errtabent { - const char *str; -}; - -#define FDT_ERRTABENT(val) \ - [(val)] = { .str = #val, } - -static struct fdt_errtabent fdt_errtable[] = { - FDT_ERRTABENT(FDT_ERR_NOTFOUND), - FDT_ERRTABENT(FDT_ERR_EXISTS), - FDT_ERRTABENT(FDT_ERR_NOSPACE), - - FDT_ERRTABENT(FDT_ERR_BADOFFSET), - FDT_ERRTABENT(FDT_ERR_BADPATH), - FDT_ERRTABENT(FDT_ERR_BADPHANDLE), - FDT_ERRTABENT(FDT_ERR_BADSTATE), - - FDT_ERRTABENT(FDT_ERR_TRUNCATED), - FDT_ERRTABENT(FDT_ERR_BADMAGIC), - FDT_ERRTABENT(FDT_ERR_BADVERSION), - FDT_ERRTABENT(FDT_ERR_BADSTRUCTURE), - FDT_ERRTABENT(FDT_ERR_BADLAYOUT), - FDT_ERRTABENT(FDT_ERR_INTERNAL), - FDT_ERRTABENT(FDT_ERR_BADNCELLS), - FDT_ERRTABENT(FDT_ERR_BADVALUE), - FDT_ERRTABENT(FDT_ERR_BADOVERLAY), - FDT_ERRTABENT(FDT_ERR_NOPHANDLES), - FDT_ERRTABENT(FDT_ERR_BADFLAGS), - FDT_ERRTABENT(FDT_ERR_ALIGNMENT), -}; -#define FDT_ERRTABSIZE ((int)(sizeof(fdt_errtable) / sizeof(fdt_errtable[0]))) - -const char *fdt_strerror(int errval) -{ - if (errval > 0) - return ""; - else if (errval == 0) - return ""; - else if (-errval < FDT_ERRTABSIZE) { - const char *s = fdt_errtable[-errval].str; - - if (s) - return s; - } - - return ""; -} diff --git a/na/scripts/dtc/libfdt/fdt_sw.c b/na/scripts/dtc/libfdt/fdt_sw.c deleted file mode 100644 index 4c569ee..0000000 --- a/na/scripts/dtc/libfdt/fdt_sw.c +++ /dev/null @@ -1,384 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -static int fdt_sw_probe_(void *fdt) -{ - if (!can_assume(VALID_INPUT)) { - if (fdt_magic(fdt) == FDT_MAGIC) - return -FDT_ERR_BADSTATE; - else if (fdt_magic(fdt) != FDT_SW_MAGIC) - return -FDT_ERR_BADMAGIC; - } - - return 0; -} - -#define FDT_SW_PROBE(fdt) \ - { \ - int err; \ - if ((err = fdt_sw_probe_(fdt)) != 0) \ - return err; \ - } - -/* 'memrsv' state: Initial state after fdt_create() - * - * Allowed functions: - * fdt_add_reservemap_entry() - * fdt_finish_reservemap() [moves to 'struct' state] - */ -static int fdt_sw_probe_memrsv_(void *fdt) -{ - int err = fdt_sw_probe_(fdt); - if (err) - return err; - - if (!can_assume(VALID_INPUT) && fdt_off_dt_strings(fdt) != 0) - return -FDT_ERR_BADSTATE; - return 0; -} - -#define FDT_SW_PROBE_MEMRSV(fdt) \ - { \ - int err; \ - if ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \ - return err; \ - } - -/* 'struct' state: Enter this state after fdt_finish_reservemap() - * - * Allowed functions: - * fdt_begin_node() - * fdt_end_node() - * fdt_property*() - * fdt_finish() [moves to 'complete' state] - */ -static int fdt_sw_probe_struct_(void *fdt) -{ - int err = fdt_sw_probe_(fdt); - if (err) - return err; - - if (!can_assume(VALID_INPUT) && - fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) - return -FDT_ERR_BADSTATE; - return 0; -} - -#define FDT_SW_PROBE_STRUCT(fdt) \ - { \ - int err; \ - if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ - return err; \ - } - -static inline uint32_t sw_flags(void *fdt) -{ - /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ - return fdt_last_comp_version(fdt); -} - -/* 'complete' state: Enter this state after fdt_finish() - * - * Allowed functions: none - */ - -static void *fdt_grab_space_(void *fdt, size_t len) -{ - unsigned int offset = fdt_size_dt_struct(fdt); - unsigned int spaceleft; - - spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) - - fdt_size_dt_strings(fdt); - - if ((offset + len < offset) || (offset + len > spaceleft)) - return NULL; - - fdt_set_size_dt_struct(fdt, offset + len); - return fdt_offset_ptr_w_(fdt, offset); -} - -int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) -{ - const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header), - sizeof(struct fdt_reserve_entry)); - void *fdt = buf; - - if (bufsize < hdrsize) - return -FDT_ERR_NOSPACE; - - if (flags & ~FDT_CREATE_FLAGS_ALL) - return -FDT_ERR_BADFLAGS; - - memset(buf, 0, bufsize); - - /* - * magic and last_comp_version keep intermediate state during the fdt - * creation process, which is replaced with the proper FDT format by - * fdt_finish(). - * - * flags should be accessed with sw_flags(). - */ - fdt_set_magic(fdt, FDT_SW_MAGIC); - fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); - fdt_set_last_comp_version(fdt, flags); - - fdt_set_totalsize(fdt, bufsize); - - fdt_set_off_mem_rsvmap(fdt, hdrsize); - fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); - fdt_set_off_dt_strings(fdt, 0); - - return 0; -} - -int fdt_create(void *buf, int bufsize) -{ - return fdt_create_with_flags(buf, bufsize, 0); -} - -int fdt_resize(void *fdt, void *buf, int bufsize) -{ - size_t headsize, tailsize; - char *oldtail, *newtail; - - FDT_SW_PROBE(fdt); - - if (bufsize < 0) - return -FDT_ERR_NOSPACE; - - headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); - tailsize = fdt_size_dt_strings(fdt); - - if (!can_assume(VALID_DTB) && - headsize + tailsize > fdt_totalsize(fdt)) - return -FDT_ERR_INTERNAL; - - if ((headsize + tailsize) > (unsigned)bufsize) - return -FDT_ERR_NOSPACE; - - oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; - newtail = (char *)buf + bufsize - tailsize; - - /* Two cases to avoid clobbering data if the old and new - * buffers partially overlap */ - if (buf <= fdt) { - memmove(buf, fdt, headsize); - memmove(newtail, oldtail, tailsize); - } else { - memmove(newtail, oldtail, tailsize); - memmove(buf, fdt, headsize); - } - - fdt_set_totalsize(buf, bufsize); - if (fdt_off_dt_strings(buf)) - fdt_set_off_dt_strings(buf, bufsize); - - return 0; -} - -int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) -{ - struct fdt_reserve_entry *re; - int offset; - - FDT_SW_PROBE_MEMRSV(fdt); - - offset = fdt_off_dt_struct(fdt); - if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) - return -FDT_ERR_NOSPACE; - - re = (struct fdt_reserve_entry *)((char *)fdt + offset); - re->address = cpu_to_fdt64(addr); - re->size = cpu_to_fdt64(size); - - fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); - - return 0; -} - -int fdt_finish_reservemap(void *fdt) -{ - int err = fdt_add_reservemap_entry(fdt, 0, 0); - - if (err) - return err; - - fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); - return 0; -} - -int fdt_begin_node(void *fdt, const char *name) -{ - struct fdt_node_header *nh; - int namelen; - - FDT_SW_PROBE_STRUCT(fdt); - - namelen = strlen(name) + 1; - nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); - if (! nh) - return -FDT_ERR_NOSPACE; - - nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); - memcpy(nh->name, name, namelen); - return 0; -} - -int fdt_end_node(void *fdt) -{ - fdt32_t *en; - - FDT_SW_PROBE_STRUCT(fdt); - - en = fdt_grab_space_(fdt, FDT_TAGSIZE); - if (! en) - return -FDT_ERR_NOSPACE; - - *en = cpu_to_fdt32(FDT_END_NODE); - return 0; -} - -static int fdt_add_string_(void *fdt, const char *s) -{ - char *strtab = (char *)fdt + fdt_totalsize(fdt); - unsigned int strtabsize = fdt_size_dt_strings(fdt); - unsigned int len = strlen(s) + 1; - unsigned int struct_top, offset; - - offset = strtabsize + len; - struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); - if (fdt_totalsize(fdt) - offset < struct_top) - return 0; /* no more room :( */ - - memcpy(strtab - offset, s, len); - fdt_set_size_dt_strings(fdt, strtabsize + len); - return -offset; -} - -/* Must only be used to roll back in case of error */ -static void fdt_del_last_string_(void *fdt, const char *s) -{ - int strtabsize = fdt_size_dt_strings(fdt); - int len = strlen(s) + 1; - - fdt_set_size_dt_strings(fdt, strtabsize - len); -} - -static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) -{ - char *strtab = (char *)fdt + fdt_totalsize(fdt); - int strtabsize = fdt_size_dt_strings(fdt); - const char *p; - - *allocated = 0; - - p = fdt_find_string_(strtab - strtabsize, strtabsize, s); - if (p) - return p - strtab; - - *allocated = 1; - - return fdt_add_string_(fdt, s); -} - -int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) -{ - struct fdt_property *prop; - int nameoff; - int allocated; - - FDT_SW_PROBE_STRUCT(fdt); - - /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ - if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { - allocated = 1; - nameoff = fdt_add_string_(fdt, name); - } else { - nameoff = fdt_find_add_string_(fdt, name, &allocated); - } - if (nameoff == 0) - return -FDT_ERR_NOSPACE; - - prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); - if (! prop) { - if (allocated) - fdt_del_last_string_(fdt, name); - return -FDT_ERR_NOSPACE; - } - - prop->tag = cpu_to_fdt32(FDT_PROP); - prop->nameoff = cpu_to_fdt32(nameoff); - prop->len = cpu_to_fdt32(len); - *valp = prop->data; - return 0; -} - -int fdt_property(void *fdt, const char *name, const void *val, int len) -{ - void *ptr; - int ret; - - ret = fdt_property_placeholder(fdt, name, len, &ptr); - if (ret) - return ret; - memcpy(ptr, val, len); - return 0; -} - -int fdt_finish(void *fdt) -{ - char *p = (char *)fdt; - fdt32_t *end; - int oldstroffset, newstroffset; - uint32_t tag; - int offset, nextoffset; - - FDT_SW_PROBE_STRUCT(fdt); - - /* Add terminator */ - end = fdt_grab_space_(fdt, sizeof(*end)); - if (! end) - return -FDT_ERR_NOSPACE; - *end = cpu_to_fdt32(FDT_END); - - /* Relocate the string table */ - oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); - newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); - memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); - fdt_set_off_dt_strings(fdt, newstroffset); - - /* Walk the structure, correcting string offsets */ - offset = 0; - while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { - if (tag == FDT_PROP) { - struct fdt_property *prop = - fdt_offset_ptr_w_(fdt, offset); - int nameoff; - - nameoff = fdt32_to_cpu(prop->nameoff); - nameoff += fdt_size_dt_strings(fdt); - prop->nameoff = cpu_to_fdt32(nameoff); - } - offset = nextoffset; - } - if (nextoffset < 0) - return nextoffset; - - /* Finally, adjust the header */ - fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); - - /* And fix up fields that were keeping intermediate state. */ - fdt_set_last_comp_version(fdt, FDT_LAST_COMPATIBLE_VERSION); - fdt_set_magic(fdt, FDT_MAGIC); - - return 0; -} diff --git a/na/scripts/dtc/libfdt/fdt_wip.c b/na/scripts/dtc/libfdt/fdt_wip.c deleted file mode 100644 index c2d7566..0000000 --- a/na/scripts/dtc/libfdt/fdt_wip.c +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include -#include - -#include "libfdt_internal.h" - -int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, - const char *name, int namelen, - uint32_t idx, const void *val, - int len) -{ - void *propval; - int proplen; - - propval = fdt_getprop_namelen_w(fdt, nodeoffset, name, namelen, - &proplen); - if (!propval) - return proplen; - - if ((unsigned)proplen < (len + idx)) - return -FDT_ERR_NOSPACE; - - memcpy((char *)propval + idx, val, len); - return 0; -} - -int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, - const void *val, int len) -{ - const void *propval; - int proplen; - - propval = fdt_getprop(fdt, nodeoffset, name, &proplen); - if (!propval) - return proplen; - - if (proplen != len) - return -FDT_ERR_NOSPACE; - - return fdt_setprop_inplace_namelen_partial(fdt, nodeoffset, name, - strlen(name), 0, - val, len); -} - -static void fdt_nop_region_(void *start, int len) -{ - fdt32_t *p; - - for (p = start; (char *)p < ((char *)start + len); p++) - *p = cpu_to_fdt32(FDT_NOP); -} - -int fdt_nop_property(void *fdt, int nodeoffset, const char *name) -{ - struct fdt_property *prop; - int len; - - prop = fdt_get_property_w(fdt, nodeoffset, name, &len); - if (!prop) - return len; - - fdt_nop_region_(prop, len + sizeof(*prop)); - - return 0; -} - -int fdt_node_end_offset_(void *fdt, int offset) -{ - int depth = 0; - - while ((offset >= 0) && (depth >= 0)) - offset = fdt_next_node(fdt, offset, &depth); - - return offset; -} - -int fdt_nop_node(void *fdt, int nodeoffset) -{ - int endoffset; - - endoffset = fdt_node_end_offset_(fdt, nodeoffset); - if (endoffset < 0) - return endoffset; - - fdt_nop_region_(fdt_offset_ptr_w(fdt, nodeoffset, 0), - endoffset - nodeoffset); - return 0; -} diff --git a/na/scripts/dtc/libfdt/libfdt.h b/na/scripts/dtc/libfdt/libfdt.h deleted file mode 100644 index 81aaf7c..0000000 --- a/na/scripts/dtc/libfdt/libfdt.h +++ /dev/null @@ -1,2540 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_H -#define LIBFDT_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ - -#include "libfdt_env.h" -#include "fdt.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define FDT_FIRST_SUPPORTED_VERSION 0x02 -#define FDT_LAST_COMPATIBLE_VERSION 0x10 -#define FDT_LAST_SUPPORTED_VERSION 0x11 - -/* Error codes: informative error codes */ -#define FDT_ERR_NOTFOUND 1 - /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ -#define FDT_ERR_EXISTS 2 - /* FDT_ERR_EXISTS: Attempted to create a node or property which - * already exists */ -#define FDT_ERR_NOSPACE 3 - /* FDT_ERR_NOSPACE: Operation needed to expand the device - * tree, but its buffer did not have sufficient space to - * contain the expanded tree. Use fdt_open_into() to move the - * device tree to a buffer with more space. */ - -/* Error codes: codes for bad parameters */ -#define FDT_ERR_BADOFFSET 4 - /* FDT_ERR_BADOFFSET: Function was passed a structure block - * offset which is out-of-bounds, or which points to an - * unsuitable part of the structure for the operation. */ -#define FDT_ERR_BADPATH 5 - /* FDT_ERR_BADPATH: Function was passed a badly formatted path - * (e.g. missing a leading / for a function which requires an - * absolute path) */ -#define FDT_ERR_BADPHANDLE 6 - /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. - * This can be caused either by an invalid phandle property - * length, or the phandle value was either 0 or -1, which are - * not permitted. */ -#define FDT_ERR_BADSTATE 7 - /* FDT_ERR_BADSTATE: Function was passed an incomplete device - * tree created by the sequential-write functions, which is - * not sufficiently complete for the requested operation. */ - -/* Error codes: codes for bad device tree blobs */ -#define FDT_ERR_TRUNCATED 8 - /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly - * terminated (overflows, goes outside allowed bounds, or - * isn't properly terminated). */ -#define FDT_ERR_BADMAGIC 9 - /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a - * device tree at all - it is missing the flattened device - * tree magic number. */ -#define FDT_ERR_BADVERSION 10 - /* FDT_ERR_BADVERSION: Given device tree has a version which - * can't be handled by the requested operation. For - * read-write functions, this may mean that fdt_open_into() is - * required to convert the tree to the expected version. */ -#define FDT_ERR_BADSTRUCTURE 11 - /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt - * structure block or other serious error (e.g. misnested - * nodes, or subnodes preceding properties). */ -#define FDT_ERR_BADLAYOUT 12 - /* FDT_ERR_BADLAYOUT: For read-write functions, the given - * device tree has it's sub-blocks in an order that the - * function can't handle (memory reserve map, then structure, - * then strings). Use fdt_open_into() to reorganize the tree - * into a form suitable for the read-write operations. */ - -/* "Can't happen" error indicating a bug in libfdt */ -#define FDT_ERR_INTERNAL 13 - /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. - * Should never be returned, if it is, it indicates a bug in - * libfdt itself. */ - -/* Errors in device tree content */ -#define FDT_ERR_BADNCELLS 14 - /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells - * or similar property with a bad format or value */ - -#define FDT_ERR_BADVALUE 15 - /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected - * value. For example: a property expected to contain a string list - * is not NUL-terminated within the length of its value. */ - -#define FDT_ERR_BADOVERLAY 16 - /* FDT_ERR_BADOVERLAY: The device tree overlay, while - * correctly structured, cannot be applied due to some - * unexpected or missing value, property or node. */ - -#define FDT_ERR_NOPHANDLES 17 - /* FDT_ERR_NOPHANDLES: The device tree doesn't have any - * phandle available anymore without causing an overflow */ - -#define FDT_ERR_BADFLAGS 18 - /* FDT_ERR_BADFLAGS: The function was passed a flags field that - * contains invalid flags or an invalid combination of flags. */ - -#define FDT_ERR_ALIGNMENT 19 - /* FDT_ERR_ALIGNMENT: The device tree base address is not 8-byte - * aligned. */ - -#define FDT_ERR_MAX 19 - -/* constants */ -#define FDT_MAX_PHANDLE 0xfffffffe - /* Valid values for phandles range from 1 to 2^32-2. */ - -/**********************************************************************/ -/* Low-level functions (you probably don't need these) */ -/**********************************************************************/ - -/** - * fdt_offset_ptr - safely get a byte range within the device tree blob - * @fdt: Pointer to the device tree blob - * @offset: Offset within the blob to the desired byte range - * @checklen: Required length of the byte range - * - * fdt_offset_ptr() returns a pointer to the byte range of length @checklen at - * the given @offset within the device tree blob, after verifying that the byte - * range fits entirely within the blob and does not overflow. - * - * returns: - * pointer to the byte range, on success - * NULL, if the requested range does not fit within the blob - */ -#ifndef SWIG /* This function is not useful in Python */ -const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); -#endif -static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) -{ - return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); -} - -/** - * fdt_next_tag - get next tag in the device tree - * @fdt: Pointer to the device tree blob - * @offset: Offset within the blob to start searching - * @nextoffset: Pointer to variable to store the offset of the next tag - * - * fdt_next_tag() returns the tag type of the next tag in the device tree - * blob starting from the given @offset. If @nextoffset is non-NULL, it will - * be set to the offset immediately following the tag. - * - * returns: - * the tag type (FDT_BEGIN_NODE, FDT_END_NODE, FDT_PROP, FDT_NOP, FDT_END), - * FDT_END, if offset is out of bounds - */ -uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); - -/* - * External helpers to access words from a device tree blob. They're built - * to work even with unaligned pointers on platforms (such as ARMv5) that don't - * like unaligned loads and stores. - */ -static inline uint16_t fdt16_ld(const fdt16_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint16_t)bp[0] << 8) | bp[1]; -} - -static inline uint32_t fdt32_ld(const fdt32_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint32_t)bp[0] << 24) - | ((uint32_t)bp[1] << 16) - | ((uint32_t)bp[2] << 8) - | bp[3]; -} - -static inline void fdt32_st(void *property, uint32_t value) -{ - uint8_t *bp = (uint8_t *)property; - - bp[0] = value >> 24; - bp[1] = (value >> 16) & 0xff; - bp[2] = (value >> 8) & 0xff; - bp[3] = value & 0xff; -} - -static inline uint64_t fdt64_ld(const fdt64_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint64_t)bp[0] << 56) - | ((uint64_t)bp[1] << 48) - | ((uint64_t)bp[2] << 40) - | ((uint64_t)bp[3] << 32) - | ((uint64_t)bp[4] << 24) - | ((uint64_t)bp[5] << 16) - | ((uint64_t)bp[6] << 8) - | bp[7]; -} - -static inline void fdt64_st(void *property, uint64_t value) -{ - uint8_t *bp = (uint8_t *)property; - - bp[0] = value >> 56; - bp[1] = (value >> 48) & 0xff; - bp[2] = (value >> 40) & 0xff; - bp[3] = (value >> 32) & 0xff; - bp[4] = (value >> 24) & 0xff; - bp[5] = (value >> 16) & 0xff; - bp[6] = (value >> 8) & 0xff; - bp[7] = value & 0xff; -} - -/**********************************************************************/ -/* Traversal functions */ -/**********************************************************************/ - -int fdt_next_node(const void *fdt, int offset, int *depth); - -/** - * fdt_first_subnode() - get offset of first direct subnode - * @fdt: FDT blob - * @offset: Offset of node to check - * - * Return: offset of first subnode, or -FDT_ERR_NOTFOUND if there is none - */ -int fdt_first_subnode(const void *fdt, int offset); - -/** - * fdt_next_subnode() - get offset of next direct subnode - * @fdt: FDT blob - * @offset: Offset of previous subnode - * - * After first calling fdt_first_subnode(), call this function repeatedly to - * get direct subnodes of a parent node. - * - * Return: offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more - * subnodes - */ -int fdt_next_subnode(const void *fdt, int offset); - -/** - * fdt_for_each_subnode - iterate over all subnodes of a parent - * - * @node: child node (int, lvalue) - * @fdt: FDT blob (const void *) - * @parent: parent node (int) - * - * This is actually a wrapper around a for loop and would be used like so: - * - * fdt_for_each_subnode(node, fdt, parent) { - * Use node - * ... - * } - * - * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { - * Error handling - * } - * - * Note that this is implemented as a macro and @node is used as - * iterator in the loop. The parent variable be constant or even a - * literal. - */ -#define fdt_for_each_subnode(node, fdt, parent) \ - for (node = fdt_first_subnode(fdt, parent); \ - node >= 0; \ - node = fdt_next_subnode(fdt, node)) - -/**********************************************************************/ -/* General functions */ -/**********************************************************************/ -#define fdt_get_header(fdt, field) \ - (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) -#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) -#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) -#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) -#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) -#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) -#define fdt_version(fdt) (fdt_get_header(fdt, version)) -#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) -#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) -#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) -#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) - -#define fdt_set_hdr_(name) \ - static inline void fdt_set_##name(void *fdt, uint32_t val) \ - { \ - struct fdt_header *fdth = (struct fdt_header *)fdt; \ - fdth->name = cpu_to_fdt32(val); \ - } -fdt_set_hdr_(magic) -fdt_set_hdr_(totalsize) -fdt_set_hdr_(off_dt_struct) -fdt_set_hdr_(off_dt_strings) -fdt_set_hdr_(off_mem_rsvmap) -fdt_set_hdr_(version) -fdt_set_hdr_(last_comp_version) -fdt_set_hdr_(boot_cpuid_phys) -fdt_set_hdr_(size_dt_strings) -fdt_set_hdr_(size_dt_struct) -#undef fdt_set_hdr_ - -/** - * fdt_header_size - return the size of the tree's header - * @fdt: pointer to a flattened device tree - * - * Return: size of DTB header in bytes - */ -size_t fdt_header_size(const void *fdt); - -/** - * fdt_header_size_ - internal function to get header size from a version number - * @version: device tree version number - * - * Return: size of DTB header in bytes - */ -size_t fdt_header_size_(uint32_t version); - -/** - * fdt_check_header - sanity check a device tree header - * @fdt: pointer to data which might be a flattened device tree - * - * fdt_check_header() checks that the given buffer contains what - * appears to be a flattened device tree, and that the header contains - * valid information (to the extent that can be determined from the - * header alone). - * - * returns: - * 0, if the buffer appears to contain a valid device tree - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_TRUNCATED, standard meanings, as above - */ -int fdt_check_header(const void *fdt); - -/** - * fdt_move - move a device tree around in memory - * @fdt: pointer to the device tree to move - * @buf: pointer to memory where the device is to be moved - * @bufsize: size of the memory space at buf - * - * fdt_move() relocates, if possible, the device tree blob located at - * fdt to the buffer at buf of size bufsize. The buffer may overlap - * with the existing device tree blob at fdt. Therefore, - * fdt_move(fdt, fdt, fdt_totalsize(fdt)) - * should always succeed. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_move(const void *fdt, void *buf, int bufsize); - -/**********************************************************************/ -/* Read-only functions */ -/**********************************************************************/ - -/** - * fdt_check_full - check device tree validity - * @fdt: pointer to the device tree blob - * @bufsize: size of the buffer containing the device tree - * - * fdt_check_full() checks that the given buffer contains a valid - * flattened device tree and that the tree structure is internally - * consistent. This is a more thorough check than fdt_check_header(). - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_check_full(const void *fdt, size_t bufsize); - -/** - * fdt_get_string - retrieve a string from the strings block of a device tree - * @fdt: pointer to the device tree blob - * @stroffset: offset of the string within the strings block (native endian) - * @lenp: optional pointer to return the string's length - * - * fdt_get_string() retrieves a pointer to a single string from the - * strings block of the device tree blob at fdt, and optionally also - * returns the string's length in *lenp. - * - * returns: - * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string - */ -const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); - -/** - * fdt_string - retrieve a string from the strings block of a device tree - * @fdt: pointer to the device tree blob - * @stroffset: offset of the string within the strings block (native endian) - * - * fdt_string() retrieves a pointer to a single string from the - * strings block of the device tree blob at fdt. - * - * returns: - * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string - */ -const char *fdt_string(const void *fdt, int stroffset); - -/** - * fdt_find_max_phandle - find and return the highest phandle in a tree - * @fdt: pointer to the device tree blob - * @phandle: return location for the highest phandle value found in the tree - * - * fdt_find_max_phandle() finds the highest phandle value in the given device - * tree. The value returned in @phandle is only valid if the function returns - * success. - * - * returns: - * 0 on success or a negative error code on failure - */ -int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); - -/** - * fdt_get_max_phandle - retrieves the highest phandle in a tree - * @fdt: pointer to the device tree blob - * - * fdt_get_max_phandle retrieves the highest phandle in the given - * device tree. This will ignore badly formatted phandles, or phandles - * with a value of 0 or -1. - * - * This function is deprecated in favour of fdt_find_max_phandle(). - * - * returns: - * the highest phandle on success - * 0, if no phandle was found in the device tree - * -1, if an error occurred - */ -static inline uint32_t fdt_get_max_phandle(const void *fdt) -{ - uint32_t phandle; - int err; - - err = fdt_find_max_phandle(fdt, &phandle); - if (err < 0) - return (uint32_t)-1; - - return phandle; -} - -/** - * fdt_generate_phandle - return a new, unused phandle for a device tree blob - * @fdt: pointer to the device tree blob - * @phandle: return location for the new phandle - * - * Walks the device tree blob and looks for the highest phandle value. On - * success, the new, unused phandle value (one higher than the previously - * highest phandle value in the device tree blob) will be returned in the - * @phandle parameter. - * - * Return: 0 on success or a negative error-code on failure - */ -int fdt_generate_phandle(const void *fdt, uint32_t *phandle); - -/** - * fdt_num_mem_rsv - retrieve the number of memory reserve map entries - * @fdt: pointer to the device tree blob - * - * Returns the number of entries in the device tree blob's memory - * reservation map. This does not include the terminating 0,0 entry - * or any other (0,0) entries reserved for expansion. - * - * returns: - * the number of entries - */ -int fdt_num_mem_rsv(const void *fdt); - -/** - * fdt_get_mem_rsv - retrieve one memory reserve map entry - * @fdt: pointer to the device tree blob - * @n: index of reserve map entry - * @address: pointer to 64-bit variable to hold the start address - * @size: pointer to 64-bit variable to hold the size of the entry - * - * On success, @address and @size will contain the address and size of - * the n-th reserve map entry from the device tree blob, in - * native-endian format. - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); - -/** - * fdt_subnode_offset_namelen - find a subnode based on substring - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * @namelen: number of characters of name to consider - * - * Identical to fdt_subnode_offset(), but only examine the first - * namelen characters of name for matching the subnode name. This is - * useful for finding subnodes based on a portion of a larger string, - * such as a full path. - * - * Return: offset of the subnode or -FDT_ERR_NOTFOUND if name not found. - */ -#ifndef SWIG /* Not available in Python */ -int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, - const char *name, int namelen); -#endif -/** - * fdt_subnode_offset - find a subnode of a given node - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * - * fdt_subnode_offset() finds a subnode of the node at structure block - * offset parentoffset with the given name. name may include a unit - * address, in which case fdt_subnode_offset() will find the subnode - * with that unit address, or the unit address may be omitted, in - * which case fdt_subnode_offset() will find an arbitrary subnode - * whose name excluding unit address matches the given name. - * - * returns: - * structure block offset of the requested subnode (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested subnode does not exist - * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); - -/** - * fdt_path_offset_namelen - find a tree node by its full path - * @fdt: pointer to the device tree blob - * @path: full path of the node to locate - * @namelen: number of characters of path to consider - * - * Identical to fdt_path_offset(), but only consider the first namelen - * characters of path as the path name. - * - * Return: offset of the node or negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); -#endif - -/** - * fdt_path_offset - find a tree node by its full path - * @fdt: pointer to the device tree blob - * @path: full path of the node to locate - * - * fdt_path_offset() finds a node of a given path in the device tree. - * Each path component may omit the unit address portion, but the - * results of this are undefined if any such path component is - * ambiguous (that is if there are multiple nodes at the relevant - * level matching the given component, differentiated only by unit - * address). - * - * If the path is not absolute (i.e. does not begin with '/'), the - * first component is treated as an alias. That is, the property by - * that name is looked up in the /aliases node, and the value of that - * property used in place of that first component. - * - * For example, for this small fragment - * - * / { - * aliases { - * i2c2 = &foo; // RHS compiles to "/soc@0/i2c@30a40000/eeprom@52" - * }; - * soc@0 { - * foo: i2c@30a40000 { - * bar: eeprom@52 { - * }; - * }; - * }; - * }; - * - * these would be equivalent: - * - * /soc@0/i2c@30a40000/eeprom@52 - * i2c2/eeprom@52 - * - * returns: - * structure block offset of the node with the requested path (>=0), on - * success - * -FDT_ERR_BADPATH, given path does not begin with '/' and the first - * component is not a valid alias - * -FDT_ERR_NOTFOUND, if the requested node does not exist - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_path_offset(const void *fdt, const char *path); - -/** - * fdt_get_name - retrieve the name of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of the starting node - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_name() retrieves the name (including unit address) of the - * device tree node at structure block offset nodeoffset. If lenp is - * non-NULL, the length of this name is also returned, in the integer - * pointed to by lenp. - * - * returns: - * pointer to the node's name, on success - * If lenp is non-NULL, *lenp contains the length of that name - * (>=0) - * NULL, on error - * if lenp is non-NULL *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); - -/** - * fdt_first_property_offset - find the offset of a node's first property - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of a node - * - * fdt_first_property_offset() finds the first property of the node at - * the given structure block offset. - * - * returns: - * structure block offset of the property (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested node has no properties - * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_first_property_offset(const void *fdt, int nodeoffset); - -/** - * fdt_next_property_offset - step through a node's properties - * @fdt: pointer to the device tree blob - * @offset: structure block offset of a property - * - * fdt_next_property_offset() finds the property immediately after the - * one at the given structure block offset. This will be a property - * of the same node as the given property. - * - * returns: - * structure block offset of the next property (>=0), on success - * -FDT_ERR_NOTFOUND, if the given property is the last in its node - * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_next_property_offset(const void *fdt, int offset); - -/** - * fdt_for_each_property_offset - iterate over all properties of a node - * - * @property: property offset (int, lvalue) - * @fdt: FDT blob (const void *) - * @node: node offset (int) - * - * This is actually a wrapper around a for loop and would be used like so: - * - * fdt_for_each_property_offset(property, fdt, node) { - * Use property - * ... - * } - * - * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { - * Error handling - * } - * - * Note that this is implemented as a macro and property is used as - * iterator in the loop. The node variable can be constant or even a - * literal. - */ -#define fdt_for_each_property_offset(property, fdt, node) \ - for (property = fdt_first_property_offset(fdt, node); \ - property >= 0; \ - property = fdt_next_property_offset(fdt, property)) - -/** - * fdt_get_property_by_offset - retrieve the property at a given offset - * @fdt: pointer to the device tree blob - * @offset: offset of the property to retrieve - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_property_by_offset() retrieves a pointer to the - * fdt_property structure within the device tree blob at the given - * offset. If lenp is non-NULL, the length of the property value is - * also returned, in the integer pointed to by lenp. - * - * Note that this code only works on device tree versions >= 16. fdt_getprop() - * works on all versions. - * - * returns: - * pointer to the structure representing the property - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const struct fdt_property *fdt_get_property_by_offset(const void *fdt, - int offset, - int *lenp); -static inline struct fdt_property *fdt_get_property_by_offset_w(void *fdt, - int offset, - int *lenp) -{ - return (struct fdt_property *)(uintptr_t) - fdt_get_property_by_offset(fdt, offset, lenp); -} - -/** - * fdt_get_property_namelen - find a property based on substring - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @namelen: number of characters of name to consider - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * Identical to fdt_get_property(), but only examine the first namelen - * characters of name for matching the property name. - * - * Return: pointer to the structure representing the property, or NULL - * if not found - */ -#ifndef SWIG /* Not available in Python */ -const struct fdt_property *fdt_get_property_namelen(const void *fdt, - int nodeoffset, - const char *name, - int namelen, int *lenp); -static inline struct fdt_property * -fdt_get_property_namelen_w(void *fdt, int nodeoffset, const char *name, - int namelen, int *lenp) -{ - return (struct fdt_property *)(uintptr_t)fdt_get_property_namelen( - fdt, nodeoffset, name, namelen, lenp); -} -#endif - -/** - * fdt_get_property - find a given property in a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_property() retrieves a pointer to the fdt_property - * structure within the device tree blob corresponding to the property - * named 'name' of the node at offset nodeoffset. If lenp is - * non-NULL, the length of the property value is also returned, in the - * integer pointed to by lenp. - * - * returns: - * pointer to the structure representing the property - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_NOTFOUND, node does not have named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, - const char *name, int *lenp); -static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, - const char *name, - int *lenp) -{ - return (struct fdt_property *)(uintptr_t) - fdt_get_property(fdt, nodeoffset, name, lenp); -} - -/** - * fdt_getprop_by_offset - retrieve the value of a property at a given offset - * @fdt: pointer to the device tree blob - * @offset: offset of the property to read - * @namep: pointer to a string variable (will be overwritten) or NULL - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_getprop_by_offset() retrieves a pointer to the value of the - * property at structure block offset 'offset' (this will be a pointer - * to within the device blob itself, not a copy of the value). If - * lenp is non-NULL, the length of the property value is also - * returned, in the integer pointed to by lenp. If namep is non-NULL, - * the property's name will also be returned in the char * pointed to - * by namep (this will be a pointer to within the device tree's string - * block, not a new copy of the name). - * - * returns: - * pointer to the property's value - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * if namep is non-NULL *namep contains a pointer to the property - * name. - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#ifndef SWIG /* This function is not useful in Python */ -const void *fdt_getprop_by_offset(const void *fdt, int offset, - const char **namep, int *lenp); -#endif - -/** - * fdt_getprop_namelen - get property value based on substring - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @namelen: number of characters of name to consider - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * Identical to fdt_getprop(), but only examine the first namelen - * characters of name for matching the property name. - * - * Return: pointer to the property's value or NULL on error - */ -#ifndef SWIG /* Not available in Python */ -const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, - const char *name, int namelen, int *lenp); -static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, - const char *name, int namelen, - int *lenp) -{ - return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, - namelen, lenp); -} -#endif - -/** - * fdt_getprop - retrieve the value of a given property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_getprop() retrieves a pointer to the value of the property - * named @name of the node at offset @nodeoffset (this will be a - * pointer to within the device blob itself, not a copy of the value). - * If @lenp is non-NULL, the length of the property value is also - * returned, in the integer pointed to by @lenp. - * - * returns: - * pointer to the property's value - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_NOTFOUND, node does not have named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const void *fdt_getprop(const void *fdt, int nodeoffset, - const char *name, int *lenp); -static inline void *fdt_getprop_w(void *fdt, int nodeoffset, - const char *name, int *lenp) -{ - return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); -} - -/** - * fdt_get_phandle - retrieve the phandle of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of the node - * - * fdt_get_phandle() retrieves the phandle of the device tree node at - * structure block offset nodeoffset. - * - * returns: - * the phandle of the node at nodeoffset, on success (!= 0, != -1) - * 0, if the node has no phandle, or another error occurs - */ -uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); - -/** - * fdt_get_alias_namelen - get alias based on substring - * @fdt: pointer to the device tree blob - * @name: name of the alias to look up - * @namelen: number of characters of name to consider - * - * Identical to fdt_get_alias(), but only examine the first @namelen - * characters of @name for matching the alias name. - * - * Return: a pointer to the expansion of the alias named @name, if it exists, - * NULL otherwise - */ -#ifndef SWIG /* Not available in Python */ -const char *fdt_get_alias_namelen(const void *fdt, - const char *name, int namelen); -#endif - -/** - * fdt_get_alias - retrieve the path referenced by a given alias - * @fdt: pointer to the device tree blob - * @name: name of the alias to look up - * - * fdt_get_alias() retrieves the value of a given alias. That is, the - * value of the property named @name in the node /aliases. - * - * returns: - * a pointer to the expansion of the alias named 'name', if it exists - * NULL, if the given alias or the /aliases node does not exist - */ -const char *fdt_get_alias(const void *fdt, const char *name); - -/** - * fdt_get_symbol_namelen - get symbol based on substring - * @fdt: pointer to the device tree blob - * @name: name of the symbol to look up - * @namelen: number of characters of name to consider - * - * Identical to fdt_get_symbol(), but only examine the first @namelen - * characters of @name for matching the symbol name. - * - * Return: a pointer to the expansion of the symbol named @name, if it exists, - * NULL otherwise - */ -#ifndef SWIG /* Not available in Python */ -const char *fdt_get_symbol_namelen(const void *fdt, - const char *name, int namelen); -#endif - -/** - * fdt_get_symbol - retrieve the path referenced by a given symbol - * @fdt: pointer to the device tree blob - * @name: name of the symbol to look up - * - * fdt_get_symbol() retrieves the value of a given symbol. That is, - * the value of the property named @name in the node - * /__symbols__. Such a node exists only for a device tree blob that - * has been compiled with the -@ dtc option. Each property corresponds - * to a label appearing in the device tree source, with the name of - * the property being the label and the value being the full path of - * the node it is attached to. - * - * returns: - * a pointer to the expansion of the symbol named 'name', if it exists - * NULL, if the given symbol or the /__symbols__ node does not exist - */ -const char *fdt_get_symbol(const void *fdt, const char *name); - -/** - * fdt_get_path - determine the full path of a node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose path to find - * @buf: character buffer to contain the returned path (will be overwritten) - * @buflen: size of the character buffer at buf - * - * fdt_get_path() computes the full path of the node at offset - * nodeoffset, and records that path in the buffer at buf. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * 0, on success - * buf contains the absolute path of the node at - * nodeoffset, as a NUL-terminated string. - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) - * characters and will not fit in the given buffer. - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); - -/** - * fdt_supernode_atdepth_offset - find a specific ancestor of a node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * @supernodedepth: depth of the ancestor to find - * @nodedepth: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_supernode_atdepth_offset() finds an ancestor of the given node - * at a specific depth from the root (where the root itself has depth - * 0, its immediate subnodes depth 1 and so forth). So - * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); - * will always return 0, the offset of the root node. If the node at - * nodeoffset has depth D, then: - * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); - * will return nodeoffset itself. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * structure block offset of the node at node offset's ancestor - * of depth supernodedepth (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of - * nodeoffset - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, - int supernodedepth, int *nodedepth); - -/** - * fdt_node_depth - find the depth of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * - * fdt_node_depth() finds the depth of a given node. The root node - * has depth 0, its immediate subnodes depth 1 and so forth. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * depth of the node at nodeoffset (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_depth(const void *fdt, int nodeoffset); - -/** - * fdt_parent_offset - find the parent of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * - * fdt_parent_offset() locates the parent node of a given node (that - * is, it finds the offset of the node which contains the node at - * nodeoffset as a subnode). - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset, *twice*. - * - * returns: - * structure block offset of the parent of the node at nodeoffset - * (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_parent_offset(const void *fdt, int nodeoffset); - -/** - * fdt_node_offset_by_prop_value - find nodes with a given property value - * @fdt: pointer to the device tree blob - * @startoffset: only find nodes after this offset - * @propname: property name to check - * @propval: property value to search for - * @proplen: length of the value in propval - * - * fdt_node_offset_by_prop_value() returns the offset of the first - * node after startoffset, which has a property named propname whose - * value is of length proplen and has value equal to propval; or if - * startoffset is -1, the very first such node in the tree. - * - * To iterate through all nodes matching the criterion, the following - * idiom can be used: - * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, - * propval, proplen); - * while (offset != -FDT_ERR_NOTFOUND) { - * // other code here - * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, - * propval, proplen); - * } - * - * Note the -1 in the first call to the function, if 0 is used here - * instead, the function will never locate the root node, even if it - * matches the criterion. - * - * returns: - * structure block offset of the located node (>= 0, >startoffset), - * on success - * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the - * tree after startoffset - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, - const char *propname, - const void *propval, int proplen); - -/** - * fdt_node_offset_by_phandle - find the node with a given phandle - * @fdt: pointer to the device tree blob - * @phandle: phandle value - * - * fdt_node_offset_by_phandle() returns the offset of the node - * which has the given phandle value. If there is more than one node - * in the tree with the given phandle (an invalid tree), results are - * undefined. - * - * returns: - * structure block offset of the located node (>= 0), on success - * -FDT_ERR_NOTFOUND, no node with that phandle exists - * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); - -/** - * fdt_node_check_compatible - check a node's compatible property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @compatible: string to match against - * - * fdt_node_check_compatible() returns 0 if the given node contains a - * @compatible property with the given string as one of its elements, - * it returns non-zero otherwise, or on error. - * - * returns: - * 0, if the node has a 'compatible' property listing the given string - * 1, if the node has a 'compatible' property, but it does not list - * the given string - * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property - * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_check_compatible(const void *fdt, int nodeoffset, - const char *compatible); - -/** - * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value - * @fdt: pointer to the device tree blob - * @startoffset: only find nodes after this offset - * @compatible: 'compatible' string to match against - * - * fdt_node_offset_by_compatible() returns the offset of the first - * node after startoffset, which has a 'compatible' property which - * lists the given compatible string; or if startoffset is -1, the - * very first such node in the tree. - * - * To iterate through all nodes matching the criterion, the following - * idiom can be used: - * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); - * while (offset != -FDT_ERR_NOTFOUND) { - * // other code here - * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); - * } - * - * Note the -1 in the first call to the function, if 0 is used here - * instead, the function will never locate the root node, even if it - * matches the criterion. - * - * returns: - * structure block offset of the located node (>= 0, >startoffset), - * on success - * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the - * tree after startoffset - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_compatible(const void *fdt, int startoffset, - const char *compatible); - -/** - * fdt_stringlist_contains - check a string list property for a string - * @strlist: Property containing a list of strings to check - * @listlen: Length of property - * @str: String to search for - * - * This is a utility function provided for convenience. The list contains - * one or more strings, each terminated by \0, as is found in a device tree - * "compatible" property. - * - * Return: 1 if the string is found in the list, 0 not found, or invalid list - */ -int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); - -/** - * fdt_stringlist_count - count the number of strings in a string list - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * - * Return: - * the number of strings in the given property - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist - */ -int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); - -/** - * fdt_stringlist_search - find a string in a string list and return its index - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * @string: string to look up in the string list - * - * Note that it is possible for this function to succeed on property values - * that are not NUL-terminated. That's because the function will stop after - * finding the first occurrence of @string. This can for example happen with - * small-valued cell properties, such as #address-cells, when searching for - * the empty string. - * - * return: - * the index of the string in the list of strings - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist or does not contain - * the given string - */ -int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, - const char *string); - -/** - * fdt_stringlist_get() - obtain the string at a given index in a string list - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * @index: index of the string to return - * @lenp: return location for the string length or an error code on failure - * - * Note that this will successfully extract strings from properties with - * non-NUL-terminated values. For example on small-valued cell properties - * this function will return the empty string. - * - * If non-NULL, the length of the string (on success) or a negative error-code - * (on failure) will be stored in the integer pointer to by lenp. - * - * Return: - * A pointer to the string at the given index in the string list or NULL on - * failure. On success the length of the string will be stored in the memory - * location pointed to by the lenp parameter, if non-NULL. On failure one of - * the following negative error codes will be returned in the lenp parameter - * (if non-NULL): - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist - */ -const char *fdt_stringlist_get(const void *fdt, int nodeoffset, - const char *property, int index, - int *lenp); - -/**********************************************************************/ -/* Read-only functions (addressing related) */ -/**********************************************************************/ - -/** - * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells - * - * This is the maximum value for #address-cells, #size-cells and - * similar properties that will be processed by libfdt. IEE1275 - * requires that OF implementations handle values up to 4. - * Implementations may support larger values, but in practice higher - * values aren't used. - */ -#define FDT_MAX_NCELLS 4 - -/** - * fdt_address_cells - retrieve address size for a bus represented in the tree - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to find the address size for - * - * When the node has a valid #address-cells property, returns its value. - * - * returns: - * 0 <= n < FDT_MAX_NCELLS, on success - * 2, if the node has no #address-cells property - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #address-cells property - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_address_cells(const void *fdt, int nodeoffset); - -/** - * fdt_size_cells - retrieve address range size for a bus represented in the - * tree - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to find the address range size for - * - * When the node has a valid #size-cells property, returns its value. - * - * returns: - * 0 <= n < FDT_MAX_NCELLS, on success - * 1, if the node has no #size-cells property - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #size-cells property - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_size_cells(const void *fdt, int nodeoffset); - - -/**********************************************************************/ -/* Write-in-place functions */ -/**********************************************************************/ - -/** - * fdt_setprop_inplace_namelen_partial - change a property's value, - * but not its size - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: number of characters of name to consider - * @idx: index of the property to change in the array - * @val: pointer to data to replace the property value with - * @len: length of the property value - * - * Identical to fdt_setprop_inplace(), but modifies the given property - * starting from the given index, and using only the first characters - * of the name. It is useful when you want to manipulate only one value of - * an array and you have a string that doesn't end with \0. - * - * Return: 0 on success, negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, - const char *name, int namelen, - uint32_t idx, const void *val, - int len); -#endif - -/** - * fdt_setprop_inplace - change a property's value, but not its size - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: pointer to data to replace the property value with - * @len: length of the property value - * - * fdt_setprop_inplace() replaces the value of a given property with - * the data in val, of length len. This function cannot change the - * size of a property, and so will only work if len is equal to the - * current length of the property. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if len is not equal to the property's current length - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#ifndef SWIG /* Not available in Python */ -int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, - const void *val, int len); -#endif - -/** - * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to replace the property with - * - * fdt_setprop_inplace_u32() replaces the value of a given property - * with the 32-bit integer value in val, converting val to big-endian - * if necessary. This function cannot change the size of a property, - * and so will only work if the property already exists and has length - * 4. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value to replace the property with - * - * fdt_setprop_inplace_u64() replaces the value of a given property - * with the 64-bit integer value in val, converting val to big-endian - * if necessary. This function cannot change the size of a property, - * and so will only work if the property already exists and has length - * 8. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, - const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_inplace_cell - change the value of a single-cell property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node containing the property - * @name: name of the property to change the value of - * @val: new value of the 32-bit cell - * - * This is an alternative name for fdt_setprop_inplace_u32() - * Return: 0 on success, negative libfdt error number otherwise. - */ -static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_nop_property - replace a property with nop tags - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to nop - * @name: name of the property to nop - * - * fdt_nop_property() will replace a given property's representation - * in the blob with FDT_NOP tags, effectively removing it from the - * tree. - * - * This function will alter only the bytes in the blob which contain - * the property, and will not alter or move any other part of the - * tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_nop_property(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_nop_node - replace a node (subtree) with nop tags - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to nop - * - * fdt_nop_node() will replace a given node's representation in the - * blob, including all its subnodes, if any, with FDT_NOP tags, - * effectively removing it from the tree. - * - * This function will alter only the bytes in the blob which contain - * the node and its properties and subnodes, and will not alter or - * move any other part of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_nop_node(void *fdt, int nodeoffset); - -/**********************************************************************/ -/* Sequential write functions */ -/**********************************************************************/ - -/* fdt_create_with_flags flags */ -#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 - /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property - * names in the fdt. This can result in faster creation times, but - * a larger fdt. */ - -#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) - -/** - * fdt_create_with_flags - begin creation of a new fdt - * @buf: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. - * - * fdt_create_with_flags() begins the process of creating a new fdt with - * the sequential write interface. - * - * fdt creation process must end with fdt_finish() to produce a valid fdt. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - * -FDT_ERR_BADFLAGS, flags is not valid - */ -int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); - -/** - * fdt_create - begin creation of a new fdt - * @buf: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * - * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - */ -int fdt_create(void *buf, int bufsize); - -/** - * fdt_resize - move and resize a device tree in sequential write state - * @fdt: Pointer to the device tree to resize - * @buf: Buffer where resized tree should be placed - * @bufsize: Size of the buffer at @buf - * - * fdt_resize() moves the device tree blob from @fdt to @buf and - * resizes it to fit in the new buffer size. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_resize(void *fdt, void *buf, int bufsize); - -/** - * fdt_add_reservemap_entry - add an entry to the memory reserve map - * @fdt: Pointer to the device tree blob - * @addr: Start address of the reserve map entry - * @size: Size of the reserved region - * - * fdt_add_reservemap_entry() adds a memory reserve map entry to the - * device tree blob during the sequential write process. This function - * can only be called after fdt_create() and before fdt_finish_reservemap(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); - -/** - * fdt_finish_reservemap - complete the memory reserve map - * @fdt: Pointer to the device tree blob - * - * fdt_finish_reservemap() completes the memory reserve map section - * of the device tree blob during sequential write. After calling this - * function, no more reserve map entries can be added and the blob - * moves to the structure creation phase. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_finish_reservemap(void *fdt); - -/** - * fdt_begin_node - start creation of a new node - * @fdt: Pointer to the device tree blob - * @name: Name of the node to create - * - * fdt_begin_node() starts the creation of a new node with the given - * @name during sequential write. After calling this function, properties - * can be added with fdt_property() and subnodes can be created with - * additional fdt_begin_node() calls. The node must be completed with - * fdt_end_node(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_begin_node(void *fdt, const char *name); - -/** - * fdt_property - add a property to the current node - * @fdt: Pointer to the device tree blob - * @name: Name of the property to add - * @val: Pointer to the property value - * @len: Length of the property value in bytes - * - * fdt_property() adds a property with the given @name and value to - * the current node during sequential write. This function can only - * be called between fdt_begin_node() and fdt_end_node(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not currently within a node - */ -int fdt_property(void *fdt, const char *name, const void *val, int len); -static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_property(fdt, name, &tmp, sizeof(tmp)); -} -static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_property(fdt, name, &tmp, sizeof(tmp)); -} - -#ifndef SWIG /* Not available in Python */ -static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) -{ - return fdt_property_u32(fdt, name, val); -} -#endif - -/** - * fdt_property_placeholder - add a new property and return a ptr to its value - * - * @fdt: pointer to the device tree blob - * @name: name of property to add - * @len: length of property value in bytes - * @valp: returns a pointer to where the value should be placed - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_NOSPACE, standard meanings - */ -int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); - -#define fdt_property_string(fdt, name, str) \ - fdt_property(fdt, name, str, strlen(str)+1) - -/** - * fdt_end_node - complete the current node - * @fdt: Pointer to the device tree blob - * - * fdt_end_node() completes the current node during sequential write. This - * function must be called to close each node started with - * fdt_begin_node(). After calling this function, no more properties or subnodes - * can be added to the node. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if not currently within a node - */ -int fdt_end_node(void *fdt); - -/** - * fdt_finish - complete device tree creation - * @fdt: Pointer to the device tree blob - * - * fdt_finish() completes the device tree creation process started with - * fdt_create(). This function finalizes the device tree blob and makes it ready - * for use. After calling this function, the blob is complete and can be used - * with libfdt read-only and read-write functions, but not with sequential write - * functions. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if the sequential write process is incomplete - */ -int fdt_finish(void *fdt); - -/**********************************************************************/ -/* Read-write functions */ -/**********************************************************************/ - -/** - * fdt_create_empty_tree - create an empty device tree - * @buf: Buffer where the empty tree should be created - * @bufsize: Size of the buffer at @buf - * - * fdt_create_empty_tree() creates a minimal empty device tree blob - * in the given buffer. The tree contains only a root node with no - * properties or subnodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small for even an empty tree - */ -int fdt_create_empty_tree(void *buf, int bufsize); - -/** - * fdt_open_into - move a device tree into a new buffer and make editable - * @fdt: Pointer to the device tree to move - * @buf: Buffer where the editable tree should be placed - * @bufsize: Size of the buffer at @buf - * - * fdt_open_into() moves and reorganizes the device tree blob from @fdt - * into @buf, converting it to a format suitable for read-write operations. - * The new buffer should allow space for modifications. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_open_into(const void *fdt, void *buf, int bufsize); - -/** - * fdt_pack - pack a device tree blob - * @fdt: Pointer to the device tree blob - * - * fdt_pack() reorganizes the device tree blob to eliminate any free space - * and pack it into the minimum possible size. This is useful after making - * modifications that might have left gaps in the blob. - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, standard meanings - */ -int fdt_pack(void *fdt); - -/** - * fdt_add_mem_rsv - add one memory reserve map entry - * @fdt: pointer to the device tree blob - * @address: 64-bit start address of the reserve map entry - * @size: 64-bit size of the reserved region - * - * Adds a reserve map entry to the given blob reserving a region at - * address address of length size. - * - * This function will insert data into the reserve map and will - * therefore change the indexes of some entries in the table. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new reservation entry - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); - -/** - * fdt_del_mem_rsv - remove a memory reserve map entry - * @fdt: pointer to the device tree blob - * @n: entry to remove - * - * fdt_del_mem_rsv() removes the n-th memory reserve map entry from - * the blob. - * - * This function will delete data from the reservation table and will - * therefore change the indexes of some entries in the table. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there - * are less than n+1 reserve map entries) - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_del_mem_rsv(void *fdt, int n); - -/** - * fdt_set_name - change the name of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of a node - * @name: name to give the node - * - * fdt_set_name() replaces the name (including unit address, if any) - * of the given node with the given string. NOTE: this function can't - * efficiently check if the new name is unique amongst the given - * node's siblings; results are undefined if this function is invoked - * with a name equal to one of the given node's siblings. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob - * to contain the new name - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_set_name(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_setprop_namelen - create or change a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: length of the name - * @val: pointer to data to set the property value to - * @len: length of the property value - * - * fdt_setprop_namelen() sets the value of the named property in the given - * node to the given value and length, creating the property if it - * does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, const void *val, int len); - -/** - * fdt_setprop - create or change a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: pointer to data to set the property value to - * @len: length of the property value - * - * fdt_setprop() sets the value of the named property in the given - * node to the given value and length, creating the property if it - * does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop(void *fdt, int nodeoffset, const char *name, - const void *val, int len) -{ - return fdt_setprop_namelen(fdt, nodeoffset, name, strlen(name), val, - len); -} - -/** - * fdt_setprop_placeholder_namelen - allocate space for a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: length of the name - * @len: length of the property value - * @prop_data: return pointer to property data - * - * fdt_setprop_placeholder_namelen() allocates the named property in the given node. - * If the property exists it is resized. In either case a pointer to the - * property data is returned. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, int len, void **prop_data); - -/** - * fdt_setprop_placeholder - allocate space for a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @len: length of the property value - * @prop_data: return pointer to property data - * - * fdt_setprop_placeholder() allocates the named property in the given node. - * If the property exists it is resized. In either case a pointer to the - * property data is returned. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_placeholder(void *fdt, int nodeoffset, - const char *name, int len, - void **prop_data) -{ - return fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, - strlen(name), len, prop_data); -} - -/** - * fdt_setprop_u32 - set a property to a 32-bit integer - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value for the property (native endian) - * - * fdt_setprop_u32() sets the value of the named property in the given - * node to the given 32-bit integer value (converting to big-endian if - * necessary), or creates a new property with that value if it does - * not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, - uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_u64 - set a property to a 64-bit integer - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value for the property (native endian) - * - * fdt_setprop_u64() sets the value of the named property in the given - * node to the given 64-bit integer value (converting to big-endian if - * necessary), or creates a new property with that value if it does - * not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, - uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_cell - set a property to a single cell value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value for the property (native endian) - * - * This is an alternative name for fdt_setprop_u32() - * - * Return: 0 on success, negative libfdt error value otherwise. - */ -static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, - uint32_t val) -{ - return fdt_setprop_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_setprop_string - set a property to a string value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @str: string value for the property - * - * fdt_setprop_string() sets the value of the named property in the - * given node to the given string value (using the length of the - * string to determine the new length of the property), or creates a - * new property with that value if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_string(fdt, nodeoffset, name, str) \ - fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) - -/** - * fdt_setprop_namelen_string - set a property to a string value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: number of characters of name to consider - * @str: string value for the property - * - * fdt_setprop_namelen_string() sets the value of the named property in the - * given node to the given string value (using the length of the - * string to determine the new length of the property), or creates a - * new property with that value if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_namelen_string(fdt, nodeoffset, name, namelen, str) \ - fdt_setprop_namelen((fdt), (nodeoffset), (name), (namelen), (str), \ - strlen(str) + 1) - -/** - * fdt_setprop_empty - set a property to an empty value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * - * fdt_setprop_empty() sets the value of the named property in the - * given node to an empty (zero length) value, or creates a new empty - * property if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_empty(fdt, nodeoffset, name) \ - fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) - -/** - * fdt_appendprop - append to or create a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to append to - * @val: pointer to data to append to the property value - * @len: length of the data to append to the property value - * - * fdt_appendprop() appends the value to the named property in the - * given node, creating the property if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_appendprop(void *fdt, int nodeoffset, const char *name, - const void *val, int len); - -/** - * fdt_appendprop_u32 - append a 32-bit integer value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to append to the property (native endian) - * - * fdt_appendprop_u32() appends the given 32-bit integer value - * (converting to big-endian if necessary) to the value of the named - * property in the given node, or creates a new property with that - * value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_appendprop_u64 - append a 64-bit integer value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value to append to the property (native endian) - * - * fdt_appendprop_u64() appends the given 64-bit integer value - * (converting to big-endian if necessary) to the value of the named - * property in the given node, or creates a new property with that - * value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, - const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_appendprop_cell - append a single cell value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to append to the property (native endian) - * - * This is an alternative name for fdt_appendprop_u32() - * - * Return: 0 on success, negative libfdt error value otherwise. - */ -static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - return fdt_appendprop_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_appendprop_string - append a string to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @str: string value to append to the property - * - * fdt_appendprop_string() appends the given string to the value of - * the named property in the given node, or creates a new property - * with that value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ - fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) - -/** - * fdt_appendprop_addrrange - append a address range property - * @fdt: pointer to the device tree blob - * @parent: offset of the parent node - * @nodeoffset: offset of the node to add a property at - * @name: name of property - * @addr: start address of a given range - * @size: size of a given range - * - * fdt_appendprop_addrrange() appends an address range value (start - * address and size) to the value of the named property in the given - * node, or creates a new property with that value if it does not - * already exist. - * - * Cell sizes are determined by parent's #address-cells and #size-cells. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #address-cells property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain a new property - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, - const char *name, uint64_t addr, uint64_t size); - -/** - * fdt_delprop - delete a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to nop - * @name: name of the property to nop - * - * fdt_delprop() will delete the given property. - * - * This function will delete data from the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_delprop(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_add_subnode_namelen - creates a new node based on substring - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to create - * @namelen: number of characters of name to consider - * - * Identical to fdt_add_subnode(), but use only the first @namelen - * characters of @name as the name of the new node. This is useful for - * creating subnodes based on a portion of a larger string, such as a - * full path. - * - * Return: structure block offset of the created subnode (>=0), - * negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_add_subnode_namelen(void *fdt, int parentoffset, - const char *name, int namelen); -#endif - -/** - * fdt_add_subnode - creates a new node - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * - * fdt_add_subnode() creates a new node as a subnode of the node at - * structure block offset parentoffset, with the given name (which - * should include the unit address, if any). - * - * This function will insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * structure block offset of the created subnode (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested subnode does not exist - * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE - * tag - * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of - * the given name - * -FDT_ERR_NOSPACE, if there is insufficient free space in the - * blob to contain the new node - * -FDT_ERR_NOSPACE - * -FDT_ERR_BADLAYOUT - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_add_subnode(void *fdt, int parentoffset, const char *name); - -/** - * fdt_del_node - delete a node (subtree) - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to nop - * - * fdt_del_node() will remove the given node, including all its - * subnodes if any, from the blob. - * - * This function will delete data from the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_del_node(void *fdt, int nodeoffset); - -/** - * fdt_overlay_apply - Applies a DT overlay on a base DT - * @fdt: pointer to the base device tree blob - * @fdto: pointer to the device tree overlay blob - * - * fdt_overlay_apply() will apply the given device tree overlay on the - * given base device tree. - * - * Expect the base device tree to be modified, even if the function - * returns an error. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there's not enough space in the base device tree - * -FDT_ERR_NOTFOUND, the overlay points to some nonexistent nodes or - * properties in the base DT - * -FDT_ERR_BADPHANDLE, - * -FDT_ERR_BADOVERLAY, - * -FDT_ERR_NOPHANDLES, - * -FDT_ERR_INTERNAL, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADOFFSET, - * -FDT_ERR_BADPATH, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_overlay_apply(void *fdt, void *fdto); - -/** - * fdt_overlay_target_offset - retrieves the offset of a fragment's target - * @fdt: Base device tree blob - * @fdto: Device tree overlay blob - * @fragment_offset: node offset of the fragment in the overlay - * @pathp: pointer which receives the path of the target (or NULL) - * - * fdt_overlay_target_offset() retrieves the target offset in the base - * device tree of a fragment, no matter how the actual targeting is - * done (through a phandle or a path) - * - * returns: - * the targeted node offset in the base device tree - * Negative error code on error - */ -int fdt_overlay_target_offset(const void *fdt, const void *fdto, - int fragment_offset, char const **pathp); - -/**********************************************************************/ -/* Debugging / informational functions */ -/**********************************************************************/ - -/** - * fdt_strerror - return string description of error code - * @errval: Error code returned by a libfdt function - * - * fdt_strerror() returns a string description of the error code passed - * in @errval. - * - * returns: - * pointer to a string describing the error code - */ -const char *fdt_strerror(int errval); - -#ifdef __cplusplus -} -#endif - -#endif /* LIBFDT_H */ diff --git a/na/scripts/dtc/libfdt/libfdt_env.h b/na/scripts/dtc/libfdt/libfdt_env.h deleted file mode 100644 index 5580b48..0000000 --- a/na/scripts/dtc/libfdt/libfdt_env.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_ENV_H -#define LIBFDT_ENV_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * Copyright 2012 Kim Phillips, Freescale Semiconductor. - */ - -#include -#include -#include -#include -#include -#include - -#ifdef __CHECKER__ -#define FDT_FORCE __attribute__((force)) -#define FDT_BITWISE __attribute__((bitwise)) -#else -#define FDT_FORCE -#define FDT_BITWISE -#endif - -typedef uint16_t FDT_BITWISE fdt16_t; -typedef uint32_t FDT_BITWISE fdt32_t; -typedef uint64_t FDT_BITWISE fdt64_t; - -#define EXTRACT_BYTE(x, n) ((unsigned long long)((uint8_t *)&x)[n]) -#define CPU_TO_FDT16(x) ((EXTRACT_BYTE(x, 0) << 8) | EXTRACT_BYTE(x, 1)) -#define CPU_TO_FDT32(x) ((EXTRACT_BYTE(x, 0) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \ - (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3)) -#define CPU_TO_FDT64(x) ((EXTRACT_BYTE(x, 0) << 56) | (EXTRACT_BYTE(x, 1) << 48) | \ - (EXTRACT_BYTE(x, 2) << 40) | (EXTRACT_BYTE(x, 3) << 32) | \ - (EXTRACT_BYTE(x, 4) << 24) | (EXTRACT_BYTE(x, 5) << 16) | \ - (EXTRACT_BYTE(x, 6) << 8) | EXTRACT_BYTE(x, 7)) - -static inline uint16_t fdt16_to_cpu(fdt16_t x) -{ - return (FDT_FORCE uint16_t)CPU_TO_FDT16(x); -} -static inline fdt16_t cpu_to_fdt16(uint16_t x) -{ - return (FDT_FORCE fdt16_t)CPU_TO_FDT16(x); -} - -static inline uint32_t fdt32_to_cpu(fdt32_t x) -{ - return (FDT_FORCE uint32_t)CPU_TO_FDT32(x); -} -static inline fdt32_t cpu_to_fdt32(uint32_t x) -{ - return (FDT_FORCE fdt32_t)CPU_TO_FDT32(x); -} - -static inline uint64_t fdt64_to_cpu(fdt64_t x) -{ - return (FDT_FORCE uint64_t)CPU_TO_FDT64(x); -} -static inline fdt64_t cpu_to_fdt64(uint64_t x) -{ - return (FDT_FORCE fdt64_t)CPU_TO_FDT64(x); -} -#undef CPU_TO_FDT64 -#undef CPU_TO_FDT32 -#undef CPU_TO_FDT16 -#undef EXTRACT_BYTE - -#endif /* LIBFDT_ENV_H */ diff --git a/na/scripts/dtc/libfdt/libfdt_internal.h b/na/scripts/dtc/libfdt/libfdt_internal.h deleted file mode 100644 index 0e103ca..0000000 --- a/na/scripts/dtc/libfdt/libfdt_internal.h +++ /dev/null @@ -1,202 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_INTERNAL_H -#define LIBFDT_INTERNAL_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include - -#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) -#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) - -int32_t fdt_ro_probe_(const void *fdt); -#define FDT_RO_PROBE(fdt) \ - { \ - if (!can_assume(VALID_DTB)) { \ - int32_t totalsize_; \ - if ((totalsize_ = fdt_ro_probe_(fdt)) < 0) \ - return totalsize_; \ - } \ - } - -int fdt_check_node_offset_(const void *fdt, int offset); -int fdt_check_prop_offset_(const void *fdt, int offset); - -const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, - int s_len); -static inline const char *fdt_find_string_(const char *strtab, int tabsize, - const char *s) -{ - return fdt_find_string_len_(strtab, tabsize, s, strlen(s)); -} - -int fdt_node_end_offset_(void *fdt, int nodeoffset); - -static inline const void *fdt_offset_ptr_(const void *fdt, int offset) -{ - return (const char *)fdt + fdt_off_dt_struct(fdt) + offset; -} - -static inline void *fdt_offset_ptr_w_(void *fdt, int offset) -{ - return (void *)(uintptr_t)fdt_offset_ptr_(fdt, offset); -} - -static inline const struct fdt_reserve_entry *fdt_mem_rsv_(const void *fdt, int n) -{ - const struct fdt_reserve_entry *rsv_table = - (const struct fdt_reserve_entry *) - ((const char *)fdt + fdt_off_mem_rsvmap(fdt)); - - return rsv_table + n; -} -static inline struct fdt_reserve_entry *fdt_mem_rsv_w_(void *fdt, int n) -{ - return (void *)(uintptr_t)fdt_mem_rsv_(fdt, n); -} - -/* - * Internal helpers to access structural elements of the device tree - * blob (rather than for example reading integers from within property - * values). We assume that we are either given a naturally aligned - * address for the platform or if we are not, we are on a platform - * where unaligned memory reads will be handled in a graceful manner. - * If not the external helpers fdtXX_ld() from libfdt.h can be used - * instead. - */ -static inline uint32_t fdt32_ld_(const fdt32_t *p) -{ - return fdt32_to_cpu(*p); -} - -static inline uint64_t fdt64_ld_(const fdt64_t *p) -{ - return fdt64_to_cpu(*p); -} - -#define FDT_SW_MAGIC (~FDT_MAGIC) - -/**********************************************************************/ -/* Checking controls */ -/**********************************************************************/ - -#ifndef FDT_ASSUME_MASK -#define FDT_ASSUME_MASK 0 -#endif - -/* - * Defines assumptions which can be enabled. Each of these can be enabled - * individually. For maximum safety, don't enable any assumptions! - * - * For minimal code size and no safety, use ASSUME_PERFECT at your own risk. - * You should have another method of validating the device tree, such as a - * signature or hash check before using libfdt. - * - * For situations where security is not a concern it may be safe to enable - * ASSUME_PERFECT. - */ -enum { - /* - * This does essentially no checks. Only the latest device-tree - * version is correctly handled. Inconsistencies or errors in the device - * tree may cause undefined behaviour or crashes. Invalid parameters - * passed to libfdt may do the same. - * - * If an error occurs when modifying the tree it may leave the tree in - * an intermediate (but valid) state. As an example, adding a property - * where there is insufficient space may result in the property name - * being added to the string table even though the property itself is - * not added to the struct section. - * - * Only use this if you have a fully validated device tree with - * the latest supported version and wish to minimise code size. - */ - ASSUME_PERFECT = 0xff, - - /* - * This assumes that the device tree is sane. i.e. header metadata - * and basic hierarchy are correct. - * - * With this assumption enabled, normal device trees produced by libfdt - * and the compiler should be handled safely. Malicious device trees and - * complete garbage may cause libfdt to behave badly or crash. Truncated - * device trees (e.g. those only partially loaded) can also cause - * problems. - * - * Note: Only checks that relate exclusively to the device tree itself - * (not the parameters passed to libfdt) are disabled by this - * assumption. This includes checking headers, tags and the like. - */ - ASSUME_VALID_DTB = 1 << 0, - - /* - * This builds on ASSUME_VALID_DTB and further assumes that libfdt - * functions are called with valid parameters, i.e. not trigger - * FDT_ERR_BADOFFSET or offsets that are out of bounds. It disables any - * extensive checking of parameters and the device tree, making various - * assumptions about correctness. - * - * It doesn't make sense to enable this assumption unless - * ASSUME_VALID_DTB is also enabled. - */ - ASSUME_VALID_INPUT = 1 << 1, - - /* - * This disables checks for device-tree version and removes all code - * which handles older versions. - * - * Only enable this if you know you have a device tree with the latest - * version. - */ - ASSUME_LATEST = 1 << 2, - - /* - * This assumes that it is OK for a failed addition to the device tree, - * due to lack of space or some other problem, to skip any rollback - * steps (such as dropping the property name from the string table). - * This is safe to enable in most circumstances, even though it may - * leave the tree in a sub-optimal state. - */ - ASSUME_NO_ROLLBACK = 1 << 3, - - /* - * This assumes that the device tree components appear in a 'convenient' - * order, i.e. the memory reservation block first, then the structure - * block and finally the string block. - * - * This order is not specified by the device-tree specification, - * but is expected by libfdt. The device-tree compiler always created - * device trees with this order. - * - * This assumption disables a check in fdt_open_into() and removes the - * ability to fix the problem there. This is safe if you know that the - * device tree is correctly ordered. See fdt_blocks_misordered_(). - */ - ASSUME_LIBFDT_ORDER = 1 << 4, - - /* - * This assumes that libfdt itself does not have any internal bugs. It - * drops certain checks that should never be needed unless libfdt has an - * undiscovered bug. - * - * This can generally be considered safe to enable. - */ - ASSUME_LIBFDT_FLAWLESS = 1 << 5, -}; - -/** - * can_assume_() - check if a particular assumption is enabled - * - * @mask: Mask to check (ASSUME_...) - * @return true if that assumption is enabled, else false - */ -static inline bool can_assume_(int mask) -{ - return FDT_ASSUME_MASK & mask; -} - -/** helper macros for checking assumptions */ -#define can_assume(_assume) can_assume_(ASSUME_ ## _assume) - -#endif /* LIBFDT_INTERNAL_H */ diff --git a/na/scripts/extract-asm-offsets.sed b/na/scripts/extract-asm-offsets.sed deleted file mode 100644 index c97ec2c..0000000 --- a/na/scripts/extract-asm-offsets.sed +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Extract `->SYM val COMMENT` markers emitted by inline asm in -# asm-offsets.c into proper `#define SYM val /* COMMENT */` lines. -# 1:1 with Linux scripts/Makefile.lib `sed-offsets`. -# -# Input : assembly source produced by `gcc -S asm-offsets.c` — -# inline asm emits lines like -# .ascii "->KERNEL_MAP_VIRT_ADDR 0 offsetof(...)" -# Output : header fragment for include/generated/asm-offsets.h -# -# Step 1 unwraps the `.ascii "..."` directive so the actual payload -# (starting with `->`) is exposed. Steps 2-4 then rewrite into a -# proper #define. -s:^[[:space:]]*\.ascii[[:space:]]*"\(.*\)".*:\1: -/^->/{s:->#\(.*\):/* \1 */:; - s:^->\([^ ]*\) [\$#]*\([^ ]*\) \(.*\):#define \1 \2 /* \3 */:; - s:->::; - p;} diff --git a/na/scripts/gen_initramfs.sh b/na/scripts/gen_initramfs.sh deleted file mode 100755 index 7eba2fd..0000000 --- a/na/scripts/gen_initramfs.sh +++ /dev/null @@ -1,250 +0,0 @@ -#!/bin/sh -# Copyright (C) Martin Schlemmer -# Copyright (C) 2006 Sam Ravnborg -# -# Released under the terms of the GNU GPL -# -# Generate a cpio packed initramfs. It uses gen_init_cpio to generate -# the cpio archive. -# This script assumes that gen_init_cpio is located in usr/ directory - -# error out on errors -set -e - -usage() { -cat << EOF -Usage: -$0 [-o ] [-l ] [-u ] [-g ] {-d | } ... - -o Create initramfs file named by using gen_init_cpio - -l Create dependency list named - -u User ID to map to user ID 0 (root). - is only meaningful if is a - directory. "squash" forces all files to uid 0. - -g Group ID to map to group ID 0 (root). - is only meaningful if is a - directory. "squash" forces all files to gid 0. - -d Use date for all file mtime values - File list or directory for cpio archive. - If is a .cpio file it will be used - as direct input to initramfs. - -All options except -o and -l may be repeated and are interpreted -sequentially and immediately. -u and -g states are preserved across - options so an explicit "-u 0 -g 0" is required -to reset the root/group mapping. -EOF -} - -# awk style field access -# $1 - field number; rest is argument string -field() { - shift $1 ; echo $1 -} - -filetype() { - local argv1="$1" - - # symlink test must come before file test - if [ -L "${argv1}" ]; then - echo "slink" - elif [ -f "${argv1}" ]; then - echo "file" - elif [ -d "${argv1}" ]; then - echo "dir" - elif [ -b "${argv1}" -o -c "${argv1}" ]; then - echo "nod" - elif [ -p "${argv1}" ]; then - echo "pipe" - elif [ -S "${argv1}" ]; then - echo "sock" - else - echo "invalid" - fi - return 0 -} - -print_mtime() { - local my_mtime="0" - - if [ -e "$1" ]; then - my_mtime=$(find "$1" -printf "%T@\n" | sort -r | head -n 1) - fi - - echo "# Last modified: ${my_mtime}" >> $cpio_list - echo "" >> $cpio_list -} - -list_parse() { - if [ -z "$dep_list" -o -L "$1" ]; then - return - fi - echo "$1" | sed 's/:/\\:/g; s/$/ \\/' >> $dep_list -} - -# for each file print a line in following format -# -# for links, devices etc the format differs. See gen_init_cpio for details -parse() { - local location="$1" - local name="/${location#${srcdir}}" - # change '//' into '/' - name=$(echo "$name" | sed -e 's://*:/:g') - local mode="$2" - local uid="$3" - local gid="$4" - local ftype=$(filetype "${location}") - # remap uid/gid to 0 if necessary - [ "$root_uid" = "squash" ] && uid=0 || [ "$uid" -eq "$root_uid" ] && uid=0 - [ "$root_gid" = "squash" ] && gid=0 || [ "$gid" -eq "$root_gid" ] && gid=0 - local str="${mode} ${uid} ${gid}" - - [ "${ftype}" = "invalid" ] && return 0 - [ "${location}" = "${srcdir}" ] && return 0 - - case "${ftype}" in - "file") - str="${ftype} ${name} ${location} ${str}" - ;; - "nod") - local dev="`LC_ALL=C ls -l "${location}"`" - local maj=`field 5 ${dev}` - local min=`field 6 ${dev}` - maj=${maj%,} - - [ -b "${location}" ] && dev="b" || dev="c" - - str="${ftype} ${name} ${str} ${dev} ${maj} ${min}" - ;; - "slink") - local target=`readlink "${location}"` - str="${ftype} ${name} ${target} ${str}" - ;; - *) - str="${ftype} ${name} ${str}" - ;; - esac - - echo "${str}" >> $cpio_list - - return 0 -} - -unknown_option() { - printf "ERROR: unknown option \"$arg\"\n" >&2 - printf "If the filename validly begins with '-', " >&2 - printf "then it must be prefixed\n" >&2 - printf "by './' so that it won't be interpreted as an option." >&2 - printf "\n" >&2 - usage >&2 - exit 1 -} - -header() { - printf "\n#####################\n# $1\n" >> $cpio_list -} - -# process one directory (incl sub-directories) -dir_filelist() { - header "$1" - - srcdir=$(echo "$1" | sed -e 's://*:/:g') - dirlist=$(find "${srcdir}" -printf "%p %m %U %G\n" | LC_ALL=C sort) - - # If $dirlist is only one line, then the directory is empty - if [ "$(echo "${dirlist}" | wc -l)" -gt 1 ]; then - print_mtime "$1" - - echo "${dirlist}" | \ - while read x; do - list_parse $x - parse $x - done - fi -} - -input_file() { - source="$1" - if [ -f "$1" ]; then - # If a regular file is specified, assume it is in - # gen_init_cpio format - header "$1" - print_mtime "$1" >> $cpio_list - cat "$1" >> $cpio_list - if [ -n "$dep_list" ]; then - echo "$1 \\" >> $dep_list - cat "$1" | while read type dir file perm ; do - if [ "$type" = "file" ]; then - echo "$file \\" >> $dep_list - fi - done - fi - elif [ -d "$1" ]; then - # If a directory is specified then add all files in it to fs - dir_filelist "$1" - else - echo " ${prog}: Cannot open '$1'" >&2 - exit 1 - fi -} - -prog=$0 -root_uid=0 -root_gid=0 -dep_list= -timestamp= -cpio_list=$(mktemp ${TMPDIR:-/tmp}/cpiolist.XXXXXX) -# gen_init_cpio writes to stdout by default -output="" - -trap "rm -f $cpio_list" EXIT - -while [ $# -gt 0 ]; do - arg="$1" - shift - case "$arg" in - "-l") # files included in initramfs - used by kbuild - dep_list="$1" - echo "deps_initramfs := \\" > $dep_list - shift - ;; - "-o") # generate cpio image named $1 - output="-o $1" - shift - ;; - "-u") # map $1 to uid=0 (root) - root_uid="$1" - [ "$root_uid" = "-1" ] && root_uid=$(id -u || echo 0) - shift - ;; - "-g") # map $1 to gid=0 (root) - root_gid="$1" - [ "$root_gid" = "-1" ] && root_gid=$(id -g || echo 0) - shift - ;; - "-d") # date for file mtimes - timestamp="$(date -d"$1" +%s || :)" - if test -n "$timestamp"; then - timestamp="-t $timestamp" - fi - shift - ;; - "-h") - usage - exit 0 - ;; - *) - case "$arg" in - "-"*) - unknown_option - ;; - *) # input file/dir - process it - input_file "$arg" - ;; - esac - ;; - esac -done - -# If output_file is set we will generate cpio archive -# we are careful to delete tmp files -usr/gen_init_cpio $output $timestamp $cpio_list diff --git a/na/test/module.mk b/na/test/module.mk deleted file mode 100644 index 47a693c..0000000 --- a/na/test/module.mk +++ /dev/null @@ -1,30 +0,0 @@ -# test/module.mk -SRCS_CC += test/selftest.cc -SRCS_CC += test/test_pmm.cc -SRCS_CC += test/test_slab.cc -SRCS_CC += test/test_spinlock.cc -SRCS_CC += test/test_mutex.cc -SRCS_CC += test/test_driver_model.cc -SRCS_CC += test/test_rbtree.cc -SRCS_CC += test/test_vmm.cc -SRCS_CC += test/test_vma.cc -SRCS_CC += test/test_atomic.cc -SRCS_CC += test/test_fdt.cc -SRCS_CC += test/test_trap.cc -SRCS_CC += test/test_sched.cc -SRCS_CC += test/test_klog.cc -SRCS_CC += test/test_random.cc -SRCS_CC += test/test_waitqueue.cc -SRCS_CC += test/test_uaccess.cc -SRCS_CC += test/test_sys_mm.cc -SRCS_CC += test/test_smp.cc -SRCS_CC += test/test_dcache.cc -SRCS_CC += test/test_namei.cc -SRCS_CC += test/test_vfs_io.cc -SRCS_CC += test/test_exec.cc -SRCS_CC += test/test_pipe_devfs.cc -SRCS_CC += test/test_sys_process.cc -SRCS_CC += test/test_signal.cc -SRCS_CC += test/test_pid.cc -SRCS_CC += test/test_sys_fs_extra.cc -SRCS_CC += test/test_page_cache.cc diff --git a/na/test/selftest.cc b/na/test/selftest.cc deleted file mode 100644 index e1bb2b4..0000000 --- a/na/test/selftest.cc +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test Runner - * - * Entry point that invokes all registered self-test suites. - * Called from kernel_main() after all subsystem initialization. - */ - -#include - -/* Individual test suite entry points */ -void selftest_pmm(); -void selftest_slab(); -void selftest_spinlock(); -void selftest_mutex(); -void selftest_driver_model(); -void selftest_rbtree(); -void selftest_vmm(); -void selftest_fdt(); -void selftest_vma(); -void selftest_atomic(); -void selftest_trap(); -void selftest_sched(); -void selftest_klog(); -void selftest_random(); -void selftest_waitqueue(); -void selftest_uaccess(); -void selftest_sys_mm(); -void selftest_dcache(); -void selftest_namei(); -void selftest_vfs_io(); -void selftest_exec(); -void selftest_devfs(); -void selftest_pipe(); -void selftest_fcntl(); -void selftest_sys_process(); -void selftest_signal(); -void selftest_pid(); -void selftest_sys_fs_extra(); -void selftest_page_cache(); - -void selftest_run_all() -{ - kprintf("\n========================================\n"); - kprintf(" Boot Self-Test Suite\n"); - kprintf("========================================\n"); - - selftest_spinlock(); - selftest_mutex(); - selftest_driver_model(); - selftest_pmm(); - selftest_slab(); - selftest_rbtree(); - selftest_vmm(); - selftest_vma(); - selftest_atomic(); - selftest_fdt(); - selftest_trap(); - selftest_sched(); - selftest_klog(); - selftest_random(); - selftest_waitqueue(); - selftest_uaccess(); - selftest_sys_mm(); - selftest_dcache(); - selftest_namei(); - selftest_vfs_io(); - selftest_devfs(); - selftest_pipe(); - selftest_fcntl(); - selftest_sys_process(); - selftest_signal(); - selftest_pid(); - selftest_sys_fs_extra(); - selftest_page_cache(); - selftest_exec(); - - kprintf("========================================\n"); - kprintf(" All self-tests PASSED\n"); - kprintf("========================================\n\n"); -} diff --git a/na/test/test_atomic.cc b/na/test/test_atomic.cc deleted file mode 100644 index 9851e7b..0000000 --- a/na/test/test_atomic.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: lume::atomic - * - * Tests: - * 1. Default construction (zero-initialized) - * 2. Explicit construction - * 3. store / load round-trip with different memory orders - * 4. exchange returns old value - * 5. fetch_add / fetch_sub arithmetic - * 6. compare_exchange success and failure paths - */ - -#include -#include - -void selftest_atomic() { - st_begin("atomic: default construction is zero"); - { - lume::atomic a; - ST_ASSERT_EQ(a.load(), 0U); - } - st_pass(); - - st_begin("atomic: explicit construction"); - { - lume::atomic a(42); - ST_ASSERT_EQ(a.load(), 42ULL); - } - st_pass(); - - st_begin("atomic: store/load round-trip"); - { - lume::atomic a; - a.store(0xDEAD, __ATOMIC_RELEASE); - ST_ASSERT_EQ(a.load(__ATOMIC_ACQUIRE), 0xDEADU); - - a.store(0, __ATOMIC_SEQ_CST); - ST_ASSERT_EQ(a.load(__ATOMIC_SEQ_CST), 0U); - } - st_pass(); - - st_begin("atomic: exchange returns old value"); - { - lume::atomic a(10); - uint32 old = a.exchange(20); - ST_ASSERT_EQ(old, 10U); - ST_ASSERT_EQ(a.load(), 20U); - } - st_pass(); - - st_begin("atomic: fetch_add / fetch_sub"); - { - lume::atomic a(100); - - uint32 old = a.fetch_add(5); - ST_ASSERT_EQ(old, 100U); - ST_ASSERT_EQ(a.load(), 105U); - - old = a.fetch_sub(10); - ST_ASSERT_EQ(old, 105U); - ST_ASSERT_EQ(a.load(), 95U); - - // Underflow wraps (unsigned) - lume::atomic b(0); - old = b.fetch_sub(1); - ST_ASSERT_EQ(old, 0U); - ST_ASSERT_EQ(b.load(), 0xFFFFFFFFU); - } - st_pass(); - - st_begin("atomic: compare_exchange success and failure"); - { - lume::atomic a(42); - - // Success path: expected matches, swap happens - uint32 expected = 42; - bool ok = a.compare_exchange(expected, 99); - ST_ASSERT(ok); - ST_ASSERT_EQ(a.load(), 99U); - ST_ASSERT_EQ(expected, 42U); // expected unchanged on success - - // Failure path: expected doesn't match, expected gets updated - expected = 0; // wrong - ok = a.compare_exchange(expected, 200); - ST_ASSERT(!ok); - ST_ASSERT_EQ(a.load(), 99U); // value unchanged - ST_ASSERT_EQ(expected, 99U); // expected updated to actual value - } - st_pass(); - - st_begin("atomic: 64-bit fetch_add correctness"); - { - lume::atomic a(0xFFFFFFFF00000000ULL); - uint64 old = a.fetch_add(1); - ST_ASSERT_EQ(old, 0xFFFFFFFF00000000ULL); - ST_ASSERT_EQ(a.load(), 0xFFFFFFFF00000001ULL); - } - st_pass(); -} diff --git a/na/test/test_dcache.cc b/na/test/test_dcache.cc deleted file mode 100644 index 9e0eec5..0000000 --- a/na/test/test_dcache.cc +++ /dev/null @@ -1,232 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_dcache.cc — Self-tests for the dentry cache layer - */ - -#include -#include -#include -#include -#include -#include - -/* Root dentry/mount from VFS init */ -extern dentry* g_root_dentry; -extern vfsmount* g_root_mnt; - -void selftest_dcache() -{ - /* ---- d_alloc: basic allocation under root ---- */ - st_begin("dcache: d_alloc basic"); - { - qstr name{}; - name.name = "test_child"; - name.len = 10; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - ST_ASSERT_EQ(child->d_name.len, 10u); - ST_ASSERT(child->d_parent == g_root_dentry); - ST_ASSERT(child->d_inode == nullptr); /* not instantiated yet */ - ST_ASSERT(child->d_sb == g_root_dentry->d_sb); - - /* Name was copied into inline buffer */ - bool match = true; - for (uint32 i = 0; i < name.len; i++) { - if (child->d_name.name[i] != name.name[i]) { match = false; break; } - } - ST_ASSERT(match); - - d_drop(child); - dput(child); - } - st_pass(); - - /* ---- d_lookup: find existing child ---- */ - st_begin("dcache: d_lookup finds child"); - { - qstr name{}; - name.name = "lookup_me"; - name.len = 9; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - - /* d_lookup should find it */ - dentry* found = d_lookup(g_root_dentry, &name); - ST_ASSERT(found != nullptr); - ST_ASSERT(found == child); - dput(found); /* drop d_lookup ref */ - - d_drop(child); - dput(child); - } - st_pass(); - - /* ---- d_lookup: miss returns nullptr ---- */ - st_begin("dcache: d_lookup miss returns nullptr"); - { - qstr name{}; - name.name = "nonexistent_xyz"; - name.len = 15; - name.hash = full_name_hash(name.name, name.len); - - dentry* found = d_lookup(g_root_dentry, &name); - ST_ASSERT(found == nullptr); - } - st_pass(); - - /* ---- d_instantiate: bind inode to dentry ---- */ - st_begin("dcache: d_instantiate binds inode"); - { - qstr name{}; - name.name = "has_inode"; - name.len = 9; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - ST_ASSERT(child->d_inode == nullptr); - - /* Create a dummy inode */ - inode* ip = new_inode(g_root_dentry->d_sb); - ST_ASSERT(ip != nullptr); - ip->i_mode = S_IFREG | 0644; - - d_instantiate(child, ip); - ST_ASSERT(child->d_inode == ip); - ST_ASSERT(child->d_flags & DCACHE_REGULAR_TYPE); - - d_drop(child); - dput(child); - } - st_pass(); - - /* ---- dget/dput: refcount ---- */ - st_begin("dcache: dget/dput refcount"); - { - qstr name{}; - name.name = "reftest"; - name.len = 7; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - /* d_alloc sets refcount = 1 */ - int rc1 = child->d_lockref.load(); - ST_ASSERT(rc1 >= 1); - - dget(child); - int rc2 = child->d_lockref.load(); - ST_ASSERT_EQ(rc2, rc1 + 1); - - dput(child); - int rc3 = child->d_lockref.load(); - ST_ASSERT_EQ(rc3, rc1); - - d_drop(child); - dput(child); - } - st_pass(); - - /* ---- d_drop: removes from parent children list ---- */ - st_begin("dcache: d_drop removes from parent"); - { - qstr name{}; - name.name = "dropme"; - name.len = 6; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - - /* Should be findable */ - dentry* f1 = d_lookup(g_root_dentry, &name); - ST_ASSERT(f1 != nullptr); - dput(f1); - - /* Drop it */ - d_drop(child); - - /* Should no longer be findable */ - dentry* f2 = d_lookup(g_root_dentry, &name); - ST_ASSERT(f2 == nullptr); - - dput(child); - } - st_pass(); - - /* ---- d_path: root returns "/" ---- */ - st_begin("dcache: d_path root returns /"); - { - path p{}; - p.de = g_root_dentry; - p.mnt = g_root_mnt; - - char buf[64]; - char* result = d_path(&p, buf, sizeof(buf)); - ST_ASSERT(result != nullptr); - ST_ASSERT(result[0] == '/'); - ST_ASSERT(result[1] == '\0'); - } - st_pass(); - - /* ---- d_path: child path ---- */ - st_begin("dcache: d_path child returns /name"); - { - qstr name{}; - name.name = "pathtest"; - name.len = 8; - name.hash = full_name_hash(name.name, name.len); - - dentry* child = d_alloc(g_root_dentry, &name); - ST_ASSERT(child != nullptr); - - path p{}; - p.de = child; - p.mnt = g_root_mnt; - - char buf[64]; - char* result = d_path(&p, buf, sizeof(buf)); - ST_ASSERT(result != nullptr); - - /* Should be "/pathtest" */ - const char* expected = "/pathtest"; - bool eq = true; - for (int i = 0; expected[i]; i++) { - if (result[i] != expected[i]) { eq = false; break; } - } - ST_ASSERT(eq); - - d_drop(child); - dput(child); - } - st_pass(); - - /* ---- d_alloc_anon: root-like dentry ---- */ - st_begin("dcache: d_alloc_anon"); - { - dentry* anon = d_alloc_anon(g_root_dentry->d_sb); - ST_ASSERT(anon != nullptr); - ST_ASSERT(anon->d_parent == anon); /* root: parent is self */ - ST_ASSERT(anon->d_sb == g_root_dentry->d_sb); - dput(anon); - } - st_pass(); - - /* ---- full_name_hash: deterministic ---- */ - st_begin("dcache: full_name_hash deterministic"); - { - uint32 h1 = full_name_hash("hello", 5); - uint32 h2 = full_name_hash("hello", 5); - ST_ASSERT_EQ(h1, h2); - - uint32 h3 = full_name_hash("world", 5); - ST_ASSERT_NE(h1, h3); - } - st_pass(); -} diff --git a/na/test/test_driver_model.cc b/na/test/test_driver_model.cc deleted file mode 100644 index 8f105b3..0000000 --- a/na/test/test_driver_model.cc +++ /dev/null @@ -1,209 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_driver_model — exercises kref, kobject, and the bus/device/driver - * core. Uses a dummy bus + dummy driver + dummy device so the test is - * self-contained and doesn't depend on FDT/platform_device. - */ - -#include -#include -#include -#include - -/* ------------------------------------------------------------------ */ -/* Test 1: kref get/put + release fires exactly once */ -/* ------------------------------------------------------------------ */ - -namespace { - -struct krefable { - kref ref; - int release_count; -}; - -void krefable_release(kref* r) -{ - auto* k = container_of(r, krefable, ref); - k->release_count++; -} - -void test_kref() -{ - st_begin("kref: init/get/put"); - - krefable k = {}; - kref_init(&k.ref); - - ST_ASSERT_EQ(kref_read(&k.ref), 1); - - kref_get(&k.ref); - kref_get(&k.ref); - ST_ASSERT_EQ(kref_read(&k.ref), 3); - - ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0); - ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0); - ST_ASSERT_EQ(k.release_count, 0); - - ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 1); - ST_ASSERT_EQ(k.release_count, 1); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Test 2: kobject parent/child + release on final put */ -/* ------------------------------------------------------------------ */ - -int g_child_released = 0; -int g_parent_released = 0; - -void child_release(kobject*) { g_child_released++; } -void parent_release(kobject*) { g_parent_released++; } - -const kobj_type child_ktype = { .release = child_release }; -const kobj_type parent_ktype = { .release = parent_release }; - -void test_kobject_hierarchy() -{ - st_begin("kobject: parent/child + ref counting"); - - g_child_released = 0; - g_parent_released = 0; - - kobject parent; - kobject_init(&parent, &parent_ktype); - - kobject child; - kobject_init(&child, &child_ktype); - kobject_add(&child, &parent, "child"); - - /* Adding child took a ref on parent. */ - ST_ASSERT_EQ(kref_read(&parent.refcount), 2); - ST_ASSERT(child.parent == &parent); - - /* Drop child's "added" reference — release should NOT fire because - * we (the test) still hold the original kobject_init ref. */ - kobject_del(&child); - ST_ASSERT(child.parent == nullptr); - ST_ASSERT_EQ(kref_read(&parent.refcount), 1); - ST_ASSERT_EQ(g_child_released, 0); - ST_ASSERT_EQ(g_parent_released, 0); - - /* Drop final refs. */ - kobject_put(&child); - ST_ASSERT_EQ(g_child_released, 1); - - kobject_put(&parent); - ST_ASSERT_EQ(g_parent_released, 1); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Test 3: bus / device / driver bind via match + probe */ -/* ------------------------------------------------------------------ */ - -struct dummy_dev { device dev; int probed; }; -struct dummy_drv { device_driver drv; const char* want; }; - -int g_probes = 0; - -int dummy_match(device* dev, device_driver* drv) -{ - auto* dd = container_of(drv, dummy_drv, drv); - return (dev->init_name != nullptr && - dd->want != nullptr && - __builtin_strcmp(dev->init_name, dd->want) == 0) ? 1 : 0; -} - -int dummy_probe(device* dev) -{ - auto* dd = container_of(dev, dummy_dev, dev); - dd->probed++; - g_probes++; - return 0; -} - -bus_type dummy_bus = { - .name = "test_dummy", - .match = dummy_match, - .probe = nullptr, /* fall through to drv->probe */ - .devices = {}, - .drivers = {}, - .lock = {}, - .registered = false, -}; - -void test_bus_bind() -{ - st_begin("driver model: bus / driver / device bind"); - - g_probes = 0; - ST_ASSERT_EQ(bus_register(&dummy_bus), 0); - - /* Register driver before any matching device exists. */ - dummy_drv drv_a = {}; - drv_a.drv.name = "drv_a"; - drv_a.drv.bus = &dummy_bus; - drv_a.drv.probe = dummy_probe; - drv_a.want = "compat_a"; - ST_ASSERT_EQ(driver_register(&drv_a.drv), 0); - - /* Register a device that should bind to drv_a immediately. */ - dummy_dev dev_a = {}; - dev_a.dev.bus = &dummy_bus; - dev_a.dev.init_name = "compat_a"; - ST_ASSERT_EQ(device_register(&dev_a.dev), 0); - ST_ASSERT_EQ(dev_a.probed, 1); - ST_ASSERT(dev_a.dev.driver == &drv_a.drv); - - /* Register a device first, then a driver — should also bind. */ - dummy_dev dev_b = {}; - dev_b.dev.bus = &dummy_bus; - dev_b.dev.init_name = "compat_b"; - ST_ASSERT_EQ(device_register(&dev_b.dev), 0); - ST_ASSERT_EQ(dev_b.probed, 0); - - dummy_drv drv_b = {}; - drv_b.drv.name = "drv_b"; - drv_b.drv.bus = &dummy_bus; - drv_b.drv.probe = dummy_probe; - drv_b.want = "compat_b"; - ST_ASSERT_EQ(driver_register(&drv_b.drv), 0); - ST_ASSERT_EQ(dev_b.probed, 1); - ST_ASSERT(dev_b.dev.driver == &drv_b.drv); - - /* Non-matching device sits unbound. */ - dummy_dev dev_c = {}; - dev_c.dev.bus = &dummy_bus; - dev_c.dev.init_name = "compat_unknown"; - ST_ASSERT_EQ(device_register(&dev_c.dev), 0); - ST_ASSERT_EQ(dev_c.probed, 0); - ST_ASSERT(dev_c.dev.driver == nullptr); - - ST_ASSERT_EQ(g_probes, 2); - - /* Cleanup. */ - device_unregister(&dev_a.dev); - device_unregister(&dev_b.dev); - device_unregister(&dev_c.dev); - driver_unregister(&drv_a.drv); - driver_unregister(&drv_b.drv); - - st_pass(); -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Entry */ -/* ------------------------------------------------------------------ */ - -void selftest_driver_model() -{ - test_kref(); - test_kobject_hierarchy(); - test_bus_bind(); -} diff --git a/na/test/test_exec.cc b/na/test/test_exec.cc deleted file mode 100644 index bbfa3b6..0000000 --- a/na/test/test_exec.cc +++ /dev/null @@ -1,447 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_exec.cc — Self-tests for execve / ELF loader - * - * Tests the ELF validation, binary loading, and user stack construction - * by creating synthetic ELF binaries in RamFS and invoking do_execve - * in a controlled environment. - * - * We cannot test the actual user-space transition (that requires - * sret to U-mode), but we CAN test everything up to and including - * the trapframe setup. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Extern: do_execve (kernel/exec.cc) */ -int do_execve(const char* pathname, const char* const* argv, - const char* const* envp); - -/* Extern: VFS file creation syscalls */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); - -/* ================================================================== */ -/* Helper: build a minimal valid RISC-V ELF64 in a buffer */ -/* ================================================================== */ - -struct TestElf { - Elf64_Ehdr ehdr; - Elf64_Phdr phdr; - uint8 code[16]; /* Tiny "program" — just a few NOPs */ -}; - -/* - * Build a minimal static ELF64 for RISC-V. - * Single PT_LOAD segment mapping the tiny code at 0x10000. - * Entry point = 0x10000 + offsetof(code). - */ -static void build_minimal_elf(TestElf* elf) -{ - /* Zero everything */ - for (uint64 i = 0; i < sizeof(*elf); i++) - reinterpret_cast(elf)[i] = 0; - - /* ELF header */ - Elf64_Ehdr* e = &elf->ehdr; - e->e_ident[EI_MAG0] = ELFMAG0; - e->e_ident[EI_MAG1] = ELFMAG1; - e->e_ident[EI_MAG2] = ELFMAG2; - e->e_ident[EI_MAG3] = ELFMAG3; - e->e_ident[EI_CLASS] = ELFCLASS64; - e->e_ident[EI_DATA] = ELFDATA2LSB; - e->e_ident[EI_VERSION] = 1; - e->e_type = ET_EXEC; - e->e_machine = EM_RISCV; - e->e_version = 1; - e->e_phoff = sizeof(Elf64_Ehdr); - e->e_phentsize = sizeof(Elf64_Phdr); - e->e_phnum = 1; - e->e_ehsize = sizeof(Elf64_Ehdr); - - /* Code section offset within the file */ - uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr); - - /* Entry point = vaddr of code */ - uint64 load_vaddr = 0x10000; - e->e_entry = load_vaddr + code_offset; - - /* Program header — single PT_LOAD covering entire file */ - Elf64_Phdr* p = &elf->phdr; - p->p_type = PT_LOAD; - p->p_flags = PF_R | PF_X; - p->p_offset = 0; - p->p_vaddr = load_vaddr; - p->p_paddr = load_vaddr; - p->p_filesz = sizeof(TestElf); - p->p_memsz = sizeof(TestElf); - p->p_align = 0x1000; - - /* Code: RISC-V "unimp" (all zeros = illegal instruction, but good enough for testing) */ - /* We just need the loader to succeed; we won't actually execute this. */ - elf->code[0] = 0x13; /* nop = addi x0, x0, 0 */ - elf->code[4] = 0x13; - elf->code[8] = 0x73; /* ebreak */ -} - -/* ================================================================== */ -/* Helper: write a buffer to a file in RamFS */ -/* ================================================================== */ - -static int create_test_file(const char* path, const void* data, uint64 size) -{ - /* Open with O_CREAT | O_WRONLY | O_TRUNC */ - int64 fd = sys_openat( - static_cast(-100), /* AT_FDCWD */ - reinterpret_cast(path), - O_CREAT | O_WRONLY | O_TRUNC, - 0755, 0, 0); - if (fd < 0) return static_cast(fd); - - int64 written = sys_write( - static_cast(fd), - reinterpret_cast(data), - size, 0, 0, 0); - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - - if (written < 0) return static_cast(written); - if (static_cast(written) != size) return -EIO; - return 0; -} - -/* ================================================================== */ -/* Helper: ensure current task has VFS context for file ops */ -/* ================================================================== */ - -struct ExecTestCtx { - TaskControlBlock* curr; - VmSpace* old_mm; - arch::TrapFrame fake_tf; - arch::TrapFrame* old_tf; - uint64 saved_satp; -}; - -static void exec_test_setup(ExecTestCtx* ctx) -{ - ctx->curr = current_task(); - - /* Save kernel SATP so we can restore it in teardown */ - asm volatile("csrr %0, satp" : "=r"(ctx->saved_satp)); - - /* Save and replace mm with a fresh one */ - ctx->old_mm = ctx->curr->mm; - ctx->curr->mm = VmSpace::create(); - - /* Set up a fake trapframe so do_execve can modify it */ - for (uint64 i = 0; i < sizeof(ctx->fake_tf); i++) - reinterpret_cast(&ctx->fake_tf)[i] = 0; - ctx->old_tf = ctx->curr->trapframe; - ctx->curr->trapframe = &ctx->fake_tf; -} - -static void exec_test_teardown(ExecTestCtx* ctx) -{ - /* Restore trapframe */ - ctx->curr->trapframe = ctx->old_tf; - - /* Destroy the test mm (may have been replaced by do_execve) */ - VmSpace* test_mm = ctx->curr->mm; - ctx->curr->mm = ctx->old_mm; - if (test_mm && test_mm != ctx->old_mm) { - /* If do_execve succeeded, it activated new_mm's page table. - * Switch back to the kernel page table before destroying. */ - asm volatile("csrw satp, %0" :: "r"(ctx->saved_satp)); - asm volatile("sfence.vma"); - test_mm->put(); - } -} - -/* ================================================================== */ -/* Test cases */ -/* ================================================================== */ - -void selftest_exec() -{ - /* ---- 1a. Non-existent path under missing directory ---- */ - st_begin("exec: missing path returns error"); - { - const char* argv[] = { "/no_such_dir/no_file", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/no_such_dir/no_file", argv, envp); - ST_ASSERT(ret < 0); /* -ENOENT from filename_lookup */ - } - st_pass(); - - /* ---- 1b. Directory is not executable ---- */ - st_begin("exec: directory returns -EACCES"); - { - /* "/" exists and is a directory */ - const char* argv[] = { "/dev", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/dev", argv, envp); - ST_ASSERT_EQ(ret, -EACCES); - } - st_pass(); - - /* ---- 2. Bad ELF magic ---- */ - st_begin("exec: bad ELF magic returns -ENOEXEC"); - { - uint8 garbage[128]; - for (int i = 0; i < 128; i++) garbage[i] = 0xAA; - int err = create_test_file("/test_bad_magic", garbage, 128); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_bad_magic", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_bad_magic", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 3. Wrong ELF class (32-bit) ---- */ - st_begin("exec: 32-bit ELF returns -ENOEXEC"); - { - TestElf elf; - build_minimal_elf(&elf); - elf.ehdr.e_ident[EI_CLASS] = 1; /* ELFCLASS32 */ - int err = create_test_file("/test_elf32", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_elf32", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_elf32", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 4. Wrong endianness (big-endian) ---- */ - st_begin("exec: big-endian ELF returns -ENOEXEC"); - { - TestElf elf; - build_minimal_elf(&elf); - elf.ehdr.e_ident[EI_DATA] = 2; /* ELFDATA2MSB */ - int err = create_test_file("/test_be", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_be", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_be", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 5. Wrong machine (x86_64 instead of RISC-V) ---- */ - st_begin("exec: wrong machine type returns -ENOEXEC"); - { - TestElf elf; - build_minimal_elf(&elf); - elf.ehdr.e_machine = 62; /* EM_X86_64 */ - int err = create_test_file("/test_x86", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_x86", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_x86", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 6. Truncated ELF (too small for header) ---- */ - st_begin("exec: truncated ELF returns -ENOEXEC"); - { - /* Valid magic but file is too short for full header */ - uint8 tiny[8] = { 0x7f, 'E', 'L', 'F', 2, 1, 1, 0 }; - int err = create_test_file("/test_tiny", tiny, sizeof(tiny)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_tiny", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_tiny", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 7. Zero program headers ---- */ - st_begin("exec: zero phnum returns -ENOEXEC"); - { - TestElf elf; - build_minimal_elf(&elf); - elf.ehdr.e_phnum = 0; - int err = create_test_file("/test_nophdr", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_nophdr", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_nophdr", argv, envp); - ST_ASSERT_EQ(ret, -ENOEXEC); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 8. Valid ELF: successful load and trapframe setup ---- */ - st_begin("exec: valid ELF loads and sets up trapframe"); - { - TestElf elf; - build_minimal_elf(&elf); - int err = create_test_file("/test_valid", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_valid", nullptr }; - const char* envp[] = { "HOME=/", nullptr }; - int ret = do_execve("/test_valid", argv, envp); - ST_ASSERT_EQ(ret, 0); - - /* Verify trapframe was set up correctly */ - arch::TrapFrame* tf = &ctx.fake_tf; - - /* Entry point should match what we set in the ELF */ - uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr); - uint64 expected_entry = 0x10000 + code_offset; - ST_ASSERT_EQ(tf->epc, expected_entry); - - /* Stack pointer should be in user space, below the stack top */ - ST_ASSERT(tf->sp > 0); - ST_ASSERT(tf->sp < kUserStackTop); - ST_ASSERT(tf->sp >= kUserStackBase); - - /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts enabled on sret) */ - uint64 spp_bit = (1ULL << 8); - uint64 spie_bit = (1ULL << 5); - ST_ASSERT_EQ(tf->sstatus & spp_bit, 0ULL); /* Not S-mode */ - ST_ASSERT_EQ(tf->sstatus & spie_bit, spie_bit); /* SPIE set */ - - /* General registers should be zeroed */ - ST_ASSERT_EQ(tf->ra, 0ULL); - ST_ASSERT_EQ(tf->gp, 0ULL); - ST_ASSERT_EQ(tf->tp, 0ULL); - for (int i = 0; i < 7; i++) - ST_ASSERT_EQ(tf->t[i], 0ULL); - for (int i = 0; i < 12; i++) - ST_ASSERT_EQ(tf->s[i], 0ULL); - for (int i = 0; i < 8; i++) - ST_ASSERT_EQ(tf->a[i], 0ULL); - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 9. Verify user stack layout (argc/argv) ---- */ - st_begin("exec: user stack has correct argc/argv layout"); - { - TestElf elf; - build_minimal_elf(&elf); - int err = create_test_file("/test_stack", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - const char* argv[] = { "/test_stack", "arg1", nullptr }; - const char* envp[] = { "A=B", nullptr }; - int ret = do_execve("/test_stack", argv, envp); - ST_ASSERT_EQ(ret, 0); - - /* The stack was set up in the new mm. - * Read argc from the top of the stack using pmap lookup. */ - VmSpace* mm = ctx.curr->mm; - uint64 sp = ctx.fake_tf.sp; - - /* Look up the physical page for sp */ - uint64 pa = 0; - bool mapped = pmap::lookup(mm->root_pa, sp & ~(kPageSize - 1), &pa); - ST_ASSERT(mapped); - - /* Read argc (first uint64 on stack) */ - uint64 page_offset = sp & (kPageSize - 1); - uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); - uint64 argc = *reinterpret_cast(kva + page_offset); - ST_ASSERT_EQ(argc, 2ULL); /* "/test_stack" and "arg1" */ - - exec_test_teardown(&ctx); - } - st_pass(); - - /* ---- 10. Multiple consecutive execve replaces mm correctly ---- */ - st_begin("exec: consecutive execve replaces address space"); - { - TestElf elf; - build_minimal_elf(&elf); - int err = create_test_file("/test_reexec", &elf, sizeof(elf)); - ST_ASSERT_EQ(err, 0); - - ExecTestCtx ctx; - exec_test_setup(&ctx); - - /* First exec */ - const char* argv[] = { "/test_reexec", nullptr }; - const char* envp[] = { nullptr }; - int ret = do_execve("/test_reexec", argv, envp); - ST_ASSERT_EQ(ret, 0); - - VmSpace* first_mm = ctx.curr->mm; - ST_ASSERT(first_mm != nullptr); - - /* Second exec — should replace the first mm */ - ret = do_execve("/test_reexec", argv, envp); - ST_ASSERT_EQ(ret, 0); - - /* mm should be different (new address space) */ - VmSpace* second_mm = ctx.curr->mm; - ST_ASSERT(second_mm != nullptr); - ST_ASSERT_NE(first_mm, second_mm); - - exec_test_teardown(&ctx); - } - st_pass(); -} diff --git a/na/test/test_fdt.cc b/na/test/test_fdt.cc deleted file mode 100644 index cf09029..0000000 --- a/na/test/test_fdt.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: FDT (Flattened Device Tree) - */ - -#include -#include - -// Manually declare what we need to avoid header conflicts with toolchain libc -extern "C" int strcmp(const char *s1, const char *s2); - -void selftest_fdt() { - st_begin("fdt: tree root access"); - { - DeviceNode* root = fdt_get_node_by_path("/"); - ST_ASSERT(root != nullptr); - ST_ASSERT_EQ(strcmp(root->get_name(), ""), 0); - } - st_pass(); - - st_begin("fdt: find existing compatible node (UART)"); - { - // QEMU virt RISC-V platform always has a ns16550a UART - DeviceNode* uart = fdt_find_compatible("ns16550a"); - ST_ASSERT(uart != nullptr); - - // Use get_prop_string which is convenience helper - const char* compat = uart->get_prop_string("compatible"); - ST_ASSERT(compat != nullptr); - - // Check raw property access - const FdtProperty* reg = uart->get_prop("reg"); - ST_ASSERT(reg != nullptr); - ST_ASSERT(reg->value != nullptr); - ST_ASSERT(reg->len > 0); - } - st_pass(); - - st_begin("fdt: non-existent node/compatible queries"); - { - DeviceNode* missing1 = fdt_get_node_by_path("/definitely_does_not_exist_node_12345"); - ST_ASSERT(missing1 == nullptr); - - DeviceNode* missing2 = fdt_find_compatible("invalid_compatible_string_xyz"); - ST_ASSERT(missing2 == nullptr); - } - st_pass(); -} diff --git a/na/test/test_klog.cc b/na/test/test_klog.cc deleted file mode 100644 index 896e7ed..0000000 --- a/na/test/test_klog.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: klog - * - * Tests: - * 1. read vs read_consume semantics - * 2. subsystem mask filtering - * 3. ratelimit bookkeeping - * 4. flush advances console tail when console filtering is active - * 5. seq counter increments on writes - */ - -#include -#include -#include - -static void drain_klog() -{ - static char buf[512]; - while (true) { - int n = klog_read_consume(buf, sizeof(buf)); - if (n <= 0) { - break; - } - } -} - -static bool test_read_peek_and_consume() -{ - drain_klog(); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_a"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_b"); - - KlogStats s0; - klog_stats(&s0); - - char buf[1024]; - int n = klog_read(buf, sizeof(buf) - 1); - if (n <= 0) { - return false; - } - buf[n] = '\0'; - if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { - return false; - } - - KlogStats s1; - klog_stats(&s1); - if (s1.tail != s0.tail) { - return false; - } - - int n2 = klog_read_consume(buf, sizeof(buf) - 1); - if (n2 <= 0) { - return false; - } - buf[n2] = '\0'; - if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { - return false; - } - - KlogStats s2; - klog_stats(&s2); - return s2.tail != s0.tail; -} - -static bool test_subsys_mask() -{ - drain_klog(); - - const uint64 all_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; - klog_set_subsys_mask(1ULL << KLOG_SUBSYS_TEST); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_subsys_ok"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, "klog_subsys_drop"); - - char buf[1024]; - int n = klog_read_consume(buf, sizeof(buf) - 1); - klog_set_subsys_mask(all_mask); - - if (n <= 0) { - return false; - } - buf[n] = '\0'; - return strstr(buf, "klog_subsys_ok") && !strstr(buf, "klog_subsys_drop"); -} - -static bool test_ratelimit() -{ - KlogRateLimit rl = {}; - rl.interval = 100; - rl.burst = 2; - - bool a = klog_ratelimit(&rl, 10); - bool b = klog_ratelimit(&rl, 20); - bool c = klog_ratelimit(&rl, 30); - - if (!a || !b || c) { - return false; - } - if (rl.printed != 2 || rl.suppressed != 1) { - return false; - } - - bool d = klog_ratelimit(&rl, 200); - if (!d) { - return false; - } - return rl.printed == 1 && rl.suppressed == 0; -} - -static bool test_flush_advances_console_tail() -{ - drain_klog(); - - klog_set_level(KLOG_ERR); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_flush_msg"); - - KlogStats s1; - klog_stats(&s1); - if (s1.console_tail == s1.head) { - klog_set_level(static_cast(KLOG_DEFAULT_LEVEL)); - return false; - } - - klog_flush(); - - KlogStats s2; - klog_stats(&s2); - - klog_set_level(static_cast(KLOG_DEFAULT_LEVEL)); - return s2.console_tail == s2.head; -} - -static bool test_seq_increments() -{ - drain_klog(); - - KlogStats s0; - klog_stats(&s0); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_1"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_2"); - - KlogStats s1; - klog_stats(&s1); - - return s1.seq >= s0.seq + 2; -} - -void selftest_klog() -{ - st_begin("klog: read vs read_consume"); - ST_ASSERT(test_read_peek_and_consume()); - st_pass(); - - st_begin("klog: subsys mask filters"); - ST_ASSERT(test_subsys_mask()); - st_pass(); - - st_begin("klog: ratelimit bookkeeping"); - ST_ASSERT(test_ratelimit()); - st_pass(); - - st_begin("klog: flush advances console tail"); - ST_ASSERT(test_flush_advances_console_tail()); - st_pass(); - - st_begin("klog: seq increments on write"); - ST_ASSERT(test_seq_increments()); - st_pass(); -} diff --git a/na/test/test_mutex.cc b/na/test/test_mutex.cc deleted file mode 100644 index e86c984..0000000 --- a/na/test/test_mutex.cc +++ /dev/null @@ -1,183 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_mutex — self-tests for the Mutex sleeping lock. - * - * Coverage: - * - basic lock/unlock single-thread - * - try_lock when free and when held - * - mutual exclusion across kthreads (no lost increments) - * - FIFO wake order (A acquires, B/C queue; release lets B in then C) - */ - -#include -#include -#include -#include -#include - -extern TaskControlBlock* kthread_create(const char* name, - void (*entry)(void*), void* arg, - uint8 priority); -extern void task_exit(int code); - -/* ------------------------------------------------------------------ */ -/* Test 1: basic lock/unlock */ -/* ------------------------------------------------------------------ */ - -static void test_basic() -{ - st_begin("mutex: basic lock/unlock"); - - Mutex m; - m.init("test_basic"); - - ST_ASSERT(!m.is_locked()); - m.lock(); - ST_ASSERT(m.is_locked()); - ST_ASSERT(m.is_owned_by_current()); - m.unlock(); - ST_ASSERT(!m.is_locked()); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Test 2: try_lock */ -/* ------------------------------------------------------------------ */ - -static void test_trylock() -{ - st_begin("mutex: try_lock"); - - Mutex m; - m.init("test_trylock"); - - ST_ASSERT(m.try_lock() == true); - ST_ASSERT(m.is_locked()); - - /* A second try_lock from the same task: since owner == current, - * we still return false (try_lock is non-recursive). */ - ST_ASSERT(m.try_lock() == false); - - m.unlock(); - ST_ASSERT(!m.is_locked()); - ST_ASSERT(m.try_lock() == true); - m.unlock(); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Test 3: mutual exclusion across kthreads */ -/* ------------------------------------------------------------------ */ - -static Mutex g_mut; -static volatile int32 g_counter = 0; -static volatile int32 g_done = 0; -static constexpr int32 kIters = 200; - -static void mutex_worker(void*) -{ - for (int i = 0; i < kIters; i++) { - g_mut.lock(); - int32 v = g_counter; - /* Yield inside the critical section to expose missing mutex. - * If the mutex were broken we'd see lost increments. */ - sched_yield(); - g_counter = v + 1; - g_mut.unlock(); - } - __sync_fetch_and_add(&g_done, 1); - task_exit(0); -} - -static void test_mutual_exclusion() -{ - st_begin("mutex: mutual exclusion (2 workers)"); - - g_mut.init("test_mutex_mx"); - g_counter = 0; - g_done = 0; - - kthread_create("mut_w1", mutex_worker, nullptr, 0); - kthread_create("mut_w2", mutex_worker, nullptr, 0); - - /* Spin-yield until both workers finish. */ - while (g_done < 2) { - sched_yield(); - } - - ST_ASSERT_EQ(g_counter, 2 * kIters); - ST_ASSERT(!g_mut.is_locked()); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Test 4: FIFO wake order */ -/* ------------------------------------------------------------------ */ - -static Mutex g_fifo_mut; -static volatile int32 g_fifo_seq = 0; // monotonically increases -static volatile int32 g_fifo_order[3]; // order each worker observed -static volatile int32 g_fifo_done = 0; - -static void fifo_worker(void* arg) -{ - int slot = static_cast(reinterpret_cast(arg)); - g_fifo_mut.lock(); - g_fifo_order[slot] = __sync_fetch_and_add(&g_fifo_seq, 1); - g_fifo_mut.unlock(); - __sync_fetch_and_add(&g_fifo_done, 1); - task_exit(0); -} - -static void test_fifo_order() -{ - st_begin("mutex: FIFO wake order (3 waiters)"); - - g_fifo_mut.init("test_mutex_fifo"); - g_fifo_seq = 0; - g_fifo_done = 0; - g_fifo_order[0] = g_fifo_order[1] = g_fifo_order[2] = -1; - - /* Main grabs the lock first so workers will queue up behind. */ - g_fifo_mut.lock(); - - /* Spawn workers in deterministic order. After each create() yield - * once so the new thread reaches mutex.lock() and enqueues itself - * before we spawn the next one. */ - kthread_create("fifo_A", fifo_worker, reinterpret_cast(0), 0); - sched_yield(); - kthread_create("fifo_B", fifo_worker, reinterpret_cast(1), 0); - sched_yield(); - kthread_create("fifo_C", fifo_worker, reinterpret_cast(2), 0); - sched_yield(); - - /* Release: A should fire next, then B, then C. */ - g_fifo_mut.unlock(); - - while (g_fifo_done < 3) { - sched_yield(); - } - - ST_ASSERT_EQ(g_fifo_order[0], 0); - ST_ASSERT_EQ(g_fifo_order[1], 1); - ST_ASSERT_EQ(g_fifo_order[2], 2); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Entry */ -/* ------------------------------------------------------------------ */ - -void selftest_mutex() -{ - test_basic(); - test_trylock(); - test_mutual_exclusion(); - test_fifo_order(); -} diff --git a/na/test/test_namei.cc b/na/test/test_namei.cc deleted file mode 100644 index 8e4ecb0..0000000 --- a/na/test/test_namei.cc +++ /dev/null @@ -1,173 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_namei.cc — Self-tests for pathname resolution (namei.cc) - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -extern dentry* g_root_dentry; -extern vfsmount* g_root_mnt; - -/* sys_mkdirat from kernel/sys_fs.cc */ -int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); - -void selftest_namei() -{ - /* ---- lookup root "/" ---- */ - st_begin("namei: lookup root /"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de == g_root_dentry); - ST_ASSERT(p.de->d_inode != nullptr); - ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); - } - st_pass(); - - /* ---- lookup /dev (exists from boot) ---- */ - st_begin("namei: lookup /dev"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de != nullptr); - ST_ASSERT(p.de->d_inode != nullptr); - ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); - } - st_pass(); - - /* ---- lookup nonexistent path returns -ENOENT ---- */ - st_begin("namei: lookup nonexistent returns -ENOENT"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/this_does_not_exist", 0, &p); - ST_ASSERT_EQ(err, -ENOENT); - } - st_pass(); - - /* ---- lookup /dev/console (chardev) ---- */ - st_begin("namei: lookup /dev/console"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev/console", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de->d_inode != nullptr); - ST_ASSERT(S_ISCHR(p.de->d_inode->i_mode)); - } - st_pass(); - - /* ---- lookup with LOOKUP_DIRECTORY on non-dir returns -ENOTDIR ---- */ - st_begin("namei: LOOKUP_DIRECTORY on file returns -ENOTDIR"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_DIRECTORY, &p); - ST_ASSERT_EQ(err, -ENOTDIR); - } - st_pass(); - - /* ---- lookup with LOOKUP_DIRECTORY on dir succeeds ---- */ - st_begin("namei: LOOKUP_DIRECTORY on dir succeeds"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev", LOOKUP_DIRECTORY, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); - } - st_pass(); - - /* ---- kern_path shortcut ---- */ - st_begin("namei: kern_path works like filename_lookup"); - { - path p{}; - int err = kern_path("/dev", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de->d_inode != nullptr); - } - st_pass(); - - /* ---- dot path: /dev/. resolves to /dev ---- */ - st_begin("namei: /dev/. resolves to /dev"); - { - path p1{}, p2{}; - int e1 = filename_lookup(AT_FDCWD, "/dev", 0, &p1); - int e2 = filename_lookup(AT_FDCWD, "/dev/.", 0, &p2); - ST_ASSERT_EQ(e1, 0); - ST_ASSERT_EQ(e2, 0); - ST_ASSERT(p1.de == p2.de); - } - st_pass(); - - /* ---- dotdot path: /dev/.. resolves to root ---- */ - st_begin("namei: /dev/.. resolves to root"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev/..", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de == g_root_dentry); - } - st_pass(); - - /* ---- lookup into non-dir returns -ENOTDIR ---- */ - st_begin("namei: path through non-dir returns -ENOTDIR"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev/console/child", 0, &p); - ST_ASSERT_EQ(err, -ENOTDIR); - } - st_pass(); - - /* ---- null/empty paths ---- */ - st_begin("namei: null name returns -EINVAL"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, nullptr, 0, &p); - ST_ASSERT_EQ(err, -EINVAL); - } - st_pass(); - - /* ---- LOOKUP_PARENT: returns parent dentry ---- */ - st_begin("namei: LOOKUP_PARENT returns parent"); - { - path p{}; - int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_PARENT, &p); - ST_ASSERT_EQ(err, 0); - /* Should return /dev, not /dev/console */ - path dev_path{}; - int e2 = filename_lookup(AT_FDCWD, "/dev", 0, &dev_path); - ST_ASSERT_EQ(e2, 0); - ST_ASSERT(p.de == dev_path.de); - } - st_pass(); - - /* ---- mkdir + lookup round-trip ---- */ - st_begin("namei: mkdir then lookup succeeds"); - { - /* Create /test_namei_dir via sys_mkdirat */ - int64 ret = sys_mkdirat( - static_cast(-100), - reinterpret_cast("/test_namei_dir"), - 0755, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - - /* Now lookup should find it */ - path p{}; - int err = filename_lookup(AT_FDCWD, "/test_namei_dir", 0, &p); - ST_ASSERT_EQ(err, 0); - ST_ASSERT(p.de->d_inode != nullptr); - ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); - } - st_pass(); - - /* NOTE: trailing slash "/dev/" not tested — Phase 1 namei - * doesn't handle empty trailing components. */ -} diff --git a/na/test/test_page_cache.cc b/na/test/test_page_cache.cc deleted file mode 100644 index ee0e0e2..0000000 --- a/na/test/test_page_cache.cc +++ /dev/null @@ -1,180 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_page_cache.cc — Self-tests for the page cache hash table - * - * Tests: insert, get, remove, duplicate insert, invalidate, - * get_or_alloc, null args. - */ - -#include -#include -#include -#include -#include - -void selftest_page_cache() -{ - /* Use sentinel pointers as fake "mapping" identifiers. - * These are never dereferenced — only used as hash keys. */ - int mapping_a_tag = 0; - int mapping_b_tag = 0; - void* mapping_a = &mapping_a_tag; - void* mapping_b = &mapping_b_tag; - - /* ---- insert and get ---- */ - st_begin("page_cache: insert then get returns same page"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - - int ret = page_cache_insert(mapping_a, 0, page); - ST_ASSERT_EQ(ret, 0); - - Page* got = page_cache_get(mapping_a, 0); - ST_ASSERT_EQ(got, page); - - /* put the reference from get */ - page_cache_put(got); - - /* Clean up */ - page_cache_remove(mapping_a, 0); - pmm_free_page(page); - } - st_pass(); - - /* ---- get nonexistent returns nullptr ---- */ - st_begin("page_cache: get nonexistent returns nullptr"); - { - Page* got = page_cache_get(mapping_a, 99999); - ST_ASSERT(got == nullptr); - } - st_pass(); - - /* ---- duplicate insert returns -EEXIST ---- */ - st_begin("page_cache: duplicate insert returns -EEXIST"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - - int ret1 = page_cache_insert(mapping_a, 1, page); - ST_ASSERT_EQ(ret1, 0); - - Page* page2 = pmm_alloc_page(); - ST_ASSERT(page2 != nullptr); - - int ret2 = page_cache_insert(mapping_a, 1, page2); - ST_ASSERT_EQ(ret2, -EEXIST); - - /* Clean up */ - page_cache_remove(mapping_a, 1); - pmm_free_page(page); - pmm_free_page(page2); - } - st_pass(); - - /* ---- remove returns page ---- */ - st_begin("page_cache: remove returns page and deletes entry"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - - page_cache_insert(mapping_a, 2, page); - - Page* removed = page_cache_remove(mapping_a, 2); - ST_ASSERT_EQ(removed, page); - - /* Should be gone now */ - Page* got = page_cache_get(mapping_a, 2); - ST_ASSERT(got == nullptr); - - pmm_free_page(page); - } - st_pass(); - - /* ---- remove nonexistent returns nullptr ---- */ - st_begin("page_cache: remove nonexistent returns nullptr"); - { - Page* got = page_cache_remove(mapping_a, 77777); - ST_ASSERT(got == nullptr); - } - st_pass(); - - /* ---- different mappings are independent ---- */ - st_begin("page_cache: different mappings are independent"); - { - Page* pa = pmm_alloc_page(); - Page* pb = pmm_alloc_page(); - ST_ASSERT(pa != nullptr); - ST_ASSERT(pb != nullptr); - - page_cache_insert(mapping_a, 5, pa); - page_cache_insert(mapping_b, 5, pb); - - Page* got_a = page_cache_get(mapping_a, 5); - Page* got_b = page_cache_get(mapping_b, 5); - ST_ASSERT_EQ(got_a, pa); - ST_ASSERT_EQ(got_b, pb); - ST_ASSERT_NE(got_a, got_b); - - page_cache_put(got_a); - page_cache_put(got_b); - page_cache_remove(mapping_a, 5); - page_cache_remove(mapping_b, 5); - pmm_free_page(pa); - pmm_free_page(pb); - } - st_pass(); - - /* ---- invalidate removes all entries for a mapping ---- */ - st_begin("page_cache: invalidate removes all entries for mapping"); - { - Page* pages[4]; - for (int i = 0; i < 4; i++) { - pages[i] = pmm_alloc_page(); - ST_ASSERT(pages[i] != nullptr); - page_cache_insert(mapping_a, 10 + i, pages[i]); - } - - /* Also insert one for mapping_b */ - Page* pb = pmm_alloc_page(); - page_cache_insert(mapping_b, 10, pb); - - /* Invalidate mapping_a */ - page_cache_invalidate(mapping_a); - - /* All mapping_a entries should be gone */ - for (int i = 0; i < 4; i++) { - Page* got = page_cache_get(mapping_a, 10 + i); - ST_ASSERT(got == nullptr); - pmm_free_page(pages[i]); - } - - /* mapping_b should still be there */ - Page* got_b = page_cache_get(mapping_b, 10); - ST_ASSERT_EQ(got_b, pb); - page_cache_put(got_b); - page_cache_remove(mapping_b, 10); - pmm_free_page(pb); - } - st_pass(); - - /* ---- insert null mapping returns -EINVAL ---- */ - st_begin("page_cache: insert null mapping returns -EINVAL"); - { - Page* page = pmm_alloc_page(); - int ret = page_cache_insert(nullptr, 0, page); - ST_ASSERT_EQ(ret, -EINVAL); - pmm_free_page(page); - } - st_pass(); - - /* ---- insert null page returns -EINVAL ---- */ - st_begin("page_cache: insert null page returns -EINVAL"); - { - int ret = page_cache_insert(mapping_a, 0, nullptr); - ST_ASSERT_EQ(ret, -EINVAL); - } - st_pass(); -} diff --git a/na/test/test_pid.cc b/na/test/test_pid.cc deleted file mode 100644 index e79eb1e..0000000 --- a/na/test/test_pid.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_pid.cc — Self-tests for PID allocator - * - * Tests: alloc, free, re-use, exhaustion guard, reserved PIDs. - */ - -#include -#include - -void selftest_pid() -{ - /* Create a local PidAllocator for isolated testing */ - PidAllocator pa; - pa.init(); - - /* ---- PID 0 is reserved (idle/swapper) ---- */ - st_begin("pid: PID 0 is reserved"); - { - /* First alloc should return 1 (PID 0 reserved for idle) */ - int32 first = pa.alloc(); - ST_ASSERT(first >= 1); - } - st_pass(); - - /* ---- Sequential allocation returns unique PIDs ---- */ - st_begin("pid: sequential allocation returns unique PIDs"); - { - PidAllocator pa2; - pa2.init(); - - int32 p1 = pa2.alloc(); - int32 p2 = pa2.alloc(); - int32 p3 = pa2.alloc(); - ST_ASSERT(p1 >= 1); - ST_ASSERT(p2 >= 1); - ST_ASSERT(p3 >= 1); - ST_ASSERT_NE(p1, p2); - ST_ASSERT_NE(p2, p3); - ST_ASSERT_NE(p1, p3); - } - st_pass(); - - /* ---- Freed PID can be reused ---- */ - st_begin("pid: freed PID can be reused"); - { - PidAllocator pa3; - pa3.init(); - - /* Allocate all PIDs from 2..N */ - constexpr int kAllocCount = 60; - int32 pids[kAllocCount]; - for (int i = 0; i < kAllocCount; i++) { - pids[i] = pa3.alloc(); - ST_ASSERT(pids[i] >= 1); - } - - /* Free one in the middle */ - int32 freed_pid = pids[kAllocCount / 2]; - pa3.free(freed_pid); - - /* Allocate again — should eventually get the freed PID */ - bool found = false; - for (int i = 0; i < kAllocCount; i++) { - int32 p = pa3.alloc(); - if (p == freed_pid) { found = true; break; } - pa3.free(p); - } - ST_ASSERT(found); - } - st_pass(); - - /* ---- free invalid PID is safe ---- */ - st_begin("pid: free invalid PID is no-op"); - { - PidAllocator pa4; - pa4.init(); - - /* These should not crash */ - pa4.free(-1); - pa4.free(kMaxPid); - pa4.free(kMaxPid + 100); - - /* Allocator should still work — PID 0 still reserved */ - int32 p = pa4.alloc(); - ST_ASSERT(p >= 1); - } - st_pass(); - - /* ---- Monotonic allocation before wrap ---- */ - st_begin("pid: monotonic allocation"); - { - PidAllocator pa5; - pa5.init(); - - int32 prev = pa5.alloc(); - for (int i = 0; i < 10; i++) { - int32 next = pa5.alloc(); - ST_ASSERT(next > prev); - prev = next; - } - } - st_pass(); -} diff --git a/na/test/test_pipe_devfs.cc b/na/test/test_pipe_devfs.cc deleted file mode 100644 index ed375ed..0000000 --- a/na/test/test_pipe_devfs.cc +++ /dev/null @@ -1,420 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_pipe_devfs.cc — Self-tests for pipe, /dev/null, /dev/zero, fcntl - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* Syscall entry points */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); - -/* ================================================================== */ -/* Helpers */ -/* ================================================================== */ - -static int64 open_dev(const char* path, int flags) -{ - return sys_openat( - static_cast(-100), /* AT_FDCWD */ - reinterpret_cast(path), - static_cast(flags), - 0, 0, 0); -} - -static int64 do_close(int64 fd) -{ - return sys_close(static_cast(fd), 0, 0, 0, 0, 0); -} - -static int64 do_read(int64 fd, void* buf, uint64 count) -{ - return sys_read(static_cast(fd), - reinterpret_cast(buf), count, 0, 0, 0); -} - -static int64 do_write(int64 fd, const void* buf, uint64 count) -{ - return sys_write(static_cast(fd), - reinterpret_cast(buf), count, 0, 0, 0); -} - -/* fcntl command constants — must match sys_fs.cc */ -inline constexpr int F_DUPFD = 0; -inline constexpr int F_GETFD = 1; -inline constexpr int F_SETFD = 2; -inline constexpr int F_GETFL = 3; -inline constexpr int F_SETFL = 4; -inline constexpr int F_DUPFD_CLOEXEC = 1030; -inline constexpr int FD_CLOEXEC = 1; - -/* ================================================================== */ -/* /dev/null tests */ -/* ================================================================== */ - -void selftest_devfs() -{ - /* ---- /dev/null: read returns EOF ---- */ - st_begin("devfs: /dev/null read returns 0 (EOF)"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - char buf[16]; - int64 n = do_read(fd, buf, sizeof(buf)); - ST_ASSERT_EQ(n, (int64)0); - do_close(fd); - } - st_pass(); - - /* ---- /dev/null: write discards data ---- */ - st_begin("devfs: /dev/null write discards data"); - { - int64 fd = open_dev("/dev/null", O_WRONLY); - ST_ASSERT(fd >= 0); - - const char* msg = "discarded"; - int64 n = do_write(fd, msg, 9); - ST_ASSERT_EQ(n, (int64)9); - do_close(fd); - } - st_pass(); - - /* ---- /dev/null: fstat reports S_IFCHR ---- */ - st_begin("devfs: /dev/null fstat is S_IFCHR"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - kstat st{}; - int64 ret = sys_fstat_wrapper( - static_cast(fd), - reinterpret_cast(&st), 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - ST_ASSERT(S_ISCHR(st.st_mode)); - do_close(fd); - } - st_pass(); - - /* ---- /dev/zero: read returns zeros ---- */ - st_begin("devfs: /dev/zero read returns zeros"); - { - int64 fd = open_dev("/dev/zero", O_RDONLY); - ST_ASSERT(fd >= 0); - - char buf[32]; - /* Pre-fill with non-zero */ - for (int i = 0; i < 32; i++) buf[i] = 0xFF; - - int64 n = do_read(fd, buf, 32); - ST_ASSERT_EQ(n, (int64)32); - - bool all_zero = true; - for (int i = 0; i < 32; i++) { - if (buf[i] != 0) { all_zero = false; break; } - } - ST_ASSERT(all_zero); - do_close(fd); - } - st_pass(); - - /* ---- /dev/zero: write discards data ---- */ - st_begin("devfs: /dev/zero write discards data"); - { - int64 fd = open_dev("/dev/zero", O_WRONLY); - ST_ASSERT(fd >= 0); - - char buf[16] = {1, 2, 3}; - int64 n = do_write(fd, buf, 16); - ST_ASSERT_EQ(n, (int64)16); - do_close(fd); - } - st_pass(); -} - -/* ================================================================== */ -/* Pipe tests */ -/* ================================================================== */ - -void selftest_pipe() -{ - /* ---- pipe2: basic creation ---- */ - st_begin("pipe: pipe2 creates two valid fds"); - { - int fds[2] = {-1, -1}; - int64 ret = sys_pipe2( - reinterpret_cast(fds), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - ST_ASSERT(fds[0] >= 0); - ST_ASSERT(fds[1] >= 0); - ST_ASSERT_NE(fds[0], fds[1]); - - do_close(fds[0]); - do_close(fds[1]); - } - st_pass(); - - /* ---- pipe: write then read ---- */ - st_begin("pipe: write then read returns correct data"); - { - int fds[2]; - sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); - - const char* msg = "pipe_test"; - int64 w = do_write(fds[1], msg, 9); - ST_ASSERT_EQ(w, (int64)9); - - char buf[16] = {}; - int64 r = do_read(fds[0], buf, 16); - ST_ASSERT_EQ(r, (int64)9); - - bool match = true; - for (int i = 0; i < 9; i++) { - if (buf[i] != msg[i]) { match = false; break; } - } - ST_ASSERT(match); - - do_close(fds[0]); - do_close(fds[1]); - } - st_pass(); - - /* ---- pipe: read from closed write end returns EOF ---- */ - st_begin("pipe: read after writer close returns EOF"); - { - int fds[2]; - sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); - - /* Write some data, then close write end */ - const char* msg = "eof"; - do_write(fds[1], msg, 3); - do_close(fds[1]); - - /* Read the data */ - char buf[16]; - int64 r1 = do_read(fds[0], buf, 16); - ST_ASSERT_EQ(r1, (int64)3); - - /* Next read should return 0 (EOF) */ - int64 r2 = do_read(fds[0], buf, 16); - ST_ASSERT_EQ(r2, (int64)0); - - do_close(fds[0]); - } - st_pass(); - - /* ---- pipe: write to closed read end returns -EPIPE ---- */ - st_begin("pipe: write after reader close returns -EPIPE"); - { - int fds[2]; - sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); - - /* Close read end */ - do_close(fds[0]); - - /* Write should fail */ - const char* msg = "broken"; - int64 w = do_write(fds[1], msg, 6); - ST_ASSERT_EQ(w, (int64)-EPIPE); - - do_close(fds[1]); - } - st_pass(); - - /* ---- pipe: multiple writes accumulate ---- */ - st_begin("pipe: multiple writes accumulate in buffer"); - { - int fds[2]; - sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); - - do_write(fds[1], "AAA", 3); - do_write(fds[1], "BBB", 3); - - char buf[16] = {}; - int64 r = do_read(fds[0], buf, 16); - ST_ASSERT_EQ(r, (int64)6); - ST_ASSERT_EQ(buf[0], 'A'); - ST_ASSERT_EQ(buf[3], 'B'); - - do_close(fds[0]); - do_close(fds[1]); - } - st_pass(); - - /* ---- pipe: read partial ---- */ - st_begin("pipe: partial read leaves remainder"); - { - int fds[2]; - sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); - - do_write(fds[1], "ABCDEF", 6); - - char buf[3] = {}; - int64 r1 = do_read(fds[0], buf, 3); - ST_ASSERT_EQ(r1, (int64)3); - ST_ASSERT_EQ(buf[0], 'A'); - ST_ASSERT_EQ(buf[2], 'C'); - - int64 r2 = do_read(fds[0], buf, 3); - ST_ASSERT_EQ(r2, (int64)3); - ST_ASSERT_EQ(buf[0], 'D'); - ST_ASSERT_EQ(buf[2], 'F'); - - do_close(fds[0]); - do_close(fds[1]); - } - st_pass(); -} - -/* ================================================================== */ -/* fcntl tests */ -/* ================================================================== */ - -void selftest_fcntl() -{ - /* ---- fcntl F_DUPFD ---- */ - st_begin("fcntl: F_DUPFD duplicates fd"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD, 0, 0, 0, 0); - ST_ASSERT(fd2 >= 0); - ST_ASSERT_NE(fd, fd2); - - /* Both should be valid reads */ - char buf[4]; - int64 r = do_read(fd2, buf, 4); - ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */ - - do_close(fd); - do_close(fd2); - } - st_pass(); - - /* ---- fcntl F_DUPFD with min_fd ---- */ - st_begin("fcntl: F_DUPFD respects min_fd"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD, 100, 0, 0, 0); - ST_ASSERT(fd2 >= 100); - - do_close(fd); - do_close(fd2); - } - st_pass(); - - /* ---- fcntl F_GETFD / F_SETFD ---- */ - st_begin("fcntl: F_GETFD/F_SETFD cloexec"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - /* Initially no CLOEXEC */ - int64 flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); - ST_ASSERT_EQ(flags, (int64)0); - - /* Set CLOEXEC */ - int64 ret = sys_fcntl(static_cast(fd), F_SETFD, FD_CLOEXEC, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - - /* Verify it was set */ - flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); - ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); - - /* Clear it */ - sys_fcntl(static_cast(fd), F_SETFD, 0, 0, 0, 0); - flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); - ST_ASSERT_EQ(flags, (int64)0); - - do_close(fd); - } - st_pass(); - - /* ---- fcntl F_GETFL ---- */ - st_begin("fcntl: F_GETFL returns open flags"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); - /* O_RDONLY == 0, so result should have no write bits */ - ST_ASSERT_EQ(fl & O_WRONLY, (int64)0); - - do_close(fd); - } - st_pass(); - - /* ---- fcntl F_SETFL O_NONBLOCK ---- */ - st_begin("fcntl: F_SETFL toggles O_NONBLOCK"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - /* Set O_NONBLOCK */ - sys_fcntl(static_cast(fd), F_SETFL, O_NONBLOCK, 0, 0, 0); - int64 fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); - ST_ASSERT(fl & O_NONBLOCK); - - /* Clear it */ - sys_fcntl(static_cast(fd), F_SETFL, 0, 0, 0, 0); - fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); - ST_ASSERT_EQ(fl & O_NONBLOCK, (int64)0); - - do_close(fd); - } - st_pass(); - - /* ---- fcntl F_DUPFD_CLOEXEC ---- */ - st_begin("fcntl: F_DUPFD_CLOEXEC sets cloexec"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD_CLOEXEC, 0, 0, 0, 0); - ST_ASSERT(fd2 >= 0); - - int64 flags = sys_fcntl(static_cast(fd2), F_GETFD, 0, 0, 0, 0); - ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); - - do_close(fd); - do_close(fd2); - } - st_pass(); - - /* ---- fcntl bad fd ---- */ - st_begin("fcntl: bad fd returns -EBADF"); - { - int64 ret = sys_fcntl(999, F_GETFD, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EBADF); - } - st_pass(); - - /* ---- fcntl unknown cmd ---- */ - st_begin("fcntl: unknown cmd returns -EINVAL"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 ret = sys_fcntl(static_cast(fd), 9999, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EINVAL); - do_close(fd); - } - st_pass(); -} diff --git a/na/test/test_pmm.cc b/na/test/test_pmm.cc deleted file mode 100644 index 3330866..0000000 --- a/na/test/test_pmm.cc +++ /dev/null @@ -1,283 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Physical Memory Manager (PMM) - * - * Tests: - * 1. Single page alloc/free round-trip - * 2. Page address alignment (4KB) - * 3. No duplicate pages from consecutive allocs - * 4. Free then re-alloc returns the same page (PCP cache) - * 5. Multi-page (order > 0) alloc/free - * 6. Bulk alloc stress test (alloc many, free all) - * 7. Reference counting (incref/decref) - * 8. page_to_pa / pa_to_page round-trip - */ - -#include -#include -#include - -void selftest_pmm() -{ - st_begin("pmm: single page alloc/free"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->type == PageType::Allocated); - ST_ASSERT(page->order == 0); - - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % kPageSize, 0ULL); // 4KB aligned - - uint64 va = page_to_va(page).raw; - ST_ASSERT_EQ(va, pa_to_va(phys_addr(pa)).raw); - - pmm_free_page(page); - } - st_pass(); - - st_begin("pmm: no duplicate pages"); - { - Page* p1 = pmm_alloc_page(); - Page* p2 = pmm_alloc_page(); - ST_ASSERT(p1 != nullptr); - ST_ASSERT(p2 != nullptr); - ST_ASSERT_NE(p1, p2); - ST_ASSERT_NE(page_to_pa(p1).raw, page_to_pa(p2).raw); - pmm_free_page(p2); - pmm_free_page(p1); - } - st_pass(); - - st_begin("pmm: free then re-alloc (PCP)"); - { - Page* p1 = pmm_alloc_page(); - uint64 pa1 = page_to_pa(p1).raw; - pmm_free_page(p1); - - // PCP should return the most recently freed page - Page* p2 = pmm_alloc_page(); - uint64 pa2 = page_to_pa(p2).raw; - ST_ASSERT_EQ(pa1, pa2); - pmm_free_page(p2); - } - st_pass(); - - st_begin("pmm: multi-page alloc (order 2 = 4 pages)"); - { - Page* page = pmm_alloc_pages(2); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->order == 2); - - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % (kPageSize * 4), 0ULL); // Naturally aligned - - pmm_free_pages(page, 2); - } - st_pass(); - - st_begin("pmm: bulk alloc/free stress (256 pages)"); - { - constexpr int N = 256; - Page* pages[N]; - - // Allocate all - for (int i = 0; i < N; i++) { - pages[i] = pmm_alloc_page(); - ST_ASSERT(pages[i] != nullptr); - } - - // Verify no duplicates (spot check: consecutive pairs) - for (int i = 1; i < N; i++) { - ST_ASSERT_NE(pages[i], pages[i - 1]); - } - - // Free all - for (int i = 0; i < N; i++) { - pmm_free_page(pages[i]); - } - } - st_pass(); - - st_begin("pmm: page_to_pa / pa_to_page round-trip"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - uint64 pa = page_to_pa(page).raw; - Page* page2 = pa_to_page(phys_addr(pa)); - ST_ASSERT_EQ(page, page2); - pmm_free_page(page); - } - st_pass(); - - st_begin("pmm: refcount incref/decref"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->refcount.load() == 1); - - page_incref(page); - ST_ASSERT(page->refcount.load() == 2); - - page_decref(page); // 2 -> 1, should NOT free - ST_ASSERT(page->refcount.load() == 1); - ST_ASSERT(page->type == PageType::Allocated); - - page_decref(page); // 1 -> 0, should free to pool - // After decref to 0, page is returned to pool. - // We can't safely check page->type because it might be reused. - } - st_pass(); - st_begin("pmm: pmm_alloc_pages natural alignment (order-3)"); - { - Page* page = pmm_alloc_pages(3); - ST_ASSERT(page != nullptr); - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % (kPageSize * 8), 0ULL); - pmm_free_pages(page, 3); - } - st_pass(); - - st_begin("pmm: page_decref to 0 and reuse (order-0)"); - { - Page* page = pmm_alloc_page(); - uint64 pa = page_to_pa(page).raw; - page_decref(page); // drops 1 -> 0, freeing it - - // Next alloc from PCP should give back the same page - Page* page2 = pmm_alloc_page(); - ST_ASSERT_EQ(page_to_pa(page2).raw, pa); - pmm_free_page(page2); - } - st_pass(); - - st_begin("pmm: page_decref to 0 on multi-page blocks (order-3)"); - { - Page* page = pmm_alloc_pages(3); - uint64 pa = page_to_pa(page).raw; - page_decref(page); // Should internally route to pmm_free_pages(page, 3) - - // Re-allocate order-3 and see if we get it back (buddy coalescing was respected) - Page* page2 = pmm_alloc_pages(3); - ST_ASSERT_EQ(page_to_pa(page2).raw, pa); - pmm_free_pages(page2, 3); - } - st_pass(); - - st_begin("pmm: buddy split and block coalescing correctness"); - { - // Force split: allocate order-1, free it, allocate two order-0 - Page* page_ord1 = pmm_alloc_pages(1); - uint64 pa1 = page_to_pa(page_ord1).raw; - pmm_free_pages(page_ord1, 1); - - Page* p0_a = pmm_alloc_page(); - Page* p0_b = pmm_alloc_page(); - - // Free them to trigger coalesce - pmm_free_page(p0_a); - pmm_free_page(p0_b); - - // Allocate order-1 again to verify successful coalescing - Page* page_ord1_again = pmm_alloc_pages(1); - ST_ASSERT_EQ(page_to_pa(page_ord1_again).raw, pa1); - pmm_free_pages(page_ord1_again, 1); - } - st_pass(); - - st_begin("pmm: mixed order cross allocation"); - { - Page* p0 = pmm_alloc_page(); - Page* p2 = pmm_alloc_pages(2); - Page* p0b = pmm_alloc_page(); - - ST_ASSERT(p0 != nullptr); - ST_ASSERT(p2 != nullptr); - ST_ASSERT(p0b != nullptr); - ST_ASSERT_NE(page_to_pa(p0).raw, page_to_pa(p2).raw); - - pmm_free_page(p0); - pmm_free_pages(p2, 2); - pmm_free_page(p0b); - } - st_pass(); - - st_begin("pmm: PCP drain/refill boundaries"); - { - // kPcpHighWatermark = 64. Allocating and freeing 65 blocks triggers drain. - Page* pages[65]; - for (int i=0; i<65; i++) pages[i] = pmm_alloc_page(); - for (int i=0; i<65; i++) pmm_free_page(pages[i]); - - // The system shouldn't crash and PCP logic should handle this. - Page* verify_page = pmm_alloc_page(); - ST_ASSERT(verify_page != nullptr); - pmm_free_page(verify_page); - } - st_pass(); - - st_begin("pmm: OOM exhaustion stability"); - { - Page* head = nullptr; - // First allocate large chunks to be fast - while (true) { - Page* page = pmm_alloc_pages(5); - if (!page) break; - uint64* ptr = reinterpret_cast(page_to_va(page).raw); - *ptr = reinterpret_cast(head); - // encode order in the second word - *(ptr + 1) = 5; - head = page; - } - // Then allocate single frames until absolute zero - while (true) { - Page* page = pmm_alloc_page(); - if (!page) break; - uint64* ptr = reinterpret_cast(page_to_va(page).raw); - *ptr = reinterpret_cast(head); - *(ptr + 1) = 0; - head = page; - } - - // We hit nullptr. System is completely exhausted. - ST_ASSERT(pmm_alloc_page() == nullptr); - - // Free everything - while (head) { - uint64* ptr = reinterpret_cast(page_to_va(head).raw); - Page* nxt = reinterpret_cast(*ptr); - uint64 order = *(ptr + 1); - if (order == 5) pmm_free_pages(head, 5); - else pmm_free_page(head); - head = nxt; - } - } - st_pass(); - - st_begin("pmm: out of bounds order allocation gracefully fails"); - { - // kMaxOrder is 11, so passing 11 or higher should safely return nullptr. - Page* page = pmm_alloc_pages(11); - ST_ASSERT(page == nullptr); - page = pmm_alloc_pages(255); - ST_ASSERT(page == nullptr); - } - st_pass(); - - st_begin("pmm: pmm_get_mem_base / pmm_get_mem_size sanity"); - { - uint64 base = pmm_get_mem_base().raw; - uint64 size = pmm_get_mem_size(); - - // We no longer assert on QEMU specific memory address - // ST_ASSERT(base >= 0x80000000ULL); - ST_ASSERT(size > 0); - // Size must be page-aligned - ST_ASSERT_EQ(size % kPageSize, 0ULL); - // Base must be page-aligned - ST_ASSERT_EQ(base % kPageSize, 0ULL); - } - st_pass(); -} diff --git a/na/test/test_random.cc b/na/test/test_random.cc deleted file mode 100644 index d546410..0000000 --- a/na/test/test_random.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include - -void selftest_random() -{ - /* Test 1: Basic 64-bit generation */ - kprintf("[test] random: basic 64-bit generation ... "); - uint64 v1 = get_random_u64(); - uint64 v2 = get_random_u64(); - if (v1 == v2) { - kernel_panic("random: two consecutive generations were identical!"); - } - kprintf("PASS\n"); - - /* Test 2: Basic 32-bit generation */ - kprintf("[test] random: basic 32-bit generation ... "); - uint32 v3 = get_random_u32(); - uint32 v4 = get_random_u32(); - if (v3 == v4) { - kernel_panic("random: two consecutive 32-bit generations were identical!"); - } - kprintf("PASS\n"); - - /* Test 3: Byte generation */ - kprintf("[test] random: byte generation ... "); - uint8 buf1[16]; - uint8 buf2[16]; - get_random_bytes(buf1, sizeof(buf1)); - get_random_bytes(buf2, sizeof(buf2)); - - bool all_zero1 = true; - bool all_zero2 = true; - bool same = true; - - for (int i = 0; i < 16; i++) { - if (buf1[i] != 0) all_zero1 = false; - if (buf2[i] != 0) all_zero2 = false; - if (buf1[i] != buf2[i]) same = false; - } - - if (all_zero1 || all_zero2 || same) { - kernel_panic("random: byte generation statistical failure!"); - } - kprintf("PASS\n"); -} diff --git a/na/test/test_rbtree.cc b/na/test/test_rbtree.cc deleted file mode 100644 index 27f049a..0000000 --- a/na/test/test_rbtree.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include - -struct TestNode { - int key; - struct rb_node rb; -}; - -static bool node_less(struct rb_node *node, const struct rb_node *parent) { - TestNode *n = rb_entry(node, TestNode, rb); - const TestNode *p = reinterpret_cast( - reinterpret_cast(parent) - __builtin_offsetof(TestNode, rb)); - return n->key < p->key; -} - -static int node_cmp(const void *key, const struct rb_node *node) { - int k = *static_cast(key); - const TestNode *n = reinterpret_cast( - reinterpret_cast(node) - __builtin_offsetof(TestNode, rb)); - if (k < n->key) return -1; - if (k > n->key) return 1; - return 0; -} - -void selftest_rbtree() { - st_begin("rbtree: basic insert, find, erase"); - { - struct rb_root tree = RB_ROOT; - TestNode n1{10, {}}, n2{20, {}}, n3{5, {}}; - - rb_add(&n1.rb, &tree, node_less); - rb_add(&n2.rb, &tree, node_less); - rb_add(&n3.rb, &tree, node_less); - - int search_key = 20; - struct rb_node *found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == &n2.rb); - - search_key = 99; - found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == nullptr); - - rb_erase(&n2.rb, &tree); - // Verify erased - search_key = 20; - found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == nullptr); - } - st_pass(); - - st_begin("rbtree: ordered iteration"); - { - struct rb_root tree = RB_ROOT; - TestNode nodes[5] = { {50, {}}, {10, {}}, {40, {}}, {20, {}}, {30, {}} }; - for (int i=0; i<5; i++) { - rb_add(&nodes[i].rb, &tree, node_less); - } - - int expected[] = {10, 20, 30, 40, 50}; - int idx = 0; - struct rb_node *node = rb_first(&tree); - while (node) { - TestNode *n = rb_entry(node, TestNode, rb); - ST_ASSERT_EQ(n->key, expected[idx]); - idx++; - node = rb_next(node); - } - ST_ASSERT_EQ(idx, 5); - } - st_pass(); - - st_begin("rbtree: large scale insert/erase balance"); - { - struct rb_root tree = RB_ROOT; - constexpr int N = 200; - static TestNode nodes[N]; // Static to avoid blowing up the BSP stack - for (int i=0; i -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ======================================================================== - * Helper: create a minimal test TCB (no real kernel stack, just metadata) - * ======================================================================== */ - -static TaskControlBlock g_test_tcbs[8]; - -static TaskControlBlock* make_test_tcb(int index, const char* name, - uint8 priority, uint8 cpu) -{ - TaskControlBlock* t = &g_test_tcbs[index]; - t->context = {}; - t->wait_chld.init("test_wq"); - t->name = name; - t->priority = priority; - t->cpu_id = cpu; - t->pid = 100 + index; - t->tgid = 100 + index; - t->state = TaskState::Runnable; - t->time_slice = priority_to_timeslice(priority); - t->run_link.init(); - t->all_link.init(); - t->children.init(); - t->sibling.init(); - t->mm = nullptr; - t->trapframe = nullptr; - t->kstack_pa = 0; - t->kstack_va = 0; - t->flags = TF_KTHREAD; - t->sched_class = &g_sched_class_o1; - t->sched_entity = &t->run_link; - return t; -} - -/* ======================================================================== - * Test cases - * ======================================================================== */ - -static bool test_current_task() -{ - /* After sched_init, current_task() should be the BSP boot TCB */ - TaskControlBlock* curr = current_task(); - return curr != nullptr && curr->state == TaskState::Running; -} - -static bool test_priority_to_timeslice() -{ - /* Higher priority (lower number) should get longer time slice */ - int32 ts_high = priority_to_timeslice(0); - int32 ts_mid = priority_to_timeslice(15); - int32 ts_low = priority_to_timeslice(31); - - return ts_high > ts_mid && ts_mid > ts_low && ts_low >= 1; -} - -static bool test_o1_pick_next_priority() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t_lo = make_test_tcb(0, "test_p5", 5, 0); - TaskControlBlock* t_hi = make_test_tcb(1, "test_p2", 2, 0); - - g_sched_class_o1.enqueue_task(&rq, t_lo); - g_sched_class_o1.enqueue_task(&rq, t_hi); - - TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); - return next == t_hi; -} - -static bool test_o1_fifo_order() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t0 = make_test_tcb(0, "test_q0", 10, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_q1", 10, 0); - TaskControlBlock* t2 = make_test_tcb(2, "test_q2", 10, 0); - - g_sched_class_o1.enqueue_task(&rq, t0); - g_sched_class_o1.enqueue_task(&rq, t1); - g_sched_class_o1.enqueue_task(&rq, t2); - - TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); - if (next != t0) return false; - g_sched_class_o1.dequeue_task(&rq, t0); - - next = g_sched_class_o1.pick_next_task(&rq); - if (next != t1) return false; - g_sched_class_o1.dequeue_task(&rq, t1); - - next = g_sched_class_o1.pick_next_task(&rq); - if (next != t2) return false; - - return true; -} - -static bool test_o1_nr_running() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t0 = make_test_tcb(0, "test_n0", 8, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_n1", 9, 0); - - if (rq.nr_running() != 0 || rq.o1().nr_running() != 0) return false; - - g_sched_class_o1.enqueue_task(&rq, t0); - if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; - - g_sched_class_o1.enqueue_task(&rq, t1); - if (rq.nr_running() != 2 || rq.o1().nr_running() != 2) return false; - - g_sched_class_o1.dequeue_task(&rq, t0); - if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; - - g_sched_class_o1.dequeue_task(&rq, t1); - return rq.nr_running() == 0 && rq.o1().nr_running() == 0; -} - -static bool test_o1_pick_next_empty() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - return g_sched_class_o1.pick_next_task(&rq) == nullptr; -} - -static bool test_priority_to_timeslice_bounds() -{ - /* Boundary values */ - int32 ts0 = priority_to_timeslice(0); - int32 ts31 = priority_to_timeslice(31); - - return ts0 == 20 && ts31 >= 1; -} - -static bool test_enqueue_dequeue() -{ - /* Create a test task and enqueue/dequeue it */ - TaskControlBlock* t = make_test_tcb(0, "test_ed", 10, 0); - - sched_enqueue(t); - if (t->state != TaskState::Runnable) return false; - - sched_dequeue(t); - /* After dequeue, run_link should be self-pointing */ - return t->run_link.next == &t->run_link; -} - -static bool test_multiple_enqueue_dequeue() -{ - /* Enqueue 3 tasks at different priorities, dequeue all */ - TaskControlBlock* t0 = make_test_tcb(0, "test_m0", 5, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_m1", 10, 0); - TaskControlBlock* t2 = make_test_tcb(2, "test_m2", 20, 0); - - sched_enqueue(t0); - sched_enqueue(t1); - sched_enqueue(t2); - - sched_dequeue(t0); - sched_dequeue(t1); - sched_dequeue(t2); - - return t0->run_link.is_empty() && - t1->run_link.is_empty() && - t2->run_link.is_empty(); -} - -static bool test_current_task_name() -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->name) return false; - - /* BSP boot TCB should be named "kernel_main" */ - const char* expected = "kernel_main"; - const char* actual = curr->name; - while (*expected && *actual) { - if (*expected != *actual) return false; - expected++; - actual++; - } - return *expected == *actual; -} - -/* ======================================================================== - * Test runner - * ======================================================================== */ - -void selftest_sched() -{ - st_begin("sched: current_task is valid after init"); - ST_ASSERT(test_current_task()); - st_pass(); - - st_begin("sched: current_task name is kernel_main"); - ST_ASSERT(test_current_task_name()); - st_pass(); - - st_begin("sched: priority_to_timeslice ordering"); - ST_ASSERT(test_priority_to_timeslice()); - st_pass(); - - st_begin("sched: priority_to_timeslice bounds"); - ST_ASSERT(test_priority_to_timeslice_bounds()); - st_pass(); - - st_begin("sched: enqueue/dequeue round-trip"); - ST_ASSERT(test_enqueue_dequeue()); - st_pass(); - - st_begin("sched: multiple enqueue/dequeue"); - ST_ASSERT(test_multiple_enqueue_dequeue()); - st_pass(); - - st_begin("sched: o1 pick_next priority"); - ST_ASSERT(test_o1_pick_next_priority()); - st_pass(); - - st_begin("sched: o1 FIFO order"); - ST_ASSERT(test_o1_fifo_order()); - st_pass(); - - st_begin("sched: o1 nr_running accounting"); - ST_ASSERT(test_o1_nr_running()); - st_pass(); - - st_begin("sched: o1 pick_next empty"); - ST_ASSERT(test_o1_pick_next_empty()); - st_pass(); -} - diff --git a/na/test/test_signal.cc b/na/test/test_signal.cc deleted file mode 100644 index 360b0e6..0000000 --- a/na/test/test_signal.cc +++ /dev/null @@ -1,193 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_signal.cc — Self-tests for signal infrastructure - * - * Tests the kernel-internal signal APIs directly (not via syscalls, - * since syscalls use copy_to_user which rejects kernel-space buffers). - */ - -#include -#include -#include -#include -#include -#include -#include - -/* From signal.cc */ -extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); - -/* Drain all queued sigqueue entries from a task's pending list */ -static void flush_pending(TaskControlBlock* task) -{ - task->pending.signal = sigset_t{}; - list_node* head = &task->pending.list; - while (head->next != head) { - list_node* node = head->next; - head->next = node->next; - node->next->prev = head; - auto* q = list_entry(node); - delete q; - } -} - -void selftest_signal() -{ - TaskControlBlock* curr = current_task(); - - /* Ensure sighand exists for this task */ - if (!curr->sighand) { - curr->sighand = new sighand_struct; - curr->sighand->init(); - } - - /* Ensure pending list is initialized */ - if (curr->pending.list.next == nullptr) { - curr->pending.list.next = &curr->pending.list; - curr->pending.list.prev = &curr->pending.list; - curr->pending.signal = sigset_t{}; - } - - /* ---- send_signal queues a signal ---- */ - st_begin("signal: send_signal queues to pending"); - { - curr->pending.signal = sigset_t{}; - - kernel_siginfo info{}; - info.si_signo = SIGUSR1; - info.si_code = 0; - - int ret = send_signal(SIGUSR1, &info, curr); - ST_ASSERT_EQ(ret, 0); - ST_ASSERT(curr->pending.signal.has(SIGUSR1)); - - flush_pending(curr); - } - st_pass(); - - /* ---- send_signal: invalid signal returns -EINVAL ---- */ - st_begin("signal: send_signal invalid sig returns -EINVAL"); - { - int ret = send_signal(0, nullptr, curr); - ST_ASSERT_EQ(ret, -EINVAL); - - ret = send_signal(_NSIG + 1, nullptr, curr); - ST_ASSERT_EQ(ret, -EINVAL); - } - st_pass(); - - /* ---- send_signal: null task returns -ESRCH ---- */ - st_begin("signal: send_signal null task returns -ESRCH"); - { - int ret = send_signal(SIGUSR1, nullptr, nullptr); - ST_ASSERT_EQ(ret, -ESRCH); - } - st_pass(); - - /* ---- duplicate standard signal is coalesced ---- */ - st_begin("signal: duplicate standard signal coalesced"); - { - curr->pending.signal = sigset_t{}; - - send_signal(SIGUSR1, nullptr, curr); - ST_ASSERT(curr->pending.signal.has(SIGUSR1)); - - int ret = send_signal(SIGUSR1, nullptr, curr); - ST_ASSERT_EQ(ret, 0); - - flush_pending(curr); - } - st_pass(); - - /* ---- sigset_t: add/del/has ---- */ - st_begin("signal: sigset_t add/del/has"); - { - sigset_t s{}; - ST_ASSERT(!s.has(SIGUSR1)); - s.add(SIGUSR1); - ST_ASSERT(s.has(SIGUSR1)); - s.del(SIGUSR1); - ST_ASSERT(!s.has(SIGUSR1)); - } - st_pass(); - - /* ---- sigset_t: bitwise operators ---- */ - st_begin("signal: sigset_t bitwise OR and AND"); - { - sigset_t a{}; - a.add(SIGUSR1); - sigset_t b{}; - b.add(SIGUSR2); - - sigset_t c = a | b; - ST_ASSERT(c.has(SIGUSR1)); - ST_ASSERT(c.has(SIGUSR2)); - - sigset_t d = c & a; - ST_ASSERT(d.has(SIGUSR1)); - ST_ASSERT(!d.has(SIGUSR2)); - } - st_pass(); - - /* ---- blocked mask: direct manipulation ---- */ - st_begin("signal: blocked mask direct manipulation"); - { - sigset_t saved = curr->blocked; - curr->blocked = sigset_t{}; - - curr->blocked.add(SIGUSR1); - curr->blocked.add(SIGUSR2); - ST_ASSERT(curr->blocked.has(SIGUSR1)); - ST_ASSERT(curr->blocked.has(SIGUSR2)); - - curr->blocked.del(SIGUSR1); - ST_ASSERT(!curr->blocked.has(SIGUSR1)); - ST_ASSERT(curr->blocked.has(SIGUSR2)); - - curr->blocked = saved; - } - st_pass(); - - /* ---- sighand action table ---- */ - st_begin("signal: sighand action table defaults to SIG_DFL"); - { - /* All handlers should default to SIG_DFL (0) */ - kernel_sigaction* ka = &curr->sighand->action[SIGUSR1 - 1]; - ST_ASSERT_EQ(ka->sa_handler, (uint64)SIG_DFL); - - /* Set a handler and verify */ - ka->sa_handler = 0x12345678ULL; - ST_ASSERT_EQ(ka->sa_handler, (uint64)0x12345678ULL); - - /* Reset */ - ka->sa_handler = SIG_DFL; - } - st_pass(); - - /* ---- sig_kernel_only rejects SIGKILL/SIGSTOP handlers ---- */ - st_begin("signal: SIGKILL/SIGSTOP are kernel-only"); - { - ST_ASSERT(sig_kernel_only(SIGKILL)); - ST_ASSERT(sig_kernel_only(SIGSTOP)); - ST_ASSERT(!sig_kernel_only(SIGUSR1)); - ST_ASSERT(!sig_kernel_only(SIGINT)); - } - st_pass(); - - /* ---- sig_default_action correctness ---- */ - st_begin("signal: sig_default_action returns correct actions"); - { - ST_ASSERT_EQ((int)sig_default_action(SIGCHLD), (int)SIG_ACT_IGN); - ST_ASSERT_EQ((int)sig_default_action(SIGKILL), (int)SIG_ACT_TERM); - ST_ASSERT_EQ((int)sig_default_action(SIGSTOP), (int)SIG_ACT_STOP); - ST_ASSERT_EQ((int)sig_default_action(SIGCONT), (int)SIG_ACT_CONT); - ST_ASSERT_EQ((int)sig_default_action(SIGSEGV), (int)SIG_ACT_CORE); - } - st_pass(); - - /* Clean up */ - flush_pending(curr); - curr->blocked = sigset_t{}; -} diff --git a/na/test/test_slab.cc b/na/test/test_slab.cc deleted file mode 100644 index 8a448b4..0000000 --- a/na/test/test_slab.cc +++ /dev/null @@ -1,255 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Slab Allocator (kmalloc/kfree) - * - * Tests: - * 1. Small allocation (8 bytes) and free - * 2. Multiple size classes - * 3. No aliasing between consecutive allocs - * 4. Alignment check (minimum 8-byte aligned) - * 5. Bulk alloc/free stress - * 6. operator new / operator delete - * 7. Large allocation (> 2048, falls back to PMM) - * 8. Zero-size and null-free edge cases - */ - -#include -#include -#include -#include - -/* Forward declarations — defined in slab.cc */ -void* kmalloc(uint32 size); -void kfree(void* ptr); - -void selftest_slab() -{ - st_begin("slab: small alloc (8 bytes)"); - { - void* p = kmalloc(8); - ST_ASSERT(p != nullptr); - // Write and read back to verify the memory is usable - *static_cast(p) = 0xDEADBEEFCAFEBABEULL; - ST_ASSERT_EQ(*static_cast(p), 0xDEADBEEFCAFEBABEULL); - kfree(p); - } - st_pass(); - - st_begin("slab: multiple size classes"); - { - uint32 sizes[] = {8, 16, 32, 64, 128, 256, 512, 1024, 2048}; - void* ptrs[9]; - for (int i = 0; i < 9; i++) { - ptrs[i] = kmalloc(sizes[i]); - ST_ASSERT(ptrs[i] != nullptr); - // Write a marker - *static_cast(ptrs[i]) = static_cast(i); - } - // Verify markers (no cross-contamination) - for (int i = 0; i < 9; i++) { - ST_ASSERT_EQ(*static_cast(ptrs[i]), static_cast(i)); - } - for (int i = 0; i < 9; i++) { - kfree(ptrs[i]); - } - } - st_pass(); - - st_begin("slab: no aliasing"); - { - void* a = kmalloc(32); - void* b = kmalloc(32); - ST_ASSERT(a != nullptr); - ST_ASSERT(b != nullptr); - ST_ASSERT_NE(a, b); - kfree(b); - kfree(a); - } - st_pass(); - - st_begin("slab: alignment (8-byte minimum)"); - { - for (int i = 0; i < 16; i++) { - void* p = kmalloc(7); // odd size, should still be 8-aligned - ST_ASSERT(p != nullptr); - ST_ASSERT_EQ(reinterpret_cast(p) % 8, 0ULL); - kfree(p); - } - } - st_pass(); - - st_begin("slab: bulk alloc/free stress (128 x 64B)"); - { - constexpr int N = 128; - void* ptrs[N]; - for (int i = 0; i < N; i++) { - ptrs[i] = kmalloc(64); - ST_ASSERT(ptrs[i] != nullptr); - } - // Free in reverse order - for (int i = N - 1; i >= 0; i--) { - kfree(ptrs[i]); - } - } - st_pass(); - - st_begin("slab: operator new/delete"); - { - struct TestObj { - uint64 a; - uint64 b; - }; - TestObj* obj = new TestObj{42, 99}; - ST_ASSERT(obj != nullptr); - ST_ASSERT_EQ(obj->a, 42ULL); - ST_ASSERT_EQ(obj->b, 99ULL); - delete obj; - } - st_pass(); - - st_begin("slab: large alloc (4096 bytes, PMM fallback)"); - { - void* p = kmalloc(4096); - ST_ASSERT(p != nullptr); - ST_ASSERT_EQ(reinterpret_cast(p) % kPageSize, 0ULL); - kfree(p); - } - st_pass(); - - st_begin("slab: edge cases (zero size, null free)"); - { - void* p = kmalloc(0); - ST_ASSERT(p == nullptr); - kfree(nullptr); // should not crash - } - st_pass(); - st_begin("slab: LIFO freelist reallocation (same address)"); - { - void* a = kmalloc(32); - kfree(a); - void* b = kmalloc(32); - ST_ASSERT_EQ(a, b); - kfree(b); - } - st_pass(); - - st_begin("slab: boundary conditions (2048 bytes size class)"); - { - // 2048 bytes should still use slab (2 objects per page) - // We allocate 3 to guarantee at least two fall in the exact same slab page - void* p1 = kmalloc(2048); - void* p2 = kmalloc(2048); - void* p3 = kmalloc(2048); - - auto diff = [](void* a, void* b) -> uint64 { - uint64 ua = reinterpret_cast(a); - uint64 ub = reinterpret_cast(b); - return ua > ub ? ua - ub : ub - ua; - }; - - // At least one adjacent pair MUST be exactly separated by 2048 bytes - bool found_2048 = (diff(p1, p2) == 2048ULL) || (diff(p2, p3) == 2048ULL); - ST_ASSERT(found_2048); - - kfree(p1); - kfree(p2); - kfree(p3); - } - st_pass(); - - st_begin("slab: cross-page allocation (> 1 page equivalent objects)"); - { - // Allocating more than one page worth of 32-byte objects (4096 / 32 = 128) - constexpr int NUM = 150; - void* ptrs[NUM]; - for (int i=0; i(p) % kPageSize == 0) { - base_ptr = p; - break; - } - drain[drain_count++] = p; - } - - // base_ptr is the very first object in a fresh slab page! - // The page holds exactly 32 objects (4096 / 128 = 32). We already hold 1 (base_ptr). - void* ptrs[32]; - ptrs[0] = base_ptr; - for (int i = 1; i < 32; i++) { - ptrs[i] = kmalloc(128); - } - - // At this point, we completely own all 32 objects of this slab page. - uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast(base_ptr))).raw; - Page* page = pa_to_page(phys_addr(page_pa)); - - ST_ASSERT(page->type == PageType::Slab); - ST_ASSERT(page->slab.obj_count == 32); - - // Free all items in this isolated slab page - for (int i = 0; i < 32; i++) { - kfree(ptrs[i]); - } - - // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached). - ST_ASSERT(page->type == PageType::Free || page->type == PageType::PcpCached); - - // Clean up the drain array - for (int i = 0; i < drain_count; i++) { - kfree(drain[i]); - } - } - st_pass(); - - st_begin("slab: continuous operator new/delete memory leak loop"); - { - struct BigObj { uint8 data[512]; }; - BigObj* ptr = nullptr; - // 10K allocations to ensure no memory leak causes OOM - for (int i = 0; i < 10000; i++) { - ptr = new BigObj; - ST_ASSERT(ptr != nullptr); - delete ptr; - } - } - st_pass(); - - st_begin("slab: allocation pressure (mixed sizes)"); - { - constexpr int MAX_ITEMS = 100; - void* ptrs[MAX_ITEMS]; - uint32 sizes[MAX_ITEMS]; - - for (int i = 0; i < MAX_ITEMS; i++) { - sizes[i] = (i * 17) % 2000 + 8; // Pseudo-random sizes between 8 and 2008 - ptrs[i] = kmalloc(sizes[i]); - ST_ASSERT(ptrs[i] != nullptr); - ST_ASSERT(reinterpret_cast(ptrs[i]) % 8 == 0); - } - - for (int i = 0; i < MAX_ITEMS; i++) { - kfree(ptrs[i]); - } - } - st_pass(); -} diff --git a/na/test/test_smp.cc b/na/test/test_smp.cc deleted file mode 100644 index f934526..0000000 --- a/na/test/test_smp.cc +++ /dev/null @@ -1,136 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_smp.cc — Post-SMP boot tests - * - * These tests run after all APs are online. They verify: - * 1. IPI delivery — BSP sends IPI to each AP, AP increments counter. - * 2. Task migration — a task moves from one runqueue to another. - * 3. TLB shootdown IPI — cross-CPU flush. - * - * Called from the init thread (PID 1) context, NOT from the pre-SMP - * self-test suite. - */ - -#include -#include -#include -#include -#include -#include - -/* Shared counter incremented by APs when they receive an IPI */ -static lume::atomic g_ipi_ack_count{0}; - -/* ------------------------------------------------------------------ */ -/* IPI delivery test */ -/* ------------------------------------------------------------------ */ - -void selftest_ipi_delivery() -{ - st_begin("smp: IPI delivery to all APs"); - - /* Reset counter */ - g_ipi_ack_count.store(0, __ATOMIC_RELAXED); - - uint32 bsp = static_cast(arch::cpu::id()); - uint32 num_cpus = 4; /* TODO: derive from FDT */ - - /* Build mask of all APs */ - uint64 ap_mask = 0; - uint32 expected = 0; - for (uint32 i = 0; i < num_cpus; i++) { - if (i == bsp) continue; - ap_mask |= (1ULL << i); - expected++; - } - - /* Send reschedule IPI to all APs */ - smp_send_ipi(ap_mask, IPI_RESCHEDULE); - - /* Wait a short while for APs to process the interrupt. - * APs are in wfi (idle loop), so they'll wake immediately - * on interrupt delivery. We just need to give them time - * to run the handler. */ - for (volatile int i = 0; i < 100000; i++) { - /* spin-wait */ - } - - /* We can't directly check that IPI was received because - * IPI_RESCHEDULE just sets need_resched — no ack. - * Instead verify no crash occurred and the IPI infrastructure - * didn't deadlock. This is a liveness test. */ - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* IPI halt test (send to self, skip) */ -/* ------------------------------------------------------------------ */ - -void selftest_ipi_self_skip() -{ - st_begin("smp: IPI reason bitmask accumulation"); - - uint32 cpu = static_cast(arch::cpu::id()); - - /* Manually set multiple reasons atomically */ - g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED); - g_ipi_data[cpu].pending.fetch_or(IPI_RESCHEDULE, __ATOMIC_RELEASE); - g_ipi_data[cpu].pending.fetch_or(IPI_TLB_SHOOTDOWN, __ATOMIC_RELEASE); - - uint32 val = g_ipi_data[cpu].pending.load(__ATOMIC_ACQUIRE); - ST_ASSERT_EQ(val, IPI_RESCHEDULE | IPI_TLB_SHOOTDOWN); - - /* Clean up */ - g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED); - - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* TLB shootdown IPI test */ -/* ------------------------------------------------------------------ */ - -void selftest_tlb_shootdown() -{ - st_begin("smp: TLB shootdown IPI (cross-CPU flush)"); - - uint32 bsp = static_cast(arch::cpu::id()); - uint32 num_cpus = 4; - - /* Build AP mask */ - uint64 ap_mask = 0; - for (uint32 i = 0; i < num_cpus; i++) { - if (i == bsp) continue; - ap_mask |= (1ULL << i); - } - - /* Set TLB range on each AP's IpiData */ - for (uint32 i = 0; i < num_cpus; i++) { - if (i == bsp) continue; - g_ipi_data[i].tlb_start = 0; - g_ipi_data[i].tlb_end = 0; /* 0,0 = full flush */ - } - - /* Send TLB shootdown */ - smp_send_ipi(ap_mask, IPI_TLB_SHOOTDOWN); - - /* Wait for APs to process */ - for (volatile int i = 0; i < 100000; i++) {} - - /* If we got here without panic, TLB shootdown succeeded */ - st_pass(); -} - -/* ------------------------------------------------------------------ */ -/* Public entry point */ -/* ------------------------------------------------------------------ */ - -void selftest_smp() -{ - selftest_ipi_delivery(); - selftest_ipi_self_skip(); - selftest_tlb_shootdown(); -} diff --git a/na/test/test_spinlock.cc b/na/test/test_spinlock.cc deleted file mode 100644 index be794a0..0000000 --- a/na/test/test_spinlock.cc +++ /dev/null @@ -1,135 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Spinlock - * - * Tests: - * 1. Basic acquire/release - * 2. LockGuard RAII acquire/release - * 3. Nested locking (two different locks) - * 4. Interrupt state preservation across lock/unlock - * 5. is_held_by_current_cpu correctness - * - * Note: We cannot test multi-core contention in BSP-only self-test. - * That requires AP participation (future Phase 2 test). - */ - -#include -#include -#include -#include - -void selftest_spinlock() -{ - st_begin("spinlock: basic acquire/release"); - { - Spinlock lock; - lock.init("test_basic"); - - ST_ASSERT(!lock.is_held_by_current_cpu()); - - lock.acquire(); - ST_ASSERT(lock.is_held_by_current_cpu()); - - lock.release(); - ST_ASSERT(!lock.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: LockGuard RAII"); - { - Spinlock lock; - lock.init("test_guard"); - - { - LockGuard guard(lock); - ST_ASSERT(lock.is_held_by_current_cpu()); - } - // After scope exit, lock should be released - ST_ASSERT(!lock.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: nested locking (two locks)"); - { - Spinlock lock_a, lock_b; - lock_a.init("test_nest_a"); - lock_b.init("test_nest_b"); - - lock_a.acquire(); - ST_ASSERT(lock_a.is_held_by_current_cpu()); - - lock_b.acquire(); - ST_ASSERT(lock_b.is_held_by_current_cpu()); - - lock_b.release(); - ST_ASSERT(!lock_b.is_held_by_current_cpu()); - ST_ASSERT(lock_a.is_held_by_current_cpu()); // A still held - - lock_a.release(); - ST_ASSERT(!lock_a.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: interrupt state preservation"); - { - Spinlock lock; - lock.init("test_intr"); - - // Interrupts are currently off (BSP init period), but let's - // verify the lock doesn't corrupt the state. - bool before = arch::cpu::intr_enabled(); - - lock.acquire(); - // Inside lock, interrupts must be disabled - ST_ASSERT(!arch::cpu::intr_enabled()); - lock.release(); - - // After release, interrupt state should be restored - ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); - } - st_pass(); - st_begin("spinlock: deep nesting tracking (> 2)"); - { - Spinlock l1, l2, l3; - l1.init("l1"); - l2.init("l2"); - l3.init("l3"); - - bool before = arch::cpu::intr_enabled(); - - l1.acquire(); - l2.acquire(); - l3.acquire(); - - ST_ASSERT(!arch::cpu::intr_enabled()); - - l3.release(); - ST_ASSERT(!arch::cpu::intr_enabled()); - - l2.release(); - ST_ASSERT(!arch::cpu::intr_enabled()); - - l1.release(); - ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); - } - st_pass(); - - st_begin("spinlock: interrupt leak detection logic (lock_depth metadata)"); - { - Spinlock l; - l.init("l"); - - auto& state = sync::g_cpu_sync_states[arch::cpu::id()]; - int initial_depth = state.lock_depth; - - l.acquire(); - // The lock depth should strictly increase - ST_ASSERT_EQ(state.lock_depth, initial_depth + 1); - l.release(); - - ST_ASSERT_EQ(state.lock_depth, initial_depth); - } - st_pass(); -} diff --git a/na/test/test_sys_fs_extra.cc b/na/test/test_sys_fs_extra.cc deleted file mode 100644 index 705ce63..0000000 --- a/na/test/test_sys_fs_extra.cc +++ /dev/null @@ -1,238 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_sys_fs_extra.cc — Self-tests for additional filesystem syscalls - * - * Tests: dup3, chdir/getcwd, writev, ioctl (stub), unlinkat (stub). - * Complements test_vfs_io.cc which covers the core read/write/open/close. - */ - -#include -#include -#include -#include -#include -#include - -/* Syscall entry points */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); - -/* ================================================================== */ -/* Helpers */ -/* ================================================================== */ - -static int64 open_dev(const char* path, int flags) -{ - return sys_openat( - static_cast(-100), - reinterpret_cast(path), - static_cast(flags), 0, 0, 0); -} - -static int64 do_close(int64 fd) -{ - return sys_close(static_cast(fd), 0, 0, 0, 0, 0); -} - -static int64 do_read(int64 fd, void* buf, uint64 count) -{ - return sys_read(static_cast(fd), - reinterpret_cast(buf), count, 0, 0, 0); -} - - -/* iovec must match sys_fs.cc definition */ -struct test_iovec { - void* iov_base; - uint64 iov_len; -}; - -/* ================================================================== */ -/* Tests */ -/* ================================================================== */ - -void selftest_sys_fs_extra() -{ - /* ---- dup3: duplicate fd to specific target ---- */ - st_begin("sys_fs: dup3 duplicates to specific fd"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - /* dup3 to fd 50 */ - int64 fd2 = sys_dup3(static_cast(fd), 50, 0, 0, 0, 0); - ST_ASSERT_EQ(fd2, (int64)50); - - /* Both should work */ - char buf[4]; - int64 r = do_read(fd2, buf, 4); - ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */ - - do_close(fd); - do_close(fd2); - } - st_pass(); - - /* ---- dup3: same fd returns -EINVAL ---- */ - st_begin("sys_fs: dup3 same fd returns -EINVAL"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 ret = sys_dup3(static_cast(fd), - static_cast(fd), 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EINVAL); - - do_close(fd); - } - st_pass(); - - /* ---- chdir to /dev, getcwd returns /dev ---- */ - st_begin("sys_fs: chdir then getcwd returns new dir"); - { - /* Save cwd */ - char saved_cwd[64] = {}; - sys_getcwd(reinterpret_cast(saved_cwd), 64, 0, 0, 0, 0); - - /* chdir to /dev */ - int64 ret = sys_chdir( - reinterpret_cast("/dev"), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - - /* getcwd should return /dev */ - char buf[64] = {}; - int64 len = sys_getcwd( - reinterpret_cast(buf), 64, 0, 0, 0, 0); - ST_ASSERT(len > 0); - ST_ASSERT_EQ(buf[0], '/'); - ST_ASSERT_EQ(buf[1], 'd'); - ST_ASSERT_EQ(buf[2], 'e'); - ST_ASSERT_EQ(buf[3], 'v'); - - /* Restore cwd to / */ - sys_chdir(reinterpret_cast("/"), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- chdir to nonexistent returns -ENOENT ---- */ - st_begin("sys_fs: chdir nonexistent returns -ENOENT"); - { - int64 ret = sys_chdir( - reinterpret_cast("/no_such_dir"), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-ENOENT); - } - st_pass(); - - /* ---- chdir to file returns -ENOTDIR ---- */ - st_begin("sys_fs: chdir to file returns -ENOTDIR"); - { - int64 ret = sys_chdir( - reinterpret_cast("/dev/null"), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-ENOTDIR); - } - st_pass(); - - /* ---- writev: scatter write to file ---- */ - st_begin("sys_fs: writev scatter write"); - { - /* Create a file, write with writev, read back */ - int64 fd = sys_openat( - static_cast(-100), - reinterpret_cast("/writev_test"), - O_CREAT | O_RDWR, 0644, 0, 0); - ST_ASSERT(fd >= 0); - - char a[] = "AAA"; - char b[] = "BBB"; - test_iovec iov[2]; - iov[0].iov_base = a; - iov[0].iov_len = 3; - iov[1].iov_base = b; - iov[1].iov_len = 3; - - int64 w = sys_writev( - static_cast(fd), - reinterpret_cast(iov), 2, 0, 0, 0); - ST_ASSERT_EQ(w, (int64)6); - - /* Seek back to start and read */ - sys_lseek(static_cast(fd), 0, 0 /* SEEK_SET */, 0, 0, 0); - - char buf[8] = {}; - int64 r = do_read(fd, buf, 8); - ST_ASSERT_EQ(r, (int64)6); - ST_ASSERT_EQ(buf[0], 'A'); - ST_ASSERT_EQ(buf[3], 'B'); - - do_close(fd); - } - st_pass(); - - /* ---- writev: bad fd returns -EBADF ---- */ - st_begin("sys_fs: writev bad fd returns -EBADF"); - { - test_iovec iov[1]; - char dummy[] = "x"; - iov[0].iov_base = dummy; - iov[0].iov_len = 1; - - int64 ret = sys_writev(999, - reinterpret_cast(iov), 1, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EBADF); - } - st_pass(); - - /* ---- ioctl stub returns -ENOTTY ---- */ - st_begin("sys_fs: ioctl stub returns -ENOTTY"); - { - int64 fd = open_dev("/dev/null", O_RDONLY); - ST_ASSERT(fd >= 0); - - int64 ret = sys_ioctl(static_cast(fd), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-ENOTTY); - - do_close(fd); - } - st_pass(); - - /* ---- unlinkat stub returns -ENOSYS ---- */ - st_begin("sys_fs: unlinkat stub returns -ENOSYS"); - { - int64 ret = sys_unlinkat( - static_cast(-100), - reinterpret_cast("/some_file"), - 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-ENOSYS); - } - st_pass(); - - /* ---- getcwd: null buf returns -EINVAL ---- */ - st_begin("sys_fs: getcwd null buf returns -EINVAL"); - { - int64 ret = sys_getcwd(0, 64, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EINVAL); - } - st_pass(); - - /* ---- getcwd: zero size returns -EINVAL ---- */ - st_begin("sys_fs: getcwd zero size returns -EINVAL"); - { - char buf[4]; - int64 ret = sys_getcwd( - reinterpret_cast(buf), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EINVAL); - } - st_pass(); -} diff --git a/na/test/test_sys_mm.cc b/na/test/test_sys_mm.cc deleted file mode 100644 index 2c1a7dc..0000000 --- a/na/test/test_sys_mm.cc +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include -#include -#include - -/* sys_mm.cc APIs */ -int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64); -int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, - uint64 flags, uint64 fd, uint64 offset); -int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64); -int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, - uint64, uint64, uint64); - -void selftest_sys_mm() -{ - st_begin("sys_mm: mmap invalid arguments"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_mmap(0, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 1), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_SHARED, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0x123, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), -EINVAL); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mmap/munmap/mprotect basic flow"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - int64 addr = sys_mmap(0, 0x3000, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - ST_ASSERT(addr > 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), 0); - - int64 mp = sys_mprotect(addr + kPageSize, kPageSize, PROT_READ, 0, 0, 0); - ST_ASSERT_EQ(mp, 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr + kPageSize, VmFaultCause::Write), -EFAULT); - - int64 mu = sys_munmap(addr, 0x3000, 0, 0, 0, 0); - ST_ASSERT_EQ(mu, 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), -EFAULT); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: MAP_FIXED replaces existing mapping"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - uint64 base = 0x600000; - ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast(base)); - - ST_ASSERT_EQ(sys_mmap(base, 0x1000, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast(base)); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Write), 0); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mmap hint fallback on overlap"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - uint64 base = 0x700000; - ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast(base)); - - int64 hint = sys_mmap(base, 0x1000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - ST_ASSERT(hint > 0); - ST_ASSERT(hint != static_cast(base)); - ST_ASSERT_EQ(vmm_handle_page_fault(space, hint, VmFaultCause::Read), 0); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: brk growth and shrink"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - int64 brk0 = sys_brk(0x400000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk0, 0x400000); - - int64 brk1 = sys_brk(0x402000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk1, 0x402000); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x400000, VmFaultCause::Read), 0); - - int64 brk2 = sys_brk(0x400000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk2, 0x400000); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x401000, VmFaultCause::Read), -EFAULT); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: brk invalid shrink below start"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_brk(0x500000, 0, 0, 0, 0, 0), 0x500000); - ST_ASSERT_EQ(sys_brk(0x4FF000, 0, 0, 0, 0, 0), -EINVAL); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mprotect invalid range"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) > 0, true); - ST_ASSERT_EQ(sys_mprotect(0x123, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mprotect(0x900000, 0x1000, PROT_READ, 0, 0, 0), -ENOMEM); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); -} diff --git a/na/test/test_sys_process.cc b/na/test/test_sys_process.cc deleted file mode 100644 index 686bed6..0000000 --- a/na/test/test_sys_process.cc +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_sys_process.cc — Self-tests for process-control syscalls - * - * Tests: getpid, gettid, getppid, getuid, getgid, set_tid_address, - * sched_yield, uname. - */ - -#include -#include -#include -#include - -/* Syscall entry points */ -int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); - -void selftest_sys_process() -{ - /* ---- getpid returns current tgid ---- */ - st_begin("sys_process: getpid returns valid pid"); - { - int64 pid = sys_getpid(0, 0, 0, 0, 0, 0); - /* Boot task pid is -1 but tgid may differ; just verify it returns */ - TaskControlBlock* curr = current_task(); - ST_ASSERT_EQ(pid, (int64)curr->tgid); - } - st_pass(); - - /* ---- gettid returns current pid (tid) ---- */ - st_begin("sys_process: gettid returns current tid"); - { - int64 tid = sys_gettid(0, 0, 0, 0, 0, 0); - TaskControlBlock* curr = current_task(); - ST_ASSERT_EQ(tid, (int64)curr->pid); - } - st_pass(); - - /* ---- getppid returns parent tgid ---- */ - st_begin("sys_process: getppid returns parent tgid"); - { - int64 ppid = sys_getppid(0, 0, 0, 0, 0, 0); - TaskControlBlock* curr = current_task(); - int64 expected = curr->parent ? (int64)curr->parent->tgid : 0; - ST_ASSERT_EQ(ppid, expected); - } - st_pass(); - - /* ---- getuid returns 0 (root) ---- */ - st_begin("sys_process: getuid returns 0 (root)"); - { - int64 uid = sys_getuid(0, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(uid, (int64)0); - } - st_pass(); - - /* ---- getgid returns 0 (root) ---- */ - st_begin("sys_process: getgid returns 0 (root)"); - { - int64 gid = sys_getgid(0, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(gid, (int64)0); - } - st_pass(); - - /* ---- set_tid_address stores pointer and returns tid ---- */ - st_begin("sys_process: set_tid_address returns tid"); - { - uint64 dummy_addr = 0x12345678ULL; - int64 tid = sys_set_tid_address(dummy_addr, 0, 0, 0, 0, 0); - TaskControlBlock* curr = current_task(); - ST_ASSERT_EQ(tid, (int64)curr->pid); - ST_ASSERT_EQ(curr->clear_child_tid, dummy_addr); - - /* Reset it to avoid side effects */ - curr->clear_child_tid = 0; - } - st_pass(); - - /* ---- sched_yield returns 0 ---- */ - st_begin("sys_process: sched_yield returns 0"); - { - int64 ret = sys_sched_yield_wrapper(0, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - } -} - diff --git a/na/test/test_trap.cc b/na/test/test_trap.cc deleted file mode 100644 index c91948a..0000000 --- a/na/test/test_trap.cc +++ /dev/null @@ -1,197 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot self-tests for the Trap/IRQ subsystem. - * - * Tests MD→MI translation, IrqManager register/dispatch/unregister, - * and stvec installation verification. - */ - -#include -#include -#include -#include -#include -#include -#include - -TrapCause trap_translate_scause(uint64 scause); - -/* ======================================================================== - * IrqManager test helpers - * ======================================================================== */ - -static volatile uint32 g_test_irq_fired = 0; -static volatile void* g_test_irq_arg = nullptr; - -static void test_irq_handler(uint32 irq, void* arg) -{ - g_test_irq_fired = irq; - g_test_irq_arg = arg; -} - -/* ======================================================================== - * Test cases - * ======================================================================== */ - -static bool test_stvec_installed() { - uint64 vec = arch::trap::get_vector(); - return vec != 0; -} - -static bool test_scause_exception_translation() { - /* Verify all exception causes map correctly */ - bool ok = true; - ok = ok && (trap_translate_scause(EXC_LOAD_PAGE_FAULT) == TrapCause::PageFaultLoad); - ok = ok && (trap_translate_scause(EXC_STORE_PAGE_FAULT) == TrapCause::PageFaultStore); - ok = ok && (trap_translate_scause(EXC_INST_PAGE_FAULT) == TrapCause::PageFaultExec); - ok = ok && (trap_translate_scause(EXC_ECALL_FROM_U) == TrapCause::Syscall); - ok = ok && (trap_translate_scause(EXC_ILLEGAL_INST) == TrapCause::IllegalInst); - ok = ok && (trap_translate_scause(EXC_BREAKPOINT) == TrapCause::Breakpoint); - ok = ok && (trap_translate_scause(EXC_LOAD_ACCESS_FAULT) == TrapCause::AccessFault); - ok = ok && (trap_translate_scause(EXC_INST_MISALIGNED) == TrapCause::AlignFault); - return ok; -} - -static bool test_scause_interrupt_translation() { - bool ok = true; - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_TIMER) == TrapCause::IrqTimer); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_SOFTWARE) == TrapCause::IrqSoftware); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_EXTERNAL) == TrapCause::IrqExternal); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | 99) == TrapCause::Unknown); - return ok; -} - -static bool test_scause_unknown() { - /* ecall from S-mode should map to Unknown (we only handle U-mode ecall) */ - return trap_translate_scause(EXC_ECALL_FROM_S) == TrapCause::Unknown; -} - -static bool test_irqmanager_register_dispatch() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - g_test_irq_arg = nullptr; - - int dummy_arg = 42; - int ret = mgr.register_handler(10, test_irq_handler, &dummy_arg); - if (ret != 0) return false; - - /* Dispatch IRQ 10 — handler should be called */ - mgr.dispatch(10); - - if (g_test_irq_fired != 10) return false; - if (g_test_irq_arg != &dummy_arg) return false; - - return true; -} - -static bool test_irqmanager_unregister() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - - mgr.register_handler(5, test_irq_handler, nullptr); - mgr.unregister_handler(5); - - /* Dispatch after unregister — handler should NOT be called */ - mgr.dispatch(5); - - return g_test_irq_fired == 0; -} - -static bool test_irqmanager_double_register() { - IrqManager mgr; - mgr.init(); - - int ret1 = mgr.register_handler(7, test_irq_handler, nullptr); - int ret2 = mgr.register_handler(7, test_irq_handler, nullptr); - - return ret1 == 0 && ret2 == -EBUSY; -} - -static bool test_irqmanager_invalid_irq() { - IrqManager mgr; - mgr.init(); - - int ret = mgr.register_handler(999, test_irq_handler, nullptr); - return ret == -EINVAL; -} - -static bool test_irqmanager_has_handler() { - IrqManager mgr; - mgr.init(); - - if (mgr.has_handler(10)) return false; /* not yet registered */ - - mgr.register_handler(10, test_irq_handler, nullptr); - if (!mgr.has_handler(10)) return false; /* now registered */ - - mgr.unregister_handler(10); - if (mgr.has_handler(10)) return false; /* unregistered */ - - return true; -} - -static bool test_irqmanager_spurious_dispatch() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - - /* Dispatch an unregistered IRQ — should not crash or fire */ - mgr.dispatch(0); - mgr.dispatch(63); - - return g_test_irq_fired == 0; -} - -/* ======================================================================== - * Test runner - * ======================================================================== */ - -void selftest_trap() -{ - st_begin("trap: stvec is installed"); - ST_ASSERT(test_stvec_installed()); - st_pass(); - - st_begin("trap: scause exception translation"); - ST_ASSERT(test_scause_exception_translation()); - st_pass(); - - st_begin("trap: scause interrupt translation"); - ST_ASSERT(test_scause_interrupt_translation()); - st_pass(); - - st_begin("trap: scause unknown (ecall from S)"); - ST_ASSERT(test_scause_unknown()); - st_pass(); - - st_begin("trap: IrqManager register/dispatch"); - ST_ASSERT(test_irqmanager_register_dispatch()); - st_pass(); - - st_begin("trap: IrqManager unregister"); - ST_ASSERT(test_irqmanager_unregister()); - st_pass(); - - st_begin("trap: IrqManager double register"); - ST_ASSERT(test_irqmanager_double_register()); - st_pass(); - - st_begin("trap: IrqManager invalid IRQ"); - ST_ASSERT(test_irqmanager_invalid_irq()); - st_pass(); - - st_begin("trap: IrqManager has_handler"); - ST_ASSERT(test_irqmanager_has_handler()); - st_pass(); - - st_begin("trap: IrqManager spurious dispatch"); - ST_ASSERT(test_irqmanager_spurious_dispatch()); - st_pass(); -} diff --git a/na/test/test_uaccess.cc b/na/test/test_uaccess.cc deleted file mode 100644 index c5a8c6c..0000000 --- a/na/test/test_uaccess.cc +++ /dev/null @@ -1,97 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -void selftest_uaccess() -{ - /* To test valid user addresses (< kKernelVaBase), we need a writable page mapped there. - * We cannot use VmSpace::create() because it does not yet inherit kernel mappings, - * so switching to it hangs. We also cannot map it with the U bit because S-mode - * would need the SUM bit enabled. - * So we manually allocate a physical page and map it into the CURRENT page table - * at 0x100000 without the U bit, just for this test. - */ - uint64 mock_user_addr = 0x100000; - - uint64 satp; - asm volatile("csrr %0, satp" : "=r"(satp)); - uint64 root_pa = (satp & 0xFFFFFFFFFFFULL) << 12; - - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - uint64 paddr = page_to_pa(page).raw; - - pmap::map(root_pa, mock_user_addr, paddr, pmap::PTE_R | pmap::PTE_W | pmap::PTE_V | pmap::PTE_A | pmap::PTE_D); - - /* Flush TLB to see the new mapping */ - tlb_shootdown_all(); - - st_begin("uaccess: copy_to_user valid range"); - char kernel_buf[32] = "Hello User Space!"; - int ret = copy_to_user(mock_user_addr, kernel_buf, 18); - ST_ASSERT_EQ(ret, 0); - st_pass(); - - st_begin("uaccess: copy_from_user valid range"); - char read_buf[32] = {0}; - /* We wrote "Hello User Space!" to mock_user_addr previously */ - ret = copy_from_user(read_buf, mock_user_addr, 18); - ST_ASSERT_EQ(ret, 0); - ST_ASSERT(read_buf[0] == 'H'); - ST_ASSERT(read_buf[1] == 'e'); - ST_ASSERT(read_buf[6] == 'U'); - st_pass(); - - st_begin("uaccess: strncpy_from_user valid range"); - char str_buf[32] = {0}; - int64 len = strncpy_from_user(str_buf, mock_user_addr, 32); - ST_ASSERT_EQ(len, 17); /* "Hello User Space!" is 17 chars long */ - ST_ASSERT(str_buf[0] == 'H'); - st_pass(); - - st_begin("uaccess: strncpy_from_user max_len truncation"); - char trunc_buf[32] = {0}; - len = strncpy_from_user(trunc_buf, mock_user_addr, 6); - ST_ASSERT_EQ(len, 5); /* Will copy 5 chars and NUL terminate */ - ST_ASSERT(trunc_buf[4] == 'o'); - ST_ASSERT(trunc_buf[5] == '\0'); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (crosses boundary)"); - ret = copy_to_user(kKernelVaBase - 10, kernel_buf, 20); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (entirely in kernel)"); - ret = copy_to_user(kKernelVaBase + 0x1000, kernel_buf, 20); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (overflow)"); - ret = copy_to_user(0xFFFFFFFFFFFFFFF0ULL, kernel_buf, 0x100); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_from_user invalid ranges"); - ret = copy_from_user(read_buf, kKernelVaBase - 5, 10); - ST_ASSERT_EQ(ret, -EFAULT); - - ret = copy_from_user(read_buf, kKernelVaBase, 10); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: strncpy_from_user invalid range"); - uint64 invalid_addr = kKernelVaBase + 0x1000; - len = strncpy_from_user(str_buf, invalid_addr, 10); - ST_ASSERT_EQ(len, -EFAULT); - st_pass(); -} diff --git a/na/test/test_vfs_io.cc b/na/test/test_vfs_io.cc deleted file mode 100644 index 3d3b125..0000000 --- a/na/test/test_vfs_io.cc +++ /dev/null @@ -1,345 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_vfs_io.cc — Self-tests for VFS file I/O via syscalls - * - * Tests openat, read, write, close, lseek, dup, fstat, getdents64, - * getcwd, chdir, and mkdirat through the kernel syscall layer. - */ - -#include -#include -#include -#include -#include -#include -#include - -/* Syscall entry points */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64); - -/* Helper: open a file, returns fd or negative error */ -static int64 open_file(const char* path, int flags, uint32 mode) -{ - return sys_openat( - static_cast(-100), /* AT_FDCWD */ - reinterpret_cast(path), - static_cast(flags), - static_cast(mode), 0, 0); -} - -void selftest_vfs_io() -{ - /* ---- openat O_CREAT creates a file ---- */ - st_begin("vfs_io: openat O_CREAT creates file"); - { - int64 fd = open_file("/vfs_test_file", O_CREAT | O_WRONLY, 0644); - ST_ASSERT(fd >= 0); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- openat existing file ---- */ - st_begin("vfs_io: openat existing file succeeds"); - { - int64 fd = open_file("/vfs_test_file", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- openat nonexistent without O_CREAT returns -ENOENT ---- */ - st_begin("vfs_io: openat nonexistent returns -ENOENT"); - { - int64 fd = open_file("/no_such_file_ever", O_RDONLY, 0); - ST_ASSERT_EQ(fd, (int64)-ENOENT); - } - st_pass(); - - /* ---- write then read ---- */ - st_begin("vfs_io: write then read back data"); - { - /* Write */ - int64 fd = open_file("/vfs_rw_test", O_CREAT | O_WRONLY, 0644); - ST_ASSERT(fd >= 0); - - const char* msg = "Hello VFS!"; - int64 written = sys_write( - static_cast(fd), - reinterpret_cast(msg), - 10, 0, 0, 0); - ST_ASSERT_EQ(written, (int64)10); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - - /* Read */ - fd = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - char buf[32] = {}; - int64 nread = sys_read( - static_cast(fd), - reinterpret_cast(buf), - sizeof(buf), 0, 0, 0); - ST_ASSERT_EQ(nread, (int64)10); - - bool match = true; - for (int i = 0; i < 10; i++) { - if (buf[i] != msg[i]) { match = false; break; } - } - ST_ASSERT(match); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- read past EOF returns 0 ---- */ - st_begin("vfs_io: read past EOF returns 0"); - { - int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - char buf[64]; - /* First read: 10 bytes */ - int64 n1 = sys_read(static_cast(fd), - reinterpret_cast(buf), 64, 0, 0, 0); - ST_ASSERT_EQ(n1, (int64)10); - - /* Second read: should be 0 (EOF) */ - int64 n2 = sys_read(static_cast(fd), - reinterpret_cast(buf), 64, 0, 0, 0); - ST_ASSERT_EQ(n2, (int64)0); - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- O_TRUNC clears file content ---- */ - st_begin("vfs_io: O_TRUNC clears file content"); - { - int64 fd = open_file("/vfs_rw_test", O_WRONLY | O_TRUNC, 0); - ST_ASSERT(fd >= 0); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - - fd = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - char buf[16]; - int64 n = sys_read(static_cast(fd), - reinterpret_cast(buf), 16, 0, 0, 0); - ST_ASSERT_EQ(n, (int64)0); /* truncated = empty */ - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- close invalid fd returns -EBADF ---- */ - st_begin("vfs_io: close bad fd returns -EBADF"); - { - int64 ret = sys_close(999, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EBADF); - } - st_pass(); - - /* ---- read/write on bad fd returns -EBADF ---- */ - st_begin("vfs_io: read/write bad fd returns -EBADF"); - { - char buf[8]; - int64 r = sys_read(999, reinterpret_cast(buf), 8, 0, 0, 0); - ST_ASSERT_EQ(r, (int64)-EBADF); - - int64 w = sys_write(999, reinterpret_cast(buf), 8, 0, 0, 0); - ST_ASSERT_EQ(w, (int64)-EBADF); - } - st_pass(); - - /* ---- lseek SEEK_SET / SEEK_CUR / SEEK_END ---- */ - st_begin("vfs_io: lseek SEEK_SET/CUR/END"); - { - /* Write 20 bytes */ - int64 fd = open_file("/vfs_lseek_test", O_CREAT | O_RDWR, 0644); - ST_ASSERT(fd >= 0); - - char data[20]; - for (int i = 0; i < 20; i++) data[i] = static_cast('A' + i); - sys_write(static_cast(fd), - reinterpret_cast(data), 20, 0, 0, 0); - - /* SEEK_SET to 5 */ - int64 pos = sys_lseek(static_cast(fd), 5, SEEK_SET, 0, 0, 0); - ST_ASSERT_EQ(pos, (int64)5); - - /* Read 1 byte, should be 'F' */ - char c = 0; - sys_read(static_cast(fd), - reinterpret_cast(&c), 1, 0, 0, 0); - ST_ASSERT_EQ(c, 'F'); - - /* SEEK_CUR +3 → pos 9 */ - pos = sys_lseek(static_cast(fd), 3, SEEK_CUR, 0, 0, 0); - ST_ASSERT_EQ(pos, (int64)9); - - /* SEEK_END -1 → pos 19 */ - pos = sys_lseek(static_cast(fd), -1, SEEK_END, 0, 0, 0); - ST_ASSERT_EQ(pos, (int64)19); - - sys_read(static_cast(fd), - reinterpret_cast(&c), 1, 0, 0, 0); - ST_ASSERT_EQ(c, 'T'); - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- lseek invalid whence returns -EINVAL ---- */ - st_begin("vfs_io: lseek invalid whence"); - { - int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - int64 ret = sys_lseek(static_cast(fd), 0, 99, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)-EINVAL); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- dup: duplicate fd shares file position ---- */ - st_begin("vfs_io: dup shares file position"); - { - int64 fd1 = open_file("/vfs_lseek_test", O_RDONLY, 0); - ST_ASSERT(fd1 >= 0); - - int64 fd2 = sys_dup(static_cast(fd1), 0, 0, 0, 0, 0); - ST_ASSERT(fd2 >= 0); - ST_ASSERT_NE(fd1, fd2); - - /* Both should work for reading */ - char c = 0; - int64 n = sys_read(static_cast(fd2), - reinterpret_cast(&c), 1, 0, 0, 0); - ST_ASSERT_EQ(n, (int64)1); - - sys_close(static_cast(fd1), 0, 0, 0, 0, 0); - sys_close(static_cast(fd2), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- fstat: basic stat ---- */ - st_begin("vfs_io: fstat returns valid kstat"); - { - int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - kstat st{}; - int64 ret = sys_fstat_wrapper( - static_cast(fd), - reinterpret_cast(&st), 0, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - ST_ASSERT(S_ISREG(st.st_mode)); - ST_ASSERT_EQ(st.st_size, (int64)20); - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- getcwd: returns "/" ---- */ - st_begin("vfs_io: getcwd returns /"); - { - char buf[64] = {}; - int64 len = sys_getcwd( - reinterpret_cast(buf), 64, 0, 0, 0, 0); - ST_ASSERT(len > 0); - ST_ASSERT_EQ(buf[0], '/'); - } - st_pass(); - - /* ---- mkdirat: create directory ---- */ - st_begin("vfs_io: mkdirat creates directory"); - { - int64 ret = sys_mkdirat( - static_cast(-100), - reinterpret_cast("/vfs_io_dir"), - 0755, 0, 0, 0); - ST_ASSERT_EQ(ret, (int64)0); - - /* Open it to confirm it's a directory */ - int64 fd = open_file("/vfs_io_dir", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - kstat st{}; - sys_fstat_wrapper(static_cast(fd), - reinterpret_cast(&st), 0, 0, 0, 0); - ST_ASSERT(S_ISDIR(st.st_mode)); - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- getdents64: list root directory ---- */ - st_begin("vfs_io: getdents64 lists root children"); - { - int64 fd = open_file("/", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - char buf[512] = {}; - int64 n = sys_getdents64( - static_cast(fd), - reinterpret_cast(buf), - 512, 0, 0, 0); - ST_ASSERT(n > 0); /* At least "." and ".." */ - - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- write to O_RDONLY fd returns -EBADF ---- */ - st_begin("vfs_io: write to O_RDONLY returns -EBADF"); - { - int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - char buf[] = "x"; - int64 w = sys_write(static_cast(fd), - reinterpret_cast(buf), 1, 0, 0, 0); - ST_ASSERT_EQ(w, (int64)-EBADF); - sys_close(static_cast(fd), 0, 0, 0, 0, 0); - } - st_pass(); - - /* ---- double close returns -EBADF ---- */ - st_begin("vfs_io: double close returns -EBADF"); - { - int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd >= 0); - - int64 r1 = sys_close(static_cast(fd), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(r1, (int64)0); - - int64 r2 = sys_close(static_cast(fd), 0, 0, 0, 0, 0); - ST_ASSERT_EQ(r2, (int64)-EBADF); - } - st_pass(); - - /* ---- fd reuse after close ---- */ - st_begin("vfs_io: fd reuse after close"); - { - int64 fd1 = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd1 >= 0); - sys_close(static_cast(fd1), 0, 0, 0, 0, 0); - - int64 fd2 = open_file("/vfs_rw_test", O_RDONLY, 0); - ST_ASSERT(fd2 >= 0); - /* fd2 should reuse the slot of fd1 */ - ST_ASSERT(fd2 <= fd1); - sys_close(static_cast(fd2), 0, 0, 0, 0, 0); - } - st_pass(); -} diff --git a/na/test/test_vma.cc b/na/test/test_vma.cc deleted file mode 100644 index 5278914..0000000 --- a/na/test/test_vma.cc +++ /dev/null @@ -1,282 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include -#include -#include - -void selftest_vma() { - st_begin("vma: create and destroy VmSpace"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - ST_ASSERT(space->root_pa != 0); - - // Add a few regions to ensure destruction works - vmm_map_user(space, 0x1000, 0x1000, VM_READ); - vmm_map_user(space, 0x3000, 0x1000, VM_READ | VM_WRITE); - - space->destroy(); - } - st_pass(); - - st_begin("vma: map user region and handle demand paging"); - { - VmSpace* space = VmSpace::create(); - - // Map 8KB (2 pages) at 0x400000 using MI-layer flags - int ret = vmm_map_user(space, 0x400000, 0x2000, VM_READ | VM_WRITE); - ST_ASSERT_EQ(ret, 0); - - // Fault in the first page - ret = vmm_handle_page_fault(space, 0x400500, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Verify mapping in hardware page table - uint64 pa = 0; - bool mapped = pmap::lookup(space->root_pa, 0x400000, &pa); - ST_ASSERT(mapped); - ST_ASSERT_NE(pa, 0ULL); - - // Fault in the second page - ret = vmm_handle_page_fault(space, 0x401FFF, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Should reject fault outside VMA with -EFAULT - ret = vmm_handle_page_fault(space, 0x402000, VmFaultCause::Read); - ST_ASSERT_EQ(ret, -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: unmap user region prevents page fault"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x10000, 0x2000, VM_READ); - - // Inside the region — should succeed - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), 0); - - // Unmap exactly - vmm_unmap_user(space, 0x10000, 0x2000); - - // Should fail now with -EFAULT - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: duplicate page fault on same page returns 0"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x200000, 0x1000, VM_READ | VM_WRITE); - - // First fault allocates the physical page - int ret = vmm_handle_page_fault(space, 0x200000, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Second fault on the same page should hit "already mapped" path - ret = vmm_handle_page_fault(space, 0x200100, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Verify both land on the same physical page - uint64 pa1 = 0, pa2 = 0; - pmap::lookup(space->root_pa, 0x200000, &pa1); - pmap::lookup(space->root_pa, 0x200100, &pa2); - // Same page, so PA for both addresses should be the same base page - ST_ASSERT_EQ(pa1, pa2); - - space->destroy(); - } - st_pass(); - - st_begin("vma: vm_perm_to_pte flag translation correctness"); - { - // Test each MI flag maps to correct hardware PTE bit - uint64 pte_r = pmap::vm_perm_to_pte(VM_READ); - ST_ASSERT(pte_r & pmap::PTE_R); - ST_ASSERT(!(pte_r & pmap::PTE_W)); - ST_ASSERT(!(pte_r & pmap::PTE_X)); - ST_ASSERT(!(pte_r & pmap::PTE_U)); - - uint64 pte_rw = pmap::vm_perm_to_pte(VM_READ | VM_WRITE); - ST_ASSERT(pte_rw & pmap::PTE_R); - ST_ASSERT(pte_rw & pmap::PTE_W); - - uint64 pte_rx = pmap::vm_perm_to_pte(VM_READ | VM_EXEC); - ST_ASSERT(pte_rx & pmap::PTE_R); - ST_ASSERT(pte_rx & pmap::PTE_X); - ST_ASSERT(!(pte_rx & pmap::PTE_W)); - - uint64 pte_u = pmap::vm_perm_to_pte(VM_READ | VM_USER); - ST_ASSERT(pte_u & pmap::PTE_R); - ST_ASSERT(pte_u & pmap::PTE_U); - - // All combined - uint64 pte_all = pmap::vm_perm_to_pte(VM_READ | VM_WRITE | VM_EXEC | VM_USER); - ST_ASSERT(pte_all & pmap::PTE_R); - ST_ASSERT(pte_all & pmap::PTE_W); - ST_ASSERT(pte_all & pmap::PTE_X); - ST_ASSERT(pte_all & pmap::PTE_U); - ST_ASSERT(pte_all & pmap::PTE_V); // Always valid - } - st_pass(); - - st_begin("vma: unmap subset only removes contained VMAs"); - { - VmSpace* space = VmSpace::create(); - // Map three separate regions - vmm_map_user(space, 0x50000, 0x1000, VM_READ); - vmm_map_user(space, 0x60000, 0x1000, VM_READ); - vmm_map_user(space, 0x70000, 0x1000, VM_READ); - - // Unmap only the middle one (range covers 0x60000-0x61000) - vmm_unmap_user(space, 0x60000, 0x1000); - - // First and third should still be reachable - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, VmFaultCause::Read), 0); - - // Middle should fail - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: null space returns -EINVAL"); - { - ST_ASSERT_EQ(vmm_map_user(nullptr, 0x1000, 0x1000, VM_READ), -EINVAL); - ST_ASSERT_EQ(vmm_unmap_user(nullptr, 0x1000, 0x1000), -EINVAL); - ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, VmFaultCause::Read), -EINVAL); - } - st_pass(); - - st_begin("vma: clone and copy-on-write"); - { - VmSpace* parent = VmSpace::create(); - vmm_map_user(parent, 0x80000, 0x1000, VM_READ | VM_WRITE); - - /* Trigger read fault in parent to allocate physical page */ - ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Read), 0); - uint64 parent_pa_initial; - ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_initial)); - - /* Clone the address space */ - VmSpace* child = vmm_clone(parent); - ST_ASSERT(child != nullptr); - - /* Verify child has the same physical page mapped but COW */ - uint64 child_pa; - ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa)); - ST_ASSERT_EQ(child_pa, parent_pa_initial); - ST_ASSERT(pmap::is_cow(child->root_pa, 0x80000)); - ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); /* Parent also marked COW */ - - /* Trigger Store Page Fault (cause=15) in child */ - ST_ASSERT_EQ(vmm_handle_page_fault(child, 0x80000, VmFaultCause::Write), 0); - - /* Child should now have a new physical page, no longer COW */ - uint64 child_pa_new; - ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa_new)); - ST_ASSERT(child_pa_new != parent_pa_initial); - ST_ASSERT(!pmap::is_cow(child->root_pa, 0x80000)); - - /* Parent is still mapped to the old page, still COW because it hasn't written */ - uint64 parent_pa_current; - ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_current)); - ST_ASSERT_EQ(parent_pa_current, parent_pa_initial); - ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); - - /* Trigger Store Page Fault in parent */ - ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Write), 0); - ST_ASSERT(!pmap::is_cow(parent->root_pa, 0x80000)); - - child->destroy(); - parent->destroy(); - } - st_pass(); - - st_begin("vma: gap search finds next free range"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x1000, 0x3000, VM_READ); - - uint64 va = mm_find_free_va(space, 0x1000, kPageSize); - ST_ASSERT_EQ(va, 0x4000ULL); - - space->destroy(); - } - st_pass(); - - st_begin("vma: reject overlapping mapping"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x20000, 0x2000, VM_READ), 0); - ST_ASSERT_EQ(vmm_map_user(space, 0x21000, 0x1000, VM_READ), -EINVAL); - space->destroy(); - } - st_pass(); - - st_begin("vma: partial unmap splits a single VMA"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x80000, 0x3000, VM_READ | VM_WRITE), 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_unmap_user(space, 0x81000, 0x1000), 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: mprotect read-only blocks write faults"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x90000, 0x2000, VM_READ | VM_WRITE), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_protect_user(space, 0x90000, 0x1000, VM_READ), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Write), -EFAULT); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x91000, VmFaultCause::Write), 0); - - space->destroy(); - } - st_pass(); - - st_begin("vma: PROT_NONE unmaps existing pages"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0xA0000, 0x1000, VM_READ | VM_WRITE), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_protect_user(space, 0xA0000, 0x1000, 0), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: range covered check"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0xB0000, 0x2000, VM_READ), 0); - - ST_ASSERT(vma_range_covered(space, 0xB0000, 0xB1000)); - ST_ASSERT(!vma_range_covered(space, 0xB0000, 0xB3000)); - - space->destroy(); - } - st_pass(); -} diff --git a/na/test/test_vmm.cc b/na/test/test_vmm.cc deleted file mode 100644 index 635e3f3..0000000 --- a/na/test/test_vmm.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: VMM and Address Translation - */ - -#include -#include -#include -#include -#include -#include -#include - -namespace { -constexpr uint64 kUartBase = 0x10000000ULL; -constexpr uint64 kPageOffsetMask = kPageSize - 1; -} - -void selftest_vmm() { - st_begin("vmm: virtual/physical address translation"); - { - PhysAddr pa = pmm_get_mem_base(); - VirtAddr va = pa_to_va(pa); - ST_ASSERT_EQ(va.raw, arch::g_direct_map_base + pa.raw); - ST_ASSERT_EQ(va_to_pa(va).raw, pa.raw); - - Pfn p = pa_to_pfn(pa); - ST_ASSERT_EQ(p.raw, pa.raw / kPageSize); - ST_ASSERT_EQ(pfn_to_pa(p).raw, pa.raw); - } - st_pass(); - - st_begin("vmm: page alignment macros"); - { - uint64 base = pmm_get_mem_base().raw; - uint64 addr = base + kPageOffsetMask; - ST_ASSERT_EQ(page_align_down(addr), base); - ST_ASSERT_EQ(page_align_up(addr), base + kPageSize); - ST_ASSERT_EQ(page_align_down(base), base); - ST_ASSERT_EQ(page_align_up(base), base); - } - st_pass(); - - st_begin("vmm: mapping kernel MMIO (driver API)"); - { - /* Use UART MMIO address (0x10000000 on RISC-V virt). - * Must NOT use pmm_get_mem_base() — that's PMP-protected firmware. */ - uint64 mmio_pa = kUartBase; - vmm_map_kernel_mmio(mmio_pa, kPageSize); - - // Verify it doesn't crash and address is reachable in higher half - uint64 va = pa_to_va(phys_addr(mmio_pa)).raw; - ST_ASSERT(va >= arch::g_direct_map_base); - } - st_pass(); -} diff --git a/na/test/test_waitqueue.cc b/na/test/test_waitqueue.cc deleted file mode 100644 index 7f50d6b..0000000 --- a/na/test/test_waitqueue.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include -#include -#include -#include - -static WaitQueue g_test_wq; -static volatile bool g_thread_woken = false; -static volatile bool g_thread_started = false; -static volatile int g_wake_count = 0; - -extern TaskControlBlock* kthread_create(const char* name, void (*entry)(void*), void* arg, uint8 priority); - -static void waitqueue_test_thread(void*) -{ - g_thread_started = true; - g_test_wq.sleep(); - g_thread_woken = true; - - extern void task_exit(int code); - task_exit(0); -} - -static void waitqueue_test_thread_all(void*) -{ - g_test_wq.sleep(); - __sync_fetch_and_add(&g_wake_count, 1); - - extern void task_exit(int code); - task_exit(0); -} - -void selftest_waitqueue() -{ - st_begin("waitqueue: sleep and wake_one"); - - g_test_wq.init("test_wq"); - g_thread_started = false; - g_thread_woken = false; - - TaskControlBlock* child = kthread_create("test_wq_child", waitqueue_test_thread, nullptr, 0); - ST_ASSERT(child != nullptr); - - sched_yield(); - - ST_ASSERT(g_thread_started == true); - ST_ASSERT(g_thread_woken == false); - ST_ASSERT(!g_test_wq.is_empty()); - - kprintf("[test_wq] waking child\n"); - bool woken = g_test_wq.wake_one(); - ST_ASSERT(woken == true); - ST_ASSERT(g_test_wq.is_empty()); - - kprintf("[test_wq] yielding to child again\n"); - sched_yield(); - kprintf("[test_wq] returned from yield 2\n"); - - ST_ASSERT(g_thread_woken == true); - - st_pass(); - - st_begin("waitqueue: wake_all"); - g_wake_count = 0; - for (int i = 0; i < 3; i++) { - kthread_create("test_wq_all", waitqueue_test_thread_all, nullptr, 0); - } - - /* Give all 3 threads a chance to start and sleep */ - kprintf("[test_wq] yielding 1 for wake_all\n"); - sched_yield(); - sched_yield(); - sched_yield(); - - ST_ASSERT(!g_test_wq.is_empty()); - - int woken_count = g_test_wq.wake_all(); - - ST_ASSERT_EQ(woken_count, 3); - ST_ASSERT(g_test_wq.is_empty()); - - /* Give all 3 threads a chance to wake up and run */ - sched_yield(); - sched_yield(); - sched_yield(); - - ST_ASSERT_EQ(g_wake_count, 3); - st_pass(); -} diff --git a/na/usr/default_cpio_list b/na/usr/default_cpio_list deleted file mode 100644 index 6721365..0000000 --- a/na/usr/default_cpio_list +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Default initramfs source — minimal, no userland. -# -# Mirrors Linux's usr/default_cpio_list: just enough nodes for the kernel -# to mount a root and panic cleanly on "no init found". Real workloads -# override CONFIG_INITRAMFS_SOURCE. -# -# Format (matches gen_init_cpio): -# dir -# nod -# file -# slink -# Lines starting with '#' are comments. Blank lines are ignored. - -dir /dev 0755 0 0 -nod /dev/console 0600 0 0 c 5 1 -nod /dev/null 0666 0 0 c 1 3 -nod /dev/zero 0666 0 0 c 1 5 -dir /proc 0755 0 0 -dir /sys 0755 0 0 -dir /tmp 1777 0 0 -dir /sbin 0755 0 0 -dir /bin 0755 0 0 -dir /etc 0755 0 0 diff --git a/na/usr/gen_init_cpio.c b/na/usr/gen_init_cpio.c deleted file mode 100644 index b7296ed..0000000 --- a/na/usr/gen_init_cpio.c +++ /dev/null @@ -1,781 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Original work by Jeff Garzik - * - * External file lists, symlink, pipe and fifo support by Thayne Harbaugh - * Hard link support by Luciano Rocha - */ - -#define xstr(s) #s -#define str(s) xstr(s) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define CPIO_HDR_LEN 110 -#define CPIO_TRAILER "TRAILER!!!" -#define padlen(_off, _align) (((_align) - ((_off) & ((_align) - 1))) % (_align)) - -/* zero-padding the filename field for data alignment is limited by PATH_MAX */ -static char padding[PATH_MAX]; -static unsigned int offset; -static unsigned int ino = 721; -static time_t default_mtime; -static bool do_file_mtime; -static bool do_csum = false; -static int outfd = STDOUT_FILENO; -static unsigned int dalign; - -struct file_handler { - const char *type; - int (*handler)(const char *line); -}; - -static int push_buf(const char *name, size_t name_len) -{ - ssize_t len; - - len = write(outfd, name, name_len); - if (len != name_len) - return -1; - - offset += name_len; - return 0; -} - -static int push_pad(size_t padlen) -{ - ssize_t len = 0; - - if (!padlen) - return 0; - - if (padlen < sizeof(padding)) - len = write(outfd, padding, padlen); - if (len != padlen) - return -1; - - offset += padlen; - return 0; -} - -static int push_rest(const char *name, size_t name_len) -{ - ssize_t len; - - len = write(outfd, name, name_len); - if (len != name_len) - return -1; - - offset += name_len; - - return push_pad(padlen(name_len + CPIO_HDR_LEN, 4)); -} - -static int cpio_trailer(void) -{ - int len; - unsigned int namesize = sizeof(CPIO_TRAILER); - - len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" - "%08X%08X%08X%08X%08X%08X%08X", - do_csum ? "070702" : "070701", /* magic */ - 0, /* ino */ - 0, /* mode */ - (long) 0, /* uid */ - (long) 0, /* gid */ - 1, /* nlink */ - (long) 0, /* mtime */ - 0, /* filesize */ - 0, /* major */ - 0, /* minor */ - 0, /* rmajor */ - 0, /* rminor */ - namesize, /* namesize */ - 0); /* chksum */ - offset += len; - - if (len != CPIO_HDR_LEN || - push_rest(CPIO_TRAILER, namesize) < 0 || - push_pad(padlen(offset, 512)) < 0) - return -1; - - if (fsync(outfd) < 0 && errno != EINVAL) - return -1; - - return 0; -} - -static int cpio_mkslink(const char *name, const char *target, - unsigned int mode, uid_t uid, gid_t gid) -{ - int len; - unsigned int namesize, targetsize = strlen(target) + 1; - - if (name[0] == '/') - name++; - namesize = strlen(name) + 1; - - len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" - "%08X%08X%08X%08X%08X%08X%08X", - do_csum ? "070702" : "070701", /* magic */ - ino++, /* ino */ - S_IFLNK | mode, /* mode */ - (long) uid, /* uid */ - (long) gid, /* gid */ - 1, /* nlink */ - (long) default_mtime, /* mtime */ - targetsize, /* filesize */ - 3, /* major */ - 1, /* minor */ - 0, /* rmajor */ - 0, /* rminor */ - namesize, /* namesize */ - 0); /* chksum */ - offset += len; - - if (len != CPIO_HDR_LEN || - push_buf(name, namesize) < 0 || - push_pad(padlen(offset, 4)) < 0 || - push_buf(target, targetsize) < 0 || - push_pad(padlen(offset, 4)) < 0) - return -1; - - return 0; - -} - -static int cpio_mkslink_line(const char *line) -{ - char name[PATH_MAX + 1]; - char target[PATH_MAX + 1]; - unsigned int mode; - int uid; - int gid; - int rc = -1; - - if (5 != sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX) "s %o %d %d", name, target, &mode, &uid, &gid)) { - fprintf(stderr, "Unrecognized dir format '%s'", line); - goto fail; - } - rc = cpio_mkslink(name, target, mode, uid, gid); - fail: - return rc; -} - -static int cpio_mkgeneric(const char *name, unsigned int mode, - uid_t uid, gid_t gid) -{ - int len; - unsigned int namesize; - - if (name[0] == '/') - name++; - namesize = strlen(name) + 1; - - len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" - "%08X%08X%08X%08X%08X%08X%08X", - do_csum ? "070702" : "070701", /* magic */ - ino++, /* ino */ - mode, /* mode */ - (long) uid, /* uid */ - (long) gid, /* gid */ - 2, /* nlink */ - (long) default_mtime, /* mtime */ - 0, /* filesize */ - 3, /* major */ - 1, /* minor */ - 0, /* rmajor */ - 0, /* rminor */ - namesize, /* namesize */ - 0); /* chksum */ - offset += len; - - if (len != CPIO_HDR_LEN || - push_rest(name, namesize) < 0) - return -1; - - return 0; -} - -enum generic_types { - GT_DIR, - GT_PIPE, - GT_SOCK -}; - -struct generic_type { - const char *type; - mode_t mode; -}; - -static const struct generic_type generic_type_table[] = { - [GT_DIR] = { - .type = "dir", - .mode = S_IFDIR - }, - [GT_PIPE] = { - .type = "pipe", - .mode = S_IFIFO - }, - [GT_SOCK] = { - .type = "sock", - .mode = S_IFSOCK - } -}; - -static int cpio_mkgeneric_line(const char *line, enum generic_types gt) -{ - char name[PATH_MAX + 1]; - unsigned int mode; - int uid; - int gid; - int rc = -1; - - if (4 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d", name, &mode, &uid, &gid)) { - fprintf(stderr, "Unrecognized %s format '%s'", - line, generic_type_table[gt].type); - goto fail; - } - mode |= generic_type_table[gt].mode; - rc = cpio_mkgeneric(name, mode, uid, gid); - fail: - return rc; -} - -static int cpio_mkdir_line(const char *line) -{ - return cpio_mkgeneric_line(line, GT_DIR); -} - -static int cpio_mkpipe_line(const char *line) -{ - return cpio_mkgeneric_line(line, GT_PIPE); -} - -static int cpio_mksock_line(const char *line) -{ - return cpio_mkgeneric_line(line, GT_SOCK); -} - -static int cpio_mknod(const char *name, unsigned int mode, - uid_t uid, gid_t gid, char dev_type, - unsigned int maj, unsigned int min) -{ - int len; - unsigned int namesize; - - if (dev_type == 'b') - mode |= S_IFBLK; - else - mode |= S_IFCHR; - - if (name[0] == '/') - name++; - namesize = strlen(name) + 1; - - len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" - "%08X%08X%08X%08X%08X%08X%08X", - do_csum ? "070702" : "070701", /* magic */ - ino++, /* ino */ - mode, /* mode */ - (long) uid, /* uid */ - (long) gid, /* gid */ - 1, /* nlink */ - (long) default_mtime, /* mtime */ - 0, /* filesize */ - 3, /* major */ - 1, /* minor */ - maj, /* rmajor */ - min, /* rminor */ - namesize, /* namesize */ - 0); /* chksum */ - offset += len; - - if (len != CPIO_HDR_LEN || - push_rest(name, namesize) < 0) - return -1; - - return 0; -} - -static int cpio_mknod_line(const char *line) -{ - char name[PATH_MAX + 1]; - unsigned int mode; - int uid; - int gid; - char dev_type; - unsigned int maj; - unsigned int min; - int rc = -1; - - if (7 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d %c %u %u", - name, &mode, &uid, &gid, &dev_type, &maj, &min)) { - fprintf(stderr, "Unrecognized nod format '%s'", line); - goto fail; - } - rc = cpio_mknod(name, mode, uid, gid, dev_type, maj, min); - fail: - return rc; -} - -static int cpio_mkfile_csum(int fd, unsigned long size, uint32_t *csum) -{ - while (size) { - unsigned char filebuf[65536]; - ssize_t this_read; - size_t i, this_size = MIN(size, sizeof(filebuf)); - - this_read = read(fd, filebuf, this_size); - if (this_read <= 0 || this_read > this_size) - return -1; - - for (i = 0; i < this_read; i++) - *csum += filebuf[i]; - - size -= this_read; - } - /* seek back to the start for data segment I/O */ - if (lseek(fd, 0, SEEK_SET) < 0) - return -1; - - return 0; -} - -static int cpio_mkfile(const char *name, const char *location, - unsigned int mode, uid_t uid, gid_t gid, - unsigned int nlinks) -{ - struct stat buf; - unsigned long size; - int file, retval, len; - int rc = -1; - time_t mtime; - int namesize, namepadlen; - unsigned int i; - uint32_t csum = 0; - ssize_t this_read; - - mode |= S_IFREG; - - file = open (location, O_RDONLY); - if (file < 0) { - fprintf (stderr, "File %s could not be opened for reading\n", location); - goto error; - } - - retval = fstat(file, &buf); - if (retval) { - fprintf(stderr, "File %s could not be stat()'ed\n", location); - goto error; - } - - if (do_file_mtime) { - mtime = default_mtime; - } else { - mtime = buf.st_mtime; - if (mtime > 0xffffffff) { - fprintf(stderr, "%s: Timestamp exceeds maximum cpio timestamp, clipping.\n", - location); - mtime = 0xffffffff; - } - - if (mtime < 0) { - fprintf(stderr, "%s: Timestamp negative, clipping.\n", - location); - mtime = 0; - } - } - - if (buf.st_size > 0xffffffff) { - fprintf(stderr, "%s: Size exceeds maximum cpio file size\n", - location); - goto error; - } - - if (do_csum && cpio_mkfile_csum(file, buf.st_size, &csum) < 0) { - fprintf(stderr, "Failed to checksum file %s\n", location); - goto error; - } - - size = 0; - namepadlen = 0; - for (i = 1; i <= nlinks; i++) { - if (name[0] == '/') - name++; - namesize = strlen(name) + 1; - - /* data goes on last link, after any alignment padding */ - if (i == nlinks) - size = buf.st_size; - - if (dalign && size > dalign) { - namepadlen = padlen(offset + CPIO_HDR_LEN + namesize, - dalign); - if (namesize + namepadlen > PATH_MAX) { - fprintf(stderr, - "%s: best-effort alignment %u missed\n", - name, dalign); - namepadlen = 0; - } - } - - len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" - "%08lX%08X%08X%08X%08X%08X%08X", - do_csum ? "070702" : "070701", /* magic */ - ino, /* ino */ - mode, /* mode */ - (long) uid, /* uid */ - (long) gid, /* gid */ - nlinks, /* nlink */ - (long) mtime, /* mtime */ - size, /* filesize */ - 3, /* major */ - 1, /* minor */ - 0, /* rmajor */ - 0, /* rminor */ - namesize + namepadlen, /* namesize */ - size ? csum : 0); /* chksum */ - offset += len; - - if (len != CPIO_HDR_LEN || - push_buf(name, namesize) < 0 || - push_pad(namepadlen ? namepadlen : padlen(offset, 4)) < 0) - goto error; - - if (size) { - this_read = copy_file_range(file, NULL, outfd, NULL, size, 0); - if (this_read > 0) { - if (this_read > size) - goto error; - offset += this_read; - size -= this_read; - } - /* short or failed copy falls back to read/write... */ - } - - while (size) { - unsigned char filebuf[65536]; - size_t this_size = MIN(size, sizeof(filebuf)); - - this_read = read(file, filebuf, this_size); - if (this_read <= 0 || this_read > this_size) { - fprintf(stderr, "Can not read %s file\n", location); - goto error; - } - - if (write(outfd, filebuf, this_read) != this_read) { - fprintf(stderr, "writing filebuf failed\n"); - goto error; - } - offset += this_read; - size -= this_read; - } - if (push_pad(padlen(offset, 4)) < 0) - goto error; - - name += namesize; - } - ino++; - rc = 0; - -error: - if (file >= 0) - close(file); - return rc; -} - -static char *cpio_replace_env(char *new_location) -{ - char expanded[PATH_MAX + 1]; - char *start, *end, *var; - - while ((start = strstr(new_location, "${")) && - (end = strchr(start + 2, '}'))) { - *start = *end = 0; - var = getenv(start + 2); - snprintf(expanded, sizeof expanded, "%s%s%s", - new_location, var ? var : "", end + 1); - strcpy(new_location, expanded); - } - - return new_location; -} - -static int cpio_mkfile_line(const char *line) -{ - char name[PATH_MAX + 1]; - char *dname = NULL; /* malloc'ed buffer for hard links */ - char location[PATH_MAX + 1]; - unsigned int mode; - int uid; - int gid; - int nlinks = 1; - int end = 0, dname_len = 0; - int rc = -1; - - if (5 > sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX) - "s %o %d %d %n", - name, location, &mode, &uid, &gid, &end)) { - fprintf(stderr, "Unrecognized file format '%s'", line); - goto fail; - } - if (end && isgraph(line[end])) { - int len; - int nend; - - dname = malloc(strlen(line)); - if (!dname) { - fprintf (stderr, "out of memory (%d)\n", dname_len); - goto fail; - } - - dname_len = strlen(name) + 1; - memcpy(dname, name, dname_len); - - do { - nend = 0; - if (sscanf(line + end, "%" str(PATH_MAX) "s %n", - name, &nend) < 1) - break; - len = strlen(name) + 1; - memcpy(dname + dname_len, name, len); - dname_len += len; - nlinks++; - end += nend; - } while (isgraph(line[end])); - } else { - dname = name; - } - rc = cpio_mkfile(dname, cpio_replace_env(location), - mode, uid, gid, nlinks); - fail: - if (dname_len) free(dname); - return rc; -} - -static void usage(const char *prog) -{ - fprintf(stderr, "Usage:\n" - "\t%s [-t ] [-c] [-o ] [-a ] \n" - "\n" - " is a file containing newline separated entries that\n" - "describe the files to be included in the initramfs archive:\n" - "\n" - "# a comment\n" - "file []\n" - "dir \n" - "nod \n" - "slink \n" - "pipe \n" - "sock \n" - "\n" - " name of the file/dir/nod/etc in the archive\n" - " location of the file in the current filesystem\n" - " expands shell variables quoted with ${}\n" - " link target\n" - " mode/permissions of the file\n" - " user id (0=root)\n" - " group id (0=root)\n" - " device type (b=block, c=character)\n" - " major number of nod\n" - " minor number of nod\n" - " space separated list of other links to file\n" - "\n" - "example:\n" - "# A simple initramfs\n" - "dir /dev 0755 0 0\n" - "nod /dev/console 0600 0 0 c 5 1\n" - "dir /root 0700 0 0\n" - "dir /sbin 0755 0 0\n" - "file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0\n" - "\n" - " is time in seconds since Epoch that will be used\n" - "as mtime for symlinks, directories, regular and special files.\n" - "The default is to use the current time for all files, but\n" - "preserve modification time for regular files.\n" - "-c: calculate and store 32-bit checksums for file data.\n" - ": write cpio to this file instead of stdout\n" - ": attempt to align file data by zero-padding the\n" - "filename field up to data_align. Must be a multiple of 4.\n" - "Alignment is best-effort; PATH_MAX limits filename padding.\n", - prog); -} - -static const struct file_handler file_handler_table[] = { - { - .type = "file", - .handler = cpio_mkfile_line, - }, { - .type = "nod", - .handler = cpio_mknod_line, - }, { - .type = "dir", - .handler = cpio_mkdir_line, - }, { - .type = "slink", - .handler = cpio_mkslink_line, - }, { - .type = "pipe", - .handler = cpio_mkpipe_line, - }, { - .type = "sock", - .handler = cpio_mksock_line, - }, { - .type = NULL, - .handler = NULL, - } -}; - -#define LINE_SIZE (2 * PATH_MAX + 50) - -int main (int argc, char *argv[]) -{ - FILE *cpio_list; - char line[LINE_SIZE]; - char *args, *type; - int ec = 0; - int line_nr = 0; - const char *filename; - - default_mtime = time(NULL); - while (1) { - int opt = getopt(argc, argv, "t:cho:a:"); - char *invalid; - - if (opt == -1) - break; - switch (opt) { - case 't': - default_mtime = strtol(optarg, &invalid, 10); - if (!*optarg || *invalid) { - fprintf(stderr, "Invalid timestamp: %s\n", - optarg); - usage(argv[0]); - exit(1); - } - do_file_mtime = true; - break; - case 'c': - do_csum = true; - break; - case 'o': - outfd = open(optarg, - O_WRONLY | O_CREAT | O_LARGEFILE | O_TRUNC, - 0600); - if (outfd < 0) { - fprintf(stderr, "failed to open %s\n", optarg); - usage(argv[0]); - exit(1); - } - break; - case 'a': - dalign = strtoul(optarg, &invalid, 10); - if (!*optarg || *invalid || (dalign & 3)) { - fprintf(stderr, "Invalid data_align: %s\n", - optarg); - usage(argv[0]); - exit(1); - } - break; - case 'h': - case '?': - usage(argv[0]); - exit(opt == 'h' ? 0 : 1); - } - } - - /* - * Timestamps after 2106-02-07 06:28:15 UTC have an ascii hex time_t - * representation that exceeds 8 chars and breaks the cpio header - * specification. Negative timestamps similarly exceed 8 chars. - */ - if (default_mtime > 0xffffffff || default_mtime < 0) { - fprintf(stderr, "ERROR: Timestamp out of range for cpio format\n"); - exit(1); - } - - if (argc - optind != 1) { - usage(argv[0]); - exit(1); - } - filename = argv[optind]; - if (!strcmp(filename, "-")) - cpio_list = stdin; - else if (!(cpio_list = fopen(filename, "r"))) { - fprintf(stderr, "ERROR: unable to open '%s': %s\n\n", - filename, strerror(errno)); - usage(argv[0]); - exit(1); - } - - while (fgets(line, LINE_SIZE, cpio_list)) { - int type_idx; - size_t slen = strlen(line); - - line_nr++; - - if ('#' == *line) { - /* comment - skip to next line */ - continue; - } - - if (! (type = strtok(line, " \t"))) { - fprintf(stderr, - "ERROR: incorrect format, could not locate file type line %d: '%s'\n", - line_nr, line); - ec = -1; - break; - } - - if ('\n' == *type) { - /* a blank line */ - continue; - } - - if (slen == strlen(type)) { - /* must be an empty line */ - continue; - } - - if (! (args = strtok(NULL, "\n"))) { - fprintf(stderr, - "ERROR: incorrect format, newline required line %d: '%s'\n", - line_nr, line); - ec = -1; - } - - for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) { - int rc; - if (! strcmp(line, file_handler_table[type_idx].type)) { - if ((rc = file_handler_table[type_idx].handler(args))) { - ec = rc; - fprintf(stderr, " line %d\n", line_nr); - } - break; - } - } - - if (NULL == file_handler_table[type_idx].type) { - fprintf(stderr, "unknown file type line %d: '%s'\n", - line_nr, line); - } - } - if (ec == 0) - ec = cpio_trailer(); - - exit(ec); -} diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include new file mode 100644 index 0000000..76466bd --- /dev/null +++ b/scripts/Kbuild.include @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# scripts/Kbuild.include — shared Make helpers for LumeOS. +# +# Mirrors a small, useful subset of Linux's scripts/Kbuild.include. +# Provides: +# +# - Q / KBUILD_VERBOSE : `make V=1` shows full commands, +# default V=0 prints a one-line summary +# - $(call cmd,) : echo a quiet summary, then run cmd_ +# - $(call if_changed,) : currently a thin alias for $(cmd) — +# no .cmd-file hashing yet (Linux's full +# if_changed uses fixdep to detect both +# recipe-text and header changes; LumeOS +# relies on `-MMD -MP` for headers and +# on `make clean` for flag changes). +# - $(call kbuild-objs,...) : translate Kbuild-style `obj-y := foo.o` +# entries into SRCS_C / SRCS_CC. +# +# Naming convention adopted from Linux: each command has both a +# `quiet_cmd_` (single-line summary) and a `cmd_` (full +# command line). $(quiet) selects which prefix to display: +# V=0 -> quiet := quiet_ -> echoes " CC foo.c" +# V=1 -> quiet := -> echoes the full $(CC) $(CFLAGS) ... +# ---------------------------------------------------------------------- + +# --- Verbosity --------------------------------------------------------- +# Override at the command line: `make V=1`. Default V=0 = quiet. +KBUILD_VERBOSE ?= $(V) +KBUILD_VERBOSE ?= 0 + +ifeq ($(KBUILD_VERBOSE),1) + Q := + quiet := +else + Q := @ + quiet := quiet_ +endif + +# --- echo-cmd / cmd / if_changed --------------------------------------- +# +# echo-cmd prints the recipe's one-line summary (when quiet) or its full +# command line (when verbose). Implementation uses $($(quiet)cmd_) +# so the right form is picked up automatically. +# +# cmd is the workhorse: print summary, then execute the real command. +echo-cmd = $(if $($(quiet)cmd_$(1)),echo ' $($(quiet)cmd_$(1))';) + +cmd = @$(echo-cmd) $(cmd_$(1)) + +# if_changed currently degenerates to `cmd` — full Linux semantics +# require tracking the command-line via per-target ..cmd files +# and comparing on every rebuild, which needs fixdep. Until LumeOS +# grows that infrastructure, `make clean` after editing CFLAGS suffices. +if_changed = $(cmd) + +# --- Standard quiet_cmd_* / cmd_* templates --------------------------- +# +# Subdir Makefiles and the top-level Makefile can reference these by +# name via $(call cmd,). Add new pairs here as new recipe shapes +# show up (e.g., cmd_lz / cmd_gz / cmd_zstd for compressed Image targets). + + quiet_cmd_cc_o_c = CC $< + cmd_cc_o_c = $(CC) $(CFLAGS_LIBC) -c $< -o $@ + + quiet_cmd_cxx_o_cc = CXX $< + cmd_cxx_o_cc = $(CXX) $(CXXFLAGS) -c $< -o $@ + + quiet_cmd_as_o_S = AS $< + cmd_as_o_S = $(CC) $(ASFLAGS) -c $< -o $@ + + quiet_cmd_cc_s_c = CC -S $< + cmd_cc_s_c = $(CC) $(CFLAGS_LIBC) -S $< -o $@ + + quiet_cmd_objcopy_bin = OBJCOPY $@ + cmd_objcopy_bin = $(OBJCOPY) -O binary $< $@ + +# Objcopy with custom flags via per-target OBJCOPYFLAGS variable — +# used by the .pi.o post-processing rule (--prefix-symbols=__pi_ etc.). + quiet_cmd_objcopy = OBJCOPY $@ + cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $< $@ + + quiet_cmd_ld_kernel = LD $@ + cmd_ld_kernel = $(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $^ + +# --- kbuild-objs: obj-y -> SRCS_C / SRCS_CC --------------------------- +# +# Usage from a subdir Makefile (e.g. arch/riscv/kernel/pi/Makefile): +# +# obj-dir := arch/riscv/kernel/pi +# obj-y := fdt_early.pi.o cmdline_early.pi.o archrandom_early.pi.o +# $(eval $(call kbuild-objs,$(obj-dir),$(obj-y:.pi.o=.o))) +# +# For each .o entry, looks up /.cc first, then +# /.c, and appends to SRCS_CC / SRCS_C respectively. +# Pure .pi.o entries should be passed through `:.pi.o=.o` first by the +# caller so the .pi suffix doesn't confuse the source-file lookup. + +define _kbuild-find-src +$(if $(wildcard $(1)/$(basename $(2)).cc),\ + $(eval SRCS_CC += $(1)/$(basename $(2)).cc),\ + $(if $(wildcard $(1)/$(basename $(2)).c),\ + $(eval SRCS_C += $(1)/$(basename $(2)).c),\ + $(error kbuild-objs: no $(1)/$(basename $(2)).{c,cc} for obj $(2)))) +endef + +define kbuild-objs +$(foreach _obj,$(2),$(call _kbuild-find-src,$(1),$(_obj))) +endef diff --git a/scripts/Kconfig.mk b/scripts/Kconfig.mk new file mode 100644 index 0000000..2bdc934 --- /dev/null +++ b/scripts/Kconfig.mk @@ -0,0 +1,238 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin +# +# scripts/Kconfig.mk — Kernel Configuration Infrastructure +# +# This file is the heart of the LumeOS build-time configuration system. +# It mirrors the role of Linux's Kconfig + auto.conf pipeline: +# +# 1. Developer edits `.config` (or copies from arch/$(ARCH)/defconfig) +# 2. This file reads `.config`, applies defaults for anything missing, +# validates constraints, and exports two things: +# a) KCPPFLAGS — a set of -DCONFIG_xxx=value flags for the compiler +# b) $(BUILD)/generated/autoconf.h — the same values as C #defines +# +# Design Principles (matching Linux conventions): +# - Every CONFIG_ symbol has a default defined HERE, so a bare `make` +# with no .config still builds a sane kernel. +# - Boolean options use 'y' / 'n'. Numeric options are plain integers. +# - The generated autoconf.h uses the exact same names so that +# `#ifdef CONFIG_DEBUG` works identically in Makefile and C. +# ====================================================================== + +# ------------------------------------------------------------------ +# §1 Read user configuration (if it exists) +# ------------------------------------------------------------------ +-include .config + +# ------------------------------------------------------------------ +# §2 Provide defaults for every CONFIG_ symbol +# +# ?= means "set only if not already set (by .config or CLI)". +# This is the single source of truth for default values. +# ------------------------------------------------------------------ + +# --- Platform ------------------------------------------------------- +CONFIG_ARCH ?= riscv + +# --- CPU / SMP ------------------------------------------------------ +CONFIG_NR_CPUS ?= 8 + +# --- Scheduler ------------------------------------------------------ +CONFIG_HZ ?= 100 +CONFIG_NR_PRIORITIES ?= 32 + +# --- Memory Management ---------------------------------------------- +CONFIG_PAGE_SIZE ?= 4096 +CONFIG_MAX_ORDER ?= 11 +CONFIG_KERNEL_STACK_SIZE ?= 8192 + +# --- Security / Hardening ------------------------------------------- +CONFIG_KASLR ?= y +CONFIG_KASLR_POLICY_ID ?= 0 +CONFIG_KASLR_LOG_LEVEL ?= 1 +CONFIG_STACK_CANARY ?= y +CONFIG_KERNEL_COMPRESSED ?= y + +# --- Debug ----------------------------------------------------------- +CONFIG_DEBUG ?= y +CONFIG_DEBUG_LOCKS ?= y +CONFIG_SELFTEST ?= y +CONFIG_KLOG ?= y + +# --- Initramfs ------------------------------------------------------ +# Source for the initramfs image. Accepted forms (auto-detected by +# scripts/gen_initramfs.sh): +# - a listing file (gen_init_cpio format) → built into a cpio +# - a directory → packed recursively +# - a *.cpio file → used as-is +# Override on the command line or pin in .config: +# make all CONFIG_INITRAMFS_SOURCE=/path/to/rootfs +CONFIG_INITRAMFS_SOURCE ?= usr/default_cpio_list +CONFIG_INITRAMFS_ROOT_UID ?= 0 +CONFIG_INITRAMFS_ROOT_GID ?= 0 + +# ------------------------------------------------------------------ +# §3 Derived constants (computed from CONFIG_ symbols) +# +# These follow Linux's pattern of expressing dependent values +# so that C code never has to do its own derivation at runtime. +# ------------------------------------------------------------------ + +# Default priority = middle of the range +CONFIG_DEFAULT_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) / 2 ))) + +# Idle priority = lowest = NR_PRIORITIES - 1 +CONFIG_IDLE_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) - 1 ))) + +# Default time slice in ticks: 100ms worth of ticks +# (same as Linux CFS's sysctl_sched_min_granularity default) +CONFIG_DEFAULT_TIMESLICE = $(shell echo $$(( $(CONFIG_HZ) / 10 ))) + +# ------------------------------------------------------------------ +# §4 Export to C/C++ via -D flags +# +# KCPPFLAGS is appended to CFLAGS/CXXFLAGS in the main Makefile. +# Every CONFIG_xxx= becomes -DCONFIG_xxx=. +# Boolean 'y' options become -DCONFIG_xxx=1. +# ------------------------------------------------------------------ +KCPPFLAGS := + +# Numeric options — pass through as-is +KCPPFLAGS += -DCONFIG_NR_CPUS=$(CONFIG_NR_CPUS) +KCPPFLAGS += -DCONFIG_HZ=$(CONFIG_HZ) +KCPPFLAGS += -DCONFIG_NR_PRIORITIES=$(CONFIG_NR_PRIORITIES) +KCPPFLAGS += -DCONFIG_DEFAULT_PRIORITY=$(CONFIG_DEFAULT_PRIORITY) +KCPPFLAGS += -DCONFIG_IDLE_PRIORITY=$(CONFIG_IDLE_PRIORITY) +KCPPFLAGS += -DCONFIG_DEFAULT_TIMESLICE=$(CONFIG_DEFAULT_TIMESLICE) +KCPPFLAGS += -DCONFIG_PAGE_SIZE=$(CONFIG_PAGE_SIZE) +KCPPFLAGS += -DCONFIG_MAX_ORDER=$(CONFIG_MAX_ORDER) +KCPPFLAGS += -DCONFIG_KERNEL_STACK_SIZE=$(CONFIG_KERNEL_STACK_SIZE) + +# Boolean options — translate 'y' → 1 +ifeq ($(CONFIG_KASLR),y) +KCPPFLAGS += -DCONFIG_KASLR=1 +endif +KCPPFLAGS += -DCONFIG_KASLR_POLICY_ID=$(CONFIG_KASLR_POLICY_ID) +KCPPFLAGS += -DCONFIG_KASLR_LOG_LEVEL=$(CONFIG_KASLR_LOG_LEVEL) +ifeq ($(CONFIG_STACK_CANARY),y) +KCPPFLAGS += -DCONFIG_STACK_CANARY=1 +endif +ifeq ($(CONFIG_DEBUG),y) +KCPPFLAGS += -DCONFIG_DEBUG=1 +endif +ifeq ($(CONFIG_DEBUG_LOCKS),y) +KCPPFLAGS += -DCONFIG_DEBUG_LOCKS=1 +endif +ifeq ($(CONFIG_SELFTEST),y) +KCPPFLAGS += -DCONFIG_SELFTEST=1 +endif +ifeq ($(CONFIG_KLOG),y) +KCPPFLAGS += -DCONFIG_KLOG=1 +endif +ifeq ($(CONFIG_KERNEL_COMPRESSED),y) +KCPPFLAGS += -DCONFIG_KERNEL_COMPRESSED=1 +endif + +# ------------------------------------------------------------------ +# §5 Generate autoconf.h (analogous to Linux's include/generated/autoconf.h) +# +# This header is auto-generated at build time so that C code can +# #include and see CONFIG_xxx macros without +# relying on -D flags alone. This is important for IDE integration. +# ------------------------------------------------------------------ +AUTOCONF_H := $(BUILD)/generated/autoconf.h + +define generate-autoconf + @mkdir -p $(dir $(AUTOCONF_H)) + @echo " GEN $(AUTOCONF_H)" + @echo "/* AUTO-GENERATED — DO NOT EDIT */" > $(AUTOCONF_H) + @echo "/* Generated by scripts/Kconfig.mk */" >> $(AUTOCONF_H) + @echo "#pragma once" >> $(AUTOCONF_H) + @echo "" >> $(AUTOCONF_H) + @echo "/* Platform */" >> $(AUTOCONF_H) + @echo "#define CONFIG_NR_CPUS $(CONFIG_NR_CPUS)" >> $(AUTOCONF_H) + @echo "" >> $(AUTOCONF_H) + @echo "/* Scheduler */" >> $(AUTOCONF_H) + @echo "#define CONFIG_HZ $(CONFIG_HZ)" >> $(AUTOCONF_H) + @echo "#define CONFIG_NR_PRIORITIES $(CONFIG_NR_PRIORITIES)" >> $(AUTOCONF_H) + @echo "#define CONFIG_DEFAULT_PRIORITY $(CONFIG_DEFAULT_PRIORITY)" >> $(AUTOCONF_H) + @echo "#define CONFIG_IDLE_PRIORITY $(CONFIG_IDLE_PRIORITY)" >> $(AUTOCONF_H) + @echo "#define CONFIG_DEFAULT_TIMESLICE $(CONFIG_DEFAULT_TIMESLICE)" >> $(AUTOCONF_H) + @echo "" >> $(AUTOCONF_H) + @echo "/* Memory Management */" >> $(AUTOCONF_H) + @echo "#define CONFIG_PAGE_SIZE $(CONFIG_PAGE_SIZE)" >> $(AUTOCONF_H) + @echo "#define CONFIG_MAX_ORDER $(CONFIG_MAX_ORDER)" >> $(AUTOCONF_H) + @echo "#define CONFIG_KERNEL_STACK_SIZE $(CONFIG_KERNEL_STACK_SIZE)" >> $(AUTOCONF_H) + @echo "" >> $(AUTOCONF_H) + @echo "/* Security */" >> $(AUTOCONF_H) + $(if $(filter y,$(CONFIG_KASLR)), @echo "#define CONFIG_KASLR 1" >> $(AUTOCONF_H)) + @echo "#define CONFIG_KASLR_POLICY_ID $(CONFIG_KASLR_POLICY_ID)" >> $(AUTOCONF_H) + @echo "#define CONFIG_KASLR_LOG_LEVEL $(CONFIG_KASLR_LOG_LEVEL)" >> $(AUTOCONF_H) + $(if $(filter y,$(CONFIG_STACK_CANARY)), @echo "#define CONFIG_STACK_CANARY 1" >> $(AUTOCONF_H)) + @echo "" >> $(AUTOCONF_H) + @echo "/* Debug */" >> $(AUTOCONF_H) + $(if $(filter y,$(CONFIG_DEBUG)), @echo "#define CONFIG_DEBUG 1" >> $(AUTOCONF_H)) + $(if $(filter y,$(CONFIG_DEBUG_LOCKS)), @echo "#define CONFIG_DEBUG_LOCKS 1" >> $(AUTOCONF_H)) + $(if $(filter y,$(CONFIG_SELFTEST)), @echo "#define CONFIG_SELFTEST 1" >> $(AUTOCONF_H)) + $(if $(filter y,$(CONFIG_KLOG)), @echo "#define CONFIG_KLOG 1" >> $(AUTOCONF_H)) + $(if $(filter y,$(CONFIG_KERNEL_COMPRESSED)), @echo "#define CONFIG_KERNEL_COMPRESSED 1" >> $(AUTOCONF_H)) +endef + +# ------------------------------------------------------------------ +# §6 Utility targets +# ------------------------------------------------------------------ + +# 'make defconfig' — reset .config to the arch default +.PHONY: defconfig savedefconfig showconfig + +defconfig: + @echo " DEFCFG arch/$(CONFIG_ARCH)/defconfig → .config" + @cp arch/$(CONFIG_ARCH)/defconfig .config + +# 'make savedefconfig' — save current effective config +savedefconfig: + @echo " SAVE .config" + @echo "# LumeOS Kernel Configuration (saved $$(date +%Y-%m-%d))" > .config + @echo "CONFIG_ARCH = $(CONFIG_ARCH)" >> .config + @echo "CONFIG_NR_CPUS = $(CONFIG_NR_CPUS)" >> .config + @echo "CONFIG_HZ = $(CONFIG_HZ)" >> .config + @echo "CONFIG_NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" >> .config + @echo "CONFIG_PAGE_SIZE = $(CONFIG_PAGE_SIZE)" >> .config + @echo "CONFIG_MAX_ORDER = $(CONFIG_MAX_ORDER)" >> .config + @echo "CONFIG_KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" >> .config + @echo "CONFIG_KASLR = $(CONFIG_KASLR)" >> .config + @echo "CONFIG_KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" >> .config + @echo "CONFIG_KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" >> .config + @echo "CONFIG_STACK_CANARY = $(CONFIG_STACK_CANARY)" >> .config + @echo "CONFIG_DEBUG = $(CONFIG_DEBUG)" >> .config + @echo "CONFIG_DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" >> .config + @echo "CONFIG_SELFTEST = $(CONFIG_SELFTEST)" >> .config + @echo "CONFIG_KLOG = $(CONFIG_KLOG)" >> .config + @echo "CONFIG_KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" >> .config + +# 'make showconfig' — print effective configuration +showconfig: + @echo "========================================" + @echo " LumeOS Effective Configuration" + @echo "========================================" + @echo " ARCH = $(CONFIG_ARCH)" + @echo " NR_CPUS = $(CONFIG_NR_CPUS)" + @echo " HZ = $(CONFIG_HZ)" + @echo " NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" + @echo " DEFAULT_PRIORITY = $(CONFIG_DEFAULT_PRIORITY)" + @echo " IDLE_PRIORITY = $(CONFIG_IDLE_PRIORITY)" + @echo " DEFAULT_TIMESLICE = $(CONFIG_DEFAULT_TIMESLICE)" + @echo " PAGE_SIZE = $(CONFIG_PAGE_SIZE)" + @echo " MAX_ORDER = $(CONFIG_MAX_ORDER)" + @echo " KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" + @echo " KASLR = $(CONFIG_KASLR)" + @echo " KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" + @echo " KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" + @echo " STACK_CANARY = $(CONFIG_STACK_CANARY)" + @echo " DEBUG = $(CONFIG_DEBUG)" + @echo " DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" + @echo " SELFTEST = $(CONFIG_SELFTEST)" + @echo " KLOG = $(CONFIG_KLOG)" + @echo " KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" + @echo "========================================" diff --git a/scripts/dtc/libfdt/fdt.c b/scripts/dtc/libfdt/fdt.c new file mode 100644 index 0000000..56d4dcb --- /dev/null +++ b/scripts/dtc/libfdt/fdt.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +/* + * Minimal sanity check for a read-only tree. fdt_ro_probe_() checks + * that the given buffer contains what appears to be a flattened + * device tree with sane information in its header. + */ +int32_t fdt_ro_probe_(const void *fdt) +{ + uint32_t totalsize = fdt_totalsize(fdt); + + if (can_assume(VALID_DTB)) + return totalsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) == FDT_MAGIC) { + /* Complete tree */ + if (!can_assume(LATEST)) { + if (fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + if (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION) + return -FDT_ERR_BADVERSION; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + /* Unfinished sequential-write blob */ + if (!can_assume(VALID_INPUT) && fdt_size_dt_struct(fdt) == 0) + return -FDT_ERR_BADSTATE; + } else { + return -FDT_ERR_BADMAGIC; + } + + if (totalsize < INT32_MAX) + return totalsize; + else + return -FDT_ERR_TRUNCATED; +} + +static int check_off_(uint32_t hdrsize, uint32_t totalsize, uint32_t off) +{ + return (off >= hdrsize) && (off <= totalsize); +} + +static int check_block_(uint32_t hdrsize, uint32_t totalsize, + uint32_t base, uint32_t size) +{ + if (!check_off_(hdrsize, totalsize, base)) + return 0; /* block start out of bounds */ + if ((base + size) < base) + return 0; /* overflow */ + if (!check_off_(hdrsize, totalsize, base + size)) + return 0; /* block end out of bounds */ + return 1; +} + +size_t fdt_header_size_(uint32_t version) +{ + if (version <= 1) + return FDT_V1_SIZE; + else if (version <= 2) + return FDT_V2_SIZE; + else if (version <= 3) + return FDT_V3_SIZE; + else if (version <= 16) + return FDT_V16_SIZE; + else + return FDT_V17_SIZE; +} + +size_t fdt_header_size(const void *fdt) +{ + return can_assume(LATEST) ? FDT_V17_SIZE : + fdt_header_size_(fdt_version(fdt)); +} + +int fdt_check_header(const void *fdt) +{ + size_t hdrsize; + + /* The device tree must be at an 8-byte aligned address */ + if ((uintptr_t)fdt & 7) + return -FDT_ERR_ALIGNMENT; + + if (fdt_magic(fdt) != FDT_MAGIC) + return -FDT_ERR_BADMAGIC; + if (!can_assume(LATEST)) { + if ((fdt_version(fdt) < FDT_FIRST_SUPPORTED_VERSION) + || (fdt_last_comp_version(fdt) > + FDT_LAST_SUPPORTED_VERSION)) + return -FDT_ERR_BADVERSION; + if (fdt_version(fdt) < fdt_last_comp_version(fdt)) + return -FDT_ERR_BADVERSION; + } + hdrsize = fdt_header_size(fdt); + if (!can_assume(VALID_DTB)) { + if ((fdt_totalsize(fdt) < hdrsize) + || (fdt_totalsize(fdt) > INT_MAX)) + return -FDT_ERR_TRUNCATED; + + /* memrsv block must be 8 byte aligned */ + if (fdt_off_mem_rsvmap(fdt) % sizeof(uint64_t)) + return -FDT_ERR_ALIGNMENT; + + /* Structure block must be 4 byte aligned */ + if (fdt_off_dt_struct(fdt) % FDT_TAGSIZE) + return -FDT_ERR_ALIGNMENT; + + /* Bounds check memrsv block */ + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_mem_rsvmap(fdt))) + return -FDT_ERR_TRUNCATED; + + /* Bounds check structure block */ + if (!can_assume(LATEST) && fdt_version(fdt) < 17) { + if (!check_off_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } else { + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_struct(fdt), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_TRUNCATED; + } + + /* Bounds check strings block */ + if (!check_block_(hdrsize, fdt_totalsize(fdt), + fdt_off_dt_strings(fdt), + fdt_size_dt_strings(fdt))) + return -FDT_ERR_TRUNCATED; + } + + return 0; +} + +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int len) +{ + unsigned int uoffset = offset; + unsigned int absoffset = offset + fdt_off_dt_struct(fdt); + + if (offset < 0) + return NULL; + + if (!can_assume(VALID_INPUT)) + if ((absoffset < uoffset) + || ((absoffset + len) < absoffset) + || (absoffset + len) > fdt_totalsize(fdt)) + return NULL; + + if (can_assume(LATEST) || fdt_version(fdt) >= 0x11) + if (((uoffset + len) < uoffset) + || ((offset + len) > fdt_size_dt_struct(fdt))) + return NULL; + + return fdt_offset_ptr_(fdt, offset); +} + +uint32_t fdt_next_tag(const void *fdt, int startoffset, int *nextoffset) +{ + const fdt32_t *tagp, *lenp; + uint32_t tag, len, sum; + int offset = startoffset; + const char *p; + + *nextoffset = -FDT_ERR_TRUNCATED; + tagp = fdt_offset_ptr(fdt, offset, FDT_TAGSIZE); + if (!can_assume(VALID_DTB) && !tagp) + return FDT_END; /* premature end */ + tag = fdt32_to_cpu(*tagp); + offset += FDT_TAGSIZE; + + *nextoffset = -FDT_ERR_BADSTRUCTURE; + switch (tag) { + case FDT_BEGIN_NODE: + /* skip name */ + do { + p = fdt_offset_ptr(fdt, offset++, 1); + } while (p && (*p != '\0')); + if (!can_assume(VALID_DTB) && !p) + return FDT_END; /* premature end */ + break; + + case FDT_PROP: + lenp = fdt_offset_ptr(fdt, offset, sizeof(*lenp)); + if (!can_assume(VALID_DTB) && !lenp) + return FDT_END; /* premature end */ + + len = fdt32_to_cpu(*lenp); + sum = len + offset; + if (!can_assume(VALID_DTB) && + (INT_MAX <= sum || sum < (uint32_t) offset)) + return FDT_END; /* premature end */ + + /* skip-name offset, length and value */ + offset += sizeof(struct fdt_property) - FDT_TAGSIZE + len; + + if (!can_assume(LATEST) && + fdt_version(fdt) < 0x10 && len >= 8 && + ((offset - len) % 8) != 0) + offset += 4; + break; + + case FDT_END: + case FDT_END_NODE: + case FDT_NOP: + break; + + default: + return FDT_END; + } + + if (!fdt_offset_ptr(fdt, startoffset, offset - startoffset)) + return FDT_END; /* premature end */ + + *nextoffset = FDT_TAGALIGN(offset); + return tag; +} + +int fdt_check_node_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_BEGIN_NODE) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_check_prop_offset_(const void *fdt, int offset) +{ + if (!can_assume(VALID_INPUT) + && ((offset < 0) || (offset % FDT_TAGSIZE))) + return -FDT_ERR_BADOFFSET; + + if (fdt_next_tag(fdt, offset, &offset) != FDT_PROP) + return -FDT_ERR_BADOFFSET; + + return offset; +} + +int fdt_next_node(const void *fdt, int offset, int *depth) +{ + int nextoffset = 0; + uint32_t tag; + + if (offset >= 0) + if ((nextoffset = fdt_check_node_offset_(fdt, offset)) < 0) + return nextoffset; + + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_PROP: + case FDT_NOP: + break; + + case FDT_BEGIN_NODE: + if (depth) + (*depth)++; + break; + + case FDT_END_NODE: + if (depth && ((--(*depth)) < 0)) + return nextoffset; + break; + + case FDT_END: + if ((nextoffset >= 0) + || ((nextoffset == -FDT_ERR_TRUNCATED) && !depth)) + return -FDT_ERR_NOTFOUND; + else + return nextoffset; + } + } while (tag != FDT_BEGIN_NODE); + + return offset; +} + +int fdt_first_subnode(const void *fdt, int offset) +{ + int depth = 0; + + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth != 1) + return -FDT_ERR_NOTFOUND; + + return offset; +} + +int fdt_next_subnode(const void *fdt, int offset) +{ + int depth = 1; + + /* + * With respect to the parent, the depth of the next subnode will be + * the same as the last. + */ + do { + offset = fdt_next_node(fdt, offset, &depth); + if (offset < 0 || depth < 1) + return -FDT_ERR_NOTFOUND; + } while (depth > 1); + + return offset; +} + +const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, + int slen) +{ + const char *last = strtab + tabsize - (slen + 1); + const char *p; + + for (p = strtab; p <= last; p++) + if (memcmp(p, s, slen) == 0 && p[slen] == '\0') + return p; + return NULL; +} + +int fdt_move(const void *fdt, void *buf, int bufsize) +{ + if (!can_assume(VALID_INPUT) && bufsize < 0) + return -FDT_ERR_NOSPACE; + + FDT_RO_PROBE(fdt); + + if (fdt_totalsize(fdt) > (unsigned int)bufsize) + return -FDT_ERR_NOSPACE; + + memmove(buf, fdt, fdt_totalsize(fdt)); + return 0; +} diff --git a/scripts/dtc/libfdt/fdt.h b/scripts/dtc/libfdt/fdt.h new file mode 100644 index 0000000..a07abfc --- /dev/null +++ b/scripts/dtc/libfdt/fdt.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef FDT_H +#define FDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#ifndef __ASSEMBLER__ + +struct fdt_header { + fdt32_t magic; /* magic word FDT_MAGIC */ + fdt32_t totalsize; /* total size of DT block */ + fdt32_t off_dt_struct; /* offset to structure */ + fdt32_t off_dt_strings; /* offset to strings */ + fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ + fdt32_t version; /* format version */ + fdt32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + fdt32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + fdt32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + fdt32_t size_dt_struct; /* size of the structure block */ +}; + +struct fdt_reserve_entry { + fdt64_t address; + fdt64_t size; +}; + +struct fdt_node_header { + fdt32_t tag; + char name[]; +}; + +struct fdt_property { + fdt32_t tag; + fdt32_t len; + fdt32_t nameoff; + char data[]; +}; + +#endif /* !__ASSEMBLER__ */ + +#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ +#define FDT_TAGSIZE sizeof(fdt32_t) + +#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ +#define FDT_END_NODE 0x2 /* End node */ +#define FDT_PROP 0x3 /* Property: name off, + size, content */ +#define FDT_NOP 0x4 /* nop */ +#define FDT_END 0x9 + +#define FDT_V1_SIZE (7*sizeof(fdt32_t)) +#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) +#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) +#define FDT_V16_SIZE FDT_V3_SIZE +#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) + +#endif /* FDT_H */ diff --git a/scripts/dtc/libfdt/fdt_addresses.c b/scripts/dtc/libfdt/fdt_addresses.c new file mode 100644 index 0000000..c40ba09 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_addresses.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2014 David Gibson + * Copyright (C) 2018 embedded brains GmbH + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_cells(const void *fdt, int nodeoffset, const char *name) +{ + const fdt32_t *c; + uint32_t val; + int len; + + c = fdt_getprop(fdt, nodeoffset, name, &len); + if (!c) + return len; + + if (len != sizeof(*c)) + return -FDT_ERR_BADNCELLS; + + val = fdt32_to_cpu(*c); + if (val > FDT_MAX_NCELLS) + return -FDT_ERR_BADNCELLS; + + return (int)val; +} + +int fdt_address_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#address-cells"); + if (val == 0) + return -FDT_ERR_BADNCELLS; + if (val == -FDT_ERR_NOTFOUND) + return 2; + return val; +} + +int fdt_size_cells(const void *fdt, int nodeoffset) +{ + int val; + + val = fdt_cells(fdt, nodeoffset, "#size-cells"); + if (val == -FDT_ERR_NOTFOUND) + return 1; + return val; +} + +/* This function assumes that [address|size]_cells is 1 or 2 */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size) +{ + int addr_cells, size_cells, ret; + uint8_t data[sizeof(fdt64_t) * 2], *prop; + + ret = fdt_address_cells(fdt, parent); + if (ret < 0) + return ret; + addr_cells = ret; + + ret = fdt_size_cells(fdt, parent); + if (ret < 0) + return ret; + size_cells = ret; + + /* check validity of address */ + prop = data; + if (addr_cells == 1) { + if ((addr > UINT32_MAX) || (((uint64_t) UINT32_MAX + 1 - addr) < size)) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)addr); + } else if (addr_cells == 2) { + fdt64_st(prop, addr); + } else { + return -FDT_ERR_BADNCELLS; + } + + /* check validity of size */ + prop += addr_cells * sizeof(fdt32_t); + if (size_cells == 1) { + if (size > UINT32_MAX) + return -FDT_ERR_BADVALUE; + + fdt32_st(prop, (uint32_t)size); + } else if (size_cells == 2) { + fdt64_st(prop, size); + } else { + return -FDT_ERR_BADNCELLS; + } + + return fdt_appendprop(fdt, nodeoffset, name, data, + (addr_cells + size_cells) * sizeof(fdt32_t)); +} diff --git a/scripts/dtc/libfdt/fdt_empty_tree.c b/scripts/dtc/libfdt/fdt_empty_tree.c new file mode 100644 index 0000000..49d54d4 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_empty_tree.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2012 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_create_empty_tree(void *buf, int bufsize) +{ + int err; + + err = fdt_create(buf, bufsize); + if (err) + return err; + + err = fdt_finish_reservemap(buf); + if (err) + return err; + + err = fdt_begin_node(buf, ""); + if (err) + return err; + + err = fdt_end_node(buf); + if (err) + return err; + + err = fdt_finish(buf); + if (err) + return err; + + return fdt_open_into(buf, buf, bufsize); +} diff --git a/scripts/dtc/libfdt/fdt_overlay.c b/scripts/dtc/libfdt/fdt_overlay.c new file mode 100644 index 0000000..51a3859 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_overlay.c @@ -0,0 +1,1101 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +/** + * overlay_get_target_phandle - retrieves the target phandle of a fragment + * @fdto: pointer to the device tree overlay blob + * @fragment: node offset of the fragment in the overlay + * + * overlay_get_target_phandle() retrieves the target phandle of an + * overlay fragment when that fragment uses a phandle (target + * property) instead of a path (target-path property). + * + * returns: + * the phandle pointed by the target property + * 0, if the phandle was not found + * -1, if the phandle was malformed + */ +static uint32_t overlay_get_target_phandle(const void *fdto, int fragment) +{ + const fdt32_t *val; + int len; + + val = fdt_getprop(fdto, fragment, "target", &len); + if (!val) + return 0; + + if ((len != sizeof(*val)) || (fdt32_to_cpu(*val) == (uint32_t)-1)) + return (uint32_t)-1; + + return fdt32_to_cpu(*val); +} + +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp) +{ + uint32_t phandle; + const char *path = NULL; + int path_len = 0, ret; + + /* Try first to do a phandle based lookup */ + phandle = overlay_get_target_phandle(fdto, fragment_offset); + if (phandle == (uint32_t)-1) + return -FDT_ERR_BADPHANDLE; + + /* no phandle, try path */ + if (!phandle) { + /* And then a path based lookup */ + path = fdt_getprop(fdto, fragment_offset, "target-path", &path_len); + if (path) + ret = fdt_path_offset(fdt, path); + else + ret = path_len; + } else + ret = fdt_node_offset_by_phandle(fdt, phandle); + + /* + * If we haven't found either a target or a + * target-path property in a node that contains a + * __overlay__ subnode (we wouldn't be called + * otherwise), consider it a improperly written + * overlay + */ + if (ret < 0 && path_len == -FDT_ERR_NOTFOUND) + ret = -FDT_ERR_BADOVERLAY; + + /* return on error */ + if (ret < 0) + return ret; + + /* return pointer to path (if available) */ + if (pathp) + *pathp = path ? path : NULL; + + return ret; +} + +/** + * overlay_phandle_add_offset - Increases a phandle by an offset + * @fdt: Base device tree blob + * @node: Device tree overlay blob + * @name: Name of the property to modify (phandle or linux,phandle) + * @delta: offset to apply + * + * overlay_phandle_add_offset() increments a node phandle by a given + * offset. + * + * returns: + * 0 on success. + * Negative error code on error + */ +static int overlay_phandle_add_offset(void *fdt, int node, + const char *name, uint32_t delta) +{ + fdt32_t *valp, val; + int len; + + valp = fdt_getprop_w(fdt, node, name, &len); + if (!valp) + return len; + + if (len != sizeof(val)) + return -FDT_ERR_BADPHANDLE; + + val = fdt32_ld(valp); + if (val + delta < val || val + delta == (uint32_t)-1) + return -FDT_ERR_NOPHANDLES; + + fdt32_st(valp, val + delta); + return 0; +} + +/** + * overlay_adjust_node_phandles - Offsets the phandles of a node + * @fdto: Device tree overlay blob + * @node: Offset of the node we want to adjust + * @delta: Offset to shift the phandles of + * + * overlay_adjust_node_phandles() adds a constant to all the phandles + * of a given node. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_node_phandles(void *fdto, int node, + uint32_t delta) +{ + int child; + int ret; + + ret = overlay_phandle_add_offset(fdto, node, "phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + ret = overlay_phandle_add_offset(fdto, node, "linux,phandle", delta); + if (ret && ret != -FDT_ERR_NOTFOUND) + return ret; + + fdt_for_each_subnode(child, fdto, node) { + ret = overlay_adjust_node_phandles(fdto, child, delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_adjust_local_phandles - Adjust the phandles of a whole overlay + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_adjust_local_phandles() adds a constant to all the + * phandles of an overlay. This is mainly use as part of the overlay + * application process, when we want to update all the overlay + * phandles to not conflict with the overlays of the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_phandles(void *fdto, uint32_t delta) +{ + /* + * Start adjusting the phandles from the overlay root + */ + return overlay_adjust_node_phandles(fdto, 0, delta); +} + +/** + * overlay_update_local_node_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @tree_node: Node offset of the node to operate on + * @fixup_node: Node offset of the matching local fixups node + * @delta: Offset to shift the phandles of + * + * overlay_update_local_nodes_references() update the phandles + * pointing to a node within the device tree overlay by adding a + * constant delta. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_node_references(void *fdto, + int tree_node, + int fixup_node, + uint32_t delta) +{ + int fixup_prop; + int fixup_child; + int ret; + + fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { + const fdt32_t *fixup_val; + const char *name; + char *tree_val; + int fixup_len; + int tree_len; + int i; + + fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, + &name, &fixup_len); + if (!fixup_val) + return fixup_len; + + if (fixup_len % sizeof(uint32_t)) + return -FDT_ERR_BADOVERLAY; + fixup_len /= sizeof(uint32_t); + + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); + if (!tree_val) { + if (tree_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + + return tree_len; + } + + for (i = 0; i < fixup_len; i++) { + fdt32_t *refp; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + + /* + * phandles to fixup can be unaligned, so use + * fdt32_{ld,st}() to read/write them. + */ + fdt32_st(refp, fdt32_ld(refp) + delta); + } + } + + fdt_for_each_subnode(fixup_child, fdto, fixup_node) { + const char *fixup_child_name = fdt_get_name(fdto, fixup_child, + NULL); + int tree_child; + + tree_child = fdt_subnode_offset(fdto, tree_node, + fixup_child_name); + if (tree_child == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (tree_child < 0) + return tree_child; + + ret = overlay_update_local_node_references(fdto, + tree_child, + fixup_child, + delta); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_references - Adjust the overlay references + * @fdto: Device tree overlay blob + * @delta: Offset to shift the phandles of + * + * overlay_update_local_references() update all the phandles pointing + * to a node within the device tree overlay by adding a constant + * delta to not conflict with the base overlay. + * + * This is mainly used as part of a device tree application process, + * where you want the device tree overlays phandles to not conflict + * with the ones from the base device tree before merging them. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_references(void *fdto, uint32_t delta) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups < 0) { + /* There's no local phandles to adjust, bail out */ + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + + return fixups; + } + + /* + * Update our local references from the root of the tree + */ + return overlay_update_local_node_references(fdto, 0, fixups, + delta); +} + +/** + * overlay_fixup_one_phandle - Set an overlay phandle to the base one + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @path: Path to a node holding a phandle in the overlay + * @path_len: number of path characters to consider + * @name: Name of the property holding the phandle reference in the overlay + * @name_len: number of name characters to consider + * @poffset: Offset within the overlay property where the phandle is stored + * @phandle: Phandle referencing the node + * + * overlay_fixup_one_phandle() resolves an overlay phandle pointing to + * a node in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_one_phandle(void *fdto, int symbols_off, + const char *path, uint32_t path_len, + const char *name, uint32_t name_len, + int poffset, uint32_t phandle) +{ + fdt32_t phandle_prop; + int fixup_off; + + if (symbols_off < 0) + return symbols_off; + + fixup_off = fdt_path_offset_namelen(fdto, path, path_len); + if (fixup_off == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (fixup_off < 0) + return fixup_off; + + phandle_prop = cpu_to_fdt32(phandle); + return fdt_setprop_inplace_namelen_partial(fdto, fixup_off, + name, name_len, poffset, + &phandle_prop, + sizeof(phandle_prop)); +} + +/** + * overlay_fixup_phandle - Set an overlay phandle to the base one + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * @symbols_off: Node offset of the symbols node in the base device tree + * @property: Property offset in the overlay holding the list of fixups + * + * overlay_fixup_phandle() resolves all the overlay phandles pointed + * to in a __fixups__ property, and updates them to match the phandles + * in use in the base device tree. + * + * This is part of the device tree overlay application process, when + * you want all the phandles in the overlay to point to the actual + * base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandle(void *fdt, void *fdto, int symbols_off, + int property) +{ + const char *value; + const char *label; + int len; + const char *symbol_path; + int prop_len; + int symbol_off; + uint32_t phandle; + + value = fdt_getprop_by_offset(fdto, property, + &label, &len); + if (!value) { + if (len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + + return len; + } + + symbol_path = fdt_getprop(fdt, symbols_off, label, &prop_len); + if (!symbol_path) + return prop_len; + + symbol_off = fdt_path_offset(fdt, symbol_path); + if (symbol_off < 0) + return symbol_off; + + phandle = fdt_get_phandle(fdt, symbol_off); + if (!phandle) + return -FDT_ERR_NOTFOUND; + + do { + const char *path, *name, *fixup_end; + const char *fixup_str = value; + uint32_t path_len, name_len; + uint32_t fixup_len; + const char *sep; + char *endptr; + int poffset, ret; + + fixup_end = memchr(value, '\0', len); + if (!fixup_end) + return -FDT_ERR_BADOVERLAY; + fixup_len = fixup_end - fixup_str; + + len -= fixup_len + 1; + value += fixup_len + 1; + + path = fixup_str; + sep = memchr(fixup_str, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + path_len = sep - path; + if (path_len == (fixup_len - 1)) + return -FDT_ERR_BADOVERLAY; + + fixup_len -= path_len + 1; + name = sep + 1; + sep = memchr(name, ':', fixup_len); + if (!sep || *sep != ':') + return -FDT_ERR_BADOVERLAY; + + name_len = sep - name; + if (!name_len) + return -FDT_ERR_BADOVERLAY; + + poffset = strtoul(sep + 1, &endptr, 10); + if ((*endptr != '\0') || (endptr <= (sep + 1))) + return -FDT_ERR_BADOVERLAY; + + ret = overlay_fixup_one_phandle(fdto, symbols_off, + path, path_len, name, name_len, + poffset, phandle); + if (ret) + return ret; + } while (len > 0); + + return 0; +} + +/** + * overlay_fixup_phandles - Resolve the overlay phandles to the base + * device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_fixup_phandles() resolves all the overlay phandles pointing + * to nodes in the base device tree. + * + * This is one of the steps of the device tree overlay application + * process, when you want all the phandles in the overlay to point to + * the actual base dt nodes. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_fixup_phandles(void *fdt, void *fdto) +{ + int fixups_off, symbols_off; + int property; + + /* We can have overlays without any fixups */ + fixups_off = fdt_path_offset(fdto, "/__fixups__"); + if (fixups_off == -FDT_ERR_NOTFOUND) + return 0; /* nothing to do */ + if (fixups_off < 0) + return fixups_off; + + /* And base DTs without symbols */ + symbols_off = fdt_path_offset(fdt, "/__symbols__"); + if ((symbols_off < 0 && (symbols_off != -FDT_ERR_NOTFOUND))) + return symbols_off; + + fdt_for_each_property_offset(property, fdto, fixups_off) { + int ret; + + ret = overlay_fixup_phandle(fdt, fdto, symbols_off, property); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_adjust_local_conflicting_phandle: Changes a phandle value + * @fdto: Device tree overlay + * @node: The node the phandle is set for + * @fdt_phandle: The new value for the phandle + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_adjust_local_conflicting_phandle(void *fdto, int node, + uint32_t fdt_phandle) +{ + const fdt32_t *php; + int len, ret; + + php = fdt_getprop(fdto, node, "phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "phandle", fdt_phandle); + if (ret) + return ret; + } + + php = fdt_getprop(fdto, node, "linux,phandle", &len); + if (php && len == sizeof(*php)) { + ret = fdt_setprop_inplace_u32(fdto, node, "linux,phandle", fdt_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_node_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @tree_node: Node to recurse into + * @fixup_node: Node offset of the matching local fixups node + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_node_conflicting_references(void *fdto, int tree_node, + int fixup_node, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixup_prop; + int fixup_child; + int ret; + + fdt_for_each_property_offset(fixup_prop, fdto, fixup_node) { + const fdt32_t *fixup_val; + const char *name; + char *tree_val; + int fixup_len; + int tree_len; + int i; + + fixup_val = fdt_getprop_by_offset(fdto, fixup_prop, + &name, &fixup_len); + if (!fixup_val) + return fixup_len; + + if (fixup_len % sizeof(uint32_t)) + return -FDT_ERR_BADOVERLAY; + fixup_len /= sizeof(uint32_t); + + tree_val = fdt_getprop_w(fdto, tree_node, name, &tree_len); + if (!tree_val) { + if (tree_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + + return tree_len; + } + + for (i = 0; i < fixup_len; i++) { + fdt32_t *refp; + uint32_t valp; + + refp = (fdt32_t *)(tree_val + fdt32_ld_(fixup_val + i)); + valp = fdt32_ld(refp); + + if (valp == fdto_phandle) + fdt32_st(refp, fdt_phandle); + } + } + + fdt_for_each_subnode(fixup_child, fdto, fixup_node) { + const char *fixup_child_name = fdt_get_name(fdto, fixup_child, NULL); + int tree_child; + + tree_child = fdt_subnode_offset(fdto, tree_node, fixup_child_name); + + if (tree_child == -FDT_ERR_NOTFOUND) + return -FDT_ERR_BADOVERLAY; + if (tree_child < 0) + return tree_child; + + ret = overlay_update_node_conflicting_references(fdto, tree_child, + fixup_child, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_update_local_conflicting_references - Recursively replace phandle values + * @fdto: Device tree overlay blob + * @fdt_phandle: Value to replace phandles with + * @fdto_phandle: Value to be replaced + * + * Replaces all phandles with value @fdto_phandle by @fdt_phandle. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_update_local_conflicting_references(void *fdto, + uint32_t fdt_phandle, + uint32_t fdto_phandle) +{ + int fixups; + + fixups = fdt_path_offset(fdto, "/__local_fixups__"); + if (fixups == -FDT_ERR_NOTFOUND) + return 0; + if (fixups < 0) + return fixups; + + return overlay_update_node_conflicting_references(fdto, 0, fixups, + fdt_phandle, + fdto_phandle); +} + +/** + * overlay_prevent_phandle_overwrite_node - Helper function for overlay_prevent_phandle_overwrite + * @fdt: Base Device tree blob + * @fdtnode: Node in fdt that is checked for an overwrite + * @fdto: Device tree overlay blob + * @fdtonode: Node in fdto matching @fdtnode + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite_node(void *fdt, int fdtnode, + void *fdto, int fdtonode) +{ + uint32_t fdt_phandle, fdto_phandle; + int fdtochild; + + fdt_phandle = fdt_get_phandle(fdt, fdtnode); + fdto_phandle = fdt_get_phandle(fdto, fdtonode); + + if (fdt_phandle && fdto_phandle) { + int ret; + + ret = overlay_adjust_local_conflicting_phandle(fdto, fdtonode, + fdt_phandle); + if (ret) + return ret; + + ret = overlay_update_local_conflicting_references(fdto, + fdt_phandle, + fdto_phandle); + if (ret) + return ret; + } + + fdt_for_each_subnode(fdtochild, fdto, fdtonode) { + const char *name = fdt_get_name(fdto, fdtochild, NULL); + int fdtchild; + int ret; + + fdtchild = fdt_subnode_offset(fdt, fdtnode, name); + if (fdtchild == -FDT_ERR_NOTFOUND) + /* + * no further overwrites possible here as this node is + * new + */ + continue; + + ret = overlay_prevent_phandle_overwrite_node(fdt, fdtchild, + fdto, fdtochild); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_prevent_phandle_overwrite - Fixes overlay phandles to not overwrite base phandles + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * Checks recursively if applying fdto overwrites phandle values in the base + * dtb. When such a phandle is found, the fdto is changed to use the fdt's + * phandle value to not break references in the base. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_prevent_phandle_overwrite(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (overlay == -FDT_ERR_NOTFOUND) + continue; + + if (overlay < 0) + return overlay; + + target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); + if (target == -FDT_ERR_NOTFOUND) + /* + * The subtree doesn't exist in the base, so nothing + * will be overwritten. + */ + continue; + else if (target < 0) + return target; + + ret = overlay_prevent_phandle_overwrite_node(fdt, target, + fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_apply_node - Merges a node into the base device tree + * @fdt: Base Device Tree blob + * @target: Node offset in the base device tree to apply the fragment to + * @fdto: Device tree overlay blob + * @node: Node offset in the overlay holding the changes to merge + * + * overlay_apply_node() merges a node into a target base device tree + * node pointed. + * + * This is part of the final step in the device tree overlay + * application process, when all the phandles have been adjusted and + * resolved and you just have to merge overlay into the base device + * tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_apply_node(void *fdt, int target, + void *fdto, int node) +{ + int property; + int subnode; + + fdt_for_each_property_offset(property, fdto, node) { + const char *name; + const void *prop; + int prop_len; + int ret; + + prop = fdt_getprop_by_offset(fdto, property, &name, + &prop_len); + if (prop_len == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + if (prop_len < 0) + return prop_len; + + ret = fdt_setprop(fdt, target, name, prop, prop_len); + if (ret) + return ret; + } + + fdt_for_each_subnode(subnode, fdto, node) { + const char *name = fdt_get_name(fdto, subnode, NULL); + int nnode; + int ret; + + nnode = fdt_add_subnode(fdt, target, name); + if (nnode == -FDT_ERR_EXISTS) { + nnode = fdt_subnode_offset(fdt, target, name); + if (nnode == -FDT_ERR_NOTFOUND) + return -FDT_ERR_INTERNAL; + } + + if (nnode < 0) + return nnode; + + ret = overlay_apply_node(fdt, nnode, fdto, subnode); + if (ret) + return ret; + } + + return 0; +} + +/** + * overlay_merge - Merge an overlay into its base device tree + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_merge() merges an overlay into its base device tree. + * + * This is the next to last step in the device tree overlay application + * process, when all the phandles have been adjusted and resolved and + * you just have to merge overlay into the base device tree. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_merge(void *fdt, void *fdto) +{ + int fragment; + + fdt_for_each_subnode(fragment, fdto, 0) { + int overlay; + int target; + int ret; + + /* + * Each fragments will have an __overlay__ node. If + * they don't, it's not supposed to be merged + */ + overlay = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (overlay == -FDT_ERR_NOTFOUND) + continue; + + if (overlay < 0) + return overlay; + + target = fdt_overlay_target_offset(fdt, fdto, fragment, NULL); + if (target < 0) + return target; + + ret = overlay_apply_node(fdt, target, fdto, overlay); + if (ret) + return ret; + } + + return 0; +} + +static int get_path_len(const void *fdt, int nodeoffset) +{ + int len = 0, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + for (;;) { + name = fdt_get_name(fdt, nodeoffset, &namelen); + if (!name) + return namelen; + + /* root? we're done */ + if (namelen == 0) + break; + + nodeoffset = fdt_parent_offset(fdt, nodeoffset); + if (nodeoffset < 0) + return nodeoffset; + len += namelen + 1; + } + + /* in case of root pretend it's "/" */ + if (len == 0) + len++; + return len; +} + +/** + * overlay_symbol_update - Update the symbols of base tree after a merge + * @fdt: Base Device Tree blob + * @fdto: Device tree overlay blob + * + * overlay_symbol_update() updates the symbols of the base tree with the + * symbols of the applied overlay + * + * This is the last step in the device tree overlay application + * process, allowing the reference of overlay symbols by subsequent + * overlay operations. + * + * returns: + * 0 on success + * Negative error code on failure + */ +static int overlay_symbol_update(void *fdt, void *fdto) +{ + int root_sym, ov_sym, prop, path_len, fragment, target; + int len, frag_name_len, ret, rel_path_len; + const char *s, *e; + const char *path; + const char *name; + const char *frag_name; + const char *rel_path; + const char *target_path; + char *buf; + void *p; + + ov_sym = fdt_subnode_offset(fdto, 0, "__symbols__"); + + /* if no overlay symbols exist no problem */ + if (ov_sym < 0) + return 0; + + root_sym = fdt_subnode_offset(fdt, 0, "__symbols__"); + + /* it no root symbols exist we should create them */ + if (root_sym == -FDT_ERR_NOTFOUND) + root_sym = fdt_add_subnode(fdt, 0, "__symbols__"); + + /* any error is fatal now */ + if (root_sym < 0) + return root_sym; + + /* iterate over each overlay symbol */ + fdt_for_each_property_offset(prop, fdto, ov_sym) { + path = fdt_getprop_by_offset(fdto, prop, &name, &path_len); + if (!path) + return path_len; + + /* verify it's a string property (terminated by a single \0) */ + if (path_len < 1 || memchr(path, '\0', path_len) != &path[path_len - 1]) + return -FDT_ERR_BADVALUE; + + /* keep end marker to avoid strlen() */ + e = path + path_len; + + if (*path != '/') + return -FDT_ERR_BADVALUE; + + /* get fragment name first */ + s = strchr(path + 1, '/'); + if (!s) { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + frag_name = path + 1; + frag_name_len = s - path - 1; + + /* verify format; safe since "s" lies in \0 terminated prop */ + len = sizeof("/__overlay__/") - 1; + if ((e - s) > len && (memcmp(s, "/__overlay__/", len) == 0)) { + /* //__overlay__/ */ + rel_path = s + len; + rel_path_len = e - rel_path - 1; + } else if ((e - s) == len + && (memcmp(s, "/__overlay__", len - 1) == 0)) { + /* //__overlay__ */ + rel_path = ""; + rel_path_len = 0; + } else { + /* Symbol refers to something that won't end + * up in the target tree */ + continue; + } + + /* find the fragment index in which the symbol lies */ + ret = fdt_subnode_offset_namelen(fdto, 0, frag_name, + frag_name_len); + /* not found? */ + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + fragment = ret; + + /* an __overlay__ subnode must exist */ + ret = fdt_subnode_offset(fdto, fragment, "__overlay__"); + if (ret < 0) + return -FDT_ERR_BADOVERLAY; + + /* get the target of the fragment */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + + /* if we have a target path use */ + if (!target_path) { + ret = get_path_len(fdt, target); + if (ret < 0) + return ret; + len = ret; + } else { + len = strlen(target_path); + } + + ret = fdt_setprop_placeholder(fdt, root_sym, name, + len + (len > 1) + rel_path_len + 1, &p); + if (ret < 0) + return ret; + + if (!target_path) { + /* again in case setprop_placeholder changed it */ + ret = fdt_overlay_target_offset(fdt, fdto, fragment, &target_path); + if (ret < 0) + return ret; + target = ret; + } + + buf = p; + if (len > 1) { /* target is not root */ + if (!target_path) { + ret = fdt_get_path(fdt, target, buf, len + 1); + if (ret < 0) + return ret; + } else + memcpy(buf, target_path, len + 1); + + } else + len--; + + buf[len] = '/'; + memcpy(buf + len + 1, rel_path, rel_path_len); + buf[len + 1 + rel_path_len] = '\0'; + } + + return 0; +} + +int fdt_overlay_apply(void *fdt, void *fdto) +{ + uint32_t delta; + int ret; + + FDT_RO_PROBE(fdt); + FDT_RO_PROBE(fdto); + + ret = fdt_find_max_phandle(fdt, &delta); + if (ret) + goto err; + + /* Increase all phandles in the fdto by delta */ + ret = overlay_adjust_local_phandles(fdto, delta); + if (ret) + goto err; + + /* Adapt the phandle values in fdto to the above increase */ + ret = overlay_update_local_references(fdto, delta); + if (ret) + goto err; + + /* Update fdto's phandles using symbols from fdt */ + ret = overlay_fixup_phandles(fdt, fdto); + if (ret) + goto err; + + /* Don't overwrite phandles in fdt */ + ret = overlay_prevent_phandle_overwrite(fdt, fdto); + if (ret) + goto err; + + ret = overlay_merge(fdt, fdto); + if (ret) + goto err; + + ret = overlay_symbol_update(fdt, fdto); + if (ret) + goto err; + + /* + * The overlay has been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + return 0; + +err: + /* + * The overlay might have been damaged, erase its magic. + */ + fdt_set_magic(fdto, ~0); + + /* + * The base device tree might have been damaged, erase its + * magic. + */ + fdt_set_magic(fdt, ~0); + + return ret; +} diff --git a/scripts/dtc/libfdt/fdt_ro.c b/scripts/dtc/libfdt/fdt_ro.c new file mode 100644 index 0000000..63494fb --- /dev/null +++ b/scripts/dtc/libfdt/fdt_ro.c @@ -0,0 +1,888 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_nodename_eq_(const void *fdt, int offset, + const char *s, int len) +{ + int olen; + const char *p = fdt_get_name(fdt, offset, &olen); + + if (!p || olen < len) + /* short match */ + return 0; + + if (memcmp(p, s, len) != 0) + return 0; + + if (p[len] == '\0') + return 1; + else if (!memchr(s, '@', len) && (p[len] == '@')) + return 1; + else + return 0; +} + +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp) +{ + int32_t totalsize; + uint32_t absoffset; + size_t len; + int err; + const char *s, *n; + + if (can_assume(VALID_INPUT)) { + s = (const char *)fdt + fdt_off_dt_strings(fdt) + stroffset; + + if (lenp) + *lenp = strlen(s); + return s; + } + totalsize = fdt_ro_probe_(fdt); + err = totalsize; + if (totalsize < 0) + goto fail; + + err = -FDT_ERR_BADOFFSET; + absoffset = stroffset + fdt_off_dt_strings(fdt); + if (absoffset >= (unsigned)totalsize) + goto fail; + len = totalsize - absoffset; + + if (fdt_magic(fdt) == FDT_MAGIC) { + if (stroffset < 0) + goto fail; + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + if ((unsigned)stroffset >= fdt_size_dt_strings(fdt)) + goto fail; + if ((fdt_size_dt_strings(fdt) - stroffset) < len) + len = fdt_size_dt_strings(fdt) - stroffset; + } + } else if (fdt_magic(fdt) == FDT_SW_MAGIC) { + unsigned int sw_stroffset = -stroffset; + + if ((stroffset >= 0) || + (sw_stroffset > fdt_size_dt_strings(fdt))) + goto fail; + if (sw_stroffset < len) + len = sw_stroffset; + } else { + err = -FDT_ERR_INTERNAL; + goto fail; + } + + s = (const char *)fdt + absoffset; + n = memchr(s, '\0', len); + if (!n) { + /* missing terminating NULL */ + err = -FDT_ERR_TRUNCATED; + goto fail; + } + + if (lenp) + *lenp = n - s; + return s; + +fail: + if (lenp) + *lenp = err; + return NULL; +} + +const char *fdt_string(const void *fdt, int stroffset) +{ + return fdt_get_string(fdt, stroffset, NULL); +} + +static int fdt_string_eq_(const void *fdt, int stroffset, + const char *s, int len) +{ + int slen; + const char *p = fdt_get_string(fdt, stroffset, &slen); + + return p && (slen == len) && (memcmp(p, s, len) == 0); +} + +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max = 0; + int offset = -1; + + while (true) { + uint32_t value; + + offset = fdt_next_node(fdt, offset, NULL); + if (offset < 0) { + if (offset == -FDT_ERR_NOTFOUND) + break; + + return offset; + } + + value = fdt_get_phandle(fdt, offset); + + if (value > max) + max = value; + } + + if (phandle) + *phandle = max; + + return 0; +} + +int fdt_generate_phandle(const void *fdt, uint32_t *phandle) +{ + uint32_t max; + int err; + + err = fdt_find_max_phandle(fdt, &max); + if (err < 0) + return err; + + if (max == FDT_MAX_PHANDLE) + return -FDT_ERR_NOPHANDLES; + + if (phandle) + *phandle = max + 1; + + return 0; +} + +static const struct fdt_reserve_entry *fdt_mem_rsv(const void *fdt, int n) +{ + unsigned int offset = n * sizeof(struct fdt_reserve_entry); + unsigned int absoffset = fdt_off_mem_rsvmap(fdt) + offset; + + if (!can_assume(VALID_INPUT)) { + if (absoffset < fdt_off_mem_rsvmap(fdt)) + return NULL; + if (absoffset > fdt_totalsize(fdt) - + sizeof(struct fdt_reserve_entry)) + return NULL; + } + return fdt_mem_rsv_(fdt, n); +} + +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size) +{ + const struct fdt_reserve_entry *re; + + FDT_RO_PROBE(fdt); + re = fdt_mem_rsv(fdt, n); + if (!can_assume(VALID_INPUT) && !re) + return -FDT_ERR_BADOFFSET; + + *address = fdt64_ld_(&re->address); + *size = fdt64_ld_(&re->size); + return 0; +} + +int fdt_num_mem_rsv(const void *fdt) +{ + int i; + const struct fdt_reserve_entry *re; + + for (i = 0; (re = fdt_mem_rsv(fdt, i)) != NULL; i++) { + if (fdt64_ld_(&re->size) == 0) + return i; + } + return -FDT_ERR_TRUNCATED; +} + +static int nextprop_(const void *fdt, int offset) +{ + uint32_t tag; + int nextoffset; + + do { + tag = fdt_next_tag(fdt, offset, &nextoffset); + + switch (tag) { + case FDT_END: + if (nextoffset >= 0) + return -FDT_ERR_BADSTRUCTURE; + else + return nextoffset; + + case FDT_PROP: + return offset; + } + offset = nextoffset; + } while (tag == FDT_NOP); + + return -FDT_ERR_NOTFOUND; +} + +int fdt_subnode_offset_namelen(const void *fdt, int offset, + const char *name, int namelen) +{ + int depth; + + FDT_RO_PROBE(fdt); + + for (depth = 0; + (offset >= 0) && (depth >= 0); + offset = fdt_next_node(fdt, offset, &depth)) + if ((depth == 1) + && fdt_nodename_eq_(fdt, offset, name, namelen)) + return offset; + + if (depth < 0) + return -FDT_ERR_NOTFOUND; + return offset; /* error */ +} + +int fdt_subnode_offset(const void *fdt, int parentoffset, + const char *name) +{ + return fdt_subnode_offset_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen) +{ + const char *end = path + namelen; + const char *p = path; + int offset = 0; + + FDT_RO_PROBE(fdt); + + if (!can_assume(VALID_INPUT) && namelen <= 0) + return -FDT_ERR_BADPATH; + + /* see if we have an alias */ + if (*path != '/') { + const char *q = memchr(path, '/', end - p); + + if (!q) + q = end; + + p = fdt_get_alias_namelen(fdt, p, q - p); + if (!p) + return -FDT_ERR_BADPATH; + offset = fdt_path_offset(fdt, p); + + p = q; + } + + while (p < end) { + const char *q; + + while (*p == '/') { + p++; + if (p == end) + return offset; + } + q = memchr(p, '/', end - p); + if (! q) + q = end; + + offset = fdt_subnode_offset_namelen(fdt, offset, p, q-p); + if (offset < 0) + return offset; + + p = q; + } + + return offset; +} + +int fdt_path_offset(const void *fdt, const char *path) +{ + return fdt_path_offset_namelen(fdt, path, strlen(path)); +} + +const char *fdt_get_name(const void *fdt, int nodeoffset, int *len) +{ + const struct fdt_node_header *nh = fdt_offset_ptr_(fdt, nodeoffset); + const char *nameptr; + int err; + + if (!can_assume(VALID_DTB) && (((err = fdt_ro_probe_(fdt)) < 0) + || ((err = fdt_check_node_offset_(fdt, nodeoffset)) < 0))) + goto fail; + + nameptr = nh->name; + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + /* + * For old FDT versions, match the naming conventions of V16: + * give only the leaf name (after all /). The actual tree + * contents are loosely checked. + */ + const char *leaf; + leaf = strrchr(nameptr, '/'); + if (leaf == NULL) { + err = -FDT_ERR_BADSTRUCTURE; + goto fail; + } + nameptr = leaf+1; + } + + if (len) + *len = strlen(nameptr); + + return nameptr; + + fail: + if (len) + *len = err; + return NULL; +} + +int fdt_first_property_offset(const void *fdt, int nodeoffset) +{ + int offset; + + if ((offset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +int fdt_next_property_offset(const void *fdt, int offset) +{ + if ((offset = fdt_check_prop_offset_(fdt, offset)) < 0) + return offset; + + return nextprop_(fdt, offset); +} + +static const struct fdt_property *fdt_get_property_by_offset_(const void *fdt, + int offset, + int *lenp) +{ + int err; + const struct fdt_property *prop; + + if (!can_assume(VALID_INPUT) && + (err = fdt_check_prop_offset_(fdt, offset)) < 0) { + if (lenp) + *lenp = err; + return NULL; + } + + prop = fdt_offset_ptr_(fdt, offset); + + if (lenp) + *lenp = fdt32_ld_(&prop->len); + + return prop; +} + +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_by_offset_(fdt, offset, lenp); +} + +static const struct fdt_property *fdt_get_property_namelen_(const void *fdt, + int offset, + const char *name, + int namelen, + int *lenp, + int *poffset) +{ + for (offset = fdt_first_property_offset(fdt, offset); + (offset >= 0); + (offset = fdt_next_property_offset(fdt, offset))) { + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!can_assume(LIBFDT_FLAWLESS) && !prop) { + offset = -FDT_ERR_INTERNAL; + break; + } + if (fdt_string_eq_(fdt, fdt32_ld_(&prop->nameoff), + name, namelen)) { + if (poffset) + *poffset = offset; + return prop; + } + } + + if (lenp) + *lenp = offset; + return NULL; +} + + +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int offset, + const char *name, + int namelen, int *lenp) +{ + /* Prior to version 16, properties may need realignment + * and this API does not work. fdt_getprop_*() will, however. */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10) { + if (lenp) + *lenp = -FDT_ERR_BADVERSION; + return NULL; + } + + return fdt_get_property_namelen_(fdt, offset, name, namelen, lenp, + NULL); +} + + +const struct fdt_property *fdt_get_property(const void *fdt, + int nodeoffset, + const char *name, int *lenp) +{ + return fdt_get_property_namelen(fdt, nodeoffset, name, + strlen(name), lenp); +} + +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp) +{ + int poffset; + const struct fdt_property *prop; + + prop = fdt_get_property_namelen_(fdt, nodeoffset, name, namelen, lenp, + &poffset); + if (!prop) + return NULL; + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (poffset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp) +{ + const struct fdt_property *prop; + + prop = fdt_get_property_by_offset_(fdt, offset, lenp); + if (!prop) + return NULL; + if (namep) { + const char *name; + int namelen; + + if (!can_assume(VALID_INPUT)) { + name = fdt_get_string(fdt, fdt32_ld_(&prop->nameoff), + &namelen); + *namep = name; + if (!name) { + if (lenp) + *lenp = namelen; + return NULL; + } + } else { + *namep = fdt_string(fdt, fdt32_ld_(&prop->nameoff)); + } + } + + /* Handle realignment */ + if (!can_assume(LATEST) && fdt_version(fdt) < 0x10 && + (offset + sizeof(*prop)) % 8 && fdt32_ld_(&prop->len) >= 8) + return prop->data + 4; + return prop->data; +} + +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return fdt_getprop_namelen(fdt, nodeoffset, name, strlen(name), lenp); +} + +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset) +{ + const fdt32_t *php; + int len; + + /* FIXME: This is a bit sub-optimal, since we potentially scan + * over all the properties twice. */ + php = fdt_getprop(fdt, nodeoffset, "phandle", &len); + if (!php || (len != sizeof(*php))) { + php = fdt_getprop(fdt, nodeoffset, "linux,phandle", &len); + if (!php || (len != sizeof(*php))) + return 0; + } + + return fdt32_ld_(php); +} + +static const void *fdt_path_getprop_namelen(const void *fdt, const char *path, + const char *propname, int propnamelen, + int *lenp) +{ + int offset = fdt_path_offset(fdt, path); + + if (offset < 0) + return NULL; + + return fdt_getprop_namelen(fdt, offset, propname, propnamelen, lenp); +} + +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen) +{ + int len; + const char *alias; + + alias = fdt_path_getprop_namelen(fdt, "/aliases", name, namelen, &len); + + if (!can_assume(VALID_DTB) && + !(alias && len > 0 && alias[len - 1] == '\0' && *alias == '/')) + return NULL; + + return alias; +} + +const char *fdt_get_alias(const void *fdt, const char *name) +{ + return fdt_get_alias_namelen(fdt, name, strlen(name)); +} + +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen) +{ + return fdt_path_getprop_namelen(fdt, "/__symbols__", name, namelen, NULL); +} + +const char *fdt_get_symbol(const void *fdt, const char *name) +{ + return fdt_get_symbol_namelen(fdt, name, strlen(name)); +} + +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen) +{ + int pdepth = 0, p = 0; + int offset, depth, namelen; + const char *name; + + FDT_RO_PROBE(fdt); + + if (buflen < 2) + return -FDT_ERR_NOSPACE; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + while (pdepth > depth) { + do { + p--; + } while (buf[p-1] != '/'); + pdepth--; + } + + if (pdepth >= depth) { + name = fdt_get_name(fdt, offset, &namelen); + if (!name) + return namelen; + if ((p + namelen + 1) <= buflen) { + memcpy(buf + p, name, namelen); + p += namelen; + buf[p++] = '/'; + pdepth++; + } + } + + if (offset == nodeoffset) { + if (pdepth < (depth + 1)) + return -FDT_ERR_NOSPACE; + + if (p > 1) /* special case so that root path is "/", not "" */ + p--; + buf[p] = '\0'; + return 0; + } + } + + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + + return offset; /* error from fdt_next_node() */ +} + +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth) +{ + int offset, depth; + int supernodeoffset = -FDT_ERR_INTERNAL; + + FDT_RO_PROBE(fdt); + + if (supernodedepth < 0) + return -FDT_ERR_NOTFOUND; + + for (offset = 0, depth = 0; + (offset >= 0) && (offset <= nodeoffset); + offset = fdt_next_node(fdt, offset, &depth)) { + if (depth == supernodedepth) + supernodeoffset = offset; + + if (offset == nodeoffset) { + if (nodedepth) + *nodedepth = depth; + + if (supernodedepth > depth) + return -FDT_ERR_NOTFOUND; + else + return supernodeoffset; + } + } + + if (!can_assume(VALID_INPUT)) { + if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) + return -FDT_ERR_BADOFFSET; + else if (offset == -FDT_ERR_BADOFFSET) + return -FDT_ERR_BADSTRUCTURE; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_depth(const void *fdt, int nodeoffset) +{ + int nodedepth; + int err; + + err = fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, &nodedepth); + if (err) + return (can_assume(LIBFDT_FLAWLESS) || err < 0) ? err : + -FDT_ERR_INTERNAL; + return nodedepth; +} + +int fdt_parent_offset(const void *fdt, int nodeoffset) +{ + int nodedepth = fdt_node_depth(fdt, nodeoffset); + + if (nodedepth < 0) + return nodedepth; + return fdt_supernode_atdepth_offset(fdt, nodeoffset, + nodedepth - 1, NULL); +} + +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen) +{ + int offset; + const void *val; + int len; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_getprop(), then if that didn't + * find what we want, we scan over them again making our way + * to the next node. Still it's the easiest to implement + * approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + val = fdt_getprop(fdt, offset, propname, &len); + if (val && (len == proplen) + && (memcmp(val, propval, len) == 0)) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle) +{ + int offset; + + if ((phandle == 0) || (phandle == ~0U)) + return -FDT_ERR_BADPHANDLE; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we + * potentially scan each property of a node in + * fdt_get_phandle(), then if that didn't find what + * we want, we scan over them again making our way to the next + * node. Still it's the easiest to implement approach; + * performance can come later. */ + for (offset = fdt_next_node(fdt, -1, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + if (fdt_get_phandle(fdt, offset) == phandle) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} + +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str) +{ + int len = strlen(str); + const char *p; + + while (listlen >= len) { + if (memcmp(str, strlist, len+1) == 0) + return 1; + p = memchr(strlist, '\0', listlen); + if (!p) + return 0; /* malformed strlist.. */ + listlen -= (p-strlist) + 1; + strlist = p + 1; + } + return 0; +} + +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property) +{ + const char *list, *end; + int length, count = 0; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + list += length; + count++; + } + + return count; +} + +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string) +{ + int length, len, idx = 0; + const char *list, *end; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) + return length; + + len = strlen(string) + 1; + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) + return -FDT_ERR_BADVALUE; + + if (length == len && memcmp(list, string, length) == 0) + return idx; + + list += length; + idx++; + } + + return -FDT_ERR_NOTFOUND; +} + +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int idx, + int *lenp) +{ + const char *list, *end; + int length; + + list = fdt_getprop(fdt, nodeoffset, property, &length); + if (!list) { + if (lenp) + *lenp = length; + + return NULL; + } + + end = list + length; + + while (list < end) { + length = strnlen(list, end - list) + 1; + + /* Abort if the last string isn't properly NUL-terminated. */ + if (list + length > end) { + if (lenp) + *lenp = -FDT_ERR_BADVALUE; + + return NULL; + } + + if (idx == 0) { + if (lenp) + *lenp = length - 1; + + return list; + } + + list += length; + idx--; + } + + if (lenp) + *lenp = -FDT_ERR_NOTFOUND; + + return NULL; +} + +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible) +{ + const void *prop; + int len; + + prop = fdt_getprop(fdt, nodeoffset, "compatible", &len); + if (!prop) + return len; + + return !fdt_stringlist_contains(prop, len, compatible); +} + +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible) +{ + int offset, err; + + FDT_RO_PROBE(fdt); + + /* FIXME: The algorithm here is pretty horrible: we scan each + * property of a node in fdt_node_check_compatible(), then if + * that didn't find what we want, we scan over them again + * making our way to the next node. Still it's the easiest to + * implement approach; performance can come later. */ + for (offset = fdt_next_node(fdt, startoffset, NULL); + offset >= 0; + offset = fdt_next_node(fdt, offset, NULL)) { + err = fdt_node_check_compatible(fdt, offset, compatible); + if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) + return err; + else if (err == 0) + return offset; + } + + return offset; /* error from fdt_next_node() */ +} diff --git a/scripts/dtc/libfdt/fdt_rw.c b/scripts/dtc/libfdt/fdt_rw.c new file mode 100644 index 0000000..90ea14e --- /dev/null +++ b/scripts/dtc/libfdt/fdt_rw.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_blocks_misordered_(const void *fdt, + int mem_rsv_size, int struct_size) +{ + return (fdt_off_mem_rsvmap(fdt) < FDT_ALIGN(sizeof(struct fdt_header), 8)) + || (fdt_off_dt_struct(fdt) < + (fdt_off_mem_rsvmap(fdt) + mem_rsv_size)) + || (fdt_off_dt_strings(fdt) < + (fdt_off_dt_struct(fdt) + struct_size)) + || (fdt_totalsize(fdt) < + (fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt))); +} + +static void fdt_downgrade_version(void *fdt) +{ + if (!can_assume(LATEST) && fdt_version(fdt) > FDT_LAST_SUPPORTED_VERSION) + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); +} + +static int fdt_rw_probe_(void *fdt) +{ + if (can_assume(VALID_DTB)) + return 0; + FDT_RO_PROBE(fdt); + + if (!can_assume(LATEST) && fdt_version(fdt) < 17) + return -FDT_ERR_BADVERSION; + if (fdt_blocks_misordered_(fdt, sizeof(struct fdt_reserve_entry), + fdt_size_dt_struct(fdt))) + return -FDT_ERR_BADLAYOUT; + + fdt_downgrade_version(fdt); + return 0; +} + +#define FDT_RW_PROBE(fdt) \ + { \ + int err_; \ + if ((err_ = fdt_rw_probe_(fdt)) != 0) \ + return err_; \ + } + +static inline unsigned int fdt_data_size_(void *fdt) +{ + return fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); +} + +static int fdt_splice_(void *fdt, void *splicepoint, int oldlen, int newlen) +{ + char *p = splicepoint; + unsigned int dsize = fdt_data_size_(fdt); + size_t soff = p - (char *)fdt; + + if ((oldlen < 0) || (soff + oldlen < soff) || (soff + oldlen > dsize)) + return -FDT_ERR_BADOFFSET; + if ((p < (char *)fdt) || (dsize + newlen < (unsigned)oldlen)) + return -FDT_ERR_BADOFFSET; + if (dsize - oldlen + newlen > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + memmove(p + newlen, p + oldlen, ((char *)fdt + dsize) - (p + oldlen)); + return 0; +} + +static int fdt_splice_mem_rsv_(void *fdt, struct fdt_reserve_entry *p, + int oldn, int newn) +{ + int delta = (newn - oldn) * sizeof(*p); + int err; + err = fdt_splice_(fdt, p, oldn * sizeof(*p), newn * sizeof(*p)); + if (err) + return err; + fdt_set_off_dt_struct(fdt, fdt_off_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +static int fdt_splice_struct_(void *fdt, void *p, + int oldlen, int newlen) +{ + int delta = newlen - oldlen; + int err; + + if ((err = fdt_splice_(fdt, p, oldlen, newlen))) + return err; + + fdt_set_size_dt_struct(fdt, fdt_size_dt_struct(fdt) + delta); + fdt_set_off_dt_strings(fdt, fdt_off_dt_strings(fdt) + delta); + return 0; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int newlen = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) - newlen); +} + +static int fdt_splice_string_(void *fdt, int newlen) +{ + void *p = (char *)fdt + + fdt_off_dt_strings(fdt) + fdt_size_dt_strings(fdt); + int err; + + if ((err = fdt_splice_(fdt, p, 0, newlen))) + return err; + + fdt_set_size_dt_strings(fdt, fdt_size_dt_strings(fdt) + newlen); + return 0; +} + +/** + * fdt_find_add_string_() - Find or allocate a string + * + * @fdt: pointer to the device tree to check/adjust + * @s: string to find/add + * @allocated: Set to 0 if the string was found, 1 if not found and so + * allocated. Ignored if can_assume(NO_ROLLBACK) + * @return offset of string in the string table (whether found or added) + */ +static int fdt_find_add_string_(void *fdt, const char *s, int slen, + int *allocated) +{ + char *strtab = (char *)fdt + fdt_off_dt_strings(fdt); + const char *p; + char *new; + int err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 0; + + p = fdt_find_string_len_(strtab, fdt_size_dt_strings(fdt), s, slen); + if (p) + /* found it */ + return (p - strtab); + + new = strtab + fdt_size_dt_strings(fdt); + err = fdt_splice_string_(fdt, slen + 1); + if (err) + return err; + + if (!can_assume(NO_ROLLBACK)) + *allocated = 1; + + memcpy(new, s, slen); + new[slen] = '\0'; + + return (new - strtab); +} + +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size) +{ + struct fdt_reserve_entry *re; + int err; + + FDT_RW_PROBE(fdt); + + re = fdt_mem_rsv_w_(fdt, fdt_num_mem_rsv(fdt)); + err = fdt_splice_mem_rsv_(fdt, re, 0, 1); + if (err) + return err; + + re->address = cpu_to_fdt64(address); + re->size = cpu_to_fdt64(size); + return 0; +} + +int fdt_del_mem_rsv(void *fdt, int n) +{ + struct fdt_reserve_entry *re = fdt_mem_rsv_w_(fdt, n); + + FDT_RW_PROBE(fdt); + + if (n >= fdt_num_mem_rsv(fdt)) + return -FDT_ERR_NOTFOUND; + + return fdt_splice_mem_rsv_(fdt, re, 1, 0); +} + +static int fdt_resize_property_(void *fdt, int nodeoffset, + const char *name, int namelen, + int len, struct fdt_property **prop) +{ + int oldlen; + int err; + + *prop = fdt_get_property_namelen_w(fdt, nodeoffset, name, namelen, + &oldlen); + if (!*prop) + return oldlen; + + if ((err = fdt_splice_struct_(fdt, (*prop)->data, FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(len)))) + return err; + + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +static int fdt_add_property_(void *fdt, int nodeoffset, const char *name, + int namelen, int len, struct fdt_property **prop) +{ + int proplen; + int nextoffset; + int namestroff; + int err; + int allocated; + + if ((nextoffset = fdt_check_node_offset_(fdt, nodeoffset)) < 0) + return nextoffset; + + namestroff = fdt_find_add_string_(fdt, name, namelen, &allocated); + if (namestroff < 0) + return namestroff; + + *prop = fdt_offset_ptr_w_(fdt, nextoffset); + proplen = sizeof(**prop) + FDT_TAGALIGN(len); + + err = fdt_splice_struct_(fdt, *prop, 0, proplen); + if (err) { + /* Delete the string if we failed to add it */ + if (!can_assume(NO_ROLLBACK) && allocated) + fdt_del_last_string_(fdt, name); + return err; + } + + (*prop)->tag = cpu_to_fdt32(FDT_PROP); + (*prop)->nameoff = cpu_to_fdt32(namestroff); + (*prop)->len = cpu_to_fdt32(len); + return 0; +} + +int fdt_set_name(void *fdt, int nodeoffset, const char *name) +{ + char *namep; + int oldlen, newlen; + int err; + + FDT_RW_PROBE(fdt); + + namep = (char *)(uintptr_t)fdt_get_name(fdt, nodeoffset, &oldlen); + if (!namep) + return oldlen; + + newlen = strlen(name); + + err = fdt_splice_struct_(fdt, namep, FDT_TAGALIGN(oldlen+1), + FDT_TAGALIGN(newlen+1)); + if (err) + return err; + + memcpy(namep, name, newlen+1); + return 0; +} + +int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, int len, void **prop_data) +{ + struct fdt_property *prop; + int err; + + FDT_RW_PROBE(fdt); + + err = fdt_resize_property_(fdt, nodeoffset, name, namelen, len, &prop); + if (err == -FDT_ERR_NOTFOUND) + err = fdt_add_property_(fdt, nodeoffset, name, namelen, len, + &prop); + if (err) + return err; + + *prop_data = prop->data; + return 0; +} + +int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, const void *val, int len) +{ + void *prop_data; + int err; + + err = fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, namelen, + len, &prop_data); + if (err) + return err; + + if (len) + memcpy(prop_data, val, len); + return 0; +} + +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + struct fdt_property *prop; + int err, oldlen, newlen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &oldlen); + if (prop) { + newlen = len + oldlen; + err = fdt_splice_struct_(fdt, prop->data, + FDT_TAGALIGN(oldlen), + FDT_TAGALIGN(newlen)); + if (err) + return err; + prop->len = cpu_to_fdt32(newlen); + memcpy(prop->data + oldlen, val, len); + } else { + err = fdt_add_property_(fdt, nodeoffset, name, strlen(name), + len, &prop); + if (err) + return err; + memcpy(prop->data, val, len); + } + return 0; +} + +int fdt_delprop(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len, proplen; + + FDT_RW_PROBE(fdt); + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + proplen = sizeof(*prop) + FDT_TAGALIGN(len); + return fdt_splice_struct_(fdt, prop, proplen, 0); +} + +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen) +{ + struct fdt_node_header *nh; + int offset, nextoffset; + int nodelen; + int err; + uint32_t tag; + fdt32_t *endtag; + + FDT_RW_PROBE(fdt); + + offset = fdt_subnode_offset_namelen(fdt, parentoffset, name, namelen); + if (offset >= 0) + return -FDT_ERR_EXISTS; + else if (offset != -FDT_ERR_NOTFOUND) + return offset; + + /* Try to place the new node after the parent's properties */ + tag = fdt_next_tag(fdt, parentoffset, &nextoffset); + /* the fdt_subnode_offset_namelen() should ensure this never hits */ + if (!can_assume(LIBFDT_FLAWLESS) && (tag != FDT_BEGIN_NODE)) + return -FDT_ERR_INTERNAL; + do { + offset = nextoffset; + tag = fdt_next_tag(fdt, offset, &nextoffset); + } while ((tag == FDT_PROP) || (tag == FDT_NOP)); + + nh = fdt_offset_ptr_w_(fdt, offset); + nodelen = sizeof(*nh) + FDT_TAGALIGN(namelen+1) + FDT_TAGSIZE; + + err = fdt_splice_struct_(fdt, nh, 0, nodelen); + if (err) + return err; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memset(nh->name, 0, FDT_TAGALIGN(namelen+1)); + memcpy(nh->name, name, namelen); + endtag = (fdt32_t *)((char *)nh + nodelen - FDT_TAGSIZE); + *endtag = cpu_to_fdt32(FDT_END_NODE); + + return offset; +} + +int fdt_add_subnode(void *fdt, int parentoffset, const char *name) +{ + return fdt_add_subnode_namelen(fdt, parentoffset, name, strlen(name)); +} + +int fdt_del_node(void *fdt, int nodeoffset) +{ + int endoffset; + + FDT_RW_PROBE(fdt); + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + return fdt_splice_struct_(fdt, fdt_offset_ptr_w_(fdt, nodeoffset), + endoffset - nodeoffset, 0); +} + +static void fdt_packblocks_(const char *old, char *new, + int mem_rsv_size, + int struct_size, + int strings_size) +{ + int mem_rsv_off, struct_off, strings_off; + + mem_rsv_off = FDT_ALIGN(sizeof(struct fdt_header), 8); + struct_off = mem_rsv_off + mem_rsv_size; + strings_off = struct_off + struct_size; + + memmove(new + mem_rsv_off, old + fdt_off_mem_rsvmap(old), mem_rsv_size); + fdt_set_off_mem_rsvmap(new, mem_rsv_off); + + memmove(new + struct_off, old + fdt_off_dt_struct(old), struct_size); + fdt_set_off_dt_struct(new, struct_off); + fdt_set_size_dt_struct(new, struct_size); + + memmove(new + strings_off, old + fdt_off_dt_strings(old), strings_size); + fdt_set_off_dt_strings(new, strings_off); + fdt_set_size_dt_strings(new, fdt_size_dt_strings(old)); +} + +int fdt_open_into(const void *fdt, void *buf, int bufsize) +{ + int err; + int mem_rsv_size, struct_size; + int newsize; + const char *fdtstart = fdt; + const char *fdtend = fdtstart + fdt_totalsize(fdt); + char *tmp; + + FDT_RO_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + + if (can_assume(LATEST) || fdt_version(fdt) >= 17) { + struct_size = fdt_size_dt_struct(fdt); + } else if (fdt_version(fdt) == 16) { + struct_size = 0; + while (fdt_next_tag(fdt, struct_size, &struct_size) != FDT_END) + ; + if (struct_size < 0) + return struct_size; + } else { + return -FDT_ERR_BADVERSION; + } + + if (can_assume(LIBFDT_ORDER) || + !fdt_blocks_misordered_(fdt, mem_rsv_size, struct_size)) { + /* no further work necessary */ + err = fdt_move(fdt, buf, bufsize); + if (err) + return err; + fdt_set_version(buf, 17); + fdt_set_size_dt_struct(buf, struct_size); + fdt_set_totalsize(buf, bufsize); + return 0; + } + + /* Need to reorder */ + newsize = FDT_ALIGN(sizeof(struct fdt_header), 8) + mem_rsv_size + + struct_size + fdt_size_dt_strings(fdt); + + if (bufsize < newsize) + return -FDT_ERR_NOSPACE; + + /* First attempt to build converted tree at beginning of buffer */ + tmp = buf; + /* But if that overlaps with the old tree... */ + if (((tmp + newsize) > fdtstart) && (tmp < fdtend)) { + /* Try right after the old tree instead */ + tmp = (char *)(uintptr_t)fdtend; + if ((tmp + newsize) > ((char *)buf + bufsize)) + return -FDT_ERR_NOSPACE; + } + + fdt_packblocks_(fdt, tmp, mem_rsv_size, struct_size, + fdt_size_dt_strings(fdt)); + memmove(buf, tmp, newsize); + + fdt_set_magic(buf, FDT_MAGIC); + fdt_set_totalsize(buf, bufsize); + fdt_set_version(buf, 17); + fdt_set_last_comp_version(buf, 16); + fdt_set_boot_cpuid_phys(buf, fdt_boot_cpuid_phys(fdt)); + + return 0; +} + +int fdt_pack(void *fdt) +{ + int mem_rsv_size; + + FDT_RW_PROBE(fdt); + + mem_rsv_size = (fdt_num_mem_rsv(fdt)+1) + * sizeof(struct fdt_reserve_entry); + fdt_packblocks_(fdt, fdt, mem_rsv_size, fdt_size_dt_struct(fdt), + fdt_size_dt_strings(fdt)); + fdt_set_totalsize(fdt, fdt_data_size_(fdt)); + + return 0; +} diff --git a/scripts/dtc/libfdt/fdt_strerror.c b/scripts/dtc/libfdt/fdt_strerror.c new file mode 100644 index 0000000..d852b77 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_strerror.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +struct fdt_errtabent { + const char *str; +}; + +#define FDT_ERRTABENT(val) \ + [(val)] = { .str = #val, } + +static struct fdt_errtabent fdt_errtable[] = { + FDT_ERRTABENT(FDT_ERR_NOTFOUND), + FDT_ERRTABENT(FDT_ERR_EXISTS), + FDT_ERRTABENT(FDT_ERR_NOSPACE), + + FDT_ERRTABENT(FDT_ERR_BADOFFSET), + FDT_ERRTABENT(FDT_ERR_BADPATH), + FDT_ERRTABENT(FDT_ERR_BADPHANDLE), + FDT_ERRTABENT(FDT_ERR_BADSTATE), + + FDT_ERRTABENT(FDT_ERR_TRUNCATED), + FDT_ERRTABENT(FDT_ERR_BADMAGIC), + FDT_ERRTABENT(FDT_ERR_BADVERSION), + FDT_ERRTABENT(FDT_ERR_BADSTRUCTURE), + FDT_ERRTABENT(FDT_ERR_BADLAYOUT), + FDT_ERRTABENT(FDT_ERR_INTERNAL), + FDT_ERRTABENT(FDT_ERR_BADNCELLS), + FDT_ERRTABENT(FDT_ERR_BADVALUE), + FDT_ERRTABENT(FDT_ERR_BADOVERLAY), + FDT_ERRTABENT(FDT_ERR_NOPHANDLES), + FDT_ERRTABENT(FDT_ERR_BADFLAGS), + FDT_ERRTABENT(FDT_ERR_ALIGNMENT), +}; +#define FDT_ERRTABSIZE ((int)(sizeof(fdt_errtable) / sizeof(fdt_errtable[0]))) + +const char *fdt_strerror(int errval) +{ + if (errval > 0) + return ""; + else if (errval == 0) + return ""; + else if (-errval < FDT_ERRTABSIZE) { + const char *s = fdt_errtable[-errval].str; + + if (s) + return s; + } + + return ""; +} diff --git a/scripts/dtc/libfdt/fdt_sw.c b/scripts/dtc/libfdt/fdt_sw.c new file mode 100644 index 0000000..4c569ee --- /dev/null +++ b/scripts/dtc/libfdt/fdt_sw.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +static int fdt_sw_probe_(void *fdt) +{ + if (!can_assume(VALID_INPUT)) { + if (fdt_magic(fdt) == FDT_MAGIC) + return -FDT_ERR_BADSTATE; + else if (fdt_magic(fdt) != FDT_SW_MAGIC) + return -FDT_ERR_BADMAGIC; + } + + return 0; +} + +#define FDT_SW_PROBE(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_(fdt)) != 0) \ + return err; \ + } + +/* 'memrsv' state: Initial state after fdt_create() + * + * Allowed functions: + * fdt_add_reservemap_entry() + * fdt_finish_reservemap() [moves to 'struct' state] + */ +static int fdt_sw_probe_memrsv_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && fdt_off_dt_strings(fdt) != 0) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_MEMRSV(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_memrsv_(fdt)) != 0) \ + return err; \ + } + +/* 'struct' state: Enter this state after fdt_finish_reservemap() + * + * Allowed functions: + * fdt_begin_node() + * fdt_end_node() + * fdt_property*() + * fdt_finish() [moves to 'complete' state] + */ +static int fdt_sw_probe_struct_(void *fdt) +{ + int err = fdt_sw_probe_(fdt); + if (err) + return err; + + if (!can_assume(VALID_INPUT) && + fdt_off_dt_strings(fdt) != fdt_totalsize(fdt)) + return -FDT_ERR_BADSTATE; + return 0; +} + +#define FDT_SW_PROBE_STRUCT(fdt) \ + { \ + int err; \ + if ((err = fdt_sw_probe_struct_(fdt)) != 0) \ + return err; \ + } + +static inline uint32_t sw_flags(void *fdt) +{ + /* assert: (fdt_magic(fdt) == FDT_SW_MAGIC) */ + return fdt_last_comp_version(fdt); +} + +/* 'complete' state: Enter this state after fdt_finish() + * + * Allowed functions: none + */ + +static void *fdt_grab_space_(void *fdt, size_t len) +{ + unsigned int offset = fdt_size_dt_struct(fdt); + unsigned int spaceleft; + + spaceleft = fdt_totalsize(fdt) - fdt_off_dt_struct(fdt) + - fdt_size_dt_strings(fdt); + + if ((offset + len < offset) || (offset + len > spaceleft)) + return NULL; + + fdt_set_size_dt_struct(fdt, offset + len); + return fdt_offset_ptr_w_(fdt, offset); +} + +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags) +{ + const int hdrsize = FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry)); + void *fdt = buf; + + if (bufsize < hdrsize) + return -FDT_ERR_NOSPACE; + + if (flags & ~FDT_CREATE_FLAGS_ALL) + return -FDT_ERR_BADFLAGS; + + memset(buf, 0, bufsize); + + /* + * magic and last_comp_version keep intermediate state during the fdt + * creation process, which is replaced with the proper FDT format by + * fdt_finish(). + * + * flags should be accessed with sw_flags(). + */ + fdt_set_magic(fdt, FDT_SW_MAGIC); + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); + fdt_set_last_comp_version(fdt, flags); + + fdt_set_totalsize(fdt, bufsize); + + fdt_set_off_mem_rsvmap(fdt, hdrsize); + fdt_set_off_dt_struct(fdt, fdt_off_mem_rsvmap(fdt)); + fdt_set_off_dt_strings(fdt, 0); + + return 0; +} + +int fdt_create(void *buf, int bufsize) +{ + return fdt_create_with_flags(buf, bufsize, 0); +} + +int fdt_resize(void *fdt, void *buf, int bufsize) +{ + size_t headsize, tailsize; + char *oldtail, *newtail; + + FDT_SW_PROBE(fdt); + + if (bufsize < 0) + return -FDT_ERR_NOSPACE; + + headsize = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + tailsize = fdt_size_dt_strings(fdt); + + if (!can_assume(VALID_DTB) && + headsize + tailsize > fdt_totalsize(fdt)) + return -FDT_ERR_INTERNAL; + + if ((headsize + tailsize) > (unsigned)bufsize) + return -FDT_ERR_NOSPACE; + + oldtail = (char *)fdt + fdt_totalsize(fdt) - tailsize; + newtail = (char *)buf + bufsize - tailsize; + + /* Two cases to avoid clobbering data if the old and new + * buffers partially overlap */ + if (buf <= fdt) { + memmove(buf, fdt, headsize); + memmove(newtail, oldtail, tailsize); + } else { + memmove(newtail, oldtail, tailsize); + memmove(buf, fdt, headsize); + } + + fdt_set_totalsize(buf, bufsize); + if (fdt_off_dt_strings(buf)) + fdt_set_off_dt_strings(buf, bufsize); + + return 0; +} + +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size) +{ + struct fdt_reserve_entry *re; + int offset; + + FDT_SW_PROBE_MEMRSV(fdt); + + offset = fdt_off_dt_struct(fdt); + if ((offset + sizeof(*re)) > fdt_totalsize(fdt)) + return -FDT_ERR_NOSPACE; + + re = (struct fdt_reserve_entry *)((char *)fdt + offset); + re->address = cpu_to_fdt64(addr); + re->size = cpu_to_fdt64(size); + + fdt_set_off_dt_struct(fdt, offset + sizeof(*re)); + + return 0; +} + +int fdt_finish_reservemap(void *fdt) +{ + int err = fdt_add_reservemap_entry(fdt, 0, 0); + + if (err) + return err; + + fdt_set_off_dt_strings(fdt, fdt_totalsize(fdt)); + return 0; +} + +int fdt_begin_node(void *fdt, const char *name) +{ + struct fdt_node_header *nh; + int namelen; + + FDT_SW_PROBE_STRUCT(fdt); + + namelen = strlen(name) + 1; + nh = fdt_grab_space_(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen)); + if (! nh) + return -FDT_ERR_NOSPACE; + + nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE); + memcpy(nh->name, name, namelen); + return 0; +} + +int fdt_end_node(void *fdt) +{ + fdt32_t *en; + + FDT_SW_PROBE_STRUCT(fdt); + + en = fdt_grab_space_(fdt, FDT_TAGSIZE); + if (! en) + return -FDT_ERR_NOSPACE; + + *en = cpu_to_fdt32(FDT_END_NODE); + return 0; +} + +static int fdt_add_string_(void *fdt, const char *s) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + unsigned int strtabsize = fdt_size_dt_strings(fdt); + unsigned int len = strlen(s) + 1; + unsigned int struct_top, offset; + + offset = strtabsize + len; + struct_top = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + if (fdt_totalsize(fdt) - offset < struct_top) + return 0; /* no more room :( */ + + memcpy(strtab - offset, s, len); + fdt_set_size_dt_strings(fdt, strtabsize + len); + return -offset; +} + +/* Must only be used to roll back in case of error */ +static void fdt_del_last_string_(void *fdt, const char *s) +{ + int strtabsize = fdt_size_dt_strings(fdt); + int len = strlen(s) + 1; + + fdt_set_size_dt_strings(fdt, strtabsize - len); +} + +static int fdt_find_add_string_(void *fdt, const char *s, int *allocated) +{ + char *strtab = (char *)fdt + fdt_totalsize(fdt); + int strtabsize = fdt_size_dt_strings(fdt); + const char *p; + + *allocated = 0; + + p = fdt_find_string_(strtab - strtabsize, strtabsize, s); + if (p) + return p - strtab; + + *allocated = 1; + + return fdt_add_string_(fdt, s); +} + +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp) +{ + struct fdt_property *prop; + int nameoff; + int allocated; + + FDT_SW_PROBE_STRUCT(fdt); + + /* String de-duplication can be slow, _NO_NAME_DEDUP skips it */ + if (sw_flags(fdt) & FDT_CREATE_FLAG_NO_NAME_DEDUP) { + allocated = 1; + nameoff = fdt_add_string_(fdt, name); + } else { + nameoff = fdt_find_add_string_(fdt, name, &allocated); + } + if (nameoff == 0) + return -FDT_ERR_NOSPACE; + + prop = fdt_grab_space_(fdt, sizeof(*prop) + FDT_TAGALIGN(len)); + if (! prop) { + if (allocated) + fdt_del_last_string_(fdt, name); + return -FDT_ERR_NOSPACE; + } + + prop->tag = cpu_to_fdt32(FDT_PROP); + prop->nameoff = cpu_to_fdt32(nameoff); + prop->len = cpu_to_fdt32(len); + *valp = prop->data; + return 0; +} + +int fdt_property(void *fdt, const char *name, const void *val, int len) +{ + void *ptr; + int ret; + + ret = fdt_property_placeholder(fdt, name, len, &ptr); + if (ret) + return ret; + memcpy(ptr, val, len); + return 0; +} + +int fdt_finish(void *fdt) +{ + char *p = (char *)fdt; + fdt32_t *end; + int oldstroffset, newstroffset; + uint32_t tag; + int offset, nextoffset; + + FDT_SW_PROBE_STRUCT(fdt); + + /* Add terminator */ + end = fdt_grab_space_(fdt, sizeof(*end)); + if (! end) + return -FDT_ERR_NOSPACE; + *end = cpu_to_fdt32(FDT_END); + + /* Relocate the string table */ + oldstroffset = fdt_totalsize(fdt) - fdt_size_dt_strings(fdt); + newstroffset = fdt_off_dt_struct(fdt) + fdt_size_dt_struct(fdt); + memmove(p + newstroffset, p + oldstroffset, fdt_size_dt_strings(fdt)); + fdt_set_off_dt_strings(fdt, newstroffset); + + /* Walk the structure, correcting string offsets */ + offset = 0; + while ((tag = fdt_next_tag(fdt, offset, &nextoffset)) != FDT_END) { + if (tag == FDT_PROP) { + struct fdt_property *prop = + fdt_offset_ptr_w_(fdt, offset); + int nameoff; + + nameoff = fdt32_to_cpu(prop->nameoff); + nameoff += fdt_size_dt_strings(fdt); + prop->nameoff = cpu_to_fdt32(nameoff); + } + offset = nextoffset; + } + if (nextoffset < 0) + return nextoffset; + + /* Finally, adjust the header */ + fdt_set_totalsize(fdt, newstroffset + fdt_size_dt_strings(fdt)); + + /* And fix up fields that were keeping intermediate state. */ + fdt_set_last_comp_version(fdt, FDT_LAST_COMPATIBLE_VERSION); + fdt_set_magic(fdt, FDT_MAGIC); + + return 0; +} diff --git a/scripts/dtc/libfdt/fdt_wip.c b/scripts/dtc/libfdt/fdt_wip.c new file mode 100644 index 0000000..c2d7566 --- /dev/null +++ b/scripts/dtc/libfdt/fdt_wip.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include "libfdt_env.h" + +#include +#include + +#include "libfdt_internal.h" + +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len) +{ + void *propval; + int proplen; + + propval = fdt_getprop_namelen_w(fdt, nodeoffset, name, namelen, + &proplen); + if (!propval) + return proplen; + + if ((unsigned)proplen < (len + idx)) + return -FDT_ERR_NOSPACE; + + memcpy((char *)propval + idx, val, len); + return 0; +} + +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + const void *propval; + int proplen; + + propval = fdt_getprop(fdt, nodeoffset, name, &proplen); + if (!propval) + return proplen; + + if (proplen != len) + return -FDT_ERR_NOSPACE; + + return fdt_setprop_inplace_namelen_partial(fdt, nodeoffset, name, + strlen(name), 0, + val, len); +} + +static void fdt_nop_region_(void *start, int len) +{ + fdt32_t *p; + + for (p = start; (char *)p < ((char *)start + len); p++) + *p = cpu_to_fdt32(FDT_NOP); +} + +int fdt_nop_property(void *fdt, int nodeoffset, const char *name) +{ + struct fdt_property *prop; + int len; + + prop = fdt_get_property_w(fdt, nodeoffset, name, &len); + if (!prop) + return len; + + fdt_nop_region_(prop, len + sizeof(*prop)); + + return 0; +} + +int fdt_node_end_offset_(void *fdt, int offset) +{ + int depth = 0; + + while ((offset >= 0) && (depth >= 0)) + offset = fdt_next_node(fdt, offset, &depth); + + return offset; +} + +int fdt_nop_node(void *fdt, int nodeoffset) +{ + int endoffset; + + endoffset = fdt_node_end_offset_(fdt, nodeoffset); + if (endoffset < 0) + return endoffset; + + fdt_nop_region_(fdt_offset_ptr_w(fdt, nodeoffset, 0), + endoffset - nodeoffset); + return 0; +} diff --git a/scripts/dtc/libfdt/libfdt.h b/scripts/dtc/libfdt/libfdt.h new file mode 100644 index 0000000..81aaf7c --- /dev/null +++ b/scripts/dtc/libfdt/libfdt.h @@ -0,0 +1,2540 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_H +#define LIBFDT_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ + +#include "libfdt_env.h" +#include "fdt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FDT_FIRST_SUPPORTED_VERSION 0x02 +#define FDT_LAST_COMPATIBLE_VERSION 0x10 +#define FDT_LAST_SUPPORTED_VERSION 0x11 + +/* Error codes: informative error codes */ +#define FDT_ERR_NOTFOUND 1 + /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ +#define FDT_ERR_EXISTS 2 + /* FDT_ERR_EXISTS: Attempted to create a node or property which + * already exists */ +#define FDT_ERR_NOSPACE 3 + /* FDT_ERR_NOSPACE: Operation needed to expand the device + * tree, but its buffer did not have sufficient space to + * contain the expanded tree. Use fdt_open_into() to move the + * device tree to a buffer with more space. */ + +/* Error codes: codes for bad parameters */ +#define FDT_ERR_BADOFFSET 4 + /* FDT_ERR_BADOFFSET: Function was passed a structure block + * offset which is out-of-bounds, or which points to an + * unsuitable part of the structure for the operation. */ +#define FDT_ERR_BADPATH 5 + /* FDT_ERR_BADPATH: Function was passed a badly formatted path + * (e.g. missing a leading / for a function which requires an + * absolute path) */ +#define FDT_ERR_BADPHANDLE 6 + /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. + * This can be caused either by an invalid phandle property + * length, or the phandle value was either 0 or -1, which are + * not permitted. */ +#define FDT_ERR_BADSTATE 7 + /* FDT_ERR_BADSTATE: Function was passed an incomplete device + * tree created by the sequential-write functions, which is + * not sufficiently complete for the requested operation. */ + +/* Error codes: codes for bad device tree blobs */ +#define FDT_ERR_TRUNCATED 8 + /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly + * terminated (overflows, goes outside allowed bounds, or + * isn't properly terminated). */ +#define FDT_ERR_BADMAGIC 9 + /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a + * device tree at all - it is missing the flattened device + * tree magic number. */ +#define FDT_ERR_BADVERSION 10 + /* FDT_ERR_BADVERSION: Given device tree has a version which + * can't be handled by the requested operation. For + * read-write functions, this may mean that fdt_open_into() is + * required to convert the tree to the expected version. */ +#define FDT_ERR_BADSTRUCTURE 11 + /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt + * structure block or other serious error (e.g. misnested + * nodes, or subnodes preceding properties). */ +#define FDT_ERR_BADLAYOUT 12 + /* FDT_ERR_BADLAYOUT: For read-write functions, the given + * device tree has it's sub-blocks in an order that the + * function can't handle (memory reserve map, then structure, + * then strings). Use fdt_open_into() to reorganize the tree + * into a form suitable for the read-write operations. */ + +/* "Can't happen" error indicating a bug in libfdt */ +#define FDT_ERR_INTERNAL 13 + /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. + * Should never be returned, if it is, it indicates a bug in + * libfdt itself. */ + +/* Errors in device tree content */ +#define FDT_ERR_BADNCELLS 14 + /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells + * or similar property with a bad format or value */ + +#define FDT_ERR_BADVALUE 15 + /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected + * value. For example: a property expected to contain a string list + * is not NUL-terminated within the length of its value. */ + +#define FDT_ERR_BADOVERLAY 16 + /* FDT_ERR_BADOVERLAY: The device tree overlay, while + * correctly structured, cannot be applied due to some + * unexpected or missing value, property or node. */ + +#define FDT_ERR_NOPHANDLES 17 + /* FDT_ERR_NOPHANDLES: The device tree doesn't have any + * phandle available anymore without causing an overflow */ + +#define FDT_ERR_BADFLAGS 18 + /* FDT_ERR_BADFLAGS: The function was passed a flags field that + * contains invalid flags or an invalid combination of flags. */ + +#define FDT_ERR_ALIGNMENT 19 + /* FDT_ERR_ALIGNMENT: The device tree base address is not 8-byte + * aligned. */ + +#define FDT_ERR_MAX 19 + +/* constants */ +#define FDT_MAX_PHANDLE 0xfffffffe + /* Valid values for phandles range from 1 to 2^32-2. */ + +/**********************************************************************/ +/* Low-level functions (you probably don't need these) */ +/**********************************************************************/ + +/** + * fdt_offset_ptr - safely get a byte range within the device tree blob + * @fdt: Pointer to the device tree blob + * @offset: Offset within the blob to the desired byte range + * @checklen: Required length of the byte range + * + * fdt_offset_ptr() returns a pointer to the byte range of length @checklen at + * the given @offset within the device tree blob, after verifying that the byte + * range fits entirely within the blob and does not overflow. + * + * returns: + * pointer to the byte range, on success + * NULL, if the requested range does not fit within the blob + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); +#endif +static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) +{ + return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); +} + +/** + * fdt_next_tag - get next tag in the device tree + * @fdt: Pointer to the device tree blob + * @offset: Offset within the blob to start searching + * @nextoffset: Pointer to variable to store the offset of the next tag + * + * fdt_next_tag() returns the tag type of the next tag in the device tree + * blob starting from the given @offset. If @nextoffset is non-NULL, it will + * be set to the offset immediately following the tag. + * + * returns: + * the tag type (FDT_BEGIN_NODE, FDT_END_NODE, FDT_PROP, FDT_NOP, FDT_END), + * FDT_END, if offset is out of bounds + */ +uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); + +/* + * External helpers to access words from a device tree blob. They're built + * to work even with unaligned pointers on platforms (such as ARMv5) that don't + * like unaligned loads and stores. + */ +static inline uint16_t fdt16_ld(const fdt16_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint16_t)bp[0] << 8) | bp[1]; +} + +static inline uint32_t fdt32_ld(const fdt32_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint32_t)bp[0] << 24) + | ((uint32_t)bp[1] << 16) + | ((uint32_t)bp[2] << 8) + | bp[3]; +} + +static inline void fdt32_st(void *property, uint32_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 24; + bp[1] = (value >> 16) & 0xff; + bp[2] = (value >> 8) & 0xff; + bp[3] = value & 0xff; +} + +static inline uint64_t fdt64_ld(const fdt64_t *p) +{ + const uint8_t *bp = (const uint8_t *)p; + + return ((uint64_t)bp[0] << 56) + | ((uint64_t)bp[1] << 48) + | ((uint64_t)bp[2] << 40) + | ((uint64_t)bp[3] << 32) + | ((uint64_t)bp[4] << 24) + | ((uint64_t)bp[5] << 16) + | ((uint64_t)bp[6] << 8) + | bp[7]; +} + +static inline void fdt64_st(void *property, uint64_t value) +{ + uint8_t *bp = (uint8_t *)property; + + bp[0] = value >> 56; + bp[1] = (value >> 48) & 0xff; + bp[2] = (value >> 40) & 0xff; + bp[3] = (value >> 32) & 0xff; + bp[4] = (value >> 24) & 0xff; + bp[5] = (value >> 16) & 0xff; + bp[6] = (value >> 8) & 0xff; + bp[7] = value & 0xff; +} + +/**********************************************************************/ +/* Traversal functions */ +/**********************************************************************/ + +int fdt_next_node(const void *fdt, int offset, int *depth); + +/** + * fdt_first_subnode() - get offset of first direct subnode + * @fdt: FDT blob + * @offset: Offset of node to check + * + * Return: offset of first subnode, or -FDT_ERR_NOTFOUND if there is none + */ +int fdt_first_subnode(const void *fdt, int offset); + +/** + * fdt_next_subnode() - get offset of next direct subnode + * @fdt: FDT blob + * @offset: Offset of previous subnode + * + * After first calling fdt_first_subnode(), call this function repeatedly to + * get direct subnodes of a parent node. + * + * Return: offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more + * subnodes + */ +int fdt_next_subnode(const void *fdt, int offset); + +/** + * fdt_for_each_subnode - iterate over all subnodes of a parent + * + * @node: child node (int, lvalue) + * @fdt: FDT blob (const void *) + * @parent: parent node (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_subnode(node, fdt, parent) { + * Use node + * ... + * } + * + * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and @node is used as + * iterator in the loop. The parent variable be constant or even a + * literal. + */ +#define fdt_for_each_subnode(node, fdt, parent) \ + for (node = fdt_first_subnode(fdt, parent); \ + node >= 0; \ + node = fdt_next_subnode(fdt, node)) + +/**********************************************************************/ +/* General functions */ +/**********************************************************************/ +#define fdt_get_header(fdt, field) \ + (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) +#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) +#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) +#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) +#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) +#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) +#define fdt_version(fdt) (fdt_get_header(fdt, version)) +#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) +#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) +#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) +#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) + +#define fdt_set_hdr_(name) \ + static inline void fdt_set_##name(void *fdt, uint32_t val) \ + { \ + struct fdt_header *fdth = (struct fdt_header *)fdt; \ + fdth->name = cpu_to_fdt32(val); \ + } +fdt_set_hdr_(magic) +fdt_set_hdr_(totalsize) +fdt_set_hdr_(off_dt_struct) +fdt_set_hdr_(off_dt_strings) +fdt_set_hdr_(off_mem_rsvmap) +fdt_set_hdr_(version) +fdt_set_hdr_(last_comp_version) +fdt_set_hdr_(boot_cpuid_phys) +fdt_set_hdr_(size_dt_strings) +fdt_set_hdr_(size_dt_struct) +#undef fdt_set_hdr_ + +/** + * fdt_header_size - return the size of the tree's header + * @fdt: pointer to a flattened device tree + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size(const void *fdt); + +/** + * fdt_header_size_ - internal function to get header size from a version number + * @version: device tree version number + * + * Return: size of DTB header in bytes + */ +size_t fdt_header_size_(uint32_t version); + +/** + * fdt_check_header - sanity check a device tree header + * @fdt: pointer to data which might be a flattened device tree + * + * fdt_check_header() checks that the given buffer contains what + * appears to be a flattened device tree, and that the header contains + * valid information (to the extent that can be determined from the + * header alone). + * + * returns: + * 0, if the buffer appears to contain a valid device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings, as above + */ +int fdt_check_header(const void *fdt); + +/** + * fdt_move - move a device tree around in memory + * @fdt: pointer to the device tree to move + * @buf: pointer to memory where the device is to be moved + * @bufsize: size of the memory space at buf + * + * fdt_move() relocates, if possible, the device tree blob located at + * fdt to the buffer at buf of size bufsize. The buffer may overlap + * with the existing device tree blob at fdt. Therefore, + * fdt_move(fdt, fdt, fdt_totalsize(fdt)) + * should always succeed. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_move(const void *fdt, void *buf, int bufsize); + +/**********************************************************************/ +/* Read-only functions */ +/**********************************************************************/ + +/** + * fdt_check_full - check device tree validity + * @fdt: pointer to the device tree blob + * @bufsize: size of the buffer containing the device tree + * + * fdt_check_full() checks that the given buffer contains a valid + * flattened device tree and that the tree structure is internally + * consistent. This is a more thorough check than fdt_check_header(). + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_check_full(const void *fdt, size_t bufsize); + +/** + * fdt_get_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * @lenp: optional pointer to return the string's length + * + * fdt_get_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt, and optionally also + * returns the string's length in *lenp. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); + +/** + * fdt_string - retrieve a string from the strings block of a device tree + * @fdt: pointer to the device tree blob + * @stroffset: offset of the string within the strings block (native endian) + * + * fdt_string() retrieves a pointer to a single string from the + * strings block of the device tree blob at fdt. + * + * returns: + * a pointer to the string, on success + * NULL, if stroffset is out of bounds, or doesn't point to a valid string + */ +const char *fdt_string(const void *fdt, int stroffset); + +/** + * fdt_find_max_phandle - find and return the highest phandle in a tree + * @fdt: pointer to the device tree blob + * @phandle: return location for the highest phandle value found in the tree + * + * fdt_find_max_phandle() finds the highest phandle value in the given device + * tree. The value returned in @phandle is only valid if the function returns + * success. + * + * returns: + * 0 on success or a negative error code on failure + */ +int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_get_max_phandle - retrieves the highest phandle in a tree + * @fdt: pointer to the device tree blob + * + * fdt_get_max_phandle retrieves the highest phandle in the given + * device tree. This will ignore badly formatted phandles, or phandles + * with a value of 0 or -1. + * + * This function is deprecated in favour of fdt_find_max_phandle(). + * + * returns: + * the highest phandle on success + * 0, if no phandle was found in the device tree + * -1, if an error occurred + */ +static inline uint32_t fdt_get_max_phandle(const void *fdt) +{ + uint32_t phandle; + int err; + + err = fdt_find_max_phandle(fdt, &phandle); + if (err < 0) + return (uint32_t)-1; + + return phandle; +} + +/** + * fdt_generate_phandle - return a new, unused phandle for a device tree blob + * @fdt: pointer to the device tree blob + * @phandle: return location for the new phandle + * + * Walks the device tree blob and looks for the highest phandle value. On + * success, the new, unused phandle value (one higher than the previously + * highest phandle value in the device tree blob) will be returned in the + * @phandle parameter. + * + * Return: 0 on success or a negative error-code on failure + */ +int fdt_generate_phandle(const void *fdt, uint32_t *phandle); + +/** + * fdt_num_mem_rsv - retrieve the number of memory reserve map entries + * @fdt: pointer to the device tree blob + * + * Returns the number of entries in the device tree blob's memory + * reservation map. This does not include the terminating 0,0 entry + * or any other (0,0) entries reserved for expansion. + * + * returns: + * the number of entries + */ +int fdt_num_mem_rsv(const void *fdt); + +/** + * fdt_get_mem_rsv - retrieve one memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: index of reserve map entry + * @address: pointer to 64-bit variable to hold the start address + * @size: pointer to 64-bit variable to hold the size of the entry + * + * On success, @address and @size will contain the address and size of + * the n-th reserve map entry from the device tree blob, in + * native-endian format. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); + +/** + * fdt_subnode_offset_namelen - find a subnode based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * @namelen: number of characters of name to consider + * + * Identical to fdt_subnode_offset(), but only examine the first + * namelen characters of name for matching the subnode name. This is + * useful for finding subnodes based on a portion of a larger string, + * such as a full path. + * + * Return: offset of the subnode or -FDT_ERR_NOTFOUND if name not found. + */ +#ifndef SWIG /* Not available in Python */ +int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, + const char *name, int namelen); +#endif +/** + * fdt_subnode_offset - find a subnode of a given node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_subnode_offset() finds a subnode of the node at structure block + * offset parentoffset with the given name. name may include a unit + * address, in which case fdt_subnode_offset() will find the subnode + * with that unit address, or the unit address may be omitted, in + * which case fdt_subnode_offset() will find an arbitrary subnode + * whose name excluding unit address matches the given name. + * + * returns: + * structure block offset of the requested subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); + +/** + * fdt_path_offset_namelen - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * @namelen: number of characters of path to consider + * + * Identical to fdt_path_offset(), but only consider the first namelen + * characters of path as the path name. + * + * Return: offset of the node or negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); +#endif + +/** + * fdt_path_offset - find a tree node by its full path + * @fdt: pointer to the device tree blob + * @path: full path of the node to locate + * + * fdt_path_offset() finds a node of a given path in the device tree. + * Each path component may omit the unit address portion, but the + * results of this are undefined if any such path component is + * ambiguous (that is if there are multiple nodes at the relevant + * level matching the given component, differentiated only by unit + * address). + * + * If the path is not absolute (i.e. does not begin with '/'), the + * first component is treated as an alias. That is, the property by + * that name is looked up in the /aliases node, and the value of that + * property used in place of that first component. + * + * For example, for this small fragment + * + * / { + * aliases { + * i2c2 = &foo; // RHS compiles to "/soc@0/i2c@30a40000/eeprom@52" + * }; + * soc@0 { + * foo: i2c@30a40000 { + * bar: eeprom@52 { + * }; + * }; + * }; + * }; + * + * these would be equivalent: + * + * /soc@0/i2c@30a40000/eeprom@52 + * i2c2/eeprom@52 + * + * returns: + * structure block offset of the node with the requested path (>=0), on + * success + * -FDT_ERR_BADPATH, given path does not begin with '/' and the first + * component is not a valid alias + * -FDT_ERR_NOTFOUND, if the requested node does not exist + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_path_offset(const void *fdt, const char *path); + +/** + * fdt_get_name - retrieve the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the starting node + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_name() retrieves the name (including unit address) of the + * device tree node at structure block offset nodeoffset. If lenp is + * non-NULL, the length of this name is also returned, in the integer + * pointed to by lenp. + * + * returns: + * pointer to the node's name, on success + * If lenp is non-NULL, *lenp contains the length of that name + * (>=0) + * NULL, on error + * if lenp is non-NULL *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); + +/** + * fdt_first_property_offset - find the offset of a node's first property + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * + * fdt_first_property_offset() finds the first property of the node at + * the given structure block offset. + * + * returns: + * structure block offset of the property (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested node has no properties + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_first_property_offset(const void *fdt, int nodeoffset); + +/** + * fdt_next_property_offset - step through a node's properties + * @fdt: pointer to the device tree blob + * @offset: structure block offset of a property + * + * fdt_next_property_offset() finds the property immediately after the + * one at the given structure block offset. This will be a property + * of the same node as the given property. + * + * returns: + * structure block offset of the next property (>=0), on success + * -FDT_ERR_NOTFOUND, if the given property is the last in its node + * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_next_property_offset(const void *fdt, int offset); + +/** + * fdt_for_each_property_offset - iterate over all properties of a node + * + * @property: property offset (int, lvalue) + * @fdt: FDT blob (const void *) + * @node: node offset (int) + * + * This is actually a wrapper around a for loop and would be used like so: + * + * fdt_for_each_property_offset(property, fdt, node) { + * Use property + * ... + * } + * + * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { + * Error handling + * } + * + * Note that this is implemented as a macro and property is used as + * iterator in the loop. The node variable can be constant or even a + * literal. + */ +#define fdt_for_each_property_offset(property, fdt, node) \ + for (property = fdt_first_property_offset(fdt, node); \ + property >= 0; \ + property = fdt_next_property_offset(fdt, property)) + +/** + * fdt_get_property_by_offset - retrieve the property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to retrieve + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property_by_offset() retrieves a pointer to the + * fdt_property structure within the device tree blob at the given + * offset. If lenp is non-NULL, the length of the property value is + * also returned, in the integer pointed to by lenp. + * + * Note that this code only works on device tree versions >= 16. fdt_getprop() + * works on all versions. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property_by_offset(const void *fdt, + int offset, + int *lenp); +static inline struct fdt_property *fdt_get_property_by_offset_w(void *fdt, + int offset, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property_by_offset(fdt, offset, lenp); +} + +/** + * fdt_get_property_namelen - find a property based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_get_property(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the structure representing the property, or NULL + * if not found + */ +#ifndef SWIG /* Not available in Python */ +const struct fdt_property *fdt_get_property_namelen(const void *fdt, + int nodeoffset, + const char *name, + int namelen, int *lenp); +static inline struct fdt_property * +fdt_get_property_namelen_w(void *fdt, int nodeoffset, const char *name, + int namelen, int *lenp) +{ + return (struct fdt_property *)(uintptr_t)fdt_get_property_namelen( + fdt, nodeoffset, name, namelen, lenp); +} +#endif + +/** + * fdt_get_property - find a given property in a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_get_property() retrieves a pointer to the fdt_property + * structure within the device tree blob corresponding to the property + * named 'name' of the node at offset nodeoffset. If lenp is + * non-NULL, the length of the property value is also returned, in the + * integer pointed to by lenp. + * + * returns: + * pointer to the structure representing the property + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, + const char *name, + int *lenp) +{ + return (struct fdt_property *)(uintptr_t) + fdt_get_property(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_getprop_by_offset - retrieve the value of a property at a given offset + * @fdt: pointer to the device tree blob + * @offset: offset of the property to read + * @namep: pointer to a string variable (will be overwritten) or NULL + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop_by_offset() retrieves a pointer to the value of the + * property at structure block offset 'offset' (this will be a pointer + * to within the device blob itself, not a copy of the value). If + * lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by lenp. If namep is non-NULL, + * the property's name will also be returned in the char * pointed to + * by namep (this will be a pointer to within the device tree's string + * block, not a new copy of the name). + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * if namep is non-NULL *namep contains a pointer to the property + * name. + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* This function is not useful in Python */ +const void *fdt_getprop_by_offset(const void *fdt, int offset, + const char **namep, int *lenp); +#endif + +/** + * fdt_getprop_namelen - get property value based on substring + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @namelen: number of characters of name to consider + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * Identical to fdt_getprop(), but only examine the first namelen + * characters of name for matching the property name. + * + * Return: pointer to the property's value or NULL on error + */ +#ifndef SWIG /* Not available in Python */ +const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, + const char *name, int namelen, int *lenp); +static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, + const char *name, int namelen, + int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, + namelen, lenp); +} +#endif + +/** + * fdt_getprop - retrieve the value of a given property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to find + * @name: name of the property to find + * @lenp: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_getprop() retrieves a pointer to the value of the property + * named @name of the node at offset @nodeoffset (this will be a + * pointer to within the device blob itself, not a copy of the value). + * If @lenp is non-NULL, the length of the property value is also + * returned, in the integer pointed to by @lenp. + * + * returns: + * pointer to the property's value + * if lenp is non-NULL, *lenp contains the length of the property + * value (>=0) + * NULL, on error + * if lenp is non-NULL, *lenp contains an error code (<0): + * -FDT_ERR_NOTFOUND, node does not have named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE + * tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +const void *fdt_getprop(const void *fdt, int nodeoffset, + const char *name, int *lenp); +static inline void *fdt_getprop_w(void *fdt, int nodeoffset, + const char *name, int *lenp) +{ + return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); +} + +/** + * fdt_get_phandle - retrieve the phandle of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of the node + * + * fdt_get_phandle() retrieves the phandle of the device tree node at + * structure block offset nodeoffset. + * + * returns: + * the phandle of the node at nodeoffset, on success (!= 0, != -1) + * 0, if the node has no phandle, or another error occurs + */ +uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); + +/** + * fdt_get_alias_namelen - get alias based on substring + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_alias(), but only examine the first @namelen + * characters of @name for matching the alias name. + * + * Return: a pointer to the expansion of the alias named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_alias_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_alias - retrieve the path referenced by a given alias + * @fdt: pointer to the device tree blob + * @name: name of the alias to look up + * + * fdt_get_alias() retrieves the value of a given alias. That is, the + * value of the property named @name in the node /aliases. + * + * returns: + * a pointer to the expansion of the alias named 'name', if it exists + * NULL, if the given alias or the /aliases node does not exist + */ +const char *fdt_get_alias(const void *fdt, const char *name); + +/** + * fdt_get_symbol_namelen - get symbol based on substring + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * @namelen: number of characters of name to consider + * + * Identical to fdt_get_symbol(), but only examine the first @namelen + * characters of @name for matching the symbol name. + * + * Return: a pointer to the expansion of the symbol named @name, if it exists, + * NULL otherwise + */ +#ifndef SWIG /* Not available in Python */ +const char *fdt_get_symbol_namelen(const void *fdt, + const char *name, int namelen); +#endif + +/** + * fdt_get_symbol - retrieve the path referenced by a given symbol + * @fdt: pointer to the device tree blob + * @name: name of the symbol to look up + * + * fdt_get_symbol() retrieves the value of a given symbol. That is, + * the value of the property named @name in the node + * /__symbols__. Such a node exists only for a device tree blob that + * has been compiled with the -@ dtc option. Each property corresponds + * to a label appearing in the device tree source, with the name of + * the property being the label and the value being the full path of + * the node it is attached to. + * + * returns: + * a pointer to the expansion of the symbol named 'name', if it exists + * NULL, if the given symbol or the /__symbols__ node does not exist + */ +const char *fdt_get_symbol(const void *fdt, const char *name); + +/** + * fdt_get_path - determine the full path of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose path to find + * @buf: character buffer to contain the returned path (will be overwritten) + * @buflen: size of the character buffer at buf + * + * fdt_get_path() computes the full path of the node at offset + * nodeoffset, and records that path in the buffer at buf. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * 0, on success + * buf contains the absolute path of the node at + * nodeoffset, as a NUL-terminated string. + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) + * characters and will not fit in the given buffer. + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); + +/** + * fdt_supernode_atdepth_offset - find a specific ancestor of a node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * @supernodedepth: depth of the ancestor to find + * @nodedepth: pointer to an integer variable (will be overwritten) or NULL + * + * fdt_supernode_atdepth_offset() finds an ancestor of the given node + * at a specific depth from the root (where the root itself has depth + * 0, its immediate subnodes depth 1 and so forth). So + * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); + * will always return 0, the offset of the root node. If the node at + * nodeoffset has depth D, then: + * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); + * will return nodeoffset itself. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * structure block offset of the node at node offset's ancestor + * of depth supernodedepth (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of + * nodeoffset + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, + int supernodedepth, int *nodedepth); + +/** + * fdt_node_depth - find the depth of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_node_depth() finds the depth of a given node. The root node + * has depth 0, its immediate subnodes depth 1 and so forth. + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset. + * + * returns: + * depth of the node at nodeoffset (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_depth(const void *fdt, int nodeoffset); + +/** + * fdt_parent_offset - find the parent of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose parent to find + * + * fdt_parent_offset() locates the parent node of a given node (that + * is, it finds the offset of the node which contains the node at + * nodeoffset as a subnode). + * + * NOTE: This function is expensive, as it must scan the device tree + * structure from the start to nodeoffset, *twice*. + * + * returns: + * structure block offset of the parent of the node at nodeoffset + * (>=0), on success + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_parent_offset(const void *fdt, int nodeoffset); + +/** + * fdt_node_offset_by_prop_value - find nodes with a given property value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @propname: property name to check + * @propval: property value to search for + * @proplen: length of the value in propval + * + * fdt_node_offset_by_prop_value() returns the offset of the first + * node after startoffset, which has a property named propname whose + * value is of length proplen and has value equal to propval; or if + * startoffset is -1, the very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, + * propval, proplen); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, + * propval, proplen); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, + const char *propname, + const void *propval, int proplen); + +/** + * fdt_node_offset_by_phandle - find the node with a given phandle + * @fdt: pointer to the device tree blob + * @phandle: phandle value + * + * fdt_node_offset_by_phandle() returns the offset of the node + * which has the given phandle value. If there is more than one node + * in the tree with the given phandle (an invalid tree), results are + * undefined. + * + * returns: + * structure block offset of the located node (>= 0), on success + * -FDT_ERR_NOTFOUND, no node with that phandle exists + * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); + +/** + * fdt_node_check_compatible - check a node's compatible property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @compatible: string to match against + * + * fdt_node_check_compatible() returns 0 if the given node contains a + * @compatible property with the given string as one of its elements, + * it returns non-zero otherwise, or on error. + * + * returns: + * 0, if the node has a 'compatible' property listing the given string + * 1, if the node has a 'compatible' property, but it does not list + * the given string + * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property + * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_check_compatible(const void *fdt, int nodeoffset, + const char *compatible); + +/** + * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value + * @fdt: pointer to the device tree blob + * @startoffset: only find nodes after this offset + * @compatible: 'compatible' string to match against + * + * fdt_node_offset_by_compatible() returns the offset of the first + * node after startoffset, which has a 'compatible' property which + * lists the given compatible string; or if startoffset is -1, the + * very first such node in the tree. + * + * To iterate through all nodes matching the criterion, the following + * idiom can be used: + * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); + * while (offset != -FDT_ERR_NOTFOUND) { + * // other code here + * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); + * } + * + * Note the -1 in the first call to the function, if 0 is used here + * instead, the function will never locate the root node, even if it + * matches the criterion. + * + * returns: + * structure block offset of the located node (>= 0, >startoffset), + * on success + * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the + * tree after startoffset + * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, standard meanings + */ +int fdt_node_offset_by_compatible(const void *fdt, int startoffset, + const char *compatible); + +/** + * fdt_stringlist_contains - check a string list property for a string + * @strlist: Property containing a list of strings to check + * @listlen: Length of property + * @str: String to search for + * + * This is a utility function provided for convenience. The list contains + * one or more strings, each terminated by \0, as is found in a device tree + * "compatible" property. + * + * Return: 1 if the string is found in the list, 0 not found, or invalid list + */ +int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); + +/** + * fdt_stringlist_count - count the number of strings in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * + * Return: + * the number of strings in the given property + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); + +/** + * fdt_stringlist_search - find a string in a string list and return its index + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @string: string to look up in the string list + * + * Note that it is possible for this function to succeed on property values + * that are not NUL-terminated. That's because the function will stop after + * finding the first occurrence of @string. This can for example happen with + * small-valued cell properties, such as #address-cells, when searching for + * the empty string. + * + * return: + * the index of the string in the list of strings + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist or does not contain + * the given string + */ +int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, + const char *string); + +/** + * fdt_stringlist_get() - obtain the string at a given index in a string list + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of a tree node + * @property: name of the property containing the string list + * @index: index of the string to return + * @lenp: return location for the string length or an error code on failure + * + * Note that this will successfully extract strings from properties with + * non-NUL-terminated values. For example on small-valued cell properties + * this function will return the empty string. + * + * If non-NULL, the length of the string (on success) or a negative error-code + * (on failure) will be stored in the integer pointer to by lenp. + * + * Return: + * A pointer to the string at the given index in the string list or NULL on + * failure. On success the length of the string will be stored in the memory + * location pointed to by the lenp parameter, if non-NULL. On failure one of + * the following negative error codes will be returned in the lenp parameter + * (if non-NULL): + * -FDT_ERR_BADVALUE if the property value is not NUL-terminated + * -FDT_ERR_NOTFOUND if the property does not exist + */ +const char *fdt_stringlist_get(const void *fdt, int nodeoffset, + const char *property, int index, + int *lenp); + +/**********************************************************************/ +/* Read-only functions (addressing related) */ +/**********************************************************************/ + +/** + * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells + * + * This is the maximum value for #address-cells, #size-cells and + * similar properties that will be processed by libfdt. IEE1275 + * requires that OF implementations handle values up to 4. + * Implementations may support larger values, but in practice higher + * values aren't used. + */ +#define FDT_MAX_NCELLS 4 + +/** + * fdt_address_cells - retrieve address size for a bus represented in the tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address size for + * + * When the node has a valid #address-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 2, if the node has no #address-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_address_cells(const void *fdt, int nodeoffset); + +/** + * fdt_size_cells - retrieve address range size for a bus represented in the + * tree + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to find the address range size for + * + * When the node has a valid #size-cells property, returns its value. + * + * returns: + * 0 <= n < FDT_MAX_NCELLS, on success + * 1, if the node has no #size-cells property + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #size-cells property + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_size_cells(const void *fdt, int nodeoffset); + + +/**********************************************************************/ +/* Write-in-place functions */ +/**********************************************************************/ + +/** + * fdt_setprop_inplace_namelen_partial - change a property's value, + * but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @idx: index of the property to change in the array + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * Identical to fdt_setprop_inplace(), but modifies the given property + * starting from the given index, and using only the first characters + * of the name. It is useful when you want to manipulate only one value of + * an array and you have a string that doesn't end with \0. + * + * Return: 0 on success, negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, + const char *name, int namelen, + uint32_t idx, const void *val, + int len); +#endif + +/** + * fdt_setprop_inplace - change a property's value, but not its size + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to replace the property value with + * @len: length of the property value + * + * fdt_setprop_inplace() replaces the value of a given property with + * the data in val, of length len. This function cannot change the + * size of a property, and so will only work if len is equal to the + * current length of the property. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if len is not equal to the property's current length + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#ifndef SWIG /* Not available in Python */ +int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, + const void *val, int len); +#endif + +/** + * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to replace the property with + * + * fdt_setprop_inplace_u32() replaces the value of a given property + * with the 32-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 4. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to replace the property with + * + * fdt_setprop_inplace_u64() replaces the value of a given property + * with the 64-bit integer value in val, converting val to big-endian + * if necessary. This function cannot change the size of a property, + * and so will only work if the property already exists and has length + * 8. + * + * This function will alter only the bytes in the blob which contain + * the given property value, and will not alter or move any other part + * of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_inplace_cell - change the value of a single-cell property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node containing the property + * @name: name of the property to change the value of + * @val: new value of the 32-bit cell + * + * This is an alternative name for fdt_setprop_inplace_u32() + * Return: 0 on success, negative libfdt error number otherwise. + */ +static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_nop_property - replace a property with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_nop_property() will replace a given property's representation + * in the blob with FDT_NOP tags, effectively removing it from the + * tree. + * + * This function will alter only the bytes in the blob which contain + * the property, and will not alter or move any other part of the + * tree. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_property(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_nop_node - replace a node (subtree) with nop tags + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_nop_node() will replace a given node's representation in the + * blob, including all its subnodes, if any, with FDT_NOP tags, + * effectively removing it from the tree. + * + * This function will alter only the bytes in the blob which contain + * the node and its properties and subnodes, and will not alter or + * move any other part of the tree. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_nop_node(void *fdt, int nodeoffset); + +/**********************************************************************/ +/* Sequential write functions */ +/**********************************************************************/ + +/* fdt_create_with_flags flags */ +#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 + /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property + * names in the fdt. This can result in faster creation times, but + * a larger fdt. */ + +#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) + +/** + * fdt_create_with_flags - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. + * + * fdt_create_with_flags() begins the process of creating a new fdt with + * the sequential write interface. + * + * fdt creation process must end with fdt_finish() to produce a valid fdt. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + * -FDT_ERR_BADFLAGS, flags is not valid + */ +int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); + +/** + * fdt_create - begin creation of a new fdt + * @buf: pointer to memory allocated where fdt will be created + * @bufsize: size of the memory space at fdt + * + * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt + */ +int fdt_create(void *buf, int bufsize); + +/** + * fdt_resize - move and resize a device tree in sequential write state + * @fdt: Pointer to the device tree to resize + * @buf: Buffer where resized tree should be placed + * @bufsize: Size of the buffer at @buf + * + * fdt_resize() moves the device tree blob from @fdt to @buf and + * resizes it to fit in the new buffer size. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_resize(void *fdt, void *buf, int bufsize); + +/** + * fdt_add_reservemap_entry - add an entry to the memory reserve map + * @fdt: Pointer to the device tree blob + * @addr: Start address of the reserve map entry + * @size: Size of the reserved region + * + * fdt_add_reservemap_entry() adds a memory reserve map entry to the + * device tree blob during the sequential write process. This function + * can only be called after fdt_create() and before fdt_finish_reservemap(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); + +/** + * fdt_finish_reservemap - complete the memory reserve map + * @fdt: Pointer to the device tree blob + * + * fdt_finish_reservemap() completes the memory reserve map section + * of the device tree blob during sequential write. After calling this + * function, no more reserve map entries can be added and the blob + * moves to the structure creation phase. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_finish_reservemap(void *fdt); + +/** + * fdt_begin_node - start creation of a new node + * @fdt: Pointer to the device tree blob + * @name: Name of the node to create + * + * fdt_begin_node() starts the creation of a new node with the given + * @name during sequential write. After calling this function, properties + * can be added with fdt_property() and subnodes can be created with + * additional fdt_begin_node() calls. The node must be completed with + * fdt_end_node(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not in the correct sequential write state + */ +int fdt_begin_node(void *fdt, const char *name); + +/** + * fdt_property - add a property to the current node + * @fdt: Pointer to the device tree blob + * @name: Name of the property to add + * @val: Pointer to the property value + * @len: Length of the property value in bytes + * + * fdt_property() adds a property with the given @name and value to + * the current node during sequential write. This function can only + * be called between fdt_begin_node() and fdt_end_node(). + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if there is insufficient space in the blob + * -FDT_ERR_BADSTATE, if not currently within a node + */ +int fdt_property(void *fdt, const char *name, const void *val, int len); +static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} +static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_property(fdt, name, &tmp, sizeof(tmp)); +} + +#ifndef SWIG /* Not available in Python */ +static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) +{ + return fdt_property_u32(fdt, name, val); +} +#endif + +/** + * fdt_property_placeholder - add a new property and return a ptr to its value + * + * @fdt: pointer to the device tree blob + * @name: name of property to add + * @len: length of property value in bytes + * @valp: returns a pointer to where the value should be placed + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_NOSPACE, standard meanings + */ +int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); + +#define fdt_property_string(fdt, name, str) \ + fdt_property(fdt, name, str, strlen(str)+1) + +/** + * fdt_end_node - complete the current node + * @fdt: Pointer to the device tree blob + * + * fdt_end_node() completes the current node during sequential write. This + * function must be called to close each node started with + * fdt_begin_node(). After calling this function, no more properties or subnodes + * can be added to the node. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if not currently within a node + */ +int fdt_end_node(void *fdt); + +/** + * fdt_finish - complete device tree creation + * @fdt: Pointer to the device tree blob + * + * fdt_finish() completes the device tree creation process started with + * fdt_create(). This function finalizes the device tree blob and makes it ready + * for use. After calling this function, the blob is complete and can be used + * with libfdt read-only and read-write functions, but not with sequential write + * functions. + * + * returns: + * 0, on success + * -FDT_ERR_BADSTATE, if the sequential write process is incomplete + */ +int fdt_finish(void *fdt); + +/**********************************************************************/ +/* Read-write functions */ +/**********************************************************************/ + +/** + * fdt_create_empty_tree - create an empty device tree + * @buf: Buffer where the empty tree should be created + * @bufsize: Size of the buffer at @buf + * + * fdt_create_empty_tree() creates a minimal empty device tree blob + * in the given buffer. The tree contains only a root node with no + * properties or subnodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small for even an empty tree + */ +int fdt_create_empty_tree(void *buf, int bufsize); + +/** + * fdt_open_into - move a device tree into a new buffer and make editable + * @fdt: Pointer to the device tree to move + * @buf: Buffer where the editable tree should be placed + * @bufsize: Size of the buffer at @buf + * + * fdt_open_into() moves and reorganizes the device tree blob from @fdt + * into @buf, converting it to a format suitable for read-write operations. + * The new buffer should allow space for modifications. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, if @bufsize is too small + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_open_into(const void *fdt, void *buf, int bufsize); + +/** + * fdt_pack - pack a device tree blob + * @fdt: Pointer to the device tree blob + * + * fdt_pack() reorganizes the device tree blob to eliminate any free space + * and pack it into the minimum possible size. This is useful after making + * modifications that might have left gaps in the blob. + * + * returns: + * 0, on success + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, standard meanings + */ +int fdt_pack(void *fdt); + +/** + * fdt_add_mem_rsv - add one memory reserve map entry + * @fdt: pointer to the device tree blob + * @address: 64-bit start address of the reserve map entry + * @size: 64-bit size of the reserved region + * + * Adds a reserve map entry to the given blob reserving a region at + * address address of length size. + * + * This function will insert data into the reserve map and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new reservation entry + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); + +/** + * fdt_del_mem_rsv - remove a memory reserve map entry + * @fdt: pointer to the device tree blob + * @n: entry to remove + * + * fdt_del_mem_rsv() removes the n-th memory reserve map entry from + * the blob. + * + * This function will delete data from the reservation table and will + * therefore change the indexes of some entries in the table. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there + * are less than n+1 reserve map entries) + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_mem_rsv(void *fdt, int n); + +/** + * fdt_set_name - change the name of a given node + * @fdt: pointer to the device tree blob + * @nodeoffset: structure block offset of a node + * @name: name to give the node + * + * fdt_set_name() replaces the name (including unit address, if any) + * of the given node with the given string. NOTE: this function can't + * efficiently check if the new name is unique amongst the given + * node's siblings; results are undefined if this function is invoked + * with a name equal to one of the given node's siblings. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob + * to contain the new name + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, standard meanings + */ +int fdt_set_name(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_setprop_namelen - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: length of the name + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop_namelen() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, const void *val, int len); + +/** + * fdt_setprop - create or change a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: pointer to data to set the property value to + * @len: length of the property value + * + * fdt_setprop() sets the value of the named property in the given + * node to the given value and length, creating the property if it + * does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len) +{ + return fdt_setprop_namelen(fdt, nodeoffset, name, strlen(name), val, + len); +} + +/** + * fdt_setprop_placeholder_namelen - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: length of the name + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholder_namelen() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, + int namelen, int len, void **prop_data); + +/** + * fdt_setprop_placeholder - allocate space for a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @len: length of the property value + * @prop_data: return pointer to property data + * + * fdt_setprop_placeholder() allocates the named property in the given node. + * If the property exists it is resized. In either case a pointer to the + * property data is returned. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_placeholder(void *fdt, int nodeoffset, + const char *name, int len, + void **prop_data) +{ + return fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, + strlen(name), len, prop_data); +} + +/** + * fdt_setprop_u32 - set a property to a 32-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * fdt_setprop_u32() sets the value of the named property in the given + * node to the given 32-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_u64 - set a property to a 64-bit integer + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value for the property (native endian) + * + * fdt_setprop_u64() sets the value of the named property in the given + * node to the given 64-bit integer value (converting to big-endian if + * necessary), or creates a new property with that value if it does + * not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, + uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_setprop_cell - set a property to a single cell value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value for the property (native endian) + * + * This is an alternative name for fdt_setprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, + uint32_t val) +{ + return fdt_setprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_setprop_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value for the property + * + * fdt_setprop_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_string(fdt, nodeoffset, name, str) \ + fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_setprop_namelen_string - set a property to a string value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @namelen: number of characters of name to consider + * @str: string value for the property + * + * fdt_setprop_namelen_string() sets the value of the named property in the + * given node to the given string value (using the length of the + * string to determine the new length of the property), or creates a + * new property with that value if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_namelen_string(fdt, nodeoffset, name, namelen, str) \ + fdt_setprop_namelen((fdt), (nodeoffset), (name), (namelen), (str), \ + strlen(str) + 1) + +/** + * fdt_setprop_empty - set a property to an empty value + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * + * fdt_setprop_empty() sets the value of the named property in the + * given node to an empty (zero length) value, or creates a new empty + * property if it does not already exist. + * + * This function may insert or delete data from the blob, and will + * therefore change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_setprop_empty(fdt, nodeoffset, name) \ + fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) + +/** + * fdt_appendprop - append to or create a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to append to + * @val: pointer to data to append to the property value + * @len: length of the data to append to the property value + * + * fdt_appendprop() appends the value to the named property in the + * given node, creating the property if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop(void *fdt, int nodeoffset, const char *name, + const void *val, int len); + +/** + * fdt_appendprop_u32 - append a 32-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u32() appends the given 32-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + fdt32_t tmp = cpu_to_fdt32(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_u64 - append a 64-bit integer value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 64-bit integer value to append to the property (native endian) + * + * fdt_appendprop_u64() appends the given 64-bit integer value + * (converting to big-endian if necessary) to the value of the named + * property in the given node, or creates a new property with that + * value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, + const char *name, uint64_t val) +{ + fdt64_t tmp = cpu_to_fdt64(val); + return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); +} + +/** + * fdt_appendprop_cell - append a single cell value to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @val: 32-bit integer value to append to the property (native endian) + * + * This is an alternative name for fdt_appendprop_u32() + * + * Return: 0 on success, negative libfdt error value otherwise. + */ +static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, + const char *name, uint32_t val) +{ + return fdt_appendprop_u32(fdt, nodeoffset, name, val); +} + +/** + * fdt_appendprop_string - append a string to a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to change + * @name: name of the property to change + * @str: string value to append to the property + * + * fdt_appendprop_string() appends the given string to the value of + * the named property in the given node, or creates a new property + * with that value if it does not already exist. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain the new property value + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_TRUNCATED, standard meanings + */ +#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ + fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) + +/** + * fdt_appendprop_addrrange - append a address range property + * @fdt: pointer to the device tree blob + * @parent: offset of the parent node + * @nodeoffset: offset of the node to add a property at + * @name: name of property + * @addr: start address of a given range + * @size: size of a given range + * + * fdt_appendprop_addrrange() appends an address range value (start + * address and size) to the value of the named property in the given + * node, or creates a new property with that value if it does not + * already exist. + * + * Cell sizes are determined by parent's #address-cells and #size-cells. + * + * This function may insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid + * #address-cells property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size + * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to + * contain a new property + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, + const char *name, uint64_t addr, uint64_t size); + +/** + * fdt_delprop - delete a property + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node whose property to nop + * @name: name of the property to nop + * + * fdt_delprop() will delete the given property. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_NOTFOUND, node does not have the named property + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_delprop(void *fdt, int nodeoffset, const char *name); + +/** + * fdt_add_subnode_namelen - creates a new node based on substring + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to create + * @namelen: number of characters of name to consider + * + * Identical to fdt_add_subnode(), but use only the first @namelen + * characters of @name as the name of the new node. This is useful for + * creating subnodes based on a portion of a larger string, such as a + * full path. + * + * Return: structure block offset of the created subnode (>=0), + * negative libfdt error value otherwise + */ +#ifndef SWIG /* Not available in Python */ +int fdt_add_subnode_namelen(void *fdt, int parentoffset, + const char *name, int namelen); +#endif + +/** + * fdt_add_subnode - creates a new node + * @fdt: pointer to the device tree blob + * @parentoffset: structure block offset of a node + * @name: name of the subnode to locate + * + * fdt_add_subnode() creates a new node as a subnode of the node at + * structure block offset parentoffset, with the given name (which + * should include the unit address, if any). + * + * This function will insert data into the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * structure block offset of the created subnode (>=0), on success + * -FDT_ERR_NOTFOUND, if the requested subnode does not exist + * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE + * tag + * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of + * the given name + * -FDT_ERR_NOSPACE, if there is insufficient free space in the + * blob to contain the new node + * -FDT_ERR_NOSPACE + * -FDT_ERR_BADLAYOUT + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings. + */ +int fdt_add_subnode(void *fdt, int parentoffset, const char *name); + +/** + * fdt_del_node - delete a node (subtree) + * @fdt: pointer to the device tree blob + * @nodeoffset: offset of the node to nop + * + * fdt_del_node() will remove the given node, including all its + * subnodes if any, from the blob. + * + * This function will delete data from the blob, and will therefore + * change the offsets of some existing nodes. + * + * returns: + * 0, on success + * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_del_node(void *fdt, int nodeoffset); + +/** + * fdt_overlay_apply - Applies a DT overlay on a base DT + * @fdt: pointer to the base device tree blob + * @fdto: pointer to the device tree overlay blob + * + * fdt_overlay_apply() will apply the given device tree overlay on the + * given base device tree. + * + * Expect the base device tree to be modified, even if the function + * returns an error. + * + * returns: + * 0, on success + * -FDT_ERR_NOSPACE, there's not enough space in the base device tree + * -FDT_ERR_NOTFOUND, the overlay points to some nonexistent nodes or + * properties in the base DT + * -FDT_ERR_BADPHANDLE, + * -FDT_ERR_BADOVERLAY, + * -FDT_ERR_NOPHANDLES, + * -FDT_ERR_INTERNAL, + * -FDT_ERR_BADLAYOUT, + * -FDT_ERR_BADMAGIC, + * -FDT_ERR_BADOFFSET, + * -FDT_ERR_BADPATH, + * -FDT_ERR_BADVERSION, + * -FDT_ERR_BADSTRUCTURE, + * -FDT_ERR_BADSTATE, + * -FDT_ERR_TRUNCATED, standard meanings + */ +int fdt_overlay_apply(void *fdt, void *fdto); + +/** + * fdt_overlay_target_offset - retrieves the offset of a fragment's target + * @fdt: Base device tree blob + * @fdto: Device tree overlay blob + * @fragment_offset: node offset of the fragment in the overlay + * @pathp: pointer which receives the path of the target (or NULL) + * + * fdt_overlay_target_offset() retrieves the target offset in the base + * device tree of a fragment, no matter how the actual targeting is + * done (through a phandle or a path) + * + * returns: + * the targeted node offset in the base device tree + * Negative error code on error + */ +int fdt_overlay_target_offset(const void *fdt, const void *fdto, + int fragment_offset, char const **pathp); + +/**********************************************************************/ +/* Debugging / informational functions */ +/**********************************************************************/ + +/** + * fdt_strerror - return string description of error code + * @errval: Error code returned by a libfdt function + * + * fdt_strerror() returns a string description of the error code passed + * in @errval. + * + * returns: + * pointer to a string describing the error code + */ +const char *fdt_strerror(int errval); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBFDT_H */ diff --git a/scripts/dtc/libfdt/libfdt_env.h b/scripts/dtc/libfdt/libfdt_env.h new file mode 100644 index 0000000..5580b48 --- /dev/null +++ b/scripts/dtc/libfdt/libfdt_env.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_ENV_H +#define LIBFDT_ENV_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + * Copyright 2012 Kim Phillips, Freescale Semiconductor. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef __CHECKER__ +#define FDT_FORCE __attribute__((force)) +#define FDT_BITWISE __attribute__((bitwise)) +#else +#define FDT_FORCE +#define FDT_BITWISE +#endif + +typedef uint16_t FDT_BITWISE fdt16_t; +typedef uint32_t FDT_BITWISE fdt32_t; +typedef uint64_t FDT_BITWISE fdt64_t; + +#define EXTRACT_BYTE(x, n) ((unsigned long long)((uint8_t *)&x)[n]) +#define CPU_TO_FDT16(x) ((EXTRACT_BYTE(x, 0) << 8) | EXTRACT_BYTE(x, 1)) +#define CPU_TO_FDT32(x) ((EXTRACT_BYTE(x, 0) << 24) | (EXTRACT_BYTE(x, 1) << 16) | \ + (EXTRACT_BYTE(x, 2) << 8) | EXTRACT_BYTE(x, 3)) +#define CPU_TO_FDT64(x) ((EXTRACT_BYTE(x, 0) << 56) | (EXTRACT_BYTE(x, 1) << 48) | \ + (EXTRACT_BYTE(x, 2) << 40) | (EXTRACT_BYTE(x, 3) << 32) | \ + (EXTRACT_BYTE(x, 4) << 24) | (EXTRACT_BYTE(x, 5) << 16) | \ + (EXTRACT_BYTE(x, 6) << 8) | EXTRACT_BYTE(x, 7)) + +static inline uint16_t fdt16_to_cpu(fdt16_t x) +{ + return (FDT_FORCE uint16_t)CPU_TO_FDT16(x); +} +static inline fdt16_t cpu_to_fdt16(uint16_t x) +{ + return (FDT_FORCE fdt16_t)CPU_TO_FDT16(x); +} + +static inline uint32_t fdt32_to_cpu(fdt32_t x) +{ + return (FDT_FORCE uint32_t)CPU_TO_FDT32(x); +} +static inline fdt32_t cpu_to_fdt32(uint32_t x) +{ + return (FDT_FORCE fdt32_t)CPU_TO_FDT32(x); +} + +static inline uint64_t fdt64_to_cpu(fdt64_t x) +{ + return (FDT_FORCE uint64_t)CPU_TO_FDT64(x); +} +static inline fdt64_t cpu_to_fdt64(uint64_t x) +{ + return (FDT_FORCE fdt64_t)CPU_TO_FDT64(x); +} +#undef CPU_TO_FDT64 +#undef CPU_TO_FDT32 +#undef CPU_TO_FDT16 +#undef EXTRACT_BYTE + +#endif /* LIBFDT_ENV_H */ diff --git a/scripts/dtc/libfdt/libfdt_internal.h b/scripts/dtc/libfdt/libfdt_internal.h new file mode 100644 index 0000000..0e103ca --- /dev/null +++ b/scripts/dtc/libfdt/libfdt_internal.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ +#ifndef LIBFDT_INTERNAL_H +#define LIBFDT_INTERNAL_H +/* + * libfdt - Flat Device Tree manipulation + * Copyright (C) 2006 David Gibson, IBM Corporation. + */ +#include + +#define FDT_ALIGN(x, a) (((x) + (a) - 1) & ~((a) - 1)) +#define FDT_TAGALIGN(x) (FDT_ALIGN((x), FDT_TAGSIZE)) + +int32_t fdt_ro_probe_(const void *fdt); +#define FDT_RO_PROBE(fdt) \ + { \ + if (!can_assume(VALID_DTB)) { \ + int32_t totalsize_; \ + if ((totalsize_ = fdt_ro_probe_(fdt)) < 0) \ + return totalsize_; \ + } \ + } + +int fdt_check_node_offset_(const void *fdt, int offset); +int fdt_check_prop_offset_(const void *fdt, int offset); + +const char *fdt_find_string_len_(const char *strtab, int tabsize, const char *s, + int s_len); +static inline const char *fdt_find_string_(const char *strtab, int tabsize, + const char *s) +{ + return fdt_find_string_len_(strtab, tabsize, s, strlen(s)); +} + +int fdt_node_end_offset_(void *fdt, int nodeoffset); + +static inline const void *fdt_offset_ptr_(const void *fdt, int offset) +{ + return (const char *)fdt + fdt_off_dt_struct(fdt) + offset; +} + +static inline void *fdt_offset_ptr_w_(void *fdt, int offset) +{ + return (void *)(uintptr_t)fdt_offset_ptr_(fdt, offset); +} + +static inline const struct fdt_reserve_entry *fdt_mem_rsv_(const void *fdt, int n) +{ + const struct fdt_reserve_entry *rsv_table = + (const struct fdt_reserve_entry *) + ((const char *)fdt + fdt_off_mem_rsvmap(fdt)); + + return rsv_table + n; +} +static inline struct fdt_reserve_entry *fdt_mem_rsv_w_(void *fdt, int n) +{ + return (void *)(uintptr_t)fdt_mem_rsv_(fdt, n); +} + +/* + * Internal helpers to access structural elements of the device tree + * blob (rather than for example reading integers from within property + * values). We assume that we are either given a naturally aligned + * address for the platform or if we are not, we are on a platform + * where unaligned memory reads will be handled in a graceful manner. + * If not the external helpers fdtXX_ld() from libfdt.h can be used + * instead. + */ +static inline uint32_t fdt32_ld_(const fdt32_t *p) +{ + return fdt32_to_cpu(*p); +} + +static inline uint64_t fdt64_ld_(const fdt64_t *p) +{ + return fdt64_to_cpu(*p); +} + +#define FDT_SW_MAGIC (~FDT_MAGIC) + +/**********************************************************************/ +/* Checking controls */ +/**********************************************************************/ + +#ifndef FDT_ASSUME_MASK +#define FDT_ASSUME_MASK 0 +#endif + +/* + * Defines assumptions which can be enabled. Each of these can be enabled + * individually. For maximum safety, don't enable any assumptions! + * + * For minimal code size and no safety, use ASSUME_PERFECT at your own risk. + * You should have another method of validating the device tree, such as a + * signature or hash check before using libfdt. + * + * For situations where security is not a concern it may be safe to enable + * ASSUME_PERFECT. + */ +enum { + /* + * This does essentially no checks. Only the latest device-tree + * version is correctly handled. Inconsistencies or errors in the device + * tree may cause undefined behaviour or crashes. Invalid parameters + * passed to libfdt may do the same. + * + * If an error occurs when modifying the tree it may leave the tree in + * an intermediate (but valid) state. As an example, adding a property + * where there is insufficient space may result in the property name + * being added to the string table even though the property itself is + * not added to the struct section. + * + * Only use this if you have a fully validated device tree with + * the latest supported version and wish to minimise code size. + */ + ASSUME_PERFECT = 0xff, + + /* + * This assumes that the device tree is sane. i.e. header metadata + * and basic hierarchy are correct. + * + * With this assumption enabled, normal device trees produced by libfdt + * and the compiler should be handled safely. Malicious device trees and + * complete garbage may cause libfdt to behave badly or crash. Truncated + * device trees (e.g. those only partially loaded) can also cause + * problems. + * + * Note: Only checks that relate exclusively to the device tree itself + * (not the parameters passed to libfdt) are disabled by this + * assumption. This includes checking headers, tags and the like. + */ + ASSUME_VALID_DTB = 1 << 0, + + /* + * This builds on ASSUME_VALID_DTB and further assumes that libfdt + * functions are called with valid parameters, i.e. not trigger + * FDT_ERR_BADOFFSET or offsets that are out of bounds. It disables any + * extensive checking of parameters and the device tree, making various + * assumptions about correctness. + * + * It doesn't make sense to enable this assumption unless + * ASSUME_VALID_DTB is also enabled. + */ + ASSUME_VALID_INPUT = 1 << 1, + + /* + * This disables checks for device-tree version and removes all code + * which handles older versions. + * + * Only enable this if you know you have a device tree with the latest + * version. + */ + ASSUME_LATEST = 1 << 2, + + /* + * This assumes that it is OK for a failed addition to the device tree, + * due to lack of space or some other problem, to skip any rollback + * steps (such as dropping the property name from the string table). + * This is safe to enable in most circumstances, even though it may + * leave the tree in a sub-optimal state. + */ + ASSUME_NO_ROLLBACK = 1 << 3, + + /* + * This assumes that the device tree components appear in a 'convenient' + * order, i.e. the memory reservation block first, then the structure + * block and finally the string block. + * + * This order is not specified by the device-tree specification, + * but is expected by libfdt. The device-tree compiler always created + * device trees with this order. + * + * This assumption disables a check in fdt_open_into() and removes the + * ability to fix the problem there. This is safe if you know that the + * device tree is correctly ordered. See fdt_blocks_misordered_(). + */ + ASSUME_LIBFDT_ORDER = 1 << 4, + + /* + * This assumes that libfdt itself does not have any internal bugs. It + * drops certain checks that should never be needed unless libfdt has an + * undiscovered bug. + * + * This can generally be considered safe to enable. + */ + ASSUME_LIBFDT_FLAWLESS = 1 << 5, +}; + +/** + * can_assume_() - check if a particular assumption is enabled + * + * @mask: Mask to check (ASSUME_...) + * @return true if that assumption is enabled, else false + */ +static inline bool can_assume_(int mask) +{ + return FDT_ASSUME_MASK & mask; +} + +/** helper macros for checking assumptions */ +#define can_assume(_assume) can_assume_(ASSUME_ ## _assume) + +#endif /* LIBFDT_INTERNAL_H */ diff --git a/scripts/extract-asm-offsets.sed b/scripts/extract-asm-offsets.sed new file mode 100644 index 0000000..c97ec2c --- /dev/null +++ b/scripts/extract-asm-offsets.sed @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Extract `->SYM val COMMENT` markers emitted by inline asm in +# asm-offsets.c into proper `#define SYM val /* COMMENT */` lines. +# 1:1 with Linux scripts/Makefile.lib `sed-offsets`. +# +# Input : assembly source produced by `gcc -S asm-offsets.c` — +# inline asm emits lines like +# .ascii "->KERNEL_MAP_VIRT_ADDR 0 offsetof(...)" +# Output : header fragment for include/generated/asm-offsets.h +# +# Step 1 unwraps the `.ascii "..."` directive so the actual payload +# (starting with `->`) is exposed. Steps 2-4 then rewrite into a +# proper #define. +s:^[[:space:]]*\.ascii[[:space:]]*"\(.*\)".*:\1: +/^->/{s:->#\(.*\):/* \1 */:; + s:^->\([^ ]*\) [\$#]*\([^ ]*\) \(.*\):#define \1 \2 /* \3 */:; + s:->::; + p;} diff --git a/scripts/gen_initramfs.sh b/scripts/gen_initramfs.sh new file mode 100755 index 0000000..7eba2fd --- /dev/null +++ b/scripts/gen_initramfs.sh @@ -0,0 +1,250 @@ +#!/bin/sh +# Copyright (C) Martin Schlemmer +# Copyright (C) 2006 Sam Ravnborg +# +# Released under the terms of the GNU GPL +# +# Generate a cpio packed initramfs. It uses gen_init_cpio to generate +# the cpio archive. +# This script assumes that gen_init_cpio is located in usr/ directory + +# error out on errors +set -e + +usage() { +cat << EOF +Usage: +$0 [-o ] [-l ] [-u ] [-g ] {-d | } ... + -o Create initramfs file named by using gen_init_cpio + -l Create dependency list named + -u User ID to map to user ID 0 (root). + is only meaningful if is a + directory. "squash" forces all files to uid 0. + -g Group ID to map to group ID 0 (root). + is only meaningful if is a + directory. "squash" forces all files to gid 0. + -d Use date for all file mtime values + File list or directory for cpio archive. + If is a .cpio file it will be used + as direct input to initramfs. + +All options except -o and -l may be repeated and are interpreted +sequentially and immediately. -u and -g states are preserved across + options so an explicit "-u 0 -g 0" is required +to reset the root/group mapping. +EOF +} + +# awk style field access +# $1 - field number; rest is argument string +field() { + shift $1 ; echo $1 +} + +filetype() { + local argv1="$1" + + # symlink test must come before file test + if [ -L "${argv1}" ]; then + echo "slink" + elif [ -f "${argv1}" ]; then + echo "file" + elif [ -d "${argv1}" ]; then + echo "dir" + elif [ -b "${argv1}" -o -c "${argv1}" ]; then + echo "nod" + elif [ -p "${argv1}" ]; then + echo "pipe" + elif [ -S "${argv1}" ]; then + echo "sock" + else + echo "invalid" + fi + return 0 +} + +print_mtime() { + local my_mtime="0" + + if [ -e "$1" ]; then + my_mtime=$(find "$1" -printf "%T@\n" | sort -r | head -n 1) + fi + + echo "# Last modified: ${my_mtime}" >> $cpio_list + echo "" >> $cpio_list +} + +list_parse() { + if [ -z "$dep_list" -o -L "$1" ]; then + return + fi + echo "$1" | sed 's/:/\\:/g; s/$/ \\/' >> $dep_list +} + +# for each file print a line in following format +# +# for links, devices etc the format differs. See gen_init_cpio for details +parse() { + local location="$1" + local name="/${location#${srcdir}}" + # change '//' into '/' + name=$(echo "$name" | sed -e 's://*:/:g') + local mode="$2" + local uid="$3" + local gid="$4" + local ftype=$(filetype "${location}") + # remap uid/gid to 0 if necessary + [ "$root_uid" = "squash" ] && uid=0 || [ "$uid" -eq "$root_uid" ] && uid=0 + [ "$root_gid" = "squash" ] && gid=0 || [ "$gid" -eq "$root_gid" ] && gid=0 + local str="${mode} ${uid} ${gid}" + + [ "${ftype}" = "invalid" ] && return 0 + [ "${location}" = "${srcdir}" ] && return 0 + + case "${ftype}" in + "file") + str="${ftype} ${name} ${location} ${str}" + ;; + "nod") + local dev="`LC_ALL=C ls -l "${location}"`" + local maj=`field 5 ${dev}` + local min=`field 6 ${dev}` + maj=${maj%,} + + [ -b "${location}" ] && dev="b" || dev="c" + + str="${ftype} ${name} ${str} ${dev} ${maj} ${min}" + ;; + "slink") + local target=`readlink "${location}"` + str="${ftype} ${name} ${target} ${str}" + ;; + *) + str="${ftype} ${name} ${str}" + ;; + esac + + echo "${str}" >> $cpio_list + + return 0 +} + +unknown_option() { + printf "ERROR: unknown option \"$arg\"\n" >&2 + printf "If the filename validly begins with '-', " >&2 + printf "then it must be prefixed\n" >&2 + printf "by './' so that it won't be interpreted as an option." >&2 + printf "\n" >&2 + usage >&2 + exit 1 +} + +header() { + printf "\n#####################\n# $1\n" >> $cpio_list +} + +# process one directory (incl sub-directories) +dir_filelist() { + header "$1" + + srcdir=$(echo "$1" | sed -e 's://*:/:g') + dirlist=$(find "${srcdir}" -printf "%p %m %U %G\n" | LC_ALL=C sort) + + # If $dirlist is only one line, then the directory is empty + if [ "$(echo "${dirlist}" | wc -l)" -gt 1 ]; then + print_mtime "$1" + + echo "${dirlist}" | \ + while read x; do + list_parse $x + parse $x + done + fi +} + +input_file() { + source="$1" + if [ -f "$1" ]; then + # If a regular file is specified, assume it is in + # gen_init_cpio format + header "$1" + print_mtime "$1" >> $cpio_list + cat "$1" >> $cpio_list + if [ -n "$dep_list" ]; then + echo "$1 \\" >> $dep_list + cat "$1" | while read type dir file perm ; do + if [ "$type" = "file" ]; then + echo "$file \\" >> $dep_list + fi + done + fi + elif [ -d "$1" ]; then + # If a directory is specified then add all files in it to fs + dir_filelist "$1" + else + echo " ${prog}: Cannot open '$1'" >&2 + exit 1 + fi +} + +prog=$0 +root_uid=0 +root_gid=0 +dep_list= +timestamp= +cpio_list=$(mktemp ${TMPDIR:-/tmp}/cpiolist.XXXXXX) +# gen_init_cpio writes to stdout by default +output="" + +trap "rm -f $cpio_list" EXIT + +while [ $# -gt 0 ]; do + arg="$1" + shift + case "$arg" in + "-l") # files included in initramfs - used by kbuild + dep_list="$1" + echo "deps_initramfs := \\" > $dep_list + shift + ;; + "-o") # generate cpio image named $1 + output="-o $1" + shift + ;; + "-u") # map $1 to uid=0 (root) + root_uid="$1" + [ "$root_uid" = "-1" ] && root_uid=$(id -u || echo 0) + shift + ;; + "-g") # map $1 to gid=0 (root) + root_gid="$1" + [ "$root_gid" = "-1" ] && root_gid=$(id -g || echo 0) + shift + ;; + "-d") # date for file mtimes + timestamp="$(date -d"$1" +%s || :)" + if test -n "$timestamp"; then + timestamp="-t $timestamp" + fi + shift + ;; + "-h") + usage + exit 0 + ;; + *) + case "$arg" in + "-"*) + unknown_option + ;; + *) # input file/dir - process it + input_file "$arg" + ;; + esac + ;; + esac +done + +# If output_file is set we will generate cpio archive +# we are careful to delete tmp files +usr/gen_init_cpio $output $timestamp $cpio_list diff --git a/src/boot/entry.S b/src/boot/entry.S deleted file mode 100644 index 8910ddc..0000000 --- a/src/boot/entry.S +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.section .text.entry -.global _start - -_start: - mv tp, a0 - - la t0, stack_base - li t1, 4096 - addi t2, a0, 1 - mul t1, t1, t2 - add sp, t0, t1 - - bnez a0, .L_jump_main - - la t0, sbss - la t1, ebss -.L_clear_bss: - bge t0, t1, .L_save_dtb - sd zero, 0(t0) - addi t0, t0, 8 - j .L_clear_bss - -.L_save_dtb: - la t0, g_dtb_addr - sd a1, 0(t0) - -.L_jump_main: - call kernel_main - -.L_park: - wfi - j .L_park - -.section .bss -.align 4 -.global stack_top -stack_base: - .space 4096 * 8 -stack_top: - -.align 8 -.global g_dtb_addr -g_dtb_addr: - .space 8 \ No newline at end of file diff --git a/src/drivers/plic.cc b/src/drivers/plic.cc deleted file mode 100644 index a975c44..0000000 --- a/src/drivers/plic.cc +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ -#include "common/types.h" -#include "kernel/riscv.h" -#include "drivers/plic.h" -#include "drivers/uart.h" -#include "kernel/fdt.h" - -// Helper Macros — all relative to PLIC base from FDT -#define PLIC_BASE (g_devices.plic.base_addr) -#define PLIC_PRIORITY (PLIC_BASE + 0x0) -#define PLIC_PENDING (PLIC_BASE + 0x1000) - -#define PLIC_SENABLE_ADDR(hart) (PLIC_BASE + 0x2080 + (hart) * 0x100) -#define PLIC_STHRESHOLD_ADDR(hart) (PLIC_BASE + 0x201000 + (hart) * 0x2000) -#define PLIC_SCLAIM_ADDR(hart) (PLIC_BASE + 0x201004 + (hart) * 0x2000) - -// Helper for Volatile Access -static inline volatile uint32 *REG32(uint64 addr) -{ - return reinterpret_cast(addr); -} - -namespace PLIC -{ - void init() - { - // Set priority for UART IRQ - if (g_devices.uart.irq > 0) - *REG32(PLIC_PRIORITY + g_devices.uart.irq * 4) = 1; - - // Set priority for all VirtIO IRQs - for (int i = 0; i < g_devices.virtio_count; i++) - { - if (g_devices.virtio[i].irq > 0) - *REG32(PLIC_PRIORITY + g_devices.virtio[i].irq * 4) = 1; - } - } - - void inithart() - { - int hart = static_cast(r_tp()); - - // Build enable bitmask from discovered devices - uint32 enable = 0; - - if (g_devices.uart.irq > 0 && g_devices.uart.irq < 32) - enable |= (1 << g_devices.uart.irq); - - for (int i = 0; i < g_devices.virtio_count; i++) - { - if (g_devices.virtio[i].irq > 0 && g_devices.virtio[i].irq < 32) - enable |= (1 << g_devices.virtio[i].irq); - } - - *REG32(PLIC_SENABLE_ADDR(hart)) = enable; - - // Set Priority Threshold to 0 (Allow all) - *REG32(PLIC_STHRESHOLD_ADDR(hart)) = 0; - - Drivers::uart_puts("[PLIC] Hart Init Done.\n"); - } - - int claim() - { - int hart = static_cast(r_tp()); - return *REG32(PLIC_SCLAIM_ADDR(hart)); - } - - void complete(int irq) - { - int hart = static_cast(r_tp()); - *REG32(PLIC_SCLAIM_ADDR(hart)) = static_cast(irq); - } -} \ No newline at end of file diff --git a/src/drivers/uart.cc b/src/drivers/uart.cc deleted file mode 100644 index 397a402..0000000 --- a/src/drivers/uart.cc +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "drivers/uart.h" -#include "kernel/riscv.h" -#include "kernel/spinlock.h" -#include "kernel/proc.h" -#include "kernel/mm.h" -#include "lib/string.h" - -namespace Drivers -{ - struct - { - Spinlock lock; - char buf[128]; // Line buffer - uint32 r; // Read pointer (position taken by the user) - uint32 w; // Write pointer (position confirmed by Enter) - uint32 e; // Editing cursor (current typing position) - } cons; - - // Circular buffer structure - struct - { - Spinlock lock; - char buf[UART_RX_BUF_SIZE]; - uint32 r; // read index - uint32 w; // write index - } uart_rx; - - Spinlock uart_tx_lock; - - void uart_init() - { - uart_rx.lock.init("uart_rx"); - cons.lock.init("console"); - uart_tx_lock.init("uart_tx"); - uart_rx.r = 0; - uart_rx.w = 0; - - // Disable interrupts and configure - *(volatile uint8 *)(uart_base() + IER) = 0x00; - - // Enter baud rate setting mode (LCR_DLAB) = 0x80; - *(volatile uint8 *)(uart_base() + LCR) = 0x80; - *(volatile uint8 *)(uart_base() + 0) = 0x03; // DLL: 38.4K baud - *(volatile uint8 *)(uart_base() + 1) = 0x00; // DLM - - // Exit baud rate setting, set 8-bit word length, no parity - *(volatile uint8 *)(uart_base() + LCR) = 0x03; - - // Reset and enable FIFO - *(volatile uint8 *)(uart_base() + FCR) = 0x07; - - // Enable Receive Data Available Interrupt - *(volatile uint8 *)(uart_base() + IER) = 0x01; - } - - void uart_putc(char c) - { - uart_tx_lock.acquire(); - while ((*(volatile uint8 *)(uart_base() + LSR) & LSR_TX_IDLE) == 0) - ; - *(volatile uint8 *)(uart_base() + THR) = c; - uart_tx_lock.release(); - } - - void uart_puts(const char *s) - { - while (*s) - uart_putc(*s++); - } - - void uart_put_int(int val) - { - char buf[16]; - int i = 0; - if (val == 0) - { - uart_puts("0"); - return; - } - if (val < 0) - { - uart_puts("-"); - val = -val; - } - while (val > 0) - { - buf[i++] = (val % 10) + '0'; - val /= 10; - } - while (i > 0) - { - uart_putc(buf[--i]); - } - } - - void print_hex(uint64 val) - { - char hex[] = "0123456789ABCDEF"; - uart_puts("0x"); - for (int i = 60; i >= 0; i -= 4) - { - uart_putc(hex[(val >> i) & 0xF]); - } - } - - void uart_intr() - { - uart_rx.lock.acquire(); - - while (1) - { - if ((*(volatile uint8 *)(uart_base() + LSR) & LSR_RX_READY) == 0) - break; - - char c = *(volatile uint8 *)(uart_base() + RHR); - - uart_rx.buf[uart_rx.w % UART_RX_BUF_SIZE] = c; - uart_rx.w++; - - ProcManager::wakeup(&uart_rx); - } - - uart_rx.lock.release(); - } - - // Modern console read: Line buffering + Editing Keys + Echo - int console_read(uint64 target, int len) - { - struct Proc *p = myproc(); - int target_idx = 0; - - cons.lock.acquire(); - - while (target_idx < len) - { - while (cons.r < cons.w) - { - char c = cons.buf[cons.r++ % sizeof(cons.buf)]; - cons.lock.release(); - - if (VM::copyout(p->pagetable, target + target_idx, &c, 1) < 0) - return -1; - - target_idx++; - if (c == '\n') - return target_idx; - if (target_idx == len) - return target_idx; - - cons.lock.acquire(); - } - - uart_rx.lock.acquire(); - while (uart_rx.r == uart_rx.w) - { - if (p->xstate) - { - uart_rx.lock.release(); - cons.lock.release(); - return -1; - } - cons.lock.release(); - ProcManager::sleep(&uart_rx, &uart_rx.lock); - cons.lock.acquire(); - } - - char c = uart_rx.buf[uart_rx.r++ % UART_RX_BUF_SIZE]; - uart_rx.lock.release(); - - if (c == 127 || c == '\b') - { - if (cons.e != cons.w) - { - cons.e--; - uart_putc('\b'); - uart_putc(' '); - uart_putc('\b'); - } - } - else - { - if (c == '\r') - c = '\n'; - - uart_putc(c); - - cons.buf[cons.e++ % sizeof(cons.buf)] = c; - - if (c == '\n' || cons.e == cons.w + sizeof(cons.buf)) - { - cons.w = cons.e; - ProcManager::wakeup(&cons); - } - } - } - - cons.lock.release(); - return target_idx; - } -} \ No newline at end of file diff --git a/src/drivers/virtio_dist.cc b/src/drivers/virtio_dist.cc deleted file mode 100644 index 8292874..0000000 --- a/src/drivers/virtio_dist.cc +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "drivers/virtio.h" -#include "kernel/fdt.h" -#include "kernel/riscv.h" -#include "kernel/pmm.h" -#include "kernel/spinlock.h" -#include "lib/string.h" -#include "drivers/uart.h" -#include "kernel/proc.h" -#include "kernel/buf.h" - -static uint64 virtio_base = 0x10001000; - -// VirtIO Block Device Config Structure -struct virtio_blk_config -{ - uint64 capacity; - uint32 size_max; - uint32 seg_max; - struct virtio_blk_geometry - { - uint16 cylinders; - uint8 heads; - uint8 sectors; - } geometry; - uint32 blk_size; - uint8 physical_block_exp; - uint8 alignment_offset; - uint16 min_io_size; - uint32 opt_io_size; -} __attribute__((packed)); - -static inline volatile uint32 *REG(uint32 offset) -{ - return reinterpret_cast(virtio_base + offset); -} - -namespace VirtIO -{ - struct Disk - { - struct VRingDesc *desc; - struct VRingAvail *avail; - struct VRingUsed *used; - - int free_head; - int num_free; - int free_chain[NUM_DESCS]; - - uint16 used_idx; - - struct TraceInfo - { - int busy; - int status; - } info[NUM_DESCS]; - - struct BlockRequest reqs[NUM_DESCS]; - uint8 req_status[NUM_DESCS]; - - Spinlock lock; - } disk; - - void init() - { - disk.lock.init("virtio_disk"); - - // 1. Probe VirtIO devices discovered by FDT - bool found = false; - for (int i = 0; i < g_devices.virtio_count; i++) - { - uint64 addr = g_devices.virtio[i].base_addr; - if (addr == 0) - continue; - - volatile uint32 *magic = (volatile uint32 *)(addr + VIRTIO_MMIO_MAGIC_VALUE); - volatile uint32 *dev_id = (volatile uint32 *)(addr + VIRTIO_MMIO_DEVICE_ID); - - if (*magic == 0x74726976 && *dev_id == 2) // ID 2 = Block Device - { - virtio_base = addr; - found = true; - Drivers::uart_puts(ANSI_GREEN "[VirtIO] Found Block Device @ "); - Drivers::print_hex(addr); - Drivers::uart_puts(ANSI_RESET "\n"); - break; - } - } - - if (!found) - { - Drivers::uart_puts(ANSI_RED "[VirtIO] Fatal - No Block Device found.\n" ANSI_RESET); - return; - } - - // 2. Check Device Version (We want Modern v2) - if (*REG(VIRTIO_MMIO_VERSION) != 2) - { - Drivers::uart_puts(ANSI_YELLOW "[VirtIO] Warning: Legacy device detected (expected v2).\n" ANSI_RESET); - } - - // 3. Reset Device - *REG(VIRTIO_MMIO_STATUS) = 0; - - // 4. Set ACKNOWLEDGE & DRIVER status - uint32 status = *REG(VIRTIO_MMIO_STATUS); - status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER; - *REG(VIRTIO_MMIO_STATUS) = status; - - // 5. Feature Negotiation - *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 0; - uint32 device_features_lo = *REG(VIRTIO_MMIO_DEVICE_FEATURES); - (void)device_features_lo; - - *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 1; - uint32 device_features_hi = *REG(VIRTIO_MMIO_DEVICE_FEATURES); - (void)device_features_hi; - - *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 0; - *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 0; - - *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 1; - // VIRTIO_F_VERSION_1 is usually bit 32. - // If not defined in header, we hardcode logic here: (1ULL << 0) for high bits - *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 1; - - status |= VIRTIO_CONFIG_S_FEATURES_OK; - *REG(VIRTIO_MMIO_STATUS) = status; - - if (!(*REG(VIRTIO_MMIO_STATUS) & VIRTIO_CONFIG_S_FEATURES_OK)) - { - Drivers::uart_puts("VirtIO: Feature negotiation failed.\n"); - return; - } - - // 6. Queue Configuration - *REG(VIRTIO_MMIO_QUEUE_SEL) = 0; - if (*REG(VIRTIO_MMIO_QUEUE_READY)) - { - Drivers::uart_puts("VirtIO: Queue 0 not ready.\n"); - } - - uint32 max_queue = *REG(VIRTIO_MMIO_QUEUE_NUM_MAX); - if (max_queue < NUM_DESCS) - { - Drivers::uart_puts("VirtIO: Queue size too small.\n"); - return; - } - - *REG(VIRTIO_MMIO_QUEUE_NUM) = NUM_DESCS; - - // Allocate Memory for VirtQueues - void *ptr = PMM::alloc_pages(1); // 4KB is enough for small rings - memset(ptr, 0, PGSIZE); - - uint64 base_addr = (uint64)ptr; - disk.desc = reinterpret_cast(base_addr); - disk.avail = reinterpret_cast(base_addr + NUM_DESCS * 16); - disk.used = reinterpret_cast(base_addr + PGSIZE / 2); - - *REG(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint32)base_addr; - *REG(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint32)(base_addr >> 32); - - uint64 avail_addr = (uint64)disk.avail; - *REG(VIRTIO_MMIO_QUEUE_DRIVER_LOW) = (uint32)avail_addr; - *REG(VIRTIO_MMIO_QUEUE_DRIVER_HIGH) = (uint32)(avail_addr >> 32); - - uint64 used_addr = (uint64)disk.used; - *REG(VIRTIO_MMIO_QUEUE_DEVICE_LOW) = (uint32)used_addr; - *REG(VIRTIO_MMIO_QUEUE_DEVICE_HIGH) = (uint32)(used_addr >> 32); - - *REG(VIRTIO_MMIO_QUEUE_READY) = 1; - - // 7. Initialize Internal Structures - disk.free_head = 0; - disk.num_free = NUM_DESCS; - disk.used_idx = 0; - for (int i = 0; i < NUM_DESCS; i++) - { - disk.free_chain[i] = i + 1; - disk.info[i].busy = 0; - } - - // 8. Finalize Setup - status |= VIRTIO_CONFIG_S_DRIVER_OK; - *REG(VIRTIO_MMIO_STATUS) = status; - - // === [Feature] Read Capacity === - volatile struct virtio_blk_config *config = (volatile struct virtio_blk_config *)(virtio_base + VIRTIO_MMIO_CONFIG); - uint64 capacity_sectors = config->capacity; - uint64 size_mb = (capacity_sectors * 512) / (1024 * 1024); - - Drivers::uart_puts(ANSI_GREEN "[VirtIO] Initialized. Capacity: "); - Drivers::uart_put_int(size_mb); - Drivers::uart_puts(" MB\n" ANSI_RESET); - } - - static int alloc_desc() - { - if (disk.num_free == 0) - return -1; - int id = disk.free_head; - disk.free_head = disk.free_chain[id]; - disk.num_free--; - return id; - } - - static void free_desc(int id) - { - disk.free_chain[id] = disk.free_head; - disk.free_head = id; - disk.num_free++; - ProcManager::wakeup(&disk.free_head); - } - - static void free_chain_soft(int head_idx) - { - int curr = head_idx; - while (1) - { - int flag = disk.desc[curr].flags; - int next = disk.desc[curr].next; - free_desc(curr); - if (flag & VRING_DESC_F_NEXT) - curr = next; - else - break; - } - } - - void rw(int write, uint64 blockno, char *buf) - { - uint64 sector = blockno * (1024 / 512); // Assume 1KB blocks - - disk.lock.acquire(); - - // Wait for enough descriptors - while (disk.num_free < 3) - { - ProcManager::sleep(&disk.free_head, &disk.lock); - } - - int idx[3]; - idx[0] = alloc_desc(); - idx[1] = alloc_desc(); - idx[2] = alloc_desc(); - - // 1. Request Header - struct BlockRequest *req = &disk.reqs[idx[0]]; - req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN; - req->reserved = 0; - req->sector = sector; - - disk.desc[idx[0]].addr = (uint64)req; - disk.desc[idx[0]].len = sizeof(struct BlockRequest); - disk.desc[idx[0]].flags = VRING_DESC_F_NEXT; - disk.desc[idx[0]].next = idx[1]; - - // 2. Data Buffer - disk.desc[idx[1]].addr = (uint64)buf; - disk.desc[idx[1]].len = 1024; // Fixed 1KB for this test - disk.desc[idx[1]].flags = VRING_DESC_F_NEXT; - if (!write) - disk.desc[idx[1]].flags |= VRING_DESC_F_WRITE; - disk.desc[idx[1]].next = idx[2]; - - // 3. Status Byte - disk.desc[idx[2]].addr = (uint64)&disk.req_status[idx[0]]; - disk.desc[idx[2]].len = 1; - disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; - disk.desc[idx[2]].next = 0; - - disk.info[idx[0]].busy = 1; - - // Submit to Avail Ring - disk.avail->ring[disk.avail->idx % NUM_DESCS] = idx[0]; - __sync_synchronize(); - disk.avail->idx++; - __sync_synchronize(); - - // Notify Device - *REG(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; - - // Wait for Completion - while (disk.info[idx[0]].busy) - { - ProcManager::sleep(&disk.info[idx[0]], &disk.lock); - } - - free_chain_soft(idx[0]); - disk.lock.release(); - } - - void intr() - { - disk.lock.acquire(); - - uint32 status = *REG(VIRTIO_MMIO_INTERRUPT_STATUS); - *REG(VIRTIO_MMIO_INTERRUPT_ACK) = status & 0x3; - - volatile struct VRingUsed *used_ring = (volatile struct VRingUsed *)disk.used; - - while (disk.used_idx != used_ring->idx) - { - int id = used_ring->ring[disk.used_idx % NUM_DESCS].id; - - if (disk.info[id].busy) - { - disk.info[id].busy = 0; - ProcManager::wakeup(&disk.info[id]); - } - disk.used_idx++; - } - - disk.lock.release(); - } - - void test_rw() - { - Drivers::uart_puts(ANSI_BLUE "[VirtIO] Disk Test Start...\n" ANSI_RESET); - - char *buf = (char *)PMM::alloc_page(); - if (!buf) - return; - - const char *msg = "Lume OS Disk Driver Test Pattern"; - strcpy(buf, msg); - - rw(1, 0, buf); // Write - memset(buf, 0, PGSIZE); - rw(0, 0, buf); // Read - - if (strcmp(buf, msg) == 0) - { - Drivers::uart_puts(ANSI_GREEN "[VirtIO] Test PASSED (R/W Verified).\n" ANSI_RESET); - } - else - { - Drivers::uart_puts(ANSI_RED "[VirtIO] Test FAILED.\n" ANSI_RESET); - } - - PMM::free_page(buf); - } - - void test_bio() - { - Drivers::uart_puts(ANSI_BLUE "[Bio] Starting Buffer Cache Test...\n" ANSI_RESET); - - struct Buf *b = BufferCache::bread(0, 100); - - Drivers::uart_puts("[Bio] Bread block 100 (1st time).\n"); - - b->data[0] = 'L'; - b->data[1] = 'u'; - b->data[2] = 'm'; - b->data[3] = 'e'; - b->data[4] = '\0'; - - BufferCache::bwrite(b); - Drivers::uart_puts("[Bio] Bwrite block 100.\n"); - - BufferCache::brelse(b); - Drivers::uart_puts("[Bio] Brelease block 100.\n"); - - struct Buf *b2 = BufferCache::bread(0, 100); - Drivers::uart_puts("[Bio] Bread block 100 (2nd time).\n"); - - if (b2->data[0] == 'L' && b2->data[1] == 'u' && b2->data[3] == 'e') - { - Drivers::uart_puts(ANSI_GREEN "[Bio] Data verification PASSED.\n" ANSI_RESET); - } - else - { - Drivers::uart_puts(ANSI_RED "[Bio] Data verification FAILED.\n" ANSI_RESET); - } - - BufferCache::brelse(b2); - } -} \ No newline at end of file diff --git a/src/fs/fat32.cc b/src/fs/fat32.cc deleted file mode 100644 index e4089d4..0000000 --- a/src/fs/fat32.cc +++ /dev/null @@ -1,806 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "fs/fat32.h" -#include "lib/string.h" -#include "drivers/uart.h" -#include "kernel/mm.h" -#include "kernel/proc.h" - -FAT32FileSystem fat32_fs; - -// Helper function -static void generate_short_name(char *dst, const char *src) -{ - memset(dst, ' ', 11); - - int i = 0; - for (; *src && *src != '.' && i < 8; src++) - { - char c = *src; - if (c >= 'a' && c <= 'z') - c -= 32; - dst[i++] = c; - } - - while (*src && *src != '.') - src++; - if (*src == '.') - src++; - - i = 8; - for (; *src && i < 11; src++) - { - char c = *src; - if (c >= 'a' && c <= 'z') - c -= 32; - dst[i++] = c; - } -} - -// FAT32 FileSystem Implementation - -void FAT32FileSystem::init() -{ - struct Buf *b = BufferCache::bread(0, 0); - uint8 *data = (uint8 *)b->data; - struct FAT32_BPB *bpb = (struct FAT32_BPB *)data; - - uint32 partition_lba = 0; - - bool is_valid_bpb = (bpb->bytesPerSector == 512) && (bpb->rootClus != 0); - - if (!is_valid_bpb) - { - Drivers::uart_puts("[FAT32] Sector 0 is not a valid BPB. Checking MBR...\n"); - - if (data[510] != 0x55 || data[511] != 0xAA) - { - Drivers::uart_puts("[FAT32] Panic: Invalid Disk Signature!\n"); - while (1) - ; - } - - uint32 lba = *(uint32 *)(data + 446 + 8); - - if (lba > 0) - { - Drivers::uart_puts("[FAT32] MBR Partition 1 found @ Sector "); - Drivers::uart_put_int(lba); - Drivers::uart_puts("\n"); - partition_lba = lba; - } - else - { - Drivers::uart_puts("[FAT32] Panic: No partition found.\n"); - while (1) - ; - } - - BufferCache::brelse(b); - - b = BufferCache::bread(0, partition_lba / 2); - bpb = (struct FAT32_BPB *)b->data; - } - - if (bpb->bytesPerSector != 512) - { - Drivers::uart_puts("[FAT32] Panic: Invalid FAT32 BPB in partition.\n"); - while (1) - ; - } - - uint32 fat_sz = bpb->FATSz32; - this->fat_sz = fat_sz; - uint32 reserved_sec = bpb->reservedSecCnt; - uint8 num_fats = bpb->numFATs; - - sectors_per_cluster = bpb->secPerClus; - bytes_per_cluster = sectors_per_cluster * 512; - root_cluster = bpb->rootClus; - - fat_start_sector = partition_lba + reserved_sec; - data_start_sector = partition_lba + reserved_sec + (fat_sz * num_fats); - - Drivers::uart_puts(ANSI_GREEN); - Drivers::uart_puts("[FAT32] Init Success: RootClus="); - Drivers::uart_put_int(root_cluster); - Drivers::uart_puts(", DataStart="); - Drivers::uart_put_int(data_start_sector); - Drivers::uart_puts(ANSI_RESET); - Drivers::uart_puts("\n"); - - BufferCache::brelse(b); -} - -Inode *FAT32FileSystem::root() -{ - FAT32Inode *ip = new FAT32Inode(); - - ip->fs = this; - ip->dev = 0; - ip->inum = root_cluster; - ip->ref_cnt = 1; - ip->valid = 1; - ip->type = T_DIR; - ip->size = 0; - ip->nlink = 1; - - ip->first_clus = root_cluster; - ip->parent_clus = 0; - - return ip; -} - -uint32 FAT32FileSystem::cluster_to_sector(uint32 cluster) -{ - return data_start_sector + (cluster - 2) * sectors_per_cluster; -} - -uint32 FAT32FileSystem::next_cluster(uint32 cluster) -{ - uint32 fat_offset = cluster * 4; - uint32 fat_sector = fat_start_sector + (fat_offset / 512); - uint32 ent_offset = fat_offset % 512; - - // block = sector / 2 - struct Buf *b = BufferCache::bread(0, fat_sector / 2); - - uint32 block_offset = (fat_sector % 2) * 512 + ent_offset; - uint32 next = *(uint32 *)(b->data + block_offset); - - BufferCache::brelse(b); - - return next & 0x0FFFFFFF; -} - -// FAT32 Inode Implementation - -static bool match_name(const char *disk_name, const char *target) -{ - char temp[13]; - int i, j; - - for (i = 0; i < 8 && disk_name[i] != ' '; i++) - temp[i] = disk_name[i]; - if (disk_name[8] != ' ') - { - temp[i++] = '.'; - for (j = 0; j < 3 && disk_name[8 + j] != ' '; j++) - temp[i++] = disk_name[8 + j]; - } - temp[i] = 0; - return strcasecmp(temp, target) == 0; -} - -int FAT32Inode::read(char *dst, uint32 off, uint32 n, int isUser) -{ - if (first_clus == 0) - return 0; - - // [FIX] 只有非目录文件才检查 size 边界 - // 目录文件 size 可能为 0,应允许读取直到簇链结束 - if (type != T_DIR) - { - if (off >= size) - { - return 0; - } - if (off + n > size) - { - n = size - off; - } - } - - uint32 tot = 0; - uint32 clus = first_clus; - uint32 clus_sz = fs->bytes_per_cluster; - - uint32 skip = off / clus_sz; - uint32 current_off = off % clus_sz; - - while (skip > 0) - { - clus = fs->next_cluster(clus); - if (clus >= 0x0FFFFFF8) - return 0; - skip--; - } - - while (tot < n) - { - uint32 sec_in_clus = current_off / 512; - - if (sec_in_clus >= fs->sectors_per_cluster) - { - clus = fs->next_cluster(clus); - if (clus >= 0x0FFFFFF8) - break; - current_off = 0; - continue; - } - - uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; - uint32 off_in_sec = current_off % 512; - - uint32 m = 512 - off_in_sec; - if (n - tot < m) - m = n - tot; - - struct Buf *b = BufferCache::bread(dev, sec / 2); - uint32 block_offset = (sec % 2) * 512 + off_in_sec; - - if (isUser) - { - if (VM::copyout(myproc()->pagetable, (uint64)dst, (char *)(b->data + block_offset), m) < 0) - { - BufferCache::brelse(b); - return -1; - } - } - else - { - memmove(dst, b->data + block_offset, m); - } - BufferCache::brelse(b); - - tot += m; - dst += m; - current_off += m; - } - return tot; -} - -Inode *FAT32Inode::lookup(const char *name) -{ - if (type != T_DIR) - return nullptr; - - uint32 clus = first_clus; - struct FAT32_DirEnt de; - - while (clus < 0x0FFFFFF8) - { - uint32 sec = fs->cluster_to_sector(clus); - - for (uint32 i = 0; i < fs->sectors_per_cluster; i++) - { - uint32 current_sec = sec + i; - struct Buf *b = BufferCache::bread(dev, current_sec / 2); - uint32 block_base = (current_sec % 2) * 512; - - for (int off = 0; off < 512; off += 32) - { - memmove(&de, b->data + block_base + off, 32); - - if (de.name[0] == 0x00) - { - BufferCache::brelse(b); - return nullptr; - } - if (de.name[0] == 0xE5) - continue; - if (de.attr & ATTR_LONG_NAME) - continue; - - if (match_name(de.name, name)) - { - FAT32Inode *ip = new FAT32Inode(); - ip->fs = fs; - ip->dev = dev; - ip->type = (de.attr & ATTR_DIRECTORY) ? T_DIR : T_FILE; - ip->size = de.fileSize; - ip->first_clus = (de.fstClusHI << 16) | de.fstClusLO; - ip->inum = ip->first_clus; - ip->nlink = 1; - ip->ref_cnt = 1; - ip->valid = 1; - ip->ent_sec = current_sec; // Log Sector - ip->ent_off = off; // Log Setoff - - BufferCache::brelse(b); - return ip; - } - } - BufferCache::brelse(b); - } - clus = fs->next_cluster(clus); - } - return nullptr; -} - -// Placeholder -uint32 FAT32FileSystem::alloc_cluster() -{ - uint32 fat_entries_per_sector = 512 / 4; - for (uint32 i = 0; i < fat_sz; i++) - { - uint32 current_fat_sec = fat_start_sector + i; - struct Buf *b = BufferCache::bread(0, current_fat_sec / 2); - uint32 *table = (uint32 *)(b->data + (current_fat_sec % 2) * 512); - for (uint32 j = 0; j < fat_entries_per_sector; j++) - { - if (i == 0 && j < 2) - continue; - - if ((table[j] & 0x0FFFFFFF) == 0) - { - // Find the free cluster and mark it as EOF (0x0FFFFFFF) - table[j] = 0x0FFFFFFF; - BufferCache::bwrite(b); // Write back to disk - BufferCache::brelse(b); - - uint32 cluster_idx = i * fat_entries_per_sector + j; - - // Clear the data content of the new cluster - uint32 data_sec = cluster_to_sector(cluster_idx); - for (uint32 k = 0; k < sectors_per_cluster; k++) - { - struct Buf *db = BufferCache::bread(0, (data_sec + k) / 2); - memset(db->data + ((data_sec + k) % 2) * 512, 0, 512); - BufferCache::bwrite(db); - BufferCache::brelse(db); - } - - return cluster_idx; - } - } - BufferCache::brelse(b); - } - Drivers::uart_puts("[FAT32] Out of space!\n"); - return 0; -} - -void FAT32FileSystem::free_cluster_chain(uint32 cluster) -{ - while (cluster >= 2 && cluster < 0x0FFFFFF8) - { - uint32 next = next_cluster(cluster); - - // Calculate the current cluster's position in the FAT table - uint32 fat_offset = cluster * 4; - uint32 fat_sector = fat_start_sector + (fat_offset / 512); - uint32 ent_offset = fat_offset % 512; - - // Read FAT sector - struct Buf *b = BufferCache::bread(0, fat_sector / 2); - - // Marked as idle (0x00000000) - uint32 *entry = (uint32 *)(b->data + (fat_sector % 2) * 512 + ent_offset); - *entry = 0; - - BufferCache::bwrite(b); - BufferCache::brelse(b); - - cluster = next; - } -} - -void FAT32Inode::update() -{ - struct Buf *b = BufferCache::bread(dev, ent_sec / 2); - uint32 offset = (ent_sec % 2) * 512 + ent_off; - - struct FAT32_DirEnt *de = (struct FAT32_DirEnt *)(b->data + offset); - - // Update file size and starting cluster - de->fileSize = size; - de->fstClusHI = (uint16)((first_clus >> 16) & 0xFFFF); - de->fstClusLO = (uint16)(first_clus & 0xFFFF); - - BufferCache::bwrite(b); - BufferCache::brelse(b); -} - -int FAT32Inode::write(const char *src, uint32 off, uint32 n, int isUser) -{ - if (off > size) - { - return -1; - } - - uint32 tot = 0; - uint32 clus = first_clus; - uint32 clus_sz = fs->bytes_per_cluster; - uint32 prev_clus = 0; - - if (clus == 0) - { - clus = fs->alloc_cluster(); - if (clus == 0) - return -1; - first_clus = clus; - // Update the metadata immediately to prevent data loss in case of a power outage. - update(); - } - - uint32 skip = off / clus_sz; - uint32 current_off = off % clus_sz; - - while (skip > 0) - { - prev_clus = clus; - clus = fs->next_cluster(clus); - if (clus >= 0x0FFFFFF8) - { - return -1; - } - skip--; - } - while (tot < n) - { - if (current_off == clus_sz) - { - uint32 next = fs->next_cluster(clus); - if (next >= 0x0FFFFFF8) - { - uint32 new_clus = fs->alloc_cluster(); - if (new_clus == 0) - break; - - // Point the current cluster to the new cluster (update the FAT table) - uint32 fat_offset = clus * 4; - uint32 fat_sec = fs->fat_start_sector + (fat_offset / 512); - struct Buf *fat_b = BufferCache::bread(dev, fat_sec / 2); - uint32 *entry = (uint32 *)(fat_b->data + (fat_sec % 2) * 512 + (fat_offset % 512)); - *entry = new_clus; // Link - BufferCache::bwrite(fat_b); - BufferCache::brelse(fat_b); - - clus = new_clus; - } - else - { - clus = next; - } - current_off = 0; - } - - // Calculate the physical sector to write - uint32 sec_in_clus = current_off / 512; - uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; - uint32 off_in_sec = current_off % 512; - - // Calculate how many bytes can be written this time - uint32 m = 512 - off_in_sec; - if (n - tot < m) - m = n - tot; - - struct Buf *b = BufferCache::bread(dev, sec / 2); - uint32 block_offset = (sec % 2) * 512 + off_in_sec; - - if (isUser) - { - if (VM::copyin(myproc()->pagetable, (char *)(b->data + block_offset), (uint64)src, m) < 0) - { - BufferCache::brelse(b); - return -1; - } - } - else - { - memmove(b->data + block_offset, src, m); - } - - BufferCache::bwrite(b); - BufferCache::brelse(b); - - tot += m; - src += m; - current_off += m; - } - if (off + n > size) - { - size = off + n; - update(); - } - - return tot; -} - -Inode *FAT32Inode::create(const char *name, short type, short major, short minor) -{ - Inode *exist = lookup(name); - if (exist) - { - return exist; - } - - struct FAT32_DirEnt de; - memset(&de, 0, sizeof(de)); - generate_short_name(de.name, name); - de.attr = (type == T_DIR) ? ATTR_DIRECTORY : ATTR_ARCHIVE; - - uint32 new_dir_clus = 0; - if (type == T_DIR) - { - new_dir_clus = fs->alloc_cluster(); - if (new_dir_clus == 0) - return nullptr; - - de.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); - de.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); - } - else - { - de.fileSize = 0; - } - - uint32 clus = first_clus; - bool written = false; - - while (true) - { - uint32 sec = fs->cluster_to_sector(clus); - - for (uint32 i = 0; i < fs->sectors_per_cluster; i++) - { - struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); - uint8 *data = (uint8 *)b->data + ((sec + i) % 2) * 512; - - for (int off = 0; off < 512; off += 32) - { - if (data[off] == 0x00 || data[off] == 0xE5) - { - memmove(data + off, &de, 32); - BufferCache::bwrite(b); - BufferCache::brelse(b); - written = true; - break; - } - } - - if (written) - break; - BufferCache::brelse(b); - } - - if (written) - break; - - uint32 next = fs->next_cluster(clus); - if (next >= 0x0FFFFFF8) - { - uint32 new_parent_clus = fs->alloc_cluster(); - if (new_parent_clus == 0) - return nullptr; - - uint32 fat_off = clus * 4; - uint32 fat_sec = fs->fat_start_sector + (fat_off / 512); - struct Buf *fb = BufferCache::bread(dev, fat_sec / 2); - *(uint32 *)(fb->data + (fat_sec % 2) * 512 + (fat_off % 512)) = new_parent_clus; - BufferCache::bwrite(fb); - BufferCache::brelse(fb); - - uint32 s = fs->cluster_to_sector(new_parent_clus); - for (uint32 k = 0; k < fs->sectors_per_cluster; k++) - { - struct Buf *zb = BufferCache::bread(dev, (s + k) / 2); - uint8 *zdata = (uint8 *)zb->data + ((s + k) % 2) * 512; - memset(zdata, 0, 512); - - if (k == 0) - { - memmove(zdata, &de, 32); - } - - BufferCache::bwrite(zb); - BufferCache::brelse(zb); - } - - written = true; - break; - } - clus = next; - } - - if (written && type == T_DIR) - { - struct FAT32_DirEnt dot; - memset(&dot, 0, 32); - memset(dot.name, ' ', 11); - dot.name[0] = '.'; - dot.attr = ATTR_DIRECTORY; - dot.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); - dot.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); - - struct FAT32_DirEnt dotdot; - memset(&dotdot, 0, 32); - memset(dotdot.name, ' ', 11); - dotdot.name[0] = '.'; - dotdot.name[1] = '.'; - dotdot.attr = ATTR_DIRECTORY; - - uint32 parent_clus_num = this->first_clus; - if (parent_clus_num == fs->root_cluster) - { - parent_clus_num = 0; - } - dotdot.fstClusHI = (uint16)((parent_clus_num >> 16) & 0xFFFF); - dotdot.fstClusLO = (uint16)(parent_clus_num & 0xFFFF); - - uint32 s = fs->cluster_to_sector(new_dir_clus); - - struct Buf *zb = BufferCache::bread(dev, s / 2); - uint8 *zdata = (uint8 *)zb->data + (s % 2) * 512; - - memmove(zdata, &dot, 32); // Offset 0 - memmove(zdata + 32, &dotdot, 32); // Offset 32 - - BufferCache::bwrite(zb); - BufferCache::brelse(zb); - } - - if (written) - { - return lookup(name); - } - - return nullptr; -} - -void FAT32Inode::truncate() -{ - if (first_clus == 0) - { - if (size != 0) - { - size = 0; - update(); - } - return; - } - - fs->free_cluster_chain(first_clus); - - size = 0; - first_clus = 0; - - update(); -} - -int FAT32Inode::unlink(const char *name) -{ - if (type != T_DIR) - return -1; - if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - return -1; - - uint32_t clus = first_clus; - bool found = false; - - // [FIX] Deadlock prevention: Do not hold buffer while looking up inode - uint32_t target_block = 0; - uint32_t target_offset = 0; - struct FAT32_DirEnt de; - - while (true) - { - uint32_t sec = fs->cluster_to_sector(clus); - for (uint32 i = 0; i < fs->sectors_per_cluster; ++i) - { - struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); - uint8_t *data = (uint8_t *)b->data + ((sec + i) % 2) * 512; - - for (int off = 0; off < 512; off += 32) - { - memmove(&de, data + off, 32); - if (de.name[0] == 0x00) // end of dir - { - BufferCache::brelse(b); - return -1; // Not found - } - if (de.name[0] == 0xE5) - continue; // have been unlink - if (de.attr & ATTR_LONG_NAME) - continue; - - if (match_name(de.name, name)) - { - target_block = (sec + i) / 2; - target_offset = ((sec + i) % 2) * 512 + off; - found = true; - break; - } - } - - BufferCache::brelse(b); // Always release buffer - if (found) - break; - } - - if (found) - break; - - clus = fs->next_cluster(clus); - if (clus >= 0x0FFFFFF8) - return -1; - } - - if (found) - { - Inode *target_ip = lookup(name); - if (target_ip) - { - VFS::ilock(target_ip); - if (target_ip->type == T_DIR) - { - VFS::iunlockput(target_ip); - return -1; - } - - target_ip->nlink--; - target_ip->update(); - VFS::iunlockput(target_ip); - } - - // Re-read buffer to mark deletion - struct Buf *target_buf = BufferCache::bread(dev, target_block); - uint8_t *ptr = (uint8_t *)target_buf->data + target_offset; - *ptr = 0xE5; - - BufferCache::bwrite(target_buf); - BufferCache::brelse(target_buf); - return 0; - } - - return -1; -} - -// Test Function -void fat32_test() -{ - static bool inited = false; - if (!inited) - { - fat32_fs.init(); - inited = true; - } - - Drivers::uart_puts("\n[FAT32] Starting Test via Syscall...\n"); - - Inode *root = fat32_fs.root(); - if (root) - { - Drivers::uart_puts("[FAT32] Root Inode retrieved. Inum: "); - Drivers::uart_put_int(root->inum); - Drivers::uart_puts("\n"); - - Drivers::uart_puts("[FAT32] Looking for TEST.TXT...\n"); - Inode *node = root->lookup("test.txt"); - - if (node) - { - Drivers::uart_puts("[FAT32] Found TEST.TXT! Size: "); - Drivers::uart_put_int(node->size); - Drivers::uart_puts("\n"); - - char buf[128]; - int n = node->read(buf, 0, 127, 0); - if (n > 0) - { - buf[n] = 0; - Drivers::uart_puts("[FAT32] Content:\n>> "); - Drivers::uart_puts(buf); - Drivers::uart_puts("\n"); - } - else - { - Drivers::uart_puts("[FAT32] Read returned 0.\n"); - } - VFS::iput(node); - } - else - { - Drivers::uart_puts("[FAT32] TEST.TXT not found.\n"); - } - VFS::iput(root); - } - else - { - Drivers::uart_puts("[FAT32] Failed to get root inode.\n"); - } - - Drivers::uart_puts("[FAT32] Test Finished.\n"); -} \ No newline at end of file diff --git a/src/fs/file.cc b/src/fs/file.cc deleted file mode 100644 index d814332..0000000 --- a/src/fs/file.cc +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "fs/file.h" -#include "kernel/spinlock.h" -#include "kernel/mm.h" -#include "drivers/uart.h" -#include "kernel/proc.h" -#include "kernel/slab.h" - -#define NFILE 100 -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 - -extern void *operator new(unsigned long, void *p); - -int Pipe::create_pair(file** f0, file** f1) -{ - void *mem = Slab::kmalloc(sizeof(Pipe)); - if (!mem) - return -1; - Pipe *pi = new (mem) Pipe(); - - *f0 = FileTable::alloc(); - *f1 = FileTable::alloc(); - - if (*f0 == nullptr || *f1 == nullptr) - { - if (*f0) - FileTable::close(*f0); - if (*f1) - FileTable::close(*f1); - pi->~Pipe(); - Slab::kfree(pi); - return -1; - } - - (*f0)->type = FD_PIPE; - (*f0)->readable = 1; - (*f0)->writable = 0; - (*f0)->pipe = pi; - - (*f1)->type = FD_PIPE; - (*f1)->readable = 0; - (*f1)->writable = 1; - (*f1)->pipe = pi; - - return 0; -} - -void Pipe::close(bool is_writer) -{ - lock.acquire(); - if (is_writer) - { - write_open = false; - ProcManager::wakeup(&nread); // Wake up the process that is currently reading - } - else - { - read_open = false; - ProcManager::wakeup(&nwrite); // Wake up the process that is writing - } - - bool empty = !read_open && !write_open; - lock.release(); - - if (empty) - { - this->~Pipe(); - Slab::kfree(this); - } -} - -int Pipe::write(uint64_t addr, int n) -{ - int i = 0; - struct Proc *p = myproc(); - - lock.acquire(); - while (i < n) - { - if (!read_open || p->xstate) // The read end is closed or the process is abnormal - { - lock.release(); - return -1; - } - - if (nwrite == nread + SIZE) // buffer is full - { - ProcManager::wakeup(&nread); - ProcManager::sleep(&nwrite, &lock); - } - else - { - char ch; - if (VM::copyin(p->pagetable, &ch, addr + i, 1) < 0) - break; - data[nwrite++ % SIZE] = ch; - i++; - } - } - ProcManager::wakeup(&nread); - lock.release(); - return i; -} - -int Pipe::read(uint64_t addr, int n) -{ - int i = 0; - struct Proc *p = myproc(); - - lock.acquire(); - while (nread == nwrite && write_open) // The buffer is empty and the write end is not closed - { - if (p->xstate) - { - lock.release(); - return -1; - } - ProcManager::sleep(&nread, &lock); - } - - for (i = 0; i < n; ++i) - { - if (nread == nwrite) - break; - char ch = data[nread++ % SIZE]; - if (VM::copyout(p->pagetable, addr + i, &ch, 1) < 0) - break; - } - ProcManager::wakeup(&nwrite); - lock.release(); - return i; -} - -namespace FileTable -{ - struct - { - Spinlock lock; - struct file files[NFILE]; - } ftable; - - void init() - { - ftable.lock.init("file_table"); - } - - struct file *alloc() - { - struct file *f; - ftable.lock.acquire(); - for (f = ftable.files; f < ftable.files + NFILE; f++) - { - if (f->ref == 0) - { - f->ref = 1; - ftable.lock.release(); - return f; - } - } - ftable.lock.release(); - return nullptr; - } - - struct file *dup(struct file *f) - { - ftable.lock.acquire(); - if (f->ref < 1) - { - Drivers::uart_puts("panic: file dup\n"); - while (1) - ; - } - f->ref++; - ftable.lock.release(); - return f; - } - - void close(struct file *f) - { - struct file ff; - - ftable.lock.acquire(); - if(f->ref < 1) - { - Drivers::uart_puts("panic: file close\n"); - while (1) - ; - } - f->ref--; - if (f->ref > 0) - { - ftable.lock.release(); - return; - } - - ff = *f; - f->ref = 0; - f->type = FD_NONE; - ftable.lock.release(); - if (ff.type == FD_INODE) - { - // Release the corresponding Inode - VFS::iput(ff.ip); - } - else if (ff.type == FD_PIPE) - { - if (ff.pipe) - ff.pipe->close(ff.writable); - } - } - - int read(struct file *f, uint64 addr, int n) - { - if (f->readable == 0) - return -1; - if (f->type == FD_INODE) - { - VFS::ilock(f->ip); - - if (f->ip->type == T_DEVICE) - { - if (f->ip->major == 1) - { - VFS::iunlock(f->ip); - return Drivers::console_read(addr, n); - } - } - - int r = f->ip->read((char*)addr, f->off, n, 1); - if(r > 0) - { - f->off += r; - } - VFS::iunlock(f->ip); - return r; - } - else if (f->type == FD_PIPE) - { - if (f->pipe) - return f->pipe->read(addr, n); - } - return -1; - } - - int write(struct file *f, uint64 addr, int n) - { - if (f->writable == 0) - return -1; - if (f->type == FD_INODE) - { - VFS::ilock(f->ip); - - if (f->ip->type == T_DEVICE) - { - if (f->ip->major == 1) // Console - { - VFS::iunlock(f->ip); - struct Proc *proc = myproc(); - constexpr int MAX_WRITE_BUF = 128; - char buf[MAX_WRITE_BUF]; - int i = 0; - while (i < n) - { - int len = n - i; - if (len > MAX_WRITE_BUF) - len = MAX_WRITE_BUF; - - if (VM::copyin(proc->pagetable, buf, addr + i, static_cast(len)) < 0) - { - return -1; - } - - for (int j = 0; j < len; j++) - { - Drivers::uart_putc(buf[j]); - } - i += len; - } - return n; - } - } - - int r = f->ip->write((char *)addr, f->off, n, 1); - if (r > 0) - { - f->off += r; - } - VFS::iunlock(f->ip); - return r; - } - else if (f->type == FD_PIPE) - { - if (f->pipe) - return f->pipe->write(addr, n); - } - - return -1; - } - - int stat(struct file *f, uint64 addr) - { - if (f->type == FD_INODE) - { - struct kstat st; - VFS::ilock(f->ip); - f->ip->getattr(&st); - VFS::iunlock(f->ip); - - struct Proc *p = myproc(); - if (VM::copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) - return -1; - return 0; - } - return -1; - } - - int lseek(struct file *f, int offset, int whence) - { - if (f->type != FD_INODE) - return -1; - - VFS::ilock(f->ip); - - int new_off = f->off; - int size = f->ip->size; - - switch (whence) - { - case SEEK_SET: - new_off = offset; - break; - case SEEK_CUR: - new_off += offset; - break; - case SEEK_END: - new_off = size + offset; - break; - default: - VFS::iunlock(f->ip); - return -1; - } - - if (new_off < 0) - { - VFS::iunlock(f->ip); - return -1; - } - - f->off = new_off; - VFS::iunlock(f->ip); - return new_off; - } -} // namespace FileTable diff --git a/src/fs/fs.cc b/src/fs/fs.cc deleted file mode 100644 index fc8f555..0000000 --- a/src/fs/fs.cc +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "fs/fs.h" -#include "kernel/riscv.h" -#include "drivers/uart.h" -#include "kernel/spinlock.h" -#include "kernel/proc.h" -// #include "fs/fat32.h" // Decoupled -#include "lib/string.h" - -static Inode *global_root_inode = nullptr; - -static const char *skipelem(const char *path, char *name) -{ - const char *s; - int len; - - while (*path == '/') - path++; - if (*path == 0) - return nullptr; - - s = path; - while (*path != '/' && *path != 0) - path++; - - len = path - s; - if (len >= 128) - len = 127; - - memmove(name, s, len); - name[len] = 0; - - while (*path == '/') - path++; - return path; -} - -void Inode::getattr(struct kstat *st) -{ - st->dev = dev; - st->ino = inum; - st->type = type; - st->nlink = nlink; - st->size = size; - - st->uid = 0; - st->gid = 0; - st->mode = 0; -} - -namespace VFS -{ - // File System Registry - static FileSystem *filesystems[8]; - static int fs_count = 0; - - void register_fs(FileSystem *fs) - { - if (fs_count < 8) - { - filesystems[fs_count++] = fs; - } - } - - void init() - { - Drivers::uart_puts("[VFS] Inode subsystem initialized.\n"); - } - - void iput(Inode *ip) - { - if (ip == nullptr) - return; - - ip->lock.acquire(); - if(ip->ref_cnt < 1) - { - Drivers::uart_puts("panic: iput ref\n"); - while (1) - ; - } - ip->ref_cnt--; - int ref = ip->ref_cnt; - ip->lock.release(); - - if (ref == 0) - { - delete ip; - } - } - - Inode* idup(Inode *ip) - { - if (ip == nullptr) - return nullptr; - - ip->lock.acquire(); - ip->ref_cnt++; - ip->lock.release(); - return ip; - } - - void ilock(Inode *ip) - { - if (ip == 0 || ip->ref_cnt < 1) - { - Drivers::uart_puts("panic: ilock\n"); - while (1) - ; - } - - ip->lock.acquire(); - - } - - void iunlock(Inode *ip) - { - if (ip == 0 || !ip->lock.holding() || ip->ref_cnt < 1) - { - Drivers::uart_puts("panic: iunlock\n"); - while (1) - ; - } - ip->lock.release(); - } - - void iunlockput(Inode *ip) - { - if (!ip) - return; - iunlock(ip); - iput(ip); - } - - void mount_root(Inode *root) - { - if (global_root_inode) - { - Drivers::uart_puts("VFS: Root already mounted!\n"); - return; - } - global_root_inode = root; - Drivers::uart_puts("[VFS] Root filesystem mounted.\n"); - } - - Inode *get_root() - { - if (global_root_inode == nullptr) - { - // Try to mount the first registered FS - if (fs_count > 0) - { - Drivers::uart_puts("[VFS] Lazy init: Mounting root FS...\n"); - filesystems[0]->init(); - mount_root(filesystems[0]->root()); - } - else - { - Drivers::uart_puts("[VFS] No filesystem registered!\n"); - } - } - if (!global_root_inode) - return nullptr; - return idup(global_root_inode); - } - - Inode *namei(const char *path) - { - char name[128]; - Inode *ip, *next; - struct Proc *p = myproc(); - - // 1. 确定起点 - if (*path == '/') - { - ip = get_root(); - } - else - { - if (p->cwd == nullptr) - { - ip = get_root(); - } - else - { - ip = idup(p->cwd); - } - } - - if (ip == nullptr) - return nullptr; - - while ((path = skipelem(path, name)) != nullptr) - { - if (strcmp(name, ".") == 0) - { - continue; - } - - if (strcmp(name, "..") == 0) - { - Inode *root = get_root(); - bool is_root = (ip->inum == root->inum); - iput(root); - - if (is_root) - { - continue; - } - } - - ilock(ip); - - if (ip->type != T_DIR) - { - iunlockput(ip); - return nullptr; - } - - next = ip->lookup(name); - iunlockput(ip); - - ip = next; - if (ip == nullptr) - return nullptr; - } - - return ip; - } - - Inode *nameiparent(const char* path, char* name) - { - Inode* ip; - Inode* next; - - if (*path == '/') - ip = get_root(); - else - ip = idup(myproc()->cwd ? myproc()->cwd : get_root()); - - while ((path = skipelem(path, name)) != nullptr) - { - if (*path == 0) - { - return ip; - } - - ilock(ip); - if (ip->type != T_DIR) - { - iunlockput(ip); - return nullptr; - } - - next = ip->lookup(name); - iunlockput(ip); - - ip = next; - if (!ip) - return nullptr; - } - iput(ip); - return nullptr; - } -} // namespace VFS diff --git a/src/kernel/bio.cc b/src/kernel/bio.cc deleted file mode 100644 index 5f1aae9..0000000 --- a/src/kernel/bio.cc +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/buf.h" -#include "kernel/spinlock.h" -#include "kernel/riscv.h" -#include "drivers/virtio.h" -#include "drivers/uart.h" - -namespace BufferCache -{ - struct - { - Spinlock lock; - struct Buf buf[NBUF]; - struct Buf head; - } bcache; - - void init() - { - bcache.lock.init("bcache"); - // Initialize LRU doubly circular linked list - bcache.head.prev = &bcache.head; - bcache.head.next = &bcache.head; - for (struct Buf *b = bcache.buf; b < bcache.buf + NBUF; ++b) - { - b->lock.init("buffer"); - b->next = bcache.head.next; - b->prev = &bcache.head; - bcache.head.next->prev = b; - bcache.head.next = b; - } - Drivers::uart_puts("[Bio] Buffer cache initialized.\n"); - } - - static struct Buf* bget(uint32 dev, uint32 blockno) - { - struct Buf* b; - bcache.lock.acquire(); - - // Attempt to hit the cache - for (b = bcache.head.next; b != &bcache.head; b = b->next) - { - if (b->dev == dev && b->blockno == blockno) - { - b->refcnt++; - bcache.lock.release(); - b->lock.acquire(); // Acquire Sleep Lock - return b; - } - } - - // Cache miss, evicting LRU - for (b = bcache.head.prev; b != &bcache.head; b = b->prev) - { - if (b->refcnt == 0) - { - b->dev = dev; - b->blockno = blockno; - b->valid = 0; - b->refcnt = 1; - bcache.lock.release(); - b->lock.acquire(); - return b; - } - } - - Drivers::uart_puts("panic: bget: no buffers\n"); - while (1) - ; - } - - struct Buf* bread(uint32 dev, uint32 blockno) - { - struct Buf *b = bget(dev, blockno); - - if (!b->valid) - { - VirtIO::rw(0, b->blockno, (char *)b->data); - b->valid = 1; - } - - return b; - } - - void bwrite(struct Buf *b) - { - if (!b->lock.holding()) - { - Drivers::uart_puts("panic: bwrite\n"); - while (1) - ; - } - VirtIO::rw(1, b->blockno, (char *)b->data); - } - - void brelse(struct Buf *b) - { - if (!b->lock.holding()) - { - Drivers::uart_puts("panic: brelse\n"); - while (1) - ; - } - b->lock.release(); - - bcache.lock.acquire(); - b->refcnt--; - - if (b->refcnt == 0) - { - b->next->prev = b->prev; - b->prev->next = b->next; - - b->next = bcache.head.next; - b->prev = &bcache.head; - bcache.head.next->prev = b; - bcache.head.next = b; - } - - bcache.lock.release(); - } - - void bpin(struct Buf *b) - { - bcache.lock.acquire(); - b->refcnt++; - bcache.lock.release(); - } - - void bunpin(struct Buf* b) - { - bcache.lock.acquire(); - b->refcnt --; - bcache.lock.release(); - } - -} // namespace BufferCache diff --git a/src/kernel/config.cc b/src/kernel/config.cc deleted file mode 100644 index 36858a1..0000000 --- a/src/kernel/config.cc +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" - -uint64 g_uart_base = 0; -// uint64 g_clint_base = 0; -uint64 g_plic_base = 0; -uint64 g_ram_start = 0; -uint64 g_ram_size = 0; - -extern "C" uint64 g_dtb_addr; \ No newline at end of file diff --git a/src/kernel/cxx.cc b/src/kernel/cxx.cc deleted file mode 100644 index 00f2799..0000000 --- a/src/kernel/cxx.cc +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/pmm.h" -#include "drivers/uart.h" - -/* - * C++ Kernel Runtime Support - * - * * Strategy: Map directly to PMM::alloc_page(). - * Pros: Simple, works immediately for VFS. - * Cons: Allocates 4KB for every object (wasteful for small objects), - * but acceptable for current stage. - */ - -void *operator new(unsigned long size) -{ - // Safety check: PMM allocates 4096 bytes max per call - if (size > 4096) - { - Drivers::uart_puts("panic: new allocation too big (max 4096)\n"); - while (1) - ; - } - - void *p = PMM::alloc_page(); - if (!p) - { - Drivers::uart_puts("panic: new out of memory\n"); - while (1) - ; - } - // PMM::alloc_page returns zeroed memory usually, - // but standard 'new' doesn't guarantee it. - // The constructor will handle initialization. - return p; -} - -void *operator new[](unsigned long size) -{ - return operator new(size); -} - -void operator delete(void *p) -{ - if (p) - PMM::free_page(p); -} - -void operator delete[](void *p) -{ - if (p) - PMM::free_page(p); -} - -// C++14 Sized Deallocation (Required by modern compilers) -// The compiler optimizes 'delete' calls to pass the size of the object -void operator delete(void *p, unsigned long size) -{ - (void)size; // We ignore size because PMM always frees a full page - if (p) - PMM::free_page(p); -} - -void operator delete[](void *p, unsigned long size) -{ - (void)size; - if (p) - PMM::free_page(p); -} - -void *operator new(unsigned long size, void *ptr) -{ - (void)size; - return ptr; -} \ No newline at end of file diff --git a/src/kernel/fdt.cc b/src/kernel/fdt.cc deleted file mode 100644 index ae09c84..0000000 --- a/src/kernel/fdt.cc +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * LumeOS - FDT Parser - * - * Parses the Flattened Device Tree to discover hardware. - * Populates g_devices for use by all drivers. - */ -#include "common/types.h" -#include "kernel/fdt.h" -#include "lib/string.h" - -// Global device registry -DeviceRegistry g_devices; - -// Keep g_dtb_addr for PMM to protect DTB pages -// g_dtb_addr is defined in entry.S, g_uart_base in config.cc -extern "C" uint64 g_dtb_addr; -extern uint64 g_uart_base; - -static inline uint32 bswap(uint32 x) -{ - return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | - ((x & 0xFF0000) >> 8) | ((x >> 24) & 0xFF); -} - -struct fdt_header -{ - uint32 magic; - uint32 totalsize; - uint32 off_dt_struct; - uint32 off_dt_strings; - uint32 off_mem_rsvmap; - uint32 version; - uint32 last_comp_version; - uint32 boot_cpuid_phys; - uint32 size_dt_strings; - uint32 size_dt_struct; -}; - -#define FDT_MAGIC 0xd00dfeed -#define FDT_BEGIN_NODE 1 -#define FDT_END_NODE 2 -#define FDT_PROP 3 -#define FDT_NOP 4 -#define FDT_END 9 - -// Check if 'str' starts with 'prefix' -static bool starts_with(const char *str, const char *prefix) -{ - while (*prefix) - { - if (*str != *prefix) - return false; - str++; - prefix++; - } - return true; -} - -// Check if 'haystack' contains 'needle' as a substring within 'len' bytes -// (compatible strings may have multiple null-separated entries) -static bool compat_match(const uint8 *data, uint32 len, const char *needle) -{ - uint32 nlen = strlen(needle); - for (uint32 i = 0; i + nlen <= len; i++) - { - if (memcmp(data + i, needle, nlen) == 0) - return true; - } - return false; -} - -// Add a MMIO region to the registry (avoids duplicates) -static void add_mmio_region(uint64 base, uint64 size) -{ - if (base == 0 || size == 0) - return; - // Only track I/O regions below RAM - if (base >= 0x80000000ULL) - return; - if (g_devices.mmio_count >= DeviceRegistry::MAX_MMIO) - return; - - // Check for overlap/duplicate - for (int i = 0; i < g_devices.mmio_count; i++) - { - if (g_devices.mmio_regions[i].base == base) - return; - } - - g_devices.mmio_regions[g_devices.mmio_count].base = base; - g_devices.mmio_regions[g_devices.mmio_count].size = size; - g_devices.mmio_count++; -} - -void fdt_parse(uint64 dtb) -{ - // Clear registry - memset(&g_devices, 0, sizeof(g_devices)); - - if (dtb == 0) - return; - - g_dtb_addr = dtb; - - struct fdt_header *header = (struct fdt_header *)dtb; - if (bswap(header->magic) != FDT_MAGIC) - return; - - uint8 *struct_ptr = (uint8 *)(dtb + bswap(header->off_dt_struct)); - char *strings_ptr = (char *)(dtb + bswap(header->off_dt_strings)); - - // Parsing state - char current_node_name[64]; - current_node_name[0] = '\0'; - - // Per-node state - uint64 current_reg_addr = 0; - uint64 current_reg_size = 0; - uint32 current_irq = 0; - const uint8 *current_compat = nullptr; - uint32 current_compat_len = 0; - bool has_reg = false; - bool has_compat = false; - // bool has_irq = false; // Unused - - // Depth tracking (for #address-cells / #size-cells) - // Default for root: 2 address-cells, 2 size-cells - int depth = 0; - - while (1) - { - uint32 token = bswap(*(uint32 *)struct_ptr); - struct_ptr += 4; - - if (token == FDT_END) - break; - if (token == FDT_NOP) - continue; - - if (token == FDT_BEGIN_NODE) - { - // Save node name - // char *name = (char *)struct_ptr; - int i = 0; - while (*struct_ptr != 0 && i < 63) - { - current_node_name[i++] = *struct_ptr; - struct_ptr++; - } - current_node_name[i] = '\0'; - while (*struct_ptr != 0) - struct_ptr++; - struct_ptr++; - struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); - - // Reset per-node state - current_reg_addr = 0; - current_reg_size = 0; - current_irq = 0; - current_compat = nullptr; - current_compat_len = 0; - has_reg = false; - has_compat = false; - // has_irq = false; - - depth++; - } - else if (token == FDT_END_NODE) - { - // Process the node we just finished - if (has_reg && has_compat) - { - // UART: ns16550a - if (compat_match(current_compat, current_compat_len, "ns16550")) - { - g_devices.uart.base_addr = current_reg_addr; - g_devices.uart.size = current_reg_size ? current_reg_size : 0x100; - g_devices.uart.irq = current_irq; - add_mmio_region(current_reg_addr, g_devices.uart.size); - } - // PLIC - else if (compat_match(current_compat, current_compat_len, "plic")) - { - g_devices.plic.base_addr = current_reg_addr; - g_devices.plic.size = current_reg_size ? current_reg_size : 0x4000000; - g_devices.plic.irq = 0; - add_mmio_region(current_reg_addr, g_devices.plic.size); - } - // CLINT - else if (compat_match(current_compat, current_compat_len, "clint")) - { - g_devices.clint.base_addr = current_reg_addr; - g_devices.clint.size = current_reg_size ? current_reg_size : 0x10000; - g_devices.clint.irq = 0; - add_mmio_region(current_reg_addr, g_devices.clint.size); - } - // VirtIO - else if (compat_match(current_compat, current_compat_len, "virtio")) - { - if (g_devices.virtio_count < DeviceRegistry::MAX_VIRTIO) - { - int idx = g_devices.virtio_count++; - g_devices.virtio[idx].base_addr = current_reg_addr; - g_devices.virtio[idx].size = current_reg_size ? current_reg_size : 0x1000; - g_devices.virtio[idx].irq = current_irq; - add_mmio_region(current_reg_addr, g_devices.virtio[idx].size); - } - } - } - - // Memory node: name starts with "memory" - if (has_reg && starts_with(current_node_name, "memory")) - { - g_devices.mem_base = current_reg_addr; - g_devices.mem_size = current_reg_size; - } - - depth--; - } - else if (token == FDT_PROP) - { - uint32 len = bswap(*(uint32 *)struct_ptr); - struct_ptr += 4; - uint32 nameoff = bswap(*(uint32 *)struct_ptr); - struct_ptr += 4; - - char *name = strings_ptr + nameoff; - uint8 *data = struct_ptr; - - struct_ptr += len; - struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); - - if (strcmp(name, "reg") == 0 && len >= 8) - { - // Assume 2 address-cells, 2 size-cells (standard for QEMU virt) - uint32 hi = bswap(*(uint32 *)data); - uint32 lo = bswap(*(uint32 *)(data + 4)); - current_reg_addr = ((uint64)hi << 32) | lo; - - if (len >= 16) - { - uint32 shi = bswap(*(uint32 *)(data + 8)); - uint32 slo = bswap(*(uint32 *)(data + 12)); - current_reg_size = ((uint64)shi << 32) | slo; - } - has_reg = true; - } - else if (strcmp(name, "compatible") == 0) - { - current_compat = data; - current_compat_len = len; - has_compat = true; - } - else if (strcmp(name, "interrupts") == 0 && len >= 4) - { - current_irq = bswap(*(uint32 *)data); - // has_irq = true; - } - else if (strcmp(name, "interrupts-extended") == 0 && len >= 8) - { - // interrupts-extended = <&phandle irq_num> - // IRQ number is typically the second cell - current_irq = bswap(*(uint32 *)(data + 4)); - // has_irq = true; - } - } - } - - // Legacy compat - g_uart_base = g_devices.uart.base_addr; -} \ No newline at end of file diff --git a/src/kernel/initcode.S b/src/kernel/initcode.S deleted file mode 100644 index 22e55ca..0000000 --- a/src/kernel/initcode.S +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.section .rodata -.global initcode_start -.global initcode_end -.align 4 - -initcode_start: - .incbin "build/user/initcode" -initcode_end: diff --git a/src/kernel/main.cc b/src/kernel/main.cc deleted file mode 100644 index 6a19915..0000000 --- a/src/kernel/main.cc +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/riscv.h" -#include "drivers/uart.h" -#include "drivers/plic.h" -#include "drivers/virtio.h" -#include "kernel/pmm.h" -#include "kernel/mm.h" -#include "kernel/slab.h" -#include "kernel/proc.h" -#include "kernel/trap.h" -#include "kernel/timer.h" -#include "kernel/cpu.h" -#include "kernel/buf.h" -#include "fs/fs.h" -#include "fs/file.h" -#include "fs/fat32.h" -#include "lib/string.h" - -#include "kernel/fdt.h" // fdt_parse - -extern uint64 g_dtb_addr; - -// Placement new support (referenced from cxx.cc) -void *operator new(unsigned long size, void *ptr); - -volatile static int started = 0; - -extern "C" void kernel_main(uint64 hartid, uint64 dtb) -{ - if (hartid == 0) - { - fdt_parse(dtb); - - Drivers::uart_init(); - Drivers::uart_puts("\n[Lume OS] Booting...\n"); - - Drivers::uart_puts("[Boot] PMP configured.\n"); - - PMM::init(); // Physical Memory Management - VM::kvminit(); // Kernel Pagetable - VM::kvminithart(); // Enable MMU - Slab::init(); - Trap::init(); // Trap Management - Trap::inithart(); - PLIC::init(); - PLIC::inithart(); - VirtIO::init(); - BufferCache::init(); - VFS::init(); - // Initialize global FAT32 object manually (global constructors not supported yet) - new (&fat32_fs) FAT32FileSystem(); - VFS::register_fs(&fat32_fs); - FileTable::init(); - Timer::init(); - ProcManager::init(); // Process Management - ProcManager::user_init(); // Initialize first user process - - __atomic_store_n(&started, 1, __ATOMIC_RELEASE); - - Drivers::uart_puts("[Boot] System initialized. Entering scheduler...\n"); - } - else - { - while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0) - ; - - VM::kvminithart(); - Trap::inithart(); - PLIC::inithart(); - Timer::init(); - Drivers::uart_puts("[Boot] Hart started.\n"); - } - - ProcManager::scheduler(); // Enable Scheduler -} \ No newline at end of file diff --git a/src/kernel/mm/pmm.cc b/src/kernel/mm/pmm.cc deleted file mode 100644 index d3d99ba..0000000 --- a/src/kernel/mm/pmm.cc +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/riscv.h" -#include "kernel/pmm.h" -#include "kernel/mm.h" -#include "kernel/fdt.h" -// #include "kernel/spinlock.h" -#include "drivers/uart.h" -#include "lib/string.h" - -extern "C" char kernel_end[]; -extern uint64 g_dtb_addr; - -// Default memory size 128MB, will be overwritten by FDT -uint64 g_phys_size = 128 * 1024 * 1024; - -namespace PMM -{ - // Global metadata array pointer - struct Page *mem_map = nullptr; - // Total number of physical memory pages - uint64 total_pages = 0; - // Free list array (free_area[k] stores the head of the free block list of order=k) - struct Page *free_area[MAX_ORDER]; - // Protective lock - Spinlock pmm_lock; - - struct Page *pa_to_page(uint64 pa) - { - if (pa < KERNBASE || pa >= PHYSTOP) - return nullptr; - uint64 pfn = (pa - KERNBASE) >> PGSHIFT; - if (pfn >= total_pages) - return nullptr; - return &mem_map[pfn]; - } - - uint64 page_to_pa(struct Page *page) - { - uint64 pfn = page - mem_map; - return KERNBASE + (pfn << PGSHIFT); - } - - void list_add(struct Page *page, int order) - { - page->order = order; - page->flags = 0; // Mark as available - page->next = free_area[order]; - page->prev = nullptr; - - if (free_area[order]) - { - free_area[order]->prev = page; - } - free_area[order] = page; - } - - void list_del(struct Page* page, int order) - { - if (page->prev) - { - page->prev->next = page->next; - } - else - { - free_area[order] = page->next; - } - if (page->next) - { - page->next->prev = page->prev; - } - page->next = nullptr; - page->prev = nullptr; - } - - void init() - { - // Use FDT memory size if available - if (g_devices.mem_size > 0) - { - g_phys_size = g_devices.mem_size; - } - - pmm_lock.init("pmm"); - - uint64 kern_end_pa = PGROUNDUP((uint64)kernel_end); - total_pages = (PHYSTOP - KERNBASE) / PGSIZE; - - Drivers::uart_puts(ANSI_GREEN "[PMM] Total RAM size: "); - Drivers::uart_put_int(g_phys_size / (1024 * 1024)); - Drivers::uart_puts(" MB\n" ANSI_RESET); - // Place the mem_map array - // mem_map is located immediately after kernel_end - mem_map = (struct Page*)kern_end_pa; - - uint64 mem_map_size = total_pages * sizeof(struct Page); - uint64 free_mem_start = PGROUNDUP(kern_end_pa + mem_map_size); - - Drivers::uart_puts(ANSI_GREEN "[PMM] MemMap placed at: " ); - Drivers::print_hex((uint64)mem_map); - Drivers::uart_puts("\n" ANSI_RESET); - - Drivers::uart_puts(ANSI_GREEN "[PMM] Free memory starts at: "); - Drivers::print_hex(free_mem_start); - Drivers::uart_puts("\n" ANSI_RESET); - - if (free_mem_start >= PHYSTOP) - { - Drivers::uart_puts(ANSI_RED "[PMM] Error - Kernel too large, no memory left!\n" ANSI_RESET); - while (1) - ; - } - - // Initialize all page states - for (uint64 i = 0; i < total_pages; i++) - { - mem_map[i].flags = PG_reserved; - mem_map[i].order = 0; - mem_map[i].refcnt = 0; - mem_map[i].next = nullptr; - mem_map[i].prev = nullptr; - } - - for (int i = 0; i < MAX_ORDER; i++) - { - free_area[i] = nullptr; - } - - // Release actual free memory to the allocator - // Range: [free_mem_start, PHYSTOP) - // Note: need to avoid the page where the DTB is located - uint64 dtb_pa_start = PGROUNDDOWN(g_dtb_addr); - uint64 dtb_pa_end = PGROUNDUP(g_dtb_addr + 0x10000); - - uint64 p = free_mem_start; - while (p < PHYSTOP) - { - if (p >= dtb_pa_start && p < dtb_pa_end) - { - p += PGSIZE; - continue; - } - - // Mark the page as Used, then call free_pages to release it - // This allows free_pages to handle the merging logic automatically - struct Page *page = pa_to_page(p); - if (page) - { - page->flags = PG_used; - free_pages((void *)p, 0); // Release page by page, let Buddy merge automatically - } - p += PGSIZE; - - - } - - } - - void *alloc_pages(int order) - { - if (order < 0 || order >= MAX_ORDER) - return nullptr; - - pmm_lock.acquire(); - - int current_order = order; - while (current_order < MAX_ORDER) - { - if (free_area[current_order]) - { - // Find free block - struct Page *page = free_area[current_order]; - list_del(page, current_order); - - // If the block is too large, split it. - while (current_order > order) - { - current_order--; - // page is the left part (Buddy A) - // buddy is the right part (Buddy B) - // Pointer addition: Page* + n is equivalent to address offset n * sizeof(Page) - // Since mem_map is linearly mapped, mem_map[i + (1<flags = PG_used; - page->order = order; // Record the orders that have been assigned - page->refcnt = 1; - - pmm_lock.release(); - return (void *)page_to_pa(page); - } - current_order++; - } - pmm_lock.release(); - Drivers::uart_puts("PMM: OOM alloc_pages\n"); - return nullptr; - } - - void free_pages(void *pa, int order) - { - if (!pa) - return; - struct Page *page = pa_to_page((uint64)pa); - if (!page) - return; - pmm_lock.acquire(); - - if (!(page->flags & PG_used)) - { - Drivers::uart_puts("PMM: Double free warning\n"); - pmm_lock.release(); - return; - } - - // Mark as basic status - page->flags = 0; - page->refcnt = 0; - - // try merge - uint64 page_idx = page - mem_map; - - while (order < MAX_ORDER - 1) - { - // calculate Buddy index: index ^ (1 << order) - uint64 buddy_idx = page_idx ^ (1 << order); - struct Page *buddy = &mem_map[buddy_idx]; - - // Check if the Buddy can be merged: - // 1. Must be within the memory range - // 2. Must be free (flags == 0) - // 3. Must be at the current order (to prevent merging already split small blocks) - if (buddy_idx >= total_pages || (buddy->flags & PG_used) || (buddy->flags & PG_reserved) || buddy->order != order) - { - break; - } - - // Can be merged! - // Remove Buddy from the free list - list_del(buddy, order); - - // Update the current block to the merged block - // The starting address of the merged block is the smaller one between index and buddy_index - if (buddy_idx < page_idx) - { - page = buddy; - page_idx = buddy_idx; - } - - order++; - } - - // Insert into the final order linked list - list_add(page, order); - - pmm_lock.release(); - } - - void *alloc_page() - { - void *p = alloc_pages(0); - if (p) - { - memset(p, 0, PGSIZE); - } - return p; - } - - void free_page(void *pa) - { - free_pages(pa, 0); - } - - void print_free_lists() - { - // Debug print the number of free blocks at each level - Drivers::uart_puts("PMM Stats: [ "); - for (int i = 0; i < MAX_ORDER; i++) - { - int count = 0; - struct Page *p = free_area[i]; - while (p) - { - count++; - p = p->next; - } - if (count > 0) - { - Drivers::uart_puts("Order"); - // Drivers::uart_put_int(i); - Drivers::uart_puts(":"); - // Drivers::uart_put_int(count); - Drivers::uart_puts(" "); - } - } - Drivers::uart_puts("]\n"); - } -} // namespace PMM diff --git a/src/kernel/mm/slab.cc b/src/kernel/mm/slab.cc deleted file mode 100644 index 3bb6b1b..0000000 --- a/src/kernel/mm/slab.cc +++ /dev/null @@ -1,259 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/slab.h" -#include "kernel/pmm.h" -#include "kernel/spinlock.h" -#include "kernel/riscv.h" -#include "drivers/uart.h" -#include "lib/string.h" - -#define PG_slab 0x04 - -// Supported object size (must be a multiple of 8 and <= 2048) -const uint64 kmalloc_sizes[] = { - 16, 32, 64, 128, 256, 512, 1024, 2048}; -const int NUM_CACHES = 8; - -struct SlabHeader -{ - struct SlabHeader *next; // Next slab in the linked list - uint16 free_idx; // Index of the head of the free list (0xFFFF means none) - uint16 used_count; // Number of allocated objects - // Followed immediately by object data... - // The free list of objects is embedded in the first 2 bytes of the object itself (stores the index of the next object) - uint32 padding; -}; - -struct KmemCache -{ - Spinlock lock; - uint64 obj_size; - uint64 obj_count_per_slab; - struct SlabHeader *slab_list; // Slab list with free objects - const char *name; -}; - -struct KmemCache caches[NUM_CACHES]; - -// Virtual address -> struct Page* -static struct Page *virt_to_page(void *ptr) -{ - uint64 pa = (uint64)ptr; - return PMM::pa_to_page(pa); -} - -// Slab Implementation - -namespace Slab -{ - - void init() - { - for (int i = 0; i < NUM_CACHES; i++) - { - caches[i].lock.init("slab_lock"); - caches[i].obj_size = kmalloc_sizes[i]; - caches[i].name = "kmalloc"; - - // Calculate how many objects can fit on a page - // Excluding the overhead of the SlabHeader - uint64 available = PGSIZE - sizeof(struct SlabHeader); - caches[i].obj_count_per_slab = available / caches[i].obj_size; - caches[i].slab_list = nullptr; - } - Drivers::uart_puts("[Slab] Initialized kmalloc caches.\n"); - } - - // Allocate a new slab page for the cache - // Must hold cache->lock - static struct SlabHeader *grow_cache(struct KmemCache *c) - { - void *p = PMM::alloc_page(); - if (!p) - return nullptr; - - // Mark this page as a Slab page - struct Page *page = virt_to_page(p); - if (page) - { - page->flags |= PG_slab; - int cache_idx = c - caches; - page->order = (uint8)cache_idx; - } - - struct SlabHeader *slab = (struct SlabHeader *)p; - slab->next = nullptr; - slab->used_count = 0; - slab->free_idx = 0; - slab->padding = 0; - - // Initialize the free list of objects - // The starting position of each object stores the index of the next object - // Starting position of the object data area - char *base = (char *)(slab + 1); - - for (uint i = 0; i < c->obj_count_per_slab - 1; i++) - { - // The content of the i-th object is i+1 - uint16 *ptr = (uint16 *)(base + i * c->obj_size); - *ptr = i + 1; - } - // The last object points to 0xFFFF - uint16 *ptr = (uint16 *)(base + (c->obj_count_per_slab - 1) * c->obj_size); - *ptr = 0xFFFF; - - return slab; - } - - void *kmalloc(uint64 size) - { - if (size == 0) - return nullptr; - - // Large objects go directly through PMM - if (size > 2048) - { - // calculate order - int order = 0; - uint64 req = size; - uint64 pages = (size + PGSIZE - 1) / PGSIZE; - (void)req; - - // calculate order: 2^order >= pages - while ((1UL << order) < pages) - { - order++; - } - if (order > MAX_ORDER) - return nullptr; - - void *p = PMM::alloc_pages(order); - // Here, page->flags defaults to PG_used, not PG_slab - // kfree needs to be able to recognize it - return p; - } - - int idx = 0; - while (idx < NUM_CACHES && kmalloc_sizes[idx] < size) - { - idx++; - } - - struct KmemCache *c = &caches[idx]; - c->lock.acquire(); - - if (!c->slab_list) - { - c->slab_list = grow_cache(c); - if (!c->slab_list) - { - c->lock.release(); - return nullptr; - } - } - - struct SlabHeader *slab = c->slab_list; - - int obj_idx = slab->free_idx; - char *base = (char *)(slab + 1); - void *ptr = base + obj_idx * c->obj_size; - - uint16 *link = (uint16 *)ptr; - slab->free_idx = *link; - slab->used_count++; - - if (slab->free_idx == 0xFFFF) - { - if (slab->next) - { - c->slab_list = slab->next; - slab->next = nullptr; - } - else - { - c->slab_list = nullptr; - } - } - - c->lock.release(); - - memset(ptr, 0, size); - return ptr; - } - - void kfree(void *ptr) - { - if (!ptr) - return; - - struct Page *page = virt_to_page(ptr); - if (!page) - return; - - if (!page || !(page->flags & PG_slab)) - { - PMM::free_pages(ptr, page->order); - return; - } - - int cache_idx = page->order; - if (cache_idx >= NUM_CACHES) - { - Drivers::uart_puts("Slab: Corrupted page metadata\n"); - return; - } - - struct KmemCache *c = &caches[cache_idx]; - c->lock.acquire(); - - struct SlabHeader *slab = (struct SlabHeader *)PGROUNDDOWN((uint64)ptr); - - if ((uint64)ptr < (uint64)(slab + 1) || (uint64)ptr >= (uint64)slab + PGSIZE) - { - Drivers::uart_puts("Slab: kfree invalid pointer\n"); - c->lock.release(); - return; - } - - char *base = (char *)(slab + 1); - int obj_idx = ((uint64)ptr - (uint64)base) / c->obj_size; - - uint16 *link = (uint16 *)ptr; - *link = slab->free_idx; - slab->free_idx = (uint16)obj_idx; - - slab->used_count--; - - if (slab->used_count == 0) - { - struct SlabHeader **pp = &c->slab_list; - // bool found = false; - while (*pp) - { - if (*pp == slab) - { - *pp = slab->next; - // found = true; - break; - } - pp = &(*pp)->next; - } - - page->flags &= ~PG_slab; - PMM::free_page(slab); - } - else - { - - if (slab->used_count == c->obj_count_per_slab - 1) - { - slab->next = c->slab_list; - c->slab_list = slab; - } - } - - c->lock.release(); - } -} \ No newline at end of file diff --git a/src/kernel/mm/vm.cc b/src/kernel/mm/vm.cc deleted file mode 100644 index be2f2f2..0000000 --- a/src/kernel/mm/vm.cc +++ /dev/null @@ -1,459 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/mm.h" -#include "kernel/pmm.h" -#include "kernel/riscv.h" -#include "common/types.h" -#include "lib/string.h" -#include "drivers/uart.h" -#include "kernel/fdt.h" - -extern "C" -{ - extern char kernel_end[]; - extern char trampoline[]; -} - -namespace VM -{ - - uint64 *kernel_pagetable; - - static void page_ref_inc(uint64 pa) - { - struct Page *p = PMM::pa_to_page(pa); - if (p) - { - __atomic_add_fetch(&p->refcnt, 1, __ATOMIC_RELAXED); - } - } - - static bool page_ref_dec(uint64 pa) - { - struct Page *p = PMM::pa_to_page(pa); - if (p) - { - if (__atomic_sub_fetch(&p->refcnt, 1, __ATOMIC_RELEASE) == 0) - { - return true; - } - } - return false; - } - - static uint64 *walk(uint64 *pagetable, uint64 va, int alloc) - { - if (va >= MAXVA) - return nullptr; - - for (int level = 2; level > 0; level--) - { - uint64 *pte = &pagetable[PX(level, va)]; - if (*pte & PTE_V) - { - pagetable = (uint64 *)PTE2PA(*pte); - } - else - { - if (!alloc || (pagetable = (uint64 *)PMM::alloc_page()) == nullptr) - return nullptr; - - memset(pagetable, 0, PGSIZE); - *pte = PA2PTE(pagetable) | PTE_V; - } - } - return &pagetable[PX(0, va)]; - } - - int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm) - { - uint64 a, last; - uint64 *pte; - - if (size == 0) - return -1; - - a = PGROUNDDOWN(va); - last = PGROUNDDOWN(va + size - 1); - - for (;;) - { - if ((pte = walk(pagetable, a, 1)) == nullptr) - return -1; - - if (*pte & PTE_V) - { - return -1; - } - - *pte = PA2PTE(pa) | perm | PTE_V; - - if (a == last) - break; - a += PGSIZE; - pa += PGSIZE; - } - return 0; - } - - uint64 *uvmcreate() - { - uint64 *pagetable = (uint64 *)PMM::alloc_page(); - if (pagetable) - { - memset(pagetable, 0, PGSIZE); - - if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0) - { - PMM::free_page(pagetable); - return nullptr; - } - } - return pagetable; - } - - void uvminit(uint64 *pagetable, uchar *src, uint64 sz) - { - char *mem; - - if (sz >= PGSIZE) - { - Drivers::uart_puts("uvminit: initcode too big!\n"); - return; - } - - mem = (char *)PMM::alloc_page(); - memset(mem, 0, PGSIZE); - - mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W | PTE_R | PTE_X | PTE_U); - memmove(mem, src, sz); - } - - uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm) - { - char *mem; - uint64 a; - - if (newsz < oldsz) - return oldsz; - - oldsz = PGROUNDUP(oldsz); - for (a = oldsz; a < newsz; a += PGSIZE) - { - mem = (char *)PMM::alloc_page(); - if (mem == nullptr) - { - uvmdealloc(pagetable, a, oldsz); - return 0; - } - memset(mem, 0, PGSIZE); - if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_U | xperm) != 0) - { - PMM::free_page(mem); - uvmdealloc(pagetable, a, oldsz); - return 0; - } - } - return newsz; - } - - uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz) - { - if (newsz >= oldsz) - return oldsz; - - if (PGROUNDUP(newsz) < PGROUNDUP(oldsz)) - { - int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE; - uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1); - } - - return newsz; - } - - void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free) - { - uint64 a; - uint64 *pte; - - if ((va % PGSIZE) != 0) - return; - - for (a = va; a < va + npages * PGSIZE; a += PGSIZE) - { - if ((pte = walk(pagetable, a, 0)) == nullptr) - continue; - - if ((*pte & PTE_V) == 0) - continue; - - if ((PTE_FLAGS(*pte) & (PTE_R | PTE_W | PTE_X)) == 0) - { - continue; - } - - if (do_free) - { - uint64 pa = PTE2PA(*pte); - if (page_ref_dec(pa)) - { - PMM::free_page((void *)pa); - } - } - *pte = 0; - } - } - - static void freewalk(uint64 *pagetable) - { - for (int i = 0; i < 512; i++) - { - uint64 pte = pagetable[i]; - if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) - { - uint64 *child = (uint64 *)PTE2PA(pte); - freewalk(child); - pagetable[i] = 0; - } - } - PMM::free_page(pagetable); - } - - void uvmfree(uint64 *pagetable, uint64 sz) - { - if (sz > 0) - uvmunmap(pagetable, 0, PGROUNDUP(sz) / PGSIZE, 1); - freewalk(pagetable); - } - - int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz) - { - uint64 *pte; - uint64 pa, i; - uint flags; - - for (i = 0; i < sz; i += PGSIZE) - { - if ((pte = walk(oldpt, i, 0)) == nullptr) - continue; - if ((*pte & PTE_V) == 0) - continue; - - pa = PTE2PA(*pte); - flags = PTE_FLAGS(*pte); - - if (flags & PTE_W) - { - flags &= ~PTE_W; - flags |= PTE_COW; - *pte = PA2PTE(pa) | flags; - } - - if (mappages(newpt, i, PGSIZE, pa, flags) != 0) - { - return -1; - } - - page_ref_inc(pa); - } - - sfence_vma(); - return 0; - } - - int handle_cow_fault(uint64 *pagetable, uint64 va); - - int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len) - { - uint64 n, va0, pa0; - uint64 *pte; - - while (len > 0) - { - va0 = PGROUNDDOWN(dstva); - pte = walk(pagetable, va0, 0); - if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return -1; - - // Note: Maybe a bug - if (*pte & PTE_COW) - { - if (handle_cow_fault(pagetable, va0) < 0) - return -1; - // Re-walk because handle_cow_fault replaced the page mapping - pte = walk(pagetable, va0, 0); - if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return -1; - } - - pa0 = PTE2PA(*pte); - n = PGSIZE - (dstva - va0); - if (n > len) - n = len; - - memmove((void *)(pa0 + (dstva - va0)), src, n); - - len -= n; - src += n; - dstva = va0 + PGSIZE; - } - return 0; - } - - int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len) - { - uint64 n, va0, pa0; - uint64 *pte; - - while (len > 0) - { - va0 = PGROUNDDOWN(srcva); - pte = walk(pagetable, va0, 0); - if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return -1; - - pa0 = PTE2PA(*pte); - n = PGSIZE - (srcva - va0); - if (n > len) - n = len; - - memmove(dst, (void *)(pa0 + (srcva - va0)), n); - - len -= n; - dst += n; - srcva = va0 + PGSIZE; - } - return 0; - } - - int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max) - { - uint64 n, va0, pa0; - uint64 *pte; - int got_null = 0; - uint64 len = 0; - - while (len < max) - { - va0 = PGROUNDDOWN(srcva); - pte = walk(pagetable, va0, 0); - - if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return -1; - - pa0 = PTE2PA(*pte); - - n = PGSIZE - (srcva - va0); - if (n > max - len) - n = max - len; - - char *p = (char *)(pa0 + (srcva - va0)); - while (n > 0) - { - if (*p == '\0') - { - *dst = '\0'; - got_null = 1; - break; - } - *dst = *p; - n--; - srcva++; - dst++; - p++; - len++; - } - if (got_null) - break; - } - - if (got_null) - return 0; - - return -1; - } - - int handle_cow_fault(uint64 *pagetable, uint64 va) - { - if (va >= MAXVA) - return -1; - - uint64 *pte = walk(pagetable, va, 0); - if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return -1; - - if ((*pte & PTE_COW) == 0) - return -1; - - uint64 pa = PTE2PA(*pte); - uint64 flags = PTE_FLAGS(*pte); - - char *mem = (char *)PMM::alloc_page(); - if (mem == nullptr) - return -1; - - memmove(mem, (char *)pa, PGSIZE); - - uint64 new_flags = (flags | PTE_W) & ~PTE_COW; - *pte = PA2PTE((uint64)mem) | new_flags | PTE_V; - - if (page_ref_dec(pa)) - { - PMM::free_page((void *)pa); - } - - sfence_vma(); - return 0; - } - - static void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm) - { - if (mappages(kernel_pagetable, va, sz, pa, perm) != 0) - { - Drivers::uart_puts("kvmmap: failed\n"); - while (1) - ; - } - } - - void kvminit() - { - kernel_pagetable = uvmcreate(); - - // 1. Map all device MMIO regions discovered by FDT - for (int i = 0; i < g_devices.mmio_count; i++) - { - uint64 base = g_devices.mmio_regions[i].base; - uint64 size = g_devices.mmio_regions[i].size; - kvmmap(base, base, size, PTE_R | PTE_W); - } - - // 2. Map Kernel Memory (PHYSTOP is now dynamic based on FDT) - kvmmap(KERNBASE, KERNBASE, PHYSTOP - KERNBASE, PTE_R | PTE_W | PTE_X); - } - - void kvminithart() - { - w_satp(MAKE_SATP(kernel_pagetable)); - sfence_vma(); - Drivers::uart_puts("[Boot] MMU ENABLED! Using virtual addresses.\n"); - } - - uint64 walkaddr(uint64 *pagetable, uint64 va) - { - uint64 *pte; - uint64 pa; - - if (va >= MAXVA) - return 0; - - pte = walk(pagetable, va, 0); - if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) - return 0; - - pa = PTE2PA(*pte); - return pa; - } -} \ No newline at end of file diff --git a/src/kernel/proc/exec.cc b/src/kernel/proc/exec.cc deleted file mode 100644 index 8d8c467..0000000 --- a/src/kernel/proc/exec.cc +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/riscv.h" -#include "kernel/proc.h" -#include "kernel/mm.h" -#include "kernel/elf.h" -#include "fs/fs.h" -#include "lib/string.h" -#include "drivers/uart.h" -#include "kernel/trap.h" - -extern char trampoline[]; - -// RAII: Page Table Resource Management -class VmGuard -{ -public: - uint64 *pagetable = nullptr; - uint64 sz = 0; - - VmGuard() : pagetable(VM::uvmcreate()) {} - - ~VmGuard() - { - if (pagetable) - { - VM::uvmfree(pagetable, sz); - } - } - - // Copying is prohibited - VmGuard(const VmGuard &) = delete; - VmGuard &operator=(const VmGuard &) = delete; - - // Submit resources: Ownership transfer, destructor no longer releases - uint64 *commit() - { - uint64 *pt = pagetable; - pagetable = nullptr; - return pt; - } -}; - -// RAII: Inode Lock Management -class InodeGuard -{ -public: - Inode *ip; - InodeGuard(Inode *i) : ip(i) - { - if (ip) - VFS::ilock(ip); - } - ~InodeGuard() - { - if (ip) - { - VFS::iunlockput(ip); // Automatically unlock and release references - } - } - // Copying is prohibited - InodeGuard(const InodeGuard &) = delete; - InodeGuard &operator=(const InodeGuard &) = delete; -}; - -// laod segment -static int loadseg(uint64 *pagetable, uint64 va, Inode *ip, uint64 offset, uint64 sz) -{ - for (uint64 i = 0; i < sz; i += PGSIZE) - { - uint64 pa = VM::walkaddr(pagetable, va + i); - if (pa == 0) - { - Drivers::uart_puts("[Exec Debug] loadseg: walkaddr failed\n"); - return -1; - } - - uint64 n = PGSIZE - (va + i) % PGSIZE; - if (i + n > sz) - n = sz - i; - - // Read physical address (isUser=0) - if (ip->read((char *)(pa + (va + i) % PGSIZE), offset + i, n, 0) != (int)n) - { - Drivers::uart_puts("[Exec Debug] loadseg: read failed\n"); - return -1; - } - } - return 0; -} - -namespace Exec -{ - int exec(char *path, char **argv) - { - // 1. Get Inode - Inode *raw_ip = VFS::namei(path); - if (!raw_ip) - { - Drivers::uart_puts("[Exec Debug] namei failed for path: "); - Drivers::uart_puts(path); - Drivers::uart_puts("\n"); - return -1; - } - InodeGuard iguard(raw_ip); - - // Get ELF head - struct elfhdr elf; - if (raw_ip->read((char *)&elf, 0, sizeof(elf), 0) != sizeof(elf)) - { - Drivers::uart_puts("[Exec Debug] read elf header failed\n"); - return -1; - } - if (elf.magic != ELF_MAGIC) - { - Drivers::uart_puts("[Exec Debug] invalid elf magic\n"); - return -1; - } - - // Create new pagetable - VmGuard vm_guard; - if (!vm_guard.pagetable) - { - Drivers::uart_puts("[Exec Debug] uvmcreate failed\n"); - return -1; - } - - // Load Program Segment - struct proghdr ph; - for (int i = 0; i < elf.phnum; i++) - { - uint64 off = elf.phoff + i * sizeof(ph); - if (raw_ip->read((char *)&ph, off, sizeof(ph), 0) != sizeof(ph)) - { - Drivers::uart_puts("[Exec Debug] read proghdr failed\n"); - return -1; - } - - if (ph.type != ELF_PROG_LOAD) - continue; - if (ph.memsz < ph.filesz) - return -1; - if (ph.vaddr + ph.memsz < ph.vaddr) - return -1; - - uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, vm_guard.sz, ph.vaddr + ph.memsz, PTE_W | PTE_X | PTE_R | PTE_U); - if (sz1 == 0) - { - Drivers::uart_puts("[Exec Debug] uvmalloc failed (segment)\n"); - return -1; - } - vm_guard.sz = sz1; - - if (loadseg(vm_guard.pagetable, ph.vaddr, raw_ip, ph.off, ph.filesz) < 0) - return -1; - } - - // Prepare user stack - uint64 sz = PGROUNDUP(vm_guard.sz); - - const int stack_data_pages = 16; - const int stack_total_pages = stack_data_pages + 1; - - uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, sz, sz + stack_total_pages * PGSIZE, PTE_W | PTE_R | PTE_U); - if (sz1 == 0) - { - Drivers::uart_puts("[Exec Debug] uvmalloc failed (stack)\n"); - return -1; - } - vm_guard.sz = sz1; - - VM::uvmunmap(vm_guard.pagetable, sz, 1, 0); - - uint64 sp = sz1; - uint64 stackbase = sp - stack_data_pages * PGSIZE; - uint64 ustack[32]; - uint64 argc = 0; - - // Push parameter string - for (; argv[argc]; argc++) - { - if (argc >= 31) - return -1; - sp -= strlen(argv[argc]) + 1; - sp -= sp % 16; - if (sp < stackbase) - { - Drivers::uart_puts("[Exec Debug] stack overflow (args)\n"); - return -1; - } - - if (VM::copyout(vm_guard.pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0) - { - Drivers::uart_puts("[Exec Debug] copyout arg string failed\n"); - return -1; - } - ustack[argc] = sp; - } - ustack[argc] = 0; - - // Push into parameter array - sp -= (argc + 1) * sizeof(uint64); - sp -= sp % 16; - if (sp < stackbase) - { - Drivers::uart_puts("[Exec Debug] stack overflow (argv array)\n"); - return -1; - } - if (VM::copyout(vm_guard.pagetable, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) < 0) - { - Drivers::uart_puts("[Exec Debug] copyout argv array failed\n"); - return -1; - } - - // Commit Changes - struct Proc *p = myproc(); - if (VM::mappages(vm_guard.pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) - { - Drivers::uart_puts("[Exec Debug] mappages TRAPFRAME failed\n"); - return -1; - } - - uint64 oldsz = p->sz; - uint64 *oldpagetable = p->pagetable; - - p->tf->a0 = argc; - p->tf->a1 = sp; - p->tf->sp = sp; - p->tf->epc = elf.entry; - - char *s = path, *last = path; - while (*s) - { - if (*s == '/') - last = s + 1; - s++; - } - - size_t len = 0; - while (last[len] && len < sizeof(p->name) - 1) - { - p->name[len] = last[len]; - len++; - } - p->name[len] = '\0'; - - // change pagetable - p->pagetable = vm_guard.commit(); - p->sz = vm_guard.sz; - - // release old pagetable - VM::uvmfree(oldpagetable, oldsz); - asm volatile("fence.i"); - - return 0; // iguard unlocks and releases IP upon destruction - } -} \ No newline at end of file diff --git a/src/kernel/proc/proc.cc b/src/kernel/proc/proc.cc deleted file mode 100644 index 3923fe8..0000000 --- a/src/kernel/proc/proc.cc +++ /dev/null @@ -1,654 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/proc.h" -#include "kernel/proc_def.h" -#include "kernel/runqueue.h" -#include "kernel/cpu.h" -#include "kernel/spinlock.h" -#include "kernel/pmm.h" -#include "kernel/mm.h" -#include "kernel/trap.h" -#include "kernel/riscv.h" -#include "kernel/slab.h" -#include "drivers/uart.h" -#include "fs/fs.h" -#include "fs/file.h" -#include "lib/string.h" - -// #define NPROC 64 -#define NCPU 8 - -extern "C" void swtch(struct Context *old_ctx, struct Context *new_ctx); -extern char uservec[], trampoline[]; -extern "C" void usertrap(); -extern "C" void userret(uint64, uint64); -extern char initcode_start[], initcode_end[]; - -// struct Proc procs[NPROC]; -struct Proc *proc_list_head = nullptr; -struct Proc *initproc = nullptr; -struct Spinlock proc_mem_lock; -struct cpu cpus[NCPU]; -struct RunQueue runqueues[NCPU]; - - -static int nextpid = 1; - - -struct cpu *mycpu() -{ - int id = r_tp(); - return &cpus[id]; -} - - -void RunQueue::init(const char *name) -{ - lock.init(name); - count = 0; - for (int i = 0; i < N_PRIO; i++) - { - heads[i] = nullptr; - tails[i] = nullptr; - } -} - -void RunQueue::enqueue(struct Proc *p) -{ - if (p->priority < 0 || p->priority >= N_PRIO) - p->priority = PRIO_MIN; - p->next = nullptr; - if (heads[p->priority] == nullptr) - { - heads[p->priority] = p; - tails[p->priority] = p; - } - else - { - tails[p->priority]->next = p; - tails[p->priority] = p; - } - count++; -} - -struct Proc *RunQueue::dequeue() -{ - for (int i = 0; i < N_PRIO; i++) - { - if (heads[i]) - { - struct Proc *p = heads[i]; - heads[i] = p->next; - if (!heads[i]) - tails[i] = nullptr; - p->next = nullptr; - count--; - return p; - } - } - return nullptr; -} - -struct Proc *RunQueue::steal() -{ - for (int i = N_PRIO - 1; i >= 0; i--) - { - if (heads[i]) - { - struct Proc *p = heads[i]; - heads[i] = p->next; - if (!heads[i]) - tails[i] = nullptr; - p->next = nullptr; - count--; - return p; - } - } - return nullptr; -} - -// Trap Return Functions - -void usertrapret() -{ - struct Proc *p = myproc(); - - intr_off(); - - // Set stvec to point to uservec in the trampoline - // When an interrupt occurs in user mode, the CPU will jump here - uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline); - w_stvec(trampoline_uservec); - - // Fill in the Trapframe, used when uservec saves the kernel state - p->tf->kernel_satp = r_satp(); - p->tf->kernel_sp = (uint64)p->kstack + PGSIZE; - p->tf->kernel_trap = (uint64)usertrap; - p->tf->kernel_hartid = r_tp(); - - // Set SSTATUS - // SPP = 0 (return to User Mode) - // SPIE = 1 (enable interrupts) - unsigned long x = r_sstatus(); - x &= ~SSTATUS_SPP; - x |= SSTATUS_SPIE; - w_sstatus(x); - - // Set SEPC to the user program counter - w_sepc(p->tf->epc); - - // Prepare user page table SATP - uint64 satp = MAKE_SATP(p->pagetable); - - // Calculate the virtual address of userret in the trampoline - uint64 fn = TRAMPOLINE + ((uint64)userret - (uint64)trampoline); - - // Jump to userret, with parameters being the TRAPFRAME address and the user page table SATP - ((void (*)(uint64, uint64))fn)(TRAPFRAME, satp); -} - - -void forkret() -{ - - struct cpu *c = mycpu(); - c->runqueue->lock.release(); - - // Debug - // Drivers::uart_puts("[Proc] forkret: jumping to user.\n"); - - // Enter user mode - usertrapret(); -} - -// ProcManager Implementation - -static Proc *allocproc() -{ - struct Proc *p; - proc_mem_lock.acquire(); - - p = (struct Proc *)Slab::kmalloc(sizeof(struct Proc)); - - if (!p) - { - proc_mem_lock.release(); - return 0; - } - - memset(p, 0, sizeof(struct Proc)); - - p->all_next = proc_list_head; - p->all_prev = nullptr; - if (proc_list_head) - proc_list_head->all_prev = p; - proc_list_head = p; - - - p->state = USED; - p->pid = nextpid++; - p->priority = 0; - p->killed = 0; - p->cpu = mycpu(); - p->cwd = nullptr; - for (int i = 0; i < NOFILE; i++) - p->ofile[i] = nullptr; - - if ((p->tf = (struct Trapframe *)PMM::alloc_page()) == 0) - { - p->state = UNUSED; - if (p->all_next) - p->all_next->all_prev = p->all_prev; - if (p->all_prev) - p->all_prev->all_next = p->all_next; - if (p == proc_list_head) - proc_list_head = p->all_next; - Slab::kfree(p); - proc_mem_lock.release(); - return 0; - } - - if ((p->kstack = PMM::alloc_page()) == 0) - { - PMM::free_page(p->tf); - p->state = UNUSED; - if (p->all_next) - p->all_next->all_prev = p->all_prev; - if (p->all_prev) - p->all_prev->all_next = p->all_next; - if (p == proc_list_head) - proc_list_head = p->all_next; - Slab::kfree(p); - - proc_mem_lock.release(); - return 0; - } - memset(p->kstack, 0, PGSIZE); - - memset(&p->context, 0, sizeof(p->context)); - - // Set the context so that the next time this process is scheduled, it starts executing directly from forkret - p->context.ra = (uint64)forkret; - p->context.sp = (uint64)p->kstack + PGSIZE; - return p; -} - -static void freeproc(struct Proc *p) -{ - if (p->tf) - PMM::free_page(p->tf); - p->tf = 0; - if (p->kstack) - PMM::free_page(p->kstack); - p->kstack = 0; - if (p->pagetable) - VM::uvmfree(p->pagetable, p->sz); - for (int i = 0; i < NOFILE; i++) - { - if (p->ofile[i]) - { - FileTable::close(p->ofile[i]); - p->ofile[i] = nullptr; - } - } - if (p->cwd) - { - VFS::iput(p->cwd); - p->cwd = nullptr; - } - - if (p->all_prev) - p->all_prev->all_next = p->all_next; - else - proc_list_head = p->all_next; - - if (p->all_next) - p->all_next->all_prev = p->all_prev; - - Slab::kfree(p); -} - -int fork() -{ - struct Proc *np; - struct Proc *p = myproc(); - - if ((np = allocproc()) == 0) - return -1; - - np->pagetable = VM::uvmcreate(); - np->sz = p->sz; - if (VM::uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) - { - freeproc(np); - proc_mem_lock.release(); - return -1; - } - - - if (VM::mappages(np->pagetable, TRAPFRAME, PGSIZE, (uint64)np->tf, PTE_R | PTE_W) < 0) - { - freeproc(np); - proc_mem_lock.release(); - return -1; - } - - *(np->tf) = *(p->tf); - np->tf->a0 = 0; - strcpy(np->name, p->name); - np->parent = p; - np->priority = p->priority; - - for (int i = 0; i < NOFILE; i++) - { - if (p->ofile[i]) - { - np->ofile[i] = FileTable::dup(p->ofile[i]); - } - } - - if (p->cwd) - { - np->cwd = VFS::idup(p->cwd); - } - - proc_mem_lock.release(); - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - np->state = RUNNABLE; - c->runqueue->enqueue(np); - c->runqueue->lock.release(); - - return np->pid; -} - -namespace ProcManager -{ - - void init() - { - proc_mem_lock.init("proc_mem"); - - proc_list_head = nullptr; - - for (int i = 0; i < NCPU; i++) - { - cpus[i].runqueue = &runqueues[i]; - cpus[i].runqueue->init("runqueue"); - } - } - - void scheduler() - { - struct Proc *p; - struct cpu *c = mycpu(); - c->proc = 0; - Drivers::uart_puts("[Proc] Scheduler started (Per-CPU).\n"); - - for (;;) - { - intr_on(); - - // Get from the local queue - c->runqueue->lock.acquire(); - p = c->runqueue->dequeue(); - c->runqueue->lock.release(); - - // Work Stealing - if (!p) - { - for (int i = 0; i < NCPU; i++) - { - if ((uint64)i == r_tp()) - continue; - - struct cpu *victim = &cpus[i]; - victim->runqueue->lock.acquire(); - if (victim->runqueue->count > 0) - p = victim->runqueue->steal(); - victim->runqueue->lock.release(); - if (p) - break; - } - } - - if (p) - { - p->state = RUNNING; - c->proc = p; - - c->runqueue->lock.acquire(); - swtch(&c->context, &p->context); - c->runqueue->lock.release(); - - c->proc = 0; - } - } - } - - void yield() - { - struct Proc *p = myproc(); - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - if (p && p->state == RUNNING) - { - p->state = RUNNABLE; - if (p->priority < PRIO_MIN) - p->priority++; - c->runqueue->enqueue(p); - swtch(&p->context, &c->context); - } - c->runqueue->lock.release(); - } - - void user_init() - { - struct Proc *p = allocproc(); - if (!p) - return; - - initproc = p; - - p->pagetable = VM::uvmcreate(); - - uint64 initcode_sz = initcode_end - initcode_start; - Drivers::uart_puts("[Proc] initcode size: "); - Drivers::uart_put_int(initcode_sz); - Drivers::uart_puts(" bytes\n"); - - uint64 code_pages = PGROUNDUP(initcode_sz) / PGSIZE; - - for (uint64 i = 0; i < code_pages; i++) - { - void *mem = PMM::alloc_page(); - if (!mem) - { - Drivers::uart_puts("user_init: out of memory for code\n"); - return; - } - memset(mem, 0, PGSIZE); - - uint64 src_offset = i * PGSIZE; - uint64 copy_len = PGSIZE; - if (src_offset + copy_len > initcode_sz) - { - copy_len = initcode_sz - src_offset; - } - memmove(mem, (void *)(initcode_start + src_offset), copy_len); - - if (VM::mappages(p->pagetable, i * PGSIZE, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_X | PTE_U) < 0) - { - Drivers::uart_puts("user_init: mappages code failed\n"); - return; - } - } - - uint64 stack_va = (code_pages + 1) * PGSIZE; - - void *stack = PMM::alloc_page(); - if (!stack) - return; - memset(stack, 0, PGSIZE); - - if (VM::mappages(p->pagetable, stack_va, PGSIZE, (uint64)stack, PTE_R | PTE_W | PTE_U) < 0) - return; - - if (VM::mappages(p->pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) - return; - - p->tf->epc = 0; - p->tf->sp = stack_va + PGSIZE; - p->sz = stack_va + PGSIZE; - strcpy(p->name, "initcode"); - - p->cwd = nullptr; - - proc_mem_lock.release(); - - struct cpu *c = &cpus[0]; - c->runqueue->lock.acquire(); - p->state = RUNNABLE; - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - - Drivers::uart_puts("[Proc] user_init success. PID: 1\n"); - asm volatile("fence.i"); - } - - void sleep(void *chan, Spinlock *lk) - { - struct Proc *p = myproc(); - struct cpu *c = mycpu(); - - if (lk && lk != &proc_mem_lock) - lk->release(); - - c->runqueue->lock.acquire(); - p->chan = chan; - p->state = SLEEPING; - swtch(&p->context, &c->context); - p->chan = 0; - c->runqueue->lock.release(); - - if (lk && lk != &proc_mem_lock) - lk->acquire(); - } - - void wakeup(void *chan) - { - for (struct Proc *p = proc_list_head; p != nullptr; p = p->all_next) - { - if (p->state == SLEEPING && p->chan == chan) - { - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - p->state = RUNNABLE; - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - } - } - } - - void exit(int status) - { - struct Proc *p = myproc(); - if (p == initproc) - while (1) - ; - - wakeup(p->parent); - - proc_mem_lock.acquire(); - - for (struct Proc *pp = proc_list_head; pp != nullptr; pp = pp->all_next) - { - if (pp->parent == p) - { - pp->parent = initproc; - wakeup(initproc); - } - } - - proc_mem_lock.release(); - - p->xstate = status; - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - p->state = ZOMBIE; - swtch(&p->context, &c->context); - while (1) - ; - } - - int wait(uint64 addr) - { - - struct Proc *np; - int havekids, pid; - struct Proc *p = myproc(); - for (;;) - { - havekids = 0; - proc_mem_lock.acquire(); - - for (np = proc_list_head; np != nullptr; np = np->all_next) - { - if (np->parent == p) - { - havekids = 1; - if (np->state == ZOMBIE) - { - - struct cpu *child_cpu = np->cpu; - if (child_cpu) - { - proc_mem_lock.release(); - child_cpu->runqueue->lock.acquire(); - child_cpu->runqueue->lock.release(); - proc_mem_lock.acquire(); - } - - pid = np->pid; - if (addr != 0 && VM::copyout(p->pagetable, addr, (char *)&np->xstate, sizeof(np->xstate)) < 0) - { - proc_mem_lock.release(); - return -1; - } - freeproc(np); - proc_mem_lock.release(); - return pid; - } - } - } - proc_mem_lock.release(); - - if (!havekids || p->state == ZOMBIE) - return -1; - sleep(p, &proc_mem_lock); - } - } - - int kill(int pid) - { - Proc* p; - proc_mem_lock.acquire(); - for (p = proc_list_head; p != nullptr; p = p->all_next) - { - if (p->pid == pid) - { - p->killed = 1; - - if (p->state == SLEEPING) - { - p->state = RUNNABLE; - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - } - - proc_mem_lock.release(); - return 0; - } - } - proc_mem_lock.release(); - return -1; - } - - int growproc(int n) - { - uint64 sz; - struct Proc *p = myproc(); - - sz = p->sz; - - if (n > 0) - { - // Call VM::uvmalloc to allocate memory - // Parameters: page table, old size, new size, permissions (PTE_W) - if ((sz = VM::uvmalloc(p->pagetable, sz, sz + n, PTE_W | PTE_R | PTE_U)) == 0) - { - return -1; - } - } - else if (n < 0) - { - sz = VM::uvmdealloc(p->pagetable, sz, sz + n); - } - - p->sz = sz; - return 0; - } - - void create_kernel_thread(void (*func)(), const char *name) - { - (void)func; - (void)name; - } -} \ No newline at end of file diff --git a/src/kernel/proc/swtch.S b/src/kernel/proc/swtch.S deleted file mode 100644 index d55c03e..0000000 --- a/src/kernel/proc/swtch.S +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.global swtch - -swtch: - 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 diff --git a/src/kernel/sbi.cc b/src/kernel/sbi.cc deleted file mode 100644 index 5ae7edb..0000000 --- a/src/kernel/sbi.cc +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/sbi.h" - -namespace SBI -{ - struct sbiret - { - long error; - long value; - }; - - struct sbiret sbi_call(int ext, int fid, uint64 arg0, uint64 arg1, uint64 arg2) - { - struct sbiret ret; - register uint64 a0 asm("a0") = arg0; - register uint64 a1 asm("a1") = arg1; - register uint64 a2 asm("a2") = arg2; - register uint64 a6 asm("a6") = fid; - register uint64 a7 asm("a7") = ext; - - asm volatile( - "ecall" - : "=r"(a0), "=r"(a1) - : "r"(a0), "r"(a1), "r"(a2), "r"(a6), "r"(a7) - : "memory"); - ret.error = a0; - ret.value = a1; - return ret; - } - - void set_timer(uint64 stime_value) - { - sbi_call(0x54494D45, 0, stime_value, 0, 0); - } - - void sbi_shutdown() - { - sbi_call(0x53525354, 0, 0, 0, 0); - sbi_call(0x08, 0, 0, 0, 0); - while (1) - ; - } - - void sbi_reboot() - { - sbi_call(SBI_EID_SRST, SBI_FID_SRST_RESET, SBI_SRST_TYPE_COLD_REBOOT, SBI_SRST_REASON_NONE, 0); - } -} \ No newline at end of file diff --git a/src/kernel/spinlock.cc b/src/kernel/spinlock.cc deleted file mode 100644 index aadce21..0000000 --- a/src/kernel/spinlock.cc +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/spinlock.h" -#include "kernel/riscv.h" -#include "kernel/cpu.h" -#include "kernel/proc_def.h" -#include "drivers/uart.h" - -extern struct cpu cpus[]; - -namespace ProcManager -{ - void sleep(void *chan, Spinlock *lk); - void wakeup(void *chan); -} - -// Interrupt Depth Management -void push_off(void); -void pop_off(void); - -// // Get the pointer of the current CPU -// // Depends on r_tp() in riscv.h to read the tp register -// struct cpu *mycpu() -// { -// int id = r_tp(); -// return &cpus[id]; -// } - -// Get the current process -// Must be called with interrupts disabled, or read atomically in a safe manner -struct Proc *myproc() -{ - push_off(); - struct cpu *c = mycpu(); - struct Proc *p = c->proc; - pop_off(); - return p; -} - -// Spinlock Implementation - -void Spinlock::init(const char *lock_name) -{ - name = lock_name; - locked = 0; - cpu = nullptr; -} - -void Spinlock::acquire() -{ - push_off(); // Disable interrupts: prevent deadlocks (interrupt handler requests a lock that is already held) - - if (holding()) - { - Drivers::uart_puts("panic: recursive acquire "); - Drivers::uart_puts(name); - Drivers::uart_puts("\n"); - while (1) - ; - } - - // Atomic Exchange - // __ATOMIC_ACQUIRE: Ensures that reads and writes after the acquire are not reordered before the acquire - while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE)) - { - // Busy-wait (Spinning) - // Optimization: Read-only, no writes, reducing cache coherence traffic - while (__atomic_load_n(&locked, __ATOMIC_RELAXED) == 1) - { - asm volatile("nop"); // RISC-V indicates that the CPU is in a spin state - } - } - - // Record holder - cpu = mycpu(); -} - -void Spinlock::release() -{ - if (!holding()) - { - Drivers::uart_puts("panic: release not holding\n"); - while (1) - ; - } - - cpu = nullptr; - - // Atomic Store - // __ATOMIC_RELEASE: Ensures all reads and writes before the Release are completed - __atomic_store_n(&locked, 0, __ATOMIC_RELEASE); - - pop_off(); -} - -// Check if the current CPU holds the lock -bool Spinlock::holding() -{ - // Interrupt checks must be disabled, otherwise a process switch may occur causing CPU fluctuations. - int r; - push_off(); - r = (locked && cpu == mycpu()); - pop_off(); - return r; -} - - -// Mutex Implementation (Sleep Locks) - -void Mutex::init(const char *lock_name) -{ - name = lock_name; - locked = 0; - pid = 0; - lk.init("mutex_internal"); -} - -void Mutex::acquire() -{ - lk.acquire(); // Acquire the internal lock to protect the locked field - - while (locked) - { - // Lock is held, preparing to sleep - // Atomicity contract of sleep(): - // 1. Mark the current process as SLEEPING - // 2. Release lk (so others can operate on locked) - // 3. Switch CPU (context switch) - // 4. After being woken up, reacquire the lk lock and return - ProcManager::sleep(this, &lk); - } - - locked = 1; - pid = myproc()->pid; - - lk.release(); -} - -void Mutex::release() -{ - lk.acquire(); - - locked = 0; - pid = 0; - - // Wake up all processes sleeping on this lock (chan = this) - ProcManager::wakeup(this); - - lk.release(); -} - -bool Mutex::holding() -{ - lk.acquire(); // Must use a lock when reading to prevent race conditions. - bool r = locked && (pid == myproc()->pid); - lk.release(); - return r; -} - -// Interrupt Depth Management - -void push_off(void) -{ - int old = intr_get(); - intr_off(); - - struct cpu *c = mycpu(); - - if (c->noff == 0) - c->intena = old; - - c->noff += 1; -} - -void pop_off(void) -{ - struct cpu *c = mycpu(); - - if (intr_get()) - { - Drivers::uart_puts("panic: pop_off - interruptible\n"); - while (1) - ; - } - if (c->noff < 1) - { - Drivers::uart_puts("panic: pop_off count\n"); - while (1) - ; - } - - c->noff -= 1; - - // An interrupt is only truly enabled when the nesting level returns to zero and the outermost layer allows interrupts. - if (c->noff == 0 && c->intena) - intr_on(); -} \ No newline at end of file diff --git a/src/kernel/syscall.cc b/src/kernel/syscall.cc deleted file mode 100644 index 0979c24..0000000 --- a/src/kernel/syscall.cc +++ /dev/null @@ -1,698 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ -#include "kernel/syscall.h" -#include "common/types.h" -#include "common/fcntl.h" -#include "kernel/proc.h" -#include "kernel/riscv.h" -#include "drivers/uart.h" -#include "drivers/virtio.h" -#include "kernel/timer.h" -#include "kernel/mm.h" -#include "kernel/trap.h" -#include "kernel/slab.h" -#include "kernel/sbi.h" -#include "fs/fat32.h" -#include "fs/file.h" -#include "lib/string.h" -#include "lib/errno.h" - -extern "C++" int fork(); -namespace Exec -{ - int exec(char *path, char **argv); -} - -namespace ProcManager -{ - void exit(int status); - int wait(uint64_t addr); -} - -static uint64_t argraw(int n) -{ - Proc *p = myproc(); - switch (n) - { - case 0: - return p->tf->a0; - case 1: - return p->tf->a1; - case 2: - return p->tf->a2; - case 3: - return p->tf->a3; - case 4: - return p->tf->a4; - case 5: - return p->tf->a5; - default: - return static_cast(-1); - } -} - -static int argint(int n, int *ip) -{ - *ip = static_cast(argraw(n)); - return 0; -} - -static int argstr(int n, char *buf, int max) -{ - uint64 addr = argraw(n); - return VM::copyinstr(myproc()->pagetable, buf, addr, max); -} - -struct KernelArgv -{ - static const int MAX_ARGS = 32; - static const int MAX_ARG_LEN = 128; - char *args[MAX_ARGS]; - - KernelArgv() { memset(args, 0, sizeof(args)); } - ~KernelArgv() - { - for (int i = 0; i < MAX_ARGS; i++) - if (args[i]) - { - Slab::kfree(args[i]); - args[i] = nullptr; - } - } - KernelArgv(const KernelArgv &) = delete; - KernelArgv &operator=(const KernelArgv &) = delete; -}; - -static int fdalloc(struct file *f) -{ - struct Proc *p = myproc(); - for (int i = 0; i < NOFILE; i++) - { - if (p->ofile[i] == 0) - { - p->ofile[i] = f; - return i; - } - } - return -1; -} - -// sys_openat(int dfd, const char *filename, int flags, mode_t mode) -static uint64 sys_openat() -{ - char path[128]; - int dfd, omode, mode_unused; - int fd; - struct file *f; - Inode *ip; - - if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &omode) < 0 || argint(3, &mode_unused) < 0) - return -EINVAL; - - if (omode & O_CREATE) - { - ip = VFS::namei(path); - if (ip == nullptr) - { - char name[128]; - Inode *dp = VFS::nameiparent(path, name); - if (dp) - { - VFS::ilock(dp); - ip = dp->create(name, T_FILE, 0, 0); - VFS::iunlockput(dp); - } - } - } - else - { - ip = VFS::namei(path); - } - - if (ip == nullptr) - return -ENOENT; - - VFS::ilock(ip); - if ((omode & O_TRUNC) && ip->type == T_FILE) - { - ip->truncate(); - } - VFS::iunlock(ip); - - f = FileTable::alloc(); - if (f == nullptr) - { - VFS::iput(ip); - return -ENFILE; - } - - fd = fdalloc(f); - if (fd < 0) - { - f->ref = 0; - f->type = FD_NONE; - VFS::iput(ip); - return -EMFILE; - } - - f->type = FD_INODE; - f->ip = ip; - f->off = 0; - f->readable = !(omode & O_WRONLY); - f->writable = (omode & O_WRONLY) || (omode & O_RDWR); - - return fd; -} - -// sys_mkdirat(int dfd, const char *pathname, mode_t mode) -static uint64 sys_mkdirat() -{ - char path[128]; - int dfd, mode; - - if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0) - return -EINVAL; - - Inode *ip = VFS::namei(path); - if (ip != nullptr) - { - VFS::iput(ip); - return -EEXIST; - } - - char name[128]; - Inode *dp = VFS::nameiparent(path, name); - if (dp == nullptr) - return -ENOENT; - - VFS::ilock(dp); - ip = dp->create(name, T_DIR, 0, 0); - VFS::iunlockput(dp); - - if (ip == nullptr) - return -EIO; - - VFS::iput(ip); - return 0; -} - -static uint64 sys_chdir() -{ - char path[128]; - if (argstr(0, path, 128) < 0) - return -EFAULT; - - Inode *ip = VFS::namei(path); - if (ip == nullptr) - return -ENOENT; - - VFS::ilock(ip); - if (ip->type != T_DIR) - { - VFS::iunlockput(ip); - return -ENOTDIR; - } - VFS::iunlock(ip); - - struct Proc *p = myproc(); - if (p->cwd) - VFS::iput(p->cwd); - p->cwd = ip; - return 0; -} - -static uint64 sys_dup() -{ - int oldfd; - if (argint(0, &oldfd) < 0) - return -EINVAL; - - struct Proc *p = myproc(); - if (oldfd < 0 || oldfd >= NOFILE || p->ofile[oldfd] == nullptr) - return -EBADF; - - struct file *f = FileTable::dup(p->ofile[oldfd]); - int newfd = fdalloc(f); - if (newfd < 0) - { - FileTable::close(f); - return -EMFILE; - } - return newfd; -} - -static uint64 sys_fstat() -{ - int fd; - uint64 stat_addr; - - if (argint(0, &fd) < 0 || argint(1, (int *)&stat_addr) < 0) - return -EINVAL; - - struct Proc *p = myproc(); - if (fd < 0 || fd >= NOFILE || p->ofile[fd] == nullptr) - return -EBADF; - - if (FileTable::stat(p->ofile[fd], stat_addr) < 0) - return -EIO; - - return 0; -} - -// sys_mknodat(int dfd, const char *filename, mode_t mode, dev_t dev) -static uint64 sys_mknodat() -{ - char path[128]; - int dfd, mode, dev; - - if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0 || argint(3, &dev) < 0) - return -EINVAL; - - Inode *ip = VFS::namei(path); - if (ip != nullptr) - { - VFS::iput(ip); - return -EEXIST; - } - - char name[128]; - Inode *dp = VFS::nameiparent(path, name); - if (dp == nullptr) - return -ENOENT; - - // Decode dev_t (Assuming simplified minor/major for now) - int major = (dev >> 20) & 0xFFF; // Just a guess for Linux packing, or pass raw - int minor = dev & 0xFFFFF; - - // For Phase 1 compatibility with init.cc: - // init.cc passes raw major/minor. Shim will pack them. - // We just unpack naively or assume Shim passed us correct structure. - - VFS::ilock(dp); - ip = dp->create(name, T_DEVICE, major, minor); - VFS::iunlockput(dp); - - if (ip == nullptr) - return -EIO; - - VFS::iput(ip); - return 0; -} - -static uint64 sys_close() -{ - int fd; - if (argint(0, &fd) < 0) - return -EINVAL; - - struct Proc *p = myproc(); - if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) - return -EBADF; - - FileTable::close(p->ofile[fd]); - p->ofile[fd] = 0; - return 0; -} - -static uint64 sys_pipe2() -{ - uint64 fdarray; - int flags; - struct file *rf, *wf; - int fd0, fd1; - struct Proc *p = myproc(); - - if (argint(0, (int *)&fdarray) < 0 || argint(1, &flags) < 0) - return -EFAULT; - - if (Pipe::create_pair(&rf, &wf) < 0) - return -ENFILE; - - if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) - { - if (fd0 >= 0) - p->ofile[fd0] = nullptr; - FileTable::close(rf); - FileTable::close(wf); - return -EMFILE; - } - - if (VM::copyout(p->pagetable, fdarray, (char *)&fd0, sizeof(int)) < 0 || - VM::copyout(p->pagetable, fdarray + sizeof(int), (char *)&fd1, sizeof(int)) < 0) - { - p->ofile[fd0] = nullptr; - p->ofile[fd1] = nullptr; - FileTable::close(rf); - FileTable::close(wf); - return -EFAULT; - } - return 0; -} - -static uint64 sys_unlinkat() -{ - char path[128], name[128]; - int dfd, flag; - - if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &flag) < 0) - return -EINVAL; - - Inode *dp = VFS::nameiparent(path, name); - if (!dp) - return -ENOENT; - - VFS::ilock(dp); - int ret = dp->unlink(name); - VFS::iunlockput(dp); - - return (ret < 0) ? -EIO : 0; -} - -// sys_linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) -static uint64 sys_linkat() -{ - // FAT32 does not support hard links. - // Return Error to allow compilation/running to proceed (ln will fail gracefully). - return -EPERM; -} - -static uint64 sys_execve() -{ - char path[128]; - uint64 uargv_ptr; - - if (argstr(0, path, sizeof(path)) < 0) - return -EFAULT; - - uargv_ptr = argraw(1); - // uenvp_ptr = argraw(2); // Ignored - - KernelArgv kargv; - - for (int i = 0; i < KernelArgv::MAX_ARGS; ++i) - { - uint64 uarg_str_ptr; - if (VM::copyin(myproc()->pagetable, (char *)&uarg_str_ptr, uargv_ptr + i * sizeof(uint64), sizeof(uint64)) < 0) - { - return -EFAULT; - } - - if (uarg_str_ptr == 0) - { - kargv.args[i] = nullptr; - break; - } - - kargv.args[i] = (char *)Slab::kmalloc(KernelArgv::MAX_ARG_LEN); - if (kargv.args[i] == nullptr) - { - return -ENOMEM; - } - - if (VM::copyinstr(myproc()->pagetable, kargv.args[i], uarg_str_ptr, KernelArgv::MAX_ARG_LEN) < 0) - { - return -EFAULT; - } - } - return Exec::exec(path, kargv.args); -} - -static uint64 sys_exit() -{ - int n; - if (argint(0, &n) < 0) - return -EINVAL; - ProcManager::exit(n); - return 0; -} - -static uint64 sys_clone() -{ - return static_cast(fork()); -} - -static uint64 sys_wait4() -{ - uint64 status_addr; - - // argraw(0) is pid (ignored) - status_addr = argraw(1); - // argraw(2) is options (ignored) - // argraw(3) is rusage (ignored) - - if (status_addr == static_cast(-1)) - return -EINVAL; - - return static_cast(ProcManager::wait(status_addr)); -} - -static uint64 sys_getpid() -{ - return static_cast(myproc()->pid); -} - -static uint64 sys_putc() -{ - char c = static_cast(argraw(0)); - Drivers::uart_putc(c); - return 0; -} - -static uint64 sys_write() -{ - struct file *f; - int n; - uint64 p; - int fd; - - if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) - return -EINVAL; - - p = argraw(1); - - struct Proc *proc = myproc(); - - if (fd == 1 && proc->ofile[fd] == nullptr) - { - constexpr int MAX_WRITE_BUF = 128; - char buf[MAX_WRITE_BUF]; - int i = 0; - - while (i < n) - { - int len = n - i; - if (len > MAX_WRITE_BUF) - len = MAX_WRITE_BUF; - - if (VM::copyin(proc->pagetable, buf, p + i, static_cast(len)) < 0) - return -EFAULT; - - for (int j = 0; j < len; j++) - { - Drivers::uart_putc(buf[j]); - } - i += len; - } - return static_cast(n); - } - - if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) - return -EBADF; - - return FileTable::write(f, p, n); -} - -static uint64_t sys_read() -{ - struct file *f; - int n; - uint64 p; - int fd; - - if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) - return -EINVAL; - - p = argraw(1); - - struct Proc *proc = myproc(); - if (fd == 0 && proc->ofile[fd] == nullptr) - return Drivers::console_read(p, n); - - if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) - return -EBADF; - - return FileTable::read(f, p, n); -} - -static uint64 sys_brk() -{ - int n; - if (argint(0, &n) < 0) - return -EINVAL; - - uint64 addr = myproc()->sz; - if (ProcManager::growproc(n) < 0) - return -ENOMEM; - - return addr; -} - -static uint64 sys_kill() -{ - int pid; - if (argint(0, &pid) < 0) - return -EINVAL; - return ProcManager::kill(pid); -} - -static uint64 sys_sleep() -{ - int n; - uint64 ticks0; - - if (argint(0, &n) < 0) - return -EINVAL; - - Spinlock *lk = Timer::get_lock(); - lk->acquire(); - ticks0 = Timer::get_ticks(); - while (Timer::get_ticks() - ticks0 < (uint64)n) - { - if (myproc()->killed) - { - lk->release(); - return -EINTR; - } - ProcManager::sleep(Timer::get_tick_chan(), lk); - } - lk->release(); - return 0; -} - -static uint64 sys_disk_test() -{ - // VirtIO::test_rw(); - // VirtIO::test_bio(); - fat32_test(); - return 0; -} - -static uint64 sys_lseek() -{ - int fd, offset, whence; - if (argint(0, &fd) < 0 || argint(1, &offset) < 0 || argint(2, &whence) < 0) - return -EINVAL; - - struct Proc *p = myproc(); - if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) - return -EBADF; - - return FileTable::lseek(p->ofile[fd], offset, whence); -} - -static uint64 sys_reboot() -{ - SBI::sbi_shutdown(); - return 0; -} - -void syscall() -{ - Proc *p = myproc(); - int num = p->tf->a7; - uint64 ret = static_cast(-ENOSYS); - - switch (num) - { - case SYS_mknodat: - ret = sys_mknodat(); - break; - case SYS_mkdirat: - ret = sys_mkdirat(); - break; - case SYS_unlinkat: - ret = sys_unlinkat(); - break; - case SYS_linkat: - ret = sys_linkat(); - break; - case SYS_chdir: - ret = sys_chdir(); - break; - case SYS_openat: - ret = sys_openat(); - break; - case SYS_dup: - ret = sys_dup(); - break; - case SYS_close: - ret = sys_close(); - break; - case SYS_pipe2: - ret = sys_pipe2(); - break; - case SYS_lseek: - ret = sys_lseek(); - break; - case SYS_read: - ret = sys_read(); - break; - case SYS_write: - ret = sys_write(); - break; - case SYS_fstat: - ret = sys_fstat(); - break; - case SYS_exit: - ret = sys_exit(); - break; - case SYS_exit_group: - ret = sys_exit(); - break; - case SYS_kill: - ret = sys_kill(); - break; - case SYS_getpid: - ret = sys_getpid(); - break; - case SYS_brk: - ret = sys_brk(); - break; - case SYS_clone: - ret = sys_clone(); - break; - case SYS_execve: - ret = sys_execve(); - if (ret == 0) - return; - break; - case SYS_wait4: - ret = sys_wait4(); - break; - - // Custom / Debug - case SYS_reboot: - ret = sys_reboot(); - break; - case SYS_putc: - ret = sys_putc(); - break; - case SYS_disk_test: - ret = sys_disk_test(); - break; - case SYS_nanosleep: - ret = sys_sleep(); - break; - - default: - Drivers::uart_puts("Unknown Syscall ID: "); - Drivers::print_hex(num); - Drivers::uart_puts("\n"); - ret = -ENOSYS; - break; - } - - p->tf->a0 = ret; -} \ No newline at end of file diff --git a/src/kernel/timer.cc b/src/kernel/timer.cc deleted file mode 100644 index fc02f27..0000000 --- a/src/kernel/timer.cc +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/config.h" -#include "kernel/spinlock.h" -#include "kernel/proc.h" -#include "kernel/sbi.h" - -#define INTERVAL 1000000 - -namespace Timer -{ - uint64 ticks; - Spinlock tickslock; - - uint64 get_time() - { - uint64 time; - asm volatile("rdtime %0" : "=r"(time)); - return time; - } - - void set_next_trigger() - { - SBI::set_timer(get_time() + INTERVAL); - } - - void init() - { - set_next_trigger(); - - uint64 sie; - asm volatile("csrr %0, sie" : "=r"(sie)); - sie |= (1L << 5); - asm volatile("csrw sie, %0" : : "r"(sie)); - } - - void tick() - { - tickslock.acquire(); - ticks++; - ProcManager::wakeup(&ticks); - tickslock.release(); - set_next_trigger(); - } - - uint64 get_ticks() - { - tickslock.acquire(); - uint64 t = ticks; - tickslock.release(); - return t; - } - - Spinlock *get_lock() - { - return &tickslock; - } - - void *get_tick_chan() - { - return &ticks; - } -} \ No newline at end of file diff --git a/src/kernel/trap/handler.cc b/src/kernel/trap/handler.cc deleted file mode 100644 index b7bd5f0..0000000 --- a/src/kernel/trap/handler.cc +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/trap.h" -#include "kernel/riscv.h" -#include "kernel/proc.h" -#include "drivers/uart.h" -#include "kernel/syscall.h" -#include "kernel/mm.h" -#include "kernel/timer.h" -#include "drivers/plic.h" -#include "drivers/virtio.h" -#include "kernel/fdt.h" -#include "lib/string.h" - - -extern "C" void kernelvec(); - -// Handle Function - -void panic(const char *s) -{ - Drivers::uart_puts("\n\n!!! KERNEL PANIC !!!\n"); - Drivers::uart_puts(s); - Drivers::uart_puts("\nSystem Halted.\n"); - while (1) - ; -} - -// Trap Implementation - -namespace Trap -{ - void init() - { - Drivers::uart_puts("[Trap] Global init done.\n"); - } - - // Each CPU core needs to call this function when starting to set its own stvec - void inithart() - { - w_stvec((uint64)kernelvec); - - // Ensure that the S-Mode interrupt enable bit (SIE) is cleared during initialization - // Interrupt enabling will be done by the scheduler when the first process runs - w_sstatus(r_sstatus() & ~SSTATUS_SIE); - - // Enable the external interrupt, clock interrupt, and software interrupt bits in S-Mode (in SIE register) - uint64 sie = r_sie(); - sie |= SIE_SEIE | SIE_STIE | SIE_SSIE; - w_sie(sie); - - Drivers::uart_puts("[Trap] Hart initialized (stvec set).\n"); - } - - // Device Interrupt Check - int devintr() - { - uint64 scause = r_scause(); - - bool is_interrupt = (scause & 0x8000000000000000L) != 0; - uint64 code = scause & 0xff; - - if (is_interrupt && code == 9) - { - // Supervisor External Interrupt (IRQ 9) - int irq = PLIC::claim(); - - if (irq == (int)g_devices.uart.irq) - { - Drivers::uart_intr(); - } - else - { - // Check if it's a VirtIO IRQ - bool handled = false; - for (int i = 0; i < g_devices.virtio_count; i++) - { - if (irq == (int)g_devices.virtio[i].irq) - { - VirtIO::intr(); - handled = true; - break; - } - } - } - - if (irq > 0) - { - PLIC::complete(irq); - } - - return 1; - } - else if (is_interrupt && code == 5) - { - // Supervisor Timer Interrupt (IRQ 5) - // Timer::set_next_trigger(); - Timer::tick(); - // w_sip(r_sip() & ~2); - return 2; - } - - return 0; - } -} - -// Exception Handling Entry - -extern "C" void kerneltrap(struct Trapframe *tf) -{ - uint64 sepc = r_sepc(); - uint64 sstatus = r_sstatus(); - uint64 scause = r_scause(); - - if ((sstatus & SSTATUS_SPP) == 0) - panic("kerneltrap: not from supervisor mode"); - - if (intr_get() != 0) - panic("kerneltrap: interrupts enabled"); - - if (Trap::devintr() == 0) - { - Drivers::uart_puts("\n--- KERNEL EXCEPTION ---\n"); - Drivers::uart_puts("scause: "); - Drivers::print_hex(scause); - Drivers::uart_puts("\n"); - Drivers::uart_puts("sepc: "); - Drivers::print_hex(sepc); - Drivers::uart_puts("\n"); - Drivers::uart_puts("stval: "); - Drivers::print_hex(r_stval()); - Drivers::uart_puts("\n"); - panic("kerneltrap exception"); - } - - w_sepc(sepc); - w_sstatus(sstatus); -} - -extern "C" void usertrap() -{ - // Drivers::uart_puts("[Debug] Entered usertrap!\n"); - int which_dev = 0; - - if ((r_sstatus() & SSTATUS_SPP) != 0) - panic("usertrap: not from user mode"); - - w_stvec((uint64)kernelvec); - - struct Proc *p = myproc(); - if (!p) - panic("usertrap: no process"); - - p->tf->epc = r_sepc(); - - uint64 scause = r_scause(); - uint64 stval = r_stval(); - - if (scause == 8) // Syscall - { - if (p->state == ZOMBIE) - ProcManager::exit(-1); - p->tf->epc += 4; - intr_on(); - syscall(); - intr_off(); - } - else if (scause == 15) // Store Page Fault - { - if (VM::handle_cow_fault(p->pagetable, PGROUNDDOWN(stval)) != 0) - { - uint64 pa = VM::walkaddr(p->pagetable, stval); - - if (pa == 0) - { - Drivers::uart_puts("usertrap: Segmentation Fault (unmapped) at "); - } - else - { - Drivers::uart_puts("usertrap: Protection Fault (or OOM) at "); - } - - Drivers::print_hex(stval); - Drivers::uart_puts("\n"); - p->state = ZOMBIE; - ProcManager::exit(-1); - } - } - else if ((which_dev = Trap::devintr()) != 0) - { - if (which_dev == 2) - { - ProcManager::yield(); - } - } - else - { - Drivers::uart_puts("\nusertrap(): unexpected scause "); - Drivers::print_hex(scause); - Drivers::uart_puts(" pid="); - Drivers::uart_put_int(p->pid); - Drivers::uart_puts(" sepc="); - Drivers::print_hex(p->tf->epc); - Drivers::uart_puts(" stval="); - Drivers::print_hex(stval); - Drivers::uart_puts("\n"); - p->state = ZOMBIE; - ProcManager::exit(-1); - } - - if (p->state == ZOMBIE) - ProcManager::exit(-1); - - usertrapret(); -} \ No newline at end of file diff --git a/src/kernel/trap/trampoline.S b/src/kernel/trap/trampoline.S deleted file mode 100644 index 57d6f25..0000000 --- a/src/kernel/trap/trampoline.S +++ /dev/null @@ -1,101 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.section .text -.globl trampoline -.globl uservec -.globl userret - -.align 12 -trampoline: -.align 4 -uservec: - - csrrw a0, sscratch, a0 - - sd ra, 40(a0) - sd sp, 48(a0) - sd gp, 56(a0) - sd tp, 64(a0) - sd t0, 72(a0) - sd t1, 80(a0) - sd t2, 88(a0) - sd s0, 96(a0) - sd s1, 104(a0) - sd a1, 120(a0) - sd a2, 128(a0) - sd a3, 136(a0) - sd a4, 144(a0) - sd a5, 152(a0) - sd a6, 160(a0) - sd a7, 168(a0) - sd s2, 176(a0) - sd s3, 184(a0) - sd s4, 192(a0) - sd s5, 200(a0) - sd s6, 208(a0) - sd s7, 216(a0) - sd s8, 224(a0) - sd s9, 232(a0) - sd s10, 240(a0) - sd s11, 248(a0) - sd t3, 256(a0) - sd t4, 264(a0) - sd t5, 272(a0) - sd t6, 280(a0) - - csrr t0, sscratch - sd t0, 112(a0) - - ld sp, 8(a0) - ld tp, 32(a0) - ld t0, 16(a0) - ld t1, 0(a0) - - csrw satp, t1 - sfence.vma zero, zero - - jr t0 - -.align 4 -userret: - - csrw satp, a1 - sfence.vma zero, zero - - csrw sscratch, a0 - - ld ra, 40(a0) - ld sp, 48(a0) - ld gp, 56(a0) - ld tp, 64(a0) - ld t0, 72(a0) - ld t1, 80(a0) - ld t2, 88(a0) - ld s0, 96(a0) - ld s1, 104(a0) - ld a1, 120(a0) - ld a2, 128(a0) - ld a3, 136(a0) - ld a4, 144(a0) - ld a5, 152(a0) - ld a6, 160(a0) - ld a7, 168(a0) - ld s2, 176(a0) - ld s3, 184(a0) - ld s4, 192(a0) - ld s5, 200(a0) - ld s6, 208(a0) - ld s7, 216(a0) - ld s8, 224(a0) - ld s9, 232(a0) - ld s10, 240(a0) - ld s11, 248(a0) - ld t3, 256(a0) - ld t4, 264(a0) - ld t5, 272(a0) - ld t6, 280(a0) - - ld a0, 112(a0) - - sret \ No newline at end of file diff --git a/src/kernel/trap/trap.S b/src/kernel/trap/trap.S deleted file mode 100644 index 8bce53d..0000000 --- a/src/kernel/trap/trap.S +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.section .text -.global kernelvec -.global uservec -.global userret -.align 12 - -kernelvec: - addi sp, sp, -288 - - sd ra, 40(sp) - sd sp, 48(sp) - sd gp, 56(sp) - sd tp, 64(sp) - sd t0, 72(sp) - sd t1, 80(sp) - sd t2, 88(sp) - sd s0, 96(sp) - sd s1, 104(sp) - sd a0, 112(sp) - sd a1, 120(sp) - sd a2, 128(sp) - sd a3, 136(sp) - sd a4, 144(sp) - sd a5, 152(sp) - sd a6, 160(sp) - sd a7, 168(sp) - sd s2, 176(sp) - sd s3, 184(sp) - sd s4, 192(sp) - sd s5, 200(sp) - sd s6, 208(sp) - sd s7, 216(sp) - sd s8, 224(sp) - sd s9, 232(sp) - sd s10, 240(sp) - sd s11, 248(sp) - sd t3, 256(sp) - sd t4, 264(sp) - sd t5, 272(sp) - sd t6, 280(sp) - - csrr t0, sepc - csrr t1, sstatus - sd t0, 24(sp) # Trapframe.epc - sd t1, 32(sp) # Trapframe.kernel_hartid - - mv a0, sp - call kerneltrap - - ld t0, 24(sp) - ld t1, 32(sp) - csrw sepc, t0 - csrw sstatus, t1 - - ld ra, 40(sp) - ld gp, 56(sp) - ld tp, 64(sp) - ld t0, 72(sp) - ld t1, 80(sp) - ld t2, 88(sp) - ld s0, 96(sp) - ld s1, 104(sp) - ld a0, 112(sp) - ld a1, 120(sp) - ld a2, 128(sp) - ld a3, 136(sp) - ld a4, 144(sp) - ld a5, 152(sp) - ld a6, 160(sp) - ld a7, 168(sp) - ld s2, 176(sp) - ld s3, 184(sp) - ld s4, 192(sp) - ld s5, 200(sp) - ld s6, 208(sp) - ld s7, 216(sp) - ld s8, 224(sp) - ld s9, 232(sp) - ld s10, 240(sp) - ld s11, 248(sp) - ld t3, 256(sp) - ld t4, 264(sp) - ld t5, 272(sp) - ld t6, 280(sp) - - addi sp, sp, 288 - sret \ No newline at end of file diff --git a/src/lib/string.cc b/src/lib/string.cc deleted file mode 100644 index e8eccb3..0000000 --- a/src/lib/string.cc +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "lib/string.h" - -void *memset(void *dst, int c, uint n) -{ - char *cdst = (char *)dst; - for (uint i = 0; i < n; i++) - cdst[i] = c; - return dst; -} - -int memcmp(const void *v1, const void *v2, uint n) -{ - const uchar *s1 = (const uchar *)v1; - const uchar *s2 = (const uchar *)v2; - while (n-- > 0) - { - if (*s1 != *s2) - return *s1 - *s2; - s1++, s2++; - } - return 0; -} - -void *memmove(void *dst, const void *src, uint n) -{ - const char *s = (const char *)src; - char *d = (char *)dst; - if (s < d && s + n > d) - { - s += n; - d += n; - while (n-- > 0) - *--d = *--s; - } - else - { - while (n-- > 0) - *d++ = *s++; - } - return dst; -} - -void *memcpy(void *dst, const void *src, uint n) -{ - return memmove(dst, src, n); -} - -void strcpy(char *dst, const char *src) -{ - while (*src) - *dst++ = *src++; - *dst = 0; -} - -int strlen(const char *s) -{ - int n = 0; - while (s[n]) - n++; - return n; -} - -int strcmp(const char *s1, const char *s2) -{ - while (*s1 && (*s1 == *s2)) - { - s1++; - s2++; - } - return *(const unsigned char *)s1 - *(const unsigned char *)s2; -} - -int strcasecmp(const char *s1, const char *s2) -{ - while (*s1) - { - if(*s1 == *s2 || *s1 + 32 == *s2 || *s1 == *s2 + 32){ - s1++; - s2++; - } - else break; - } - return *(const unsigned char *)s1 - *(const unsigned char *)s2; -} \ No newline at end of file diff --git a/src/ulib/assert.c b/src/ulib/assert.c deleted file mode 100644 index c60643a..0000000 --- a/src/ulib/assert.c +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "stdio.h" -#include "stdlib.h" - -void __assert_fail(const char *expr, const char *file, int line, const char *func) -{ - fprintf(stderr, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); - abort(); -} \ No newline at end of file diff --git a/src/ulib/cxx.cc b/src/ulib/cxx.cc deleted file mode 100644 index d8f510d..0000000 --- a/src/ulib/cxx.cc +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdlib.h" - -void *operator new(unsigned long n) -{ - return malloc(n); -} - -void *operator new[](unsigned long n) -{ - return malloc(n); -} - -void operator delete(void *p) -{ - free(p); -} - -void operator delete[](void *p) -{ - free(p); -} - -void operator delete(void *p, unsigned long) -{ - free(p); -} - -void operator delete[](void *p, unsigned long) -{ - free(p); -} \ No newline at end of file diff --git a/src/ulib/iostream.cpp b/src/ulib/iostream.cpp deleted file mode 100644 index 4d721c1..0000000 --- a/src/ulib/iostream.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/iostream" - -namespace std -{ - istream cin(stdin); - ostream cout(stdout); - ostream cerr(stderr); -} // namespace std diff --git a/src/ulib/stdio.c b/src/ulib/stdio.c deleted file mode 100644 index e81f69a..0000000 --- a/src/ulib/stdio.c +++ /dev/null @@ -1,1056 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/string.h" -#include "ulib/stdlib.h" - -/* Include STB Sprintf Implementation */ -#define STB_SPRINTF_IMPLEMENTATION -#include "ulib/stb_sprintf.h" - -const int STDIN_FD = 0; -const int STDOUT_FD = 1; -const int STDERR_FD = 2; - - - -extern int open(const char *path, int flags); -extern int close(int fd); -extern int read(int fd, void *buf, uint64 count); -extern int write(int fd, const void *buf, uint64 count); -extern int lseek(int fd, int offset, int whence); - -static int isspace(int c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; -} - -/* Core Formatting Implementation */ - -static int _ul_to_str_invert(char *buf, uint64 val, int base, int uppercase) -{ - int i = 0; - const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; - if (val == 0) - { - buf[i++] = '0'; - return i; - } - while (val != 0) - { - buf[i++] = digits[val % base]; - val /= base; - } - return i; -} - -typedef struct -{ - char *buf; - uint64 limit; - uint64 written; - FILE *stream; -} _OutContext; - -static void _out_char(_OutContext *ctx, char c) -{ - if (ctx->stream) - { - fputc(c, ctx->stream); - ctx->written++; - } - else - { - if (ctx->limit > 0 && ctx->written < ctx->limit - 1) - { - ctx->buf[ctx->written] = c; - } - ctx->written++; - } -} - -static int _vprintf_core(_OutContext *ctx, const char *format, va_list ap) -{ - const char *p = format; - char num_buf[32]; - - while (*p) - { - if (*p != '%') - { - _out_char(ctx, *p++); - continue; - } - p++; - - int flag_left = 0, flag_zero = 0, flag_plus = 0, flag_space = 0, flag_hash = 0; - while (1) - { - if (*p == '-') - flag_left = 1; - else if (*p == '0') - flag_zero = 1; - else if (*p == '+') - flag_plus = 1; - else if (*p == ' ') - flag_space = 1; - else if (*p == '#') - flag_hash = 1; - else - break; - p++; - } - - int width = 0; - if (*p == '*') - { - width = va_arg(ap, int); - if (width < 0) - { - flag_left = 1; - width = -width; - } - p++; - } - else - { - while (*p >= '0' && *p <= '9') - { - width = width * 10 + (*p - '0'); - p++; - } - } - - int precision = -1; - if (*p == '.') - { - p++; - if (*p == '*') - { - precision = va_arg(ap, int); - p++; - } - else - { - precision = 0; - while (*p >= '0' && *p <= '9') - { - precision = precision * 10 + (*p - '0'); - p++; - } - } - } - - int len_mod = 0; - if (*p == 'l') - { - len_mod = 1; - p++; - if (*p == 'l') - { - len_mod = 2; - p++; - } - } - else if (*p == 'z') - { - len_mod = 2; - p++; - } - - char spec = *p++; - if (!spec) - break; - - if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x' || spec == 'X' || spec == 'p') - { - uint64 val; - int is_signed = (spec == 'd' || spec == 'i'); - int base = (spec == 'x' || spec == 'X' || spec == 'p') ? 16 : 10; - - if (spec == 'p') - val = (uint64)va_arg(ap, void *); - else if (len_mod == 2) - val = is_signed ? (uint64)va_arg(ap, int64) : va_arg(ap, uint64); - else if (len_mod == 1) - val = is_signed ? (uint64)va_arg(ap, long) : (uint64)va_arg(ap, unsigned long); - else - val = is_signed ? (uint64)((int64)va_arg(ap, int)) : (uint64)va_arg(ap, uint32); - - char sign_char = 0; - if (is_signed) - { - int64 sval = (int64)val; - if (sval < 0) - { - sign_char = '-'; - val = (uint64)(-sval); - } - else if (flag_plus) - sign_char = '+'; - else if (flag_space) - sign_char = ' '; - } - - int digit_len = _ul_to_str_invert(num_buf, val, base, (spec == 'X')); - if (precision >= 0) - flag_zero = 0; - int prec_zeros = (precision > digit_len) ? precision - digit_len : 0; - if (precision == 0 && val == 0) - digit_len = 0; - - int prefix_len = sign_char ? 1 : 0; - char *prefix_str = NULL; - if (spec == 'p' || (flag_hash && val != 0 && (spec == 'x' || spec == 'X'))) - { - prefix_str = (spec == 'X') ? "0X" : "0x"; - prefix_len += 2; - } - - int padding = (width > prefix_len + prec_zeros + digit_len) ? width - (prefix_len + prec_zeros + digit_len) : 0; - - if (!flag_left && !flag_zero) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - if (sign_char) - _out_char(ctx, sign_char); - if (prefix_str) - { - _out_char(ctx, prefix_str[0]); - _out_char(ctx, prefix_str[1]); - } - int total_zeros = prec_zeros + ((flag_zero && !flag_left) ? padding : 0); - for (int i = 0; i < total_zeros; i++) - _out_char(ctx, '0'); - for (int i = digit_len - 1; i >= 0; i--) - _out_char(ctx, num_buf[i]); - if (flag_left) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - } - else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') - { - char fmt_tmp[32]; - int f_idx = 0; - fmt_tmp[f_idx++] = '%'; - if (flag_left) - fmt_tmp[f_idx++] = '-'; - if (flag_zero) - fmt_tmp[f_idx++] = '0'; - if (flag_plus) - fmt_tmp[f_idx++] = '+'; - if (flag_space) - fmt_tmp[f_idx++] = ' '; - if (flag_hash) - fmt_tmp[f_idx++] = '#'; - if (width > 0) - f_idx += sprintf(&fmt_tmp[f_idx], "%d", width); - if (precision >= 0) - f_idx += sprintf(&fmt_tmp[f_idx], ".%d", precision); - fmt_tmp[f_idx++] = spec; - fmt_tmp[f_idx] = '\0'; - double f_val = va_arg(ap, double); - char f_buf[128]; - stbsp_snprintf(f_buf, sizeof(f_buf), fmt_tmp, f_val); - for (int i = 0; f_buf[i] != '\0'; i++) - _out_char(ctx, f_buf[i]); - } - else if (spec == 's') - { - const char *s = va_arg(ap, char *); - if (!s) - s = "(null)"; - int len = 0; - const char *tmp = s; - while (*tmp && (precision < 0 || len < precision)) - { - tmp++; - len++; - } - int padding = (width > len) ? width - len : 0; - if (!flag_left) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - for (int i = 0; i < len; i++) - _out_char(ctx, s[i]); - if (flag_left) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - } - else if (spec == 'c') - { - char c = (char)va_arg(ap, int); - int padding = (width > 1) ? width - 1 : 0; - if (!flag_left) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - _out_char(ctx, c); - if (flag_left) - for (int i = 0; i < padding; i++) - _out_char(ctx, ' '); - } - else - { - _out_char(ctx, '%'); - if (spec != '%') - _out_char(ctx, spec); - } - } - return (int)ctx->written; -} - -int vsnprintf(char *str, uint64 size, const char *format, va_list ap) -{ - _OutContext ctx; - ctx.buf = str; - ctx.limit = size; - ctx.written = 0; - ctx.stream = NULL; - _vprintf_core(&ctx, format, ap); - if (size > 0) - { - if (ctx.written < size) - str[ctx.written] = '\0'; - else - str[size - 1] = '\0'; - } - return (int)ctx.written; -} -int snprintf(char *str, uint64 size, const char *format, ...) -{ - va_list ap; - va_start(ap, format); - int ret = vsnprintf(str, size, format, ap); - va_end(ap); - return ret; -} -int sprintf(char *str, const char *format, ...) -{ - va_list ap; - va_start(ap, format); - int ret = vsnprintf(str, (uint64)-1, format, ap); - va_end(ap); - return ret; -} -int printf(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - _OutContext ctx; - ctx.buf = NULL; - ctx.limit = 0; - ctx.written = 0; - ctx.stream = stdout; - _vprintf_core(&ctx, format, ap); - va_end(ap); - return (int)ctx.written; -} - -/* Core Function Implementation */ - -typedef struct -{ - const char *buf; - int fd; - uint64 pos; - int ungotten; -} _InContext; - -static int _in_char(_InContext *ctx) -{ - if (ctx->ungotten >= 0) - { - int c = ctx->ungotten; - ctx->ungotten = -1; - return c; - } - if (ctx->fd == -1) - { - if (ctx->buf[ctx->pos] == '\0') - return -1; - return (unsigned char)ctx->buf[ctx->pos++]; - } - char c; - if (read(ctx->fd, &c, 1) == 1) - return (unsigned char)c; - return -1; -} - -static void _unget_char(_InContext *ctx, int c) -{ - if (c != -1) - ctx->ungotten = c; -} -static void _skip_space(_InContext *ctx) -{ - int c; - while ((c = _in_char(ctx)) != -1) - { - if (!isspace(c)) - { - _unget_char(ctx, c); - break; - } - } -} - -static double _simple_strtod(const char *str, char **endptr) -{ - double val = 0.0; - int sign = 1; - if (*str == '-') - { - sign = -1; - str++; - } - else if (*str == '+') - str++; - while (*str >= '0' && *str <= '9') - { - val = val * 10.0 + (*str - '0'); - str++; - } - if (*str == '.') - { - str++; - double div = 10.0; - while (*str >= '0' && *str <= '9') - { - val += (*str - '0') / div; - div *= 10.0; - str++; - } - } - if (*str == 'e' || *str == 'E') - { - str++; - int esign = 1; - if (*str == '-') - { - esign = -1; - str++; - } - else if (*str == '+') - str++; - int eval = 0; - while (*str >= '0' && *str <= '9') - { - eval = eval * 10 + (*str - '0'); - str++; - } - double p = 1.0; - while (eval--) - p *= 10.0; - if (esign == 1) - val *= p; - else - val /= p; - } - if (endptr) - *endptr = (char *)str; - return sign * val; -} - -static int _vscanf_core(_InContext *ctx, const char *format, va_list ap) -{ - int assigned = 0; - const char *p = format; - while (*p) - { - if (isspace(*p)) - { - _skip_space(ctx); - while (isspace(*p)) - p++; - continue; - } - if (*p != '%') - { - _skip_space(ctx); - int c = _in_char(ctx); - if (c != *p) - { - _unget_char(ctx, c); - return assigned; - } - p++; - continue; - } - p++; - int suppress = 0; - if (*p == '*') - { - suppress = 1; - p++; - } - int width = -1; - while (*p >= '0' && *p <= '9') - { - if (width == -1) - width = 0; - width = width * 10 + (*p - '0'); - p++; - } - int len_mod = 0; - if (*p == 'l') - { - len_mod = 1; - p++; - if (*p == 'l') - { - len_mod = 2; - p++; - } - } - else if (*p == 'h') - { - p++; - if (*p == 'h') - p++; - } - char spec = *p++; - if (!spec) - break; - - if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x') - { - _skip_space(ctx); - char num_buf[64]; - int i = 0, c = _in_char(ctx); - if (c == '-' || c == '+') - { - num_buf[i++] = (char)c; - c = _in_char(ctx); - } - int base = (spec == 'x') ? 16 : (spec == 'i' ? 0 : 10); - while (c != -1 && i < 63) - { - int digit = (c >= '0' && c <= '9'); - int hex = (base == 16 || base == 0) && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); - int x = (c == 'x' || c == 'X'); - if (base == 0 && i == 1 && num_buf[0] == '0' && x) - base = 16; - if (width > 0 && i >= width) - break; - if (digit || hex || x) - { - num_buf[i++] = (char)c; - c = _in_char(ctx); - } - else - break; - } - num_buf[i] = '\0'; - _unget_char(ctx, c); - if (i == 0) - return assigned; - if (!suppress) - { - int64 val = strtoll(num_buf, NULL, base); - if (len_mod == 2) - *va_arg(ap, int64 *) = val; - else if (len_mod == 1) - *va_arg(ap, long *) = (long)val; - else - *va_arg(ap, int *) = (int)val; - assigned++; - } - } - else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') - { - _skip_space(ctx); - char num_buf[64]; - int i = 0, c = _in_char(ctx); - if (c == '-' || c == '+') - { - num_buf[i++] = (char)c; - c = _in_char(ctx); - } - while (c != -1 && i < 63) - { - if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') - { - num_buf[i++] = (char)c; - c = _in_char(ctx); - } - else - break; - } - num_buf[i] = '\0'; - _unget_char(ctx, c); - if (i == 0) - return assigned; - if (!suppress) - { - double val = _simple_strtod(num_buf, NULL); - if (len_mod == 1) - *va_arg(ap, double *) = val; - else - *va_arg(ap, float *) = (float)val; - assigned++; - } - } - else if (spec == 's') - { - _skip_space(ctx); - char *buf = suppress ? NULL : va_arg(ap, char *); - int i = 0, c = _in_char(ctx); - while (c != -1 && !isspace(c)) - { - if (width > 0 && i >= width) - break; - if (!suppress) - buf[i] = (char)c; - i++; - c = _in_char(ctx); - } - if (!suppress) - buf[i] = '\0'; - _unget_char(ctx, c); - if (i > 0 && !suppress) - assigned++; - } - else if (spec == 'c') - { - int count = (width > 0) ? width : 1; - char *buf = suppress ? NULL : va_arg(ap, char *); - for (int i = 0; i < count; i++) - { - int c = _in_char(ctx); - if (c == -1) - return assigned; - if (!suppress) - buf[i] = (char)c; - } - if (!suppress) - assigned++; - } - } - return assigned; -} - -int sscanf(const char *str, const char *format, ...) -{ - va_list ap; - va_start(ap, format); - _InContext ctx; - ctx.buf = str; - ctx.fd = -1; - ctx.pos = 0; - ctx.ungotten = -1; - int ret = _vscanf_core(&ctx, format, ap); - va_end(ap); - return ret; -} -int scanf(const char *format, ...) -{ - va_list ap; - va_start(ap, format); - _InContext ctx; - ctx.buf = NULL; - ctx.fd = STDIN_FD; - ctx.pos = 0; - ctx.ungotten = -1; - int ret = _vscanf_core(&ctx, format, ap); - va_end(ap); - return ret; -} - -/* FILE Stream Implementation */ - -#define O_RDONLY 00 -#define O_WRONLY 01 -#define O_RDWR 02 -#define O_CREAT 0100 -#define O_TRUNC 01000 -#define O_APPEND 02000 - - -static FILE _stdin = {.fd = 0, .buf_mode = 1}; -static FILE _stdout = {.fd = 1, .buf_mode = 1}; -static FILE _stderr = {.fd = 2, .buf_mode = 2}; - -FILE *stdin = &_stdin; -FILE *stdout = &_stdout; -FILE *stderr = &_stderr; - -FILE *fopen(const char *filename, const char *mode) -{ - int flags = 0; - if (strcmp(mode, "r") == 0) - flags = O_RDONLY; - else if (strcmp(mode, "w") == 0) - flags = O_WRONLY | O_CREAT | O_TRUNC; - else if (strcmp(mode, "a") == 0) - flags = O_WRONLY | O_CREAT | O_APPEND; - else if (strcmp(mode, "r+") == 0) - flags = O_RDWR; - else - return NULL; - - int64 fd = open(filename, flags); - if (fd < 0) - return NULL; - - FILE *f = (FILE *)malloc(sizeof(FILE)); - if (!f) - { - close(fd); - return NULL; - } - - memset(f, 0, sizeof(FILE)); - f->fd = (int)fd; - f->flags = flags; - - if (fd == 1) - f->buf_mode = 1; - else if (fd == 2) - f->buf_mode = 2; - else - f->buf_mode = 0; - return f; -} - -int fclose(FILE *stream) -{ - if (!stream) - return -1; - fflush(stream); - if (stream != stdin && stream != stdout && stream != stderr) - { - close(stream->fd); - free(stream); - } - return 0; -} - -int fflush(FILE *stream) -{ - if (!stream) - return EOF; - - if (stream->buf_write_pos > 0) - { - int64 ret = write(stream->fd, stream->buffer, stream->buf_write_pos); - if (ret < stream->buf_write_pos) - { - stream->error = 1; - stream->buf_write_pos = 0; - return EOF; - } - stream->buf_write_pos = 0; - } - return 0; -} - -int fprintf(FILE *stream, const char *format, ...) -{ - if (!stream) - return -1; - va_list ap; - va_start(ap, format); - _OutContext ctx; - ctx.buf = NULL; - ctx.limit = 0; - ctx.written = 0; - ctx.stream = stream; - _vprintf_core(&ctx, format, ap); - va_end(ap); - return (int)ctx.written; -} - -int fscanf(FILE *stream, const char *format, ...) -{ - if (!stream) - return -1; - va_list ap; - va_start(ap, format); - _InContext ctx; - ctx.buf = NULL; - ctx.fd = stream->fd; - ctx.pos = 0; - ctx.ungotten = -1; - int ret = _vscanf_core(&ctx, format, ap); - va_end(ap); - return ret; -} - -int fputc(int c, FILE *stream) -{ - if (!stream) - return EOF; - if (stream->buf_mode == 2) - { - char ch = (char)c; - if (write(stream->fd, &ch, 1) != 1) - { - stream->error = 1; - return EOF; - } - return c; - } - - stream->buffer[stream->buf_write_pos++] = (char)c; - - if (stream->buf_write_pos >= BUFSIZ) - { - fflush(stream); - } - else if (stream->buf_mode == 1 && c == '\n') - { - fflush(stream); - } - - return c; -} - -int fputs(const char *s, FILE *stream) -{ - if (!stream) - return -1; - while (*s) - { - if (fputc(*s++, stream) == EOF) - return EOF; - } - return 0; -} - -int fgetc(FILE *stream) -{ - if (!stream) - return EOF; - - if (stream->buf_write_pos > 0) - { - fflush(stream); - } - - if (stream->buf_read_pos < stream->buf_read_cnt) - { - return (unsigned char)stream->buffer[stream->buf_read_pos++]; - } - - int64 ret = read(stream->fd, stream->buffer, BUFSIZ); - if (ret <= 0) - { - if (ret == 0) - stream->eof = 1; - else - stream->error = 1; - return EOF; - } - - stream->buf_read_cnt = (int)ret; - stream->buf_read_pos = 0; - return (unsigned char)stream->buffer[stream->buf_read_pos++]; -} - -char *fgets(char *str, int n, FILE *stream) -{ - if (!stream || n <= 0) - return NULL; - int i = 0, c; - while (i < n - 1) - { - c = fgetc(stream); - if (c == -1) - { - if (i == 0) - return NULL; - break; - } - str[i++] = (char)c; - if (c == '\n') - break; - } - str[i] = '\0'; - return str; -} - -uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream) -{ - if (!stream || size == 0 || nmemb == 0) - return 0; - - uint64 total_bytes = size * nmemb; - uint64 bytes_read = 0; - char *dst = (char *)ptr; - - if (stream->buf_write_pos > 0) - fflush(stream); - - while (bytes_read < total_bytes) - { - if (stream->buf_read_pos < stream->buf_read_cnt) - { - uint64 available = stream->buf_read_cnt - stream->buf_read_pos; - uint64 remaining = total_bytes - bytes_read; - uint64 to_copy = (available < remaining) ? available : remaining; - - memcpy(dst + bytes_read, stream->buffer + stream->buf_read_pos, to_copy); - - stream->buf_read_pos += to_copy; - bytes_read += to_copy; - } - else - { - uint64 remaining = total_bytes - bytes_read; - - if (remaining >= BUFSIZ) - { - int64 ret = read(stream->fd, dst + bytes_read, remaining); - if (ret <= 0) - { - if (ret == 0) - stream->eof = 1; - else - stream->error = 1; - return bytes_read / size; - } - bytes_read += ret; - } - else - { - stream->buf_read_pos = 0; - stream->buf_read_cnt = 0; - int64 ret = read(stream->fd, stream->buffer, BUFSIZ); - - if (ret <= 0) - { - if (ret == 0) - stream->eof = 1; - else - stream->error = 1; - return bytes_read / size; - } - stream->buf_read_cnt = (int)ret; - } - } - } - return bytes_read / size; -} - -uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream) -{ - if (!stream || !ptr || size == 0 || nmemb == 0) - return 0; - uint64 total_bytes = size * nmemb; - const char *src = (const char *)ptr; - uint64 bytes_written = 0; - - if (stream->buf_mode == 2) - { - while (bytes_written < total_bytes) - { - int64 ret = write(stream->fd, src + bytes_written, total_bytes - bytes_written); - if (ret <= 0) - { - stream->error = 1; - break; - } - bytes_written += ret; - } - return bytes_written / size; - } - - while (bytes_written < total_bytes) - { - uint64 space_left = BUFSIZ - stream->buf_write_pos; - uint64 remaining = total_bytes - bytes_written; - - if (remaining <= space_left) - { - memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, remaining); - stream->buf_write_pos += remaining; - bytes_written += remaining; - if (stream->buf_write_pos == BUFSIZ) - fflush(stream); - } - else - { - memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, space_left); - stream->buf_write_pos += space_left; - bytes_written += space_left; - fflush(stream); - } - } - - return bytes_written / size; -} - -int fseek(FILE *stream, long offset, int whence) -{ - if (!stream) - return -1; - - if (stream->buf_write_pos > 0) - { - fflush(stream); - } - stream->buf_read_pos = 0; - stream->buf_read_cnt = 0; - stream->eof = 0; - - if (lseek(stream->fd, offset, whence) < 0) - { - stream->error = 1; - return -1; - } - return 0; -} -long ftell(FILE *stream) -{ - if (!stream) - return -1L; - int64 ret = lseek(stream->fd, 0, SEEK_CUR); - if (ret < 0) - { - stream->error = 1; - return -1L; - } - return (long)ret; -} -void rewind(FILE *stream) -{ - if (stream) - { - fseek(stream, 0, SEEK_SET); - stream->error = 0; - } -} - -int feof(FILE *stream) -{ - return (stream) ? stream->eof : 1; -} -int ferror(FILE *stream) -{ - return (stream) ? stream->error : 1; -} -void clearerr(FILE *stream) -{ - if (stream) - { - stream->error = 0; - stream->eof = 0; - } -} - -int ungetc(int c, FILE *stream) -{ - if (!stream || c == EOF) - return EOF; - - if (stream->buf_read_pos > 0) - { - stream->buf_read_pos--; - stream->buffer[stream->buf_read_pos] = (char)c; - stream->eof = 0; - return c; - } - return EOF; -} \ No newline at end of file diff --git a/src/ulib/stdlib.c b/src/ulib/stdlib.c deleted file mode 100644 index 8b22b6f..0000000 --- a/src/ulib/stdlib.c +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "ulib/ctypes.h" - -extern int sys_exit(int status); -extern void *sbrk(long increment); - -#undef errno -int errno = 0; - -#define ATEXIT_MAX 32 -static void (*__atexit_funcs[ATEXIT_MAX])(void); -static int __atexit_func_count = 0; - -int atexit(void (*function)(void)) -{ - if (__atexit_func_count >= ATEXIT_MAX) - return -1; - __atexit_funcs[__atexit_func_count++] = function; - return 0; -} - -int exit(int status) -{ - while (__atexit_func_count > 0) - { - (__atexit_funcs[--__atexit_func_count])(); - } - sys_exit(status); - - while (1) - ; - return 0; -} - -void abort(void) -{ - exit(127); -} - -char *getenv(const char *name) -{ - /* None */ - return NULL; -} - -int system(const char *command) -{ - /* None */ - return -1; -} - -/* Memory Management */ -#define ALIGNMENT 16 -#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1)) - -typedef struct header -{ - size_t size; - struct header *next; -} header_t; - -static header_t base; -static header_t *freep = NULL; - - -#define LOCK() -#define UNLOCK() - -void free(void *ptr) -{ - header_t *bp, *p; - - if (!ptr) - return; - - - bp = (header_t *)ptr - 1; - - LOCK(); - - - for (p = freep; !(bp > p && bp < p->next); p = p->next) - { - if (p >= p->next && (bp > p || bp < p->next)) - break; - } - - if ((char *)bp + bp->size == (char *)p->next) - { - bp->size += p->next->size; - bp->next = p->next->next; - } - else - { - bp->next = p->next; - } - - if ((char *)p + p->size == (char *)bp) - { - p->size += bp->size; - p->next = bp->next; - } - else - { - p->next = bp; - } - - freep = p; - UNLOCK(); -} - -static header_t *morecore(size_t nu) -{ - char *cp; - header_t *up; - - if (nu < 4096) - nu = 4096; - - nu = ALIGN(nu); - - cp = (char *)sbrk(nu); - if (cp == (void *)-1) - return NULL; - - up = (header_t *)cp; - up->size = nu; - - free((void *)(up + 1)); - return freep; -} - -void *malloc(size_t size) -{ - header_t *p, *prevp; - - if (size == 0) - return NULL; - - size = ALIGN(size + sizeof(header_t)); - - LOCK(); - - if ((prevp = freep) == NULL) - { - base.next = freep = prevp = &base; - base.size = 0; - } - - for (p = prevp->next;; prevp = p, p = p->next) - { - if (p->size >= size) - { - if (p->size == size) - { - prevp->next = p->next; - } - else - { - p->size -= size; - - p = (header_t *)((char *)p + p->size); - - p->size = size; - } - freep = prevp; - UNLOCK(); - return (void *)(p + 1); - } - - if (p == freep) - { - if ((p = morecore(size)) == NULL) - { - UNLOCK(); - errno = ENOMEM; - return NULL; - } - } - } -} - -void *calloc(size_t nmemb, size_t size) -{ - size_t total; - if (__builtin_mul_overflow(nmemb, size, &total)) - { - errno = ENOMEM; - return NULL; - } - void *p = malloc(total); - if (p) - memset(p, 0, total); - return p; -} - -void *realloc(void *ptr, size_t size) -{ - if (!ptr) - return malloc(size); - if (size == 0) - { - free(ptr); - return NULL; - } - - header_t *bp = (header_t *)ptr - 1; - size_t new_size = ALIGN(size + sizeof(header_t)); - - if (bp->size >= new_size) - return ptr; - - void *new_ptr = malloc(size); - if (!new_ptr) - return NULL; - - memcpy(new_ptr, ptr, bp->size - sizeof(header_t)); - free(ptr); - return new_ptr; -} - -/* String Conversion*/ - -unsigned long long strtoull(const char *nptr, char **endptr, int base) -{ - const char *s = nptr; - unsigned long long acc; - unsigned long long cutoff; - int c; - int neg = 0, any, cutlim; - - if (base < 0 || base == 1 || base > 36) - { - if (endptr) - *endptr = (char *)nptr; - errno = 0; /* EINVAL */ - return 0; - } - - do - { - c = *s++; - } while (isspace(c)); - - if (c == '-') - { - neg = 1; - c = *s++; - } - else if (c == '+') - { - c = *s++; - } - - if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) - { - c = s[1]; - s += 2; - base = 16; - } - if (base == 0) - base = (c == '0' ? 8 : 10); - - cutoff = ULLONG_MAX / (unsigned long long)base; - cutlim = ULLONG_MAX % (unsigned long long)base; - - for (acc = 0, any = 0;; c = *s++) - { - if (isdigit(c)) - c -= '0'; - else if (isalpha(c)) - c -= isupper(c) ? 'A' - 10 : 'a' - 10; - else - break; - - if (c >= base) - break; - - if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) - any = -1; - else - { - any = 1; - acc *= base; - acc += c; - } - } - - if (any < 0) - { - acc = ULLONG_MAX; - errno = ERANGE; - } - else if (neg) - acc = -acc; - - if (endptr != 0) - *endptr = (char *)(any ? s - 1 : nptr); - - return acc; -} - -long long strtoll(const char *nptr, char **endptr, int base) -{ - const char *s = nptr; - unsigned long long acc; - int c, neg = 0; - - do - { - c = *s++; - } while (isspace(c)); - if (c == '-') - neg = 1; - - acc = strtoull(nptr, endptr, base); - - if (errno == ERANGE && acc == ULLONG_MAX) - return neg ? LLONG_MIN : LLONG_MAX; - - if (!neg && acc > (unsigned long long)LLONG_MAX) - { - errno = ERANGE; - return LLONG_MAX; - } - if (neg && acc > (unsigned long long)LLONG_MAX + 1) - { - errno = ERANGE; - return LLONG_MIN; - } - - return neg ? -(long long)acc : (long long)acc; -} - -unsigned long strtoul(const char *nptr, char **endptr, int base) -{ - unsigned long long ret = strtoull(nptr, endptr, base); - if (ret > ULONG_MAX) - { - errno = ERANGE; - return ULONG_MAX; - } - return (unsigned long)ret; -} - -long strtol(const char *nptr, char **endptr, int base) -{ - long long ret = strtoll(nptr, endptr, base); - if (ret > LONG_MAX) - { - errno = ERANGE; - return LONG_MAX; - } - if (ret < LONG_MIN) - { - errno = ERANGE; - return LONG_MIN; - } - return (long)ret; -} - -int atoi(const char *nptr) { return (int)strtol(nptr, NULL, 10); } -long atol(const char *nptr) { return strtol(nptr, NULL, 10); } -long long atoll(const char *nptr) { return strtoll(nptr, NULL, 10); } - -double strtod(const char *nptr, char **endptr) -{ - double val = 0.0; - int sign = 1; - const char *s = nptr; - - while (isspace(*s)) - s++; - if (*s == '-') - { - sign = -1; - s++; - } - else if (*s == '+') - s++; - - while (isdigit(*s)) - { - val = val * 10.0 + (*s - '0'); - s++; - } - if (*s == '.') - { - double factor = 1.0; - s++; - while (isdigit(*s)) - { - factor *= 0.1; - val += (*s - '0') * factor; - s++; - } - } - - if (endptr) - *endptr = (char *)s; - return val * sign; -} - -double atof(const char *nptr) { return strtod(nptr, NULL); } -float strtof(const char *nptr, char **endptr) { return (float)strtod(nptr, endptr); } - -/* Sort & Search */ - -static void swap_bytes(char *a, char *b, size_t size) -{ - char temp; - while (size--) - { - temp = *a; - *a++ = *b; - *b++ = temp; - } -} - -void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) -{ - if (nmemb < 2 || size == 0) - return; - - char *base_ptr = (char *)base; - char *pivot = base_ptr + (nmemb - 1) * size; - char *i = base_ptr; - char *j = base_ptr; - - /* Partition */ - for (; j < pivot; j += size) - { - if (compar(j, pivot) < 0) - { - if (i != j) - swap_bytes(i, j, size); - i += size; - } - } - swap_bytes(i, pivot, size); - - /* Recurse */ - size_t left_count = (i - base_ptr) / size; - qsort(base, left_count, size, compar); - qsort(i + size, nmemb - left_count - 1, size, compar); -} - -void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) -{ - size_t l = 0, u = nmemb; - while (l < u) - { - size_t idx = (l + u) / 2; - const void *p = (void *)((const char *)base + (idx * size)); - int cmp = compar(key, p); - if (cmp < 0) - u = idx; - else if (cmp > 0) - l = idx + 1; - else - return (void *)p; - } - return NULL; -} - -/* Math & Mics */ - -int abs(int j) { return (j < 0) ? -j : j; } -long labs(long j) { return (j < 0) ? -j : j; } -long long llabs(long long j) { return (j < 0) ? -j : j; } - -div_t div(int numer, int denom) -{ - div_t res = {numer / denom, numer % denom}; - return res; -} -ldiv_t ldiv(long numer, long denom) -{ - ldiv_t res = {numer / denom, numer % denom}; - return res; -} -lldiv_t lldiv(long long numer, long long denom) -{ - lldiv_t res = {numer / denom, numer % denom}; - return res; -} - -static unsigned long next = 1; -int rand(void) -{ - next = next * 1103515245 + 12345; - return (unsigned int)(next / 65536) % 32768; -} -void srand(unsigned int seed) { next = seed; } - -/* Stub */ -int mblen(const char *s, size_t n) { return (s && *s && n) ? 1 : 0; } -int mbtowc(wchar_t *pwc, const char *s, size_t n) -{ - if (!s) - return 0; - if (n == 0) - return -1; - if (pwc) - *pwc = (wchar_t)*s; - return (*s != 0); -} -int wctomb(char *s, wchar_t wchar) -{ - if (!s) - return 0; - *s = (char)wchar; - return 1; -} -size_t mbstowcs(wchar_t *dest, const char *src, size_t n) -{ - size_t i = 0; - while (src[i] && i < n) - { - dest[i] = (wchar_t)src[i]; - i++; - } - if (i < n) - dest[i] = 0; - return i; -} -size_t wcstombs(char *dest, const wchar_t *src, size_t n) -{ - size_t i = 0; - while (src[i] && i < n) - { - dest[i] = (char)src[i]; - i++; - } - if (i < n) - dest[i] = 0; - return i; -} \ No newline at end of file diff --git a/src/ulib/string.c b/src/ulib/string.c deleted file mode 100644 index c3dbe9a..0000000 --- a/src/ulib/string.c +++ /dev/null @@ -1,335 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/string.h" -#include "ulib/stdlib.h" - - -/* Memory Manipulation Functions */ - -void *memset(void *dst, int c, size_t n) -{ - char *cdst = (char *)dst; - for (size_t i = 0; i < n; i++) - cdst[i] = (char)c; - return dst; -} - -void *memmove(void *vdst, const void *vsrc, size_t n) -{ - char *dst = (char *)vdst; - const char *src = (const char *)vsrc; - if (dst == src) - return vdst; - - if (src > dst) - { - while (n--) - *dst++ = *src++; - } - else - { - dst += n; - src += n; - while (n-- > 0) - *--dst = *--src; - } - return vdst; -} - -void *memcpy(void *vdst, const void *vsrc, size_t n) -{ - return memmove(vdst, vsrc, n); -} - -int memcmp(const void *s1, const void *s2, size_t n) -{ - const unsigned char *p1 = (const unsigned char *)s1; - const unsigned char *p2 = (const unsigned char *)s2; - while (n--) - { - if (*p1 != *p2) - return *p1 - *p2; - p1++; - p2++; - } - return 0; -} - -void *memchr(const void *s, int c, size_t n) -{ - const unsigned char *p = (const unsigned char *)s; - while (n--) - { - if (*p == (unsigned char)c) - return (void *)p; - p++; - } - return NULL; -} - -/* String Manipulation Functions */ - -size_t strlen(const char *s) -{ - const char *p = s; - while (*p) - p++; - return (size_t)(p - s); -} - -char *strcpy(char *dst, const char *src) -{ - char *os = dst; - while ((*dst++ = *src++) != 0) - ; - return os; -} - -char *strncpy(char *dst, const char *src, size_t n) -{ - char *os = dst; - size_t i; - - for (i = 0; i < n && src[i] != '\0'; i++) - dst[i] = src[i]; - - for (; i < n; i++) - dst[i] = '\0'; - - return os; -} - -char *strcat(char *dst, const char *src) -{ - char *os = dst; - while (*dst) - dst++; - while ((*dst++ = *src++) != 0) - ; - return os; -} - -char *strncat(char *dst, const char *src, size_t n) -{ - char *os = dst; - size_t dest_len = strlen(dst); - size_t i; - - dst += dest_len; - - for (i = 0; i < n && src[i] != '\0'; i++) - dst[i] = src[i]; - - dst[i] = '\0'; - - return os; -} - -int strcmp(const char *p, const char *q) -{ - while (*p && *p == *q) - { - p++; - q++; - } - return (int)((unsigned char)*p - (unsigned char)*q); -} - -int strncmp(const char *s1, const char *s2, size_t n) -{ - if (n == 0) - return 0; - - while (n-- > 0 && *s1 && *s1 == *s2) - { - if (n == 0 || *s1 == '\0') - break; - s1++; - s2++; - } - return (int)((unsigned char)*s1 - (unsigned char)*s2); -} - -char *strchr(const char *s, int c) -{ - while (*s != (char)c) - { - if (!*s++) - return NULL; - } - return (char *)s; -} - -char *strrchr(const char *s, int c) -{ - const char *last = NULL; - do - { - if (*s == (char)c) - last = s; - } while (*s++); - - return (char *)last; -} - - -char *strstr(const char *haystack, const char *needle) -{ - size_t n_len = strlen(needle); - if (n_len == 0) - return (char *)haystack; - - for (; *haystack; haystack++) - { - if (*haystack != *needle) - continue; - - if (strncmp(haystack, needle, n_len) == 0) - return (char *)haystack; - } - return NULL; -} - -char *strdup(const char *s) -{ - size_t len = strlen(s) + 1; - char *new_str = (char *)malloc(len); - if (new_str == NULL) - return NULL; - - return (char *)memcpy(new_str, s, len); -} - -size_t strlcpy(char *dst, const char *src, size_t size) -{ - size_t src_len = strlen(src); - - if (size > 0) - { - size_t copy_len = (src_len >= size) ? size - 1 : src_len; - memcpy(dst, src, copy_len); - dst[copy_len] = '\0'; - } - - return src_len; -} - -size_t strlcat(char *dst, const char *src, size_t size) -{ - size_t dst_len = 0; - size_t src_len = strlen(src); - - while (dst_len < size && dst[dst_len] != '\0') - dst_len++; - - if (dst_len == size) - return size + src_len; - - size_t space_left = size - dst_len; - size_t copy_len = (src_len >= space_left) ? space_left - 1 : src_len; - - memcpy(dst + dst_len, src, copy_len); - dst[dst_len + copy_len] = '\0'; - - return dst_len + src_len; -} - - -size_t strspn(const char *s, const char *accept) -{ - const char *p = s; - const char *a; - - while (*p) - { - for (a = accept; *a; a++) - { - if (*p == *a) - break; - } - if (*a == '\0') - return (size_t)(p - s); - p++; - } - return (size_t)(p - s); -} - -size_t strcspn(const char *s, const char *reject) -{ - const char *p = s; - const char *r; - - while (*p) - { - for (r = reject; *r; r++) - { - if (*p == *r) - return (size_t)(p - s); - } - p++; - } - return (size_t)(p - s); -} - -char *strpbrk(const char *s, const char *accept) -{ - const char *a; - while (*s) - { - for (a = accept; *a; a++) - { - if (*s == *a) - return (char *)s; - } - s++; - } - return NULL; -} - -char *strtok(char *str, const char *delim) -{ - static char *olds; - char *token; - - if (str == NULL) - str = olds; - - str += strspn(str, delim); - if (*str == '\0') - { - olds = str; - return NULL; - } - - token = str; - str = strpbrk(token, delim); - - if (str == NULL) - { - olds = strchr(token, '\0'); - } - else - { - *str = '\0'; - olds = str + 1; - } - - return token; -} - -char *strerror(int errnum) -{ - - switch (errnum) - { - case 0: - return "Success"; - // case EINVAL: return "Invalid argument"; - case ENOMEM: return "Out of memory"; - - default: - return "Unknown error"; - } -} \ No newline at end of file diff --git a/src/ulib/syscalls.c b/src/ulib/syscalls.c deleted file mode 100644 index 676bf18..0000000 --- a/src/ulib/syscalls.c +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * Libc Syscall Shims - */ - -#include "common/types.h" -#include "user/user.h" - -// Define external raw syscalls from usys.S -extern int sys_openat(int dfd, const char *filename, int flags, int mode); -extern int sys_mknodat(int dfd, const char *filename, int mode, int dev); -extern int sys_mkdirat(int dfd, const char *path, int mode); -extern int sys_unlinkat(int dfd, const char *path, int flags); -extern int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); -extern int sys_chdir(const char *path); -extern int sys_dup(int oldfd); -extern int sys_fstat(int fd, void *statbuf); -extern int sys_read(int fd, void *buf, int count); -extern int sys_write(int fd, const void *buf, int count); -extern int sys_close(int fd); -extern int sys_pipe2(int pipefd[2], int flags); -extern int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); -extern int sys_execve(const char *path, char *const argv[], char *const envp[]); -extern int sys_wait4(int pid, int *status, int options, void *rusage); -extern int sys_exit(int status); -extern int sys_getpid(); -extern int sys_brk(int n); -extern int sys_kill(int pid, int sig); -extern int sys_nanosleep(void *req, void *rem); -extern int sys_reboot(int magic1, int magic2, int cmd, void *arg); -extern int sys_lseek(int fd, int offset, int whence); -extern void sys_putc(char c); -extern int sys_disk_test(); - -// --- POSIX Implementation --- - -int open(const char *path, int flags) -{ - return sys_openat(AT_FDCWD, path, flags, 0); -} - -int mknod(const char *path, short major, short minor) -{ - // Pack major/minor into simplified dev_t (major << 20 | minor) - int dev = (major << 20) | (minor & 0xFFFFF); - return sys_mknodat(AT_FDCWD, path, 0, dev); -} - -int mkdir(const char *path) -{ - return sys_mkdirat(AT_FDCWD, path, 0); -} - -int unlink(const char *path) -{ - return sys_unlinkat(AT_FDCWD, path, 0); -} - -int link(const char *oldpath, const char *newpath) -{ - return sys_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0); -} - -int chdir(const char *path) -{ - return sys_chdir(path); -} - -int fork() -{ - return sys_clone(0, 0, 0, 0, 0); -} - -int exec(char *path, char **argv) -{ - return sys_execve(path, argv, 0); -} - -int wait(uint64 status_addr) -{ - return sys_wait4(-1, (int *)status_addr, 0, 0); -} - -int pipe(int p[]) -{ - return sys_pipe2(p, 0); -} - -int read(int fd, void *buf, int n) -{ - return sys_read(fd, buf, n); -} - -int write(int fd, const void *buf, int n) -{ - return sys_write(fd, buf, n); -} - -int close(int fd) -{ - return sys_close(fd); -} - -int fstat(int fd, void *st) -{ - return sys_fstat(fd, st); -} - -int dup(int fd) -{ - return sys_dup(fd); -} - -int getpid() -{ - return sys_getpid(); -} - -char *sbrk(int n) -{ - return (char *)(long)sys_brk(n); -} - -int kill(int pid) -{ - return sys_kill(pid, 0); -} - -int sleep(int ticks) -{ - return sys_nanosleep((void *)(long)ticks, 0); -} - -int shutdown() -{ - return sys_reboot(0, 0, 0, 0); -} - -void putc(char c) -{ - sys_putc(c); -} - -int disk_test() -{ - return sys_disk_test(); -} - -int lseek(int fd, int offset, int whence) -{ - return sys_lseek(fd, offset, whence); -} \ No newline at end of file diff --git a/src/user/cat.cc b/src/user/cat.cc deleted file mode 100644 index b518d77..0000000 --- a/src/user/cat.cc +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "common/fcntl.h" - -char buf[512]; - -void cat(int fd) -{ - int n; - while ((n = read(fd, buf, sizeof(buf))) > 0) - { - if (write(1, buf, n) != n) - { - printf("cat: write error\n"); - exit(1); - } - } - if (n < 0) - { - printf("cat: read error\n"); - exit(1); - } -} - -int main(int argc, char *argv[]) -{ - int fd, i; - - if (argc <= 1) - { - cat(0); - exit(0); - } - - for (i = 1; i < argc; i++) - { - if ((fd = open(argv[i], O_RDONLY)) < 0) - { - printf("cat: cannot open %s\n", argv[i]); - exit(1); - } - cat(fd); - close(fd); - } - exit(0); -} \ No newline at end of file diff --git a/src/user/cp.cc b/src/user/cp.cc deleted file mode 100644 index ca35e82..0000000 --- a/src/user/cp.cc +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" -#include "common/fcntl.h" - -char buf[512]; - -int main(int argc, char *argv[]) -{ - int fd_src, fd_dst, n; - - if (argc != 3) - { - printf("Usage: cp src dst\n"); - exit(1); - } - - if ((fd_src = open(argv[1], O_RDONLY)) < 0) - { - printf("cp: cannot open source %s\n", argv[1]); - exit(1); - } - - if ((fd_dst = open(argv[2], O_WRONLY | O_CREATE | O_TRUNC)) < 0) - { - printf("cp: cannot create destination %s\n", argv[2]); - close(fd_src); - exit(1); - } - - while ((n = read(fd_src, buf, sizeof(buf))) > 0) - { - if (write(fd_dst, buf, n) != n) - { - printf("cp: write error\n"); - exit(1); - } - } - - if (n < 0) - { - printf("cp: read error\n"); - } - - close(fd_src); - close(fd_dst); - exit(0); -} \ No newline at end of file diff --git a/src/user/echo.cc b/src/user/echo.cc deleted file mode 100644 index cc9dc2a..0000000 --- a/src/user/echo.cc +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "user/user.h" -#include "ulib/stdio.h" -#include "ulib/string.h" - -int main(int argc, char* argv[]) -{ - int i; - - for (i = 1; i < argc; i++) - { - write(1, argv[i], strlen(argv[i])); - if (i + 1 < argc) - { - write(1, " ", 1); - } - else - { - write(1, "\n", 1); - } - } - exit(0); -} \ No newline at end of file diff --git a/src/user/entry.S b/src/user/entry.S deleted file mode 100644 index 41e0964..0000000 --- a/src/user/entry.S +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.section .text -.global _start - -_start: - call main - - call exit - -1: j 1b \ No newline at end of file diff --git a/src/user/grep.cc b/src/user/grep.cc deleted file mode 100644 index 1e1c42d..0000000 --- a/src/user/grep.cc +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "common/fcntl.h" - -char buf[1024]; - -void grep(char *pattern, int fd) -{ - int n, m; - char *p, *q; - - m = 0; - while ((n = read(fd, buf + m, sizeof(buf) - m - 1)) > 0) - { - m += n; - buf[m] = '\0'; - p = buf; - while ((q = strchr(p, '\n')) != 0) - { - *q = 0; - if (strstr(p, pattern) != 0) - { - printf("%s\n", p); - } - p = q + 1; - } - if (p == buf) - m = 0; - if (m > 0) - { - m -= p - buf; - memmove(buf, p, m); - } - } -} - -int main(int argc, char *argv[]) -{ - int fd, i; - char *pattern; - - if (argc < 2) - { - printf("Usage: grep pattern [file...]\n"); - exit(1); - } - pattern = argv[1]; - - if (argc == 2) - { - grep(pattern, 0); // stdin - exit(0); - } - - for (i = 2; i < argc; i++) - { - if ((fd = open(argv[i], O_RDONLY)) < 0) - { - printf("grep: cannot open %s\n", argv[i]); - exit(1); - } - grep(pattern, fd); - close(fd); - } - exit(0); -} \ No newline at end of file diff --git a/src/user/init.cc b/src/user/init.cc deleted file mode 100644 index 376e697..0000000 --- a/src/user/init.cc +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "drivers/uart_color.h" - -int main() -{ - char *argv[] = {(char *)"sh", 0}; - - printf(ANSI_BLUE "\n[Init] Starting Lume OS Shell..." ANSI_RESET); - printf("\n"); - - while (1) - { - // printf("fork start\n"); - int pid = fork(); - // printf("fork suceess, pid=%i\n", pid); - - if (pid < 0) - { - printf("[Init] fork failed!\n"); - exit(1); - } - - if (pid == 0) - { - - char sh_path[] = "/sh"; - exec(sh_path, argv); - - printf("[Init] exec sh failed\n"); - return 0; - exit(1); - } - else - { - // break; - int status; - int wpid; - while (1) - { - wpid = wait((uint64)&status); - - if (wpid == pid) - { - printf("Shell Exit"); - break; - } - if (wpid < 0) - { - printf("[Init] wait error or no child\n"); - - break; - } - } - } - } - return 0; -} \ No newline at end of file diff --git a/src/user/kill.cc b/src/user/kill.cc deleted file mode 100644 index d38cdd6..0000000 --- a/src/user/kill.cc +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" - -int main(int argc, char *argv[]) -{ - int i; - - if (argc < 2) - { - printf("Usage: kill pid...\n"); - exit(1); - } - for (i = 1; i < argc; i++) - { - int pid = atoi(argv[i]); - if (kill(pid) < 0) - { - printf("kill: failed to kill %d\n", pid); - } - } - exit(0); -} \ No newline at end of file diff --git a/src/user/ln.cc b/src/user/ln.cc deleted file mode 100644 index a21ddf9..0000000 --- a/src/user/ln.cc +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" - -int main(int argc, char *argv[]) -{ - if (argc != 3) - { - printf("Usage: ln old new\n"); - exit(1); - } - if (link(argv[1], argv[2]) < 0) - { - printf("link %s %s failed\n", argv[1], argv[2]); - exit(1); - } - exit(0); -} \ No newline at end of file diff --git a/src/user/ls.cc b/src/user/ls.cc deleted file mode 100644 index c44c799..0000000 --- a/src/user/ls.cc +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "common/fcntl.h" - - -struct FAT32_DirEnt -{ - char name[11]; - uint8 attr; - uint8 lcase; - uint8 ctime_cs; - uint16 ctime; - uint16 cdate; - uint16 adate; - uint16 fstClusHI; - uint16 wtime; - uint16 wdate; - uint16 fstClusLO; - uint32 fileSize; -} __attribute__((packed)); - -#define ATTR_DIRECTORY 0x10 -#define ATTR_LONG_NAME 0x0F - -void fmt_name(char *dst, const char *src) -{ - int i; - char *d = dst; - - for (i = 0; i < 8; i++) - { - if (src[i] == ' ') - break; - *d++ = src[i]; - } - - if (src[8] != ' ') - { - *d++ = '.'; - for (i = 8; i < 11; i++) - { - if (src[i] == ' ') - break; - *d++ = src[i]; - } - } - *d = 0; - - for (d = dst; *d; d++) - { - if (*d >= 'A' && *d <= 'Z') - *d += 32; - } -} - -void ls(const char *path) -{ - int fd; - struct FAT32_DirEnt de; - char name_buf[16]; - - if ((fd = open(path, O_RDONLY)) < 0) - { - printf("ls: cannot open %s\n", path); - return; - } - - while (read(fd, &de, sizeof(de)) == sizeof(de)) - { - if (de.name[0] == 0) - { - break; - } - - if ((unsigned char)de.name[0] == 0xE5) - { - continue; - } - - if ((de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) - { - continue; - } - - fmt_name(name_buf, de.name); - - printf("%s %d\t%s\n", - (de.attr & ATTR_DIRECTORY) ? "" : " ", - de.fileSize, - name_buf); - } - - close(fd); -} - -int main(int argc, char *argv[]) -{ - if (argc < 2) - { - ls("."); - } - else - { - for (int i = 1; i < argc; i++) - { - ls(argv[i]); - } - } - exit(0); -} \ No newline at end of file diff --git a/src/user/mkdir.cc b/src/user/mkdir.cc deleted file mode 100644 index 6da8b49..0000000 --- a/src/user/mkdir.cc +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "user/user.h" - -int main(int argc, char* argv[]) -{ - if (argc < 2) - { - printf("Usage: mkdir ...\n"); - exit(1); - } - - for (int i = 1; i < argc; i++) - { - if (mkdir(argv[i]) < 0) - { - printf("mkdir: cannot create directory '%s'\n", argv[i]); - } - } - - exit(0); -} \ No newline at end of file diff --git a/src/user/mv.cc b/src/user/mv.cc deleted file mode 100644 index a196145..0000000 --- a/src/user/mv.cc +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" - -int main(int argc, char *argv[]) -{ - if (argc != 3) - { - printf("Usage: mv old new\n"); - exit(1); - } - - if (link(argv[1], argv[2]) < 0) - { - printf("mv: failed to link %s to %s\n", argv[1], argv[2]); - exit(1); - } - - if (unlink(argv[1]) < 0) - { - printf("mv: failed to unlink %s\n", argv[1]); - exit(1); - } - - exit(0); -} \ No newline at end of file diff --git a/src/user/rm.cc b/src/user/rm.cc deleted file mode 100644 index 4777ade..0000000 --- a/src/user/rm.cc +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" - -int main(int argc, char *argv[]) -{ - int i; - - if (argc < 2) - { - printf("Usage: rm files...\n"); - exit(1); - } - - for (i = 1; i < argc; i++) - { - if (unlink(argv[i]) < 0) - { - printf("rm: %s failed to delete\n", argv[i]); - exit(1); - } - } - exit(0); -} \ No newline at end of file diff --git a/src/user/sh.cc b/src/user/sh.cc deleted file mode 100644 index 47aad9e..0000000 --- a/src/user/sh.cc +++ /dev/null @@ -1,423 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "common/fcntl.h" -#include "drivers/uart_color.h" - -constexpr int MAX_ARGS = 10; -const char *WHITESPACE = " \t\r\n\v"; -const char *SYMBOLS = "<|>&;()"; - -void panic(const char *s) -{ - printf("shell panic: %s\n", s); - exit(1); -} - -int fork_or_panic() -{ - int pid = fork(); - if (pid == -1) - panic("fork"); - return pid; -} - -struct Command -{ - virtual ~Command() = default; - - virtual void run() = 0; -}; - -struct ExecCommand : public Command -{ - char *argv[MAX_ARGS]; - - ExecCommand() - { - memset(argv, 0, sizeof(argv)); - } - - void run() override - { - if (argv[0] == nullptr) - exit(1); - - exec(argv[0], argv); - - printf("exec %s failed\n", argv[0]); - exit(1); - } -}; - -struct RedirCommand : public Command -{ - Command *sub_cmd; - char *file; - char *efile; // file name end pointer (used by parser) - int mode; - int fd; - - RedirCommand(Command *c, char *f, char *ef, int m, int fd_in) - : sub_cmd(c), file(f), efile(ef), mode(m), fd(fd_in) {} - - ~RedirCommand() { delete sub_cmd; } - - void run() override - { - close(fd); - - int newfd = open(file, mode); - - if (newfd < 0) - { - printf("open %s failed\n", file); - exit(1); - } - - if (newfd != fd) - { - - if (dup(newfd) != fd) - { - printf("Redirection failed: could not move fd %d to %d\n", newfd, fd); - exit(1); - } - close(newfd); - } - - sub_cmd->run(); - } -}; - -struct PipeCommand : public Command -{ - Command *left; - Command *right; - - PipeCommand(Command *l, Command *r) : left(l), right(r) {} - ~PipeCommand() - { - delete left; - delete right; - } - - void run() override - { - int p[2]; - if (pipe(p) < 0) - panic("pipe"); - - if (fork_or_panic() == 0) - { - // Left child: write to pipe - close(1); // close stdout - dup(p[1]); // dup write-end to stdout - close(p[0]); - close(p[1]); - left->run(); - } - - if (fork_or_panic() == 0) - { - // Right child: read from pipe - close(0); // close stdin - dup(p[0]); // dup read-end to stdin - close(p[0]); - close(p[1]); - right->run(); - } - - close(p[0]); - close(p[1]); - wait(0); - wait(0); - exit(0); - } -}; - -struct ListCommand : public Command -{ - Command *left; - Command *right; - - ListCommand(Command *l, Command *r) : left(l), right(r) {} - ~ListCommand() - { - delete left; - delete right; - } - - void run() override - { - if (fork_or_panic() == 0) - { - left->run(); - } - wait(0); - right->run(); - } -}; - -struct BackCommand : public Command -{ - Command *sub_cmd; - - BackCommand(Command *c) : sub_cmd(c) {} - ~BackCommand() { delete sub_cmd; } - - void run() override - { - if (fork_or_panic() == 0) - { - sub_cmd->run(); - } - // Parent doesn't wait - exit(0); - } -}; - -class Parser -{ -public: - static Command *parse(char *s) - { - char *es = s + strlen(s); - Command *cmd = parse_line(&s, es); - peek(&s, es, ""); - if (s != es) - { - printf("leftovers: %s\n", s); - // panic("syntax"); // strict syntax check - } - terminate_strings(cmd); - return cmd; - } - -private: - // Helper: NUL-terminate strings found during parsing - static void terminate_strings(Command *cmd) - { - if (!cmd) - return; - } - - static char get_token(char **ps, char *es, char **q, char **eq) - { - char *s; - int ret; - - s = *ps; - while (s < es && strchr(WHITESPACE, *s)) - s++; - if (q) - *q = s; - ret = *s; - switch (*s) - { - case 0: - break; - case '|': - case '(': - case ')': - case ';': - case '&': - case '<': - s++; - break; - case '>': - s++; - if (*s == '>') - { - ret = '+'; - s++; - } - break; - default: - ret = 'a'; - while (s < es && !strchr(WHITESPACE, *s) && !strchr(SYMBOLS, *s)) - s++; - break; - } - if (eq) - *eq = s; - - while (s < es && strchr(WHITESPACE, *s)) - s++; - *ps = s; - return ret; - } - - static int peek(char **ps, char *es, const char *toks) - { - char *s = *ps; - while (s < es && strchr(WHITESPACE, *s)) - s++; - *ps = s; - return *s && strchr(toks, *s); - } - - // Recursive Descent Parsers - - static Command *parse_line(char **ps, char *es) - { - Command *cmd = parse_pipe(ps, es); - while (peek(ps, es, "&")) - { - get_token(ps, es, 0, 0); - cmd = new BackCommand(cmd); - } - if (peek(ps, es, ";")) - { - get_token(ps, es, 0, 0); - cmd = new ListCommand(cmd, parse_line(ps, es)); - } - return cmd; - } - - static Command *parse_pipe(char **ps, char *es) - { - Command *cmd = parse_exec(ps, es); - if (peek(ps, es, "|")) - { - get_token(ps, es, 0, 0); - cmd = new PipeCommand(cmd, parse_pipe(ps, es)); - } - return cmd; - } - - static Command *parse_exec(char **ps, char *es) - { - char *q, *eq; - int tok, argc; - ExecCommand *cmd = new ExecCommand(); - Command *ret = cmd; - - argc = 0; - ret = parse_redirs(ret, ps, es); - while (!peek(ps, es, "|)&;")) - { - if ((tok = get_token(ps, es, &q, &eq)) == 0) - break; - if (tok != 'a') - panic("syntax"); - - cmd->argv[argc] = q; - *eq = 0; // Temp - - argc++; - if (argc >= MAX_ARGS) - panic("too many args"); - ret = parse_redirs(ret, ps, es); - } - cmd->argv[argc] = 0; - return ret; - } - - static Command *parse_redirs(Command *cmd, char **ps, char *es) - { - int tok; - char *q, *eq; - - while (peek(ps, es, "<>")) - { - tok = get_token(ps, es, 0, 0); - if (get_token(ps, es, &q, &eq) != 'a') - panic("missing file for redirection"); - - *eq = 0; // Null terminate filename immediately - - switch (tok) - { - case '<': - cmd = new RedirCommand(cmd, q, eq, O_RDONLY, 0); - break; - case '>': - cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE | O_TRUNC, 1); - break; - case '+': // >> - cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE, 1); // Append not fully supported in simple OS yet? - break; - } - } - return cmd; - } -}; - -int getcmd(char *buf, int nbuf) -{ - write(1, "$ ", 2); - memset(buf, 0, nbuf); - - int n = read(0, buf, nbuf - 1); - - if (n < 0) - return -1; - - buf[n] = 0; - return 0; -} - -int main() -{ - static char buf[100]; - - - printf(ANSI_GREEN "\n[Lume Shell] Welcome! \n" ANSI_RESET); - printf(ANSI_YELLOW "[Lume Shell] Start your journey of exploration!" ANSI_RESET); - printf("\n"); - - while (getcmd(buf, sizeof(buf)) >= 0) - { - int len = strlen(buf); - if (len > 0 && buf[len - 1] == '\n') - { - buf[len - 1] = 0; - } - - if (buf[0] == 'c' && buf[1] == 'd' && (buf[2] == ' ' || buf[2] == '\n' || buf[2] == 0)) - { - if (buf[strlen(buf) - 1] == '\n') - buf[strlen(buf) - 1] = 0; - - char *path = buf + 3; - if (buf[2] == '\n' || buf[2] == 0) - { - printf("cd: argument missing\n"); - continue; - } - - if (chdir(path) < 0) - { - printf("cannot cd %s\n", path); - } - continue; - } - if (strcmp(buf, "shutdown") == 0 || strcmp(buf, "poweroff") == 0) - { - printf(ANSI_BLUE "System shutting down..." ANSI_RESET); - printf("\n"); - shutdown(); - - continue; - } - - if (fork_or_panic() == 0) - { - Command *cmd = Parser::parse(buf); - if (cmd) - { - cmd->run(); - delete cmd; - } - exit(0); - } - wait(0); - } - return 0; -} \ No newline at end of file diff --git a/src/user/touch.cc b/src/user/touch.cc deleted file mode 100644 index 2f9f9f6..0000000 --- a/src/user/touch.cc +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "user/user.h" -#include "common/fcntl.h" - -int main(int argc, char *argv[]) -{ - int i, fd; - - if (argc < 2) - { - printf("Usage: touch files...\n"); - exit(1); - } - - for (i = 1; i < argc; i++) - { - fd = open(argv[i], O_CREATE | O_RDWR); - if (fd < 0) - { - printf("touch: cannot touch %s\n", argv[i]); - exit(1); - } - close(fd); - } - exit(0); -} \ No newline at end of file diff --git a/src/user/user.ld b/src/user/user.ld deleted file mode 100644 index f0a4b27..0000000 --- a/src/user/user.ld +++ /dev/null @@ -1,36 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -OUTPUT_ARCH( "riscv" ) -ENTRY( _start ) - -SECTIONS -{ - . = 0x0; - - .text : { - *(.text.entry) - *(.text .text.*) - } - - .rodata : { - . = ALIGN(16); - *(.srodata .srodata.*) - . = ALIGN(16); - *(.rodata .rodata.*) - } - - .data : { - . = ALIGN(16); - *(.sdata .sdata.*) - . = ALIGN(16); - *(.data .data.*) - } - - .bss : { - . = ALIGN(16); - *(.sbss .sbss.*) - . = ALIGN(16); - *(.bss .bss.*) - } -} \ No newline at end of file diff --git a/src/user/usys.S b/src/user/usys.S deleted file mode 100644 index 1e373d3..0000000 --- a/src/user/usys.S +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -#include "kernel/syscall.h" - -#define SYSCALL_NAME(name, sysname) \ - .global name; \ - name: \ - li a7, SYS_ ## sysname; \ - ecall; \ - ret - -SYSCALL_NAME(sys_openat, openat) -SYSCALL_NAME(sys_mknodat, mknodat) -SYSCALL_NAME(sys_mkdirat, mkdirat) -SYSCALL_NAME(sys_unlinkat, unlinkat) -SYSCALL_NAME(sys_linkat, linkat) -SYSCALL_NAME(sys_chdir, chdir) -SYSCALL_NAME(sys_dup, dup) -SYSCALL_NAME(sys_fstat, fstat) -SYSCALL_NAME(sys_read, read) -SYSCALL_NAME(sys_write, write) -SYSCALL_NAME(sys_close, close) -SYSCALL_NAME(sys_pipe2, pipe2) -SYSCALL_NAME(sys_clone, clone) -SYSCALL_NAME(sys_execve, execve) -SYSCALL_NAME(sys_wait4, wait4) -SYSCALL_NAME(sys_exit, exit) -SYSCALL_NAME(sys_getpid, getpid) -SYSCALL_NAME(sys_brk, brk) -SYSCALL_NAME(sys_kill, kill) -SYSCALL_NAME(sys_nanosleep, nanosleep) -SYSCALL_NAME(sys_reboot, reboot) -SYSCALL_NAME(sys_lseek, lseek) - -# === Custom / Debug Syscalls === -SYSCALL_NAME(sys_putc, putc) -SYSCALL_NAME(sys_disk_test, disk_test) \ No newline at end of file diff --git a/src/user/wc.cc b/src/user/wc.cc deleted file mode 100644 index fbe9735..0000000 --- a/src/user/wc.cc +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "ulib/stdio.h" -#include "ulib/stdlib.h" -#include "ulib/string.h" -#include "user/user.h" -#include "common/fcntl.h" - -char buf[512]; - -void wc(int fd, char *name) -{ - int i, n; - int l, w, c, inword; - - l = w = c = 0; - inword = 0; - while ((n = read(fd, buf, sizeof(buf))) > 0) - { - for (i = 0; i < n; i++) - { - c++; - if (buf[i] == '\n') - l++; - if (strchr(" \r\t\n\v", buf[i])) - inword = 0; - else if (!inword) - { - w++; - inword = 1; - } - } - } - printf("%d %d %d %s\n", l, w, c, name); -} - -int main(int argc, char *argv[]) -{ - int fd, i; - - if (argc <= 1) - { - wc(0, ""); - exit(0); - } - - for (i = 1; i < argc; i++) - { - if ((fd = open(argv[i], O_RDONLY)) < 0) - { - printf("wc: cannot open %s\n", argv[i]); - exit(1); - } - wc(fd, argv[i]); - close(fd); - } - exit(0); -} \ No newline at end of file diff --git a/test/module.mk b/test/module.mk new file mode 100644 index 0000000..47a693c --- /dev/null +++ b/test/module.mk @@ -0,0 +1,30 @@ +# test/module.mk +SRCS_CC += test/selftest.cc +SRCS_CC += test/test_pmm.cc +SRCS_CC += test/test_slab.cc +SRCS_CC += test/test_spinlock.cc +SRCS_CC += test/test_mutex.cc +SRCS_CC += test/test_driver_model.cc +SRCS_CC += test/test_rbtree.cc +SRCS_CC += test/test_vmm.cc +SRCS_CC += test/test_vma.cc +SRCS_CC += test/test_atomic.cc +SRCS_CC += test/test_fdt.cc +SRCS_CC += test/test_trap.cc +SRCS_CC += test/test_sched.cc +SRCS_CC += test/test_klog.cc +SRCS_CC += test/test_random.cc +SRCS_CC += test/test_waitqueue.cc +SRCS_CC += test/test_uaccess.cc +SRCS_CC += test/test_sys_mm.cc +SRCS_CC += test/test_smp.cc +SRCS_CC += test/test_dcache.cc +SRCS_CC += test/test_namei.cc +SRCS_CC += test/test_vfs_io.cc +SRCS_CC += test/test_exec.cc +SRCS_CC += test/test_pipe_devfs.cc +SRCS_CC += test/test_sys_process.cc +SRCS_CC += test/test_signal.cc +SRCS_CC += test/test_pid.cc +SRCS_CC += test/test_sys_fs_extra.cc +SRCS_CC += test/test_page_cache.cc diff --git a/test/selftest.cc b/test/selftest.cc new file mode 100644 index 0000000..e1bb2b4 --- /dev/null +++ b/test/selftest.cc @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test Runner + * + * Entry point that invokes all registered self-test suites. + * Called from kernel_main() after all subsystem initialization. + */ + +#include + +/* Individual test suite entry points */ +void selftest_pmm(); +void selftest_slab(); +void selftest_spinlock(); +void selftest_mutex(); +void selftest_driver_model(); +void selftest_rbtree(); +void selftest_vmm(); +void selftest_fdt(); +void selftest_vma(); +void selftest_atomic(); +void selftest_trap(); +void selftest_sched(); +void selftest_klog(); +void selftest_random(); +void selftest_waitqueue(); +void selftest_uaccess(); +void selftest_sys_mm(); +void selftest_dcache(); +void selftest_namei(); +void selftest_vfs_io(); +void selftest_exec(); +void selftest_devfs(); +void selftest_pipe(); +void selftest_fcntl(); +void selftest_sys_process(); +void selftest_signal(); +void selftest_pid(); +void selftest_sys_fs_extra(); +void selftest_page_cache(); + +void selftest_run_all() +{ + kprintf("\n========================================\n"); + kprintf(" Boot Self-Test Suite\n"); + kprintf("========================================\n"); + + selftest_spinlock(); + selftest_mutex(); + selftest_driver_model(); + selftest_pmm(); + selftest_slab(); + selftest_rbtree(); + selftest_vmm(); + selftest_vma(); + selftest_atomic(); + selftest_fdt(); + selftest_trap(); + selftest_sched(); + selftest_klog(); + selftest_random(); + selftest_waitqueue(); + selftest_uaccess(); + selftest_sys_mm(); + selftest_dcache(); + selftest_namei(); + selftest_vfs_io(); + selftest_devfs(); + selftest_pipe(); + selftest_fcntl(); + selftest_sys_process(); + selftest_signal(); + selftest_pid(); + selftest_sys_fs_extra(); + selftest_page_cache(); + selftest_exec(); + + kprintf("========================================\n"); + kprintf(" All self-tests PASSED\n"); + kprintf("========================================\n\n"); +} diff --git a/test/test_atomic.cc b/test/test_atomic.cc new file mode 100644 index 0000000..9851e7b --- /dev/null +++ b/test/test_atomic.cc @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: lume::atomic + * + * Tests: + * 1. Default construction (zero-initialized) + * 2. Explicit construction + * 3. store / load round-trip with different memory orders + * 4. exchange returns old value + * 5. fetch_add / fetch_sub arithmetic + * 6. compare_exchange success and failure paths + */ + +#include +#include + +void selftest_atomic() { + st_begin("atomic: default construction is zero"); + { + lume::atomic a; + ST_ASSERT_EQ(a.load(), 0U); + } + st_pass(); + + st_begin("atomic: explicit construction"); + { + lume::atomic a(42); + ST_ASSERT_EQ(a.load(), 42ULL); + } + st_pass(); + + st_begin("atomic: store/load round-trip"); + { + lume::atomic a; + a.store(0xDEAD, __ATOMIC_RELEASE); + ST_ASSERT_EQ(a.load(__ATOMIC_ACQUIRE), 0xDEADU); + + a.store(0, __ATOMIC_SEQ_CST); + ST_ASSERT_EQ(a.load(__ATOMIC_SEQ_CST), 0U); + } + st_pass(); + + st_begin("atomic: exchange returns old value"); + { + lume::atomic a(10); + uint32 old = a.exchange(20); + ST_ASSERT_EQ(old, 10U); + ST_ASSERT_EQ(a.load(), 20U); + } + st_pass(); + + st_begin("atomic: fetch_add / fetch_sub"); + { + lume::atomic a(100); + + uint32 old = a.fetch_add(5); + ST_ASSERT_EQ(old, 100U); + ST_ASSERT_EQ(a.load(), 105U); + + old = a.fetch_sub(10); + ST_ASSERT_EQ(old, 105U); + ST_ASSERT_EQ(a.load(), 95U); + + // Underflow wraps (unsigned) + lume::atomic b(0); + old = b.fetch_sub(1); + ST_ASSERT_EQ(old, 0U); + ST_ASSERT_EQ(b.load(), 0xFFFFFFFFU); + } + st_pass(); + + st_begin("atomic: compare_exchange success and failure"); + { + lume::atomic a(42); + + // Success path: expected matches, swap happens + uint32 expected = 42; + bool ok = a.compare_exchange(expected, 99); + ST_ASSERT(ok); + ST_ASSERT_EQ(a.load(), 99U); + ST_ASSERT_EQ(expected, 42U); // expected unchanged on success + + // Failure path: expected doesn't match, expected gets updated + expected = 0; // wrong + ok = a.compare_exchange(expected, 200); + ST_ASSERT(!ok); + ST_ASSERT_EQ(a.load(), 99U); // value unchanged + ST_ASSERT_EQ(expected, 99U); // expected updated to actual value + } + st_pass(); + + st_begin("atomic: 64-bit fetch_add correctness"); + { + lume::atomic a(0xFFFFFFFF00000000ULL); + uint64 old = a.fetch_add(1); + ST_ASSERT_EQ(old, 0xFFFFFFFF00000000ULL); + ST_ASSERT_EQ(a.load(), 0xFFFFFFFF00000001ULL); + } + st_pass(); +} diff --git a/test/test_dcache.cc b/test/test_dcache.cc new file mode 100644 index 0000000..9e0eec5 --- /dev/null +++ b/test/test_dcache.cc @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_dcache.cc — Self-tests for the dentry cache layer + */ + +#include +#include +#include +#include +#include +#include + +/* Root dentry/mount from VFS init */ +extern dentry* g_root_dentry; +extern vfsmount* g_root_mnt; + +void selftest_dcache() +{ + /* ---- d_alloc: basic allocation under root ---- */ + st_begin("dcache: d_alloc basic"); + { + qstr name{}; + name.name = "test_child"; + name.len = 10; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + ST_ASSERT_EQ(child->d_name.len, 10u); + ST_ASSERT(child->d_parent == g_root_dentry); + ST_ASSERT(child->d_inode == nullptr); /* not instantiated yet */ + ST_ASSERT(child->d_sb == g_root_dentry->d_sb); + + /* Name was copied into inline buffer */ + bool match = true; + for (uint32 i = 0; i < name.len; i++) { + if (child->d_name.name[i] != name.name[i]) { match = false; break; } + } + ST_ASSERT(match); + + d_drop(child); + dput(child); + } + st_pass(); + + /* ---- d_lookup: find existing child ---- */ + st_begin("dcache: d_lookup finds child"); + { + qstr name{}; + name.name = "lookup_me"; + name.len = 9; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + + /* d_lookup should find it */ + dentry* found = d_lookup(g_root_dentry, &name); + ST_ASSERT(found != nullptr); + ST_ASSERT(found == child); + dput(found); /* drop d_lookup ref */ + + d_drop(child); + dput(child); + } + st_pass(); + + /* ---- d_lookup: miss returns nullptr ---- */ + st_begin("dcache: d_lookup miss returns nullptr"); + { + qstr name{}; + name.name = "nonexistent_xyz"; + name.len = 15; + name.hash = full_name_hash(name.name, name.len); + + dentry* found = d_lookup(g_root_dentry, &name); + ST_ASSERT(found == nullptr); + } + st_pass(); + + /* ---- d_instantiate: bind inode to dentry ---- */ + st_begin("dcache: d_instantiate binds inode"); + { + qstr name{}; + name.name = "has_inode"; + name.len = 9; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + ST_ASSERT(child->d_inode == nullptr); + + /* Create a dummy inode */ + inode* ip = new_inode(g_root_dentry->d_sb); + ST_ASSERT(ip != nullptr); + ip->i_mode = S_IFREG | 0644; + + d_instantiate(child, ip); + ST_ASSERT(child->d_inode == ip); + ST_ASSERT(child->d_flags & DCACHE_REGULAR_TYPE); + + d_drop(child); + dput(child); + } + st_pass(); + + /* ---- dget/dput: refcount ---- */ + st_begin("dcache: dget/dput refcount"); + { + qstr name{}; + name.name = "reftest"; + name.len = 7; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + /* d_alloc sets refcount = 1 */ + int rc1 = child->d_lockref.load(); + ST_ASSERT(rc1 >= 1); + + dget(child); + int rc2 = child->d_lockref.load(); + ST_ASSERT_EQ(rc2, rc1 + 1); + + dput(child); + int rc3 = child->d_lockref.load(); + ST_ASSERT_EQ(rc3, rc1); + + d_drop(child); + dput(child); + } + st_pass(); + + /* ---- d_drop: removes from parent children list ---- */ + st_begin("dcache: d_drop removes from parent"); + { + qstr name{}; + name.name = "dropme"; + name.len = 6; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + + /* Should be findable */ + dentry* f1 = d_lookup(g_root_dentry, &name); + ST_ASSERT(f1 != nullptr); + dput(f1); + + /* Drop it */ + d_drop(child); + + /* Should no longer be findable */ + dentry* f2 = d_lookup(g_root_dentry, &name); + ST_ASSERT(f2 == nullptr); + + dput(child); + } + st_pass(); + + /* ---- d_path: root returns "/" ---- */ + st_begin("dcache: d_path root returns /"); + { + path p{}; + p.de = g_root_dentry; + p.mnt = g_root_mnt; + + char buf[64]; + char* result = d_path(&p, buf, sizeof(buf)); + ST_ASSERT(result != nullptr); + ST_ASSERT(result[0] == '/'); + ST_ASSERT(result[1] == '\0'); + } + st_pass(); + + /* ---- d_path: child path ---- */ + st_begin("dcache: d_path child returns /name"); + { + qstr name{}; + name.name = "pathtest"; + name.len = 8; + name.hash = full_name_hash(name.name, name.len); + + dentry* child = d_alloc(g_root_dentry, &name); + ST_ASSERT(child != nullptr); + + path p{}; + p.de = child; + p.mnt = g_root_mnt; + + char buf[64]; + char* result = d_path(&p, buf, sizeof(buf)); + ST_ASSERT(result != nullptr); + + /* Should be "/pathtest" */ + const char* expected = "/pathtest"; + bool eq = true; + for (int i = 0; expected[i]; i++) { + if (result[i] != expected[i]) { eq = false; break; } + } + ST_ASSERT(eq); + + d_drop(child); + dput(child); + } + st_pass(); + + /* ---- d_alloc_anon: root-like dentry ---- */ + st_begin("dcache: d_alloc_anon"); + { + dentry* anon = d_alloc_anon(g_root_dentry->d_sb); + ST_ASSERT(anon != nullptr); + ST_ASSERT(anon->d_parent == anon); /* root: parent is self */ + ST_ASSERT(anon->d_sb == g_root_dentry->d_sb); + dput(anon); + } + st_pass(); + + /* ---- full_name_hash: deterministic ---- */ + st_begin("dcache: full_name_hash deterministic"); + { + uint32 h1 = full_name_hash("hello", 5); + uint32 h2 = full_name_hash("hello", 5); + ST_ASSERT_EQ(h1, h2); + + uint32 h3 = full_name_hash("world", 5); + ST_ASSERT_NE(h1, h3); + } + st_pass(); +} diff --git a/test/test_driver_model.cc b/test/test_driver_model.cc new file mode 100644 index 0000000..8f105b3 --- /dev/null +++ b/test/test_driver_model.cc @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_driver_model — exercises kref, kobject, and the bus/device/driver + * core. Uses a dummy bus + dummy driver + dummy device so the test is + * self-contained and doesn't depend on FDT/platform_device. + */ + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Test 1: kref get/put + release fires exactly once */ +/* ------------------------------------------------------------------ */ + +namespace { + +struct krefable { + kref ref; + int release_count; +}; + +void krefable_release(kref* r) +{ + auto* k = container_of(r, krefable, ref); + k->release_count++; +} + +void test_kref() +{ + st_begin("kref: init/get/put"); + + krefable k = {}; + kref_init(&k.ref); + + ST_ASSERT_EQ(kref_read(&k.ref), 1); + + kref_get(&k.ref); + kref_get(&k.ref); + ST_ASSERT_EQ(kref_read(&k.ref), 3); + + ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0); + ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 0); + ST_ASSERT_EQ(k.release_count, 0); + + ST_ASSERT_EQ(kref_put(&k.ref, krefable_release), 1); + ST_ASSERT_EQ(k.release_count, 1); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Test 2: kobject parent/child + release on final put */ +/* ------------------------------------------------------------------ */ + +int g_child_released = 0; +int g_parent_released = 0; + +void child_release(kobject*) { g_child_released++; } +void parent_release(kobject*) { g_parent_released++; } + +const kobj_type child_ktype = { .release = child_release }; +const kobj_type parent_ktype = { .release = parent_release }; + +void test_kobject_hierarchy() +{ + st_begin("kobject: parent/child + ref counting"); + + g_child_released = 0; + g_parent_released = 0; + + kobject parent; + kobject_init(&parent, &parent_ktype); + + kobject child; + kobject_init(&child, &child_ktype); + kobject_add(&child, &parent, "child"); + + /* Adding child took a ref on parent. */ + ST_ASSERT_EQ(kref_read(&parent.refcount), 2); + ST_ASSERT(child.parent == &parent); + + /* Drop child's "added" reference — release should NOT fire because + * we (the test) still hold the original kobject_init ref. */ + kobject_del(&child); + ST_ASSERT(child.parent == nullptr); + ST_ASSERT_EQ(kref_read(&parent.refcount), 1); + ST_ASSERT_EQ(g_child_released, 0); + ST_ASSERT_EQ(g_parent_released, 0); + + /* Drop final refs. */ + kobject_put(&child); + ST_ASSERT_EQ(g_child_released, 1); + + kobject_put(&parent); + ST_ASSERT_EQ(g_parent_released, 1); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Test 3: bus / device / driver bind via match + probe */ +/* ------------------------------------------------------------------ */ + +struct dummy_dev { device dev; int probed; }; +struct dummy_drv { device_driver drv; const char* want; }; + +int g_probes = 0; + +int dummy_match(device* dev, device_driver* drv) +{ + auto* dd = container_of(drv, dummy_drv, drv); + return (dev->init_name != nullptr && + dd->want != nullptr && + __builtin_strcmp(dev->init_name, dd->want) == 0) ? 1 : 0; +} + +int dummy_probe(device* dev) +{ + auto* dd = container_of(dev, dummy_dev, dev); + dd->probed++; + g_probes++; + return 0; +} + +bus_type dummy_bus = { + .name = "test_dummy", + .match = dummy_match, + .probe = nullptr, /* fall through to drv->probe */ + .devices = {}, + .drivers = {}, + .lock = {}, + .registered = false, +}; + +void test_bus_bind() +{ + st_begin("driver model: bus / driver / device bind"); + + g_probes = 0; + ST_ASSERT_EQ(bus_register(&dummy_bus), 0); + + /* Register driver before any matching device exists. */ + dummy_drv drv_a = {}; + drv_a.drv.name = "drv_a"; + drv_a.drv.bus = &dummy_bus; + drv_a.drv.probe = dummy_probe; + drv_a.want = "compat_a"; + ST_ASSERT_EQ(driver_register(&drv_a.drv), 0); + + /* Register a device that should bind to drv_a immediately. */ + dummy_dev dev_a = {}; + dev_a.dev.bus = &dummy_bus; + dev_a.dev.init_name = "compat_a"; + ST_ASSERT_EQ(device_register(&dev_a.dev), 0); + ST_ASSERT_EQ(dev_a.probed, 1); + ST_ASSERT(dev_a.dev.driver == &drv_a.drv); + + /* Register a device first, then a driver — should also bind. */ + dummy_dev dev_b = {}; + dev_b.dev.bus = &dummy_bus; + dev_b.dev.init_name = "compat_b"; + ST_ASSERT_EQ(device_register(&dev_b.dev), 0); + ST_ASSERT_EQ(dev_b.probed, 0); + + dummy_drv drv_b = {}; + drv_b.drv.name = "drv_b"; + drv_b.drv.bus = &dummy_bus; + drv_b.drv.probe = dummy_probe; + drv_b.want = "compat_b"; + ST_ASSERT_EQ(driver_register(&drv_b.drv), 0); + ST_ASSERT_EQ(dev_b.probed, 1); + ST_ASSERT(dev_b.dev.driver == &drv_b.drv); + + /* Non-matching device sits unbound. */ + dummy_dev dev_c = {}; + dev_c.dev.bus = &dummy_bus; + dev_c.dev.init_name = "compat_unknown"; + ST_ASSERT_EQ(device_register(&dev_c.dev), 0); + ST_ASSERT_EQ(dev_c.probed, 0); + ST_ASSERT(dev_c.dev.driver == nullptr); + + ST_ASSERT_EQ(g_probes, 2); + + /* Cleanup. */ + device_unregister(&dev_a.dev); + device_unregister(&dev_b.dev); + device_unregister(&dev_c.dev); + driver_unregister(&drv_a.drv); + driver_unregister(&drv_b.drv); + + st_pass(); +} + +} // namespace + +/* ------------------------------------------------------------------ */ +/* Entry */ +/* ------------------------------------------------------------------ */ + +void selftest_driver_model() +{ + test_kref(); + test_kobject_hierarchy(); + test_bus_bind(); +} diff --git a/test/test_exec.cc b/test/test_exec.cc new file mode 100644 index 0000000..bbfa3b6 --- /dev/null +++ b/test/test_exec.cc @@ -0,0 +1,447 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_exec.cc — Self-tests for execve / ELF loader + * + * Tests the ELF validation, binary loading, and user stack construction + * by creating synthetic ELF binaries in RamFS and invoking do_execve + * in a controlled environment. + * + * We cannot test the actual user-space transition (that requires + * sret to U-mode), but we CAN test everything up to and including + * the trapframe setup. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Extern: do_execve (kernel/exec.cc) */ +int do_execve(const char* pathname, const char* const* argv, + const char* const* envp); + +/* Extern: VFS file creation syscalls */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); + +/* ================================================================== */ +/* Helper: build a minimal valid RISC-V ELF64 in a buffer */ +/* ================================================================== */ + +struct TestElf { + Elf64_Ehdr ehdr; + Elf64_Phdr phdr; + uint8 code[16]; /* Tiny "program" — just a few NOPs */ +}; + +/* + * Build a minimal static ELF64 for RISC-V. + * Single PT_LOAD segment mapping the tiny code at 0x10000. + * Entry point = 0x10000 + offsetof(code). + */ +static void build_minimal_elf(TestElf* elf) +{ + /* Zero everything */ + for (uint64 i = 0; i < sizeof(*elf); i++) + reinterpret_cast(elf)[i] = 0; + + /* ELF header */ + Elf64_Ehdr* e = &elf->ehdr; + e->e_ident[EI_MAG0] = ELFMAG0; + e->e_ident[EI_MAG1] = ELFMAG1; + e->e_ident[EI_MAG2] = ELFMAG2; + e->e_ident[EI_MAG3] = ELFMAG3; + e->e_ident[EI_CLASS] = ELFCLASS64; + e->e_ident[EI_DATA] = ELFDATA2LSB; + e->e_ident[EI_VERSION] = 1; + e->e_type = ET_EXEC; + e->e_machine = EM_RISCV; + e->e_version = 1; + e->e_phoff = sizeof(Elf64_Ehdr); + e->e_phentsize = sizeof(Elf64_Phdr); + e->e_phnum = 1; + e->e_ehsize = sizeof(Elf64_Ehdr); + + /* Code section offset within the file */ + uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr); + + /* Entry point = vaddr of code */ + uint64 load_vaddr = 0x10000; + e->e_entry = load_vaddr + code_offset; + + /* Program header — single PT_LOAD covering entire file */ + Elf64_Phdr* p = &elf->phdr; + p->p_type = PT_LOAD; + p->p_flags = PF_R | PF_X; + p->p_offset = 0; + p->p_vaddr = load_vaddr; + p->p_paddr = load_vaddr; + p->p_filesz = sizeof(TestElf); + p->p_memsz = sizeof(TestElf); + p->p_align = 0x1000; + + /* Code: RISC-V "unimp" (all zeros = illegal instruction, but good enough for testing) */ + /* We just need the loader to succeed; we won't actually execute this. */ + elf->code[0] = 0x13; /* nop = addi x0, x0, 0 */ + elf->code[4] = 0x13; + elf->code[8] = 0x73; /* ebreak */ +} + +/* ================================================================== */ +/* Helper: write a buffer to a file in RamFS */ +/* ================================================================== */ + +static int create_test_file(const char* path, const void* data, uint64 size) +{ + /* Open with O_CREAT | O_WRONLY | O_TRUNC */ + int64 fd = sys_openat( + static_cast(-100), /* AT_FDCWD */ + reinterpret_cast(path), + O_CREAT | O_WRONLY | O_TRUNC, + 0755, 0, 0); + if (fd < 0) return static_cast(fd); + + int64 written = sys_write( + static_cast(fd), + reinterpret_cast(data), + size, 0, 0, 0); + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + + if (written < 0) return static_cast(written); + if (static_cast(written) != size) return -EIO; + return 0; +} + +/* ================================================================== */ +/* Helper: ensure current task has VFS context for file ops */ +/* ================================================================== */ + +struct ExecTestCtx { + TaskControlBlock* curr; + VmSpace* old_mm; + arch::TrapFrame fake_tf; + arch::TrapFrame* old_tf; + uint64 saved_satp; +}; + +static void exec_test_setup(ExecTestCtx* ctx) +{ + ctx->curr = current_task(); + + /* Save kernel SATP so we can restore it in teardown */ + asm volatile("csrr %0, satp" : "=r"(ctx->saved_satp)); + + /* Save and replace mm with a fresh one */ + ctx->old_mm = ctx->curr->mm; + ctx->curr->mm = VmSpace::create(); + + /* Set up a fake trapframe so do_execve can modify it */ + for (uint64 i = 0; i < sizeof(ctx->fake_tf); i++) + reinterpret_cast(&ctx->fake_tf)[i] = 0; + ctx->old_tf = ctx->curr->trapframe; + ctx->curr->trapframe = &ctx->fake_tf; +} + +static void exec_test_teardown(ExecTestCtx* ctx) +{ + /* Restore trapframe */ + ctx->curr->trapframe = ctx->old_tf; + + /* Destroy the test mm (may have been replaced by do_execve) */ + VmSpace* test_mm = ctx->curr->mm; + ctx->curr->mm = ctx->old_mm; + if (test_mm && test_mm != ctx->old_mm) { + /* If do_execve succeeded, it activated new_mm's page table. + * Switch back to the kernel page table before destroying. */ + asm volatile("csrw satp, %0" :: "r"(ctx->saved_satp)); + asm volatile("sfence.vma"); + test_mm->put(); + } +} + +/* ================================================================== */ +/* Test cases */ +/* ================================================================== */ + +void selftest_exec() +{ + /* ---- 1a. Non-existent path under missing directory ---- */ + st_begin("exec: missing path returns error"); + { + const char* argv[] = { "/no_such_dir/no_file", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/no_such_dir/no_file", argv, envp); + ST_ASSERT(ret < 0); /* -ENOENT from filename_lookup */ + } + st_pass(); + + /* ---- 1b. Directory is not executable ---- */ + st_begin("exec: directory returns -EACCES"); + { + /* "/" exists and is a directory */ + const char* argv[] = { "/dev", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/dev", argv, envp); + ST_ASSERT_EQ(ret, -EACCES); + } + st_pass(); + + /* ---- 2. Bad ELF magic ---- */ + st_begin("exec: bad ELF magic returns -ENOEXEC"); + { + uint8 garbage[128]; + for (int i = 0; i < 128; i++) garbage[i] = 0xAA; + int err = create_test_file("/test_bad_magic", garbage, 128); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_bad_magic", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_bad_magic", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 3. Wrong ELF class (32-bit) ---- */ + st_begin("exec: 32-bit ELF returns -ENOEXEC"); + { + TestElf elf; + build_minimal_elf(&elf); + elf.ehdr.e_ident[EI_CLASS] = 1; /* ELFCLASS32 */ + int err = create_test_file("/test_elf32", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_elf32", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_elf32", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 4. Wrong endianness (big-endian) ---- */ + st_begin("exec: big-endian ELF returns -ENOEXEC"); + { + TestElf elf; + build_minimal_elf(&elf); + elf.ehdr.e_ident[EI_DATA] = 2; /* ELFDATA2MSB */ + int err = create_test_file("/test_be", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_be", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_be", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 5. Wrong machine (x86_64 instead of RISC-V) ---- */ + st_begin("exec: wrong machine type returns -ENOEXEC"); + { + TestElf elf; + build_minimal_elf(&elf); + elf.ehdr.e_machine = 62; /* EM_X86_64 */ + int err = create_test_file("/test_x86", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_x86", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_x86", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 6. Truncated ELF (too small for header) ---- */ + st_begin("exec: truncated ELF returns -ENOEXEC"); + { + /* Valid magic but file is too short for full header */ + uint8 tiny[8] = { 0x7f, 'E', 'L', 'F', 2, 1, 1, 0 }; + int err = create_test_file("/test_tiny", tiny, sizeof(tiny)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_tiny", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_tiny", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 7. Zero program headers ---- */ + st_begin("exec: zero phnum returns -ENOEXEC"); + { + TestElf elf; + build_minimal_elf(&elf); + elf.ehdr.e_phnum = 0; + int err = create_test_file("/test_nophdr", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_nophdr", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_nophdr", argv, envp); + ST_ASSERT_EQ(ret, -ENOEXEC); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 8. Valid ELF: successful load and trapframe setup ---- */ + st_begin("exec: valid ELF loads and sets up trapframe"); + { + TestElf elf; + build_minimal_elf(&elf); + int err = create_test_file("/test_valid", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_valid", nullptr }; + const char* envp[] = { "HOME=/", nullptr }; + int ret = do_execve("/test_valid", argv, envp); + ST_ASSERT_EQ(ret, 0); + + /* Verify trapframe was set up correctly */ + arch::TrapFrame* tf = &ctx.fake_tf; + + /* Entry point should match what we set in the ELF */ + uint64 code_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr); + uint64 expected_entry = 0x10000 + code_offset; + ST_ASSERT_EQ(tf->epc, expected_entry); + + /* Stack pointer should be in user space, below the stack top */ + ST_ASSERT(tf->sp > 0); + ST_ASSERT(tf->sp < kUserStackTop); + ST_ASSERT(tf->sp >= kUserStackBase); + + /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts enabled on sret) */ + uint64 spp_bit = (1ULL << 8); + uint64 spie_bit = (1ULL << 5); + ST_ASSERT_EQ(tf->sstatus & spp_bit, 0ULL); /* Not S-mode */ + ST_ASSERT_EQ(tf->sstatus & spie_bit, spie_bit); /* SPIE set */ + + /* General registers should be zeroed */ + ST_ASSERT_EQ(tf->ra, 0ULL); + ST_ASSERT_EQ(tf->gp, 0ULL); + ST_ASSERT_EQ(tf->tp, 0ULL); + for (int i = 0; i < 7; i++) + ST_ASSERT_EQ(tf->t[i], 0ULL); + for (int i = 0; i < 12; i++) + ST_ASSERT_EQ(tf->s[i], 0ULL); + for (int i = 0; i < 8; i++) + ST_ASSERT_EQ(tf->a[i], 0ULL); + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 9. Verify user stack layout (argc/argv) ---- */ + st_begin("exec: user stack has correct argc/argv layout"); + { + TestElf elf; + build_minimal_elf(&elf); + int err = create_test_file("/test_stack", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + const char* argv[] = { "/test_stack", "arg1", nullptr }; + const char* envp[] = { "A=B", nullptr }; + int ret = do_execve("/test_stack", argv, envp); + ST_ASSERT_EQ(ret, 0); + + /* The stack was set up in the new mm. + * Read argc from the top of the stack using pmap lookup. */ + VmSpace* mm = ctx.curr->mm; + uint64 sp = ctx.fake_tf.sp; + + /* Look up the physical page for sp */ + uint64 pa = 0; + bool mapped = pmap::lookup(mm->root_pa, sp & ~(kPageSize - 1), &pa); + ST_ASSERT(mapped); + + /* Read argc (first uint64 on stack) */ + uint64 page_offset = sp & (kPageSize - 1); + uint8* kva = reinterpret_cast(pa_to_va(PhysAddr{pa}).raw); + uint64 argc = *reinterpret_cast(kva + page_offset); + ST_ASSERT_EQ(argc, 2ULL); /* "/test_stack" and "arg1" */ + + exec_test_teardown(&ctx); + } + st_pass(); + + /* ---- 10. Multiple consecutive execve replaces mm correctly ---- */ + st_begin("exec: consecutive execve replaces address space"); + { + TestElf elf; + build_minimal_elf(&elf); + int err = create_test_file("/test_reexec", &elf, sizeof(elf)); + ST_ASSERT_EQ(err, 0); + + ExecTestCtx ctx; + exec_test_setup(&ctx); + + /* First exec */ + const char* argv[] = { "/test_reexec", nullptr }; + const char* envp[] = { nullptr }; + int ret = do_execve("/test_reexec", argv, envp); + ST_ASSERT_EQ(ret, 0); + + VmSpace* first_mm = ctx.curr->mm; + ST_ASSERT(first_mm != nullptr); + + /* Second exec — should replace the first mm */ + ret = do_execve("/test_reexec", argv, envp); + ST_ASSERT_EQ(ret, 0); + + /* mm should be different (new address space) */ + VmSpace* second_mm = ctx.curr->mm; + ST_ASSERT(second_mm != nullptr); + ST_ASSERT_NE(first_mm, second_mm); + + exec_test_teardown(&ctx); + } + st_pass(); +} diff --git a/test/test_fdt.cc b/test/test_fdt.cc new file mode 100644 index 0000000..cf09029 --- /dev/null +++ b/test/test_fdt.cc @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: FDT (Flattened Device Tree) + */ + +#include +#include + +// Manually declare what we need to avoid header conflicts with toolchain libc +extern "C" int strcmp(const char *s1, const char *s2); + +void selftest_fdt() { + st_begin("fdt: tree root access"); + { + DeviceNode* root = fdt_get_node_by_path("/"); + ST_ASSERT(root != nullptr); + ST_ASSERT_EQ(strcmp(root->get_name(), ""), 0); + } + st_pass(); + + st_begin("fdt: find existing compatible node (UART)"); + { + // QEMU virt RISC-V platform always has a ns16550a UART + DeviceNode* uart = fdt_find_compatible("ns16550a"); + ST_ASSERT(uart != nullptr); + + // Use get_prop_string which is convenience helper + const char* compat = uart->get_prop_string("compatible"); + ST_ASSERT(compat != nullptr); + + // Check raw property access + const FdtProperty* reg = uart->get_prop("reg"); + ST_ASSERT(reg != nullptr); + ST_ASSERT(reg->value != nullptr); + ST_ASSERT(reg->len > 0); + } + st_pass(); + + st_begin("fdt: non-existent node/compatible queries"); + { + DeviceNode* missing1 = fdt_get_node_by_path("/definitely_does_not_exist_node_12345"); + ST_ASSERT(missing1 == nullptr); + + DeviceNode* missing2 = fdt_find_compatible("invalid_compatible_string_xyz"); + ST_ASSERT(missing2 == nullptr); + } + st_pass(); +} diff --git a/test/test_klog.cc b/test/test_klog.cc new file mode 100644 index 0000000..896e7ed --- /dev/null +++ b/test/test_klog.cc @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: klog + * + * Tests: + * 1. read vs read_consume semantics + * 2. subsystem mask filtering + * 3. ratelimit bookkeeping + * 4. flush advances console tail when console filtering is active + * 5. seq counter increments on writes + */ + +#include +#include +#include + +static void drain_klog() +{ + static char buf[512]; + while (true) { + int n = klog_read_consume(buf, sizeof(buf)); + if (n <= 0) { + break; + } + } +} + +static bool test_read_peek_and_consume() +{ + drain_klog(); + + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_a"); + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_b"); + + KlogStats s0; + klog_stats(&s0); + + char buf[1024]; + int n = klog_read(buf, sizeof(buf) - 1); + if (n <= 0) { + return false; + } + buf[n] = '\0'; + if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { + return false; + } + + KlogStats s1; + klog_stats(&s1); + if (s1.tail != s0.tail) { + return false; + } + + int n2 = klog_read_consume(buf, sizeof(buf) - 1); + if (n2 <= 0) { + return false; + } + buf[n2] = '\0'; + if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { + return false; + } + + KlogStats s2; + klog_stats(&s2); + return s2.tail != s0.tail; +} + +static bool test_subsys_mask() +{ + drain_klog(); + + const uint64 all_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; + klog_set_subsys_mask(1ULL << KLOG_SUBSYS_TEST); + + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_subsys_ok"); + klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, "klog_subsys_drop"); + + char buf[1024]; + int n = klog_read_consume(buf, sizeof(buf) - 1); + klog_set_subsys_mask(all_mask); + + if (n <= 0) { + return false; + } + buf[n] = '\0'; + return strstr(buf, "klog_subsys_ok") && !strstr(buf, "klog_subsys_drop"); +} + +static bool test_ratelimit() +{ + KlogRateLimit rl = {}; + rl.interval = 100; + rl.burst = 2; + + bool a = klog_ratelimit(&rl, 10); + bool b = klog_ratelimit(&rl, 20); + bool c = klog_ratelimit(&rl, 30); + + if (!a || !b || c) { + return false; + } + if (rl.printed != 2 || rl.suppressed != 1) { + return false; + } + + bool d = klog_ratelimit(&rl, 200); + if (!d) { + return false; + } + return rl.printed == 1 && rl.suppressed == 0; +} + +static bool test_flush_advances_console_tail() +{ + drain_klog(); + + klog_set_level(KLOG_ERR); + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_flush_msg"); + + KlogStats s1; + klog_stats(&s1); + if (s1.console_tail == s1.head) { + klog_set_level(static_cast(KLOG_DEFAULT_LEVEL)); + return false; + } + + klog_flush(); + + KlogStats s2; + klog_stats(&s2); + + klog_set_level(static_cast(KLOG_DEFAULT_LEVEL)); + return s2.console_tail == s2.head; +} + +static bool test_seq_increments() +{ + drain_klog(); + + KlogStats s0; + klog_stats(&s0); + + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_1"); + klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_2"); + + KlogStats s1; + klog_stats(&s1); + + return s1.seq >= s0.seq + 2; +} + +void selftest_klog() +{ + st_begin("klog: read vs read_consume"); + ST_ASSERT(test_read_peek_and_consume()); + st_pass(); + + st_begin("klog: subsys mask filters"); + ST_ASSERT(test_subsys_mask()); + st_pass(); + + st_begin("klog: ratelimit bookkeeping"); + ST_ASSERT(test_ratelimit()); + st_pass(); + + st_begin("klog: flush advances console tail"); + ST_ASSERT(test_flush_advances_console_tail()); + st_pass(); + + st_begin("klog: seq increments on write"); + ST_ASSERT(test_seq_increments()); + st_pass(); +} diff --git a/test/test_mutex.cc b/test/test_mutex.cc new file mode 100644 index 0000000..e86c984 --- /dev/null +++ b/test/test_mutex.cc @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_mutex — self-tests for the Mutex sleeping lock. + * + * Coverage: + * - basic lock/unlock single-thread + * - try_lock when free and when held + * - mutual exclusion across kthreads (no lost increments) + * - FIFO wake order (A acquires, B/C queue; release lets B in then C) + */ + +#include +#include +#include +#include +#include + +extern TaskControlBlock* kthread_create(const char* name, + void (*entry)(void*), void* arg, + uint8 priority); +extern void task_exit(int code); + +/* ------------------------------------------------------------------ */ +/* Test 1: basic lock/unlock */ +/* ------------------------------------------------------------------ */ + +static void test_basic() +{ + st_begin("mutex: basic lock/unlock"); + + Mutex m; + m.init("test_basic"); + + ST_ASSERT(!m.is_locked()); + m.lock(); + ST_ASSERT(m.is_locked()); + ST_ASSERT(m.is_owned_by_current()); + m.unlock(); + ST_ASSERT(!m.is_locked()); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Test 2: try_lock */ +/* ------------------------------------------------------------------ */ + +static void test_trylock() +{ + st_begin("mutex: try_lock"); + + Mutex m; + m.init("test_trylock"); + + ST_ASSERT(m.try_lock() == true); + ST_ASSERT(m.is_locked()); + + /* A second try_lock from the same task: since owner == current, + * we still return false (try_lock is non-recursive). */ + ST_ASSERT(m.try_lock() == false); + + m.unlock(); + ST_ASSERT(!m.is_locked()); + ST_ASSERT(m.try_lock() == true); + m.unlock(); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Test 3: mutual exclusion across kthreads */ +/* ------------------------------------------------------------------ */ + +static Mutex g_mut; +static volatile int32 g_counter = 0; +static volatile int32 g_done = 0; +static constexpr int32 kIters = 200; + +static void mutex_worker(void*) +{ + for (int i = 0; i < kIters; i++) { + g_mut.lock(); + int32 v = g_counter; + /* Yield inside the critical section to expose missing mutex. + * If the mutex were broken we'd see lost increments. */ + sched_yield(); + g_counter = v + 1; + g_mut.unlock(); + } + __sync_fetch_and_add(&g_done, 1); + task_exit(0); +} + +static void test_mutual_exclusion() +{ + st_begin("mutex: mutual exclusion (2 workers)"); + + g_mut.init("test_mutex_mx"); + g_counter = 0; + g_done = 0; + + kthread_create("mut_w1", mutex_worker, nullptr, 0); + kthread_create("mut_w2", mutex_worker, nullptr, 0); + + /* Spin-yield until both workers finish. */ + while (g_done < 2) { + sched_yield(); + } + + ST_ASSERT_EQ(g_counter, 2 * kIters); + ST_ASSERT(!g_mut.is_locked()); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Test 4: FIFO wake order */ +/* ------------------------------------------------------------------ */ + +static Mutex g_fifo_mut; +static volatile int32 g_fifo_seq = 0; // monotonically increases +static volatile int32 g_fifo_order[3]; // order each worker observed +static volatile int32 g_fifo_done = 0; + +static void fifo_worker(void* arg) +{ + int slot = static_cast(reinterpret_cast(arg)); + g_fifo_mut.lock(); + g_fifo_order[slot] = __sync_fetch_and_add(&g_fifo_seq, 1); + g_fifo_mut.unlock(); + __sync_fetch_and_add(&g_fifo_done, 1); + task_exit(0); +} + +static void test_fifo_order() +{ + st_begin("mutex: FIFO wake order (3 waiters)"); + + g_fifo_mut.init("test_mutex_fifo"); + g_fifo_seq = 0; + g_fifo_done = 0; + g_fifo_order[0] = g_fifo_order[1] = g_fifo_order[2] = -1; + + /* Main grabs the lock first so workers will queue up behind. */ + g_fifo_mut.lock(); + + /* Spawn workers in deterministic order. After each create() yield + * once so the new thread reaches mutex.lock() and enqueues itself + * before we spawn the next one. */ + kthread_create("fifo_A", fifo_worker, reinterpret_cast(0), 0); + sched_yield(); + kthread_create("fifo_B", fifo_worker, reinterpret_cast(1), 0); + sched_yield(); + kthread_create("fifo_C", fifo_worker, reinterpret_cast(2), 0); + sched_yield(); + + /* Release: A should fire next, then B, then C. */ + g_fifo_mut.unlock(); + + while (g_fifo_done < 3) { + sched_yield(); + } + + ST_ASSERT_EQ(g_fifo_order[0], 0); + ST_ASSERT_EQ(g_fifo_order[1], 1); + ST_ASSERT_EQ(g_fifo_order[2], 2); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Entry */ +/* ------------------------------------------------------------------ */ + +void selftest_mutex() +{ + test_basic(); + test_trylock(); + test_mutual_exclusion(); + test_fifo_order(); +} diff --git a/test/test_namei.cc b/test/test_namei.cc new file mode 100644 index 0000000..8e4ecb0 --- /dev/null +++ b/test/test_namei.cc @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_namei.cc — Self-tests for pathname resolution (namei.cc) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern dentry* g_root_dentry; +extern vfsmount* g_root_mnt; + +/* sys_mkdirat from kernel/sys_fs.cc */ +int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); + +void selftest_namei() +{ + /* ---- lookup root "/" ---- */ + st_begin("namei: lookup root /"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de == g_root_dentry); + ST_ASSERT(p.de->d_inode != nullptr); + ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); + } + st_pass(); + + /* ---- lookup /dev (exists from boot) ---- */ + st_begin("namei: lookup /dev"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de != nullptr); + ST_ASSERT(p.de->d_inode != nullptr); + ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); + } + st_pass(); + + /* ---- lookup nonexistent path returns -ENOENT ---- */ + st_begin("namei: lookup nonexistent returns -ENOENT"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/this_does_not_exist", 0, &p); + ST_ASSERT_EQ(err, -ENOENT); + } + st_pass(); + + /* ---- lookup /dev/console (chardev) ---- */ + st_begin("namei: lookup /dev/console"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev/console", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de->d_inode != nullptr); + ST_ASSERT(S_ISCHR(p.de->d_inode->i_mode)); + } + st_pass(); + + /* ---- lookup with LOOKUP_DIRECTORY on non-dir returns -ENOTDIR ---- */ + st_begin("namei: LOOKUP_DIRECTORY on file returns -ENOTDIR"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_DIRECTORY, &p); + ST_ASSERT_EQ(err, -ENOTDIR); + } + st_pass(); + + /* ---- lookup with LOOKUP_DIRECTORY on dir succeeds ---- */ + st_begin("namei: LOOKUP_DIRECTORY on dir succeeds"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev", LOOKUP_DIRECTORY, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); + } + st_pass(); + + /* ---- kern_path shortcut ---- */ + st_begin("namei: kern_path works like filename_lookup"); + { + path p{}; + int err = kern_path("/dev", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de->d_inode != nullptr); + } + st_pass(); + + /* ---- dot path: /dev/. resolves to /dev ---- */ + st_begin("namei: /dev/. resolves to /dev"); + { + path p1{}, p2{}; + int e1 = filename_lookup(AT_FDCWD, "/dev", 0, &p1); + int e2 = filename_lookup(AT_FDCWD, "/dev/.", 0, &p2); + ST_ASSERT_EQ(e1, 0); + ST_ASSERT_EQ(e2, 0); + ST_ASSERT(p1.de == p2.de); + } + st_pass(); + + /* ---- dotdot path: /dev/.. resolves to root ---- */ + st_begin("namei: /dev/.. resolves to root"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev/..", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de == g_root_dentry); + } + st_pass(); + + /* ---- lookup into non-dir returns -ENOTDIR ---- */ + st_begin("namei: path through non-dir returns -ENOTDIR"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev/console/child", 0, &p); + ST_ASSERT_EQ(err, -ENOTDIR); + } + st_pass(); + + /* ---- null/empty paths ---- */ + st_begin("namei: null name returns -EINVAL"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, nullptr, 0, &p); + ST_ASSERT_EQ(err, -EINVAL); + } + st_pass(); + + /* ---- LOOKUP_PARENT: returns parent dentry ---- */ + st_begin("namei: LOOKUP_PARENT returns parent"); + { + path p{}; + int err = filename_lookup(AT_FDCWD, "/dev/console", LOOKUP_PARENT, &p); + ST_ASSERT_EQ(err, 0); + /* Should return /dev, not /dev/console */ + path dev_path{}; + int e2 = filename_lookup(AT_FDCWD, "/dev", 0, &dev_path); + ST_ASSERT_EQ(e2, 0); + ST_ASSERT(p.de == dev_path.de); + } + st_pass(); + + /* ---- mkdir + lookup round-trip ---- */ + st_begin("namei: mkdir then lookup succeeds"); + { + /* Create /test_namei_dir via sys_mkdirat */ + int64 ret = sys_mkdirat( + static_cast(-100), + reinterpret_cast("/test_namei_dir"), + 0755, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* Now lookup should find it */ + path p{}; + int err = filename_lookup(AT_FDCWD, "/test_namei_dir", 0, &p); + ST_ASSERT_EQ(err, 0); + ST_ASSERT(p.de->d_inode != nullptr); + ST_ASSERT(S_ISDIR(p.de->d_inode->i_mode)); + } + st_pass(); + + /* NOTE: trailing slash "/dev/" not tested — Phase 1 namei + * doesn't handle empty trailing components. */ +} diff --git a/test/test_page_cache.cc b/test/test_page_cache.cc new file mode 100644 index 0000000..ee0e0e2 --- /dev/null +++ b/test/test_page_cache.cc @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_page_cache.cc — Self-tests for the page cache hash table + * + * Tests: insert, get, remove, duplicate insert, invalidate, + * get_or_alloc, null args. + */ + +#include +#include +#include +#include +#include + +void selftest_page_cache() +{ + /* Use sentinel pointers as fake "mapping" identifiers. + * These are never dereferenced — only used as hash keys. */ + int mapping_a_tag = 0; + int mapping_b_tag = 0; + void* mapping_a = &mapping_a_tag; + void* mapping_b = &mapping_b_tag; + + /* ---- insert and get ---- */ + st_begin("page_cache: insert then get returns same page"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + + int ret = page_cache_insert(mapping_a, 0, page); + ST_ASSERT_EQ(ret, 0); + + Page* got = page_cache_get(mapping_a, 0); + ST_ASSERT_EQ(got, page); + + /* put the reference from get */ + page_cache_put(got); + + /* Clean up */ + page_cache_remove(mapping_a, 0); + pmm_free_page(page); + } + st_pass(); + + /* ---- get nonexistent returns nullptr ---- */ + st_begin("page_cache: get nonexistent returns nullptr"); + { + Page* got = page_cache_get(mapping_a, 99999); + ST_ASSERT(got == nullptr); + } + st_pass(); + + /* ---- duplicate insert returns -EEXIST ---- */ + st_begin("page_cache: duplicate insert returns -EEXIST"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + + int ret1 = page_cache_insert(mapping_a, 1, page); + ST_ASSERT_EQ(ret1, 0); + + Page* page2 = pmm_alloc_page(); + ST_ASSERT(page2 != nullptr); + + int ret2 = page_cache_insert(mapping_a, 1, page2); + ST_ASSERT_EQ(ret2, -EEXIST); + + /* Clean up */ + page_cache_remove(mapping_a, 1); + pmm_free_page(page); + pmm_free_page(page2); + } + st_pass(); + + /* ---- remove returns page ---- */ + st_begin("page_cache: remove returns page and deletes entry"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + + page_cache_insert(mapping_a, 2, page); + + Page* removed = page_cache_remove(mapping_a, 2); + ST_ASSERT_EQ(removed, page); + + /* Should be gone now */ + Page* got = page_cache_get(mapping_a, 2); + ST_ASSERT(got == nullptr); + + pmm_free_page(page); + } + st_pass(); + + /* ---- remove nonexistent returns nullptr ---- */ + st_begin("page_cache: remove nonexistent returns nullptr"); + { + Page* got = page_cache_remove(mapping_a, 77777); + ST_ASSERT(got == nullptr); + } + st_pass(); + + /* ---- different mappings are independent ---- */ + st_begin("page_cache: different mappings are independent"); + { + Page* pa = pmm_alloc_page(); + Page* pb = pmm_alloc_page(); + ST_ASSERT(pa != nullptr); + ST_ASSERT(pb != nullptr); + + page_cache_insert(mapping_a, 5, pa); + page_cache_insert(mapping_b, 5, pb); + + Page* got_a = page_cache_get(mapping_a, 5); + Page* got_b = page_cache_get(mapping_b, 5); + ST_ASSERT_EQ(got_a, pa); + ST_ASSERT_EQ(got_b, pb); + ST_ASSERT_NE(got_a, got_b); + + page_cache_put(got_a); + page_cache_put(got_b); + page_cache_remove(mapping_a, 5); + page_cache_remove(mapping_b, 5); + pmm_free_page(pa); + pmm_free_page(pb); + } + st_pass(); + + /* ---- invalidate removes all entries for a mapping ---- */ + st_begin("page_cache: invalidate removes all entries for mapping"); + { + Page* pages[4]; + for (int i = 0; i < 4; i++) { + pages[i] = pmm_alloc_page(); + ST_ASSERT(pages[i] != nullptr); + page_cache_insert(mapping_a, 10 + i, pages[i]); + } + + /* Also insert one for mapping_b */ + Page* pb = pmm_alloc_page(); + page_cache_insert(mapping_b, 10, pb); + + /* Invalidate mapping_a */ + page_cache_invalidate(mapping_a); + + /* All mapping_a entries should be gone */ + for (int i = 0; i < 4; i++) { + Page* got = page_cache_get(mapping_a, 10 + i); + ST_ASSERT(got == nullptr); + pmm_free_page(pages[i]); + } + + /* mapping_b should still be there */ + Page* got_b = page_cache_get(mapping_b, 10); + ST_ASSERT_EQ(got_b, pb); + page_cache_put(got_b); + page_cache_remove(mapping_b, 10); + pmm_free_page(pb); + } + st_pass(); + + /* ---- insert null mapping returns -EINVAL ---- */ + st_begin("page_cache: insert null mapping returns -EINVAL"); + { + Page* page = pmm_alloc_page(); + int ret = page_cache_insert(nullptr, 0, page); + ST_ASSERT_EQ(ret, -EINVAL); + pmm_free_page(page); + } + st_pass(); + + /* ---- insert null page returns -EINVAL ---- */ + st_begin("page_cache: insert null page returns -EINVAL"); + { + int ret = page_cache_insert(mapping_a, 0, nullptr); + ST_ASSERT_EQ(ret, -EINVAL); + } + st_pass(); +} diff --git a/test/test_pid.cc b/test/test_pid.cc new file mode 100644 index 0000000..e79eb1e --- /dev/null +++ b/test/test_pid.cc @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_pid.cc — Self-tests for PID allocator + * + * Tests: alloc, free, re-use, exhaustion guard, reserved PIDs. + */ + +#include +#include + +void selftest_pid() +{ + /* Create a local PidAllocator for isolated testing */ + PidAllocator pa; + pa.init(); + + /* ---- PID 0 is reserved (idle/swapper) ---- */ + st_begin("pid: PID 0 is reserved"); + { + /* First alloc should return 1 (PID 0 reserved for idle) */ + int32 first = pa.alloc(); + ST_ASSERT(first >= 1); + } + st_pass(); + + /* ---- Sequential allocation returns unique PIDs ---- */ + st_begin("pid: sequential allocation returns unique PIDs"); + { + PidAllocator pa2; + pa2.init(); + + int32 p1 = pa2.alloc(); + int32 p2 = pa2.alloc(); + int32 p3 = pa2.alloc(); + ST_ASSERT(p1 >= 1); + ST_ASSERT(p2 >= 1); + ST_ASSERT(p3 >= 1); + ST_ASSERT_NE(p1, p2); + ST_ASSERT_NE(p2, p3); + ST_ASSERT_NE(p1, p3); + } + st_pass(); + + /* ---- Freed PID can be reused ---- */ + st_begin("pid: freed PID can be reused"); + { + PidAllocator pa3; + pa3.init(); + + /* Allocate all PIDs from 2..N */ + constexpr int kAllocCount = 60; + int32 pids[kAllocCount]; + for (int i = 0; i < kAllocCount; i++) { + pids[i] = pa3.alloc(); + ST_ASSERT(pids[i] >= 1); + } + + /* Free one in the middle */ + int32 freed_pid = pids[kAllocCount / 2]; + pa3.free(freed_pid); + + /* Allocate again — should eventually get the freed PID */ + bool found = false; + for (int i = 0; i < kAllocCount; i++) { + int32 p = pa3.alloc(); + if (p == freed_pid) { found = true; break; } + pa3.free(p); + } + ST_ASSERT(found); + } + st_pass(); + + /* ---- free invalid PID is safe ---- */ + st_begin("pid: free invalid PID is no-op"); + { + PidAllocator pa4; + pa4.init(); + + /* These should not crash */ + pa4.free(-1); + pa4.free(kMaxPid); + pa4.free(kMaxPid + 100); + + /* Allocator should still work — PID 0 still reserved */ + int32 p = pa4.alloc(); + ST_ASSERT(p >= 1); + } + st_pass(); + + /* ---- Monotonic allocation before wrap ---- */ + st_begin("pid: monotonic allocation"); + { + PidAllocator pa5; + pa5.init(); + + int32 prev = pa5.alloc(); + for (int i = 0; i < 10; i++) { + int32 next = pa5.alloc(); + ST_ASSERT(next > prev); + prev = next; + } + } + st_pass(); +} diff --git a/test/test_pipe_devfs.cc b/test/test_pipe_devfs.cc new file mode 100644 index 0000000..ed375ed --- /dev/null +++ b/test/test_pipe_devfs.cc @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_pipe_devfs.cc — Self-tests for pipe, /dev/null, /dev/zero, fcntl + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Syscall entry points */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static int64 open_dev(const char* path, int flags) +{ + return sys_openat( + static_cast(-100), /* AT_FDCWD */ + reinterpret_cast(path), + static_cast(flags), + 0, 0, 0); +} + +static int64 do_close(int64 fd) +{ + return sys_close(static_cast(fd), 0, 0, 0, 0, 0); +} + +static int64 do_read(int64 fd, void* buf, uint64 count) +{ + return sys_read(static_cast(fd), + reinterpret_cast(buf), count, 0, 0, 0); +} + +static int64 do_write(int64 fd, const void* buf, uint64 count) +{ + return sys_write(static_cast(fd), + reinterpret_cast(buf), count, 0, 0, 0); +} + +/* fcntl command constants — must match sys_fs.cc */ +inline constexpr int F_DUPFD = 0; +inline constexpr int F_GETFD = 1; +inline constexpr int F_SETFD = 2; +inline constexpr int F_GETFL = 3; +inline constexpr int F_SETFL = 4; +inline constexpr int F_DUPFD_CLOEXEC = 1030; +inline constexpr int FD_CLOEXEC = 1; + +/* ================================================================== */ +/* /dev/null tests */ +/* ================================================================== */ + +void selftest_devfs() +{ + /* ---- /dev/null: read returns EOF ---- */ + st_begin("devfs: /dev/null read returns 0 (EOF)"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + char buf[16]; + int64 n = do_read(fd, buf, sizeof(buf)); + ST_ASSERT_EQ(n, (int64)0); + do_close(fd); + } + st_pass(); + + /* ---- /dev/null: write discards data ---- */ + st_begin("devfs: /dev/null write discards data"); + { + int64 fd = open_dev("/dev/null", O_WRONLY); + ST_ASSERT(fd >= 0); + + const char* msg = "discarded"; + int64 n = do_write(fd, msg, 9); + ST_ASSERT_EQ(n, (int64)9); + do_close(fd); + } + st_pass(); + + /* ---- /dev/null: fstat reports S_IFCHR ---- */ + st_begin("devfs: /dev/null fstat is S_IFCHR"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + kstat st{}; + int64 ret = sys_fstat_wrapper( + static_cast(fd), + reinterpret_cast(&st), 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + ST_ASSERT(S_ISCHR(st.st_mode)); + do_close(fd); + } + st_pass(); + + /* ---- /dev/zero: read returns zeros ---- */ + st_begin("devfs: /dev/zero read returns zeros"); + { + int64 fd = open_dev("/dev/zero", O_RDONLY); + ST_ASSERT(fd >= 0); + + char buf[32]; + /* Pre-fill with non-zero */ + for (int i = 0; i < 32; i++) buf[i] = 0xFF; + + int64 n = do_read(fd, buf, 32); + ST_ASSERT_EQ(n, (int64)32); + + bool all_zero = true; + for (int i = 0; i < 32; i++) { + if (buf[i] != 0) { all_zero = false; break; } + } + ST_ASSERT(all_zero); + do_close(fd); + } + st_pass(); + + /* ---- /dev/zero: write discards data ---- */ + st_begin("devfs: /dev/zero write discards data"); + { + int64 fd = open_dev("/dev/zero", O_WRONLY); + ST_ASSERT(fd >= 0); + + char buf[16] = {1, 2, 3}; + int64 n = do_write(fd, buf, 16); + ST_ASSERT_EQ(n, (int64)16); + do_close(fd); + } + st_pass(); +} + +/* ================================================================== */ +/* Pipe tests */ +/* ================================================================== */ + +void selftest_pipe() +{ + /* ---- pipe2: basic creation ---- */ + st_begin("pipe: pipe2 creates two valid fds"); + { + int fds[2] = {-1, -1}; + int64 ret = sys_pipe2( + reinterpret_cast(fds), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + ST_ASSERT(fds[0] >= 0); + ST_ASSERT(fds[1] >= 0); + ST_ASSERT_NE(fds[0], fds[1]); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: write then read ---- */ + st_begin("pipe: write then read returns correct data"); + { + int fds[2]; + sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); + + const char* msg = "pipe_test"; + int64 w = do_write(fds[1], msg, 9); + ST_ASSERT_EQ(w, (int64)9); + + char buf[16] = {}; + int64 r = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r, (int64)9); + + bool match = true; + for (int i = 0; i < 9; i++) { + if (buf[i] != msg[i]) { match = false; break; } + } + ST_ASSERT(match); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: read from closed write end returns EOF ---- */ + st_begin("pipe: read after writer close returns EOF"); + { + int fds[2]; + sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); + + /* Write some data, then close write end */ + const char* msg = "eof"; + do_write(fds[1], msg, 3); + do_close(fds[1]); + + /* Read the data */ + char buf[16]; + int64 r1 = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r1, (int64)3); + + /* Next read should return 0 (EOF) */ + int64 r2 = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r2, (int64)0); + + do_close(fds[0]); + } + st_pass(); + + /* ---- pipe: write to closed read end returns -EPIPE ---- */ + st_begin("pipe: write after reader close returns -EPIPE"); + { + int fds[2]; + sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); + + /* Close read end */ + do_close(fds[0]); + + /* Write should fail */ + const char* msg = "broken"; + int64 w = do_write(fds[1], msg, 6); + ST_ASSERT_EQ(w, (int64)-EPIPE); + + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: multiple writes accumulate ---- */ + st_begin("pipe: multiple writes accumulate in buffer"); + { + int fds[2]; + sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); + + do_write(fds[1], "AAA", 3); + do_write(fds[1], "BBB", 3); + + char buf[16] = {}; + int64 r = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r, (int64)6); + ST_ASSERT_EQ(buf[0], 'A'); + ST_ASSERT_EQ(buf[3], 'B'); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: read partial ---- */ + st_begin("pipe: partial read leaves remainder"); + { + int fds[2]; + sys_pipe2(reinterpret_cast(fds), 0, 0, 0, 0, 0); + + do_write(fds[1], "ABCDEF", 6); + + char buf[3] = {}; + int64 r1 = do_read(fds[0], buf, 3); + ST_ASSERT_EQ(r1, (int64)3); + ST_ASSERT_EQ(buf[0], 'A'); + ST_ASSERT_EQ(buf[2], 'C'); + + int64 r2 = do_read(fds[0], buf, 3); + ST_ASSERT_EQ(r2, (int64)3); + ST_ASSERT_EQ(buf[0], 'D'); + ST_ASSERT_EQ(buf[2], 'F'); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); +} + +/* ================================================================== */ +/* fcntl tests */ +/* ================================================================== */ + +void selftest_fcntl() +{ + /* ---- fcntl F_DUPFD ---- */ + st_begin("fcntl: F_DUPFD duplicates fd"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + ST_ASSERT_NE(fd, fd2); + + /* Both should be valid reads */ + char buf[4]; + int64 r = do_read(fd2, buf, 4); + ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */ + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl F_DUPFD with min_fd ---- */ + st_begin("fcntl: F_DUPFD respects min_fd"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD, 100, 0, 0, 0); + ST_ASSERT(fd2 >= 100); + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl F_GETFD / F_SETFD ---- */ + st_begin("fcntl: F_GETFD/F_SETFD cloexec"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + /* Initially no CLOEXEC */ + int64 flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)0); + + /* Set CLOEXEC */ + int64 ret = sys_fcntl(static_cast(fd), F_SETFD, FD_CLOEXEC, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* Verify it was set */ + flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); + + /* Clear it */ + sys_fcntl(static_cast(fd), F_SETFD, 0, 0, 0, 0); + flags = sys_fcntl(static_cast(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_GETFL ---- */ + st_begin("fcntl: F_GETFL returns open flags"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); + /* O_RDONLY == 0, so result should have no write bits */ + ST_ASSERT_EQ(fl & O_WRONLY, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_SETFL O_NONBLOCK ---- */ + st_begin("fcntl: F_SETFL toggles O_NONBLOCK"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + /* Set O_NONBLOCK */ + sys_fcntl(static_cast(fd), F_SETFL, O_NONBLOCK, 0, 0, 0); + int64 fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); + ST_ASSERT(fl & O_NONBLOCK); + + /* Clear it */ + sys_fcntl(static_cast(fd), F_SETFL, 0, 0, 0, 0); + fl = sys_fcntl(static_cast(fd), F_GETFL, 0, 0, 0, 0); + ST_ASSERT_EQ(fl & O_NONBLOCK, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_DUPFD_CLOEXEC ---- */ + st_begin("fcntl: F_DUPFD_CLOEXEC sets cloexec"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast(fd), F_DUPFD_CLOEXEC, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + + int64 flags = sys_fcntl(static_cast(fd2), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl bad fd ---- */ + st_begin("fcntl: bad fd returns -EBADF"); + { + int64 ret = sys_fcntl(999, F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EBADF); + } + st_pass(); + + /* ---- fcntl unknown cmd ---- */ + st_begin("fcntl: unknown cmd returns -EINVAL"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 ret = sys_fcntl(static_cast(fd), 9999, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + do_close(fd); + } + st_pass(); +} diff --git a/test/test_pmm.cc b/test/test_pmm.cc new file mode 100644 index 0000000..3330866 --- /dev/null +++ b/test/test_pmm.cc @@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: Physical Memory Manager (PMM) + * + * Tests: + * 1. Single page alloc/free round-trip + * 2. Page address alignment (4KB) + * 3. No duplicate pages from consecutive allocs + * 4. Free then re-alloc returns the same page (PCP cache) + * 5. Multi-page (order > 0) alloc/free + * 6. Bulk alloc stress test (alloc many, free all) + * 7. Reference counting (incref/decref) + * 8. page_to_pa / pa_to_page round-trip + */ + +#include +#include +#include + +void selftest_pmm() +{ + st_begin("pmm: single page alloc/free"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + ST_ASSERT(page->type == PageType::Allocated); + ST_ASSERT(page->order == 0); + + uint64 pa = page_to_pa(page).raw; + ST_ASSERT_EQ(pa % kPageSize, 0ULL); // 4KB aligned + + uint64 va = page_to_va(page).raw; + ST_ASSERT_EQ(va, pa_to_va(phys_addr(pa)).raw); + + pmm_free_page(page); + } + st_pass(); + + st_begin("pmm: no duplicate pages"); + { + Page* p1 = pmm_alloc_page(); + Page* p2 = pmm_alloc_page(); + ST_ASSERT(p1 != nullptr); + ST_ASSERT(p2 != nullptr); + ST_ASSERT_NE(p1, p2); + ST_ASSERT_NE(page_to_pa(p1).raw, page_to_pa(p2).raw); + pmm_free_page(p2); + pmm_free_page(p1); + } + st_pass(); + + st_begin("pmm: free then re-alloc (PCP)"); + { + Page* p1 = pmm_alloc_page(); + uint64 pa1 = page_to_pa(p1).raw; + pmm_free_page(p1); + + // PCP should return the most recently freed page + Page* p2 = pmm_alloc_page(); + uint64 pa2 = page_to_pa(p2).raw; + ST_ASSERT_EQ(pa1, pa2); + pmm_free_page(p2); + } + st_pass(); + + st_begin("pmm: multi-page alloc (order 2 = 4 pages)"); + { + Page* page = pmm_alloc_pages(2); + ST_ASSERT(page != nullptr); + ST_ASSERT(page->order == 2); + + uint64 pa = page_to_pa(page).raw; + ST_ASSERT_EQ(pa % (kPageSize * 4), 0ULL); // Naturally aligned + + pmm_free_pages(page, 2); + } + st_pass(); + + st_begin("pmm: bulk alloc/free stress (256 pages)"); + { + constexpr int N = 256; + Page* pages[N]; + + // Allocate all + for (int i = 0; i < N; i++) { + pages[i] = pmm_alloc_page(); + ST_ASSERT(pages[i] != nullptr); + } + + // Verify no duplicates (spot check: consecutive pairs) + for (int i = 1; i < N; i++) { + ST_ASSERT_NE(pages[i], pages[i - 1]); + } + + // Free all + for (int i = 0; i < N; i++) { + pmm_free_page(pages[i]); + } + } + st_pass(); + + st_begin("pmm: page_to_pa / pa_to_page round-trip"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + uint64 pa = page_to_pa(page).raw; + Page* page2 = pa_to_page(phys_addr(pa)); + ST_ASSERT_EQ(page, page2); + pmm_free_page(page); + } + st_pass(); + + st_begin("pmm: refcount incref/decref"); + { + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + ST_ASSERT(page->refcount.load() == 1); + + page_incref(page); + ST_ASSERT(page->refcount.load() == 2); + + page_decref(page); // 2 -> 1, should NOT free + ST_ASSERT(page->refcount.load() == 1); + ST_ASSERT(page->type == PageType::Allocated); + + page_decref(page); // 1 -> 0, should free to pool + // After decref to 0, page is returned to pool. + // We can't safely check page->type because it might be reused. + } + st_pass(); + st_begin("pmm: pmm_alloc_pages natural alignment (order-3)"); + { + Page* page = pmm_alloc_pages(3); + ST_ASSERT(page != nullptr); + uint64 pa = page_to_pa(page).raw; + ST_ASSERT_EQ(pa % (kPageSize * 8), 0ULL); + pmm_free_pages(page, 3); + } + st_pass(); + + st_begin("pmm: page_decref to 0 and reuse (order-0)"); + { + Page* page = pmm_alloc_page(); + uint64 pa = page_to_pa(page).raw; + page_decref(page); // drops 1 -> 0, freeing it + + // Next alloc from PCP should give back the same page + Page* page2 = pmm_alloc_page(); + ST_ASSERT_EQ(page_to_pa(page2).raw, pa); + pmm_free_page(page2); + } + st_pass(); + + st_begin("pmm: page_decref to 0 on multi-page blocks (order-3)"); + { + Page* page = pmm_alloc_pages(3); + uint64 pa = page_to_pa(page).raw; + page_decref(page); // Should internally route to pmm_free_pages(page, 3) + + // Re-allocate order-3 and see if we get it back (buddy coalescing was respected) + Page* page2 = pmm_alloc_pages(3); + ST_ASSERT_EQ(page_to_pa(page2).raw, pa); + pmm_free_pages(page2, 3); + } + st_pass(); + + st_begin("pmm: buddy split and block coalescing correctness"); + { + // Force split: allocate order-1, free it, allocate two order-0 + Page* page_ord1 = pmm_alloc_pages(1); + uint64 pa1 = page_to_pa(page_ord1).raw; + pmm_free_pages(page_ord1, 1); + + Page* p0_a = pmm_alloc_page(); + Page* p0_b = pmm_alloc_page(); + + // Free them to trigger coalesce + pmm_free_page(p0_a); + pmm_free_page(p0_b); + + // Allocate order-1 again to verify successful coalescing + Page* page_ord1_again = pmm_alloc_pages(1); + ST_ASSERT_EQ(page_to_pa(page_ord1_again).raw, pa1); + pmm_free_pages(page_ord1_again, 1); + } + st_pass(); + + st_begin("pmm: mixed order cross allocation"); + { + Page* p0 = pmm_alloc_page(); + Page* p2 = pmm_alloc_pages(2); + Page* p0b = pmm_alloc_page(); + + ST_ASSERT(p0 != nullptr); + ST_ASSERT(p2 != nullptr); + ST_ASSERT(p0b != nullptr); + ST_ASSERT_NE(page_to_pa(p0).raw, page_to_pa(p2).raw); + + pmm_free_page(p0); + pmm_free_pages(p2, 2); + pmm_free_page(p0b); + } + st_pass(); + + st_begin("pmm: PCP drain/refill boundaries"); + { + // kPcpHighWatermark = 64. Allocating and freeing 65 blocks triggers drain. + Page* pages[65]; + for (int i=0; i<65; i++) pages[i] = pmm_alloc_page(); + for (int i=0; i<65; i++) pmm_free_page(pages[i]); + + // The system shouldn't crash and PCP logic should handle this. + Page* verify_page = pmm_alloc_page(); + ST_ASSERT(verify_page != nullptr); + pmm_free_page(verify_page); + } + st_pass(); + + st_begin("pmm: OOM exhaustion stability"); + { + Page* head = nullptr; + // First allocate large chunks to be fast + while (true) { + Page* page = pmm_alloc_pages(5); + if (!page) break; + uint64* ptr = reinterpret_cast(page_to_va(page).raw); + *ptr = reinterpret_cast(head); + // encode order in the second word + *(ptr + 1) = 5; + head = page; + } + // Then allocate single frames until absolute zero + while (true) { + Page* page = pmm_alloc_page(); + if (!page) break; + uint64* ptr = reinterpret_cast(page_to_va(page).raw); + *ptr = reinterpret_cast(head); + *(ptr + 1) = 0; + head = page; + } + + // We hit nullptr. System is completely exhausted. + ST_ASSERT(pmm_alloc_page() == nullptr); + + // Free everything + while (head) { + uint64* ptr = reinterpret_cast(page_to_va(head).raw); + Page* nxt = reinterpret_cast(*ptr); + uint64 order = *(ptr + 1); + if (order == 5) pmm_free_pages(head, 5); + else pmm_free_page(head); + head = nxt; + } + } + st_pass(); + + st_begin("pmm: out of bounds order allocation gracefully fails"); + { + // kMaxOrder is 11, so passing 11 or higher should safely return nullptr. + Page* page = pmm_alloc_pages(11); + ST_ASSERT(page == nullptr); + page = pmm_alloc_pages(255); + ST_ASSERT(page == nullptr); + } + st_pass(); + + st_begin("pmm: pmm_get_mem_base / pmm_get_mem_size sanity"); + { + uint64 base = pmm_get_mem_base().raw; + uint64 size = pmm_get_mem_size(); + + // We no longer assert on QEMU specific memory address + // ST_ASSERT(base >= 0x80000000ULL); + ST_ASSERT(size > 0); + // Size must be page-aligned + ST_ASSERT_EQ(size % kPageSize, 0ULL); + // Base must be page-aligned + ST_ASSERT_EQ(base % kPageSize, 0ULL); + } + st_pass(); +} diff --git a/test/test_random.cc b/test/test_random.cc new file mode 100644 index 0000000..d546410 --- /dev/null +++ b/test/test_random.cc @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include + +void selftest_random() +{ + /* Test 1: Basic 64-bit generation */ + kprintf("[test] random: basic 64-bit generation ... "); + uint64 v1 = get_random_u64(); + uint64 v2 = get_random_u64(); + if (v1 == v2) { + kernel_panic("random: two consecutive generations were identical!"); + } + kprintf("PASS\n"); + + /* Test 2: Basic 32-bit generation */ + kprintf("[test] random: basic 32-bit generation ... "); + uint32 v3 = get_random_u32(); + uint32 v4 = get_random_u32(); + if (v3 == v4) { + kernel_panic("random: two consecutive 32-bit generations were identical!"); + } + kprintf("PASS\n"); + + /* Test 3: Byte generation */ + kprintf("[test] random: byte generation ... "); + uint8 buf1[16]; + uint8 buf2[16]; + get_random_bytes(buf1, sizeof(buf1)); + get_random_bytes(buf2, sizeof(buf2)); + + bool all_zero1 = true; + bool all_zero2 = true; + bool same = true; + + for (int i = 0; i < 16; i++) { + if (buf1[i] != 0) all_zero1 = false; + if (buf2[i] != 0) all_zero2 = false; + if (buf1[i] != buf2[i]) same = false; + } + + if (all_zero1 || all_zero2 || same) { + kernel_panic("random: byte generation statistical failure!"); + } + kprintf("PASS\n"); +} diff --git a/test/test_rbtree.cc b/test/test_rbtree.cc new file mode 100644 index 0000000..27f049a --- /dev/null +++ b/test/test_rbtree.cc @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include + +struct TestNode { + int key; + struct rb_node rb; +}; + +static bool node_less(struct rb_node *node, const struct rb_node *parent) { + TestNode *n = rb_entry(node, TestNode, rb); + const TestNode *p = reinterpret_cast( + reinterpret_cast(parent) - __builtin_offsetof(TestNode, rb)); + return n->key < p->key; +} + +static int node_cmp(const void *key, const struct rb_node *node) { + int k = *static_cast(key); + const TestNode *n = reinterpret_cast( + reinterpret_cast(node) - __builtin_offsetof(TestNode, rb)); + if (k < n->key) return -1; + if (k > n->key) return 1; + return 0; +} + +void selftest_rbtree() { + st_begin("rbtree: basic insert, find, erase"); + { + struct rb_root tree = RB_ROOT; + TestNode n1{10, {}}, n2{20, {}}, n3{5, {}}; + + rb_add(&n1.rb, &tree, node_less); + rb_add(&n2.rb, &tree, node_less); + rb_add(&n3.rb, &tree, node_less); + + int search_key = 20; + struct rb_node *found = rb_find(&search_key, &tree, node_cmp); + ST_ASSERT(found == &n2.rb); + + search_key = 99; + found = rb_find(&search_key, &tree, node_cmp); + ST_ASSERT(found == nullptr); + + rb_erase(&n2.rb, &tree); + // Verify erased + search_key = 20; + found = rb_find(&search_key, &tree, node_cmp); + ST_ASSERT(found == nullptr); + } + st_pass(); + + st_begin("rbtree: ordered iteration"); + { + struct rb_root tree = RB_ROOT; + TestNode nodes[5] = { {50, {}}, {10, {}}, {40, {}}, {20, {}}, {30, {}} }; + for (int i=0; i<5; i++) { + rb_add(&nodes[i].rb, &tree, node_less); + } + + int expected[] = {10, 20, 30, 40, 50}; + int idx = 0; + struct rb_node *node = rb_first(&tree); + while (node) { + TestNode *n = rb_entry(node, TestNode, rb); + ST_ASSERT_EQ(n->key, expected[idx]); + idx++; + node = rb_next(node); + } + ST_ASSERT_EQ(idx, 5); + } + st_pass(); + + st_begin("rbtree: large scale insert/erase balance"); + { + struct rb_root tree = RB_ROOT; + constexpr int N = 200; + static TestNode nodes[N]; // Static to avoid blowing up the BSP stack + for (int i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ======================================================================== + * Helper: create a minimal test TCB (no real kernel stack, just metadata) + * ======================================================================== */ + +static TaskControlBlock g_test_tcbs[8]; + +static TaskControlBlock* make_test_tcb(int index, const char* name, + uint8 priority, uint8 cpu) +{ + TaskControlBlock* t = &g_test_tcbs[index]; + t->context = {}; + t->wait_chld.init("test_wq"); + t->name = name; + t->priority = priority; + t->cpu_id = cpu; + t->pid = 100 + index; + t->tgid = 100 + index; + t->state = TaskState::Runnable; + t->time_slice = priority_to_timeslice(priority); + t->run_link.init(); + t->all_link.init(); + t->children.init(); + t->sibling.init(); + t->mm = nullptr; + t->trapframe = nullptr; + t->kstack_pa = 0; + t->kstack_va = 0; + t->flags = TF_KTHREAD; + t->sched_class = &g_sched_class_o1; + t->sched_entity = &t->run_link; + return t; +} + +/* ======================================================================== + * Test cases + * ======================================================================== */ + +static bool test_current_task() +{ + /* After sched_init, current_task() should be the BSP boot TCB */ + TaskControlBlock* curr = current_task(); + return curr != nullptr && curr->state == TaskState::Running; +} + +static bool test_priority_to_timeslice() +{ + /* Higher priority (lower number) should get longer time slice */ + int32 ts_high = priority_to_timeslice(0); + int32 ts_mid = priority_to_timeslice(15); + int32 ts_low = priority_to_timeslice(31); + + return ts_high > ts_mid && ts_mid > ts_low && ts_low >= 1; +} + +static bool test_o1_pick_next_priority() +{ + RunQueue rq; + rq.init(0); + g_sched_class_o1.init_rq(&rq); + + TaskControlBlock* t_lo = make_test_tcb(0, "test_p5", 5, 0); + TaskControlBlock* t_hi = make_test_tcb(1, "test_p2", 2, 0); + + g_sched_class_o1.enqueue_task(&rq, t_lo); + g_sched_class_o1.enqueue_task(&rq, t_hi); + + TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); + return next == t_hi; +} + +static bool test_o1_fifo_order() +{ + RunQueue rq; + rq.init(0); + g_sched_class_o1.init_rq(&rq); + + TaskControlBlock* t0 = make_test_tcb(0, "test_q0", 10, 0); + TaskControlBlock* t1 = make_test_tcb(1, "test_q1", 10, 0); + TaskControlBlock* t2 = make_test_tcb(2, "test_q2", 10, 0); + + g_sched_class_o1.enqueue_task(&rq, t0); + g_sched_class_o1.enqueue_task(&rq, t1); + g_sched_class_o1.enqueue_task(&rq, t2); + + TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); + if (next != t0) return false; + g_sched_class_o1.dequeue_task(&rq, t0); + + next = g_sched_class_o1.pick_next_task(&rq); + if (next != t1) return false; + g_sched_class_o1.dequeue_task(&rq, t1); + + next = g_sched_class_o1.pick_next_task(&rq); + if (next != t2) return false; + + return true; +} + +static bool test_o1_nr_running() +{ + RunQueue rq; + rq.init(0); + g_sched_class_o1.init_rq(&rq); + + TaskControlBlock* t0 = make_test_tcb(0, "test_n0", 8, 0); + TaskControlBlock* t1 = make_test_tcb(1, "test_n1", 9, 0); + + if (rq.nr_running() != 0 || rq.o1().nr_running() != 0) return false; + + g_sched_class_o1.enqueue_task(&rq, t0); + if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; + + g_sched_class_o1.enqueue_task(&rq, t1); + if (rq.nr_running() != 2 || rq.o1().nr_running() != 2) return false; + + g_sched_class_o1.dequeue_task(&rq, t0); + if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; + + g_sched_class_o1.dequeue_task(&rq, t1); + return rq.nr_running() == 0 && rq.o1().nr_running() == 0; +} + +static bool test_o1_pick_next_empty() +{ + RunQueue rq; + rq.init(0); + g_sched_class_o1.init_rq(&rq); + + return g_sched_class_o1.pick_next_task(&rq) == nullptr; +} + +static bool test_priority_to_timeslice_bounds() +{ + /* Boundary values */ + int32 ts0 = priority_to_timeslice(0); + int32 ts31 = priority_to_timeslice(31); + + return ts0 == 20 && ts31 >= 1; +} + +static bool test_enqueue_dequeue() +{ + /* Create a test task and enqueue/dequeue it */ + TaskControlBlock* t = make_test_tcb(0, "test_ed", 10, 0); + + sched_enqueue(t); + if (t->state != TaskState::Runnable) return false; + + sched_dequeue(t); + /* After dequeue, run_link should be self-pointing */ + return t->run_link.next == &t->run_link; +} + +static bool test_multiple_enqueue_dequeue() +{ + /* Enqueue 3 tasks at different priorities, dequeue all */ + TaskControlBlock* t0 = make_test_tcb(0, "test_m0", 5, 0); + TaskControlBlock* t1 = make_test_tcb(1, "test_m1", 10, 0); + TaskControlBlock* t2 = make_test_tcb(2, "test_m2", 20, 0); + + sched_enqueue(t0); + sched_enqueue(t1); + sched_enqueue(t2); + + sched_dequeue(t0); + sched_dequeue(t1); + sched_dequeue(t2); + + return t0->run_link.is_empty() && + t1->run_link.is_empty() && + t2->run_link.is_empty(); +} + +static bool test_current_task_name() +{ + TaskControlBlock* curr = current_task(); + if (!curr || !curr->name) return false; + + /* BSP boot TCB should be named "kernel_main" */ + const char* expected = "kernel_main"; + const char* actual = curr->name; + while (*expected && *actual) { + if (*expected != *actual) return false; + expected++; + actual++; + } + return *expected == *actual; +} + +/* ======================================================================== + * Test runner + * ======================================================================== */ + +void selftest_sched() +{ + st_begin("sched: current_task is valid after init"); + ST_ASSERT(test_current_task()); + st_pass(); + + st_begin("sched: current_task name is kernel_main"); + ST_ASSERT(test_current_task_name()); + st_pass(); + + st_begin("sched: priority_to_timeslice ordering"); + ST_ASSERT(test_priority_to_timeslice()); + st_pass(); + + st_begin("sched: priority_to_timeslice bounds"); + ST_ASSERT(test_priority_to_timeslice_bounds()); + st_pass(); + + st_begin("sched: enqueue/dequeue round-trip"); + ST_ASSERT(test_enqueue_dequeue()); + st_pass(); + + st_begin("sched: multiple enqueue/dequeue"); + ST_ASSERT(test_multiple_enqueue_dequeue()); + st_pass(); + + st_begin("sched: o1 pick_next priority"); + ST_ASSERT(test_o1_pick_next_priority()); + st_pass(); + + st_begin("sched: o1 FIFO order"); + ST_ASSERT(test_o1_fifo_order()); + st_pass(); + + st_begin("sched: o1 nr_running accounting"); + ST_ASSERT(test_o1_nr_running()); + st_pass(); + + st_begin("sched: o1 pick_next empty"); + ST_ASSERT(test_o1_pick_next_empty()); + st_pass(); +} + diff --git a/test/test_signal.cc b/test/test_signal.cc new file mode 100644 index 0000000..360b0e6 --- /dev/null +++ b/test/test_signal.cc @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_signal.cc — Self-tests for signal infrastructure + * + * Tests the kernel-internal signal APIs directly (not via syscalls, + * since syscalls use copy_to_user which rejects kernel-space buffers). + */ + +#include +#include +#include +#include +#include +#include +#include + +/* From signal.cc */ +extern int send_signal(int sig, kernel_siginfo* info, TaskControlBlock* task); + +/* Drain all queued sigqueue entries from a task's pending list */ +static void flush_pending(TaskControlBlock* task) +{ + task->pending.signal = sigset_t{}; + list_node* head = &task->pending.list; + while (head->next != head) { + list_node* node = head->next; + head->next = node->next; + node->next->prev = head; + auto* q = list_entry(node); + delete q; + } +} + +void selftest_signal() +{ + TaskControlBlock* curr = current_task(); + + /* Ensure sighand exists for this task */ + if (!curr->sighand) { + curr->sighand = new sighand_struct; + curr->sighand->init(); + } + + /* Ensure pending list is initialized */ + if (curr->pending.list.next == nullptr) { + curr->pending.list.next = &curr->pending.list; + curr->pending.list.prev = &curr->pending.list; + curr->pending.signal = sigset_t{}; + } + + /* ---- send_signal queues a signal ---- */ + st_begin("signal: send_signal queues to pending"); + { + curr->pending.signal = sigset_t{}; + + kernel_siginfo info{}; + info.si_signo = SIGUSR1; + info.si_code = 0; + + int ret = send_signal(SIGUSR1, &info, curr); + ST_ASSERT_EQ(ret, 0); + ST_ASSERT(curr->pending.signal.has(SIGUSR1)); + + flush_pending(curr); + } + st_pass(); + + /* ---- send_signal: invalid signal returns -EINVAL ---- */ + st_begin("signal: send_signal invalid sig returns -EINVAL"); + { + int ret = send_signal(0, nullptr, curr); + ST_ASSERT_EQ(ret, -EINVAL); + + ret = send_signal(_NSIG + 1, nullptr, curr); + ST_ASSERT_EQ(ret, -EINVAL); + } + st_pass(); + + /* ---- send_signal: null task returns -ESRCH ---- */ + st_begin("signal: send_signal null task returns -ESRCH"); + { + int ret = send_signal(SIGUSR1, nullptr, nullptr); + ST_ASSERT_EQ(ret, -ESRCH); + } + st_pass(); + + /* ---- duplicate standard signal is coalesced ---- */ + st_begin("signal: duplicate standard signal coalesced"); + { + curr->pending.signal = sigset_t{}; + + send_signal(SIGUSR1, nullptr, curr); + ST_ASSERT(curr->pending.signal.has(SIGUSR1)); + + int ret = send_signal(SIGUSR1, nullptr, curr); + ST_ASSERT_EQ(ret, 0); + + flush_pending(curr); + } + st_pass(); + + /* ---- sigset_t: add/del/has ---- */ + st_begin("signal: sigset_t add/del/has"); + { + sigset_t s{}; + ST_ASSERT(!s.has(SIGUSR1)); + s.add(SIGUSR1); + ST_ASSERT(s.has(SIGUSR1)); + s.del(SIGUSR1); + ST_ASSERT(!s.has(SIGUSR1)); + } + st_pass(); + + /* ---- sigset_t: bitwise operators ---- */ + st_begin("signal: sigset_t bitwise OR and AND"); + { + sigset_t a{}; + a.add(SIGUSR1); + sigset_t b{}; + b.add(SIGUSR2); + + sigset_t c = a | b; + ST_ASSERT(c.has(SIGUSR1)); + ST_ASSERT(c.has(SIGUSR2)); + + sigset_t d = c & a; + ST_ASSERT(d.has(SIGUSR1)); + ST_ASSERT(!d.has(SIGUSR2)); + } + st_pass(); + + /* ---- blocked mask: direct manipulation ---- */ + st_begin("signal: blocked mask direct manipulation"); + { + sigset_t saved = curr->blocked; + curr->blocked = sigset_t{}; + + curr->blocked.add(SIGUSR1); + curr->blocked.add(SIGUSR2); + ST_ASSERT(curr->blocked.has(SIGUSR1)); + ST_ASSERT(curr->blocked.has(SIGUSR2)); + + curr->blocked.del(SIGUSR1); + ST_ASSERT(!curr->blocked.has(SIGUSR1)); + ST_ASSERT(curr->blocked.has(SIGUSR2)); + + curr->blocked = saved; + } + st_pass(); + + /* ---- sighand action table ---- */ + st_begin("signal: sighand action table defaults to SIG_DFL"); + { + /* All handlers should default to SIG_DFL (0) */ + kernel_sigaction* ka = &curr->sighand->action[SIGUSR1 - 1]; + ST_ASSERT_EQ(ka->sa_handler, (uint64)SIG_DFL); + + /* Set a handler and verify */ + ka->sa_handler = 0x12345678ULL; + ST_ASSERT_EQ(ka->sa_handler, (uint64)0x12345678ULL); + + /* Reset */ + ka->sa_handler = SIG_DFL; + } + st_pass(); + + /* ---- sig_kernel_only rejects SIGKILL/SIGSTOP handlers ---- */ + st_begin("signal: SIGKILL/SIGSTOP are kernel-only"); + { + ST_ASSERT(sig_kernel_only(SIGKILL)); + ST_ASSERT(sig_kernel_only(SIGSTOP)); + ST_ASSERT(!sig_kernel_only(SIGUSR1)); + ST_ASSERT(!sig_kernel_only(SIGINT)); + } + st_pass(); + + /* ---- sig_default_action correctness ---- */ + st_begin("signal: sig_default_action returns correct actions"); + { + ST_ASSERT_EQ((int)sig_default_action(SIGCHLD), (int)SIG_ACT_IGN); + ST_ASSERT_EQ((int)sig_default_action(SIGKILL), (int)SIG_ACT_TERM); + ST_ASSERT_EQ((int)sig_default_action(SIGSTOP), (int)SIG_ACT_STOP); + ST_ASSERT_EQ((int)sig_default_action(SIGCONT), (int)SIG_ACT_CONT); + ST_ASSERT_EQ((int)sig_default_action(SIGSEGV), (int)SIG_ACT_CORE); + } + st_pass(); + + /* Clean up */ + flush_pending(curr); + curr->blocked = sigset_t{}; +} diff --git a/test/test_slab.cc b/test/test_slab.cc new file mode 100644 index 0000000..8a448b4 --- /dev/null +++ b/test/test_slab.cc @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: Slab Allocator (kmalloc/kfree) + * + * Tests: + * 1. Small allocation (8 bytes) and free + * 2. Multiple size classes + * 3. No aliasing between consecutive allocs + * 4. Alignment check (minimum 8-byte aligned) + * 5. Bulk alloc/free stress + * 6. operator new / operator delete + * 7. Large allocation (> 2048, falls back to PMM) + * 8. Zero-size and null-free edge cases + */ + +#include +#include +#include +#include + +/* Forward declarations — defined in slab.cc */ +void* kmalloc(uint32 size); +void kfree(void* ptr); + +void selftest_slab() +{ + st_begin("slab: small alloc (8 bytes)"); + { + void* p = kmalloc(8); + ST_ASSERT(p != nullptr); + // Write and read back to verify the memory is usable + *static_cast(p) = 0xDEADBEEFCAFEBABEULL; + ST_ASSERT_EQ(*static_cast(p), 0xDEADBEEFCAFEBABEULL); + kfree(p); + } + st_pass(); + + st_begin("slab: multiple size classes"); + { + uint32 sizes[] = {8, 16, 32, 64, 128, 256, 512, 1024, 2048}; + void* ptrs[9]; + for (int i = 0; i < 9; i++) { + ptrs[i] = kmalloc(sizes[i]); + ST_ASSERT(ptrs[i] != nullptr); + // Write a marker + *static_cast(ptrs[i]) = static_cast(i); + } + // Verify markers (no cross-contamination) + for (int i = 0; i < 9; i++) { + ST_ASSERT_EQ(*static_cast(ptrs[i]), static_cast(i)); + } + for (int i = 0; i < 9; i++) { + kfree(ptrs[i]); + } + } + st_pass(); + + st_begin("slab: no aliasing"); + { + void* a = kmalloc(32); + void* b = kmalloc(32); + ST_ASSERT(a != nullptr); + ST_ASSERT(b != nullptr); + ST_ASSERT_NE(a, b); + kfree(b); + kfree(a); + } + st_pass(); + + st_begin("slab: alignment (8-byte minimum)"); + { + for (int i = 0; i < 16; i++) { + void* p = kmalloc(7); // odd size, should still be 8-aligned + ST_ASSERT(p != nullptr); + ST_ASSERT_EQ(reinterpret_cast(p) % 8, 0ULL); + kfree(p); + } + } + st_pass(); + + st_begin("slab: bulk alloc/free stress (128 x 64B)"); + { + constexpr int N = 128; + void* ptrs[N]; + for (int i = 0; i < N; i++) { + ptrs[i] = kmalloc(64); + ST_ASSERT(ptrs[i] != nullptr); + } + // Free in reverse order + for (int i = N - 1; i >= 0; i--) { + kfree(ptrs[i]); + } + } + st_pass(); + + st_begin("slab: operator new/delete"); + { + struct TestObj { + uint64 a; + uint64 b; + }; + TestObj* obj = new TestObj{42, 99}; + ST_ASSERT(obj != nullptr); + ST_ASSERT_EQ(obj->a, 42ULL); + ST_ASSERT_EQ(obj->b, 99ULL); + delete obj; + } + st_pass(); + + st_begin("slab: large alloc (4096 bytes, PMM fallback)"); + { + void* p = kmalloc(4096); + ST_ASSERT(p != nullptr); + ST_ASSERT_EQ(reinterpret_cast(p) % kPageSize, 0ULL); + kfree(p); + } + st_pass(); + + st_begin("slab: edge cases (zero size, null free)"); + { + void* p = kmalloc(0); + ST_ASSERT(p == nullptr); + kfree(nullptr); // should not crash + } + st_pass(); + st_begin("slab: LIFO freelist reallocation (same address)"); + { + void* a = kmalloc(32); + kfree(a); + void* b = kmalloc(32); + ST_ASSERT_EQ(a, b); + kfree(b); + } + st_pass(); + + st_begin("slab: boundary conditions (2048 bytes size class)"); + { + // 2048 bytes should still use slab (2 objects per page) + // We allocate 3 to guarantee at least two fall in the exact same slab page + void* p1 = kmalloc(2048); + void* p2 = kmalloc(2048); + void* p3 = kmalloc(2048); + + auto diff = [](void* a, void* b) -> uint64 { + uint64 ua = reinterpret_cast(a); + uint64 ub = reinterpret_cast(b); + return ua > ub ? ua - ub : ub - ua; + }; + + // At least one adjacent pair MUST be exactly separated by 2048 bytes + bool found_2048 = (diff(p1, p2) == 2048ULL) || (diff(p2, p3) == 2048ULL); + ST_ASSERT(found_2048); + + kfree(p1); + kfree(p2); + kfree(p3); + } + st_pass(); + + st_begin("slab: cross-page allocation (> 1 page equivalent objects)"); + { + // Allocating more than one page worth of 32-byte objects (4096 / 32 = 128) + constexpr int NUM = 150; + void* ptrs[NUM]; + for (int i=0; i(p) % kPageSize == 0) { + base_ptr = p; + break; + } + drain[drain_count++] = p; + } + + // base_ptr is the very first object in a fresh slab page! + // The page holds exactly 32 objects (4096 / 128 = 32). We already hold 1 (base_ptr). + void* ptrs[32]; + ptrs[0] = base_ptr; + for (int i = 1; i < 32; i++) { + ptrs[i] = kmalloc(128); + } + + // At this point, we completely own all 32 objects of this slab page. + uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast(base_ptr))).raw; + Page* page = pa_to_page(phys_addr(page_pa)); + + ST_ASSERT(page->type == PageType::Slab); + ST_ASSERT(page->slab.obj_count == 32); + + // Free all items in this isolated slab page + for (int i = 0; i < 32; i++) { + kfree(ptrs[i]); + } + + // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached). + ST_ASSERT(page->type == PageType::Free || page->type == PageType::PcpCached); + + // Clean up the drain array + for (int i = 0; i < drain_count; i++) { + kfree(drain[i]); + } + } + st_pass(); + + st_begin("slab: continuous operator new/delete memory leak loop"); + { + struct BigObj { uint8 data[512]; }; + BigObj* ptr = nullptr; + // 10K allocations to ensure no memory leak causes OOM + for (int i = 0; i < 10000; i++) { + ptr = new BigObj; + ST_ASSERT(ptr != nullptr); + delete ptr; + } + } + st_pass(); + + st_begin("slab: allocation pressure (mixed sizes)"); + { + constexpr int MAX_ITEMS = 100; + void* ptrs[MAX_ITEMS]; + uint32 sizes[MAX_ITEMS]; + + for (int i = 0; i < MAX_ITEMS; i++) { + sizes[i] = (i * 17) % 2000 + 8; // Pseudo-random sizes between 8 and 2008 + ptrs[i] = kmalloc(sizes[i]); + ST_ASSERT(ptrs[i] != nullptr); + ST_ASSERT(reinterpret_cast(ptrs[i]) % 8 == 0); + } + + for (int i = 0; i < MAX_ITEMS; i++) { + kfree(ptrs[i]); + } + } + st_pass(); +} diff --git a/test/test_smp.cc b/test/test_smp.cc new file mode 100644 index 0000000..f934526 --- /dev/null +++ b/test/test_smp.cc @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_smp.cc — Post-SMP boot tests + * + * These tests run after all APs are online. They verify: + * 1. IPI delivery — BSP sends IPI to each AP, AP increments counter. + * 2. Task migration — a task moves from one runqueue to another. + * 3. TLB shootdown IPI — cross-CPU flush. + * + * Called from the init thread (PID 1) context, NOT from the pre-SMP + * self-test suite. + */ + +#include +#include +#include +#include +#include +#include + +/* Shared counter incremented by APs when they receive an IPI */ +static lume::atomic g_ipi_ack_count{0}; + +/* ------------------------------------------------------------------ */ +/* IPI delivery test */ +/* ------------------------------------------------------------------ */ + +void selftest_ipi_delivery() +{ + st_begin("smp: IPI delivery to all APs"); + + /* Reset counter */ + g_ipi_ack_count.store(0, __ATOMIC_RELAXED); + + uint32 bsp = static_cast(arch::cpu::id()); + uint32 num_cpus = 4; /* TODO: derive from FDT */ + + /* Build mask of all APs */ + uint64 ap_mask = 0; + uint32 expected = 0; + for (uint32 i = 0; i < num_cpus; i++) { + if (i == bsp) continue; + ap_mask |= (1ULL << i); + expected++; + } + + /* Send reschedule IPI to all APs */ + smp_send_ipi(ap_mask, IPI_RESCHEDULE); + + /* Wait a short while for APs to process the interrupt. + * APs are in wfi (idle loop), so they'll wake immediately + * on interrupt delivery. We just need to give them time + * to run the handler. */ + for (volatile int i = 0; i < 100000; i++) { + /* spin-wait */ + } + + /* We can't directly check that IPI was received because + * IPI_RESCHEDULE just sets need_resched — no ack. + * Instead verify no crash occurred and the IPI infrastructure + * didn't deadlock. This is a liveness test. */ + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* IPI halt test (send to self, skip) */ +/* ------------------------------------------------------------------ */ + +void selftest_ipi_self_skip() +{ + st_begin("smp: IPI reason bitmask accumulation"); + + uint32 cpu = static_cast(arch::cpu::id()); + + /* Manually set multiple reasons atomically */ + g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED); + g_ipi_data[cpu].pending.fetch_or(IPI_RESCHEDULE, __ATOMIC_RELEASE); + g_ipi_data[cpu].pending.fetch_or(IPI_TLB_SHOOTDOWN, __ATOMIC_RELEASE); + + uint32 val = g_ipi_data[cpu].pending.load(__ATOMIC_ACQUIRE); + ST_ASSERT_EQ(val, IPI_RESCHEDULE | IPI_TLB_SHOOTDOWN); + + /* Clean up */ + g_ipi_data[cpu].pending.store(0, __ATOMIC_RELAXED); + + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* TLB shootdown IPI test */ +/* ------------------------------------------------------------------ */ + +void selftest_tlb_shootdown() +{ + st_begin("smp: TLB shootdown IPI (cross-CPU flush)"); + + uint32 bsp = static_cast(arch::cpu::id()); + uint32 num_cpus = 4; + + /* Build AP mask */ + uint64 ap_mask = 0; + for (uint32 i = 0; i < num_cpus; i++) { + if (i == bsp) continue; + ap_mask |= (1ULL << i); + } + + /* Set TLB range on each AP's IpiData */ + for (uint32 i = 0; i < num_cpus; i++) { + if (i == bsp) continue; + g_ipi_data[i].tlb_start = 0; + g_ipi_data[i].tlb_end = 0; /* 0,0 = full flush */ + } + + /* Send TLB shootdown */ + smp_send_ipi(ap_mask, IPI_TLB_SHOOTDOWN); + + /* Wait for APs to process */ + for (volatile int i = 0; i < 100000; i++) {} + + /* If we got here without panic, TLB shootdown succeeded */ + st_pass(); +} + +/* ------------------------------------------------------------------ */ +/* Public entry point */ +/* ------------------------------------------------------------------ */ + +void selftest_smp() +{ + selftest_ipi_delivery(); + selftest_ipi_self_skip(); + selftest_tlb_shootdown(); +} diff --git a/test/test_spinlock.cc b/test/test_spinlock.cc new file mode 100644 index 0000000..be794a0 --- /dev/null +++ b/test/test_spinlock.cc @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: Spinlock + * + * Tests: + * 1. Basic acquire/release + * 2. LockGuard RAII acquire/release + * 3. Nested locking (two different locks) + * 4. Interrupt state preservation across lock/unlock + * 5. is_held_by_current_cpu correctness + * + * Note: We cannot test multi-core contention in BSP-only self-test. + * That requires AP participation (future Phase 2 test). + */ + +#include +#include +#include +#include + +void selftest_spinlock() +{ + st_begin("spinlock: basic acquire/release"); + { + Spinlock lock; + lock.init("test_basic"); + + ST_ASSERT(!lock.is_held_by_current_cpu()); + + lock.acquire(); + ST_ASSERT(lock.is_held_by_current_cpu()); + + lock.release(); + ST_ASSERT(!lock.is_held_by_current_cpu()); + } + st_pass(); + + st_begin("spinlock: LockGuard RAII"); + { + Spinlock lock; + lock.init("test_guard"); + + { + LockGuard guard(lock); + ST_ASSERT(lock.is_held_by_current_cpu()); + } + // After scope exit, lock should be released + ST_ASSERT(!lock.is_held_by_current_cpu()); + } + st_pass(); + + st_begin("spinlock: nested locking (two locks)"); + { + Spinlock lock_a, lock_b; + lock_a.init("test_nest_a"); + lock_b.init("test_nest_b"); + + lock_a.acquire(); + ST_ASSERT(lock_a.is_held_by_current_cpu()); + + lock_b.acquire(); + ST_ASSERT(lock_b.is_held_by_current_cpu()); + + lock_b.release(); + ST_ASSERT(!lock_b.is_held_by_current_cpu()); + ST_ASSERT(lock_a.is_held_by_current_cpu()); // A still held + + lock_a.release(); + ST_ASSERT(!lock_a.is_held_by_current_cpu()); + } + st_pass(); + + st_begin("spinlock: interrupt state preservation"); + { + Spinlock lock; + lock.init("test_intr"); + + // Interrupts are currently off (BSP init period), but let's + // verify the lock doesn't corrupt the state. + bool before = arch::cpu::intr_enabled(); + + lock.acquire(); + // Inside lock, interrupts must be disabled + ST_ASSERT(!arch::cpu::intr_enabled()); + lock.release(); + + // After release, interrupt state should be restored + ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); + } + st_pass(); + st_begin("spinlock: deep nesting tracking (> 2)"); + { + Spinlock l1, l2, l3; + l1.init("l1"); + l2.init("l2"); + l3.init("l3"); + + bool before = arch::cpu::intr_enabled(); + + l1.acquire(); + l2.acquire(); + l3.acquire(); + + ST_ASSERT(!arch::cpu::intr_enabled()); + + l3.release(); + ST_ASSERT(!arch::cpu::intr_enabled()); + + l2.release(); + ST_ASSERT(!arch::cpu::intr_enabled()); + + l1.release(); + ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); + } + st_pass(); + + st_begin("spinlock: interrupt leak detection logic (lock_depth metadata)"); + { + Spinlock l; + l.init("l"); + + auto& state = sync::g_cpu_sync_states[arch::cpu::id()]; + int initial_depth = state.lock_depth; + + l.acquire(); + // The lock depth should strictly increase + ST_ASSERT_EQ(state.lock_depth, initial_depth + 1); + l.release(); + + ST_ASSERT_EQ(state.lock_depth, initial_depth); + } + st_pass(); +} diff --git a/test/test_sys_fs_extra.cc b/test/test_sys_fs_extra.cc new file mode 100644 index 0000000..705ce63 --- /dev/null +++ b/test/test_sys_fs_extra.cc @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_sys_fs_extra.cc — Self-tests for additional filesystem syscalls + * + * Tests: dup3, chdir/getcwd, writev, ioctl (stub), unlinkat (stub). + * Complements test_vfs_io.cc which covers the core read/write/open/close. + */ + +#include +#include +#include +#include +#include +#include + +/* Syscall entry points */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static int64 open_dev(const char* path, int flags) +{ + return sys_openat( + static_cast(-100), + reinterpret_cast(path), + static_cast(flags), 0, 0, 0); +} + +static int64 do_close(int64 fd) +{ + return sys_close(static_cast(fd), 0, 0, 0, 0, 0); +} + +static int64 do_read(int64 fd, void* buf, uint64 count) +{ + return sys_read(static_cast(fd), + reinterpret_cast(buf), count, 0, 0, 0); +} + + +/* iovec must match sys_fs.cc definition */ +struct test_iovec { + void* iov_base; + uint64 iov_len; +}; + +/* ================================================================== */ +/* Tests */ +/* ================================================================== */ + +void selftest_sys_fs_extra() +{ + /* ---- dup3: duplicate fd to specific target ---- */ + st_begin("sys_fs: dup3 duplicates to specific fd"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + /* dup3 to fd 50 */ + int64 fd2 = sys_dup3(static_cast(fd), 50, 0, 0, 0, 0); + ST_ASSERT_EQ(fd2, (int64)50); + + /* Both should work */ + char buf[4]; + int64 r = do_read(fd2, buf, 4); + ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */ + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- dup3: same fd returns -EINVAL ---- */ + st_begin("sys_fs: dup3 same fd returns -EINVAL"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 ret = sys_dup3(static_cast(fd), + static_cast(fd), 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + + do_close(fd); + } + st_pass(); + + /* ---- chdir to /dev, getcwd returns /dev ---- */ + st_begin("sys_fs: chdir then getcwd returns new dir"); + { + /* Save cwd */ + char saved_cwd[64] = {}; + sys_getcwd(reinterpret_cast(saved_cwd), 64, 0, 0, 0, 0); + + /* chdir to /dev */ + int64 ret = sys_chdir( + reinterpret_cast("/dev"), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* getcwd should return /dev */ + char buf[64] = {}; + int64 len = sys_getcwd( + reinterpret_cast(buf), 64, 0, 0, 0, 0); + ST_ASSERT(len > 0); + ST_ASSERT_EQ(buf[0], '/'); + ST_ASSERT_EQ(buf[1], 'd'); + ST_ASSERT_EQ(buf[2], 'e'); + ST_ASSERT_EQ(buf[3], 'v'); + + /* Restore cwd to / */ + sys_chdir(reinterpret_cast("/"), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- chdir to nonexistent returns -ENOENT ---- */ + st_begin("sys_fs: chdir nonexistent returns -ENOENT"); + { + int64 ret = sys_chdir( + reinterpret_cast("/no_such_dir"), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-ENOENT); + } + st_pass(); + + /* ---- chdir to file returns -ENOTDIR ---- */ + st_begin("sys_fs: chdir to file returns -ENOTDIR"); + { + int64 ret = sys_chdir( + reinterpret_cast("/dev/null"), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-ENOTDIR); + } + st_pass(); + + /* ---- writev: scatter write to file ---- */ + st_begin("sys_fs: writev scatter write"); + { + /* Create a file, write with writev, read back */ + int64 fd = sys_openat( + static_cast(-100), + reinterpret_cast("/writev_test"), + O_CREAT | O_RDWR, 0644, 0, 0); + ST_ASSERT(fd >= 0); + + char a[] = "AAA"; + char b[] = "BBB"; + test_iovec iov[2]; + iov[0].iov_base = a; + iov[0].iov_len = 3; + iov[1].iov_base = b; + iov[1].iov_len = 3; + + int64 w = sys_writev( + static_cast(fd), + reinterpret_cast(iov), 2, 0, 0, 0); + ST_ASSERT_EQ(w, (int64)6); + + /* Seek back to start and read */ + sys_lseek(static_cast(fd), 0, 0 /* SEEK_SET */, 0, 0, 0); + + char buf[8] = {}; + int64 r = do_read(fd, buf, 8); + ST_ASSERT_EQ(r, (int64)6); + ST_ASSERT_EQ(buf[0], 'A'); + ST_ASSERT_EQ(buf[3], 'B'); + + do_close(fd); + } + st_pass(); + + /* ---- writev: bad fd returns -EBADF ---- */ + st_begin("sys_fs: writev bad fd returns -EBADF"); + { + test_iovec iov[1]; + char dummy[] = "x"; + iov[0].iov_base = dummy; + iov[0].iov_len = 1; + + int64 ret = sys_writev(999, + reinterpret_cast(iov), 1, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EBADF); + } + st_pass(); + + /* ---- ioctl stub returns -ENOTTY ---- */ + st_begin("sys_fs: ioctl stub returns -ENOTTY"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 ret = sys_ioctl(static_cast(fd), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-ENOTTY); + + do_close(fd); + } + st_pass(); + + /* ---- unlinkat stub returns -ENOSYS ---- */ + st_begin("sys_fs: unlinkat stub returns -ENOSYS"); + { + int64 ret = sys_unlinkat( + static_cast(-100), + reinterpret_cast("/some_file"), + 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-ENOSYS); + } + st_pass(); + + /* ---- getcwd: null buf returns -EINVAL ---- */ + st_begin("sys_fs: getcwd null buf returns -EINVAL"); + { + int64 ret = sys_getcwd(0, 64, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + } + st_pass(); + + /* ---- getcwd: zero size returns -EINVAL ---- */ + st_begin("sys_fs: getcwd zero size returns -EINVAL"); + { + char buf[4]; + int64 ret = sys_getcwd( + reinterpret_cast(buf), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + } + st_pass(); +} diff --git a/test/test_sys_mm.cc b/test/test_sys_mm.cc new file mode 100644 index 0000000..2c1a7dc --- /dev/null +++ b/test/test_sys_mm.cc @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include +#include +#include + +/* sys_mm.cc APIs */ +int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64); +int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, + uint64 flags, uint64 fd, uint64 offset); +int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64); +int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, + uint64, uint64, uint64); + +void selftest_sys_mm() +{ + st_begin("sys_mm: mmap invalid arguments"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + ST_ASSERT_EQ(sys_mmap(0, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0), -EINVAL); + ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 1), -EINVAL); + ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); + ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_SHARED, 0, 0), -EINVAL); + ST_ASSERT_EQ(sys_mmap(0x123, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), -EINVAL); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: mmap/munmap/mprotect basic flow"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + int64 addr = sys_mmap(0, 0x3000, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + ST_ASSERT(addr > 0); + + ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), 0); + + int64 mp = sys_mprotect(addr + kPageSize, kPageSize, PROT_READ, 0, 0, 0); + ST_ASSERT_EQ(mp, 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, addr + kPageSize, VmFaultCause::Write), -EFAULT); + + int64 mu = sys_munmap(addr, 0x3000, 0, 0, 0, 0); + ST_ASSERT_EQ(mu, 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), -EFAULT); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: MAP_FIXED replaces existing mapping"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + uint64 base = 0x600000; + ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), + static_cast(base)); + + ST_ASSERT_EQ(sys_mmap(base, 0x1000, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), + static_cast(base)); + + ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Write), 0); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: mmap hint fallback on overlap"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + uint64 base = 0x700000; + ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), + static_cast(base)); + + int64 hint = sys_mmap(base, 0x1000, PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + ST_ASSERT(hint > 0); + ST_ASSERT(hint != static_cast(base)); + ST_ASSERT_EQ(vmm_handle_page_fault(space, hint, VmFaultCause::Read), 0); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: brk growth and shrink"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + int64 brk0 = sys_brk(0x400000, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(brk0, 0x400000); + + int64 brk1 = sys_brk(0x402000, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(brk1, 0x402000); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x400000, VmFaultCause::Read), 0); + + int64 brk2 = sys_brk(0x400000, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(brk2, 0x400000); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x401000, VmFaultCause::Read), -EFAULT); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: brk invalid shrink below start"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + ST_ASSERT_EQ(sys_brk(0x500000, 0, 0, 0, 0, 0), 0x500000); + ST_ASSERT_EQ(sys_brk(0x4FF000, 0, 0, 0, 0, 0), -EINVAL); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); + + st_begin("sys_mm: mprotect invalid range"); + { + TaskControlBlock* curr = current_task(); + VmSpace* old_mm = curr->mm; + + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + curr->mm = space; + + ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) > 0, true); + ST_ASSERT_EQ(sys_mprotect(0x123, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); + ST_ASSERT_EQ(sys_mprotect(0x900000, 0x1000, PROT_READ, 0, 0, 0), -ENOMEM); + + curr->mm = old_mm; + space->destroy(); + } + st_pass(); +} diff --git a/test/test_sys_process.cc b/test/test_sys_process.cc new file mode 100644 index 0000000..686bed6 --- /dev/null +++ b/test/test_sys_process.cc @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_sys_process.cc — Self-tests for process-control syscalls + * + * Tests: getpid, gettid, getppid, getuid, getgid, set_tid_address, + * sched_yield, uname. + */ + +#include +#include +#include +#include + +/* Syscall entry points */ +int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); + +void selftest_sys_process() +{ + /* ---- getpid returns current tgid ---- */ + st_begin("sys_process: getpid returns valid pid"); + { + int64 pid = sys_getpid(0, 0, 0, 0, 0, 0); + /* Boot task pid is -1 but tgid may differ; just verify it returns */ + TaskControlBlock* curr = current_task(); + ST_ASSERT_EQ(pid, (int64)curr->tgid); + } + st_pass(); + + /* ---- gettid returns current pid (tid) ---- */ + st_begin("sys_process: gettid returns current tid"); + { + int64 tid = sys_gettid(0, 0, 0, 0, 0, 0); + TaskControlBlock* curr = current_task(); + ST_ASSERT_EQ(tid, (int64)curr->pid); + } + st_pass(); + + /* ---- getppid returns parent tgid ---- */ + st_begin("sys_process: getppid returns parent tgid"); + { + int64 ppid = sys_getppid(0, 0, 0, 0, 0, 0); + TaskControlBlock* curr = current_task(); + int64 expected = curr->parent ? (int64)curr->parent->tgid : 0; + ST_ASSERT_EQ(ppid, expected); + } + st_pass(); + + /* ---- getuid returns 0 (root) ---- */ + st_begin("sys_process: getuid returns 0 (root)"); + { + int64 uid = sys_getuid(0, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(uid, (int64)0); + } + st_pass(); + + /* ---- getgid returns 0 (root) ---- */ + st_begin("sys_process: getgid returns 0 (root)"); + { + int64 gid = sys_getgid(0, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(gid, (int64)0); + } + st_pass(); + + /* ---- set_tid_address stores pointer and returns tid ---- */ + st_begin("sys_process: set_tid_address returns tid"); + { + uint64 dummy_addr = 0x12345678ULL; + int64 tid = sys_set_tid_address(dummy_addr, 0, 0, 0, 0, 0); + TaskControlBlock* curr = current_task(); + ST_ASSERT_EQ(tid, (int64)curr->pid); + ST_ASSERT_EQ(curr->clear_child_tid, dummy_addr); + + /* Reset it to avoid side effects */ + curr->clear_child_tid = 0; + } + st_pass(); + + /* ---- sched_yield returns 0 ---- */ + st_begin("sys_process: sched_yield returns 0"); + { + int64 ret = sys_sched_yield_wrapper(0, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + } +} + diff --git a/test/test_trap.cc b/test/test_trap.cc new file mode 100644 index 0000000..c91948a --- /dev/null +++ b/test/test_trap.cc @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot self-tests for the Trap/IRQ subsystem. + * + * Tests MD→MI translation, IrqManager register/dispatch/unregister, + * and stvec installation verification. + */ + +#include +#include +#include +#include +#include +#include +#include + +TrapCause trap_translate_scause(uint64 scause); + +/* ======================================================================== + * IrqManager test helpers + * ======================================================================== */ + +static volatile uint32 g_test_irq_fired = 0; +static volatile void* g_test_irq_arg = nullptr; + +static void test_irq_handler(uint32 irq, void* arg) +{ + g_test_irq_fired = irq; + g_test_irq_arg = arg; +} + +/* ======================================================================== + * Test cases + * ======================================================================== */ + +static bool test_stvec_installed() { + uint64 vec = arch::trap::get_vector(); + return vec != 0; +} + +static bool test_scause_exception_translation() { + /* Verify all exception causes map correctly */ + bool ok = true; + ok = ok && (trap_translate_scause(EXC_LOAD_PAGE_FAULT) == TrapCause::PageFaultLoad); + ok = ok && (trap_translate_scause(EXC_STORE_PAGE_FAULT) == TrapCause::PageFaultStore); + ok = ok && (trap_translate_scause(EXC_INST_PAGE_FAULT) == TrapCause::PageFaultExec); + ok = ok && (trap_translate_scause(EXC_ECALL_FROM_U) == TrapCause::Syscall); + ok = ok && (trap_translate_scause(EXC_ILLEGAL_INST) == TrapCause::IllegalInst); + ok = ok && (trap_translate_scause(EXC_BREAKPOINT) == TrapCause::Breakpoint); + ok = ok && (trap_translate_scause(EXC_LOAD_ACCESS_FAULT) == TrapCause::AccessFault); + ok = ok && (trap_translate_scause(EXC_INST_MISALIGNED) == TrapCause::AlignFault); + return ok; +} + +static bool test_scause_interrupt_translation() { + bool ok = true; + ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_TIMER) == TrapCause::IrqTimer); + ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_SOFTWARE) == TrapCause::IrqSoftware); + ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_EXTERNAL) == TrapCause::IrqExternal); + ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | 99) == TrapCause::Unknown); + return ok; +} + +static bool test_scause_unknown() { + /* ecall from S-mode should map to Unknown (we only handle U-mode ecall) */ + return trap_translate_scause(EXC_ECALL_FROM_S) == TrapCause::Unknown; +} + +static bool test_irqmanager_register_dispatch() { + IrqManager mgr; + mgr.init(); + + g_test_irq_fired = 0; + g_test_irq_arg = nullptr; + + int dummy_arg = 42; + int ret = mgr.register_handler(10, test_irq_handler, &dummy_arg); + if (ret != 0) return false; + + /* Dispatch IRQ 10 — handler should be called */ + mgr.dispatch(10); + + if (g_test_irq_fired != 10) return false; + if (g_test_irq_arg != &dummy_arg) return false; + + return true; +} + +static bool test_irqmanager_unregister() { + IrqManager mgr; + mgr.init(); + + g_test_irq_fired = 0; + + mgr.register_handler(5, test_irq_handler, nullptr); + mgr.unregister_handler(5); + + /* Dispatch after unregister — handler should NOT be called */ + mgr.dispatch(5); + + return g_test_irq_fired == 0; +} + +static bool test_irqmanager_double_register() { + IrqManager mgr; + mgr.init(); + + int ret1 = mgr.register_handler(7, test_irq_handler, nullptr); + int ret2 = mgr.register_handler(7, test_irq_handler, nullptr); + + return ret1 == 0 && ret2 == -EBUSY; +} + +static bool test_irqmanager_invalid_irq() { + IrqManager mgr; + mgr.init(); + + int ret = mgr.register_handler(999, test_irq_handler, nullptr); + return ret == -EINVAL; +} + +static bool test_irqmanager_has_handler() { + IrqManager mgr; + mgr.init(); + + if (mgr.has_handler(10)) return false; /* not yet registered */ + + mgr.register_handler(10, test_irq_handler, nullptr); + if (!mgr.has_handler(10)) return false; /* now registered */ + + mgr.unregister_handler(10); + if (mgr.has_handler(10)) return false; /* unregistered */ + + return true; +} + +static bool test_irqmanager_spurious_dispatch() { + IrqManager mgr; + mgr.init(); + + g_test_irq_fired = 0; + + /* Dispatch an unregistered IRQ — should not crash or fire */ + mgr.dispatch(0); + mgr.dispatch(63); + + return g_test_irq_fired == 0; +} + +/* ======================================================================== + * Test runner + * ======================================================================== */ + +void selftest_trap() +{ + st_begin("trap: stvec is installed"); + ST_ASSERT(test_stvec_installed()); + st_pass(); + + st_begin("trap: scause exception translation"); + ST_ASSERT(test_scause_exception_translation()); + st_pass(); + + st_begin("trap: scause interrupt translation"); + ST_ASSERT(test_scause_interrupt_translation()); + st_pass(); + + st_begin("trap: scause unknown (ecall from S)"); + ST_ASSERT(test_scause_unknown()); + st_pass(); + + st_begin("trap: IrqManager register/dispatch"); + ST_ASSERT(test_irqmanager_register_dispatch()); + st_pass(); + + st_begin("trap: IrqManager unregister"); + ST_ASSERT(test_irqmanager_unregister()); + st_pass(); + + st_begin("trap: IrqManager double register"); + ST_ASSERT(test_irqmanager_double_register()); + st_pass(); + + st_begin("trap: IrqManager invalid IRQ"); + ST_ASSERT(test_irqmanager_invalid_irq()); + st_pass(); + + st_begin("trap: IrqManager has_handler"); + ST_ASSERT(test_irqmanager_has_handler()); + st_pass(); + + st_begin("trap: IrqManager spurious dispatch"); + ST_ASSERT(test_irqmanager_spurious_dispatch()); + st_pass(); +} diff --git a/test/test_uaccess.cc b/test/test_uaccess.cc new file mode 100644 index 0000000..c5a8c6c --- /dev/null +++ b/test/test_uaccess.cc @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +void selftest_uaccess() +{ + /* To test valid user addresses (< kKernelVaBase), we need a writable page mapped there. + * We cannot use VmSpace::create() because it does not yet inherit kernel mappings, + * so switching to it hangs. We also cannot map it with the U bit because S-mode + * would need the SUM bit enabled. + * So we manually allocate a physical page and map it into the CURRENT page table + * at 0x100000 without the U bit, just for this test. + */ + uint64 mock_user_addr = 0x100000; + + uint64 satp; + asm volatile("csrr %0, satp" : "=r"(satp)); + uint64 root_pa = (satp & 0xFFFFFFFFFFFULL) << 12; + + Page* page = pmm_alloc_page(); + ST_ASSERT(page != nullptr); + uint64 paddr = page_to_pa(page).raw; + + pmap::map(root_pa, mock_user_addr, paddr, pmap::PTE_R | pmap::PTE_W | pmap::PTE_V | pmap::PTE_A | pmap::PTE_D); + + /* Flush TLB to see the new mapping */ + tlb_shootdown_all(); + + st_begin("uaccess: copy_to_user valid range"); + char kernel_buf[32] = "Hello User Space!"; + int ret = copy_to_user(mock_user_addr, kernel_buf, 18); + ST_ASSERT_EQ(ret, 0); + st_pass(); + + st_begin("uaccess: copy_from_user valid range"); + char read_buf[32] = {0}; + /* We wrote "Hello User Space!" to mock_user_addr previously */ + ret = copy_from_user(read_buf, mock_user_addr, 18); + ST_ASSERT_EQ(ret, 0); + ST_ASSERT(read_buf[0] == 'H'); + ST_ASSERT(read_buf[1] == 'e'); + ST_ASSERT(read_buf[6] == 'U'); + st_pass(); + + st_begin("uaccess: strncpy_from_user valid range"); + char str_buf[32] = {0}; + int64 len = strncpy_from_user(str_buf, mock_user_addr, 32); + ST_ASSERT_EQ(len, 17); /* "Hello User Space!" is 17 chars long */ + ST_ASSERT(str_buf[0] == 'H'); + st_pass(); + + st_begin("uaccess: strncpy_from_user max_len truncation"); + char trunc_buf[32] = {0}; + len = strncpy_from_user(trunc_buf, mock_user_addr, 6); + ST_ASSERT_EQ(len, 5); /* Will copy 5 chars and NUL terminate */ + ST_ASSERT(trunc_buf[4] == 'o'); + ST_ASSERT(trunc_buf[5] == '\0'); + st_pass(); + + st_begin("uaccess: copy_to_user invalid range (crosses boundary)"); + ret = copy_to_user(kKernelVaBase - 10, kernel_buf, 20); + ST_ASSERT_EQ(ret, -EFAULT); + st_pass(); + + st_begin("uaccess: copy_to_user invalid range (entirely in kernel)"); + ret = copy_to_user(kKernelVaBase + 0x1000, kernel_buf, 20); + ST_ASSERT_EQ(ret, -EFAULT); + st_pass(); + + st_begin("uaccess: copy_to_user invalid range (overflow)"); + ret = copy_to_user(0xFFFFFFFFFFFFFFF0ULL, kernel_buf, 0x100); + ST_ASSERT_EQ(ret, -EFAULT); + st_pass(); + + st_begin("uaccess: copy_from_user invalid ranges"); + ret = copy_from_user(read_buf, kKernelVaBase - 5, 10); + ST_ASSERT_EQ(ret, -EFAULT); + + ret = copy_from_user(read_buf, kKernelVaBase, 10); + ST_ASSERT_EQ(ret, -EFAULT); + st_pass(); + + st_begin("uaccess: strncpy_from_user invalid range"); + uint64 invalid_addr = kKernelVaBase + 0x1000; + len = strncpy_from_user(str_buf, invalid_addr, 10); + ST_ASSERT_EQ(len, -EFAULT); + st_pass(); +} diff --git a/test/test_vfs_io.cc b/test/test_vfs_io.cc new file mode 100644 index 0000000..3d3b125 --- /dev/null +++ b/test/test_vfs_io.cc @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_vfs_io.cc — Self-tests for VFS file I/O via syscalls + * + * Tests openat, read, write, close, lseek, dup, fstat, getdents64, + * getcwd, chdir, and mkdirat through the kernel syscall layer. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Syscall entry points */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64); + +/* Helper: open a file, returns fd or negative error */ +static int64 open_file(const char* path, int flags, uint32 mode) +{ + return sys_openat( + static_cast(-100), /* AT_FDCWD */ + reinterpret_cast(path), + static_cast(flags), + static_cast(mode), 0, 0); +} + +void selftest_vfs_io() +{ + /* ---- openat O_CREAT creates a file ---- */ + st_begin("vfs_io: openat O_CREAT creates file"); + { + int64 fd = open_file("/vfs_test_file", O_CREAT | O_WRONLY, 0644); + ST_ASSERT(fd >= 0); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- openat existing file ---- */ + st_begin("vfs_io: openat existing file succeeds"); + { + int64 fd = open_file("/vfs_test_file", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- openat nonexistent without O_CREAT returns -ENOENT ---- */ + st_begin("vfs_io: openat nonexistent returns -ENOENT"); + { + int64 fd = open_file("/no_such_file_ever", O_RDONLY, 0); + ST_ASSERT_EQ(fd, (int64)-ENOENT); + } + st_pass(); + + /* ---- write then read ---- */ + st_begin("vfs_io: write then read back data"); + { + /* Write */ + int64 fd = open_file("/vfs_rw_test", O_CREAT | O_WRONLY, 0644); + ST_ASSERT(fd >= 0); + + const char* msg = "Hello VFS!"; + int64 written = sys_write( + static_cast(fd), + reinterpret_cast(msg), + 10, 0, 0, 0); + ST_ASSERT_EQ(written, (int64)10); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + + /* Read */ + fd = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + char buf[32] = {}; + int64 nread = sys_read( + static_cast(fd), + reinterpret_cast(buf), + sizeof(buf), 0, 0, 0); + ST_ASSERT_EQ(nread, (int64)10); + + bool match = true; + for (int i = 0; i < 10; i++) { + if (buf[i] != msg[i]) { match = false; break; } + } + ST_ASSERT(match); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- read past EOF returns 0 ---- */ + st_begin("vfs_io: read past EOF returns 0"); + { + int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + char buf[64]; + /* First read: 10 bytes */ + int64 n1 = sys_read(static_cast(fd), + reinterpret_cast(buf), 64, 0, 0, 0); + ST_ASSERT_EQ(n1, (int64)10); + + /* Second read: should be 0 (EOF) */ + int64 n2 = sys_read(static_cast(fd), + reinterpret_cast(buf), 64, 0, 0, 0); + ST_ASSERT_EQ(n2, (int64)0); + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- O_TRUNC clears file content ---- */ + st_begin("vfs_io: O_TRUNC clears file content"); + { + int64 fd = open_file("/vfs_rw_test", O_WRONLY | O_TRUNC, 0); + ST_ASSERT(fd >= 0); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + + fd = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + char buf[16]; + int64 n = sys_read(static_cast(fd), + reinterpret_cast(buf), 16, 0, 0, 0); + ST_ASSERT_EQ(n, (int64)0); /* truncated = empty */ + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- close invalid fd returns -EBADF ---- */ + st_begin("vfs_io: close bad fd returns -EBADF"); + { + int64 ret = sys_close(999, 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EBADF); + } + st_pass(); + + /* ---- read/write on bad fd returns -EBADF ---- */ + st_begin("vfs_io: read/write bad fd returns -EBADF"); + { + char buf[8]; + int64 r = sys_read(999, reinterpret_cast(buf), 8, 0, 0, 0); + ST_ASSERT_EQ(r, (int64)-EBADF); + + int64 w = sys_write(999, reinterpret_cast(buf), 8, 0, 0, 0); + ST_ASSERT_EQ(w, (int64)-EBADF); + } + st_pass(); + + /* ---- lseek SEEK_SET / SEEK_CUR / SEEK_END ---- */ + st_begin("vfs_io: lseek SEEK_SET/CUR/END"); + { + /* Write 20 bytes */ + int64 fd = open_file("/vfs_lseek_test", O_CREAT | O_RDWR, 0644); + ST_ASSERT(fd >= 0); + + char data[20]; + for (int i = 0; i < 20; i++) data[i] = static_cast('A' + i); + sys_write(static_cast(fd), + reinterpret_cast(data), 20, 0, 0, 0); + + /* SEEK_SET to 5 */ + int64 pos = sys_lseek(static_cast(fd), 5, SEEK_SET, 0, 0, 0); + ST_ASSERT_EQ(pos, (int64)5); + + /* Read 1 byte, should be 'F' */ + char c = 0; + sys_read(static_cast(fd), + reinterpret_cast(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(c, 'F'); + + /* SEEK_CUR +3 → pos 9 */ + pos = sys_lseek(static_cast(fd), 3, SEEK_CUR, 0, 0, 0); + ST_ASSERT_EQ(pos, (int64)9); + + /* SEEK_END -1 → pos 19 */ + pos = sys_lseek(static_cast(fd), -1, SEEK_END, 0, 0, 0); + ST_ASSERT_EQ(pos, (int64)19); + + sys_read(static_cast(fd), + reinterpret_cast(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(c, 'T'); + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- lseek invalid whence returns -EINVAL ---- */ + st_begin("vfs_io: lseek invalid whence"); + { + int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + int64 ret = sys_lseek(static_cast(fd), 0, 99, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- dup: duplicate fd shares file position ---- */ + st_begin("vfs_io: dup shares file position"); + { + int64 fd1 = open_file("/vfs_lseek_test", O_RDONLY, 0); + ST_ASSERT(fd1 >= 0); + + int64 fd2 = sys_dup(static_cast(fd1), 0, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + ST_ASSERT_NE(fd1, fd2); + + /* Both should work for reading */ + char c = 0; + int64 n = sys_read(static_cast(fd2), + reinterpret_cast(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(n, (int64)1); + + sys_close(static_cast(fd1), 0, 0, 0, 0, 0); + sys_close(static_cast(fd2), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- fstat: basic stat ---- */ + st_begin("vfs_io: fstat returns valid kstat"); + { + int64 fd = open_file("/vfs_lseek_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + kstat st{}; + int64 ret = sys_fstat_wrapper( + static_cast(fd), + reinterpret_cast(&st), 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + ST_ASSERT(S_ISREG(st.st_mode)); + ST_ASSERT_EQ(st.st_size, (int64)20); + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- getcwd: returns "/" ---- */ + st_begin("vfs_io: getcwd returns /"); + { + char buf[64] = {}; + int64 len = sys_getcwd( + reinterpret_cast(buf), 64, 0, 0, 0, 0); + ST_ASSERT(len > 0); + ST_ASSERT_EQ(buf[0], '/'); + } + st_pass(); + + /* ---- mkdirat: create directory ---- */ + st_begin("vfs_io: mkdirat creates directory"); + { + int64 ret = sys_mkdirat( + static_cast(-100), + reinterpret_cast("/vfs_io_dir"), + 0755, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* Open it to confirm it's a directory */ + int64 fd = open_file("/vfs_io_dir", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + kstat st{}; + sys_fstat_wrapper(static_cast(fd), + reinterpret_cast(&st), 0, 0, 0, 0); + ST_ASSERT(S_ISDIR(st.st_mode)); + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- getdents64: list root directory ---- */ + st_begin("vfs_io: getdents64 lists root children"); + { + int64 fd = open_file("/", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + char buf[512] = {}; + int64 n = sys_getdents64( + static_cast(fd), + reinterpret_cast(buf), + 512, 0, 0, 0); + ST_ASSERT(n > 0); /* At least "." and ".." */ + + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- write to O_RDONLY fd returns -EBADF ---- */ + st_begin("vfs_io: write to O_RDONLY returns -EBADF"); + { + int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + char buf[] = "x"; + int64 w = sys_write(static_cast(fd), + reinterpret_cast(buf), 1, 0, 0, 0); + ST_ASSERT_EQ(w, (int64)-EBADF); + sys_close(static_cast(fd), 0, 0, 0, 0, 0); + } + st_pass(); + + /* ---- double close returns -EBADF ---- */ + st_begin("vfs_io: double close returns -EBADF"); + { + int64 fd = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd >= 0); + + int64 r1 = sys_close(static_cast(fd), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(r1, (int64)0); + + int64 r2 = sys_close(static_cast(fd), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(r2, (int64)-EBADF); + } + st_pass(); + + /* ---- fd reuse after close ---- */ + st_begin("vfs_io: fd reuse after close"); + { + int64 fd1 = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd1 >= 0); + sys_close(static_cast(fd1), 0, 0, 0, 0, 0); + + int64 fd2 = open_file("/vfs_rw_test", O_RDONLY, 0); + ST_ASSERT(fd2 >= 0); + /* fd2 should reuse the slot of fd1 */ + ST_ASSERT(fd2 <= fd1); + sys_close(static_cast(fd2), 0, 0, 0, 0, 0); + } + st_pass(); +} diff --git a/test/test_vma.cc b/test/test_vma.cc new file mode 100644 index 0000000..5278914 --- /dev/null +++ b/test/test_vma.cc @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include +#include +#include + +void selftest_vma() { + st_begin("vma: create and destroy VmSpace"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT(space != nullptr); + ST_ASSERT(space->root_pa != 0); + + // Add a few regions to ensure destruction works + vmm_map_user(space, 0x1000, 0x1000, VM_READ); + vmm_map_user(space, 0x3000, 0x1000, VM_READ | VM_WRITE); + + space->destroy(); + } + st_pass(); + + st_begin("vma: map user region and handle demand paging"); + { + VmSpace* space = VmSpace::create(); + + // Map 8KB (2 pages) at 0x400000 using MI-layer flags + int ret = vmm_map_user(space, 0x400000, 0x2000, VM_READ | VM_WRITE); + ST_ASSERT_EQ(ret, 0); + + // Fault in the first page + ret = vmm_handle_page_fault(space, 0x400500, VmFaultCause::Read); + ST_ASSERT_EQ(ret, 0); + + // Verify mapping in hardware page table + uint64 pa = 0; + bool mapped = pmap::lookup(space->root_pa, 0x400000, &pa); + ST_ASSERT(mapped); + ST_ASSERT_NE(pa, 0ULL); + + // Fault in the second page + ret = vmm_handle_page_fault(space, 0x401FFF, VmFaultCause::Read); + ST_ASSERT_EQ(ret, 0); + + // Should reject fault outside VMA with -EFAULT + ret = vmm_handle_page_fault(space, 0x402000, VmFaultCause::Read); + ST_ASSERT_EQ(ret, -EFAULT); + + space->destroy(); + } + st_pass(); + + st_begin("vma: unmap user region prevents page fault"); + { + VmSpace* space = VmSpace::create(); + vmm_map_user(space, 0x10000, 0x2000, VM_READ); + + // Inside the region — should succeed + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), 0); + + // Unmap exactly + vmm_unmap_user(space, 0x10000, 0x2000); + + // Should fail now with -EFAULT + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), -EFAULT); + + space->destroy(); + } + st_pass(); + + st_begin("vma: duplicate page fault on same page returns 0"); + { + VmSpace* space = VmSpace::create(); + vmm_map_user(space, 0x200000, 0x1000, VM_READ | VM_WRITE); + + // First fault allocates the physical page + int ret = vmm_handle_page_fault(space, 0x200000, VmFaultCause::Read); + ST_ASSERT_EQ(ret, 0); + + // Second fault on the same page should hit "already mapped" path + ret = vmm_handle_page_fault(space, 0x200100, VmFaultCause::Read); + ST_ASSERT_EQ(ret, 0); + + // Verify both land on the same physical page + uint64 pa1 = 0, pa2 = 0; + pmap::lookup(space->root_pa, 0x200000, &pa1); + pmap::lookup(space->root_pa, 0x200100, &pa2); + // Same page, so PA for both addresses should be the same base page + ST_ASSERT_EQ(pa1, pa2); + + space->destroy(); + } + st_pass(); + + st_begin("vma: vm_perm_to_pte flag translation correctness"); + { + // Test each MI flag maps to correct hardware PTE bit + uint64 pte_r = pmap::vm_perm_to_pte(VM_READ); + ST_ASSERT(pte_r & pmap::PTE_R); + ST_ASSERT(!(pte_r & pmap::PTE_W)); + ST_ASSERT(!(pte_r & pmap::PTE_X)); + ST_ASSERT(!(pte_r & pmap::PTE_U)); + + uint64 pte_rw = pmap::vm_perm_to_pte(VM_READ | VM_WRITE); + ST_ASSERT(pte_rw & pmap::PTE_R); + ST_ASSERT(pte_rw & pmap::PTE_W); + + uint64 pte_rx = pmap::vm_perm_to_pte(VM_READ | VM_EXEC); + ST_ASSERT(pte_rx & pmap::PTE_R); + ST_ASSERT(pte_rx & pmap::PTE_X); + ST_ASSERT(!(pte_rx & pmap::PTE_W)); + + uint64 pte_u = pmap::vm_perm_to_pte(VM_READ | VM_USER); + ST_ASSERT(pte_u & pmap::PTE_R); + ST_ASSERT(pte_u & pmap::PTE_U); + + // All combined + uint64 pte_all = pmap::vm_perm_to_pte(VM_READ | VM_WRITE | VM_EXEC | VM_USER); + ST_ASSERT(pte_all & pmap::PTE_R); + ST_ASSERT(pte_all & pmap::PTE_W); + ST_ASSERT(pte_all & pmap::PTE_X); + ST_ASSERT(pte_all & pmap::PTE_U); + ST_ASSERT(pte_all & pmap::PTE_V); // Always valid + } + st_pass(); + + st_begin("vma: unmap subset only removes contained VMAs"); + { + VmSpace* space = VmSpace::create(); + // Map three separate regions + vmm_map_user(space, 0x50000, 0x1000, VM_READ); + vmm_map_user(space, 0x60000, 0x1000, VM_READ); + vmm_map_user(space, 0x70000, 0x1000, VM_READ); + + // Unmap only the middle one (range covers 0x60000-0x61000) + vmm_unmap_user(space, 0x60000, 0x1000); + + // First and third should still be reachable + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, VmFaultCause::Read), 0); + + // Middle should fail + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, VmFaultCause::Read), -EFAULT); + + space->destroy(); + } + st_pass(); + + st_begin("vma: null space returns -EINVAL"); + { + ST_ASSERT_EQ(vmm_map_user(nullptr, 0x1000, 0x1000, VM_READ), -EINVAL); + ST_ASSERT_EQ(vmm_unmap_user(nullptr, 0x1000, 0x1000), -EINVAL); + ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, VmFaultCause::Read), -EINVAL); + } + st_pass(); + + st_begin("vma: clone and copy-on-write"); + { + VmSpace* parent = VmSpace::create(); + vmm_map_user(parent, 0x80000, 0x1000, VM_READ | VM_WRITE); + + /* Trigger read fault in parent to allocate physical page */ + ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Read), 0); + uint64 parent_pa_initial; + ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_initial)); + + /* Clone the address space */ + VmSpace* child = vmm_clone(parent); + ST_ASSERT(child != nullptr); + + /* Verify child has the same physical page mapped but COW */ + uint64 child_pa; + ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa)); + ST_ASSERT_EQ(child_pa, parent_pa_initial); + ST_ASSERT(pmap::is_cow(child->root_pa, 0x80000)); + ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); /* Parent also marked COW */ + + /* Trigger Store Page Fault (cause=15) in child */ + ST_ASSERT_EQ(vmm_handle_page_fault(child, 0x80000, VmFaultCause::Write), 0); + + /* Child should now have a new physical page, no longer COW */ + uint64 child_pa_new; + ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa_new)); + ST_ASSERT(child_pa_new != parent_pa_initial); + ST_ASSERT(!pmap::is_cow(child->root_pa, 0x80000)); + + /* Parent is still mapped to the old page, still COW because it hasn't written */ + uint64 parent_pa_current; + ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_current)); + ST_ASSERT_EQ(parent_pa_current, parent_pa_initial); + ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); + + /* Trigger Store Page Fault in parent */ + ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Write), 0); + ST_ASSERT(!pmap::is_cow(parent->root_pa, 0x80000)); + + child->destroy(); + parent->destroy(); + } + st_pass(); + + st_begin("vma: gap search finds next free range"); + { + VmSpace* space = VmSpace::create(); + vmm_map_user(space, 0x1000, 0x3000, VM_READ); + + uint64 va = mm_find_free_va(space, 0x1000, kPageSize); + ST_ASSERT_EQ(va, 0x4000ULL); + + space->destroy(); + } + st_pass(); + + st_begin("vma: reject overlapping mapping"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT_EQ(vmm_map_user(space, 0x20000, 0x2000, VM_READ), 0); + ST_ASSERT_EQ(vmm_map_user(space, 0x21000, 0x1000, VM_READ), -EINVAL); + space->destroy(); + } + st_pass(); + + st_begin("vma: partial unmap splits a single VMA"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT_EQ(vmm_map_user(space, 0x80000, 0x3000, VM_READ | VM_WRITE), 0); + + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); + + ST_ASSERT_EQ(vmm_unmap_user(space, 0x81000, 0x1000), 0); + + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), -EFAULT); + + space->destroy(); + } + st_pass(); + + st_begin("vma: mprotect read-only blocks write faults"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT_EQ(vmm_map_user(space, 0x90000, 0x2000, VM_READ | VM_WRITE), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Read), 0); + + ST_ASSERT_EQ(vmm_protect_user(space, 0x90000, 0x1000, VM_READ), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Write), -EFAULT); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x91000, VmFaultCause::Write), 0); + + space->destroy(); + } + st_pass(); + + st_begin("vma: PROT_NONE unmaps existing pages"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT_EQ(vmm_map_user(space, 0xA0000, 0x1000, VM_READ | VM_WRITE), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), 0); + + ST_ASSERT_EQ(vmm_protect_user(space, 0xA0000, 0x1000, 0), 0); + ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), -EFAULT); + + space->destroy(); + } + st_pass(); + + st_begin("vma: range covered check"); + { + VmSpace* space = VmSpace::create(); + ST_ASSERT_EQ(vmm_map_user(space, 0xB0000, 0x2000, VM_READ), 0); + + ST_ASSERT(vma_range_covered(space, 0xB0000, 0xB1000)); + ST_ASSERT(!vma_range_covered(space, 0xB0000, 0xB3000)); + + space->destroy(); + } + st_pass(); +} diff --git a/test/test_vmm.cc b/test/test_vmm.cc new file mode 100644 index 0000000..635e3f3 --- /dev/null +++ b/test/test_vmm.cc @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Boot Self-Test: VMM and Address Translation + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr uint64 kUartBase = 0x10000000ULL; +constexpr uint64 kPageOffsetMask = kPageSize - 1; +} + +void selftest_vmm() { + st_begin("vmm: virtual/physical address translation"); + { + PhysAddr pa = pmm_get_mem_base(); + VirtAddr va = pa_to_va(pa); + ST_ASSERT_EQ(va.raw, arch::g_direct_map_base + pa.raw); + ST_ASSERT_EQ(va_to_pa(va).raw, pa.raw); + + Pfn p = pa_to_pfn(pa); + ST_ASSERT_EQ(p.raw, pa.raw / kPageSize); + ST_ASSERT_EQ(pfn_to_pa(p).raw, pa.raw); + } + st_pass(); + + st_begin("vmm: page alignment macros"); + { + uint64 base = pmm_get_mem_base().raw; + uint64 addr = base + kPageOffsetMask; + ST_ASSERT_EQ(page_align_down(addr), base); + ST_ASSERT_EQ(page_align_up(addr), base + kPageSize); + ST_ASSERT_EQ(page_align_down(base), base); + ST_ASSERT_EQ(page_align_up(base), base); + } + st_pass(); + + st_begin("vmm: mapping kernel MMIO (driver API)"); + { + /* Use UART MMIO address (0x10000000 on RISC-V virt). + * Must NOT use pmm_get_mem_base() — that's PMP-protected firmware. */ + uint64 mmio_pa = kUartBase; + vmm_map_kernel_mmio(mmio_pa, kPageSize); + + // Verify it doesn't crash and address is reachable in higher half + uint64 va = pa_to_va(phys_addr(mmio_pa)).raw; + ST_ASSERT(va >= arch::g_direct_map_base); + } + st_pass(); +} diff --git a/test/test_waitqueue.cc b/test/test_waitqueue.cc new file mode 100644 index 0000000..7f50d6b --- /dev/null +++ b/test/test_waitqueue.cc @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include +#include +#include +#include + +static WaitQueue g_test_wq; +static volatile bool g_thread_woken = false; +static volatile bool g_thread_started = false; +static volatile int g_wake_count = 0; + +extern TaskControlBlock* kthread_create(const char* name, void (*entry)(void*), void* arg, uint8 priority); + +static void waitqueue_test_thread(void*) +{ + g_thread_started = true; + g_test_wq.sleep(); + g_thread_woken = true; + + extern void task_exit(int code); + task_exit(0); +} + +static void waitqueue_test_thread_all(void*) +{ + g_test_wq.sleep(); + __sync_fetch_and_add(&g_wake_count, 1); + + extern void task_exit(int code); + task_exit(0); +} + +void selftest_waitqueue() +{ + st_begin("waitqueue: sleep and wake_one"); + + g_test_wq.init("test_wq"); + g_thread_started = false; + g_thread_woken = false; + + TaskControlBlock* child = kthread_create("test_wq_child", waitqueue_test_thread, nullptr, 0); + ST_ASSERT(child != nullptr); + + sched_yield(); + + ST_ASSERT(g_thread_started == true); + ST_ASSERT(g_thread_woken == false); + ST_ASSERT(!g_test_wq.is_empty()); + + kprintf("[test_wq] waking child\n"); + bool woken = g_test_wq.wake_one(); + ST_ASSERT(woken == true); + ST_ASSERT(g_test_wq.is_empty()); + + kprintf("[test_wq] yielding to child again\n"); + sched_yield(); + kprintf("[test_wq] returned from yield 2\n"); + + ST_ASSERT(g_thread_woken == true); + + st_pass(); + + st_begin("waitqueue: wake_all"); + g_wake_count = 0; + for (int i = 0; i < 3; i++) { + kthread_create("test_wq_all", waitqueue_test_thread_all, nullptr, 0); + } + + /* Give all 3 threads a chance to start and sleep */ + kprintf("[test_wq] yielding 1 for wake_all\n"); + sched_yield(); + sched_yield(); + sched_yield(); + + ST_ASSERT(!g_test_wq.is_empty()); + + int woken_count = g_test_wq.wake_all(); + + ST_ASSERT_EQ(woken_count, 3); + ST_ASSERT(g_test_wq.is_empty()); + + /* Give all 3 threads a chance to wake up and run */ + sched_yield(); + sched_yield(); + sched_yield(); + + ST_ASSERT_EQ(g_wake_count, 3); + st_pass(); +} diff --git a/usr/default_cpio_list b/usr/default_cpio_list new file mode 100644 index 0000000..6721365 --- /dev/null +++ b/usr/default_cpio_list @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Default initramfs source — minimal, no userland. +# +# Mirrors Linux's usr/default_cpio_list: just enough nodes for the kernel +# to mount a root and panic cleanly on "no init found". Real workloads +# override CONFIG_INITRAMFS_SOURCE. +# +# Format (matches gen_init_cpio): +# dir +# nod +# file +# slink +# Lines starting with '#' are comments. Blank lines are ignored. + +dir /dev 0755 0 0 +nod /dev/console 0600 0 0 c 5 1 +nod /dev/null 0666 0 0 c 1 3 +nod /dev/zero 0666 0 0 c 1 5 +dir /proc 0755 0 0 +dir /sys 0755 0 0 +dir /tmp 1777 0 0 +dir /sbin 0755 0 0 +dir /bin 0755 0 0 +dir /etc 0755 0 0 diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c new file mode 100644 index 0000000..b7296ed --- /dev/null +++ b/usr/gen_init_cpio.c @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Original work by Jeff Garzik + * + * External file lists, symlink, pipe and fifo support by Thayne Harbaugh + * Hard link support by Luciano Rocha + */ + +#define xstr(s) #s +#define str(s) xstr(s) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define CPIO_HDR_LEN 110 +#define CPIO_TRAILER "TRAILER!!!" +#define padlen(_off, _align) (((_align) - ((_off) & ((_align) - 1))) % (_align)) + +/* zero-padding the filename field for data alignment is limited by PATH_MAX */ +static char padding[PATH_MAX]; +static unsigned int offset; +static unsigned int ino = 721; +static time_t default_mtime; +static bool do_file_mtime; +static bool do_csum = false; +static int outfd = STDOUT_FILENO; +static unsigned int dalign; + +struct file_handler { + const char *type; + int (*handler)(const char *line); +}; + +static int push_buf(const char *name, size_t name_len) +{ + ssize_t len; + + len = write(outfd, name, name_len); + if (len != name_len) + return -1; + + offset += name_len; + return 0; +} + +static int push_pad(size_t padlen) +{ + ssize_t len = 0; + + if (!padlen) + return 0; + + if (padlen < sizeof(padding)) + len = write(outfd, padding, padlen); + if (len != padlen) + return -1; + + offset += padlen; + return 0; +} + +static int push_rest(const char *name, size_t name_len) +{ + ssize_t len; + + len = write(outfd, name, name_len); + if (len != name_len) + return -1; + + offset += name_len; + + return push_pad(padlen(name_len + CPIO_HDR_LEN, 4)); +} + +static int cpio_trailer(void) +{ + int len; + unsigned int namesize = sizeof(CPIO_TRAILER); + + len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" + "%08X%08X%08X%08X%08X%08X%08X", + do_csum ? "070702" : "070701", /* magic */ + 0, /* ino */ + 0, /* mode */ + (long) 0, /* uid */ + (long) 0, /* gid */ + 1, /* nlink */ + (long) 0, /* mtime */ + 0, /* filesize */ + 0, /* major */ + 0, /* minor */ + 0, /* rmajor */ + 0, /* rminor */ + namesize, /* namesize */ + 0); /* chksum */ + offset += len; + + if (len != CPIO_HDR_LEN || + push_rest(CPIO_TRAILER, namesize) < 0 || + push_pad(padlen(offset, 512)) < 0) + return -1; + + if (fsync(outfd) < 0 && errno != EINVAL) + return -1; + + return 0; +} + +static int cpio_mkslink(const char *name, const char *target, + unsigned int mode, uid_t uid, gid_t gid) +{ + int len; + unsigned int namesize, targetsize = strlen(target) + 1; + + if (name[0] == '/') + name++; + namesize = strlen(name) + 1; + + len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" + "%08X%08X%08X%08X%08X%08X%08X", + do_csum ? "070702" : "070701", /* magic */ + ino++, /* ino */ + S_IFLNK | mode, /* mode */ + (long) uid, /* uid */ + (long) gid, /* gid */ + 1, /* nlink */ + (long) default_mtime, /* mtime */ + targetsize, /* filesize */ + 3, /* major */ + 1, /* minor */ + 0, /* rmajor */ + 0, /* rminor */ + namesize, /* namesize */ + 0); /* chksum */ + offset += len; + + if (len != CPIO_HDR_LEN || + push_buf(name, namesize) < 0 || + push_pad(padlen(offset, 4)) < 0 || + push_buf(target, targetsize) < 0 || + push_pad(padlen(offset, 4)) < 0) + return -1; + + return 0; + +} + +static int cpio_mkslink_line(const char *line) +{ + char name[PATH_MAX + 1]; + char target[PATH_MAX + 1]; + unsigned int mode; + int uid; + int gid; + int rc = -1; + + if (5 != sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX) "s %o %d %d", name, target, &mode, &uid, &gid)) { + fprintf(stderr, "Unrecognized dir format '%s'", line); + goto fail; + } + rc = cpio_mkslink(name, target, mode, uid, gid); + fail: + return rc; +} + +static int cpio_mkgeneric(const char *name, unsigned int mode, + uid_t uid, gid_t gid) +{ + int len; + unsigned int namesize; + + if (name[0] == '/') + name++; + namesize = strlen(name) + 1; + + len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" + "%08X%08X%08X%08X%08X%08X%08X", + do_csum ? "070702" : "070701", /* magic */ + ino++, /* ino */ + mode, /* mode */ + (long) uid, /* uid */ + (long) gid, /* gid */ + 2, /* nlink */ + (long) default_mtime, /* mtime */ + 0, /* filesize */ + 3, /* major */ + 1, /* minor */ + 0, /* rmajor */ + 0, /* rminor */ + namesize, /* namesize */ + 0); /* chksum */ + offset += len; + + if (len != CPIO_HDR_LEN || + push_rest(name, namesize) < 0) + return -1; + + return 0; +} + +enum generic_types { + GT_DIR, + GT_PIPE, + GT_SOCK +}; + +struct generic_type { + const char *type; + mode_t mode; +}; + +static const struct generic_type generic_type_table[] = { + [GT_DIR] = { + .type = "dir", + .mode = S_IFDIR + }, + [GT_PIPE] = { + .type = "pipe", + .mode = S_IFIFO + }, + [GT_SOCK] = { + .type = "sock", + .mode = S_IFSOCK + } +}; + +static int cpio_mkgeneric_line(const char *line, enum generic_types gt) +{ + char name[PATH_MAX + 1]; + unsigned int mode; + int uid; + int gid; + int rc = -1; + + if (4 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d", name, &mode, &uid, &gid)) { + fprintf(stderr, "Unrecognized %s format '%s'", + line, generic_type_table[gt].type); + goto fail; + } + mode |= generic_type_table[gt].mode; + rc = cpio_mkgeneric(name, mode, uid, gid); + fail: + return rc; +} + +static int cpio_mkdir_line(const char *line) +{ + return cpio_mkgeneric_line(line, GT_DIR); +} + +static int cpio_mkpipe_line(const char *line) +{ + return cpio_mkgeneric_line(line, GT_PIPE); +} + +static int cpio_mksock_line(const char *line) +{ + return cpio_mkgeneric_line(line, GT_SOCK); +} + +static int cpio_mknod(const char *name, unsigned int mode, + uid_t uid, gid_t gid, char dev_type, + unsigned int maj, unsigned int min) +{ + int len; + unsigned int namesize; + + if (dev_type == 'b') + mode |= S_IFBLK; + else + mode |= S_IFCHR; + + if (name[0] == '/') + name++; + namesize = strlen(name) + 1; + + len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" + "%08X%08X%08X%08X%08X%08X%08X", + do_csum ? "070702" : "070701", /* magic */ + ino++, /* ino */ + mode, /* mode */ + (long) uid, /* uid */ + (long) gid, /* gid */ + 1, /* nlink */ + (long) default_mtime, /* mtime */ + 0, /* filesize */ + 3, /* major */ + 1, /* minor */ + maj, /* rmajor */ + min, /* rminor */ + namesize, /* namesize */ + 0); /* chksum */ + offset += len; + + if (len != CPIO_HDR_LEN || + push_rest(name, namesize) < 0) + return -1; + + return 0; +} + +static int cpio_mknod_line(const char *line) +{ + char name[PATH_MAX + 1]; + unsigned int mode; + int uid; + int gid; + char dev_type; + unsigned int maj; + unsigned int min; + int rc = -1; + + if (7 != sscanf(line, "%" str(PATH_MAX) "s %o %d %d %c %u %u", + name, &mode, &uid, &gid, &dev_type, &maj, &min)) { + fprintf(stderr, "Unrecognized nod format '%s'", line); + goto fail; + } + rc = cpio_mknod(name, mode, uid, gid, dev_type, maj, min); + fail: + return rc; +} + +static int cpio_mkfile_csum(int fd, unsigned long size, uint32_t *csum) +{ + while (size) { + unsigned char filebuf[65536]; + ssize_t this_read; + size_t i, this_size = MIN(size, sizeof(filebuf)); + + this_read = read(fd, filebuf, this_size); + if (this_read <= 0 || this_read > this_size) + return -1; + + for (i = 0; i < this_read; i++) + *csum += filebuf[i]; + + size -= this_read; + } + /* seek back to the start for data segment I/O */ + if (lseek(fd, 0, SEEK_SET) < 0) + return -1; + + return 0; +} + +static int cpio_mkfile(const char *name, const char *location, + unsigned int mode, uid_t uid, gid_t gid, + unsigned int nlinks) +{ + struct stat buf; + unsigned long size; + int file, retval, len; + int rc = -1; + time_t mtime; + int namesize, namepadlen; + unsigned int i; + uint32_t csum = 0; + ssize_t this_read; + + mode |= S_IFREG; + + file = open (location, O_RDONLY); + if (file < 0) { + fprintf (stderr, "File %s could not be opened for reading\n", location); + goto error; + } + + retval = fstat(file, &buf); + if (retval) { + fprintf(stderr, "File %s could not be stat()'ed\n", location); + goto error; + } + + if (do_file_mtime) { + mtime = default_mtime; + } else { + mtime = buf.st_mtime; + if (mtime > 0xffffffff) { + fprintf(stderr, "%s: Timestamp exceeds maximum cpio timestamp, clipping.\n", + location); + mtime = 0xffffffff; + } + + if (mtime < 0) { + fprintf(stderr, "%s: Timestamp negative, clipping.\n", + location); + mtime = 0; + } + } + + if (buf.st_size > 0xffffffff) { + fprintf(stderr, "%s: Size exceeds maximum cpio file size\n", + location); + goto error; + } + + if (do_csum && cpio_mkfile_csum(file, buf.st_size, &csum) < 0) { + fprintf(stderr, "Failed to checksum file %s\n", location); + goto error; + } + + size = 0; + namepadlen = 0; + for (i = 1; i <= nlinks; i++) { + if (name[0] == '/') + name++; + namesize = strlen(name) + 1; + + /* data goes on last link, after any alignment padding */ + if (i == nlinks) + size = buf.st_size; + + if (dalign && size > dalign) { + namepadlen = padlen(offset + CPIO_HDR_LEN + namesize, + dalign); + if (namesize + namepadlen > PATH_MAX) { + fprintf(stderr, + "%s: best-effort alignment %u missed\n", + name, dalign); + namepadlen = 0; + } + } + + len = dprintf(outfd, "%s%08X%08X%08lX%08lX%08X%08lX" + "%08lX%08X%08X%08X%08X%08X%08X", + do_csum ? "070702" : "070701", /* magic */ + ino, /* ino */ + mode, /* mode */ + (long) uid, /* uid */ + (long) gid, /* gid */ + nlinks, /* nlink */ + (long) mtime, /* mtime */ + size, /* filesize */ + 3, /* major */ + 1, /* minor */ + 0, /* rmajor */ + 0, /* rminor */ + namesize + namepadlen, /* namesize */ + size ? csum : 0); /* chksum */ + offset += len; + + if (len != CPIO_HDR_LEN || + push_buf(name, namesize) < 0 || + push_pad(namepadlen ? namepadlen : padlen(offset, 4)) < 0) + goto error; + + if (size) { + this_read = copy_file_range(file, NULL, outfd, NULL, size, 0); + if (this_read > 0) { + if (this_read > size) + goto error; + offset += this_read; + size -= this_read; + } + /* short or failed copy falls back to read/write... */ + } + + while (size) { + unsigned char filebuf[65536]; + size_t this_size = MIN(size, sizeof(filebuf)); + + this_read = read(file, filebuf, this_size); + if (this_read <= 0 || this_read > this_size) { + fprintf(stderr, "Can not read %s file\n", location); + goto error; + } + + if (write(outfd, filebuf, this_read) != this_read) { + fprintf(stderr, "writing filebuf failed\n"); + goto error; + } + offset += this_read; + size -= this_read; + } + if (push_pad(padlen(offset, 4)) < 0) + goto error; + + name += namesize; + } + ino++; + rc = 0; + +error: + if (file >= 0) + close(file); + return rc; +} + +static char *cpio_replace_env(char *new_location) +{ + char expanded[PATH_MAX + 1]; + char *start, *end, *var; + + while ((start = strstr(new_location, "${")) && + (end = strchr(start + 2, '}'))) { + *start = *end = 0; + var = getenv(start + 2); + snprintf(expanded, sizeof expanded, "%s%s%s", + new_location, var ? var : "", end + 1); + strcpy(new_location, expanded); + } + + return new_location; +} + +static int cpio_mkfile_line(const char *line) +{ + char name[PATH_MAX + 1]; + char *dname = NULL; /* malloc'ed buffer for hard links */ + char location[PATH_MAX + 1]; + unsigned int mode; + int uid; + int gid; + int nlinks = 1; + int end = 0, dname_len = 0; + int rc = -1; + + if (5 > sscanf(line, "%" str(PATH_MAX) "s %" str(PATH_MAX) + "s %o %d %d %n", + name, location, &mode, &uid, &gid, &end)) { + fprintf(stderr, "Unrecognized file format '%s'", line); + goto fail; + } + if (end && isgraph(line[end])) { + int len; + int nend; + + dname = malloc(strlen(line)); + if (!dname) { + fprintf (stderr, "out of memory (%d)\n", dname_len); + goto fail; + } + + dname_len = strlen(name) + 1; + memcpy(dname, name, dname_len); + + do { + nend = 0; + if (sscanf(line + end, "%" str(PATH_MAX) "s %n", + name, &nend) < 1) + break; + len = strlen(name) + 1; + memcpy(dname + dname_len, name, len); + dname_len += len; + nlinks++; + end += nend; + } while (isgraph(line[end])); + } else { + dname = name; + } + rc = cpio_mkfile(dname, cpio_replace_env(location), + mode, uid, gid, nlinks); + fail: + if (dname_len) free(dname); + return rc; +} + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage:\n" + "\t%s [-t ] [-c] [-o ] [-a ] \n" + "\n" + " is a file containing newline separated entries that\n" + "describe the files to be included in the initramfs archive:\n" + "\n" + "# a comment\n" + "file []\n" + "dir \n" + "nod \n" + "slink \n" + "pipe \n" + "sock \n" + "\n" + " name of the file/dir/nod/etc in the archive\n" + " location of the file in the current filesystem\n" + " expands shell variables quoted with ${}\n" + " link target\n" + " mode/permissions of the file\n" + " user id (0=root)\n" + " group id (0=root)\n" + " device type (b=block, c=character)\n" + " major number of nod\n" + " minor number of nod\n" + " space separated list of other links to file\n" + "\n" + "example:\n" + "# A simple initramfs\n" + "dir /dev 0755 0 0\n" + "nod /dev/console 0600 0 0 c 5 1\n" + "dir /root 0700 0 0\n" + "dir /sbin 0755 0 0\n" + "file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0\n" + "\n" + " is time in seconds since Epoch that will be used\n" + "as mtime for symlinks, directories, regular and special files.\n" + "The default is to use the current time for all files, but\n" + "preserve modification time for regular files.\n" + "-c: calculate and store 32-bit checksums for file data.\n" + ": write cpio to this file instead of stdout\n" + ": attempt to align file data by zero-padding the\n" + "filename field up to data_align. Must be a multiple of 4.\n" + "Alignment is best-effort; PATH_MAX limits filename padding.\n", + prog); +} + +static const struct file_handler file_handler_table[] = { + { + .type = "file", + .handler = cpio_mkfile_line, + }, { + .type = "nod", + .handler = cpio_mknod_line, + }, { + .type = "dir", + .handler = cpio_mkdir_line, + }, { + .type = "slink", + .handler = cpio_mkslink_line, + }, { + .type = "pipe", + .handler = cpio_mkpipe_line, + }, { + .type = "sock", + .handler = cpio_mksock_line, + }, { + .type = NULL, + .handler = NULL, + } +}; + +#define LINE_SIZE (2 * PATH_MAX + 50) + +int main (int argc, char *argv[]) +{ + FILE *cpio_list; + char line[LINE_SIZE]; + char *args, *type; + int ec = 0; + int line_nr = 0; + const char *filename; + + default_mtime = time(NULL); + while (1) { + int opt = getopt(argc, argv, "t:cho:a:"); + char *invalid; + + if (opt == -1) + break; + switch (opt) { + case 't': + default_mtime = strtol(optarg, &invalid, 10); + if (!*optarg || *invalid) { + fprintf(stderr, "Invalid timestamp: %s\n", + optarg); + usage(argv[0]); + exit(1); + } + do_file_mtime = true; + break; + case 'c': + do_csum = true; + break; + case 'o': + outfd = open(optarg, + O_WRONLY | O_CREAT | O_LARGEFILE | O_TRUNC, + 0600); + if (outfd < 0) { + fprintf(stderr, "failed to open %s\n", optarg); + usage(argv[0]); + exit(1); + } + break; + case 'a': + dalign = strtoul(optarg, &invalid, 10); + if (!*optarg || *invalid || (dalign & 3)) { + fprintf(stderr, "Invalid data_align: %s\n", + optarg); + usage(argv[0]); + exit(1); + } + break; + case 'h': + case '?': + usage(argv[0]); + exit(opt == 'h' ? 0 : 1); + } + } + + /* + * Timestamps after 2106-02-07 06:28:15 UTC have an ascii hex time_t + * representation that exceeds 8 chars and breaks the cpio header + * specification. Negative timestamps similarly exceed 8 chars. + */ + if (default_mtime > 0xffffffff || default_mtime < 0) { + fprintf(stderr, "ERROR: Timestamp out of range for cpio format\n"); + exit(1); + } + + if (argc - optind != 1) { + usage(argv[0]); + exit(1); + } + filename = argv[optind]; + if (!strcmp(filename, "-")) + cpio_list = stdin; + else if (!(cpio_list = fopen(filename, "r"))) { + fprintf(stderr, "ERROR: unable to open '%s': %s\n\n", + filename, strerror(errno)); + usage(argv[0]); + exit(1); + } + + while (fgets(line, LINE_SIZE, cpio_list)) { + int type_idx; + size_t slen = strlen(line); + + line_nr++; + + if ('#' == *line) { + /* comment - skip to next line */ + continue; + } + + if (! (type = strtok(line, " \t"))) { + fprintf(stderr, + "ERROR: incorrect format, could not locate file type line %d: '%s'\n", + line_nr, line); + ec = -1; + break; + } + + if ('\n' == *type) { + /* a blank line */ + continue; + } + + if (slen == strlen(type)) { + /* must be an empty line */ + continue; + } + + if (! (args = strtok(NULL, "\n"))) { + fprintf(stderr, + "ERROR: incorrect format, newline required line %d: '%s'\n", + line_nr, line); + ec = -1; + } + + for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) { + int rc; + if (! strcmp(line, file_handler_table[type_idx].type)) { + if ((rc = file_handler_table[type_idx].handler(args))) { + ec = rc; + fprintf(stderr, " line %d\n", line_nr); + } + break; + } + } + + if (NULL == file_handler_table[type_idx].type) { + fprintf(stderr, "unknown file type line %d: '%s'\n", + line_nr, line); + } + } + if (ec == 0) + ec = cpio_trailer(); + + exit(ec); +} -- cgit v1.2.3