diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:24:56 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:24:56 +0800 |
| commit | 1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (patch) | |
| tree | 0f5e01cd7c7a05abb6b1c7472d7f00f49caf02cc | |
| parent | 63dc025705f6533b91fb1a7d26e6ed864efd9900 (diff) | |
| download | lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.gz lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.xz | |
feat: establish bootable new architecture checkpoint
Replace the compressed boot path with a relocatable higher-half RISC-V entry, expand memory and SMP support, and add the driver, TTY, VFS, exec, signal, syscall, initramfs, and self-test infrastructure needed for a bootable kernel checkpoint.
212 files changed, 26859 insertions, 2530 deletions
diff --git a/na/Makefile b/na/Makefile index e950b35..88689f5 100644 --- a/na/Makefile +++ b/na/Makefile @@ -18,16 +18,37 @@ # ============================================================ 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) -VMLINUX_PRE := $(BUILD)/kernel.pre.elf -VMLINUX := $(BUILD)/kernel.elf -VMLINUX_RAW := $(BUILD)/vmlume.raw.bin -IMAGE := $(BUILD)/vmlume.bin -RELOC_BIN := $(BUILD)/reloc.bin -RELOC_OBJ := $(BUILD)/reloc.o + +# 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 @@ -37,15 +58,18 @@ PYTHON ?= python3 # ============================================================ 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-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- @@ -86,8 +110,10 @@ SRCS_s := -include arch/$(ARCH)/module.mk include kernel/module.mk include mm/module.mk -# include fs/module.mk # Phase 3 +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 @@ -96,11 +122,17 @@ include test/module.mk 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 @@ -110,9 +142,23 @@ CXXFLAGS := $(CFLAGS) \ -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ -fno-threadsafe-statics -ASFLAGS := $(CFLAGS) - -LDFLAGS := -nostdlib -static --emit-relocs +# 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 @@ -129,14 +175,94 @@ DEPS := $(OBJS:.o=.d) # ============================================================ .PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig -all: $(IMAGE) +all: $(VMLUME) $(IMAGE) # Generate autoconf.h before any compilation $(AUTOCONF_H): scripts/Kconfig.mk $(wildcard .config) $(generate-autoconf) -# All objects depend on autoconf.h -$(OBJS): $(AUTOCONF_H) +# ============================================================ +# 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 +# <generated/asm-offsets.h>` 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_<name>. +# 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 @@ -144,68 +270,96 @@ $(LINKER_SCRIPT): $(LDS_SRC) include/lume/lumeos.lds.h @mkdir -p $(dir $@) @$(CC) -E -P -x c $(INCLUDES) $< -o $@ -# Link kernel ELF -$(VMLINUX_PRE): $(OBJS) $(LINKER_SCRIPT) - @echo " LD $@" - @mkdir -p $(dir $@) - @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) - @$(TOOLCHAIN_PREFIX)size $@ +# ============================================================ +# 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) -$(RELOC_BIN): $(VMLINUX_PRE) scripts/gen_reloc.py - @echo " RELOC $@" +$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ + $(GEN_INIT_CPIO) scripts/gen_initramfs.sh + @echo " INITRAMFS $(INITRAMFS_SOURCE)" @mkdir -p $(dir $@) - @$(PYTHON) scripts/gen_reloc.py --elf $< --out $@ + @./scripts/gen_initramfs.sh -o $@ \ + -u $(CONFIG_INITRAMFS_ROOT_UID) \ + -g $(CONFIG_INITRAMFS_ROOT_GID) \ + $(INITRAMFS_SOURCE) -$(RELOC_OBJ): $(RELOC_BIN) - @echo " OBJCOPY $@" +$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) + @echo " OBJCOPY $@ (initramfs)" @$(OBJCOPY) -I binary -O $(OBJCOPY_ELF) -B $(OBJCOPY_BINARCH) \ - --rename-section .data=.lume.reloc,alloc,load,readonly,data,contents \ + --rename-section .data=.init.ramfs,alloc,load,readonly,data,contents \ $< $@ -$(VMLINUX): $(OBJS) $(RELOC_OBJ) $(LINKER_SCRIPT) +# 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) $(RELOC_OBJ) + @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) $(INITRAMFS_OBJ) @$(TOOLCHAIN_PREFIX)size $@ -# Generate flat binary for QEMU loading -$(VMLINUX_RAW): $(VMLINUX) +# 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 $< $@ -ifeq ($(CONFIG_KERNEL_COMPRESSED),y) -$(IMAGE): $(VMLINUX_RAW) $(VMLINUX) scripts/pack_image.py - @echo " PACK $@" - @$(PYTHON) scripts/pack_image.py --elf $(VMLINUX) --input $< --output $@ --compress lz4 -else -$(IMAGE): $(VMLINUX_RAW) $(VMLINUX) scripts/pack_image.py - @echo " PACK $@" - @$(PYTHON) scripts/pack_image.py --elf $(VMLINUX) --input $< --output $@ --compress none -endif - -# Compile C++ sources +# 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 - @echo " CXX $<" - @mkdir -p $(dir $@) - @$(CXX) $(CXXFLAGS) -c $< -o $@ + $(Q)mkdir -p $(dir $@) + $(call cmd,cxx_o_cc) -# Compile C sources (musl libc) $(BUILD)/%.o: %.c - @echo " CC $<" - @mkdir -p $(dir $@) - @$(CC) $(CFLAGS_LIBC) -c $< -o $@ + $(Q)mkdir -p $(dir $@) + $(call cmd,cc_o_c) -# Assemble (preprocessed .S) $(BUILD)/%.o: %.S - @echo " AS $<" - @mkdir -p $(dir $@) - @$(CC) $(ASFLAGS) -c $< -o $@ + $(Q)mkdir -p $(dir $@) + $(call cmd,as_o_S) -# Assemble (raw .s, no preprocessing) $(BUILD)/%.o: %.s - @echo " AS $<" - @mkdir -p $(dir $@) - @$(CC) $(ASFLAGS) -c $< -o $@ + $(Q)mkdir -p $(dir $@) + $(call cmd,as_o_S) # Auto-dependency tracking -include $(DEPS) @@ -215,7 +369,7 @@ $(BUILD)/%.o: %.s # ============================================================ ifeq ($(ARCH),riscv) QEMU := qemu-system-riscv64 -QEMU_MACHINE := -machine virt -m 128M -nographic +QEMU_MACHINE := -machine virt -smp 4 -m 128M -nographic QEMU_BIOS := -bios default else ifeq ($(ARCH),aarch64) QEMU := qemu-system-aarch64 @@ -227,13 +381,13 @@ QEMU_MACHINE := -m 128M -nographic QEMU_BIOS := endif -run: $(IMAGE) +run: $(VMLUME) $(IMAGE) @echo " QEMU Running..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(IMAGE) + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -debug: $(IMAGE) +debug: $(VMLUME) $(IMAGE) @echo " QEMU Waiting for GDB on :1234..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(IMAGE) -s -S + $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -s -S # ============================================================ # Utilities @@ -241,10 +395,10 @@ debug: $(IMAGE) clean: rm -rf $(BUILD) -disasm: $(VMLINUX) +disasm: $(VMLUME) @echo " OBJDUMP $<" - @$(OBJDUMP) -d -S $(VMLINUX) > $(VMLINUX).asm - @echo " Written to $(VMLINUX).asm" + @$(OBJDUMP) -d -S $(VMLUME) > $(VMLUME).asm + @echo " Written to $(VMLUME).asm" fmt: @echo " FMT Formatting source files..." diff --git a/na/arch/riscv/boot/compressed.cc b/na/arch/riscv/boot/compressed.cc deleted file mode 100644 index 27513bf..0000000 --- a/na/arch/riscv/boot/compressed.cc +++ /dev/null @@ -1,471 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <arch/config.h> -#include <lume/boot_params.h> -#include <lume/compressed.h> -#include <lume/console.h> -#include <lume/config.h> -#include <lume/decompress.h> -#include <lume/fdt.h> -#include <lume/klog.h> -#include <lume/relocate.h> -#include <lume/types.h> - -extern "C" const CompressedKernelHeader __lume_image_header; -extern "C" char _start[]; -extern "C" char __lume_image_end[]; -extern "C" char __lume_reloc_start[]; -extern "C" char __lume_reloc_end[]; - -struct FdtHeader { - 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; -}; - -struct ReservedRange { - uint64 start; - uint64 end; -}; - -static inline uint32 bswap32(uint32 x) -{ - return ((x & 0xFF000000u) >> 24) | - ((x & 0x00FF0000u) >> 8) | - ((x & 0x0000FF00u) << 8) | - ((x & 0x000000FFu) << 24); -} - -static inline uint64 read_be64(const uint8* p) -{ - uint64 v = 0; - for (int i = 0; i < 8; ++i) { - v = (v << 8) | p[i]; - } - return v; -} - -static inline uint64 addr_to_phys_early(const void* ptr) -{ - uint64 addr = reinterpret_cast<uint64>(ptr); - if (addr >= arch::kDirectMapBaseDefault) { - return addr - arch::kDirectMapBaseDefault; - } - return addr; -} - -static inline uint64 align_down_page(uint64 v) -{ - return v & ~(kPageSize - 1); -} - -static inline bool ranges_overlap(uint64 base, uint64 size, - const ReservedRange& r) -{ - if (size == 0) { - return false; - } - if (base > ~0ULL - size) { - return true; - } - uint64 end = base + size; - return (base < r.end) && (end > r.start); -} - -static uint32 crc32_init() -{ - return 0xFFFFFFFFu; -} - -static uint32 crc32_update(uint32 crc, const uint8* data, uint64 len) -{ - for (uint64 i = 0; i < len; ++i) { - crc ^= data[i]; - for (int b = 0; b < 8; ++b) { - uint32 mask = static_cast<uint32>(-(crc & 1u)); - crc = (crc >> 1) ^ (0xEDB88320u & mask); - } - } - return crc; -} - -static uint32 crc32_final(uint32 crc) -{ - return crc ^ 0xFFFFFFFFu; -} - -static uint32 crc32_bytes(const uint8* data, uint64 len) -{ - return crc32_final(crc32_update(crc32_init(), data, len)); -} - -static void early_put_hex_u64(const char* label, uint64 value) -{ - static const char kHex[] = "0123456789abcdef"; - early_puts(label); - early_puts("0x"); - for (int i = 60; i >= 0; i -= 4) { - early_putc(kHex[(value >> i) & 0xF]); - } - early_putc('\n'); -} - -/* - * boot_stage3_trace — lightweight early-boot diagnostic hook. - * - * Called from entry.S at three points during the decompress/relocate - * flow. Arguments: a0=fdt_paddr, a1=link_base, a2=load_base, a3=phase. - * - * Phase 0: before decompress (load_base not yet known → 0) - * Phase 1: after decompress, before jump - * Phase 2: after flag-set, before re-entry jump - */ -extern "C" void boot_stage3_trace(uint64 fdt_paddr, uint64 link_base, - uint64 load_base, uint64 phase) -{ - /* Phase 0 may fire before console is initialised — keep it cheap. */ - if (phase == 0) { - console_early_init(fdt_paddr); - early_puts("[boot] stage3 trace: phase 0 (pre-decompress)\n"); - return; - } - - static const char* labels[] = { - "[boot] stage3 trace: phase 0\n", - "[boot] stage3 trace: phase 1 (post-decompress)\n", - "[boot] stage3 trace: phase 2 (pre-reentry)\n", - }; - if (phase < 3) { - early_puts(labels[phase]); - } - early_put_hex_u64(" link_base=", link_base); - early_put_hex_u64(" load_base=", load_base); -} - -extern "C" void boot_stage3_loop_panic(uint64 fdt_paddr, uint64 link_base) -{ - console_early_init(fdt_paddr); - early_puts("[boot] stage3 loop detected\n"); - - auto* hdr = reinterpret_cast<const CompressedKernelHeader*>(link_base + 64); - early_put_hex_u64("link_base=", link_base); - early_put_hex_u64("hdr.flags=", hdr->flags); - early_put_hex_u64("hdr.comp=", hdr->compression); - early_put_hex_u64("hdr.uncomp=", hdr->uncompressed_size); - early_put_hex_u64("hdr.comp_size=", hdr->compressed_size); - early_put_hex_u64("hdr.payload_off=", hdr->payload_offset); - early_put_hex_u64("hdr.load_base=", hdr->load_phys_base); - early_put_hex_u64("hdr.reloc_off=", hdr->reloc_offset); - early_put_hex_u64("hdr.reloc_size=", hdr->reloc_size); - - kernel_panic("boot: stage3 loop"); -} - -static int collect_fdt_reserved(uint64 fdt_paddr, ReservedRange* out, int max) -{ - if (!out || max <= 0 || fdt_paddr == 0) { - return 0; - } - - const FdtHeader* hdr = reinterpret_cast<const FdtHeader*>(fdt_paddr); - if (bswap32(hdr->magic) != 0xd00dfeedu) { - return 0; - } - - int count = 0; - uint64 totalsize = bswap32(hdr->totalsize); - if (totalsize != 0 && count < max) { - out[count++] = ReservedRange{fdt_paddr, fdt_paddr + totalsize}; - } - - uint64 rsv_off = bswap32(hdr->off_mem_rsvmap); - const uint8* p = reinterpret_cast<const uint8*>(fdt_paddr + rsv_off); - while (count < max) { - uint64 addr = read_be64(p); - uint64 size = read_be64(p + 8); - p += 16; - if (addr == 0 && size == 0) { - break; - } - if (size == 0) { - continue; - } - out[count++] = ReservedRange{addr, addr + size}; - } - - return count; -} - -static uint64 xorshift64(uint64* state) -{ - uint64 x = *state; - x ^= x << 13; - x ^= x >> 7; - x ^= x << 17; - *state = x; - return x; -} - -static uint64 find_gap_from(uint64 start, uint64 min_base, - const ReservedRange* ranges, int count, - uint64 image_size) -{ - uint64 candidate = align_down_page(start); - - for (int iter = 0; iter < 64; ++iter) { - if (candidate < min_base) { - return 0; - } - - bool hit = false; - uint64 next = candidate; - for (int i = 0; i < count; ++i) { - if (!ranges_overlap(candidate, image_size, ranges[i])) { - continue; - } - hit = true; - if (ranges[i].start < image_size) { - return 0; - } - uint64 below = ranges[i].start - image_size; - below = align_down_page(below); - if (below < next) { - next = below; - } - } - - if (!hit) { - return candidate; - } - if (next >= candidate) { - return 0; - } - candidate = next; - } - - return 0; -} - -static uint64 choose_load_base(uint64 fdt_paddr, uint64 image_size, - uint64 link_base, uint64 payload_base, - uint64 payload_size) -{ - uint64 mem_base = 0; - uint64 mem_size = 0; - FdtManager::early_scan_mem(fdt_paddr, &mem_base, &mem_size); - if (mem_size == 0) { - return link_base; - } - - uint64 mem_top = mem_base + mem_size; - if (mem_top < mem_base || mem_top < image_size) { - return link_base; - } - - ReservedRange reserved[32] = {}; - int count = 0; - - if (count < static_cast<int>(sizeof(reserved) / sizeof(reserved[0]))) { - reserved[count++] = ReservedRange{link_base, link_base + image_size}; - } - if (payload_size != 0 && - count < static_cast<int>(sizeof(reserved) / sizeof(reserved[0]))) { - reserved[count++] = ReservedRange{payload_base, payload_base + payload_size}; - } - count += collect_fdt_reserved(fdt_paddr, reserved + count, - static_cast<int>(sizeof(reserved) / - sizeof(reserved[0])) - count); - - uint64 min_base = align_down_page(mem_base); - uint64 max_base = align_down_page(mem_top - image_size); - if (max_base < min_base) { - return link_base; - } - - uint64 seed = 0; - FdtManager::early_scan_kaslr(fdt_paddr, &seed); - uint64 candidate = max_base; - if (seed != 0) { - seed ^= link_base; - uint64 slots = (max_base - min_base) / kPageSize; - uint64 pick = xorshift64(&seed) % (slots + 1); - candidate = min_base + pick * kPageSize; - } - - uint64 base = find_gap_from(candidate, min_base, reserved, count, image_size); - if (base != 0) { - return base; - } - if (candidate != max_base) { - base = find_gap_from(max_base, min_base, reserved, count, image_size); - if (base != 0) { - return base; - } - } - - return link_base; -} - -extern "C" uint64 boot_decompress_and_relocate(uint64 fdt_paddr) -{ - console_early_init(fdt_paddr); - early_puts("[boot] stage3 start\n"); - - const uint64 image_va_base = reinterpret_cast<uint64>(_start); - uint64 image_size = reinterpret_cast<uint64>(__lume_image_end) - image_va_base; - uint64 reloc_offset = reinterpret_cast<uint64>(__lume_reloc_start) - image_va_base; - uint64 reloc_size = reinterpret_cast<uint64>(__lume_reloc_end) - - reinterpret_cast<uint64>(__lume_reloc_start); - - const uint64 link_base = addr_to_phys_early(_start); - uint64 load_base = link_base; - - uint32 comp = KERNEL_COMP_NONE; - uint32 flags = 0; - uint64 compressed_size = 0; - uint64 payload_offset = 0; - - const CompressedKernelHeader* hdr = &__lume_image_header; - const uint64 header_offset = reinterpret_cast<uint64>(&__lume_image_header) - - image_va_base; - if (hdr->magic == kCompressedKernelMagic && - hdr->version == kCompressedKernelVersion && - hdr->header_size >= sizeof(CompressedKernelHeader)) { - if (hdr->header_crc32 != 0) { - uint32 crc = crc32_init(); - const uint8* hbytes = reinterpret_cast<const uint8*>(hdr); - constexpr uint64 crc_off = offsetof(CompressedKernelHeader, header_crc32); - uint64 header_size = hdr->header_size; - if (header_size > sizeof(CompressedKernelHeader)) { - header_size = sizeof(CompressedKernelHeader); - } - crc = crc32_update(crc, hbytes, crc_off); - uint32 zero = 0; - crc = crc32_update(crc, reinterpret_cast<const uint8*>(&zero), sizeof(zero)); - uint64 tail_off = crc_off + sizeof(uint32); - if (header_size > tail_off) { - crc = crc32_update(crc, hbytes + tail_off, header_size - tail_off); - } - uint32 calc = crc32_final(crc); - if (calc != hdr->header_crc32) { - early_puts("[boot] header crc mismatch\n"); - kernel_panic("boot: header crc mismatch"); - } - } - comp = hdr->compression; - compressed_size = hdr->compressed_size; - if (hdr->uncompressed_size != 0) { - image_size = hdr->uncompressed_size; - } - if (hdr->load_phys_base != 0) { - load_base = hdr->load_phys_base; - } else if (comp != KERNEL_COMP_NONE) { - uint64 payload_base = link_base + payload_offset; - load_base = choose_load_base(fdt_paddr, image_size, link_base, - payload_base, compressed_size); - } - if (hdr->reloc_size != 0) { - reloc_offset = hdr->reloc_offset; - reloc_size = hdr->reloc_size; - } - payload_offset = (hdr->payload_offset != 0) - ? hdr->payload_offset - : hdr->header_size; - } - - if (comp != KERNEL_COMP_NONE && load_base == link_base) { - early_puts("[boot] no safe relocation base\n"); - flags |= BOOT_IMAGE_RELOC_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: no safe relocation base"); - } - - if (comp != KERNEL_COMP_NONE) { - flags |= BOOT_IMAGE_COMPRESSED; - - if (compressed_size == 0 || payload_offset == 0) { - early_puts("[boot] compressed header invalid\n"); - flags |= BOOT_IMAGE_DECOMP_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: compressed header invalid"); - } - - if (hdr->payload_crc32 != 0) { - const uint8* payload = reinterpret_cast<const uint8*>( - image_va_base + payload_offset); - uint32 calc = crc32_bytes(payload, compressed_size); - if (calc != hdr->payload_crc32) { - early_puts("[boot] payload crc mismatch\n"); - flags |= BOOT_IMAGE_DECOMP_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: payload crc mismatch"); - } - } - - if (comp == KERNEL_COMP_LZ4) { - const uint8* payload = reinterpret_cast<const uint8*>( - image_va_base + payload_offset); - uint8* out = reinterpret_cast<uint8*>(load_base); - int written = lz4_decompress(payload, - static_cast<uint32>(compressed_size), - out, static_cast<uint32>(image_size)); - if (written < 0 || static_cast<uint64>(written) != image_size) { - early_puts("[boot] lz4 decompress failed\n"); - flags |= BOOT_IMAGE_DECOMP_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: lz4 decompress failed"); - } - } else { - early_puts("[boot] unsupported compression\n"); - flags |= BOOT_IMAGE_DECOMP_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: unsupported compression"); - } - } - - if (reloc_size != 0 && load_base != link_base) { - if (reloc_offset + reloc_size > image_size) { - early_puts("[boot] reloc table out of range\n"); - flags |= BOOT_IMAGE_RELOC_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: reloc table out of range"); - } - const void* reloc_blob = reinterpret_cast<const void*>( - image_va_base + reloc_offset); - RelocStatus st = relocate_apply(load_base, image_size, link_base, - reloc_blob, reloc_size); - if (st != RELOC_OK) { - early_puts("[boot] relocation failed\n"); - flags |= BOOT_IMAGE_RELOC_FAILED; - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - kernel_panic("boot: relocation failed", reloc_status_str(st)); - } - flags |= BOOT_IMAGE_RELOCATED; - } - - if (load_base != link_base) { - auto* new_hdr = reinterpret_cast<CompressedKernelHeader*>( - load_base + header_offset); - new_hdr->flags |= LUME_HDR_FLAG_STAGE3_DONE; - } - - early_puts("[boot] stage3 done\n"); - boot_params_set_image_info(load_base, image_size, reloc_offset, - reloc_size, comp, flags); - return load_base; -} diff --git a/na/arch/riscv/boot/entry.S b/na/arch/riscv/boot/entry.S deleted file mode 100644 index 9154b7f..0000000 --- a/na/arch/riscv/boot/entry.S +++ /dev/null @@ -1,309 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * LumeOS Kernel Entry Point (RISC-V 64) - * - * This file implements the earliest boot sequence: - * 1. RISC-V Linux Boot Protocol Image Header (64 bytes) - * 2. BSP/AP synchronization - * 3. BSS zeroing (BSP only) - * 4. Early page table setup (1GB superpages for identity + higher-half) - * 5. MMU enable and higher-half trampoline - * 6. C++ global constructor invocation - * 7. Jump to kernel_main() - * - * Register contract on entry (from OpenSBI / GRUB / U-Boot): - * a0 = CPU ID (Hart ID) - * a1 = FDT physical base address - * - * Reference: docs/specs/linker_and_entry.md - */ - -#include <lume/config.h> - -#define PAGE_OFFSET 0xFFFFFFC000000000 -#define BOOT_STACK_SIZE 8192 - -/* SV39 PTE flags */ -#define PTE_V (1 << 0) /* Valid */ -#define PTE_R (1 << 1) /* Readable */ -#define PTE_W (1 << 2) /* Writable */ -#define PTE_X (1 << 3) /* Executable */ -#define PTE_A (1 << 6) /* Accessed */ -#define PTE_D (1 << 7) /* Dirty */ - -/* SATP mode field for SV39 */ -#define SATP_SV39 (8UL << 60) - -/* LumeOS compressed image header constants */ -#define LUME_COMP_NONE 0 -#define LUME_HDR_SIZE 80 -#define LUME_HDR_MAGIC 0x4C554D45434F4D50 /* "LUMECOMP" */ -#define LUME_HDR_OFF 64 -#define LUME_HDR_FLAGS_OFF 14 -#define LUME_HDR_FLAG_STAGE3_DONE 0x1 - -/* ======================================================================== - * Section: .head.text — RISC-V Linux Boot Protocol Image Header - * Must be at byte 0 of the kernel binary. - * ======================================================================== */ - .section .head.text, "ax" - .globl _start -_start: - /* code0 (u32, offset 0): executable jump for OpenSBI bare boot */ - j real_start - /* code1 (u32, offset 4): reserved */ - .word 0 - /* text_offset (u64, offset 8): load offset from RAM base */ - .dword 0x200000 - /* image_size (u64, offset 16): will be filled post-link, 0 = unknown */ - .dword 0 - /* flags (u64, offset 24): bit 0: 0=LE, 1=BE */ - .dword 0 - /* version (u32, offset 32): header version 0.2 */ - .word 0x0002 - /* res1 (u32, offset 36) */ - .word 0 - /* res2 (u64, offset 40) */ - .dword 0 - /* magic (u64, offset 48): RISCV_IMAGE_MAGIC "RISCV\0\0\0" LE */ - .dword 0x5643534952 - /* magic2 (u32, offset 56): RISCV_IMAGE_MAGIC2 "RSC\x05" LE */ - .word 0x05435352 - /* res3 (u32, offset 60): reserved for PE/COFF */ - .word 0 - - /* LumeOS compressed kernel header (80 bytes) */ - .align 3 - .globl __lume_image_header -__lume_image_header: - .dword LUME_HDR_MAGIC /* magic */ - .word 1 /* version */ - .half LUME_HDR_SIZE /* header_size */ - .half 0 /* flags */ - .word LUME_COMP_NONE /* compression */ - .word 0 /* reserved0 */ - .dword __lume_image_end - _start /* uncompressed_size */ - .dword 0 /* compressed_size */ - .dword 0 /* load_phys_base (0 = auto) */ - .dword __lume_reloc_start - _start /* reloc_offset */ - .dword __lume_reloc_end - __lume_reloc_start /* reloc_size */ - .dword _ebss - _start /* payload_offset (when compressed) */ - .word 0 /* header_crc32 */ - .word 0 /* payload_crc32 */ - -/* ======================================================================== - * real_start: Actual boot logic begins here (all CPUs land here) - * ======================================================================== */ - .text - .globl real_start -real_start: - /* ---- Step 1: Disable interrupts ---- */ - csrw sie, zero - csrw sip, zero - - /* ---- Step 2: Set up gp (global pointer) ---- */ - /* Critical for medany model: all global variable access goes through gp */ - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* ---- Step 3: Set up per-CPU boot stack ---- */ - /* sp = boot_stack + (cpu_id + 1) * BOOT_STACK_SIZE */ - la t0, boot_stack - li t1, BOOT_STACK_SIZE - addi t2, a0, 1 /* t2 = cpu_id + 1 */ - mul t1, t1, t2 /* t1 = (cpu_id + 1) * BOOT_STACK_SIZE */ - add sp, t0, t1 - - /* ---- Step 4: Store boot args (cpu_id and fdt_paddr) ---- */ - mv tp, a0 /* tp = cpu_id */ - mv s0, a1 /* s0 = fdt_paddr (preserved across all C++ calls) */ - /* ---- Step 5: BSP / AP divergence ---- */ - /* CPU 0 is the BSP; all others spin-wait */ - bnez a0, .Lap_wait - -/* =================================================================== - * BSP-only path: Clear BSS, set up early page table, release APs - * =================================================================== */ -.Lbsp_init: - /* ---- 5.1: Clear BSS ---- */ - la t0, _sbss - la t1, _ebss -.Lbss_loop: - bgeu t0, t1, .Lbss_done - sd zero, 0(t0) - addi t0, t0, 8 - j .Lbss_loop -.Lbss_done: - - - /* ---- 5.2: Build early page table (1GB superpages) ---- */ - /* - * SV39 root page table (512 entries x 8 bytes = 4KB) - * We need 4 entries: - * [0] = Identity map: 0x00000000 -> 0x00000000 (MMIO: UART, PLIC, CLINT) - * [2] = Identity map: 0x80000000 -> 0x80000000 (RAM) - * [256] = Higher-half: 0xFFFFFFC000000000 -> 0x00000000 (MMIO via VA) - * [258] = Higher-half: 0xFFFFFFC080000000 -> 0x80000000 (RAM via VA) - * - * VPN[2] index = (VA >> 30) & 0x1FF - * 0x00000000 -> 0 0xFFFFFFC000000000 -> 256 - * 0x80000000 -> 2 0xFFFFFFC080000000 -> 258 - */ - la t0, early_pgdir - - /* PTE flags: V|R|W|X|A|D (1GB leaf superpage) */ - li t2, (PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D) - - /* --- MMIO region: physical 0x00000000 --- */ - /* PPN = 0x00000000 >> 12 = 0, PTE = flags only */ - mv t1, t2 /* PTE = 0 | flags */ - - /* Entry [0]: Identity map MMIO */ - sd t1, (0 * 8)(t0) - - /* Entry [256]: Higher-half MMIO (0xFFFFFFC000000000) */ - li t3, (256 * 8) - add t3, t0, t3 - sd t1, 0(t3) - - /* --- RAM region: physical 0x80000000 --- */ - li t1, 0x80000 /* PPN = 0x80000000 >> 12 */ - slli t1, t1, 10 /* PPN field in PTE */ - or t1, t1, t2 /* t1 = complete PTE */ - - /* Entry [2]: Identity map RAM */ - sd t1, (2 * 8)(t0) - - /* Entry [258]: Higher-half RAM (0xFFFFFFC080000000) */ - li t3, (258 * 8) - add t3, t0, t3 - sd t1, 0(t3) - - - /* ---- 5.3: Memory fence ---- */ - fence rw, rw - - /* ---- 5.4: Release APs ---- */ - la t0, boot_sync_flag - li t1, 1 - amoswap.w.rl zero, t1, (t0) - j .Lenable_mmu - -/* =================================================================== - * AP path: spin-wait until BSP signals readiness - * =================================================================== */ -.Lap_wait: - la t0, boot_sync_flag -.Lap_spin: - lw t1, 0(t0) - beqz t1, .Lap_spin - fence r, rw /* Acquire: ensure we see BSP's writes */ - -/* =================================================================== - * All CPUs: Enable MMU and jump to higher-half virtual addresses - * =================================================================== */ -.Lenable_mmu: - - - /* ---- 6.1: Write satp = SV39 mode | (early_pgdir >> 12) ---- */ - /* NOTE: `la` at this point is PC-relative. Since we are still executing - * at physical addresses, it yields the PHYSICAL address directly. - * No VA-to-PA conversion needed. */ - la t0, early_pgdir - srli t0, t0, 12 /* PPN = PA >> 12 */ - li t1, SATP_SV39 - or t0, t0, t1 /* satp = mode | PPN */ - csrw satp, t0 - - /* ---- 6.2: Flush TLB ---- */ - sfence.vma zero, zero - - /* ---- 6.3: Jump to higher-half virtual address ---- */ - /* Now we are executing via the identity map (PA == VA for 0x8...). - * `la` is PC-relative (medany), so it yields a PHYSICAL address. - * We must add PAGE_OFFSET to convert to the higher-half VA. */ - la t0, .Lhigher_half - li t1, PAGE_OFFSET - add t0, t0, t1 - jr t0 - -.Lhigher_half: - /* We are now running at virtual addresses (0xFFFFFFC0...) ! - * The identity map is still active but will be removed by vmm_init(). */ - - /* ---- 6.4: Fix sp and gp to virtual addresses ---- */ - /* sp was set up before MMU using PC-relative `la`, so it holds a - * physical address. Add PAGE_OFFSET to convert to higher-half VA. - * gp is similarly reloaded — now that PC is at VA, `la` resolves - * correctly via PC-relative addressing. */ - li t0, PAGE_OFFSET - add sp, sp, t0 - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - /* Mark MMU enabled for early console mapping logic */ - call console_set_mmu_enabled - - /* ---- Step 7: BSP calls early_cpp_init ---- */ - bnez tp, .Lskip_cpp_init - - /* BSP only: invoke C++ global constructors */ - call early_cpp_init - -.Lskip_cpp_init: - - /* All CPUs: enter kernel_main(cpu_id, fdt_paddr) */ - mv a0, tp /* a0 = cpu_id (from tp) */ - mv a1, s0 /* a1 = fdt_paddr (from s0) */ - call kernel_main - - /* kernel_main should never return; if it does, halt */ -.Lhalt: - csrc sstatus, 2 /* Clear SIE */ - wfi - j .Lhalt - - -/* ======================================================================== - * early_cpp_init: Traverse .init_array and call global constructors - * Called by BSP only, before kernel_main. - * ======================================================================== */ - .globl early_cpp_init -early_cpp_init: - /* Prologue: save ra and callee-saved regs we use as loop state */ - addi sp, sp, -32 - sd ra, 0(sp) - sd s1, 8(sp) - sd s2, 16(sp) - - la s1, __init_array_start - la s2, __init_array_end -.Linit_loop: - bgeu s1, s2, .Linit_done - ld t0, 0(s1) /* Load function pointer */ - jalr ra, t0 /* Call constructor (sets ra = return addr) */ - addi s1, s1, 8 /* Next entry */ - j .Linit_loop -.Linit_done: - /* Epilogue: restore and return */ - ld ra, 0(sp) - ld s1, 8(sp) - ld s2, 16(sp) - addi sp, sp, 32 - ret - - -/* ======================================================================== - * BSS Data: boot_sync_flag - * ======================================================================== */ - .section .bss - .align 2 - .globl boot_sync_flag -boot_sync_flag: - .word 0 diff --git a/na/arch/riscv/include/arch/config.h b/na/arch/riscv/include/arch/config.h index 0949c41..8ae0502 100644 --- a/na/arch/riscv/include/arch/config.h +++ b/na/arch/riscv/include/arch/config.h @@ -35,6 +35,11 @@ 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 * diff --git a/na/arch/riscv/include/arch/cpu.h b/na/arch/riscv/include/arch/cpu.h index d16cf2d..8c573ec 100644 --- a/na/arch/riscv/include/arch/cpu.h +++ b/na/arch/riscv/include/arch/cpu.h @@ -76,4 +76,20 @@ 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/fpstate.h b/na/arch/riscv/include/arch/fpstate.h new file mode 100644 index 0000000..4b42a22 --- /dev/null +++ b/na/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 <lume/types.h> + +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 index a0ee1ed..f4c9b26 100644 --- a/na/arch/riscv/include/arch/mmu.h +++ b/na/arch/riscv/include/arch/mmu.h @@ -50,6 +50,14 @@ static inline uint64 get_page_table() { 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"); @@ -60,4 +68,11 @@ 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 index a99825a..0b43b9e 100644 --- a/na/arch/riscv/include/arch/pmap.h +++ b/na/arch/riscv/include/arch/pmap.h @@ -35,6 +35,7 @@ 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); diff --git a/na/arch/riscv/include/arch/sbi.h b/na/arch/riscv/include/arch/sbi.h index b65c37e..80a6d7c 100644 --- a/na/arch/riscv/include/arch/sbi.h +++ b/na/arch/riscv/include/arch/sbi.h @@ -3,6 +3,15 @@ #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 <lume/types.h> namespace arch::sbi { @@ -12,7 +21,20 @@ struct sbiret { long value; }; -static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0, uint64 arg1, uint64 arg2, uint64 arg3, uint64 arg4, uint64 arg5) { +/* 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; @@ -23,7 +45,7 @@ static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0, uint64 arg1, uint6 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"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) : "memory"); ret.error = static_cast<long>(a0); @@ -31,9 +53,59 @@ static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0, uint64 arg1, uint6 return ret; } +/* ---- Timer Extension (EID 0x54494D45) ---- */ + static inline void set_timer(uint64 stime_value) { - // Standard SBI TIME Extension (EID 0x54494D45) - sbi_ecall(0x54494D45, 0, stime_value, 0, 0, 0, 0, 0); + 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 new file mode 100644 index 0000000..46c6b97 --- /dev/null +++ b/na/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 <lume/types.h> + +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 index c99d8cb..8e55250 100644 --- a/na/arch/riscv/include/arch/trap.h +++ b/na/arch/riscv/include/arch/trap.h @@ -79,4 +79,16 @@ 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 index de49e9c..b198646 100644 --- a/na/arch/riscv/include/arch/trapframe.h +++ b/na/arch/riscv/include/arch/trapframe.h @@ -61,6 +61,7 @@ #ifndef __ASSEMBLER__ #include <lume/types.h> +#include <arch/cpu.h> namespace arch { @@ -89,4 +90,51 @@ static_assert(sizeof(TrapFrame) == 296, "TrapFrame size mismatch with assembly o } // 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<uint64>(tf); + tf->kernel_trap = reinterpret_cast<uint64>(&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 new file mode 100644 index 0000000..42f1eb4 --- /dev/null +++ b/na/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/na/arch/riscv/include/asm/page.h b/na/arch/riscv/include/asm/page.h new file mode 100644 index 0000000..7b9f155 --- /dev/null +++ b/na/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/na/arch/riscv/include/asm/pgtable.h b/na/arch/riscv/include/asm/pgtable.h new file mode 100644 index 0000000..28fc4c5 --- /dev/null +++ b/na/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 <asm/page.h>) + * + * 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 <lume/types.h> +#include <asm/page.h> + +#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 new file mode 100644 index 0000000..454d9de --- /dev/null +++ b/na/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/na/arch/riscv/kernel/asm-offsets.c b/na/arch/riscv/kernel/asm-offsets.c new file mode 100644 index 0000000..edecab7 --- /dev/null +++ b/na/arch/riscv/kernel/asm-offsets.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2026 Richard Qin +// +// arch/riscv/kernel/asm-offsets.c — emit struct field offsets and +// scalar constants for consumption by assembly sources (head.S etc.). +// +// 1:1 with Linux arch/riscv/kernel/asm-offsets.c. The Makefile +// compiles this to an .s file (not .o) and runs +// scripts/extract-asm-offsets.sed over the output to produce +// include/generated/asm-offsets.h. +// +// Adding a new offset: include the struct's header, add an OFFSET() +// or DEFINE() line below, rebuild. head.S can then reference the +// generated #define directly. + +#include <lume/kbuild.h> +#include <lume/kernel_map.h> + +void asm_offsets(void) +{ + COMMENT("LumeOS asm-offsets — struct kernel_mapping field offsets"); + OFFSET(KERNEL_MAP_VIRT_ADDR, kernel_mapping, virt_addr); + OFFSET(KERNEL_MAP_VIRT_OFFSET, kernel_mapping, virt_offset); + OFFSET(KERNEL_MAP_PHYS_ADDR, kernel_mapping, phys_addr); + OFFSET(KERNEL_MAP_SIZE, kernel_mapping, size); + OFFSET(KERNEL_MAP_VA_PA_OFFSET, kernel_mapping, va_pa_offset); + OFFSET(KERNEL_MAP_PAGE_OFFSET, kernel_mapping, page_offset); + OFFSET(KERNEL_MAP_VA_KERNEL_PA_OFFSET, kernel_mapping, va_kernel_pa_offset); +} diff --git a/na/arch/riscv/kernel/head.S b/na/arch/riscv/kernel/head.S new file mode 100644 index 0000000..e04c44c --- /dev/null +++ b/na/arch/riscv/kernel/head.S @@ -0,0 +1,317 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * RISC-V kernel entry — Linux-aligned layout. + * + * Flow (mirrors arch/riscv/kernel/head.S in Linux): + * + * _start [.head.text, byte 0 of image] + * · 64-byte Boot Protocol Image Header + * · j real_start + * + * real_start [.text] + * · save a0 (hartid) / a1 (dtb_pa) + * · setup gp / per-hart sp + * · BSP/AP election via amoswap + * · BSP: clear BSS, call setup_vm, then relocate_enable_mmu + * · AP: spin-wait, then relocate_enable_mmu + * + * relocate_enable_mmu [C function, arch/riscv/mm/init.cc] + * · csrw satp <- trampoline_pg_dir + * · jump to higher-half VA + * · csrw satp <- early_pg_dir + * · slide sp/ra/gp to higher-half + * · call relocate_kernel (walks .rela.dyn, patches R_RISCV_RELATIVE) + * + * real_start (continued in higher-half) + * · BSP only: call early_cpp_init (walks .init_array) + * · call kernel_main(hartid, dtb_pa) + * + * Register contract from OpenSBI / U-Boot: + * a0 = hart ID + * a1 = FDT physical base + */ + +#include <lume/config.h> + +#define PAGE_OFFSET 0xFFFFFFC000000000 +#define BOOT_STACK_SIZE 8192 + +/* ======================================================================== + * .head.text — RISC-V Boot Protocol Image Header + * + * Layout per Documentation/arch/riscv/boot-image-header.rst: + * code0/code1 (8 B) : executable code, lands at PC after OpenSBI hand-off + * text_offset (8 B) : load offset from RAM base + * image_size (8 B) : effective image size (0 = unknown) + * flags (8 B) : bit 0: endian + * version (4 B) : major.minor + * res1/res2 (4+8) : reserved + * magic (8 B) : "RISCV\0\0\0" + * magic2 (4 B) : "RSC\x05" + * res3 (4 B) : PE/COFF reserved + * ======================================================================== */ + .section .head.text, "ax" + .globl _start +_start: + j real_start /* code0 */ + .word 0 /* code1 */ + .dword 0x200000 /* text_offset */ + .dword 0 /* image_size (post-link) */ + .dword 0 /* flags */ + .word 0x0002 /* version 0.2 */ + .word 0 /* res1 */ + .dword 0 /* res2 */ + .dword 0x5643534952 /* magic "RISCV\0\0\0" */ + .word 0x05435352 /* magic2 "RSC\x05" */ + .word 0 /* res3 */ + +/* ======================================================================== + * real_start — all harts land here at physical PC + * ======================================================================== */ + .text + .globl real_start +real_start: + /* DEBUG: 'R<hartid>\n' — UART direct at 0x10000000, MMU off. */ + li t0, 0x10000000 + li t1, 'R' + sb t1, 0(t0) + addi t1, a0, '0' + sb t1, 0(t0) + li t1, '\n' + sb t1, 0(t0) + + /* Mask interrupts and supervisor pending bits. */ + csrw sie, zero + csrw sip, zero + + /* gp for medany — relax must be disabled across the `la`. */ + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + /* per-hart boot stack: sp = boot_stack + (hartid + 1) * STACK_SIZE */ + la t0, boot_stack + li t1, BOOT_STACK_SIZE + addi t2, a0, 1 + mul t1, t1, t2 + add sp, t0, t1 + + /* Save hartid / dtb_pa in callee-saved regs preserved across calls. */ + mv tp, a0 /* tp = hartid */ + mv s0, a1 /* s0 = dtb_pa */ + + /* BSP/AP election: first hart to swap bsp_elected 0→1 is BSP. */ + la t0, bsp_elected + li t1, 1 + amoswap.w.aq t2, t1, (t0) + bnez t2, .Lap_wait /* prev != 0 → we're an AP */ + + /* === BSP path === */ + la t0, bsp_hart_id + sw a0, 0(t0) + + li t3, 0x10000000 + li t4, 'B' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Clear BSS — must precede setup_vm, which writes kernel_map. */ + la t0, _sbss + la t1, _ebss +.Lbss_loop: + bgeu t0, t1, .Lbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lbss_loop +.Lbss_done: + + li t3, 0x10000000 + li t4, 'V' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* ---- BSP-only: pre-MMU PIC fixup of .rela.dyn ---- + * + * The kernel is built with -fPIE -shared. All global-data accesses + * compile to GOT-indirect loads (auipc + ld), where the linker + * emits each .got slot's initial value as the link-time VA and a + * companion R_RISCV_RELATIVE entry in .rela.dyn. At this point + * we're running at the physical load address — those link-time VAs + * are not yet mapped, so any global access would fault. + * + * Walk .rela.dyn in pure assembly (PC-relative only — no globals) + * and patch every R_RISCV_RELATIVE slot to addend + (PA - LINK_VA). + * After this, setup_vm can dereference kernel_map / early_pg_dir + * / etc. via the GOT and get correct PA-side pointers. + * + * Mirrors Linux arch/riscv/kernel/head.S — same idea, same loop. + * + * reloc: + * t0 = pa_offset = PA(_start) - KERNEL_LINK_ADDR (negative) + * t1 = &__rela_dyn_start + * t2 = &__rela_dyn_end + * for each 24-byte rela in [t1, t2): + * t3 = r_offset; t4 = r_info; t5 = r_addend + * if (t4 & 0xFFFFFFFF) != R_RISCV_RELATIVE: skip + * slot_pa = r_offset + pa_offset + * *slot_pa = r_addend + pa_offset + */ +#define R_RISCV_RELATIVE 3 + + la t0, _start /* PA of _start */ + li t1, 0xFFFFFFC080200000 /* KERNEL_LINK_ADDR (asm/page.h) */ + sub t0, t0, t1 /* t0 = pa_offset (PA - LINK_VA) */ + + la t1, __rela_dyn_start + la t2, __rela_dyn_end +.Lrela_loop: + bgeu t1, t2, .Lrela_done + ld t3, 0(t1) /* r_offset */ + ld t4, 8(t1) /* r_info */ + ld t5, 16(t1) /* r_addend */ + /* r_info low 32 bits = type */ + li t6, 0xFFFFFFFF + and t4, t4, t6 + li t6, R_RISCV_RELATIVE + bne t4, t6, .Lrela_next + /* slot_pa = r_offset + pa_offset */ + add t3, t3, t0 + /* value = r_addend + pa_offset */ + add t5, t5, t0 + sd t5, 0(t3) +.Lrela_next: + addi t1, t1, 24 /* sizeof(Elf64_Rela) */ + j .Lrela_loop +.Lrela_done: + + /* Memory fence so subsequent loads see the patched slots. */ + fence w, r + + li t3, 0x10000000 + li t4, 'P' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Call setup_vm(load_pa = PA of _start, dtb_pa = s0). + * Both args physical at this point — MMU still off. */ + la a0, _start + mv a1, s0 + call setup_vm + + li t3, 0x10000000 + li t4, 'v' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Release APs once page tables are built. */ + la t0, boot_sync_flag + li t1, 1 + amoswap.w.rl zero, t1, (t0) + j .Lenable_mmu + +/* ======================================================================== + * AP path — spin until BSP signals readiness. + * ======================================================================== */ +.Lap_wait: + la t0, boot_sync_flag +.Lap_spin: + lw t1, 0(t0) + beqz t1, .Lap_spin + fence r, rw + + /* fall through */ + +/* ======================================================================== + * .Lenable_mmu — all harts: enable MMU, jump to higher-half, run reloc. + * + * relocate_enable_mmu does its own SATP write + higher-half jump in + * C (with inline asm), then calls relocate_kernel. After it returns, + * we are at higher-half VA with sp / ra / gp slid up, and .rela.dyn + * is applied (so .init_array pointers are valid). + * ======================================================================== */ +.Lenable_mmu: + la a0, early_pg_dir + call relocate_enable_mmu + /* On return: PC, sp, ra, gp are all at higher-half VA. */ + + li t3, 0x10000000 + li t4, 'M' + sb t4, 0(t3) + li t4, '\n' + sb t4, 0(t3) + + /* Mark MMU enabled for console / pmap layers that check this. */ + call console_set_mmu_enabled + + /* BSP only: walk __init_array and call each C++ ctor. */ + la t0, bsp_hart_id + lw t0, 0(t0) + bne tp, t0, .Lskip_cpp_init + call early_cpp_init +.Lskip_cpp_init: + + /* Hand off to kernel_main(hartid, dtb_pa). */ + mv a0, tp + mv a1, s0 + call kernel_main + + /* kernel_main should not return. */ +.Lhalt: + csrc sstatus, 2 + wfi + j .Lhalt + +/* ======================================================================== + * early_cpp_init — walk .init_array and call each constructor. + * + * Called only by BSP, after relocate_kernel has patched the function + * pointers stored in __init_array. Before relocate_kernel runs those + * pointers are zero-filled by the linker. + * ======================================================================== */ + .globl early_cpp_init +early_cpp_init: + addi sp, sp, -32 + sd ra, 0(sp) + sd s1, 8(sp) + sd s2, 16(sp) + + la s1, __init_array_start + la s2, __init_array_end +.Linit_loop: + bgeu s1, s2, .Linit_done + ld t0, 0(s1) + jalr ra, t0 + addi s1, s1, 8 + j .Linit_loop +.Linit_done: + ld ra, 0(sp) + ld s1, 8(sp) + ld s2, 16(sp) + addi sp, sp, 32 + ret + +/* ======================================================================== + * Election state — in .data so explicit zero survives the BSP BSS clear + * (these are read before BSS is cleared on the BSP, and concurrently by + * APs while BSP is in the clear loop). + * ======================================================================== */ + .section .data + .align 2 + .globl boot_sync_flag +boot_sync_flag: + .word 0 + + .globl bsp_elected +bsp_elected: + .word 0 + + .globl bsp_hart_id +bsp_hart_id: + .word 0 diff --git a/na/arch/riscv/kernel/lumeos.lds.S b/na/arch/riscv/kernel/lumeos.lds.S index ca5218f..9bd083b 100644 --- a/na/arch/riscv/kernel/lumeos.lds.S +++ b/na/arch/riscv/kernel/lumeos.lds.S @@ -38,12 +38,33 @@ ENTRY(_start) PROVIDE(__global_pointer$ = . + 0x800); \ *(.sdata .sdata.*) -/* ===== RISC-V: early page table + per-CPU boot stacks in .bss ===== */ +/* ===== 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 \ - /* Early page table: 4KB aligned, used by entry.S before VMM */ \ . = ALIGN(4096); \ - early_pgdir = .; \ + 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 = .; \ @@ -65,16 +86,27 @@ SECTIONS { . = ALIGN(4096); - RODATA_SECTION + /* .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); - RELOC_SECTION + 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) */ diff --git a/na/arch/riscv/kernel/signal.cc b/na/arch/riscv/kernel/signal.cc new file mode 100644 index 0000000..39fe34e --- /dev/null +++ b/na/arch/riscv/kernel/signal.cc @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * arch/riscv/signal.cc — Signal frame construction and sigreturn + * + * Constructs a signal frame on the user stack before returning to + * the user signal handler. On sigreturn, restores the original + * registers from the saved frame. + * + * Frame layout on user stack (grows downward): + * + * +-----------------------+ <- original sp + * | sigset_t saved_mask | 8 bytes + * | TrapFrame saved_regs | 296 bytes + * | kernel_siginfo info | 128 bytes + * +-----------------------+ <- new sp (16-byte aligned) + * + * Reference: Linux arch/riscv/kernel/signal.c + */ + +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/uaccess.h> +#include <lume/errno.h> +#include <lume/klog.h> +#include <arch/trapframe.h> + +/* ================================================================== */ +/* Signal frame layout */ +/* ================================================================== */ + +struct rt_sigframe { + kernel_siginfo info; /* siginfo for SA_SIGINFO handlers */ + arch::TrapFrame saved_regs; /* saved register state */ + sigset_t saved_mask; /* blocked mask to restore on sigreturn */ +}; + +/* ================================================================== */ +/* Signal trampoline (written to user stack) */ +/* ================================================================== */ + +/* + * When the user's signal handler returns (via `ret`), ra points here. + * This tiny code sequence invokes sys_rt_sigreturn to restore the + * original execution context. + * + * RISC-V instructions (8 bytes): + * li a7, 139 → addi a7, zero, 139 → 0x08b00893 + * ecall → 0x00000073 + */ +static constexpr uint32 sigreturn_trampoline[2] = { + 0x08b00893, /* li a7, 139 (SYS_rt_sigreturn) */ + 0x00000073, /* ecall */ +}; + +/* ================================================================== */ +/* setup_signal_frame */ +/* ================================================================== */ + +int setup_signal_frame(arch::TrapFrame* tf, int signo, + kernel_sigaction* ka, kernel_siginfo* info) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + /* Calculate frame location on user stack */ + uint64 sp = tf->sp; + + /* Reserve space for the trampoline below the frame */ + sp -= sizeof(sigreturn_trampoline); + sp &= ~15ULL; /* 16-byte align */ + uint64 trampoline_addr = sp; + + /* Reserve space for the signal frame */ + sp -= sizeof(rt_sigframe); + sp &= ~15ULL; /* 16-byte align */ + uint64 frame_addr = sp; + + /* Build the frame in kernel memory, then copy to user stack */ + rt_sigframe frame{}; + if (info) { + frame.info = *info; + } + frame.saved_regs = *tf; + frame.saved_mask = curr->blocked; + + /* Copy frame to user stack */ + int err = copy_to_user(frame_addr, &frame, sizeof(frame)); + if (err < 0) return -EFAULT; + + /* Copy trampoline to user stack */ + err = copy_to_user(trampoline_addr, sigreturn_trampoline, + sizeof(sigreturn_trampoline)); + if (err < 0) return -EFAULT; + + /* Set up the trap frame to execute the signal handler */ + tf->epc = ka->sa_handler; + tf->sp = frame_addr; + + /* Arguments to the handler: handler(int signo, siginfo_t*, ucontext_t*) */ + tf->a[0] = static_cast<uint64>(signo); + + if (ka->sa_flags & SA_SIGINFO) { + /* SA_SIGINFO: pass siginfo and ucontext pointers */ + tf->a[1] = frame_addr; /* &info */ + tf->a[2] = frame_addr + sizeof(kernel_siginfo); /* &saved_regs (as ucontext) */ + } else { + tf->a[1] = 0; + tf->a[2] = 0; + } + + /* Return address: when the handler does `ret`, jump to trampoline */ + if (ka->sa_flags & SA_RESTORER) { + tf->ra = ka->sa_restorer; + } else { + tf->ra = trampoline_addr; + } + + return 0; +} + +/* ================================================================== */ +/* arch_sigreturn — restore state from signal frame */ +/* ================================================================== */ + +int64 arch_sigreturn(arch::TrapFrame* tf) +{ + TaskControlBlock* curr = current_task(); + if (!curr) return -EINVAL; + + /* + * The signal frame is located at the current sp. + * We need to read it back and restore the original state. + */ + uint64 frame_addr = tf->sp; + + rt_sigframe frame{}; + int err = copy_from_user(&frame, frame_addr, sizeof(frame)); + if (err < 0) { + printk(KERN_ERR "sigreturn: failed to read signal frame at %llx\n", + frame_addr); + task_kill_current(SIGSEGV); + return -EFAULT; + } + + /* Restore the saved register state */ + *tf = frame.saved_regs; + + /* Restore the blocked mask */ + curr->blocked = frame.saved_mask; + curr->blocked.del(SIGKILL); + curr->blocked.del(SIGSTOP); + + /* + * Return value: we return the saved a0 from the original context. + * The syscall dispatch will NOT overwrite a0 because sigreturn + * is special — it returns via the restored trap frame directly. + */ + return tf->a[0]; +} diff --git a/na/arch/riscv/mm/init.cc b/na/arch/riscv/mm/init.cc new file mode 100644 index 0000000..3cebcd9 --- /dev/null +++ b/na/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 <lume/init.h> +#include <lume/setup.h> +#include <lume/elf.h> +#include <lume/kernel_map.h> +#include <lume/types.h> +#include <asm/page.h> +#include <asm/csr.h> +#include <asm/pgtable.h> +#include <asm/sections.h> + +/* + * 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 index ae21d0e..f0a6848 100644 --- a/na/arch/riscv/mm/pmap.cc +++ b/na/arch/riscv/mm/pmap.cc @@ -21,6 +21,7 @@ #include <lume/memblock.h> #include <arch/mmu.h> #include <lume/klog.h> +#include <lume/errno.h> namespace pmap { @@ -45,6 +46,13 @@ inline constexpr uint64 PTE_COW = 1ULL << 8; 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) { @@ -101,13 +109,43 @@ static uint64 alloc_pt_page() uint64 create() { - return alloc_pt_page(); + 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); - for (int i = 0; i < kPtePerPage; i++) { + + /* 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) */ @@ -139,7 +177,7 @@ int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) if (!(table[idx] & PTE_V)) { uint64 child_pa = alloc_pt_page(); if (!child_pa) - return -12; /* -ENOMEM */ + return -ENOMEM; /* -ENOMEM */ table[idx] = pa_to_pte(child_pa, PTE_V); } table = pa_to_ptr(pte_to_pa(table[idx])); @@ -156,7 +194,7 @@ 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 -22; /* -EINVAL */ + return -EINVAL; /* -EINVAL */ } uint64 *table = pa_to_ptr(root_pa); @@ -166,7 +204,7 @@ int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) if (!(table[idx2] & PTE_V)) { uint64 child_pa = alloc_pt_page(); if (!child_pa) { - return -12; /* -ENOMEM */ + return -ENOMEM; /* -ENOMEM */ } table[idx2] = pa_to_pte(child_pa, PTE_V); } @@ -197,7 +235,7 @@ int protect(uint64 root_pa, uint64 va, uint64 perm) uint64 idx = vpn(va, level); uint64 pte = table[idx]; if (!(pte & PTE_V)) - return -14; /* -EFAULT */ + 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); @@ -205,7 +243,7 @@ int protect(uint64 root_pa, uint64 va, uint64 perm) } table = pa_to_ptr(pte_to_pa(pte)); } - return -14; + return -EFAULT; } int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm) @@ -250,6 +288,21 @@ bool lookup(uint64 root_pa, uint64 va, uint64 *pa_out) 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); @@ -273,7 +326,7 @@ int make_cow(uint64 root_pa, uint64 va) uint64 idx = vpn(va, level); uint64 pte = table[idx]; if (!(pte & PTE_V)) - return -14; /* -EFAULT */ + return -EFAULT; /* -EFAULT */ if (pte & (PTE_R | PTE_W | PTE_X)) { /* Clear write bit, set COW bit */ pte &= ~PTE_W; @@ -283,7 +336,7 @@ int make_cow(uint64 root_pa, uint64 va) } table = pa_to_ptr(pte_to_pa(pte)); } - return -14; + return -EFAULT; } void activate(uint64 root_pa) diff --git a/na/arch/riscv/mm/tlb_md.cc b/na/arch/riscv/mm/tlb_md.cc new file mode 100644 index 0000000..ed9aa29 --- /dev/null +++ b/na/arch/riscv/mm/tlb_md.cc @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#include <arch/tlb.h> +#include <arch/sbi.h> + +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 index af1cc26..97d22de 100644 --- a/na/arch/riscv/module.mk +++ b/na/arch/riscv/module.mk @@ -1,16 +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/boot/compressed.cc -SRCS_CC += arch/riscv/relocate.cc -SRCS_S += arch/riscv/boot/entry.S +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/relocate.cc b/na/arch/riscv/relocate.cc deleted file mode 100644 index 0216827..0000000 --- a/na/arch/riscv/relocate.cc +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/relocate.h> - -static inline bool range_ok(uint64 offset, uint64 size, uint64 image_size) -{ - if (offset > image_size) { - return false; - } - if (size > image_size - offset) { - return false; - } - return true; -} - -RelocStatus relocate_apply(uint64 load_base, uint64 image_size, - uint64 link_base, const void* reloc_blob, - uint64 reloc_size) -{ - if (!reloc_blob || reloc_size == 0) { - return RELOC_OK; - } - if (reloc_size < sizeof(RelocTableHeader)) { - return RELOC_ERR_SIZE; - } - - const auto* hdr = reinterpret_cast<const RelocTableHeader*>(reloc_blob); - if (hdr->magic != kRelocTableMagic) { - return RELOC_ERR_MAGIC; - } - if (hdr->version != kRelocTableVersion) { - return RELOC_ERR_VERSION; - } - if (hdr->arch != RELOC_ARCH_RISCV) { - return RELOC_ERR_ARCH; - } - if (hdr->entry_size < sizeof(RelocEntry) || (hdr->entry_size % 8) != 0) { - return RELOC_ERR_SIZE; - } - - uint64 max_entries = (reloc_size - sizeof(RelocTableHeader)) / - static_cast<uint64>(hdr->entry_size); - if (hdr->entry_count > max_entries) { - return RELOC_ERR_SIZE; - } - - if (image_size < sizeof(uint64)) { - return RELOC_ERR_RANGE; - } - - const uint64 delta = load_base - link_base; - const uint8* p = reinterpret_cast<const uint8*>(reloc_blob) + - sizeof(RelocTableHeader); - - for (uint32 i = 0; i < hdr->entry_count; ++i) { - const auto* ent = reinterpret_cast<const RelocEntry*>(p); - if ((ent->offset & 0x7) != 0 || - !range_ok(ent->offset, sizeof(uint64), image_size)) { - return RELOC_ERR_RANGE; - } - - uint64* target = reinterpret_cast<uint64*>(load_base + ent->offset); - switch (ent->type) { - case RELOC_RISCV_RELATIVE: - *target = load_base + ent->addend; - break; - case RELOC_RISCV_64: - *target = ent->addend + delta; - break; - default: - return RELOC_ERR_UNSUPPORTED; - } - p += hdr->entry_size; - } - - return RELOC_OK; -} - -const char* reloc_status_str(RelocStatus st) -{ - switch (st) { - case RELOC_OK: - return "ok"; - case RELOC_ERR_MAGIC: - return "bad magic"; - case RELOC_ERR_VERSION: - return "bad version"; - case RELOC_ERR_ARCH: - return "arch mismatch"; - case RELOC_ERR_SIZE: - return "size mismatch"; - case RELOC_ERR_RANGE: - return "range"; - case RELOC_ERR_UNSUPPORTED: - return "unsupported"; - default: - return "unknown"; - } -} diff --git a/na/arch/riscv/syscall/syscall_md.cc b/na/arch/riscv/syscall/syscall_md.cc index a52eb0e..26320c8 100644 --- a/na/arch/riscv/syscall/syscall_md.cc +++ b/na/arch/riscv/syscall/syscall_md.cc @@ -11,7 +11,9 @@ */ #include <lume/syscall.h> +#include <lume/syscall_nr.h> #include <arch/trapframe.h> +#include <arch/cpu.h> void syscall_dispatch_md(arch::TrapFrame* tf) { @@ -22,7 +24,19 @@ void syscall_dispatch_md(arch::TrapFrame* tf) 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<uint64>(SYS_rt_sigreturn)){ + return; + } tf->a[0] = static_cast<uint64>(ret); /* Return value in a0 */ } + diff --git a/na/arch/riscv/trap/fpu.S b/na/arch/riscv/trap/fpu.S new file mode 100644 index 0000000..90d96d9 --- /dev/null +++ b/na/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/na/arch/riscv/trap/trap_md.cc b/na/arch/riscv/trap/trap_md.cc index f910b26..e30aea6 100644 --- a/na/arch/riscv/trap/trap_md.cc +++ b/na/arch/riscv/trap/trap_md.cc @@ -120,16 +120,45 @@ static TrapInfo build_trap_info(arch::TrapFrame* tf, * * 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 trap_dispatch(arch::TrapFrame* tf) +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<uint64>(&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<unsigned>(info.cause), info.epc, info.val); + } + /* Save trapframe in TCB for syscall handlers (like fork) */ TaskControlBlock* curr = current_task(); if (curr) { @@ -152,6 +181,13 @@ extern "C" void trap_dispatch(arch::TrapFrame* tf) /* 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<uint64>(&trap_vector)); + userret(tf); + __builtin_unreachable(); } /* ======================================================================== diff --git a/na/arch/riscv/trap/trap_vector.S b/na/arch/riscv/trap/trap_vector.S index 160b122..8e45cae 100644 --- a/na/arch/riscv/trap/trap_vector.S +++ b/na/arch/riscv/trap/trap_vector.S @@ -90,6 +90,15 @@ trap_vector: /* 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 @@ -97,7 +106,6 @@ trap_vector: mv tp, t2 /* Step 7: jump to C++ trap_handler */ - /* Step 7: jump to C++ trap_handler */ jalr t3 diff --git a/na/drivers/base/bus.cc b/na/drivers/base/bus.cc new file mode 100644 index 0000000..975f676 --- /dev/null +++ b/na/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 <lume/device.h> +#include <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/errno.h> + +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 new file mode 100644 index 0000000..f92b2a4 --- /dev/null +++ b/na/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 <lume/device.h> +#include <lume/container_of.h> +#include <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/errno.h> +#include <lume/list.h> + +/* ------------------------------------------------------------------ */ +/* 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<device_driver, &device_driver::bus_node>(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 new file mode 100644 index 0000000..ce15b6d --- /dev/null +++ b/na/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 <lume/device.h> +#include <lume/container_of.h> +#include <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/errno.h> + +/* 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<device, &device::bus_list>(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<device, &device::driver_list>(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 new file mode 100644 index 0000000..1d8fb05 --- /dev/null +++ b/na/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/na/drivers/base/platform.cc b/na/drivers/base/platform.cc new file mode 100644 index 0000000..65ec21e --- /dev/null +++ b/na/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 <lume/platform_device.h> +#include <lume/driver.h> +#include <lume/device_node.h> +#include <lume/fdt.h> +#include <string.h> + +/* kmalloc is declared extern "C" in <lume/slab.h>, 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 <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/errno.h> +#include <lume/container_of.h> + +/* ------------------------------------------------------------------ */ +/* 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<platform_device*>( + kmalloc(static_cast<uint32>(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 new file mode 100644 index 0000000..f741dd2 --- /dev/null +++ b/na/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 <lume/file.h> +#include <lume/chardev.h> +#include <lume/types.h> + +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<int64>(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<int64>(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<int64>(count); +} + +static int64 zero_write(file* /*fp*/, const char* /*buf*/, uint64 count, int64* /*pos*/) +{ + return static_cast<int64>(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/driver.cc b/na/drivers/driver.cc deleted file mode 100644 index 5810645..0000000 --- a/na/drivers/driver.cc +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/driver.h> -#include <lume/fdt.h> -#include <lume/kprintf.h> - -extern "C" { - extern DriverEntry __drivers_start[]; - extern DriverEntry __drivers_end[]; -} - -void driver_probe_all() { - DeviceNode* root = fdt_get_node_by_path("/"); - if (!root) { - kprintf("[driver] WARN: No FDT root found\n"); - return; - } - - for (DriverEntry* drv = __drivers_start; drv < __drivers_end; ++drv) { - DeviceNode* node = fdt_find_compatible(drv->compatible); - if (node) { - kprintf("[driver] Probing %s\n", drv->compatible); - drv->probe(node); - } - } -} diff --git a/na/drivers/module.mk b/na/drivers/module.mk index 6e1396c..9326f30 100644 --- a/na/drivers/module.mk +++ b/na/drivers/module.mk @@ -4,7 +4,7 @@ # Drivers module SRCS_CC += drivers/uart.cc -SRCS_CC += drivers/driver.cc +SRCS_CC += drivers/console.cc ifeq ($(ARCH),riscv) SRCS_CC += drivers/plic.cc diff --git a/na/drivers/plic.cc b/na/drivers/plic.cc index ba6ac44..2818700 100644 --- a/na/drivers/plic.cc +++ b/na/drivers/plic.cc @@ -25,7 +25,7 @@ #include <lume/addr.h> #include <lume/config.h> #include <lume/fdt.h> -#include <lume/driver.h> +#include <lume/platform_device.h> #include <lume/irq.h> #include <lume/types.h> #include <lume/vmm.h> @@ -33,6 +33,8 @@ /* 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; @@ -159,7 +161,8 @@ static void plic_probe(DeviceNode* node) { vmm_map_kernel_mmio(plic_pa, 0x4000000); plic_va = pa_to_va(phys_addr(plic_pa)).raw; - uint64 ctx = s_context(0); /* BSP = hart 0 */ + /* 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++) { @@ -233,9 +236,24 @@ static const IrqChip plic_chip = { plic_set_priority }; -static void plic_probe_wrapper(DeviceNode* node) { - plic_probe(node); +static int plic_platform_probe(platform_device* pdev) { + plic_probe(pdev->of_node); g_irq_manager.set_chip(&plic_chip); + return 0; } -REGISTER_DRIVER("riscv,plic0", plic_probe_wrapper); +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 new file mode 100644 index 0000000..49190d4 --- /dev/null +++ b/na/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/na/drivers/tty/n_tty.cc b/na/drivers/tty/n_tty.cc new file mode 100644 index 0000000..0e312b0 --- /dev/null +++ b/na/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 <lume/tty.h> +#include <lume/tty_ioctl.h> +#include <lume/signal.h> +#include <lume/signal_nr.h> +#include <lume/slab.h> +#include <lume/new.h> +#include <lume/errno.h> +#include <lume/task.h> +#include <lume/sched.h> + +#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<n_tty_data*>(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<n_tty_data*>(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<int>(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<int>(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<int>(count); + } + + /* Raw output: pass directly to driver */ + return tty->driver->ops->write(tty, buf, static_cast<int>(count)); +} + +/* ================================================================== */ +/* N_TTY ioctl */ +/* ================================================================== */ + +static int n_tty_ioctl(tty_struct* tty, unsigned int cmd, uint64 arg) +{ + auto* d = static_cast<n_tty_data*>(tty->ldisc.data); + + switch (cmd) { + case FIONREAD: { + int count = buf_count(d); + auto* p = reinterpret_cast<int*>(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<n_tty_data*>(tty->ldisc.data); + if (d) { + delete d; + tty->ldisc.data = nullptr; + } +} + +static void n_tty_flush_buffer(tty_struct* tty) +{ + auto* d = static_cast<n_tty_data*>(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<n_tty_data*>(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<n_tty_data*>(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 new file mode 100644 index 0000000..5985bd9 --- /dev/null +++ b/na/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 <lume/tty.h> +#include <lume/tty_ioctl.h> +#include <lume/file.h> +#include <lume/fs_types.h> +#include <lume/chardev.h> +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/pgrp.h> +#include <lume/poll.h> +#include <lume/new.h> +#include <lume/errno.h> +#include <lume/klog.h> +#include <lume/slab.h> + +#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<termios*>(arg); + if (!p) return -EINVAL; + *p = tty->termios_data; + return 0; + } + + case TCSETS: + case TCSETSW: + case TCSETSF: { + auto* p = reinterpret_cast<const termios*>(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<winsize*>(arg); + if (!p) return -EINVAL; + *p = tty->ws; + return 0; + } + + case TIOCSWINSZ: { + auto* p = reinterpret_cast<const winsize*>(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<int32*>(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<const int32*>(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<int>(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<tty_struct*>(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<tty_struct*>(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<tty_struct*>(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<tty_struct*>(fp->private_data); + if (!tty) return POLLNVAL; + + poll_wait(fp, &tty->read_wait, static_cast<poll_table*>(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<tty_struct*>(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 new file mode 100644 index 0000000..973a6e9 --- /dev/null +++ b/na/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 <lume/tty.h> +#include <lume/irq.h> +#include <lume/addr.h> +#include <lume/fdt.h> +#include <lume/vmm.h> +#include <lume/errno.h> +#include <lume/klog.h> + +#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<char>(uart_read(UART_RBR)); +} + +/* ================================================================== */ +/* UART RX interrupt handler */ +/* ================================================================== */ + +static void uart_irq_handler(uint32 /*irq*/, void* arg) +{ + auto* tty = static_cast<tty_struct*>(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<volatile uint8*>( + 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<const uint8*>(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 index 264ec6a..467b496 100644 --- a/na/drivers/uart.cc +++ b/na/drivers/uart.cc @@ -2,9 +2,18 @@ /* Copyright (C) 2026 Richard Qin */ /* - * uart.cc — 16550a UART driver (early console) + * uart.cc — 16550a UART driver + console registry * - * Implements the early UART output mapping required by panic and kernel_main. + * 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 <lume/console.h> @@ -13,11 +22,19 @@ #include <lume/addr.h> #include <arch/config.h> +/* ================================================================== */ +/* 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; } @@ -70,3 +87,95 @@ void console_init() { 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<unsigned int>(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/fs/chardev.cc b/na/fs/chardev.cc new file mode 100644 index 0000000..fa96276 --- /dev/null +++ b/na/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 <lume/chardev.h> +#include <lume/list.h> +#include <lume/klog.h> +#include <kernel/sync/spinlock.h> + +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 new file mode 100644 index 0000000..94ab287 --- /dev/null +++ b/na/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 <lume/dcache.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* 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<uint8>(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<const char*>(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<const char*>(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<Spinlock&>(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 new file mode 100644 index 0000000..b4b2cf3 --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/chardev.h> +#include <lume/mount.h> +#include <lume/klog.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* 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 new file mode 100644 index 0000000..5fc8ab0 --- /dev/null +++ b/na/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 <lume/fdtable.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* ================================================================== */ +/* 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<int>(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 new file mode 100644 index 0000000..06095c1 --- /dev/null +++ b/na/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 <lume/file.h> +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* ================================================================== */ +/* 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 new file mode 100644 index 0000000..090819f --- /dev/null +++ b/na/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 <lume/fs_struct.h> +#include <lume/slab.h> + +/* ================================================================== */ +/* 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 new file mode 100644 index 0000000..ba10a3e --- /dev/null +++ b/na/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 <lume/klog.h> +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* 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<uint32>(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<uint64>(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<const cpio_header*>(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<uint64>(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 new file mode 100644 index 0000000..1d3d006 --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> + +namespace { +lume::atomic<uint64> 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 new file mode 100644 index 0000000..f1be157 --- /dev/null +++ b/na/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/na/fs/namei.cc b/na/fs/namei.cc new file mode 100644 index 0000000..9124ea9 --- /dev/null +++ b/na/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 <lume/namei.h> +#include <lume/dcache.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/mount.h> +#include <lume/task.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* ================================================================== */ +/* 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<uint32>(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<dentry*>(-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<dentry*>(-ENOENT); + + child = d_alloc(parent, name); + if (!child) + return reinterpret_cast<dentry*>(-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<dentry*>(-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<int64>(child); + if (err < 0 && err >= -4095) { + dput(current_de); + return static_cast<int>(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<int64>(lchild); + if (lerr < 0 && lerr >= -4095) { + dput(link_cur); + dput(current_de); + return static_cast<int>(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<uint32>(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 new file mode 100644 index 0000000..f7708f5 --- /dev/null +++ b/na/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 <lume/pipe.h> +#include <lume/file.h> +#include <lume/fdtable.h> +#include <lume/errno.h> +#include <lume/slab.h> +#include <lume/fs_types.h> +#include <lume/klog.h> +#include <lume/sched.h> + +/* ================================================================== */ +/* 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<pipe_inode_info*>(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<int64>(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<int>(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<pipe_inode_info*>(fp->private_data); + if (!pi) return -EBADF; + + if (pi->readers.load() == 0) { + return -EPIPE; /* Broken pipe */ + } + + int64 written = 0; + + while (static_cast<uint64>(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<int64>(count) - written; + int to_write = avail < static_cast<int>(remaining) + ? avail + : static_cast<int>(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<pipe_inode_info*>(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<pipe_inode_info*>(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 new file mode 100644 index 0000000..6d9a070 --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/file.h> +#include <lume/dcache.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* + * 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<ramfs_file_data*>(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<int64>(count) > avail) + count = static_cast<uint64>(avail); + + auto* data = static_cast<ramfs_file_data*>(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<int64>(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<uint64>(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<int64>(end) > ip->i_size) + ip->i_size = end; + + *pos = end; + mark_inode_dirty(ip); + return static_cast<int64>(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<int>(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 new file mode 100644 index 0000000..c5e49bf --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/slab.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +/* 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 new file mode 100644 index 0000000..6f929f2 --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/mount.h> +#include <lume/slab.h> +#include <lume/kprintf.h> +#include <lume/errno.h> +#include <lume/fs_types.h> + +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<ramfs_file_data*>(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 new file mode 100644 index 0000000..231b57c --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/mount.h> +#include <lume/klog.h> +#include <lume/slab.h> +#include <lume/errno.h> + +/* 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 new file mode 100644 index 0000000..ec56e88 --- /dev/null +++ b/na/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 <lume/fs.h> +#include <lume/dcache.h> +#include <lume/file.h> +#include <lume/mount.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/klog.h> +#include <lume/kprintf.h> +#include <lume/task.h> +#include <lume/sched.h> + +/* ================================================================== */ +/* 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/lume/args.h b/na/include/lume/args.h new file mode 100644 index 0000000..0562dc5 --- /dev/null +++ b/na/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/na/include/lume/atomic.h b/na/include/lume/atomic.h index e6331c2..6e31129 100644 --- a/na/include/lume/atomic.h +++ b/na/include/lume/atomic.h @@ -51,6 +51,16 @@ public: 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, diff --git a/na/include/lume/boot_params.h b/na/include/lume/boot_params.h deleted file mode 100644 index 7877468..0000000 --- a/na/include/lume/boot_params.h +++ /dev/null @@ -1,85 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -/* - * Boot parameters shared between bootloader/firmware and kernel. - */ - -inline constexpr uint64 kBootParamsMagic = 0x4C554D45424F4F54ULL; /* LUMEBOOT */ -inline constexpr uint32 kBootParamsVersion = 2; - -enum BootMemType : uint32 { - BOOT_MEM_USABLE = 1, - BOOT_MEM_RESERVED = 2, - BOOT_MEM_ACPI = 3, - BOOT_MEM_MMIO = 4, -}; - -enum BootSeedSource : uint32 { - BOOT_SEED_NONE = 0, - BOOT_SEED_FDT = 1, - BOOT_SEED_HW = 2, - BOOT_SEED_CYCLE = 3, -}; - -enum BootImageFlags : uint32 { - BOOT_IMAGE_COMPRESSED = 1u << 0, - BOOT_IMAGE_RELOCATED = 1u << 1, - BOOT_IMAGE_DECOMP_FAILED = 1u << 2, - BOOT_IMAGE_RELOC_FAILED = 1u << 3, -}; - -struct BootMemRegion { - uint64 base; - uint64 size; - uint32 type; - uint32 reserved; -}; - -struct BootParams { - uint64 magic; - uint32 version; - uint32 size; - - uint64 cmdline_ptr; - uint32 cmdline_len; - uint32 memmap_count; - uint64 memmap_ptr; - uint32 memmap_entry_size; - uint32 reserved0; - - uint64 initrd_start; - uint64 initrd_end; - - uint64 acpi_ptr; - uint64 fdt_ptr; - - uint64 kaslr_seed; - uint32 seed_source; - uint32 flags; - - uint64 kimage_phys_base; - uint64 kimage_size; - uint64 kimage_reloc_offset; - uint64 kimage_reloc_size; - uint32 kimage_flags; - uint32 kimage_comp; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -void boot_params_init_fdt(uint64 fdt_paddr); -const BootParams* boot_params_get(); -void boot_params_set_image_info(uint64 phys_base, uint64 image_size, - uint64 reloc_offset, uint64 reloc_size, - uint32 comp, uint32 image_flags); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/chardev.h b/na/include/lume/chardev.h new file mode 100644 index 0000000..be81ab9 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> + +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 new file mode 100644 index 0000000..156bae4 --- /dev/null +++ b/na/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 <lume/types.h> + +/* 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 index fa05363..243f0de 100644 --- a/na/include/lume/compiler.h +++ b/na/include/lume/compiler.h @@ -1,27 +1,54 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* Copyright (C) 2026 Richard Qin */ -#pragma once +#ifndef _LUME_COMPILER_H +#define _LUME_COMPILER_H -/** - * compiler.h - Centralized management of compiler-specific macros and built-in wrappers. +/* + * 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. */ -/* Memory barrier macros */ -#define WRITE_ONCE(x, val) \ - do { \ - __asm__ volatile("" ::: "memory"); \ - (x) = (val); \ - __asm__ volatile("" ::: "memory"); \ - } while (0) +/* Branch prediction hints */ +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) -#define READ_ONCE(x) (*(volatile __typeof__(x) *)&(x)) +/* 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") -#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__)) -/* Branch prediction hints */ -#define unlikely(x) __builtin_expect(!!(x), 0) -#define likely(x) __builtin_expect(!!(x), 1) +/* + * 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)) -/* Function attributes */ -#define __always_inline inline __attribute__((always_inline)) +#endif /* _LUME_COMPILER_H */ diff --git a/na/include/lume/compressed.h b/na/include/lume/compressed.h deleted file mode 100644 index 8ca775d..0000000 --- a/na/include/lume/compressed.h +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -inline constexpr uint64 kCompressedKernelMagic = 0x4C554D45434F4D50ULL; /* LUMECOMP */ -inline constexpr uint32 kCompressedKernelVersion = 1; - -enum KernelCompressionType : uint32 { - KERNEL_COMP_NONE = 0, - KERNEL_COMP_LZ4 = 1, -}; - -enum CompressedHeaderFlags : uint16 { - LUME_HDR_FLAG_STAGE3_DONE = 1u << 0, -}; - -struct CompressedKernelHeader { - uint64 magic; - uint32 version; - uint16 header_size; - uint16 flags; - uint32 compression; - uint32 reserved0; - uint64 uncompressed_size; - uint64 compressed_size; - uint64 load_phys_base; - uint64 reloc_offset; - uint64 reloc_size; - uint64 payload_offset; - uint32 header_crc32; - uint32 payload_crc32; -}; - -static_assert(sizeof(CompressedKernelHeader) == 80); diff --git a/na/include/lume/config.h b/na/include/lume/config.h index d3539c5..bde717a 100644 --- a/na/include/lume/config.h +++ b/na/include/lume/config.h @@ -37,7 +37,7 @@ #endif #ifndef CONFIG_KERNEL_STACK_SIZE -#define CONFIG_KERNEL_STACK_SIZE 8192 +#define CONFIG_KERNEL_STACK_SIZE 16384 #endif #ifndef CONFIG_HZ @@ -86,8 +86,15 @@ 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 <arch/config.h> + +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 index a4fdf3f..af187a6 100644 --- a/na/include/lume/console.h +++ b/na/include/lume/console.h @@ -3,7 +3,30 @@ #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 <lume/types.h> +#include <lume/list.h> + +/* ================================================================== */ +/* Early console (Phase 1 — direct UART access) */ +/* ================================================================== */ extern "C" void early_putc(char c); extern "C" void early_puts(const char* s); @@ -21,3 +44,34 @@ void console_init(); * 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 new file mode 100644 index 0000000..a6889eb --- /dev/null +++ b/na/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 <lume/types.h> + +#define container_of(ptr, type, member) \ + reinterpret_cast<type*>( \ + reinterpret_cast<uintptr>(ptr) - \ + reinterpret_cast<uintptr>(&(reinterpret_cast<type*>(0)->member))) diff --git a/na/include/lume/dcache.h b/na/include/lume/dcache.h new file mode 100644 index 0000000..a31402e --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <lume/atomic.h> +#include <lume/fs_types.h> +#include <kernel/sync/spinlock.h> + +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<int32> 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/decompress.h b/na/include/lume/decompress.h deleted file mode 100644 index 04082a9..0000000 --- a/na/include/lume/decompress.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -/* - * LZ4 block decompressor (no frame headers). Returns bytes written or -1. - */ -int lz4_decompress(const uint8* src, uint32 src_len, uint8* dst, uint32 dst_cap); diff --git a/na/include/lume/device.h b/na/include/lume/device.h new file mode 100644 index 0000000..46e22e4 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <lume/kobject.h> +#include <kernel/sync/spinlock.h> + +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/driver.h b/na/include/lume/driver.h index c9d5669..a8bd787 100644 --- a/na/include/lume/driver.h +++ b/na/include/lume/driver.h @@ -4,30 +4,21 @@ #pragma once /* - * Driver Registration Subsystem + * driver.h — boot-time driver probing entry point. * - * Provides a linker-set based registration mechanism for hardware drivers. - * Drivers define a probe function and register it with the REGISTER_DRIVER macro. - * During boot, driver_probe_all() scans the Device Tree (FDT) and invokes the - * probe function for any matching compatible strings. + * The actual driver-model primitives (kobject, device, bus_type, + * platform_device, platform_driver) live in their own headers: + * <lume/kobject.h>, <lume/device.h>, <lume/platform_device.h>. * - * This decouples the MI kernel initialization from MD driver specifics. + * 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 */ -#include <lume/types.h> - -class DeviceNode; - -using DriverProbeFunc = void (*)(DeviceNode* node); - -struct DriverEntry { - const char* compatible; - DriverProbeFunc probe; -}; - -#define REGISTER_DRIVER(compat_str, probe_fn) \ - static const DriverEntry __driver_##probe_fn \ - __attribute__((used, section(".drivers"))) = { compat_str, probe_fn } - -/* MI initialization function called from kernel_main() */ +/* 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 new file mode 100644 index 0000000..772ad97 --- /dev/null +++ b/na/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 <lume/types.h> + +/* ================================================================== */ +/* 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 index 089aa94..f3bf09f 100644 --- a/na/include/lume/errno.h +++ b/na/include/lume/errno.h @@ -1,151 +1,186 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_ERRNO_H +#define _LUME_ERRNO_H + /* - * Standard error codes — asm-generic/errno-base.h + asm-generic/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. * - * LumeOS targets only asm-generic architectures (RISC-V, AArch64, LoongArch), - * so a single unified file is sufficient. No per-arch overrides needed. + * Defined as plain #define so this header is usable from C, C++, and + * assembly (the macros expand to integer literals, no types). */ -#pragma once +/* ===== 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 */ -/* ====== errno-base.h (1-34) ====== */ +/* ===== 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 */ -#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 */ - -/* ====== errno.h (35-133) ====== */ +/* ===== 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 */ -#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 */ +/* 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 */ -/* 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 */
\ No newline at end of file +#endif /* _LUME_ERRNO_H */ diff --git a/na/include/lume/fdtable.h b/na/include/lume/fdtable.h new file mode 100644 index 0000000..6159519 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> +#include <kernel/sync/spinlock.h> + +struct file; + +/* ================================================================== */ +/* files_struct — per-task open file table */ +/* ================================================================== */ + +inline constexpr int NR_OPEN_DEFAULT = 256; + +struct files_struct { + lume::atomic<int32> 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/file.h b/na/include/lume/file.h new file mode 100644 index 0000000..349059b --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> +#include <lume/fs_types.h> +#include <kernel/sync/spinlock.h> + +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<int32> 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 new file mode 100644 index 0000000..8dac552 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <lume/atomic.h> +#include <lume/fs_types.h> +#include <kernel/sync/spinlock.h> + +/* 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<int32> 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<int32> i_count; + lume::atomic<int32> 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 new file mode 100644 index 0000000..ba643b5 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> +#include <lume/file.h> /* struct path */ +#include <kernel/sync/spinlock.h> + +/* ================================================================== */ +/* fs_struct */ +/* ================================================================== */ + +struct fs_struct { + lume::atomic<int32> 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 new file mode 100644 index 0000000..767f37c --- /dev/null +++ b/na/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 <lume/types.h> + +/* ================================================================== */ +/* 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 new file mode 100644 index 0000000..541ab96 --- /dev/null +++ b/na/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 <lume/setup.h>. + */ + +#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/kbuild.h b/na/include/lume/kbuild.h new file mode 100644 index 0000000..4c3e12a --- /dev/null +++ b/na/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/<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 new file mode 100644 index 0000000..7eafe88 --- /dev/null +++ b/na/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 + * <generated/asm-offsets.h>, 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 <lume/types.h> + +#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 <generated/asm-offsets.h> + +#endif /* __ASSEMBLY__ */ + +#endif /* _LUME_KERNEL_MAP_H */ diff --git a/na/include/lume/klog.h b/na/include/lume/klog.h index b63810f..69ab269 100644 --- a/na/include/lume/klog.h +++ b/na/include/lume/klog.h @@ -51,7 +51,11 @@ enum KlogSubsys : uint8 { KLOG_SUBSYS_FS = 8, KLOG_SUBSYS_DRIVER = 9, KLOG_SUBSYS_TEST = 10, - KLOG_SUBSYS_MAX = 11, + 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. */ @@ -165,6 +169,15 @@ void klog_stats(KlogStats* out); #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" diff --git a/na/include/lume/kobject.h b/na/include/lume/kobject.h new file mode 100644 index 0000000..8581d6e --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <lume/kref.h> +#include <kernel/sync/spinlock.h> + +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/kref.h b/na/include/lume/kref.h new file mode 100644 index 0000000..13d53f3 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> + +struct kref { + lume::atomic<int32> 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 new file mode 100644 index 0000000..e3496e9 --- /dev/null +++ b/na/include/lume/libfdt.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INCLUDE_LIBFDT_H_ +#define _INCLUDE_LIBFDT_H_ + +#include <lume/libfdt_env.h> +#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 new file mode 100644 index 0000000..a95fb1d --- /dev/null +++ b/na/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 <linux/...> includes. Provides the surface + * libfdt needs and nothing else. + */ + +#include <lume/types.h> /* uint16_t / uint32_t / uint64_t / size_t */ +#include <lume/limits.h> /* INT_MAX */ +#include <string.h> /* libc-shim memcpy / memcmp / strlen ... */ + +/* libfdt uses these names; both come from <lume/limits.h>. */ +#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 new file mode 100644 index 0000000..a275a71 --- /dev/null +++ b/na/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 <limits.h> 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 <limits.h> (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 index 7c020a0..ed1202a 100644 --- a/na/include/lume/list.h +++ b/na/include/lume/list.h @@ -4,6 +4,7 @@ #pragma once #include <lume/types.h> +#include <lume/container_of.h> /** * @brief The node to be embedded in a struct to make it linkable. @@ -289,4 +290,57 @@ public: private: list_node head_; -};
\ No newline at end of file +}; + +/* ======================================================================== + * 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 index a6b2b5f..58d2f72 100644 --- a/na/include/lume/lumeos.lds.h +++ b/na/include/lume/lumeos.lds.h @@ -15,36 +15,100 @@ #ifndef _LUME_LUMEOS_LDS_H #define _LUME_LUMEOS_LDS_H -/* ===== Text Section: executable code ===== */ +/* ===== 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 ===== */ +/* ===== 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 = .; \ } -/* ===== Relocation Table (optional, stays in file image) ===== */ -#define RELOC_SECTION \ - .reloc : AT(ADDR(.reloc) - PAGE_OFFSET) { \ - . = ALIGN(8); \ - __lume_reloc_start = .; \ - KEEP(*(.lume.reloc .lume.reloc.*)) \ - __lume_reloc_end = .; \ +/* ===== .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 ===== */ @@ -61,11 +125,16 @@ KEEP(*(.drivers)) \ PROVIDE(__drivers_end = .); -/* ===== Initialized Read-Write Data ===== */ +/* ===== 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 = .; \ } @@ -80,12 +149,40 @@ _ebss = .; \ } -/* ===== Discard Unwanted Sections ===== */ -#define DISCARDS \ - /DISCARD/ : { \ - *(.eh_frame .eh_frame_hdr) \ - *(.note .note.*) \ - *(.comment) \ +/* ===== 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 ===== */ diff --git a/na/include/lume/mm_layout.h b/na/include/lume/mm_layout.h new file mode 100644 index 0000000..7744de0 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/config.h> + +/* 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/mount.h b/na/include/lume/mount.h new file mode 100644 index 0000000..13f8e62 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <lume/atomic.h> +#include <lume/file.h> + +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<int32> mnt_count; +}; + +inline mount* real_mount(vfsmount* m) { + return reinterpret_cast<mount*>(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 new file mode 100644 index 0000000..9719aa6 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <kernel/sync/spinlock.h> + +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 new file mode 100644 index 0000000..1764693 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/file.h> /* struct path */ +#include <lume/dcache.h> /* 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/page.h b/na/include/lume/page.h index 7f3f8d1..61bff95 100644 --- a/na/include/lume/page.h +++ b/na/include/lume/page.h @@ -10,8 +10,21 @@ * 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 `state`. The refcount - * field is independent of the union to support COW sharing. + * 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 */ @@ -20,40 +33,119 @@ #include <lume/list.h> #include <lume/atomic.h> -/* Forward declaration — defined in slab module (Phase 2) */ +/* Forward declarations */ class KmemCache; -enum class PageState : uint8 { +/* ================================================================== */ +/* Page type discriminant */ +/* ================================================================== */ + +enum class PageType : uint8 { Free, // In Buddy free list PcpCached, // In Per-CPU cache (invisible to buddy coalescing) - Allocated, // Handed out to Slab / VMM / user + 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 { - /* - * Reference count (atomic). - * Starts at 1 on allocation. Incremented by fork() for COW pages. - * When decremented to 0, the page is returned to the free pool. - */ - lume::atomic<uint32> refcount; + /* --- Counts (atomic, lock-free) --- */ + lume::atomic<uint32> refcount; // Lifecycle ref: 0 = reclaimable + lume::atomic<uint32> mapcount; // PTE mapping count: 0 = no user mapping + + /* --- State --- */ + lume::atomic<uint32> flags; // PG_* bits (also serves as bit-lock) + PageType type; // Discriminant for union + uint8 order; // Buddy order (0–10, i.e. 4KB–4MB) - PageState state; // Current usage state - 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 state == Free */ + /* Valid when type == Free */ struct { - list_node free_link; // Link into Buddy or PCP free list + list_node free_link; // Link into Buddy or PCP free list } free; - /* Valid when state == Slab */ + /* 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 + 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; - /* state == Allocated: no extra metadata (yet) */ + /* 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<uint64>(page->mapping) & 1ULL) != 0; +} + +inline void* page_mapping_raw(const Page* page) +{ + return reinterpret_cast<void*>( + reinterpret_cast<uint64>(page->mapping) & ~1ULL); +} + +inline void page_set_anon_mapping(Page* page, void* anon_vma) +{ + page->mapping = reinterpret_cast<void*>( + reinterpret_cast<uint64>(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 new file mode 100644 index 0000000..abdc535 --- /dev/null +++ b/na/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 <lume/types.h> + +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 new file mode 100644 index 0000000..127f3f8 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/list.h> +#include <kernel/sync/spinlock.h> + +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 <typename F> +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<int>(static_cast<uint32>(pgid)) & kPgrpHashMask; +} + +#include <lume/signal_types.h> + +template <typename F> +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<signal_struct, &signal_struct::pgrp_link>(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 new file mode 100644 index 0000000..e309a82 --- /dev/null +++ b/na/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 <lume/types.h> + +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/pipe.h b/na/include/lume/pipe.h new file mode 100644 index 0000000..c905c9a --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> +#include <lume/waitqueue.h> +#include <kernel/sync/spinlock.h> + +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<int32> readers; /* Number of open read ends */ + lume::atomic<int32> writers; /* Number of open write ends */ + lume::atomic<int32> 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 new file mode 100644 index 0000000..ac3e527 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/device.h> + +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 index f02a146..3e4b819 100644 --- a/na/include/lume/pmm.h +++ b/na/include/lume/pmm.h @@ -64,6 +64,20 @@ void pmm_free_pages(Page* page, uint8 order); 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. @@ -75,6 +89,20 @@ 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 new file mode 100644 index 0000000..7d5bca2 --- /dev/null +++ b/na/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 <lume/types.h> + +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 new file mode 100644 index 0000000..687d103 --- /dev/null +++ b/na/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/<pid>/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 <lume/types.h> + +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/rbtree.h b/na/include/lume/rbtree.h index 8bc8104..80b0c6c 100644 --- a/na/include/lume/rbtree.h +++ b/na/include/lume/rbtree.h @@ -10,16 +10,7 @@ #include <lume/compiler.h> #include <lume/rbtree_types.h> - -/* ------------------------------------------------------------------ */ -/* Linux-compat macros for freestanding kernel */ -/* ------------------------------------------------------------------ */ - -#ifndef container_of -#define container_of(ptr, type, member) \ - reinterpret_cast<type *>( \ - reinterpret_cast<char *>(ptr) - __builtin_offsetof(type, member)) -#endif +#include <lume/container_of.h> /* ------------------------------------------------------------------ */ /* Core macros */ diff --git a/na/include/lume/relocate.h b/na/include/lume/relocate.h deleted file mode 100644 index 120824f..0000000 --- a/na/include/lume/relocate.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -inline constexpr uint64 kRelocTableMagic = 0x4C554D45524C4F43ULL; /* LUMERLOC */ -inline constexpr uint16 kRelocTableVersion = 1; - -enum RelocArch : uint16 { - RELOC_ARCH_RISCV = 1, - RELOC_ARCH_AARCH64 = 2, - RELOC_ARCH_X86_64 = 3, -}; - -enum RelocType : uint32 { - RELOC_RISCV_RELATIVE = 1, - RELOC_RISCV_64 = 2, -}; - -struct RelocTableHeader { - uint64 magic; - uint16 version; - uint16 arch; - uint32 entry_size; - uint32 entry_count; - uint32 reserved; -}; - -struct RelocEntry { - uint64 offset; - uint64 addend; - uint32 type; - uint32 reserved; -}; - -enum RelocStatus : uint32 { - RELOC_OK = 0, - RELOC_ERR_MAGIC, - RELOC_ERR_VERSION, - RELOC_ERR_ARCH, - RELOC_ERR_SIZE, - RELOC_ERR_RANGE, - RELOC_ERR_UNSUPPORTED, -}; - -/* Apply relocations to the image loaded at load_base. */ -RelocStatus relocate_apply(uint64 load_base, uint64 image_size, - uint64 link_base, const void* reloc_blob, - uint64 reloc_size); - -const char* reloc_status_str(RelocStatus st); diff --git a/na/include/lume/sched.h b/na/include/lume/sched.h index 0f25e58..ab26bac 100644 --- a/na/include/lume/sched.h +++ b/na/include/lume/sched.h @@ -71,3 +71,32 @@ void sched_dequeue(TaskControlBlock* task); /* 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/setup.h b/na/include/lume/setup.h new file mode 100644 index 0000000..f8e6cb6 --- /dev/null +++ b/na/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 <lume/types.h> +#include <asm/pgtable.h> + +#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 index 5ff6ed0..05b7542 100644 --- a/na/include/lume/signal.h +++ b/na/include/lume/signal.h @@ -4,5 +4,57 @@ #pragma once /* - * Signal subsystem interface. + * 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 <lume/types.h> + +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_types.h b/na/include/lume/signal_types.h new file mode 100644 index 0000000..f66231b --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/signal_nr.h> +#include <lume/list.h> +#include <kernel/sync/spinlock.h> + +/* ================================================================== */ +/* 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<uint64>(-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/smp.h b/na/include/lume/smp.h new file mode 100644 index 0000000..a5292b6 --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/atomic.h> +#include <lume/config.h> + +/* + * 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<uint32> 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 new file mode 100644 index 0000000..c8dc7f4 --- /dev/null +++ b/na/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/na/include/lume/stringify.h b/na/include/lume/stringify.h new file mode 100644 index 0000000..0e84cbe --- /dev/null +++ b/na/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/na/include/lume/syscall_nr.h b/na/include/lume/syscall_nr.h index 1b677d4..29e3904 100644 --- a/na/include/lume/syscall_nr.h +++ b/na/include/lume/syscall_nr.h @@ -47,11 +47,28 @@ 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; @@ -59,3 +76,7 @@ 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 index c87938c..e959147 100644 --- a/na/include/lume/task.h +++ b/na/include/lume/task.h @@ -17,7 +17,9 @@ #include <lume/list.h> #include <arch/context.h> #include <arch/trapframe.h> +#include <arch/fpstate.h> #include <lume/waitqueue.h> +#include <lume/signal_types.h> /* Forward declarations */ struct VmSpace; @@ -33,6 +35,7 @@ enum class TaskState : uint8 { 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 }; /* ======================================================================== @@ -43,13 +46,15 @@ inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user m inline constexpr uint8 TF_IDLE = 0x02; // Idle thread inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1) -inline constexpr int kExitCodeSignalFlag = 0x100; - /* ======================================================================== * 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; /* ======================================================================== @@ -66,6 +71,7 @@ struct TaskControlBlock { 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 @@ -73,6 +79,7 @@ struct TaskControlBlock { /* ===== 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 @@ -81,6 +88,11 @@ struct TaskControlBlock { /* ===== 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 @@ -89,11 +101,34 @@ struct TaskControlBlock { /* ===== 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; }; diff --git a/na/include/lume/timer.h b/na/include/lume/timer.h index e7de2f1..9bdddd5 100644 --- a/na/include/lume/timer.h +++ b/na/include/lume/timer.h @@ -9,9 +9,24 @@ * MI Timer Subsystem */ +#include <lume/list.h> + +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; -// BSP global initialization void timer_init(); // AP per-CPU initialization @@ -22,3 +37,7 @@ 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 new file mode 100644 index 0000000..b4d51ea --- /dev/null +++ b/na/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 <lume/types.h> + +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/tty.h b/na/include/lume/tty.h new file mode 100644 index 0000000..34cf7bb --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/tty_ioctl.h> +#include <lume/waitqueue.h> +#include <kernel/sync/spinlock.h> + +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 new file mode 100644 index 0000000..87ba59b --- /dev/null +++ b/na/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 <lume/types.h> + +/* ================================================================== */ +/* 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 index 1d5b896..2342359 100644 --- a/na/include/lume/types.h +++ b/na/include/lume/types.h @@ -1,35 +1,78 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* Copyright (C) 2026 Richard Qin */ -#pragma once +#ifndef _LUME_TYPES_H +#define _LUME_TYPES_H /* * LumeOS Base Type Definitions * - * All kernel code should use these types instead of - * stdint.h (which may not exist in freestanding mode). + * Three naming layers coexist so that source files imported from + * upstream projects (Linux kernel, libfdt) compile unchanged: * - * Kernel-wide configuration constants are in <lume/config.h>. + * 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. */ -using uint8 = unsigned char; -using uint16 = unsigned short; -using uint32 = unsigned int; -using uint64 = unsigned long long; +/* ===== LumeOS native names ===== */ +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; -using int8 = signed char; -using int16 = signed short; -using int32 = signed int; -using int64 = signed long long; +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; -using ssize_t = int64; -using uintptr = uint64; +typedef __SIZE_TYPE__ size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef int64 ssize_t; +typedef uint64 uintptr; -#define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER) +/* ===== 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; -/* Null pointer constant */ -#ifndef nullptr -/* C++11 and later have nullptr built-in */ +/* ===== POSIX / C99 aliases ===== + * Guarded so a later #include <stdint.h> 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 <lume/libfdt_env.h>. ===== */ +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/uuid.h b/na/include/lume/uuid.h new file mode 100644 index 0000000..fcbd4a3 --- /dev/null +++ b/na/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 <ying.huang@intel.com> + */ + +#pragma once + +#include <lume/types.h> +#include <libc-shim/string.h> + +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<int>(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<uint8>((hi << 4) | lo); + i += 2; + } + + return out_index == static_cast<int>(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<uint8>((a >> 24) & 0xff), + static_cast<uint8>((a >> 16) & 0xff), + static_cast<uint8>((a >> 8) & 0xff), + static_cast<uint8>(a & 0xff), + static_cast<uint8>((b >> 8) & 0xff), + static_cast<uint8>(b & 0xff), + static_cast<uint8>((c >> 8) & 0xff), + static_cast<uint8>(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<uint8>(a & 0xff), + static_cast<uint8>((a >> 8) & 0xff), + static_cast<uint8>((a >> 16) & 0xff), + static_cast<uint8>((a >> 24) & 0xff), + static_cast<uint8>(b & 0xff), + static_cast<uint8>((b >> 8) & 0xff), + static_cast<uint8>(c & 0xff), + static_cast<uint8>((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<int>(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 index 85988e4..9cbebf1 100644 --- a/na/include/lume/vmm.h +++ b/na/include/lume/vmm.h @@ -13,6 +13,7 @@ #include <lume/types.h> #include <lume/rbtree.h> +#include <lume/atomic.h> #include <kernel/sync/spinlock.h> /* ------------------------------------------------------------------ */ @@ -61,14 +62,25 @@ 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<int32> refcount; // Reference count (for CLONE_VM sharing) + lume::atomic<uint64> 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) + // Create a brand new VmSpace (allocates root pagetable, refcount=1) static VmSpace* create(); - // Destroy VmSpace, unmapping all VMAs and freeing pagetables + // 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(); }; diff --git a/na/include/lume/wait.h b/na/include/lume/wait.h index 02012ab..865c1f2 100644 --- a/na/include/lume/wait.h +++ b/na/include/lume/wait.h @@ -11,12 +11,21 @@ /* 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 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_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 index dd5edfe..3720cb7 100644 --- a/na/include/lume/waitqueue.h +++ b/na/include/lume/waitqueue.h @@ -6,11 +6,24 @@ /* * WaitQueue — blocking synchronization primitive * - * A thread calls wq.sleep() to block itself until another thread - * (or interrupt handler) calls wq.wake_one() or wq.wake_all(). + * Supports two usage patterns: * - * Implementation: intrusive doubly-linked list of sleeping tasks, - * protected by an internal spinlock. + * 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 */ @@ -20,6 +33,7 @@ #include <kernel/sync/spinlock.h> struct TaskControlBlock; +enum class TaskState : uint8; class WaitQueue { public: @@ -28,11 +42,26 @@ public: lock_.init(name); } - /* Block the current task on this queue. + /* + * 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, - * to avoid lost wakeups. - * Returns after wake_one/wake_all unblocks this task. */ + * 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. */ diff --git a/na/kernel/boot_params.cc b/na/kernel/boot_params.cc deleted file mode 100644 index e5d4ee5..0000000 --- a/na/kernel/boot_params.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/boot_params.h> - -static BootParams g_boot_params = {}; - -static void boot_params_init_once() -{ - if (g_boot_params.magic == kBootParamsMagic) { - return; - } - g_boot_params = {}; - g_boot_params.magic = kBootParamsMagic; - g_boot_params.version = kBootParamsVersion; - g_boot_params.size = static_cast<uint32>(sizeof(BootParams)); -} - -void boot_params_init_fdt(uint64 fdt_paddr) -{ - boot_params_init_once(); - g_boot_params.fdt_ptr = fdt_paddr; - g_boot_params.seed_source = BOOT_SEED_NONE; -} - -const BootParams* boot_params_get() -{ - if (g_boot_params.magic != kBootParamsMagic) { - return nullptr; - } - return &g_boot_params; -} - -void boot_params_set_image_info(uint64 phys_base, uint64 image_size, - uint64 reloc_offset, uint64 reloc_size, - uint32 comp, uint32 image_flags) -{ - boot_params_init_once(); - g_boot_params.kimage_phys_base = phys_base; - g_boot_params.kimage_size = image_size; - g_boot_params.kimage_reloc_offset = reloc_offset; - g_boot_params.kimage_reloc_size = reloc_size; - g_boot_params.kimage_comp = comp; - g_boot_params.kimage_flags = image_flags; -} diff --git a/na/kernel/exec.cc b/na/kernel/exec.cc new file mode 100644 index 0000000..616c0da --- /dev/null +++ b/na/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 <lume/elf.h> +#include <lume/types.h> +#include <lume/task.h> +#include <lume/vmm.h> +#include <lume/addr.h> +#include <lume/config.h> +#include <lume/errno.h> +#include <lume/pmm.h> +#include <lume/kprintf.h> +#include <lume/klog.h> +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/dcache.h> +#include <lume/namei.h> +#include <lume/fdtable.h> +#include <lume/mm_layout.h> +#include <lume/signal_types.h> +#include <arch/pmap.h> +#include <arch/trapframe.h> +#include <arch/mmu.h> +#include <arch/cpu.h> +#include <arch/trap.h> + +/* 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<char*>(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<uint8*>(dst); + auto* s = static_cast<const uint8*>(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<uint8*>(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<void*>(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<int64>(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<uint64>(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<int64>(ehdr.e_phoff)); + if (n < static_cast<int64>(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<Elf64_Phdr*>( + 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<uint8*>(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<int64>(file_off)); + if (n < static_cast<int64>(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<uint8*>(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<uint8*>(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<uint8*>(pa_to_va(PhysAddr{pa}).raw); + uint64 off = addr - page_align_down(addr); + *reinterpret_cast<uint64*>(kva + off) = val; + return 0; + }; + + /* argc */ + err = write_u64(sp, static_cast<uint64>(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<uint8*>(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<uint8*>(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<const char*>(a0); + const char* const* argv = reinterpret_cast<const char* const*>(a1); + const char* const* envp = reinterpret_cast<const char* const*>(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<uint64>(trap_vector)); + userret(curr->trapframe); + __builtin_unreachable(); +} diff --git a/na/kernel/main.cc b/na/kernel/main.cc index f28d078..db5843c 100644 --- a/na/kernel/main.cc +++ b/na/kernel/main.cc @@ -10,26 +10,50 @@ * BSP (cpu_id == 0) executes the full subsystem initialization DAG. * APs spin on ap_boot_sync, then perform per-CPU init only. * + * Boot DAG invariants (docs/specs/linux_abi_mm_plan.md §Boot Sequence): + * - trap_init() MUST precede any allocator (exception safety net) + * - sched_init() MUST precede timer_init() (software before hardware) + * - PID 1 (init) is spawned before entering idle loop + * * Reference: docs/specs/global_init.md */ +#include <arch/config.h> #include <arch/cpu.h> +#include <arch/extable.h> +#include <arch/sbi.h> +#include <lume/addr.h> #include <lume/atomic.h> -#include <lume/boot_params.h> #include <lume/console.h> #include <lume/driver.h> #include <lume/fdt.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/fs_types.h> #include <lume/irq.h> +#include <lume/kernel_map.h> #include <lume/klog.h> #include <lume/kprintf.h> #include <lume/memblock.h> +#include <lume/mount.h> +#include <lume/page_cache.h> #include <lume/pmm.h> #include <lume/random.h> #include <lume/sched.h> +#include <lume/smp.h> +#include <lume/task.h> +#include <lume/pid.h> +#include <lume/pgrp.h> +#include <lume/signal.h> +#include <lume/signal_types.h> #include <lume/trap.h> #include <lume/types.h> -#include <arch/config.h> -#include <arch/extable.h> + +// Declared by entry.S: the hart ID that won BSP election +extern "C" uint32 bsp_hart_id; + +// Linker symbol: kernel entry point (physical start address) +extern "C" char _start[]; // ============================================================ // Forward declarations for subsystem init functions. @@ -42,11 +66,20 @@ void console_init(); // 1. UART ready; kprintf/panic can output void fdt_init(uint64 fdt_paddr); // 2. Parse FDT, build device node table void pmm_init(); // 3. Buddy allocator ready void slab_init(); // 4. kmem_cache ready; operator new usable -void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir -void task_init(); // 7. PID allocator + global task list -void timer_init(); // 8. Configure timer interrupt (scheduler tick) -void sched_init(); // 9. Create BSP Idle Task, init ready queues -void syscall_init(); // 10. Register syscall jump table +void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir +void task_init(); // 6. PID allocator + global task list +void pgrp_init(); // 6.5. Process group hash table +void sched_init(); // 7. Create BSP Idle Task, init ready queues +void timer_init(); // 8. Configure timer interrupt (scheduler tick) +void syscall_init(); // 9. Register syscall jump table +void super_init(); // 10. VFS superblock registry +void ramfs_init(); // 11. RamFS filesystem registration +void vfs_init(); // 12. Mount root filesystem +void cdev_init(); // 13. Character device registry +void console_cdev_init(); // 14. Register console/null/zero cdevs +void devfs_init(); // 15. Create /dev and populate nodes +void tty_init(); // 15.5. TTY subsystem (console TTY + UART RX IRQ) +void populate_rootfs(); // 15.6. Unpack embedded initramfs CPIO into ramfs // Boot self-test runner void selftest_run_all(); @@ -56,6 +89,102 @@ void vmm_init_ap(); // Switch to BSP-built kernel page table void timer_init_ap(); // Start this CPU's timer interrupt void sched_init_ap(); // Init per-CPU ready queue, create Idle Task +// kthread_create from task.cc +extern TaskControlBlock *kthread_create(const char *name, void (*entry)(void *), + void *arg, uint8 priority); + +// ============================================================ +// PID 1 — init thread +// ============================================================ +static void init_thread_func(void *) { + printk(KERN_INFO "[init] PID 1 started\n"); + + /* Run post-SMP tests — APs are online by now */ + extern void selftest_smp(); + kprintf("\n========================================\n"); + kprintf(" Post-SMP Test Suite\n"); + kprintf("========================================\n"); + selftest_smp(); + kprintf("========================================\n"); + kprintf(" Post-SMP tests PASSED\n"); + kprintf("========================================\n\n"); + + /* + * Set up init's own VFS context (like Linux kernel_init). + * kthread_create does NOT inherit files/fs, so init creates its own. + */ + TaskControlBlock *self = current_task(); + + if (!self->files) { + self->files = alloc_files_struct(); + if (!self->files) { + printk(KERN_ERR "[init] failed to allocate files_struct\n"); + goto out; + } + } + if (!self->fs) { + self->fs = alloc_fs_struct(); + if (!self->fs) { + printk(KERN_ERR "[init] failed to allocate fs_struct\n"); + goto out; + } + /* Point to the global root filesystem */ + self->fs->root.de = g_root_dentry; + self->fs->root.mnt = g_root_mnt; + self->fs->pwd = self->fs->root; + } + + /* Open /dev/console → fd 0 (stdin), dup to fd 1, 2 */ + { + extern int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); + extern int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); + + int64 fd0 = + sys_openat(static_cast<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>("/dev/console"), O_RDWR, 0, 0, 0); + if (fd0 < 0) { + printk(KERN_ERR "[init] open /dev/console failed: %d\n", + static_cast<int>(fd0)); + } else { + sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 1 */ + sys_dup(static_cast<uint64>(fd0), 0, 0, 0, 0, 0); /* fd 2 */ + printk(KERN_INFO "[init] fd 0/1/2 -> /dev/console\n"); + } + } + + /* + * Try to exec /sbin/init (like Linux kernel_init). + * If not found, fall through to idle. + */ + { + extern int kernel_execve(const char *, const char *const *, + const char *const *); + + const char *init_paths[] = {"/sbin/init", "/etc/init", "/bin/init", + "/bin/sh", nullptr}; + const char *argv[] = {"/sbin/init", nullptr}; + const char *envp[] = {"HOME=/", "PATH=/sbin:/bin", nullptr}; + + for (int i = 0; init_paths[i]; i++) { + argv[0] = init_paths[i]; + int ret = kernel_execve(init_paths[i], argv, envp); + if (ret == 0) { + /* Success — do_execve modified our trapframe. + * When this kthread returns through the trap return path, + * it will jump to user space. */ + return; + } + printk(KERN_INFO "[init] exec %s failed: %d\n", init_paths[i], ret); + } + + printk(KERN_ERR "[init] No init found! Kernel panic.\n"); + } + +out: + extern void task_exit(int code); + task_exit(0); +} + // ============================================================ // Second-stage synchronization barrier (C++ layer) // AP cores spin here until BSP finishes all global init. @@ -67,61 +196,46 @@ static lume::atomic<uint32> ap_boot_sync; // ============================================================ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { - if (cpu_id == 0) { + if (cpu_id == bsp_hart_id) { // ======================================================== // BSP: Absolute single-core period — no locks needed. // APs are spinning on ap_boot_sync. // ======================================================== - boot_params_init_fdt(fdt_paddr); - const BootParams* bp = boot_params_get(); - uint64 fdt_pa = bp ? bp->fdt_ptr : fdt_paddr; + uint64 fdt_pa = fdt_paddr; + + // ---- Phase 1: Console + Exception Safety Net ---- - // Early console setup: discover UART via FDT before ANY output. + // 1. Early console setup: discover UART via FDT before ANY output. console_early_init(fdt_pa); klog_init(); printk("\n"); printk(KERN_INFO "LumeOS Kernel v0.1\n"); - printk(KERN_INFO "boot: BSP online, cpu_id=0\n"); + printk(KERN_INFO "boot: BSP online, cpu_id=%llu\n", cpu_id); printk(KERN_INFO "boot: higher-half kernel active\n"); - BOOT_STAGE(0, "firmware handoff"); - BOOT_STAGE(1, "bootloader handoff"); - BOOT_STAGE(2, "arch early asm complete"); - if (bp) { - if (bp->kimage_flags & BOOT_IMAGE_DECOMP_FAILED) { - BOOT_STAGE(3, "decompress failed"); - } else if (bp->kimage_flags & BOOT_IMAGE_COMPRESSED) { - BOOT_STAGE(3, "decompress ok"); - } else { - BOOT_STAGE(3, "decompress skipped"); - } - - if (bp->kimage_flags & BOOT_IMAGE_RELOC_FAILED) { - BOOT_STAGE(3, "relocate failed"); - } else if (bp->kimage_flags & BOOT_IMAGE_RELOCATED) { - BOOT_STAGE(3, "relocate ok"); - } else { - BOOT_STAGE(3, "relocate skipped"); - } - - printk(KERN_INFO "boot: kimage phys=0x%llx size=%llu reloc=%llu\n", - bp->kimage_phys_base, - static_cast<unsigned long long>(bp->kimage_size), - static_cast<unsigned long long>(bp->kimage_reloc_size)); - } else { - BOOT_STAGE(3, "decompress/relocate unknown"); - } - BOOT_STAGE(4, "early c init"); + /* kernel_map filled by arch/riscv/mm/init.cc::setup_vm before MMU + * enable; relocate_kernel has already applied .rela.dyn. */ + printk(KERN_INFO "boot: kimage phys=0x%llx virt=0x%llx size=%llu\n", + static_cast<unsigned long long>(kernel_map.phys_addr), + static_cast<unsigned long long>(kernel_map.virt_addr), + static_cast<unsigned long long>(kernel_map.size)); // --- Subsystem init DAG (strict top-down order) --- - // 1. FDT parsing — make device tree available + // 2. Trap handler — install exception vector FIRST. + // Any fault from here on gets a proper panic backtrace + // instead of a silent hang. (Linux: trap_init is in start_kernel + // right after setup_arch.) + trap_init(); + printk(KERN_INFO "init: trap_init done\n"); + + // 3. FDT parsing — make device tree available fdt_init(fdt_pa); printk(KERN_INFO "init: fdt_init done\n"); - // 2. KASLR — seed PRNG and randomise VA layout. + // 4. KASLR — seed PRNG and randomise VA layout. // MUST happen before vmm_init() builds the kernel page table. { uint64 kaslr_seed = 0; @@ -133,52 +247,122 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // Sort exception table before setting RO permissions in vmm_init() extable_init(); - // 3. Memblock allocator (Early boot PA allocator) + // ---- Phase 2: Memory Subsystem (strict cascade) ---- + + // 5. Memblock allocator (Early boot PA allocator) memblock_init(fdt_pa); printk(KERN_INFO "init: memblock_init done\n"); - // 4. Virtual memory manager — builds page table with KASLR'd bases + // 6. Virtual memory manager — builds page table with KASLR'd bases vmm_init(); printk(KERN_INFO "init: vmm_init done\n"); - // 5. Physical memory manager (Buddy) + // 7. Physical memory manager (Buddy) pmm_init(); printk(KERN_INFO "init: pmm_init done\n"); - // 6. Slab allocator — operator new usable after this + // 8. Slab allocator — operator new usable after this slab_init(); printk(KERN_INFO "init: slab_init done\n"); - // 7. FDT Phase 2 — build DeviceNode object tree (needs Slab) + // 9. Page cache (minimal hash table, stable interface for file-backed mmap) + page_cache_init(); + + // 10. FDT Phase 2 — build DeviceNode object tree (needs Slab) fdt_unflatten(); printk(KERN_INFO "init: fdt_unflatten done\n"); - // 6. Trap handler — set trap vector - trap_init(); - printk(KERN_INFO "init: trap_init done\n"); + // ---- Phase 3: Task & Scheduler (software state before hardware) ---- - // 6.5 Task management (PID allocator + global task list) + // 11. Task management (PID allocator + global task list) task_init(); + printk(KERN_INFO "init: task_init done\n"); - // 7. Hardware Drivers Discovery (Interrupt Controllers, etc) + pgrp_init(); + + sched_init(); + printk(KERN_INFO "init: sched_init done\n"); + + // ---- Phase 3.5: VFS (needs slab + sched) ---- + super_init(); + ramfs_init(); + vfs_init(); + printk(KERN_INFO "init: vfs_init done (ramfs root mounted)\n"); + + // ---- Phase 3.55: Unpack initramfs into root ---- + populate_rootfs(); + + // ---- Phase 3.6: Device nodes ---- + cdev_init(); + console_cdev_init(); + devfs_init(); + printk(KERN_INFO "init: devfs done\n"); + + // ---- Phase 4: Hardware Drivers & Interrupts ---- + + // 13. Hardware Drivers Discovery (Interrupt Controllers, etc) driver_probe_all(); printk(KERN_INFO "init: driver_probe_all done\n"); - // 8. Timer interrupt (scheduler tick source) + // 13.5. TTY subsystem — needs PLIC (driver_probe_all) for UART RX IRQ + tty_init(); + printk(KERN_INFO "init: tty_init done\n"); + + // 14. Timer interrupt (scheduler tick source) + // Safe now: scheduler is fully initialized. timer_init(); printk(KERN_INFO "init: timer_init done\n"); - // 9. Scheduler — create Idle Task 0, init ready queues - sched_init(); - printk(KERN_INFO "init: sched_init done\n"); - - // 10. Syscall jump table + // 15. Syscall jump table syscall_init(); + printk(KERN_INFO "init: syscall_init done\n"); + + // 16. SMP / IPI infrastructure + smp_init(); + arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI + arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - // 10. Boot self-tests — verify subsystem correctness + // ---- Phase 5: Self-test & Launch ---- + + // 17. Boot self-tests — verify subsystem correctness. + // Tests that create kthreads must wait() for them (freeing PIDs). selftest_run_all(); - // kernel_panic("Manual Panic Test", "triggered by developer"); // VERIFIED - // OK + + // 18. Spawn PID 1 (init) — the first persistent task. + // Like Linux's rest_init() → user_mode_thread(kernel_init). + // All test PIDs have been freed, so PID 1 is available. + TaskControlBlock *init_task = + kthread_create("init", init_thread_func, nullptr, 0); + if (init_task) { + init_task->flags |= TF_INIT; + + /* Init is session and process-group leader: sid = pgid = pid. + * Allocate signal_struct AND sighand_struct eagerly so every + * descendant (created via task_clone) inherits a real identity + * and a real lock object — signal sending must take siglock and + * cannot tolerate a null sighand. */ + init_task->signal = alloc_signal_struct(init_task->pid, init_task); + if (!init_task->signal) { + printk(KERN_ERR "boot: FAILED to allocate init signal_struct\n"); + } + init_task->sighand = alloc_sighand(); + if (!init_task->sighand) { + printk(KERN_ERR "boot: FAILED to allocate init sighand\n"); + } + + /* Publish g_init_task so reparenting paths can find the foster + * parent when other tasks exit before init takes over user space. */ + extern TaskControlBlock* g_init_task; + g_init_task = init_task; + + printk(KERN_INFO "boot: init thread created (pid=%d sid=%d pgid=%d)\n", + init_task->pid, + init_task->signal ? init_task->signal->sid : -1, + init_task->signal ? init_task->signal->pgid : -1); + } else { + printk(KERN_ERR "boot: FAILED to create init thread!\n"); + } + BOOT_STAGE(5, "core kernel init"); printk(KERN_INFO "boot: BSP init complete, releasing APs\n"); @@ -187,9 +371,37 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // guarantees all global data is visible to APs. ap_boot_sync.store(1, __ATOMIC_RELEASE); + // Start AP harts via SBI HSM extension. + // OpenSBI v1.0+ parks non-boot harts in STOPPED state. + // They must be explicitly started with sbi_hart_start(). + // + // kernel_map.phys_addr was filled by setup_vm in arch/riscv/mm/init.cc + // before MMU enable — it's the runtime PA at which the kernel was + // loaded. APs start at the same PA as the BSP. + { + uint64 start_pa = kernel_map.phys_addr; + if (start_pa == 0) { + /* Defensive fallback if setup_vm hasn't filled the field. */ + start_pa = + boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(_start))).raw; + } + uint32 num_harts = 4; // TODO: read from FDT /cpus + for (uint32 h = 0; h < num_harts; h++) { + if (h == static_cast<uint32>(cpu_id)) + continue; // skip BSP + auto ret = arch::sbi::sbi_hart_start(h, start_pa, fdt_paddr); + if (ret.error == arch::sbi::SBI_SUCCESS) { + printk(KERN_INFO "smp: started hart %u at PA 0x%llx\n", h, start_pa); + } else { + printk(KERN_ERR "smp: failed to start hart %u (err=%ld)\n", h, + ret.error); + } + } + } + // BSP enables interrupts — first timer tick will preempt - // into the scheduler loop. - // arch::cpu::intr_on(); + // into the scheduler loop, which will pick up PID 1. + arch::cpu::intr_on(); printk(KERN_INFO "boot: BSP entering idle loop\n"); @@ -197,22 +409,59 @@ extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { // ======================================================== // AP: spin until BSP finishes global init // ======================================================== + // DEBUG (commented out — uncomment for SMP debugging): + // arch::sbi::sbi_ecall(1, 0, '1'); // AP entered kernel_main + while (ap_boot_sync.load(__ATOMIC_ACQUIRE) == 0) ; + // arch::sbi::sbi_ecall(1, 0, '2'); // AP passed ap_boot_sync + + // --- Per-CPU init (must happen before any printk!) --- + // early_pgdir doesn't map UART MMIO, so we MUST switch to + // BSP's kernel page table before any console output. + vmm_init_ap(); // Switch to BSP-built kernel page table + + // arch::sbi::sbi_ecall(1, 0, '3'); // vmm_init_ap done + + trap_init_ap(); // Install stvec on this CPU + + // arch::sbi::sbi_ecall(1, 0, '4'); // trap_init_ap done + printk(KERN_INFO "boot: AP online, cpu_id=%llu\n", cpu_id); - // --- Per-CPU init only (no global data mutation) --- - // vmm_init_ap(); - // arch_irq_chip_init_ap(); - // timer_init_ap(); - // sched_init_ap(); + sched_init_ap(); // Init per-CPU RunQueue, create Idle Task + + // arch::sbi::sbi_ecall(1, 0, '5'); // sched_init_ap done + + timer_init_ap(); // Start this CPU's timer interrupt + arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI + arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - // AP enables interrupts — becomes Idle Task on this CPU - // arch::cpu::intr_on(); + // AP is now a fully functional scheduler CPU. + // Enable interrupts — first timer tick preempts into idle loop. + arch::cpu::intr_on(); + + printk(KERN_INFO "boot: AP cpu_id=%llu fully initialized\n", cpu_id); + } + + // All CPUs reach here. + // The "current task" is the boot stub (bsp_task or ap_task, pid=-1). + // We must hand off to the scheduler's idle task by calling schedule(). + // Without this, the BSP runs as bsp_task (O1 class, timeslice=20 ticks) + // instead of the idle task (which yields immediately when any task is + // runnable). That delays PID 1 by up to 200ms and can stall entirely + // if no timer tick fires before the first WFI. + { + TaskControlBlock *self = current_task(); + if (self) { + self->state = TaskState::Dead; // Boot stub is done; don't re-enqueue + } + schedule(); // Switch to idle task (or init if it's already runnable) } - // All CPUs: idle loop (wfi until interrupt) + // Should not reach here — schedule() switches away permanently. + // But just in case, spin with WFI as a safety net. while (true) { arch::cpu::halt_until_interrupt(); } diff --git a/na/kernel/module.mk b/na/kernel/module.mk index 662f57e..754a14c 100644 --- a/na/kernel/module.mk +++ b/na/kernel/module.mk @@ -1,17 +1,26 @@ # kernel/module.mk SRCS_CC += kernel/main.cc -SRCS_CC += kernel/boot_params.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 new file mode 100644 index 0000000..e5b382c --- /dev/null +++ b/na/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 <lume/pgrp.h> +#include <lume/signal_types.h> +#include <lume/task.h> +#include <lume/new.h> +#include <lume/klog.h> + +/* ================================================================== */ +/* 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<signal_struct, &signal_struct::pgrp_link>(node); + if (sig->pgid == pgid) n++; + } + return n; +} diff --git a/na/kernel/pid.cc b/na/kernel/pid.cc index 81b9494..04b846b 100644 --- a/na/kernel/pid.cc +++ b/na/kernel/pid.cc @@ -12,8 +12,10 @@ void PidAllocator::init() { for (int i = 0; i < kBitmapWords; i++) bitmap_[i] = 0; - next_hint_ = 2; - bitmap_[0] |= 0x3ULL; /* Reserve PID 0 (idle) and 1 (init) */ + 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() diff --git a/na/kernel/poll.cc b/na/kernel/poll.cc new file mode 100644 index 0000000..3674491 --- /dev/null +++ b/na/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 <lume/poll.h> +#include <lume/file.h> +#include <lume/fdtable.h> +#include <lume/task.h> +#include <lume/sched.h> +#include <lume/timer.h> +#include <lume/waitqueue.h> +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/uaccess.h> +#include <lume/errno.h> +#include <lume/klog.h> +#include <lume/new.h> + +/* ================================================================== */ +/* 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<uint64>(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<uint32>(fds[i].events) | always; + fds[i].revents = static_cast<int16>(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<uint32>(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<uint64>(nfds) * sizeof(pollfd); + int64 ret = 0; + + if (nfds > 0) { + kfds = static_cast<pollfd*>(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 new file mode 100644 index 0000000..3280c07 --- /dev/null +++ b/na/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 <lume/ptrace.h> +#include <arch/trapframe.h> + +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 index a8ba5df..28c693c 100644 --- a/na/kernel/sched_class_o1.cc +++ b/na/kernel/sched_class_o1.cc @@ -44,8 +44,13 @@ int32 priority_to_timeslice(uint8 priority) static void o1_task_tick(RunQueue* rq, TaskControlBlock* curr) { - if (curr == rq->idle()) + 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) { diff --git a/na/kernel/sched_core.cc b/na/kernel/sched_core.cc index 85248ef..c0e068e 100644 --- a/na/kernel/sched_core.cc +++ b/na/kernel/sched_core.cc @@ -13,11 +13,15 @@ #include <lume/sched_class.h> #include <lume/task.h> #include <lume/pmm.h> +#include <lume/vmm.h> #include <lume/addr.h> #include <lume/new.h> #include <lume/kprintf.h> #include <lume/klog.h> +#include <lume/smp.h> #include <arch/cpu.h> +#include <arch/mmu.h> +#include <arch/trap.h> #include <arch/context.h> #include <kernel/sched_internal.h> @@ -37,11 +41,15 @@ static const SchedClass* g_sched_class_head = &g_sched_class_o1; 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) * ======================================================================== */ @@ -108,6 +116,9 @@ static const char* idle_name_for_cpu(uint32 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(); @@ -117,74 +128,38 @@ extern "C" void kthread_entry(void (*entry)(void*), void* arg) entry(arg); - kprintf("[sched] kthread exited\n"); - while (true) { - arch::cpu::halt_until_interrupt(); - } + /* 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(); - /* Do NOT enable interrupts here! userret expects them disabled. */ - - /* Jump to user space */ + /* Set stvec to trap_vector for user-mode traps, then return to user */ + arch::trap::set_vector(reinterpret_cast<uint64>(trap_vector)); userret(tf); __builtin_unreachable(); } /* ======================================================================== - * Helper: create a TCB with a kernel stack + * 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. * ======================================================================== */ -static TaskControlBlock* create_tcb(const char* name, uint8 priority, - uint8 flags, uint8 cpu) -{ - auto* task = new TaskControlBlock; - if (!task) return nullptr; - - task->context = {}; - - 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; - - *reinterpret_cast<uint64*>(task->kstack_va) = kStackCanary; - - 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->parent = nullptr; - task->mm = nullptr; - task->trapframe = nullptr; - - task->run_link.init(); - task->all_link.init(); - task->children.init(); - task->sibling.init(); - task->wait_chld.init("wait_chld"); - - task->sched_class = &g_sched_class_o1; - task->sched_entity = &task->run_link; - - return task; -} +extern TaskControlBlock* alloc_task(const char* name, uint8 priority, + uint8 flags, uint8 cpu); /* ======================================================================== * Public API: current_task() @@ -212,6 +187,10 @@ void sched_enqueue(TaskControlBlock* task) } 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) @@ -262,6 +241,52 @@ void schedule() 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<uint32>(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(); @@ -274,24 +299,37 @@ void schedule() void sched_tick() { RunQueue* rq = this_rq(); - LockGuard guard(rq->lock()); - - TaskControlBlock* curr = rq->current(); - if (!curr) return; + { + LockGuard guard(rq->lock()); + TaskControlBlock* curr = rq->current(); + if (!curr) return; + curr->sched_class->task_tick(rq, curr); + } - curr->sched_class->task_tick(rq, curr); + /* Periodic load balancing — runs without holding rq lock */ + sched_balance(); } /* ======================================================================== - * sched_check_preempt() — called before returning to user mode + * 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()) { - schedule(); + 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(); } /* ======================================================================== @@ -328,32 +366,37 @@ void sched_init() g_sched_class_head = &g_sched_class_o1; - TaskControlBlock* idle0 = create_tcb(idle_name_for_cpu(0), kIdlePriority, - TF_KTHREAD | TF_IDLE, 0); - if (!idle0) kernel_panic("sched_init: failed to create idle/0"); + uint64 bsp_cpu = arch::cpu::id(); + uint8 bsp_cpu8 = static_cast<uint8>(bsp_cpu); + + TaskControlBlock* idle0 = alloc_task(idle_name_for_cpu(static_cast<uint32>(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; + uint64 stack_top = idle0->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); idle0->context = {}; idle0->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline); idle0->context.sp = stack_top; idle0->context.s[0] = reinterpret_cast<uint64>(idle_thread_func); idle0->context.s[1] = 0; - g_rq[0].set_idle(idle0); + g_rq[bsp_cpu].set_idle(idle0); - TaskControlBlock* bsp_task = create_tcb("kernel_main", 0, TF_KTHREAD, 0); + 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[0].set_current(bsp_task); + g_rq[bsp_cpu].set_current(bsp_task); } /* ======================================================================== @@ -366,7 +409,7 @@ void sched_init_ap() const char* name = idle_name_for_cpu(static_cast<uint32>(cpu)); - TaskControlBlock* idle = create_tcb(name, kIdlePriority, + TaskControlBlock* idle = alloc_task(name, kIdlePriority, TF_KTHREAD | TF_IDLE, static_cast<uint8>(cpu)); if (!idle) kernel_panic("sched_init_ap: failed to create idle"); @@ -376,7 +419,8 @@ void sched_init_ap() idle->sched_class = &g_sched_class_idle; idle->sched_entity = nullptr; - uint64 stack_top = idle->kstack_va + kKernelStackSize; + uint64 stack_top = idle->kstack_va + kKernelStackSize + - sizeof(arch::TrapFrame); idle->context = {}; idle->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline); idle->context.sp = stack_top; @@ -385,7 +429,7 @@ void sched_init_ap() g_rq[cpu].set_idle(idle); - TaskControlBlock* ap_task = create_tcb("ap_boot", 0, TF_KTHREAD, + TaskControlBlock* ap_task = alloc_task("ap_boot", 0, TF_KTHREAD, static_cast<uint8>(cpu)); if (!ap_task) kernel_panic("sched_init_ap: failed to create ap_task"); @@ -395,3 +439,133 @@ void sched_init_ap() 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<uint32>(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<uint8>(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/signal.cc b/na/kernel/signal.cc new file mode 100644 index 0000000..53d26da --- /dev/null +++ b/na/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 <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/sched.h> +#include <lume/wait.h> +#include <lume/console.h> +#include <lume/kprintf.h> +#include <lume/klog.h> +#include <lume/new.h> +#include <lume/errno.h> +#include <arch/trapframe.h> + +/* 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<sigqueue, &sigqueue::list>(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<sigqueue, &sigqueue::list>(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<uint64>(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 new file mode 100644 index 0000000..1a79ac3 --- /dev/null +++ b/na/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 <lume/smp.h> +#include <lume/klog.h> +#include <lume/sched.h> +#include <arch/cpu.h> +#include <arch/mmu.h> +#include <arch/sbi.h> + +/* 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<unsigned>(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<uint32>(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/mutex.cc b/na/kernel/sync/mutex.cc new file mode 100644 index 0000000..c4d3e9d --- /dev/null +++ b/na/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 <lume/mutex.h> +#include <lume/task.h> +#include <lume/sched.h> +#include <lume/klog.h> +#include <arch/cpu.h> + +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<MutexWaiter, &MutexWaiter::link>(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 index bca4684..f649fe7 100644 --- a/na/kernel/sync/spinlock.cc +++ b/na/kernel/sync/spinlock.cc @@ -17,6 +17,7 @@ #include "cpu_state.h" #include <arch/cpu.h> #include <lume/klog.h> +#include <lume/task.h> // ============================================================ // Per-CPU sync state (BSS zero-initialized) @@ -88,6 +89,10 @@ void Spinlock::acquire() { // 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() { @@ -102,7 +107,11 @@ void Spinlock::release() { // Step 3: release the lock with release semantics locked_.store(0, __ATOMIC_RELEASE); - // Step 4: restore interrupt state if this was the last lock + // 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(); } diff --git a/na/kernel/sys_fs.cc b/na/kernel/sys_fs.cc new file mode 100644 index 0000000..1d26fdd --- /dev/null +++ b/na/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 <lume/syscall.h> +#include <lume/syscall_nr.h> +#include <lume/errno.h> +#include <lume/console.h> +#include <lume/task.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/dcache.h> +#include <lume/namei.h> +#include <lume/fdtable.h> +#include <lume/fs_struct.h> +#include <lume/mount.h> +#include <lume/fs_types.h> +#include <lume/klog.h> + +/* ================================================================== */ +/* 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<int>(a0); + const char* pathname = reinterpret_cast<const char*>(a1); + int flags = static_cast<int>(a2); + uint32 mode = static_cast<uint32>(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<int>(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<int>(a0); + char* buf = reinterpret_cast<char*>(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<int>(a0); + const char* buf = reinterpret_cast<const char*>(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<int>(a0); + int64 offset = static_cast<int64>(a1); + int whence = static_cast<int>(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<int>(a0); + auto* statbuf = reinterpret_cast<kstat*>(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<int>( + (sizeof(linux_dirent64) + namelen + 1 + 7) & ~7ULL); + + if (gctx->written + reclen > gctx->count) { + return false; /* Buffer full */ + } + + auto* dirent = reinterpret_cast<linux_dirent64*>( + gctx->buf + gctx->written); + + dirent->d_ino = ino; + dirent->d_off = offset + 1; + dirent->d_reclen = static_cast<uint16>(reclen); + dirent->d_type = static_cast<uint8>(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<int>(a0); + char* buf = reinterpret_cast<char*>(a1); + int count = static_cast<int>(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<int>(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<int>(a0); + int newfd = static_cast<int>(a1); + int flags = static_cast<int>(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<char*>(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<uint64>(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<const char*>(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<int>(a0); + const char* pathname = reinterpret_cast<const char*>(a1); + uint32 mode = static_cast<uint32>(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<int>(a0); + const iovec* iov = reinterpret_cast<const iovec*>(a1); + int iovcnt = static_cast<int>(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<const char*>(iov[i].iov_base), + iov[i].iov_len, &fp->f_pos); + if (ret < 0) { + return total > 0 ? total : ret; + } + total += ret; + if (static_cast<uint64>(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<int>(a0); + unsigned int cmd = static_cast<unsigned int>(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 <lume/pipe.h> + +int64 sys_pipe2(uint64 a0, uint64 a1, uint64 /*a2*/, + uint64 /*a3*/, uint64 /*a4*/, uint64 /*a5*/) +{ + int* user_fds = reinterpret_cast<int*>(a0); + int flags = static_cast<int>(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<int>(a0); + int cmd = static_cast<int>(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<unsigned int>(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<unsigned int>(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_misc.cc b/na/kernel/sys_misc.cc new file mode 100644 index 0000000..48eb47b --- /dev/null +++ b/na/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 <lume/types.h> +#include <lume/task.h> +#include <lume/errno.h> +#include <lume/kprintf.h> +#include <lume/klog.h> +#include <lume/pid.h> +#include <lume/pgrp.h> +#include <lume/signal_types.h> +#include <lume/tty.h> +#include <lume/uaccess.h> +#include <arch/cpu.h> + +/* ====================================================================== + * 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<uint32>(magic1) != LINUX_REBOOT_MAGIC1) + return -EINVAL; + if (static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2 && + static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2A && + static_cast<uint32>(magic2) != LINUX_REBOOT_MAGIC2B && + static_cast<uint32>(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 <linux/prctl.h>) */ +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<int>(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<TaskControlBlock, &TaskControlBlock::all_link>(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<int32>(pid); + int32 target_pgid = static_cast<int32>(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<int32>(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<int32>(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<int64>(sec); + ts.tv_nsec = static_cast<int64>(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 index ff03e96..bfa067c 100644 --- a/na/kernel/sys_mm.cc +++ b/na/kernel/sys_mm.cc @@ -15,6 +15,7 @@ #include <lume/errno.h> #include <lume/mman.h> #include <lume/config.h> +#include <lume/klog.h> /* Page alignment helpers */ @@ -78,6 +79,9 @@ 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) { + printk("[mmap] addr=%llx len=%llx prot=%llx flags=%llx fd=%lld off=%llx\n", + addr, length, prot, flags, static_cast<int64>(fd), offset); + if (length == 0) return -EINVAL; length = page_align_up(length); if (offset % kPageSize != 0) return -EINVAL; @@ -89,8 +93,10 @@ int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, if (!mm) return -ENOMEM; /* Phase 2: only anonymous private mappings */ - if (!(flags & MAP_ANONYMOUS)) + 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 */ diff --git a/na/kernel/sys_process.cc b/na/kernel/sys_process.cc index 1759a81..438ad1b 100644 --- a/na/kernel/sys_process.cc +++ b/na/kernel/sys_process.cc @@ -11,6 +11,7 @@ * Reference: docs/specs/syscall.md §7.1 */ +#include "lume/klog.h" #include <lume/types.h> #include <lume/task.h> #include <lume/sched.h> @@ -20,8 +21,10 @@ /* External: task lifecycle (kernel/task.cc) */ extern void task_exit(int code); -extern void task_destroy(TaskControlBlock* task); -extern int32 task_wait(int32* exit_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); /* ====================================================================== @@ -75,14 +78,10 @@ int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64) int64 sys_clone_wrapper(uint64 flags, uint64 child_stack, uint64 ptid, uint64 ctid, uint64 tls, uint64) { - /* Phase A: Simple fork() support. - * Ignore flags/stack for now and just clone the current task. */ - (void)flags; (void)child_stack; (void)ptid; (void)ctid; (void)tls; - arch::TrapFrame* tf = current_task()->trapframe; if (!tf) return -EINVAL; - return task_fork(tf); + return task_clone(tf, flags, child_stack, ptid, ctid, tls); } /* ======================================================================== @@ -92,23 +91,16 @@ int64 sys_clone_wrapper(uint64 flags, uint64 child_stack, int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr, uint64 options, uint64, uint64, uint64) { - /* Phase A: Basic wait() support. - * TODO: Implement pid-specific wait and WNOHANG in task_wait. */ - (void)pid; (void)options; - int32 exit_code = 0; - int32 child_pid = task_wait(&exit_code); + int32 child_pid = do_wait4(static_cast<int32>(pid), + static_cast<int>(options), + &exit_code); if (child_pid < 0) return child_pid; + /* exit_code is already in Linux wstatus format */ if (wstatus_ptr != 0) { - int wstatus = 0; - if (exit_code & kExitCodeSignalFlag) { - wstatus = exit_code & 0x7F; - } else { - wstatus = W_EXITCODE(exit_code); - } - if (copy_to_user(wstatus_ptr, &wstatus, sizeof(int)) < 0) + if (copy_to_user(wstatus_ptr, &exit_code, sizeof(int)) < 0) return -EFAULT; } @@ -122,9 +114,9 @@ int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr, int64 sys_set_tid_address(uint64 tidptr, uint64, uint64, uint64, uint64, uint64) { - /* Phase 2: store the clear_child_tid pointer (needed for futex wakeup). - * For now just return current tid. */ - (void)tidptr; + /* 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; } @@ -141,3 +133,53 @@ int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64) { return 0; /* root */ } + +/* ====================================================================== + * sys_nanosleep (SYS_nanosleep = 101) + * ====================================================================== */ +#include <lume/wait.h> +#include <lume/kprintf.h> +#include <lume/timer.h> + +struct linux_timespec { + int64 tv_sec; + int64 tv_nsec; +}; + +static void nanosleep_wakeup(TimerList* t) { + TaskControlBlock* task = static_cast<TaskControlBlock*>(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 new file mode 100644 index 0000000..8b459d5 --- /dev/null +++ b/na/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 <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/errno.h> +#include <lume/uaccess.h> +#include <lume/kprintf.h> +#include <lume/klog.h> +#include <lume/new.h> +#include <lume/pgrp.h> + +/* 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<TaskControlBlock, &TaskControlBlock::all_link>(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<int32>(a0); + int sig = static_cast<int>(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<signal_struct, + &signal_struct::pgrp_link>(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<int32>(a0), + static_cast<int>(a1)); +} + +int64 sys_tgkill(uint64 a0, uint64 a1, uint64 a2, uint64, uint64, uint64) +{ + return do_tkill(static_cast<int32>(a0), + static_cast<int32>(a1), + static_cast<int>(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<int>(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<int>(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 <lume/timer.h> +#include <lume/sched.h> + +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 index 4dfbb5d..a5b80ec 100644 --- a/na/kernel/syscall.cc +++ b/na/kernel/syscall.cc @@ -15,6 +15,8 @@ #include <lume/errno.h> #include <lume/kprintf.h> #include <lume/klog.h> +#include <lume/console.h> +#include <arch/cpu.h> /* ====================================================================== * Jump table @@ -30,12 +32,23 @@ static SyscallHandler sys_call_table[kNrSyscalls]; int64 syscall_dispatch(uint64 nr, uint64 args[6]) { - if (nr >= static_cast<uint64>(kNrSyscalls)) + if (nr >= static_cast<uint64>(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) + 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]); @@ -57,6 +70,7 @@ 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); @@ -67,6 +81,52 @@ 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 * ====================================================================== */ @@ -95,5 +155,54 @@ void syscall_init() sys_call_table[SYS_mprotect] = sys_mprotect; sys_call_table[SYS_uname] = sys_uname; - printk(KERN_INFO "init: syscall_init done (16 handlers registered)\n"); + /* 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 index 9f09d62..9f9b043 100644 --- a/na/kernel/task.cc +++ b/na/kernel/task.cc @@ -20,8 +20,18 @@ #include <lume/vmm.h> #include <lume/errno.h> #include <lume/pid.h> +#include <lume/wait.h> +#include <lume/clone.h> +#include <lume/signal_nr.h> #include <arch/cpu.h> #include <arch/context.h> +#include <lume/uaccess.h> +#include <lume/fs_struct.h> +#include <lume/fdtable.h> +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/pgrp.h> +#include <arch/pmap.h> #include <kernel/sync/spinlock.h> /* ====================================================================== @@ -57,11 +67,16 @@ void task_init() } /* ====================================================================== - * Internal: allocate TCB + kernel stack + * 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. * ====================================================================== */ -static TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags) +TaskControlBlock* alloc_task(const char* name, uint8 priority, + uint8 flags, uint8 cpu) { auto* task = new TaskControlBlock; if (!task) return nullptr; @@ -80,23 +95,45 @@ static TaskControlBlock* alloc_task(const char* name, uint8 priority, /* Write stack canary at bottom */ *reinterpret_cast<uint64*>(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<arch::TrapFrame*>(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 = static_cast<uint8>(arch::cpu::id()); + 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->trapframe = 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 */ @@ -143,7 +180,8 @@ TaskControlBlock* kthread_create(const char* name, void* arg, uint8 priority) { - TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD); + TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD, + static_cast<uint8>(arch::cpu::id())); if (!task) return nullptr; /* Allocate PID */ @@ -156,19 +194,30 @@ TaskControlBlock* kthread_create(const char* name, } task->tgid = task->pid; - /* Set up context for kthread_entry_trampoline */ - uint64 stack_top = task->kstack_va + kKernelStackSize; + /* 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<uint64>(kthread_entry_trampoline); task->context.sp = stack_top; task->context.s[0] = reinterpret_cast<uint64>(entry); task->context.s[1] = reinterpret_cast<uint64>(arg); - /* Parent is kernel_task (PID 0) */ - task->parent = g_kernel_task; - if (g_kernel_task) { - LockGuard guard(g_task_list_lock); - list_node* head = &g_kernel_task->children; + /* 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; @@ -191,18 +240,32 @@ void task_exit(int code) TaskControlBlock* curr = current_task(); if (!curr) kernel_panic("task_exit: no current task"); - curr->exit_code = code; + /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */ + curr->exit_code = (code & 0xFF) << 8; - /* Release user address space if we're the last reference */ - if (curr->mm) { - curr->mm->destroy(); + /* 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(g_task_list_lock); + LockGuard guard(curr->lock); list_node* child_node = curr->children.next; while (child_node != &curr->children) { @@ -226,7 +289,46 @@ void task_exit(int code) curr->children.init(); } - /* Transition to Zombie — TCB + kstack preserved for parent wait() */ + + /* ---- 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 */ @@ -242,8 +344,55 @@ void task_exit(int code) void task_kill_current(int sig) { - int code = (sig & 0x7F) | kExitCodeSignalFlag; - task_exit(code); + 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<TaskControlBlock, + &TaskControlBlock::sibling>(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(); } /* ====================================================================== @@ -263,6 +412,19 @@ void task_destroy(TaskControlBlock* task) /* 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); @@ -273,61 +435,155 @@ void task_destroy(TaskControlBlock* task) } /* ====================================================================== - * task_wait — sleep until a child exits, then reclaim it - * Returns PID of exited child, or -ECHILD if no children exist. + * 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 task_wait(int32* exit_code) +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(); - /* Look for a Zombie child */ - g_task_list_lock.acquire(); if (curr->children.is_empty()) { - g_task_list_lock.release(); + 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<TaskControlBlock, &TaskControlBlock::sibling>(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; } - g_task_list_lock.release(); + curr->lock.release(); if (zombie) { + curr->wait_chld.finish_wait(curr); int32 pid = zombie->pid; - if (exit_code) { - /* In a real kernel, we copy_to_user */ - *exit_code = zombie->exit_code; - } + if (exit_code) *exit_code = zombie->exit_code; task_destroy(zombie); return pid; } - /* Sleep if no child has exited yet */ - curr->wait_chld.sleep(&g_task_list_lock); + 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_fork — clone current task + * 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_fork(arch::TrapFrame* tf) +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("forked", curr->priority, 0); + TaskControlBlock* child = alloc_task("cloned", curr->priority, 0, + static_cast<uint8>(arch::cpu::id())); if (!child) return -ENOMEM; /* Allocate PID */ @@ -337,16 +593,35 @@ int32 task_fork(arch::TrapFrame* tf) delete child; return -ENOMEM; } - child->tgid = child->pid; - /* Copy VmSpace */ - 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; + /* 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<int32>(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; + } } } @@ -355,20 +630,94 @@ int32 task_fork(arch::TrapFrame* tf) child->trapframe = reinterpret_cast<arch::TrapFrame*>(tf_addr); *child->trapframe = *tf; - /* Child returns 0 from fork */ + /* Set Kernel SP */ + child->trapframe->kernel_sp = reinterpret_cast<uint64>(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; // Context goes below TrapFrame conceptually, but we just set SP + uint64 stack_top = tf_addr; child->context = {}; child->context.ra = reinterpret_cast<uint64>(ret_from_fork); child->context.sp = stack_top; - child->context.s[0] = tf_addr; /* Passed to a0 in ret_from_fork */ + 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(g_task_list_lock); + LockGuard guard(curr->lock); list_node* head = &curr->children; child->sibling.prev = head->prev; child->sibling.next = head; @@ -380,9 +729,41 @@ int32 task_fork(arch::TrapFrame* tf) 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 diff --git a/na/kernel/timer.cc b/na/kernel/timer.cc index 01e8f9d..3cf03cf 100644 --- a/na/kernel/timer.cc +++ b/na/kernel/timer.cc @@ -7,10 +7,72 @@ #include <arch/cpu.h> #include <arch/sbi.h> +#include <kernel/sync/spinlock.h> + 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<TimerList, &TimerList::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<TimerList, &TimerList::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); } @@ -24,21 +86,33 @@ void timer_init() { // 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() { - g_jiffies++; + 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 index 738b2db..2116d91 100644 --- a/na/kernel/trap.cc +++ b/na/kernel/trap.cc @@ -15,6 +15,7 @@ * Reference: docs/specs/trap.md §4 */ +#include <arch/cpu.h> #include <lume/trap.h> #include <lume/irq.h> #include <lume/console.h> @@ -26,8 +27,11 @@ #include <lume/syscall.h> #include <lume/task.h> #include <lume/vmm.h> +#include <lume/signal.h> #include <lume/signal_nr.h> +#include <lume/smp.h> #include <arch/trapframe.h> /* TrapFrame: cross-layer data contract */ +#include <arch/extable.h> /* search_extable: kernel fault recovery */ /* ======================================================================== * trap_handle() — MI-layer dispatcher for traps @@ -39,22 +43,18 @@ static TrapStats g_trap_stats = {}; static uint32 trap_cause_index(TrapCause cause) { - switch (cause) { - case TrapCause::PageFaultLoad: return 0; - case TrapCause::PageFaultStore: return 1; - case TrapCause::PageFaultExec: return 2; - case TrapCause::Syscall: return 3; - case TrapCause::IllegalInst: return 4; - case TrapCause::Breakpoint: return 5; - case TrapCause::AccessFault: return 6; - case TrapCause::AlignFault: return 7; - case TrapCause::IrqTimer: return 8; - case TrapCause::IrqSoftware: return 9; - case TrapCause::IrqExternal: return 10; - case TrapCause::Unknown: - default: - return 11; + uint32 raw = static_cast<uint32>(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) @@ -100,10 +100,33 @@ 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) { - kprintf("[trap] kernel exception\n"); - kernel_panic("kernel exception"); + + /* 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<unsigned>(info.cause), info.epc, info.val); + kernel_panic("unrecoverable kernel exception"); + } } switch (info.cause) { @@ -120,17 +143,43 @@ void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) int ret = vmm_handle_page_fault(curr->mm, info.val, vm_fault_cause_from_trap(info.cause)); if (ret == -EFAULT) { - task_kill_current(SIGSEGV); + 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) { - task_kill_current(SIGKILL); + 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) { - task_kill_current(SIGSEGV); + 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; } - kprintf("[trap] kernel page fault\n"); - kernel_panic("unhandled page fault"); + /* 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<int>(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 ---- */ @@ -155,14 +204,20 @@ void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) } case TrapCause::IrqSoftware: - /* IPI handling — Phase 2 stub */ + /* S-mode software interrupt = IPI from another hart */ + smp_handle_ipi(); break; /* ---- Fatal exceptions ---- */ case TrapCause::IllegalInst: - kprintf("[trap] illegal instruction\n"); - /* Phase 2: panic. Phase 3: task_kill(SIGILL) */ - kernel_panic("illegal instruction in user mode"); + 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: @@ -187,8 +242,17 @@ void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) break; } - /* Check if rescheduling is needed before returning to user mode */ + /* 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 index 4a5b963..14c5fb7 100644 --- a/na/kernel/uaccess.cc +++ b/na/kernel/uaccess.cc @@ -15,25 +15,11 @@ #include <lume/task.h> #include <lume/errno.h> #include <lume/config.h> - -/* Kernel VA base — addresses at or above this are kernel space */ -inline constexpr uint64 kKernelVABase = 0xFFFFFFC000000000ULL; +#include <lume/mm_layout.h> 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); -/* Validate that [addr, addr+len) is entirely in user space */ -static bool is_user_range(uint64 addr, uint64 len) -{ - if (addr >= kKernelVABase) - return false; - if (addr + len < addr) /* overflow */ - return false; - if (addr + len > kKernelVABase) - return false; - return true; -} - int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len) { if (len == 0) return 0; @@ -56,9 +42,9 @@ int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len) /* 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; + 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; } diff --git a/na/kernel/waitqueue.cc b/na/kernel/waitqueue.cc index 9ee3f46..9689417 100644 --- a/na/kernel/waitqueue.cc +++ b/na/kernel/waitqueue.cc @@ -4,11 +4,18 @@ /* * WaitQueue — blocking synchronization implementation * - * sleep(): current task → Sleeping, link into queue, schedule() - * wake_one(): dequeue head task → Runnable, sched_enqueue() + * 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 the spinlock to avoid + * schedule() is called AFTER releasing all spinlocks to avoid * holding a lock across a context switch. */ @@ -18,40 +25,80 @@ #include <lume/kprintf.h> #include <arch/cpu.h> -void WaitQueue::sleep(Spinlock* external_lock) -{ - TaskControlBlock* curr = current_task(); - if (!curr) { - if (external_lock) external_lock->release(); - return; - } +/* ------------------------------------------------------------------ */ +/* Two-phase wait API */ +/* ------------------------------------------------------------------ */ - { - LockGuard guard(lock_); - - /* Mark task as sleeping */ - curr->state = TaskState::Sleeping; +void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state) +{ + LockGuard guard(lock_); - /* Remove from RunQueue (sched_dequeue acquires its own lock) */ - /* We do NOT call sched_dequeue here because schedule() handles - * the put_prev_task logic when state != Running. */ + /* 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 = &curr->run_link; + 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(); } - /* Context switch away — will return here when woken */ + /* 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_); diff --git a/na/lib/decompress_lz4.cc b/na/lib/decompress_lz4.cc deleted file mode 100644 index 6a93936..0000000 --- a/na/lib/decompress_lz4.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/decompress.h> - -int lz4_decompress(const uint8* src, uint32 src_len, uint8* dst, uint32 dst_cap) -{ - const uint8* ip = src; - const uint8* iend = src + src_len; - uint8* op = dst; - uint8* oend = dst + dst_cap; - - while (ip < iend) { - uint8 token = *ip++; - uint32 lit_len = token >> 4; - - if (lit_len == 15) { - uint8 s = 0; - do { - if (ip >= iend) { - return -1; - } - s = *ip++; - lit_len += s; - } while (s == 255); - } - - if (ip + lit_len > iend) { - return -1; - } - if (op + lit_len > oend) { - return -1; - } - - for (uint32 i = 0; i < lit_len; ++i) { - *op++ = *ip++; - } - - if (ip >= iend) { - break; - } - if (ip + 2 > iend) { - return -1; - } - - uint32 off = static_cast<uint32>(ip[0]) | (static_cast<uint32>(ip[1]) << 8); - ip += 2; - if (off == 0 || off > static_cast<uint32>(op - dst)) { - return -1; - } - - uint32 match_len = token & 0x0F; - if (match_len == 15) { - uint8 s = 0; - do { - if (ip >= iend) { - return -1; - } - s = *ip++; - match_len += s; - } while (s == 255); - } - match_len += 4; - - if (op + match_len > oend) { - return -1; - } - - uint8* match = op - off; - for (uint32 i = 0; i < match_len; ++i) { - op[i] = match[i]; - } - op += match_len; - } - - return static_cast<int>(op - dst); -} diff --git a/na/lib/fdt.c b/na/lib/fdt.c new file mode 100644 index 0000000..1b20383 --- /dev/null +++ b/na/lib/fdt.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt.c" diff --git a/na/lib/fdt_addresses.c b/na/lib/fdt_addresses.c new file mode 100644 index 0000000..d58f896 --- /dev/null +++ b/na/lib/fdt_addresses.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_addresses.c" diff --git a/na/lib/fdt_empty_tree.c b/na/lib/fdt_empty_tree.c new file mode 100644 index 0000000..37f8ef8 --- /dev/null +++ b/na/lib/fdt_empty_tree.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_empty_tree.c" diff --git a/na/lib/fdt_ro.c b/na/lib/fdt_ro.c new file mode 100644 index 0000000..3599d49 --- /dev/null +++ b/na/lib/fdt_ro.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_ro.c" diff --git a/na/lib/fdt_rw.c b/na/lib/fdt_rw.c new file mode 100644 index 0000000..65d7a71 --- /dev/null +++ b/na/lib/fdt_rw.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_rw.c" diff --git a/na/lib/fdt_strerror.c b/na/lib/fdt_strerror.c new file mode 100644 index 0000000..dae35ea --- /dev/null +++ b/na/lib/fdt_strerror.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_strerror.c" diff --git a/na/lib/fdt_sw.c b/na/lib/fdt_sw.c new file mode 100644 index 0000000..d04f220 --- /dev/null +++ b/na/lib/fdt_sw.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_sw.c" diff --git a/na/lib/fdt_wip.c b/na/lib/fdt_wip.c new file mode 100644 index 0000000..ebfb3c1 --- /dev/null +++ b/na/lib/fdt_wip.c @@ -0,0 +1,2 @@ +#include <lume/libfdt_env.h> +#include "../scripts/dtc/libfdt/fdt_wip.c" diff --git a/na/lib/klog.cc b/na/lib/klog.cc index bf081de..86c67f8 100644 --- a/na/lib/klog.cc +++ b/na/lib/klog.cc @@ -166,9 +166,7 @@ static void ring_drop_oldest(uint32 needed) static void console_emit(const char* s, int len) { - for (int i = 0; i < len; ++i) { - early_putc(s[i]); - } + console_write_all(s, len); } static int klog_format_line(char* out, int out_size, @@ -329,12 +327,20 @@ static void klog_vex(KlogLevel level, KlogSubsys subsys, 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); + } } - } - - if (do_console) { + } 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); + int line_len = klog_format_line(line, sizeof(line), + hdr, msg, hdr.len); if (line_len > 0) { console_emit(line, line_len); } diff --git a/na/lib/kobject.cc b/na/lib/kobject.cc new file mode 100644 index 0000000..8e8329e --- /dev/null +++ b/na/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 <lume/kobject.h> +#include <lume/container_of.h> +#include <lume/klog.h> + +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 index ddd2e59..a98de71 100644 --- a/na/lib/kprintf.cc +++ b/na/lib/kprintf.cc @@ -5,21 +5,44 @@ * kprintf.cc — Kernel formatted output * * Uses mpaland/printf's vsnprintf_ as the core formatting engine. - * All kernel output ultimately goes through this module. * * Architecture: * vsnprintf_() ← pure formatting, writes to buffer * ↓ - * kprintf() ← format + emit to UART via early_putc + * 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 <lume/kprintf.h> #include <lume/console.h> +#include <lume/atomic.h> +#include <arch/cpu.h> #include "printf.h" /* ------------------------------------------------------------------ */ +/* Console spinlock (raw, no scheduler dependency — safe in panic) */ +/* ------------------------------------------------------------------ */ + +static lume::atomic<uint32> 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_() */ /* ------------------------------------------------------------------ */ @@ -40,10 +63,10 @@ extern "C" int kprintf(const char* fmt, ...) int len = vsnprintf_(buf, sizeof(buf), fmt, va); va_end(va); - /* Emit to console */ - for (int i = 0; i < len && buf[i] != '\0'; ++i) { - early_putc(buf[i]); - } + /* Emit to console under lock */ + console_lock_acquire(); + console_write_all(buf, len); + console_lock_release(); return len; } @@ -52,9 +75,9 @@ extern "C" int kvprintf(const char* fmt, va_list va) char buf[256]; int len = vsnprintf_(buf, sizeof(buf), fmt, va); - for (int i = 0; i < len && buf[i] != '\0'; ++i) { - early_putc(buf[i]); - } + console_lock_acquire(); + console_write_all(buf, len); + console_lock_release(); return len; } diff --git a/na/lib/module.mk b/na/lib/module.mk index 2cfadf7..0b178f8 100644 --- a/na/lib/module.mk +++ b/na/lib/module.mk @@ -1,10 +1,10 @@ # 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_CC += lib/decompress_lz4.cc SRCS_C += lib/printf.c # ============================================================ @@ -39,3 +39,28 @@ 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 <linux/libfdt_env.h> +# #include "../scripts/dtc/libfdt/fdt*.c" +# matching Linux lib/fdt*.c verbatim. The compile recipe needs: +# - <linux/libfdt_env.h> reachable via the default -Iinclude path +# - -I scripts/dtc/libfdt so the upstream source's #include <libfdt.h> +# 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 <linux/libfdt_env.h>. +# ============================================================ +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 <linux/libfdt_env.h>. +$(addprefix $(BUILD)/lib/,$(LIBFDT_FILES:.c=.o)): CFLAGS_LIBC = \ + $(CFLAGS) -I scripts/dtc/libfdt diff --git a/na/mm/memblock.cc b/na/mm/memblock.cc index 4cd0b16..aee0245 100644 --- a/na/mm/memblock.cc +++ b/na/mm/memblock.cc @@ -16,6 +16,7 @@ #include <lume/fdt.h> #include <lume/klog.h> #include <lume/kprintf.h> +#include <lume/config.h> #include <lume/addr.h> extern "C" char _stext[], _kernel_end[]; @@ -24,7 +25,11 @@ extern "C" char _stext[], _kernel_end[]; /* Module state */ /* ------------------------------------------------------------------ */ -static struct { +namespace { + +constexpr uint64 kMB = 1024ULL * 1024ULL; + +struct { uint64 mem_base; uint64 mem_size; @@ -41,7 +46,7 @@ static struct { /* ------------------------------------------------------------------ */ /* Insert a reserved region, keeping the array sorted by base. */ -static void insert_reserved(uint64 base, uint64 size) +void insert_reserved(uint64 base, uint64 size) { if (g_mb.reserved_count >= kMemblockMaxRegions) { kernel_panic("memblock: too many reserved regions"); @@ -57,7 +62,7 @@ static void insert_reserved(uint64 base, uint64 size) } /* Check if [base, base+size) overlaps any reserved region. */ -static bool overlaps_reserved(uint64 base, uint64 size) +bool overlaps_reserved(uint64 base, uint64 size) { uint64 end = base + size; for (int i = 0; i < g_mb.reserved_count; i++) { @@ -71,7 +76,7 @@ static bool overlaps_reserved(uint64 base, uint64 size) } /* Find the end of the first reserved region that covers `addr`. */ -static uint64 reserved_end_covering(uint64 addr) +uint64 reserved_end_covering(uint64 addr) { for (int i = 0; i < g_mb.reserved_count; i++) { uint64 rb = g_mb.reserved[i].base; @@ -83,13 +88,14 @@ static uint64 reserved_end_covering(uint64 addr) return addr; } +} // namespace + /* ------------------------------------------------------------------ */ /* Public API */ /* ------------------------------------------------------------------ */ void memblock_init(uint64 fdt_paddr) { - (void)fdt_paddr; /* 1. Get memory layout from FDT */ uint64 mem_base = 0; @@ -104,21 +110,58 @@ void memblock_init(uint64 fdt_paddr) g_mb.reserved_count = 0; g_mb.active = true; - /* 2. Reserve firmware region [mem_base, kernel_start). - * OpenSBI occupies the first 2MB of RAM on RISC-V virt. */ - uint64 ks_pa = boot_va_to_pa(virt_addr( - reinterpret_cast<uint64>(_stext))).raw; - if (ks_pa > mem_base) { - memblock_reserve(mem_base, ks_pa - mem_base); + /* 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<uint64>(_stext))).raw; + if (ks_pa != kPhysStart) { + /* Kernel was relocated — reserve the original location */ + uint64 kernel_size = boot_va_to_pa(virt_addr( + reinterpret_cast<uint64>(_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<uint64>(_stext))).raw; uint64 ke_pa = boot_va_to_pa(virt_addr( reinterpret_cast<uint64>(_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<const fdt_hdr*>(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 / (1024 * 1024)); + mem_base, mem_base + mem_size, mem_size / kMB); kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa); } @@ -196,7 +239,6 @@ void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) { uint64 rb = g_mb.reserved[i].base; if (rb < pos) { - /* Overlapping/adjacent — skip */ uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size; if (re > pos) { pos = re; @@ -204,13 +246,11 @@ void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), continue; } if (rb > pos) { - /* Free gap [pos, rb) */ cb(pos, rb - pos, ctx); } pos = g_mb.reserved[i].base + g_mb.reserved[i].size; } - /* Trailing free region after last reserved */ if (pos < mem_end) { cb(pos, mem_end - pos, ctx); } diff --git a/na/mm/module.mk b/na/mm/module.mk index d497067..93cef80 100644 --- a/na/mm/module.mk +++ b/na/mm/module.mk @@ -3,3 +3,5 @@ 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 new file mode 100644 index 0000000..25c6a60 --- /dev/null +++ b/na/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 <lume/page_cache.h> +#include <lume/page.h> +#include <lume/pmm.h> +#include <lume/errno.h> +#include <lume/new.h> +#include <kernel/sync/spinlock.h> + +/* ------------------------------------------------------------------ */ +/* 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<uint64>(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<uint64>(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 index a5a0b0e..b7cf611 100644 --- a/na/mm/pmm.cc +++ b/na/mm/pmm.cc @@ -43,16 +43,25 @@ struct PerCpuCache { /* Module-private state */ /* ------------------------------------------------------------------ */ -static Spinlock g_pmm_lock; -static list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10] -static PerCpuCache g_pcp[kMaxCpus]; +namespace { -static Page *g_page_map = nullptr; // Flat array of Page descriptors -static uint64 g_page_map_pa = 0; // Physical base of page_map storage -static uint64 g_page_map_bytes = 0; // Byte size of page_map storage -static uint64 g_mem_base = 0; // Physical memory base (from FDT) -static uint64 g_mem_size = 0; // Total physical memory size -static uint64 g_num_pages = 0; // Total managed pages +/* 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; @@ -62,63 +71,68 @@ uint32 g_pcp_batch_size = 0; /* ------------------------------------------------------------------ */ /* - * Physical Page Number: index into the page_map array. - * PFN = (pa - g_mem_base) / kPageSize + * 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. */ -static inline uint64 pa_to_page_index(uint64 pa) { - if (pa < g_mem_base || pa >= g_mem_base + g_num_pages * kPageSize) - kernel_panic("pa_to_page: physical address outside managed range"); - return (pa - g_mem_base) / kPageSize; -} - -static inline uint64 page_index_to_pa(uint64 idx) { - if (idx >= g_num_pages) - kernel_panic("page_to_pa: page index outside managed range"); - return g_mem_base + idx * kPageSize; -} - -PhysAddr page_to_pa(const Page *page) { +uint64 page_to_pfn(const Page *page) { uintptr base = reinterpret_cast<uintptr>(g_page_map); uintptr addr = reinterpret_cast<uintptr>(page); uintptr end = base + g_num_pages * sizeof(Page); - if (addr < base || addr >= end) - kernel_panic("page_to_pa: page outside page_map bounds"); + if (addr < base || addr >= end) { + kernel_panic("page_to_pfn: page outside page_map bounds"); + } uint64 idx = (addr - base) / sizeof(Page); - return phys_addr(page_index_to_pa(idx)); + 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) { - uint64 idx = pa_to_page_index(pa.raw); - return &g_page_map[idx]; + 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). */ -static inline Page *buddy_of(Page *page, uint8 order) { +inline Page *buddy_of(Page *page, uint8 order) { uint64 pfn = static_cast<uint64>(page - g_page_map); uint64 buddy_pfn = pfn ^ (1ULL << order); - if (buddy_pfn >= g_num_pages) + 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. */ -static void buddy_list_add(Page *page, uint8 order) { - page->state = PageState::Free; +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; @@ -131,7 +145,7 @@ static void buddy_list_add(Page *page, uint8 order) { /* * Remove a block from whatever buddy free list it is on. */ -static void buddy_list_del(Page *page) { +void buddy_list_del(Page *page) { list_node *node = &page->free.free_link; node->prev->next = node->next; node->next->prev = node->prev; @@ -144,11 +158,12 @@ static void buddy_list_del(Page *page) { * Splits higher-order blocks if no exact match is available. * Returns nullptr if no memory available. */ -static Page *buddy_alloc(uint8 order) { +Page *buddy_alloc(uint8 order) { for (int cur = order; cur < kMaxOrder; cur++) { list_node *head = &g_free_areas[cur]; - if (head->next == head) + if (head->next == head) { continue; // Empty at this order + } // Pop the first block from this order's free list list_node *node = head->next; @@ -166,9 +181,13 @@ static Page *buddy_alloc(uint8 order) { buddy_list_add(split, static_cast<uint8>(cur)); } - page->state = PageState::Allocated; + 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; @@ -178,16 +197,18 @@ static Page *buddy_alloc(uint8 order) { * Return a block of 2^order pages to the buddy system. * Coalesces with its buddy recursively up to kMaxOrder-1. */ -static void buddy_free(Page *page, uint8 order) { +void buddy_free(Page *page, uint8 order) { uint64 pfn = static_cast<uint64>(page - g_page_map); while (order < kMaxOrder - 1) { Page *buddy = buddy_of(page, order); - if (!buddy) + if (!buddy) { break; + } // Buddy must be free AND at the same order to coalesce - if (buddy->state != PageState::Free || buddy->order != order) + if (buddy->type != PageType::Free || buddy->order != order) { break; + } // Remove buddy from its free list and merge buddy_list_del(buddy); @@ -212,14 +233,15 @@ static void buddy_free(Page *page, uint8 order) { * Refill the PCP from the global buddy system. * Acquires g_pmm_lock internally. */ -static void refill_pcp(PerCpuCache *pcp) { +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) + if (!page) { break; + } // Put onto PCP list (reuse the free_link for PCP chain) - page->state = PageState::PcpCached; + page->type = PageType::PcpCached; list_node *node = &page->free.free_link; node->next = pcp->free_list.next; node->prev = &pcp->free_list; @@ -233,13 +255,14 @@ static void refill_pcp(PerCpuCache *pcp) { * Drain half the PCP back to the global buddy system. * Acquires g_pmm_lock internally. */ -static void drain_pcp(PerCpuCache *pcp) { +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) + if (head->next == head) { break; + } list_node *node = head->next; // Unlink from PCP node->prev->next = node->next; @@ -253,6 +276,8 @@ static void drain_pcp(PerCpuCache *pcp) { } } +} // namespace + /* ------------------------------------------------------------------ */ /* Public API: single-page (fast path via PCP) */ /* ------------------------------------------------------------------ */ @@ -269,8 +294,9 @@ Page *pmm_alloc_page() { uint64 cpu = arch::cpu::id(); PerCpuCache *pcp = &g_pcp[cpu]; - if (pcp->count == 0) + if (pcp->count == 0) { refill_pcp(pcp); + } Page *page = nullptr; if (pcp->count > 0) { @@ -289,19 +315,25 @@ Page *pmm_alloc_page() { kernel_panic("pmm_alloc_page: PCP returned out-of-bounds page"); } - page->state = PageState::Allocated; + 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) + if (was_on) { arch::cpu::intr_on(); + } return page; } void pmm_free_page(Page *page) { - if (!page) + if (!page) { kernel_panic("pmm_free_page: null page"); + } bool was_on = arch::cpu::intr_enabled(); arch::cpu::intr_off(); @@ -310,7 +342,7 @@ void pmm_free_page(Page *page) { PerCpuCache *pcp = &g_pcp[cpu]; // Push onto PCP - page->state = PageState::PcpCached; + page->type = PageType::PcpCached; page->order = 0; list_node *node = &page->free.free_link; node->next = pcp->free_list.next; @@ -320,11 +352,13 @@ void pmm_free_page(Page *page) { pcp->count++; // Drain if over high watermark - if (pcp->count >= g_pcp_high_watermark) + if (pcp->count >= g_pcp_high_watermark) { drain_pcp(pcp); + } - if (was_on) + if (was_on) { arch::cpu::intr_on(); + } } /* ------------------------------------------------------------------ */ @@ -332,17 +366,20 @@ void pmm_free_page(Page *page) { /* ------------------------------------------------------------------ */ Page *pmm_alloc_pages(uint8 order) { - if (order >= kMaxOrder) + if (order >= kMaxOrder) { return nullptr; + } LockGuard guard(g_pmm_lock); return buddy_alloc(order); } void pmm_free_pages(Page *page, uint8 order) { - if (!page) + if (!page) { kernel_panic("pmm_free_pages: null page"); - if (order >= kMaxOrder) + } + if (order >= kMaxOrder) { kernel_panic("pmm_free_pages: order out of range"); + } LockGuard guard(g_pmm_lock); buddy_free(page, order); } @@ -358,10 +395,11 @@ void page_decref(Page *page) { if (old == 1) { // Refcount dropped to 0 — return to pool based on originally allocated // order - if (page->order == 0) + if (page->order == 0) { pmm_free_page(page); - else + } else { pmm_free_pages(page, page->order); + } } } @@ -373,11 +411,14 @@ void page_decref(Page *page) { * Helper: zero out a range of bytes. We cannot use memset here * because lib/string may not be linked yet. */ -static void zero_range(void *start, uint64 bytes) { +namespace { +void zero_range(void *start, uint64 bytes) { uint8 *p = static_cast<uint8 *>(start); - for (uint64 i = 0; i < bytes; i++) + for (uint64 i = 0; i < bytes; i++) { p[i] = 0; + } } +} // namespace void pmm_init() { /* 1. Get memory layout from memblock (already scanned FDT) */ @@ -390,9 +431,9 @@ void pmm_init() { } /* 2. PCP parameters */ - uint32 calc_batch = static_cast<uint32>(g_num_pages / (kMaxCpus * 1024)); - g_pcp_batch_size = (calc_batch > 16) ? calc_batch : 16; - g_pcp_high_watermark = g_pcp_batch_size * 2; + uint32 calc_batch = static_cast<uint32>(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. */ @@ -407,8 +448,13 @@ void pmm_init() { for (uint64 i = 0; i < g_num_pages; i++) { Page *page = &g_page_map[i]; page->refcount.store(0, __ATOMIC_RELAXED); - page->state = PageState::Allocated; + 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(); } @@ -438,9 +484,7 @@ void pmm_init() { uint64 start_pfn = (base - memblock_get_mem_base()) / kPageSize; uint64 end_pfn = start_pfn + size / kPageSize; - /* Access page_map via the extern g_page_map (already at vmemmap VA) */ - extern Page *g_page_map; - extern list_node g_free_areas[]; + /* Access page_map via the globals directly (they are in anon namespace now) */ uint64 pfn = start_pfn; while (pfn < end_pfn) { @@ -454,7 +498,7 @@ void pmm_init() { } Page *page = &g_page_map[pfn]; - page->state = PageState::Free; + page->type = PageType::Free; page->order = order; page->refcount.store(0, __ATOMIC_RELAXED); page->free.free_link.init(); @@ -474,7 +518,7 @@ void pmm_init() { &ctx); kprintf("[pmm] buddy: %llu free pages (%llu MB)\n", ctx.total_pages, - (ctx.total_pages * kPageSize) / (1024 * 1024)); + (ctx.total_pages * kPageSize) / kMB); /* 8. Retire memblock — all future allocations go through PMM */ memblock_retire(); diff --git a/na/mm/slab.cc b/na/mm/slab.cc index e9b06cc..43d8c4a 100644 --- a/na/mm/slab.cc +++ b/na/mm/slab.cc @@ -79,7 +79,7 @@ Page* KmemCache::new_slab() if (!page) return nullptr; - page->state = PageState::Slab; + page->type = PageType::Slab; page->slab.cache = this; page->slab.obj_count = 0; @@ -197,7 +197,7 @@ void KmemCache::free(void* obj) uint64 page_pa = obj_pa & ~(kPageSize - 1); Page* page = pa_to_page(phys_addr(page_pa)); - if (page->state != PageState::Slab || page->slab.cache != this) + 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(); @@ -274,12 +274,12 @@ void kfree(void* ptr) uint64 page_pa = pa & ~(kPageSize - 1); Page* page = pa_to_page(phys_addr(page_pa)); - if (page->state == PageState::Slab) { + 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->state == PageState::Allocated) { + } else if (page->type == PageType::Allocated) { /* Large allocation — return directly to PMM */ pmm_free_pages(page, page->order); } else { diff --git a/na/mm/tlb.cc b/na/mm/tlb.cc new file mode 100644 index 0000000..b7a8ba7 --- /dev/null +++ b/na/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 <lume/tlb.h> +#include <lume/vmm.h> +#include <lume/config.h> +#include <arch/mmu.h> +#include <arch/tlb.h> +#include <arch/cpu.h> + +/* + * 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 index 10db65d..da89e08 100644 --- a/na/mm/vma.cc +++ b/na/mm/vma.cc @@ -22,7 +22,8 @@ #include <lume/new.h> #include <lume/klog.h> #include <arch/pmap.h> -#include <arch/mmu.h> +#include <lume/tlb.h> +#include <lume/mm_layout.h> /* ------------------------------------------------------------------ */ /* Rbtree callbacks for VMA interval tree */ @@ -65,8 +66,7 @@ static bool range_overlaps(uint64 a_start, uint64 a_end, return a_start < b_end && b_start < a_end; } -static constexpr uint64 kUserVaBase = kPageSize; // Avoid mapping the null page -static constexpr uint64 kUserVaLimit = 0xFFFFFFC000000000ULL; + static bool vma_overlaps_locked(VmSpace* space, uint64 start, uint64 end) { @@ -188,11 +188,22 @@ VmSpace* VmSpace::create() { 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(); @@ -202,11 +213,13 @@ void VmSpace::destroy() { VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - /* Decref all mapped physical pages */ + /* 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_decref(pa_to_page(phys_addr(pa))); + Page* pg = pa_to_page(phys_addr(pa)); + page_dec_mapcount(pg); + page_decref(pg); } } @@ -285,8 +298,10 @@ int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) { for (uint64 addr = unmap_start; addr < unmap_end; addr += kPageSize) { uint64 pa; if (pmap::lookup(space->root_pa, addr, &pa)) { - page_decref(pa_to_page(phys_addr(pa))); pmap::unmap(space->root_pa, addr); + Page* pg = pa_to_page(phys_addr(pa)); + page_dec_mapcount(pg); + page_decref(pg); } } } @@ -362,7 +377,9 @@ int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) } if (perm == 0) { - page_decref(pa_to_page(phys_addr(pa))); + 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; @@ -385,7 +402,7 @@ int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) } if (tlb_flush_needed) { - arch::mmu::flush_tlb_all(); + tlb_shootdown_range(space, va, end); } return 0; } @@ -399,7 +416,11 @@ uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align) LockGuard guard(space->lock); - uint64 cursor = align_up_any(kUserVaBase, align); + /* 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); @@ -494,22 +515,44 @@ int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause) return -ENOMEM; } - /* Decrement refcount of old page */ - page_decref(pa_to_page(phys_addr(paddr))); + /* 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 */ - asm volatile("sfence.vma %0" : : "r"(aligned_addr) : "memory"); + 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<int>(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 */ + /* 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<uint8*>(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); @@ -518,7 +561,9 @@ int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause) return -ENOMEM; } - /* Initial mapping, refcount is already 1 from pmm_alloc_page */ + /* Initial mapping: refcount is already 1 from pmm_alloc_page. + * Set mapcount = 1 to track this PTE mapping. */ + page_inc_mapcount(page); return 0; } @@ -561,7 +606,7 @@ VmSpace* vmm_clone(VmSpace* parent) { if (child_vma->perm & VM_WRITE) { pmap::make_cow(parent->root_pa, va); /* Flush TLB for parent since we reduced permissions */ - asm volatile("sfence.vma %0" : : "r"(va) : "memory"); + tlb_shootdown_page(parent, va); } /* Map in child as COW or read-only */ @@ -580,8 +625,10 @@ VmSpace* vmm_clone(VmSpace* parent) { return nullptr; } - /* Increment physical page refcount */ - page_incref(pa_to_page(phys_addr(pa))); + /* Increment physical page refcount and mapcount */ + Page* shared_page = pa_to_page(phys_addr(pa)); + page_incref(shared_page); + page_inc_mapcount(shared_page); } } diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc index 0aa2fb0..51617e3 100644 --- a/na/mm/vmm.cc +++ b/na/mm/vmm.cc @@ -18,6 +18,7 @@ #include <arch/config.h> #include <arch/mmu.h> #include <arch/pmap.h> +#include <lume/tlb.h> #include <lume/addr.h> #include <lume/console.h> #include <lume/fdt.h> @@ -28,6 +29,7 @@ #include <lume/pmm.h> #include <lume/types.h> #include <lume/vmm.h> +#include <lume/config.h> /* Linker-exported section boundaries (virtual addresses) */ extern "C" char _stext[], _etext[]; @@ -36,25 +38,23 @@ extern "C" char _sdata[], _edata[]; extern "C" char _sbss[], _ebss[]; extern "C" char _kernel_end[]; -/* Global kernel page table root PA */ -static uint64 g_kernel_pgtbl = 0; +/* Global kernel page table root PA — external linkage for task_exit */ +uint64 g_kernel_pgtbl = 0; -/* VMM exports for PMM: page_map location after vmemmap mapping. */ -static uint64 g_vmemmap_page_map_pa = 0; -static uint64 g_vmemmap_page_map_size = 0; -static uint64 g_vmemmap_num_pages = 0; +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; } +/* 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). */ -static inline uint64 sym_to_pa(const void *sym) { +inline uint64 sym_to_pa(const void *sym) { return boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(sym))).raw; } /* Map a PA range into the kernel page table via the direct map. */ -static void map_range(uint64 root, uint64 pa_start, uint64 pa_end, +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; @@ -66,7 +66,7 @@ static void map_range(uint64 root, uint64 pa_start, uint64 pa_end, } /* Map a PA range into the kernel page table at the fixed linked VA. */ -static void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end, +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; @@ -78,7 +78,7 @@ static void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end, } /* Map a PA range to an explicit VA base, using 2MB pages where possible. */ -static void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start, +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; @@ -109,6 +109,12 @@ static void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start, } } +} // 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(); @@ -137,17 +143,36 @@ void vmm_init() { */ map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X); - /* 4. Map .rodata (R) at fixed linked VA */ - map_range_linked(root, ro_start, ro_end, pmap::PTE_R); + /* 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 remaining physical memory after kernel (RW) */ - uint64 after_kernel = page_align_up(sym_to_pa(_kernel_end)); - if (after_kernel < mem_end) { - map_range(root, after_kernel, mem_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. * @@ -206,5 +231,5 @@ void vmm_map_kernel_mmio(uint64 pa, uint64 size) { map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size, pmap::PTE_R | pmap::PTE_W | pmap::PTE_G); - arch::mmu::flush_tlb_all(); + tlb_shootdown_all(); } diff --git a/na/scripts/Kbuild.include b/na/scripts/Kbuild.include new file mode 100644 index 0000000..76466bd --- /dev/null +++ b/na/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,<name>) : echo a quiet summary, then run cmd_<name> +# - $(call if_changed,<name>) : 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_<name>` (single-line summary) and a `cmd_<name>` (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_<name>) +# 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 .<file>.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,<name>). 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 <name>.o entry, looks up <dir>/<name>.cc first, then +# <dir>/<name>.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 index c979e76..2bdc934 100644 --- a/na/scripts/Kconfig.mk +++ b/na/scripts/Kconfig.mk @@ -60,6 +60,18 @@ 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) # diff --git a/na/scripts/dtc/libfdt/fdt.c b/na/scripts/dtc/libfdt/fdt.c new file mode 100644 index 0000000..56d4dcb --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..a07abfc --- /dev/null +++ b/na/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/na/scripts/dtc/libfdt/fdt_addresses.c b/na/scripts/dtc/libfdt/fdt_addresses.c new file mode 100644 index 0000000..c40ba09 --- /dev/null +++ b/na/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 <david@gibson.dropbear.id.au> + * Copyright (C) 2018 embedded brains GmbH + */ +#include "libfdt_env.h" + +#include <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..49d54d4 --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..51a3859 --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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)) { + /* /<fragment-name>/__overlay__/<relative-subnode-path> */ + rel_path = s + len; + rel_path_len = e - rel_path - 1; + } else if ((e - s) == len + && (memcmp(s, "/__overlay__", len - 1) == 0)) { + /* /<fragment-name>/__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 new file mode 100644 index 0000000..63494fb --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..90ea14e --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..d852b77 --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 "<valid offset/length>"; + else if (errval == 0) + return "<no error>"; + else if (-errval < FDT_ERRTABSIZE) { + const char *s = fdt_errtable[-errval].str; + + if (s) + return s; + } + + return "<unknown error>"; +} diff --git a/na/scripts/dtc/libfdt/fdt_sw.c b/na/scripts/dtc/libfdt/fdt_sw.c new file mode 100644 index 0000000..4c569ee --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..c2d7566 --- /dev/null +++ b/na/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 <fdt.h> +#include <libfdt.h> + +#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 new file mode 100644 index 0000000..81aaf7c --- /dev/null +++ b/na/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/na/scripts/dtc/libfdt/libfdt_env.h b/na/scripts/dtc/libfdt/libfdt_env.h new file mode 100644 index 0000000..5580b48 --- /dev/null +++ b/na/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 <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#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 new file mode 100644 index 0000000..0e103ca --- /dev/null +++ b/na/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 <fdt.h> + +#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 new file mode 100644 index 0000000..c97ec2c --- /dev/null +++ b/na/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/na/scripts/gen_initramfs.sh b/na/scripts/gen_initramfs.sh new file mode 100755 index 0000000..7eba2fd --- /dev/null +++ b/na/scripts/gen_initramfs.sh @@ -0,0 +1,250 @@ +#!/bin/sh +# Copyright (C) Martin Schlemmer <azarah@nosferatu.za.org> +# Copyright (C) 2006 Sam Ravnborg <sam@ravnborg.org> +# +# 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 <file>] [-l <dep_list>] [-u <uid>] [-g <gid>] {-d | <cpio_source>} ... + -o <file> Create initramfs file named <file> by using gen_init_cpio + -l <dep_list> Create dependency list named <dep_list> + -u <uid> User ID to map to user ID 0 (root). + <uid> is only meaningful if <cpio_source> is a + directory. "squash" forces all files to uid 0. + -g <gid> Group ID to map to group ID 0 (root). + <gid> is only meaningful if <cpio_source> is a + directory. "squash" forces all files to gid 0. + -d <date> Use date for all file mtime values + <cpio_source> File list or directory for cpio archive. + If <cpio_source> 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 +<cpio_source> 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 +# <filetype> <name> <path to file> <octal mode> <uid> <gid> +# 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/scripts/gen_reloc.py b/na/scripts/gen_reloc.py deleted file mode 100644 index 6709e17..0000000 --- a/na/scripts/gen_reloc.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -import argparse -import struct -import sys - -ELF_MAGIC = b"\x7fELF" -ELFCLASS64 = 2 -ELFDATA2LSB = 1 -EM_RISCV = 243 -SHT_SYMTAB = 2 -SHT_RELA = 4 -PT_LOAD = 1 - -R_RISCV_64 = 2 -R_RISCV_RELATIVE = 3 - -RELOC_MAGIC = 0x4C554D45524C4F43 # "LUMERLOC" -RELOC_VERSION = 1 -RELOC_ARCH_RISCV = 1 - - -def read_struct(fmt, data, offset): - size = struct.calcsize(fmt) - return struct.unpack_from(fmt, data, offset), size - - -def parse_elf_header(data): - if data[:4] != ELF_MAGIC: - raise ValueError("not an ELF file") - ei_class = data[4] - ei_data = data[5] - if ei_class != ELFCLASS64 or ei_data != ELFDATA2LSB: - raise ValueError("unsupported ELF class/data") - fmt = "<16sHHIQQQIHHHHHH" - (e_ident, e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, - e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, - e_shstrndx), _ = read_struct(fmt, data, 0) - return { - "e_machine": e_machine, - "e_phoff": e_phoff, - "e_phentsize": e_phentsize, - "e_phnum": e_phnum, - "e_shoff": e_shoff, - "e_shentsize": e_shentsize, - "e_shnum": e_shnum, - "e_shstrndx": e_shstrndx, - } - - -def parse_program_headers(data, ehdr): - phdrs = [] - fmt = "<IIQQQQQQ" - for i in range(ehdr["e_phnum"]): - off = ehdr["e_phoff"] + i * ehdr["e_phentsize"] - (p_type, p_flags, p_offset, p_vaddr, p_paddr, p_filesz, p_memsz, - p_align), _ = read_struct(fmt, data, off) - phdrs.append({ - "p_type": p_type, - "p_offset": p_offset, - "p_vaddr": p_vaddr, - "p_filesz": p_filesz, - "p_memsz": p_memsz, - }) - return phdrs - - -def parse_section_headers(data, ehdr): - shdrs = [] - fmt = "<IIQQQQIIQQ" - for i in range(ehdr["e_shnum"]): - off = ehdr["e_shoff"] + i * ehdr["e_shentsize"] - (sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, - sh_link, sh_info, sh_addralign, sh_entsize), _ = read_struct(fmt, data, off) - shdrs.append({ - "sh_name": sh_name, - "sh_type": sh_type, - "sh_addr": sh_addr, - "sh_offset": sh_offset, - "sh_size": sh_size, - "sh_link": sh_link, - "sh_entsize": sh_entsize, - }) - return shdrs - - -def get_strtab(data, shdrs, idx): - sh = shdrs[idx] - return data[sh["sh_offset"]:sh["sh_offset"] + sh["sh_size"]] - - -def get_section_name(strtab, sh_name): - end = strtab.find(b"\x00", sh_name) - if end < 0: - return "" - return strtab[sh_name:end].decode("ascii", errors="ignore") - - -def find_symbol(data, shdrs, name): - for i, sh in enumerate(shdrs): - if sh["sh_type"] != SHT_SYMTAB: - continue - strtab = get_strtab(data, shdrs, sh["sh_link"]) - entsize = sh["sh_entsize"] or 24 - count = sh["sh_size"] // entsize - for j in range(count): - off = sh["sh_offset"] + j * entsize - (st_name, st_info, st_other, st_shndx, st_value, st_size), _ = read_struct( - "<IBBHQQ", data, off - ) - sym_name = get_section_name(strtab, st_name) - if sym_name == name: - return st_value - return None - - -def read_symtab(data, shdrs, idx): - sh = shdrs[idx] - entsize = sh["sh_entsize"] or 24 - count = sh["sh_size"] // entsize - syms = [] - for j in range(count): - off = sh["sh_offset"] + j * entsize - (st_name, st_info, st_other, st_shndx, st_value, st_size), _ = read_struct( - "<IBBHQQ", data, off - ) - syms.append((st_value, st_size, st_info, st_shndx)) - return syms - - -def vaddr_in_load(vaddr, phdrs, size): - for ph in phdrs: - if ph["p_type"] != PT_LOAD: - continue - start = ph["p_vaddr"] - end = ph["p_vaddr"] + ph["p_memsz"] - if vaddr >= start and vaddr + size <= end: - return True - return False - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--elf", required=True) - ap.add_argument("--out", required=True) - args = ap.parse_args() - - data = open(args.elf, "rb").read() - ehdr = parse_elf_header(data) - if ehdr["e_machine"] != EM_RISCV: - raise ValueError("only EM_RISCV supported") - - phdrs = parse_program_headers(data, ehdr) - shdrs = parse_section_headers(data, ehdr) - - link_base = find_symbol(data, shdrs, "_start") - if link_base is None: - raise ValueError("_start symbol not found") - - image_end = find_symbol(data, shdrs, "__lume_image_end") - if image_end is None: - raise ValueError("__lume_image_end symbol not found") - image_size = image_end - link_base - - entries = [] - for sh in shdrs: - if sh["sh_type"] != SHT_RELA: - continue - if sh["sh_link"] >= len(shdrs): - continue - symtab = read_symtab(data, shdrs, sh["sh_link"]) - entsize = sh["sh_entsize"] or 24 - count = sh["sh_size"] // entsize - for i in range(count): - off = sh["sh_offset"] + i * entsize - (r_offset, r_info, r_addend), _ = read_struct("<QQq", data, off) - r_type = r_info & 0xFFFFFFFF - r_sym = r_info >> 32 - if r_type not in (R_RISCV_RELATIVE, R_RISCV_64): - continue - if r_offset < link_base or r_offset >= link_base + image_size: - continue - if not vaddr_in_load(r_offset, phdrs, 8): - continue - ent_offset = r_offset - link_base - if r_type == R_RISCV_RELATIVE: - ent_addend = r_addend - ent_type = 1 - else: - sym_val = 0 - if r_sym < len(symtab): - sym_val = symtab[r_sym][0] - ent_addend = sym_val + r_addend - if ent_addend < link_base or ent_addend >= link_base + image_size: - continue - ent_type = 2 - entries.append((ent_offset, ent_addend, ent_type)) - - header = struct.pack( - "<QHHIII", - RELOC_MAGIC, - RELOC_VERSION, - RELOC_ARCH_RISCV, - 24, - len(entries), - 0, - ) - - with open(args.out, "wb") as f: - f.write(header) - for off, add, typ in entries: - f.write(struct.pack("<QQII", off, add, typ, 0)) - - return 0 - - -if __name__ == "__main__": - try: - sys.exit(main()) - except Exception as e: - print("gen_reloc: " + str(e), file=sys.stderr) - sys.exit(1) diff --git a/na/scripts/pack_image.py b/na/scripts/pack_image.py deleted file mode 100644 index dc5c6db..0000000 --- a/na/scripts/pack_image.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -import argparse -import struct -import sys - -MAGIC = 0x4C554D45434F4D50 # "LUMECOMP" -COMP_NONE = 0 -COMP_LZ4 = 1 - -HDR_FMT = "<QIHHIIQQQQQQII" -HDR_SIZE = struct.calcsize(HDR_FMT) -HDR_OFFSET = 64 - - -def crc32_update(crc: int, data: bytes) -> int: - for b in data: - crc ^= b - for _ in range(8): - mask = -(crc & 1) & 0xFFFFFFFF - crc = ((crc >> 1) ^ (0xEDB88320 & mask)) & 0xFFFFFFFF - return crc - - -def crc32_ieee(data: bytes) -> int: - crc = 0xFFFFFFFF - crc = crc32_update(crc, data) - return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF - - -def lz4_compress(data: bytes) -> bytes: - n = len(data) - if n == 0: - return b"" - - min_match = 4 - hash_log = 16 - hash_size = 1 << hash_log - max_offset = 65535 - - def hash4(b0, b1, b2, b3): - v = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) - return (v * 2654435761) & (hash_size - 1) - - htab = [-1] * hash_size - out = bytearray() - anchor = 0 - i = 0 - - while i + min_match <= n: - h = hash4(data[i], data[i + 1], data[i + 2], data[i + 3]) - ref = htab[h] - htab[h] = i - - if ref >= 0 and i - ref <= max_offset and data[ref:ref + min_match] == data[i:i + min_match]: - match_len = min_match - while i + match_len < n and data[ref + match_len] == data[i + match_len]: - match_len += 1 - - lit_len = i - anchor - token = (15 if lit_len >= 15 else lit_len) << 4 - match_field = match_len - min_match - token |= 15 if match_field >= 15 else match_field - out.append(token) - - if lit_len >= 15: - l = lit_len - 15 - while l >= 255: - out.append(255) - l -= 255 - out.append(l) - - out.extend(data[anchor:i]) - - offset = i - ref - out.append(offset & 0xFF) - out.append((offset >> 8) & 0xFF) - - if match_field >= 15: - l = match_field - 15 - while l >= 255: - out.append(255) - l -= 255 - out.append(l) - - i += match_len - anchor = i - continue - - i += 1 - - lit_len = n - anchor - token = (15 if lit_len >= 15 else lit_len) << 4 - out.append(token) - if lit_len >= 15: - l = lit_len - 15 - while l >= 255: - out.append(255) - l -= 255 - out.append(l) - out.extend(data[anchor:]) - return bytes(out) - - -def get_header_offset(data: bytes) -> int: - if len(data) < HDR_OFFSET + HDR_SIZE: - return -1 - if data[HDR_OFFSET:HDR_OFFSET + 8] != struct.pack("<Q", MAGIC): - return -1 - return HDR_OFFSET - - -def read_struct(fmt, data, offset): - size = struct.calcsize(fmt) - return struct.unpack_from(fmt, data, offset), size - - -def parse_elf_symbols(path: str): - data = open(path, "rb").read() - if data[:4] != b"\x7fELF": - return {} - if data[4] != 2 or data[5] != 1: - return {} - (e_ident, e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, - e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, - e_shstrndx), _ = read_struct("<16sHHIQQQIHHHHHH", data, 0) - - symbols = {} - for i in range(e_shnum): - off = e_shoff + i * e_shentsize - (sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, - sh_link, sh_info, sh_addralign, sh_entsize), _ = read_struct( - "<IIQQQQIIQQ", data, off) - if sh_type != 2: - continue - if sh_link >= e_shnum: - continue - str_off = e_shoff + sh_link * e_shentsize - (sname, stype, sflags, saddr, soff, ssize, slink, sinfo, - salign, sentsz), _ = read_struct("<IIQQQQIIQQ", data, str_off) - strtab = data[soff:soff + ssize] - entsize = sh_entsize or 24 - count = sh_size // entsize - for j in range(count): - ent_off = sh_offset + j * entsize - (st_name, st_info, st_other, st_shndx, st_value, st_size), _ = read_struct( - "<IBBHQQ", data, ent_off) - if st_name == 0: - continue - end = strtab.find(b"\x00", st_name) - if end < 0: - continue - name = strtab[st_name:end].decode("ascii", errors="ignore") - if name: - symbols[name] = st_value - break - return symbols - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--elf") - ap.add_argument("--input", required=True) - ap.add_argument("--output", required=True) - ap.add_argument("--compress", choices=["none", "lz4"], default="none") - args = ap.parse_args() - - raw = open(args.input, "rb").read() - hdr_off = get_header_offset(raw) - if hdr_off < 0: - raise ValueError("compressed header magic not found") - - header = list(struct.unpack_from(HDR_FMT, raw, hdr_off)) - if header[0] != MAGIC: - raise ValueError("bad header magic") - if header[2] < HDR_SIZE: - raise ValueError("header_size too small") - - if args.compress == "lz4": - payload = lz4_compress(raw) - comp = COMP_LZ4 - else: - payload = b"" - comp = COMP_NONE - - uncompressed_size = len(raw) - compressed_size = len(payload) - payload_offset = (len(raw) + 7) & ~7 if payload else 0 - if payload and args.elf: - syms = parse_elf_symbols(args.elf) - link_base = syms.get("_start") - bss_end = syms.get("_ebss") - if link_base is not None and bss_end is not None and bss_end >= link_base: - payload_offset = (bss_end - link_base + 7) & ~7 - - header[4] = comp - header[6] = uncompressed_size - header[7] = compressed_size - header[8] = 0 # load_phys_base auto - header[11] = payload_offset - header[12] = 0 - header[13] = crc32_ieee(payload) if payload else 0 - - header_bytes = struct.pack(HDR_FMT, *header) - header[12] = crc32_ieee(header_bytes) - - out = bytearray(raw) - struct.pack_into(HDR_FMT, out, hdr_off, *header) - if payload: - if payload_offset > len(out): - out.extend(b"\x00" * (payload_offset - len(out))) - out.extend(payload) - - with open(args.output, "wb") as f: - f.write(out) - - return 0 - - -if __name__ == "__main__": - try: - sys.exit(main()) - except Exception as e: - print("pack_image: " + str(e), file=sys.stderr) - sys.exit(1) diff --git a/na/test/module.mk b/na/test/module.mk index da6c065..47a693c 100644 --- a/na/test/module.mk +++ b/na/test/module.mk @@ -3,6 +3,8 @@ 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 @@ -12,7 +14,17 @@ 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_reloc.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 index 96962e2..e1bb2b4 100644 --- a/na/test/selftest.cc +++ b/na/test/selftest.cc @@ -14,6 +14,8 @@ 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(); @@ -23,10 +25,21 @@ void selftest_trap(); void selftest_sched(); void selftest_klog(); void selftest_random(); -void selftest_reloc(); 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() { @@ -35,6 +48,8 @@ void selftest_run_all() kprintf("========================================\n"); selftest_spinlock(); + selftest_mutex(); + selftest_driver_model(); selftest_pmm(); selftest_slab(); selftest_rbtree(); @@ -46,10 +61,21 @@ void selftest_run_all() selftest_sched(); selftest_klog(); selftest_random(); - selftest_reloc(); 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"); diff --git a/na/test/test_dcache.cc b/na/test/test_dcache.cc new file mode 100644 index 0000000..9e0eec5 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/dcache.h> +#include <lume/fs.h> +#include <lume/mount.h> +#include <lume/errno.h> +#include <lume/slab.h> + +/* 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 new file mode 100644 index 0000000..8f105b3 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/kref.h> +#include <lume/kobject.h> +#include <lume/device.h> + +/* ------------------------------------------------------------------ */ +/* 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 new file mode 100644 index 0000000..bbfa3b6 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/elf.h> +#include <lume/task.h> +#include <lume/vmm.h> +#include <lume/pmm.h> +#include <lume/addr.h> +#include <lume/errno.h> +#include <lume/fdtable.h> +#include <lume/fs.h> +#include <lume/fs_struct.h> +#include <lume/dcache.h> +#include <lume/namei.h> +#include <lume/file.h> +#include <lume/mm_layout.h> +#include <arch/trapframe.h> +#include <arch/pmap.h> + +/* 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<uint8*>(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<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>(path), + O_CREAT | O_WRONLY | O_TRUNC, + 0755, 0, 0); + if (fd < 0) return static_cast<int>(fd); + + int64 written = sys_write( + static_cast<uint64>(fd), + reinterpret_cast<uint64>(data), + size, 0, 0, 0); + + sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0); + + if (written < 0) return static_cast<int>(written); + if (static_cast<uint64>(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<uint8*>(&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<uint8*>(pa_to_va(PhysAddr{pa}).raw); + uint64 argc = *reinterpret_cast<uint64*>(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_mutex.cc b/na/test/test_mutex.cc new file mode 100644 index 0000000..e86c984 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/mutex.h> +#include <lume/task.h> +#include <lume/sched.h> +#include <lume/kprintf.h> + +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<int>(reinterpret_cast<uint64>(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<void*>(0), 0); + sched_yield(); + kthread_create("fifo_B", fifo_worker, reinterpret_cast<void*>(1), 0); + sched_yield(); + kthread_create("fifo_C", fifo_worker, reinterpret_cast<void*>(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 new file mode 100644 index 0000000..8e4ecb0 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/namei.h> +#include <lume/dcache.h> +#include <lume/fs.h> +#include <lume/file.h> +#include <lume/mount.h> +#include <lume/task.h> +#include <lume/errno.h> + +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<uint64>(-100), + reinterpret_cast<uint64>("/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 new file mode 100644 index 0000000..ee0e0e2 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/page_cache.h> +#include <lume/page.h> +#include <lume/pmm.h> +#include <lume/errno.h> + +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 new file mode 100644 index 0000000..e79eb1e --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/pid.h> + +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 new file mode 100644 index 0000000..ed375ed --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/errno.h> +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/fs_types.h> +#include <lume/task.h> +#include <lume/fdtable.h> +#include <lume/pipe.h> + +/* 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<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>(path), + static_cast<uint64>(flags), + 0, 0, 0); +} + +static int64 do_close(int64 fd) +{ + return sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0); +} + +static int64 do_read(int64 fd, void* buf, uint64 count) +{ + return sys_read(static_cast<uint64>(fd), + reinterpret_cast<uint64>(buf), count, 0, 0, 0); +} + +static int64 do_write(int64 fd, const void* buf, uint64 count) +{ + return sys_write(static_cast<uint64>(fd), + reinterpret_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(&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<uint64>(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<uint64>(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<uint64>(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<uint64>(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<uint64>(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<uint64>(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<uint64>(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<uint64>(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<uint64>(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)0); + + /* Set CLOEXEC */ + int64 ret = sys_fcntl(static_cast<uint64>(fd), F_SETFD, FD_CLOEXEC, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* Verify it was set */ + flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); + + /* Clear it */ + sys_fcntl(static_cast<uint64>(fd), F_SETFD, 0, 0, 0, 0); + flags = sys_fcntl(static_cast<uint64>(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<uint64>(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<uint64>(fd), F_SETFL, O_NONBLOCK, 0, 0, 0); + int64 fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0); + ST_ASSERT(fl & O_NONBLOCK); + + /* Clear it */ + sys_fcntl(static_cast<uint64>(fd), F_SETFL, 0, 0, 0, 0); + fl = sys_fcntl(static_cast<uint64>(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<uint64>(fd), F_DUPFD_CLOEXEC, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + + int64 flags = sys_fcntl(static_cast<uint64>(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<uint64>(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 index 53b2b2a..3330866 100644 --- a/na/test/test_pmm.cc +++ b/na/test/test_pmm.cc @@ -25,7 +25,7 @@ void selftest_pmm() { Page* page = pmm_alloc_page(); ST_ASSERT(page != nullptr); - ST_ASSERT(page->state == PageState::Allocated); + ST_ASSERT(page->type == PageType::Allocated); ST_ASSERT(page->order == 0); uint64 pa = page_to_pa(page).raw; @@ -123,11 +123,11 @@ void selftest_pmm() page_decref(page); // 2 -> 1, should NOT free ST_ASSERT(page->refcount.load() == 1); - ST_ASSERT(page->state == PageState::Allocated); + 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->state because it might be reused. + // We can't safely check page->type because it might be reused. } st_pass(); st_begin("pmm: pmm_alloc_pages natural alignment (order-3)"); diff --git a/na/test/test_reloc.cc b/na/test/test_reloc.cc deleted file mode 100644 index 54e7144..0000000 --- a/na/test/test_reloc.cc +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: relocation engine - */ - -#include <lume/relocate.h> -#include <lume/selftest.h> - -struct RelocBlob { - RelocTableHeader hdr; - RelocEntry entries[2]; -}; - -void selftest_reloc() -{ - st_begin("reloc: relative + abs64"); - - alignas(8) uint8 buf[64] = {}; - uint64 base = reinterpret_cast<uint64>(buf); - uint64 link_base = base - 0x1000; - - RelocBlob blob = {}; - blob.hdr.magic = kRelocTableMagic; - blob.hdr.version = kRelocTableVersion; - blob.hdr.arch = RELOC_ARCH_RISCV; - blob.hdr.entry_size = sizeof(RelocEntry); - blob.hdr.entry_count = 2; - - blob.entries[0].offset = 8; - blob.entries[0].addend = 0x200; - blob.entries[0].type = RELOC_RISCV_RELATIVE; - - blob.entries[1].offset = 16; - blob.entries[1].addend = link_base + 0x1234; - blob.entries[1].type = RELOC_RISCV_64; - - RelocStatus st = relocate_apply(base, sizeof(buf), link_base, - &blob, sizeof(blob)); - ST_ASSERT_EQ(st, RELOC_OK); - ST_ASSERT_EQ(*reinterpret_cast<uint64*>(base + 8), base + 0x200); - ST_ASSERT_EQ(*reinterpret_cast<uint64*>(base + 16), base + 0x1234); - - st_pass(); -} diff --git a/na/test/test_signal.cc b/na/test/test_signal.cc new file mode 100644 index 0000000..360b0e6 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/signal.h> +#include <lume/signal_types.h> +#include <lume/signal_nr.h> +#include <lume/task.h> +#include <lume/errno.h> +#include <lume/new.h> + +/* 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<sigqueue, &sigqueue::list>(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 index 2ba1a5d..8a448b4 100644 --- a/na/test/test_slab.cc +++ b/na/test/test_slab.cc @@ -203,7 +203,7 @@ void selftest_slab() uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast<uint64>(base_ptr))).raw; Page* page = pa_to_page(phys_addr(page_pa)); - ST_ASSERT(page->state == PageState::Slab); + ST_ASSERT(page->type == PageType::Slab); ST_ASSERT(page->slab.obj_count == 32); // Free all items in this isolated slab page @@ -212,7 +212,7 @@ void selftest_slab() } // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached). - ST_ASSERT(page->state == PageState::Free || page->state == PageState::PcpCached); + ST_ASSERT(page->type == PageType::Free || page->type == PageType::PcpCached); // Clean up the drain array for (int i = 0; i < drain_count; i++) { diff --git a/na/test/test_smp.cc b/na/test/test_smp.cc new file mode 100644 index 0000000..f934526 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/smp.h> +#include <lume/sched.h> +#include <lume/atomic.h> +#include <lume/config.h> +#include <arch/cpu.h> + +/* Shared counter incremented by APs when they receive an IPI */ +static lume::atomic<uint32> 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<uint32>(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<uint32>(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<uint32>(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_sys_fs_extra.cc b/na/test/test_sys_fs_extra.cc new file mode 100644 index 0000000..705ce63 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/errno.h> +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/fs_types.h> +#include <lume/task.h> + +/* 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<uint64>(-100), + reinterpret_cast<uint64>(path), + static_cast<uint64>(flags), 0, 0, 0); +} + +static int64 do_close(int64 fd) +{ + return sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0); +} + +static int64 do_read(int64 fd, void* buf, uint64 count) +{ + return sys_read(static_cast<uint64>(fd), + reinterpret_cast<uint64>(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<uint64>(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<uint64>(fd), + static_cast<uint64>(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<uint64>(saved_cwd), 64, 0, 0, 0, 0); + + /* chdir to /dev */ + int64 ret = sys_chdir( + reinterpret_cast<uint64>("/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<uint64>(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<uint64>("/"), 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<uint64>("/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<uint64>("/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<uint64>(-100), + reinterpret_cast<uint64>("/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<uint64>(fd), + reinterpret_cast<uint64>(iov), 2, 0, 0, 0); + ST_ASSERT_EQ(w, (int64)6); + + /* Seek back to start and read */ + sys_lseek(static_cast<uint64>(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<uint64>(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<uint64>(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<uint64>(-100), + reinterpret_cast<uint64>("/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<uint64>(buf), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + } + st_pass(); +} diff --git a/na/test/test_sys_process.cc b/na/test/test_sys_process.cc new file mode 100644 index 0000000..686bed6 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/errno.h> +#include <lume/task.h> +#include <lume/types.h> + +/* 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_uaccess.cc b/na/test/test_uaccess.cc index d5795fc..c5a8c6c 100644 --- a/na/test/test_uaccess.cc +++ b/na/test/test_uaccess.cc @@ -7,13 +7,13 @@ #include <lume/vmm.h> #include <lume/pmm.h> +#include <lume/tlb.h> +#include <lume/mm_layout.h> #include <arch/pmap.h> -constexpr uint64 kKernelVABase = 0xFFFFFFC000000000ULL; - void selftest_uaccess() { - /* To test valid user addresses (< kKernelVABase), we need a writable page mapped there. + /* 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. @@ -33,7 +33,7 @@ void selftest_uaccess() 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 */ - asm volatile("sfence.vma" : : : "memory"); + tlb_shootdown_all(); st_begin("uaccess: copy_to_user valid range"); char kernel_buf[32] = "Hello User Space!"; @@ -67,12 +67,12 @@ void selftest_uaccess() st_pass(); st_begin("uaccess: copy_to_user invalid range (crosses boundary)"); - ret = copy_to_user(kKernelVABase - 10, kernel_buf, 20); + 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); + ret = copy_to_user(kKernelVaBase + 0x1000, kernel_buf, 20); ST_ASSERT_EQ(ret, -EFAULT); st_pass(); @@ -82,15 +82,15 @@ void selftest_uaccess() st_pass(); st_begin("uaccess: copy_from_user invalid ranges"); - ret = copy_from_user(read_buf, kKernelVABase - 5, 10); + ret = copy_from_user(read_buf, kKernelVaBase - 5, 10); ST_ASSERT_EQ(ret, -EFAULT); - ret = copy_from_user(read_buf, kKernelVABase, 10); + 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; + 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 new file mode 100644 index 0000000..3d3b125 --- /dev/null +++ b/na/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 <lume/selftest.h> +#include <lume/errno.h> +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/fs_types.h> +#include <lume/task.h> +#include <lume/fdtable.h> + +/* 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<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>(path), + static_cast<uint64>(flags), + static_cast<uint64>(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<uint64>(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<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(msg), + 10, 0, 0, 0); + ST_ASSERT_EQ(written, (int64)10); + sys_close(static_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(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<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(buf), 64, 0, 0, 0); + ST_ASSERT_EQ(n1, (int64)10); + + /* Second read: should be 0 (EOF) */ + int64 n2 = sys_read(static_cast<uint64>(fd), + reinterpret_cast<uint64>(buf), 64, 0, 0, 0); + ST_ASSERT_EQ(n2, (int64)0); + + sys_close(static_cast<uint64>(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<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(buf), 16, 0, 0, 0); + ST_ASSERT_EQ(n, (int64)0); /* truncated = empty */ + sys_close(static_cast<uint64>(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<uint64>(buf), 8, 0, 0, 0); + ST_ASSERT_EQ(r, (int64)-EBADF); + + int64 w = sys_write(999, reinterpret_cast<uint64>(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<char>('A' + i); + sys_write(static_cast<uint64>(fd), + reinterpret_cast<uint64>(data), 20, 0, 0, 0); + + /* SEEK_SET to 5 */ + int64 pos = sys_lseek(static_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(c, 'F'); + + /* SEEK_CUR +3 → pos 9 */ + pos = sys_lseek(static_cast<uint64>(fd), 3, SEEK_CUR, 0, 0, 0); + ST_ASSERT_EQ(pos, (int64)9); + + /* SEEK_END -1 → pos 19 */ + pos = sys_lseek(static_cast<uint64>(fd), -1, SEEK_END, 0, 0, 0); + ST_ASSERT_EQ(pos, (int64)19); + + sys_read(static_cast<uint64>(fd), + reinterpret_cast<uint64>(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(c, 'T'); + + sys_close(static_cast<uint64>(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<uint64>(fd), 0, 99, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + sys_close(static_cast<uint64>(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<uint64>(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<uint64>(fd2), + reinterpret_cast<uint64>(&c), 1, 0, 0, 0); + ST_ASSERT_EQ(n, (int64)1); + + sys_close(static_cast<uint64>(fd1), 0, 0, 0, 0, 0); + sys_close(static_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(&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<uint64>(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<uint64>(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<uint64>(-100), + reinterpret_cast<uint64>("/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<uint64>(fd), + reinterpret_cast<uint64>(&st), 0, 0, 0, 0); + ST_ASSERT(S_ISDIR(st.st_mode)); + + sys_close(static_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(buf), + 512, 0, 0, 0); + ST_ASSERT(n > 0); /* At least "." and ".." */ + + sys_close(static_cast<uint64>(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<uint64>(fd), + reinterpret_cast<uint64>(buf), 1, 0, 0, 0); + ST_ASSERT_EQ(w, (int64)-EBADF); + sys_close(static_cast<uint64>(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<uint64>(fd), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(r1, (int64)0); + + int64 r2 = sys_close(static_cast<uint64>(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<uint64>(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<uint64>(fd2), 0, 0, 0, 0, 0); + } + st_pass(); +} diff --git a/na/test/test_vmm.cc b/na/test/test_vmm.cc index 049570f..635e3f3 100644 --- a/na/test/test_vmm.cc +++ b/na/test/test_vmm.cc @@ -6,12 +6,17 @@ */ #include <lume/selftest.h> +#include <lume/klog.h> +#include <lume/config.h> #include <lume/addr.h> #include <lume/vmm.h> #include <lume/pmm.h> #include <arch/config.h> -/* Assuming pmap namespace is accessible or we can test basic structures */ +namespace { +constexpr uint64 kUartBase = 0x10000000ULL; +constexpr uint64 kPageOffsetMask = kPageSize - 1; +} void selftest_vmm() { st_begin("vmm: virtual/physical address translation"); @@ -30,7 +35,7 @@ void selftest_vmm() { st_begin("vmm: page alignment macros"); { uint64 base = pmm_get_mem_base().raw; - uint64 addr = base + 0xFFF; + 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); @@ -40,8 +45,10 @@ void selftest_vmm() { st_begin("vmm: mapping kernel MMIO (driver API)"); { - uint64 mmio_pa = pmm_get_mem_base().raw; - vmm_map_kernel_mmio(mmio_pa, 0x1000); + /* 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; diff --git a/na/test/test_waitqueue.cc b/na/test/test_waitqueue.cc index 1bf88bb..7f50d6b 100644 --- a/na/test/test_waitqueue.cc +++ b/na/test/test_waitqueue.cc @@ -59,7 +59,7 @@ void selftest_waitqueue() kprintf("[test_wq] returned from yield 2\n"); ST_ASSERT(g_thread_woken == true); - + st_pass(); st_begin("waitqueue: wake_all"); diff --git a/na/usr/default_cpio_list b/na/usr/default_cpio_list new file mode 100644 index 0000000..6721365 --- /dev/null +++ b/na/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 <path> <mode> <uid> <gid> +# nod <path> <mode> <uid> <gid> <c|b> <maj> <min> +# file <path> <hostsrc> <mode> <uid> <gid> +# slink <path> <target> <mode> <uid> <gid> +# 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 new file mode 100644 index 0000000..b7296ed --- /dev/null +++ b/na/usr/gen_init_cpio.c @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <fcntl.h> +#include <errno.h> +#include <ctype.h> +#include <limits.h> + +/* + * 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 <timestamp>] [-c] [-o <output_file>] [-a <data_align>] <cpio_list>\n" + "\n" + "<cpio_list> 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 <name> <location> <mode> <uid> <gid> [<hard links>]\n" + "dir <name> <mode> <uid> <gid>\n" + "nod <name> <mode> <uid> <gid> <dev_type> <maj> <min>\n" + "slink <name> <target> <mode> <uid> <gid>\n" + "pipe <name> <mode> <uid> <gid>\n" + "sock <name> <mode> <uid> <gid>\n" + "\n" + "<name> name of the file/dir/nod/etc in the archive\n" + "<location> location of the file in the current filesystem\n" + " expands shell variables quoted with ${}\n" + "<target> link target\n" + "<mode> mode/permissions of the file\n" + "<uid> user id (0=root)\n" + "<gid> group id (0=root)\n" + "<dev_type> device type (b=block, c=character)\n" + "<maj> major number of nod\n" + "<min> minor number of nod\n" + "<hard links> 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" + "<timestamp> 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" + "<output_file>: write cpio to this file instead of stdout\n" + "<data_align>: 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); +} |
