diff options
Diffstat (limited to 'na')
313 files changed, 0 insertions, 44967 deletions
diff --git a/na/.clang-format b/na/.clang-format deleted file mode 100644 index 141e1e2..0000000 --- a/na/.clang-format +++ /dev/null @@ -1,35 +0,0 @@ -Language: Cpp -Standard: c++23 -BasedOnStyle: Google - -# Core Formatting -IndentWidth: 4 -ColumnLimit: 100 -TabWidth: 4 -UseTab: Never - -# Pointers and References -PointerAlignment: Left -ReferenceAlignment: Left - -# Includes -SortIncludes: true - -# Alignment -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignConsecutiveMacros: true - -# Control Statements -BreakBeforeBraces: Attach -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Empty - -# Spaces -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpaceAfterTemplateKeyword: true diff --git a/na/.clang-tidy b/na/.clang-tidy deleted file mode 100644 index 1af2c5a..0000000 --- a/na/.clang-tidy +++ /dev/null @@ -1,23 +0,0 @@ -Checks: > - -*, - clang-analyzer-*, - performance-*, - bugprone-*, - readability-*, - misc-*, - modernize-*, - -modernize-use-trailing-return-type, - -modernize-use-nodiscard, - -readability-identifier-length - -WarningsAsErrors: '' -HeaderFilterRegex: '.*' -FormatStyle: 'file' -CheckOptions: - readability-identifier-naming.ClassCase: PascalCase - readability-identifier-naming.StructCase: PascalCase - readability-identifier-naming.VariableCase: lower_case - readability-identifier-naming.FunctionCase: lower_case - readability-identifier-naming.GlobalVariableCase: lower_case - readability-identifier-naming.GlobalVariablePrefix: g_ - readability-identifier-naming.PrivateMemberSuffix: _ diff --git a/na/Makefile b/na/Makefile deleted file mode 100644 index 88689f5..0000000 --- a/na/Makefile +++ /dev/null @@ -1,419 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# LumeOS Kernel Makefile (Non-Recursive) -# -# Usage: -# make - Build kernel ELF -# make run - Run in QEMU -# make debug - Run in QEMU with GDB server -# make clean - Clean build artifacts -# make fmt - Format all source files -# make disasm - Generate disassembly listing -# -# Reference: docs/specs/00_conventions.md §7 - -# ============================================================ -# Build Configuration -# ============================================================ -BUILD := build - -# Make sure `make` with no arguments builds the kernel image, not the -# first rule encountered during makefile parsing. scripts/Kconfig.mk -# (included next) defines `defconfig:` at the top of its rule list, -# which would otherwise become the default goal and silently overwrite -# .config from arch/$(ARCH)/defconfig. -.DEFAULT_GOAL := all - -# Load Kbuild helpers (Q, $(call cmd,...), quiet_cmd_* / cmd_* templates, -# kbuild-objs translator). Must come before Kconfig.mk so subsequent -# rules can use $(call cmd,...) immediately. -include scripts/Kbuild.include - -# Load Kconfig infrastructure (reads .config, sets KCPPFLAGS) -include scripts/Kconfig.mk - -ARCH ?= $(CONFIG_ARCH) - -# Two boot products — same pair as Linux RISC-V's arch/riscv/boot/Makefile -# (vmlinux + Image), but named LumeOS-style: -# VMLUME -> build/vmlume.elf : ELF with .symtab/PT_DYNAMIC/.rela.dyn -# IMAGE -> build/vmlume.bin : raw binary (objcopy -O binary) -VMLUME := $(BUILD)/vmlume.elf -IMAGE := $(BUILD)/vmlume.bin - -# Initramfs paths -INITRAMFS_DIR := $(BUILD)/initramfs -INITRAMFS_CPIO := $(BUILD)/initramfs.cpio -INITRAMFS_OBJ := $(BUILD)/initramfs.o -USR_INIT_ELF := $(BUILD)/usr/init -# Relocations: linker emits .rela.dyn under -shared -Bsymbolic; boot -# code in arch/riscv/mm/init.cc::relocate_kernel walks it at startup. -LDS_SRC := arch/$(ARCH)/kernel/lumeos.lds.S -LINKER_SCRIPT := $(BUILD)/lumeos.lds -PYTHON ?= python3 - -# ============================================================ -# Toolchain Detection -# ============================================================ -ifndef TOOLCHAIN_PREFIX -ifeq ($(ARCH),riscv) - # Prefer the linux-gnu toolchain — it supports -shared / -pie which - # are required to emit .rela.dyn (Linux RISC-V kernel uses the same). - # The bare-metal `riscv64-unknown-elf-` is kept only as a last-resort - # fallback; with that one, `-shared` fails ("not supported") and the - # kernel can't relocate at runtime. - TOOLCHAIN_PREFIX := $(shell \ - if command -v riscv64-unknown-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-unknown-linux-gnu-; \ - elif command -v riscv64-linux-gnu-g++ >/dev/null 2>&1; then \ - echo riscv64-linux-gnu-; \ - else \ - echo riscv64-unknown-linux-gnu-; \ - fi) -else ifeq ($(ARCH),aarch64) - TOOLCHAIN_PREFIX := aarch64-linux-gnu- -else ifeq ($(ARCH),x86_64) - TOOLCHAIN_PREFIX := x86_64-linux-gnu- -endif -endif - -CC := $(TOOLCHAIN_PREFIX)gcc -CXX := $(TOOLCHAIN_PREFIX)g++ -LD := $(TOOLCHAIN_PREFIX)ld -OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy -OBJDUMP := $(TOOLCHAIN_PREFIX)objdump -# Objcopy output formats for binary-to-ELF relocation blob -OBJCOPY_ELF_riscv := elf64-littleriscv -OBJCOPY_ELF_aarch64 := elf64-littleaarch64 -OBJCOPY_ELF_x86_64 := elf64-x86-64 -OBJCOPY_ELF := $(OBJCOPY_ELF_$(ARCH)) - -OBJCOPY_BINARCH_riscv := riscv -OBJCOPY_BINARCH_aarch64 := aarch64 -OBJCOPY_BINARCH_x86_64 := i386:x86-64 -OBJCOPY_BINARCH := $(OBJCOPY_BINARCH_$(ARCH)) - -# Common flags (base, before module includes) -WARNINGS := -Wall -Wextra -Wno-unused-parameter -INCLUDES := -Iinclude -I. - -# ============================================================ -# Source Collection (Non-Recursive Module Fragments) -# Module .mk files may append to SRCS_CC, SRCS_S, INCLUDES -# ============================================================ -SRCS_CC := -SRCS_C := -SRCS_S := -SRCS_s := - --include arch/$(ARCH)/module.mk -include kernel/module.mk -include mm/module.mk -include fs/module.mk -include drivers/base/module.mk -include drivers/module.mk -include drivers/tty/module.mk -include fdt/module.mk -include lib/module.mk -include test/module.mk - -# Compiler flags (defined AFTER includes so INCLUDES is complete) -CFLAGS := $(WARNINGS) -O2 -g \ - -ffreestanding -nostdlib \ - -mcmodel=medany \ - -fPIE \ - -MMD -MP \ - $(KCPPFLAGS) \ - $(INCLUDES) \ - -I$(BUILD)/generated - -# -fPIE makes the compiler emit position-independent code (PC-relative -# addressing + GOT for globals), which is what `-shared` linking expects. -# Mirrors Linux's arch/riscv/Makefile under CONFIG_RELOCATABLE=y: -# KBUILD_CFLAGS += -fPIE - -# C flags for musl libc sources: add libc-shim headers first -CFLAGS_LIBC := $(CFLAGS) -Iinclude/libc-shim \ - -Wno-parentheses -Wno-sign-compare - -CXXFLAGS := $(CFLAGS) \ - -std=c++17 \ - -fno-exceptions -fno-rtti -fno-use-cxa-atexit \ - -fno-threadsafe-statics - -# Assembler flags MUST NOT include -fPIE. -# Linux's arch/riscv/Makefile adds -fPIE only to KBUILD_CFLAGS, NOT to -# KBUILD_AFLAGS: with -fPIE the gcc driver puts the assembler in PIC -# mode, where the `la` macro silently expands to a GOT-indirect load -# (`auipc + ld` reading a slot that holds the link-time VA). In our -# pre-MMU boot path that VA is unmapped, so any `la sym, sym` faults -# and reboots into a `_start` loop. Stripping -fPIE makes `la` emit -# direct PC-relative addressing (`auipc + addi`) which works at PA. -ASFLAGS := $(filter-out -fPIE,$(CFLAGS)) - -# Link as a position-independent shared object so the linker emits -# `.rela.dyn` with R_RISCV_RELATIVE entries that the boot stub applies -# at load time. Mirrors Linux's arch/riscv/Makefile §10: -# LDFLAGS_vmlinux += -shared -Bsymbolic -z notext -# `-Bsymbolic` resolves all global refs at link time (no dynamic -# resolution); `-z notext` lets us keep relocations in code pages. -LDFLAGS := -nostdlib -shared -Bsymbolic -z notext -z norelro --no-dynamic-linker - -# ============================================================ -# Object File Computation -# ============================================================ -OBJS := $(patsubst %.cc,$(BUILD)/%.o,$(SRCS_CC)) \ - $(patsubst %.c,$(BUILD)/%.o,$(SRCS_C)) \ - $(patsubst %.S,$(BUILD)/%.o,$(SRCS_S)) \ - $(patsubst %.s,$(BUILD)/%.o,$(SRCS_s)) - -DEPS := $(OBJS:.o=.d) - -# ============================================================ -# Build Rules -# ============================================================ -.PHONY: all clean run debug fmt disasm defconfig savedefconfig showconfig - -all: $(VMLUME) $(IMAGE) - -# Generate autoconf.h before any compilation -$(AUTOCONF_H): scripts/Kconfig.mk $(wildcard .config) - $(generate-autoconf) - -# ============================================================ -# Generated header: asm-offsets.h -# -# Linux-style trick (mirrors include/linux/kbuild.h + arch/*/kernel/ -# asm-offsets.c + scripts/Makefile.build): compile asm-offsets.c to -# assembly, scrape the magic `->SYM val` markers produced by the -# DEFINE/OFFSET inline-asm macros, rewrite them into `#define` -# directives via sed. head.S and friends then `#include -# <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 - @echo " LDS $@" - @mkdir -p $(dir $@) - @$(CC) -E -P -x c $(INCLUDES) $< -o $@ - -# ============================================================ -# Initramfs: pack a root filesystem into a newc cpio. -# -# Pipeline mirrors Linux's Kbuild: -# usr/gen_init_cpio — host C tool, produces cpio bytes -# scripts/gen_initramfs.sh — wrapper that prepares the listing, -# handles directories vs. listing files, -# applies uid/gid remap, then invokes the -# host tool. -# Sourced from $(CONFIG_INITRAMFS_SOURCE) — see scripts/Kconfig.mk. -# ============================================================ - -# Host compiler — distinct from $(CC), which may be the cross compiler. -HOSTCC ?= cc -HOSTCFLAGS ?= -O2 -Wall - -# Output path matches Linux convention (usr/gen_init_cpio) so the -# verbatim copy of scripts/gen_initramfs.sh from Linux finds it via its -# hardcoded relative path when invoked from the kernel source root. -GEN_INIT_CPIO := usr/gen_init_cpio - -$(GEN_INIT_CPIO): usr/gen_init_cpio.c - @echo " HOSTCC $@" - @$(HOSTCC) $(HOSTCFLAGS) -o $@ $< - -INITRAMFS_SOURCE := $(CONFIG_INITRAMFS_SOURCE) - -# Record the chosen source so changing CONFIG_INITRAMFS_SOURCE on the -# command line forces a rebuild — without this, `make` only compares -# file mtimes and would happily reuse a cpio packed from a different -# source as long as that other source isn't newer. Evaluated at -# Makefile parse time so the dep edge is correct from the first rule -# check. Mirrors the idea behind Linux's $(call if_changed,...). -INITRAMFS_SRC_STAMP := $(BUILD)/.initramfs_src.stamp -$(shell mkdir -p $(BUILD) 2>/dev/null; \ - prev=$$([ -f "$(INITRAMFS_SRC_STAMP)" ] && cat "$(INITRAMFS_SRC_STAMP)" || echo); \ - if [ "$$prev" != "$(INITRAMFS_SOURCE)" ]; then \ - printf '%s\n' "$(INITRAMFS_SOURCE)" > "$(INITRAMFS_SRC_STAMP)"; \ - fi) - -$(INITRAMFS_CPIO): $(INITRAMFS_SOURCE) $(INITRAMFS_SRC_STAMP) \ - $(GEN_INIT_CPIO) scripts/gen_initramfs.sh - @echo " INITRAMFS $(INITRAMFS_SOURCE)" - @mkdir -p $(dir $@) - @./scripts/gen_initramfs.sh -o $@ \ - -u $(CONFIG_INITRAMFS_ROOT_UID) \ - -g $(CONFIG_INITRAMFS_ROOT_GID) \ - $(INITRAMFS_SOURCE) - -$(INITRAMFS_OBJ): $(INITRAMFS_CPIO) - @echo " OBJCOPY $@ (initramfs)" - @$(OBJCOPY) -I binary -O $(OBJCOPY_ELF) -B $(OBJCOPY_BINARCH) \ - --rename-section .data=.init.ramfs,alloc,load,readonly,data,contents \ - $< $@ - -# Single-pass kernel link. With `-shared -Bsymbolic`, the linker -# emits `.rela.dyn` containing R_RISCV_RELATIVE entries directly into -# the output ELF — no separate gen_reloc pass. -$(VMLUME): $(OBJS) $(INITRAMFS_OBJ) $(LINKER_SCRIPT) - @echo " LD $@" - @mkdir -p $(dir $@) - @$(LD) $(LDFLAGS) -T $(LINKER_SCRIPT) -o $@ $(OBJS) $(INITRAMFS_OBJ) - @$(TOOLCHAIN_PREFIX)size $@ - -# Image = raw binary, mirrors Linux's `arch/riscv/boot/Image` (objcopy -# vmlinux to raw bytes, no ELF header — what U-Boot or QEMU's `-bios` -# bare-loading would consume). Linux always builds both vmlinux and -# Image; LumeOS follows the same convention. -$(IMAGE): $(VMLUME) - @echo " OBJCOPY $@" - @$(OBJCOPY) -O binary $< $@ - -# Compile C++ sources. Recipe goes through $(call cmd,...) so that -# `make V=1` echoes the full command line. $(Q) prefix on mkdir keeps -# the directory-creation step silent in quiet mode but visible at V=1. -$(BUILD)/%.o: %.cc - $(Q)mkdir -p $(dir $@) - $(call cmd,cxx_o_cc) - -$(BUILD)/%.o: %.c - $(Q)mkdir -p $(dir $@) - $(call cmd,cc_o_c) - -$(BUILD)/%.o: %.S - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) - -$(BUILD)/%.o: %.s - $(Q)mkdir -p $(dir $@) - $(call cmd,as_o_S) - -# Auto-dependency tracking --include $(DEPS) - -# ============================================================ -# Run & Debug -# ============================================================ -ifeq ($(ARCH),riscv) -QEMU := qemu-system-riscv64 -QEMU_MACHINE := -machine virt -smp 4 -m 128M -nographic -QEMU_BIOS := -bios default -else ifeq ($(ARCH),aarch64) -QEMU := qemu-system-aarch64 -QEMU_MACHINE := -machine virt -cpu cortex-a53 -m 128M -nographic -QEMU_BIOS := -else ifeq ($(ARCH),x86_64) -QEMU := qemu-system-x86_64 -QEMU_MACHINE := -m 128M -nographic -QEMU_BIOS := -endif - -run: $(VMLUME) $(IMAGE) - @echo " QEMU Running..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) - -debug: $(VMLUME) $(IMAGE) - @echo " QEMU Waiting for GDB on :1234..." - $(QEMU) $(QEMU_MACHINE) $(QEMU_BIOS) -kernel $(VMLUME) -s -S - -# ============================================================ -# Utilities -# ============================================================ -clean: - rm -rf $(BUILD) - -disasm: $(VMLUME) - @echo " OBJDUMP $<" - @$(OBJDUMP) -d -S $(VMLUME) > $(VMLUME).asm - @echo " Written to $(VMLUME).asm" - -fmt: - @echo " FMT Formatting source files..." - @find . -name '*.cc' -o -name '*.h' | xargs clang-format -i 2>/dev/null || true - -lint: - @echo " LINT Generating compile_commands.json via bear..." - @bear -- $(MAKE) all - @echo " LINT Running clang-tidy on C++ sources..." - @clang-tidy -p . $(SRCS_CC) || true - -# Print build info (for debugging Makefile issues) -info: - @echo "TOOLCHAIN_PREFIX = $(TOOLCHAIN_PREFIX)" - @echo "SRCS_CC = $(SRCS_CC)" - @echo "SRCS_S = $(SRCS_S)" - @echo "OBJS = $(OBJS)" - @echo "KCPPFLAGS = $(KCPPFLAGS)" diff --git a/na/arch/riscv/config.cc b/na/arch/riscv/config.cc deleted file mode 100644 index a18a825..0000000 --- a/na/arch/riscv/config.cc +++ /dev/null @@ -1,276 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V Architecture Configuration — runtime state and KASLR - * - * Provides the runtime (post-KASLR) values of the higher-half - * virtual address bases. kaslr_init() is called once during - * early boot, before vmm_init(), to randomise the layout and - * fill the unified KaslrLayout structure. - * - * KASLR slide budget: - * The default Direct Map starts at 0xFFFFFFC0_0000_0000 (VPN[2]=256). - * The default Vmemmap starts at 0xFFFFFFD0_0000_0000 (VPN[2]=320). - * A 1GB superpage occupies one VPN[2] slot. We allow a slide of - * 0..63 slots (0..63 GB) upward, which keeps both regions inside - * the upper canonical half and avoids colliding with the kernel - * text region (linked at VPN[2] = 258 + 2 = ~0xFFFFFFC0_8020_0000). - * - * In practice, with 128MB RAM the kernel+direct map only touches - * VPN[2] 258-259, so a slide of up to 63GB is safe. - */ - -#include <arch/config.h> -#include <lume/fdt.h> -#include <lume/page.h> -#include <lume/config.h> -#include <lume/klog.h> -#include <lume/kprintf.h> -#include <lume/random.h> - -extern "C" char _stext[], _kernel_end[]; - -namespace arch { - -/* Global KASLR Layout Structure*/ -KaslrLayout g_kaslr_layout = {}; - -/* Runtime bases — updated by kaslr_layout_compute() */ -uint64 g_direct_map_base = kDirectMapBaseDefault; -uint64 g_vmemmap_base = kVmemmapBaseDefault; - -/* VA Range for validation */ -struct VaRange { - const char* name; - uint64 start; - uint64 end; // exclusive -}; - -/* ================================================================ - * Utilities - * ================================================================ */ -static inline uint64 align_down_page(uint64 v) -{ - return v & ~(kPageSize - 1); -} - -static inline uint64 align_up_page(uint64 v) -{ - return (v + kPageSize - 1) & ~(kPageSize - 1); -} - -static uint64 checked_add_u64(uint64 a, uint64 b, const char* detail) -{ - if (a > (~0ULL - b)) { - kernel_panic("kaslr: address overflow", detail); - } - return a + b; -} - -/* Splitmix64: non-plaintext hash derived from seed (used for seed_hash) */ -static uint64 splitmix64(uint64 x) -{ - x += 0x9e3779b97f4a7c15ULL; - x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; - x = x ^ (x >> 27); - return x * 0x94d049bb133111ebULL; -} - -/* SV39 canonical VA check: bits [63:39] must equal sign bit 38. */ -static bool is_sv39_canonical(uint64 va) -{ - uint64 sign = (va >> 38) & 1ULL; - uint64 upper = va >> 39; - return sign ? (upper == ((1ULL << 25) - 1ULL)) : (upper == 0); -} - -static bool is_sv39_kernel_half(uint64 va) -{ - return is_sv39_canonical(va) && (((va >> 38) & 1ULL) == 1ULL); -} - -static bool ranges_overlap(const VaRange& a, const VaRange& b) -{ - return (a.start < b.end) && (b.start < a.end); -} - -/* ================================================================ - * KASLR Computation (Phase A: Centralized in single function) - * - * This function: - * 1. Generates or derives random seed - * 2. Selects 1GB-aligned slot [0..63] - * 3. Computes seed_hash via splitmix64 - * 4. Fills KaslrLayout structure completely - * 5. Validates ranges (canonical, overlaps) - * 6. Logs according to CONFIG_KASLR_LOG_LEVEL - * ================================================================ */ -static void kaslr_layout_compute() -{ -#ifdef CONFIG_KASLR - constexpr uint64 k1GB = 1ULL << 30; - constexpr uint64 kMaxSlots = 64; - - /* 1. Get seed and source */ - uint64 seed = random_early_seed_value(); - RandomSeedSource rsrc = random_early_seed_source(); - KaslrSeedSource source; - switch (rsrc) { - case RandomSeedSource::FdtProvided: - source = KaslrSeedSource::kFdt; - break; - case RandomSeedSource::HwRandom: - source = KaslrSeedSource::kHardware; - break; - case RandomSeedSource::CycleFallback: - case RandomSeedSource::Unknown: - default: - source = KaslrSeedSource::kCycle; - break; - } - - /* 2. Select random slot and compute slide */ - uint64 raw = get_random_u64(); - uint64 slot = raw % kMaxSlots; - if (slot == 0) { - slot = 1; /* slot=0 causes direct_map to overlap with kernel image VA */ - } - uint64 slide = slot * k1GB; - - /* 3. Compute non-plaintext hash */ - uint64 seed_hash = splitmix64(seed ^ 0xdeadbeefdeadbeefULL); - - /* 4. Compute randomised bases */ - uint64 direct_map_base = kDirectMapBaseDefault + slide; - uint64 vmemmap_base = kVmemmapBaseDefault + slide; - - /* 5. Fill KaslrLayout */ - g_kaslr_layout.seed = seed; - g_kaslr_layout.seed_hash = seed_hash; - g_kaslr_layout.source = source; - g_kaslr_layout.slot = slot; - g_kaslr_layout.slide_bytes = slide; - g_kaslr_layout.policy_id = kKaslrPolicyId; - g_kaslr_layout.direct_map_base = direct_map_base; - g_kaslr_layout.vmemmap_base = vmemmap_base; - g_kaslr_layout.canonical_ok = false; - g_kaslr_layout.overlap_ok = false; - - /* Update global runtime bases */ - g_direct_map_base = direct_map_base; - g_vmemmap_base = vmemmap_base; - - /* 6. Self-check: canonical VA validation and overlap detection */ - uint64 mem_base = 0, mem_size = 0; - fdt_early_get_mem_info(&mem_base, &mem_size); - if (mem_size == 0) { - kernel_panic("kaslr_layout_compute: invalid memory from FDT"); - } - - /* Construct VA ranges */ - uint64 num_pages = mem_size / kPageSize; - uint64 page_bytes = num_pages * sizeof(Page); - uint64 page_span = align_up_page(page_bytes); - - VaRange direct_map { - "direct-map", - checked_add_u64(g_direct_map_base, mem_base, "direct_map start"), - checked_add_u64(checked_add_u64(g_direct_map_base, mem_base, - "direct_map start"), - mem_size, "direct_map end"), - }; - - VaRange vmemmap { - "vmemmap", - g_vmemmap_base, - checked_add_u64(g_vmemmap_base, page_span, "vmemmap end"), - }; - - VaRange kernel_img { - "kernel-image", - align_down_page(reinterpret_cast<uint64>(_stext)), - align_up_page(reinterpret_cast<uint64>(_kernel_end)), - }; - - /* Validate each range is canonical */ - auto check_kernel_half = [](const VaRange& r) { - if (r.start >= r.end) { - kernel_panic("kaslr: invalid VA range", r.name); - } - if (!is_sv39_kernel_half(r.start) || !is_sv39_kernel_half(r.end - 1)) { - kernel_panic("kaslr: non-canonical kernel VA range", r.name); - } - }; - - check_kernel_half(direct_map); - check_kernel_half(vmemmap); - check_kernel_half(kernel_img); - - /* Validate no overlaps */ - if (ranges_overlap(direct_map, kernel_img)) { - kernel_panic("kaslr: direct-map overlaps kernel image"); - } - if (ranges_overlap(vmemmap, kernel_img)) { - kernel_panic("kaslr: vmemmap overlaps kernel image"); - } - if (ranges_overlap(direct_map, vmemmap)) { - kernel_panic("kaslr: direct-map overlaps vmemmap"); - } - - /* Mark validation success */ - g_kaslr_layout.canonical_ok = true; - g_kaslr_layout.overlap_ok = true; - - /* 7. Logging according to CONFIG_KASLR_LOG_LEVEL */ - if (kKaslrLogLevel >= 1) { - /* Minimal logging: just the seed and slot (release mode) */ - kprintf("[boot][kaslr] policy_id=%u source=%s slot=%llu\n", - g_kaslr_layout.policy_id, - random_seed_source_name(rsrc), - slot); - } - - if (kKaslrLogLevel >= 2) { - /* Debug logging: full ranges and hashes */ - kprintf("[boot][kaslr] seed=0x%llx seed_hash=0x%llx\n", - seed, seed_hash); - kprintf("[boot][kaslr] direct-map=[0x%llx,0x%llx)\n", - direct_map.start, direct_map.end); - kprintf("[boot][kaslr] vmemmap=[0x%llx,0x%llx)\n", - vmemmap.start, vmemmap.end); - kprintf("[boot][kaslr] kernel-image=[0x%llx,0x%llx)\n", - kernel_img.start, kernel_img.end); - } - -#else /* CONFIG_KASLR disabled */ - g_kaslr_layout.seed = 0; - g_kaslr_layout.seed_hash = 0; - g_kaslr_layout.source = KaslrSeedSource::kCycle; - g_kaslr_layout.slot = 0; - g_kaslr_layout.slide_bytes = 0; - g_kaslr_layout.policy_id = kKaslrPolicyId; - g_kaslr_layout.direct_map_base = kDirectMapBaseDefault; - g_kaslr_layout.vmemmap_base = kVmemmapBaseDefault; - g_kaslr_layout.canonical_ok = true; - g_kaslr_layout.overlap_ok = true; - - g_direct_map_base = kDirectMapBaseDefault; - g_vmemmap_base = kVmemmapBaseDefault; - - if (kKaslrLogLevel >= 1) { - kprintf("[boot][kaslr] disabled policy_id=%u\n", - g_kaslr_layout.policy_id); - } -#endif -} - -/* ================================================================ - * Public entry point - * ================================================================ */ -void kaslr_init() -{ - kaslr_layout_compute(); -} - -} // namespace arch diff --git a/na/arch/riscv/defconfig b/na/arch/riscv/defconfig deleted file mode 100644 index 87d3ffd..0000000 --- a/na/arch/riscv/defconfig +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# LumeOS Default Kernel Configuration — RISC-V (virt) -# -# This is the "golden" default configuration for the RISC-V virt platform. -# To customise, copy this file to the source root as '.config': -# -# cp arch/riscv/defconfig .config -# $EDITOR .config -# make -# -# Any variable not set here inherits its default from scripts/Kconfig.mk. -# ====================================================================== - -# --- Platform ----------------------------------------------------------- -CONFIG_ARCH = riscv - -# --- CPU / SMP ---------------------------------------------------------- -CONFIG_NR_CPUS = 8 - -# --- Scheduler ---------------------------------------------------------- -CONFIG_HZ = 100 -CONFIG_NR_PRIORITIES = 32 - -# --- Memory Management -------------------------------------------------- -CONFIG_PAGE_SIZE = 4096 -CONFIG_MAX_ORDER = 11 -CONFIG_KERNEL_STACK_SIZE = 8192 - -# --- Security / Hardening ----------------------------------------------- -CONFIG_KASLR = y -CONFIG_STACK_CANARY = y - -# --- Debug --------------------------------------------------------------- -CONFIG_DEBUG = y -CONFIG_DEBUG_LOCKS = y -CONFIG_SELFTEST = y -CONFIG_KLOG = y diff --git a/na/arch/riscv/include/arch/config.h b/na/arch/riscv/include/arch/config.h deleted file mode 100644 index 8ae0502..0000000 --- a/na/arch/riscv/include/arch/config.h +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * RISC-V Architecture Configuration - * - * These constants define the architecture-specific memory layout. - * Each arch/ implementation must provide this header with the same symbols. - * - * KASLR: At boot, kaslr_init() randomises g_direct_map_base and - * g_vmemmap_base. All MI code must read these runtime variables - * (via pa_to_va / vmm helpers) rather than the static defaults. - */ - -#include <lume/types.h> - -namespace arch { - -/* ================================================================ - * Default (compile-time) virtual addresses - * - * SV39 gives a 512GB virtual address space. - * Higher-half layout (defaults before KASLR): - * - * 0xFFFFFFC0_00000000 (256) Direct map (PA+offset) - * 0xFFFFFFD0_00000000 (320) Vmemmap (Page array) - * - * The 64GB gap between them is the KASLR slide range. - * ================================================================ */ -inline constexpr uint64 kDirectMapBaseDefault = 0xFFFFFFC000000000ULL; -inline constexpr uint64 kVmemmapBaseDefault = 0xFFFFFFD000000000ULL; - -/* Physical memory base (where RAM starts on RISC-V virt platform) */ -inline constexpr uint64 kPhysBase = 0x80000000ULL; - -/* Kernel physical load address (= PHYS_BASE in linker script). - * Everything in [kPhysBase, kPhysStart) is firmware territory (PMP-protected). - * Analogous to Linux's CONFIG_PHYSICAL_START. */ -inline constexpr uint64 kPhysStart = 0x80200000ULL; - -/* ================================================================ - * Runtime (post-KASLR) virtual addresses - * - * These start equal to the defaults above. If CONFIG_KASLR is - * enabled, kaslr_init() adds a random, 1GB-aligned offset - * to each, sliding both regions upward in VA space. - * ================================================================ */ -extern uint64 g_direct_map_base; -extern uint64 g_vmemmap_base; - -/* ================================================================ - * KASLR Layout Structure (Phase A: Unified KASLR State) - * - * This struct holds all KASLR results: the random seed, derived - * hash, selected slot, and the randomised virtual address bases. - * It serves as the single source of truth for all KASLR decisions - * and is filled once during boot and read-only thereafter. - * ================================================================ */ - -enum class KaslrSeedSource : uint8 { - kFdt = 0, /* Loaded from FDT /random node */ - kHardware = 1, /* From hardware RNG (e.g., RDRAND) */ - kCycle = 2, /* Fallback: CPU cycle counter */ -}; - -struct KaslrLayout { - /* Seed and provenance */ - uint64 seed; /* 64-bit random seed value */ - uint64 seed_hash; /* Non-plaintext derived hash (splitmix64) */ - KaslrSeedSource source; /* Seed provenance (fdt/hw/cycle) */ - - /* Randomisation parameters */ - uint64 slot; /* Slot index: [0, 63] */ - uint64 slide_bytes; /* Byte offset: slot * 1GB */ - - /* Build-time policy */ - uint32 policy_id; /* CONFIG_KASLR_POLICY_ID at build time */ - - /* Randomised base addresses (post-KASLR) */ - uint64 direct_map_base; /* Direct map VA (kDirectMapBaseDefault + slide) */ - uint64 vmemmap_base; /* Vmemmap VA (kVmemmapBaseDefault + slide) */ - - /* Validation flags (for debug logging) */ - bool canonical_ok; /* All VA ranges passed canonical check */ - bool overlap_ok; /* No overlaps detected between regions */ -}; - -/* ================================================================ - * KASLR entry point - * - * Must be called AFTER random_early_init() and BEFORE vmm_init(). - * Fills g_kaslr_layout with randomised bases and self-check results. - * When CONFIG_KASLR is disabled this is a no-op. - * ================================================================ */ -extern KaslrLayout g_kaslr_layout; - -void kaslr_init(); - -/* Helper to query the KASLR layout safely */ -static inline const KaslrLayout& kaslr_layout_get() { - return g_kaslr_layout; -} - -} // namespace arch diff --git a/na/arch/riscv/include/arch/context.h b/na/arch/riscv/include/arch/context.h deleted file mode 100644 index 3d935fa..0000000 --- a/na/arch/riscv/include/arch/context.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Context Switch Structure (RISC-V 64) - * - * Holds only callee-saved registers for voluntary (non-preemptive) - * context switches between kernel threads / tasks. - * - * Used by: arch::context_switch() in swtch.S - * Reference: docs/specs/arch_abstraction.md §3.1 - */ - -#include <lume/types.h> - -namespace arch { - -struct Context { - uint64 ra; // 0: Return address - uint64 sp; // 8: Stack pointer - uint64 s[12]; // 16: s0-s11 (callee-saved registers) -}; - -static_assert(sizeof(Context) == 112); - -// Assembly-implemented context switch. -// Saves current CPU registers to old_ctx, loads next_ctx into CPU. -// Defined in arch/riscv/trap/swtch.S -extern "C" void context_switch(Context* old_ctx, Context* next_ctx); - -} // namespace arch diff --git a/na/arch/riscv/include/arch/cpu.h b/na/arch/riscv/include/arch/cpu.h deleted file mode 100644 index 8c573ec..0000000 --- a/na/arch/riscv/include/arch/cpu.h +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * CPU Control & Status Primitives (RISC-V 64) - * - * Provides interrupt enable/disable, CPU ID, timer, and halt. - * All functions are static inline — zero call overhead. - * - * Precondition: tp register must be initialized with CPU ID - * by entry.S before any of these functions are called. - * - * Reference: docs/specs/arch_abstraction.md §4.1 - */ - -#include <lume/types.h> - -/* RISC-V sstatus register bit definitions */ -#define SSTATUS_SIE (1UL << 1) /* Supervisor Interrupt Enable */ -#define SSTATUS_SPIE (1UL << 5) /* Supervisor Previous Interrupt Enable */ -#define SSTATUS_SPP (1UL << 8) /* Supervisor Previous Privilege (0=U, 1=S) */ - -/* RISC-V sie register bit definitions */ -#define SIE_SSIE (1UL << 1) /* Supervisor Software Interrupt Enable */ -#define SIE_STIE (1UL << 5) /* Supervisor Timer Interrupt Enable */ -#define SIE_SEIE (1UL << 9) /* Supervisor External Interrupt Enable */ - -namespace arch::cpu { - -// Get current CPU ID (from tp register, set by entry.S) -static inline uint64 id() { - uint64 val; - __asm__ volatile("mv %0, tp" : "=r"(val)); - return val; -} - -// Enable interrupts (set sstatus.SIE = 1) -static inline void intr_on() { - __asm__ volatile("csrs sstatus, %0" :: "r"(SSTATUS_SIE)); -} - -// Disable interrupts (clear sstatus.SIE = 0) -static inline void intr_off() { - __asm__ volatile("csrc sstatus, %0" :: "r"(SSTATUS_SIE)); -} - -// Check if interrupts are currently enabled -static inline bool intr_enabled() { - uint64 sstatus; - __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); - return (sstatus & SSTATUS_SIE) != 0; -} - -// Halt CPU until next interrupt (wfi instruction) -static inline void halt_until_interrupt() { - __asm__ volatile("wfi"); -} - -// Read hardware monotonic timer (rdtime instruction) -// Returns ticks since boot; frequency from FDT timebase-frequency. -static inline uint64 read_time() { - uint64 val; - __asm__ volatile("rdtime %0" : "=r"(val)); - return val; -} - -// Enable timer interrupt in sie (sie.STIE = 1) -static inline void timer_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_STIE)); -} - -// Disable timer interrupt in sie (sie.STIE = 0) -static inline void timer_intr_off() { - __asm__ volatile("csrc sie, %0" :: "r"(SIE_STIE)); -} - -// Enable software interrupt in sie (sie.SSIE = 1) — needed for IPI -static inline void software_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_SSIE)); -} - -// Enable external interrupt in sie (sie.SEIE = 1) — needed for PLIC (UART, etc.) -static inline void external_intr_on() { - __asm__ volatile("csrs sie, %0" :: "r"(SIE_SEIE)); -} - -// Clear S-mode software interrupt pending bit (sip.SSIP = 0) -// Must be called in the IPI handler to acknowledge the interrupt. -static inline void clear_sip_ssip() { - __asm__ volatile("csrc sip, %0" :: "r"(1UL << 1)); -} - -} // namespace arch::cpu diff --git a/na/arch/riscv/include/arch/extable.h b/na/arch/riscv/include/arch/extable.h deleted file mode 100644 index eb2daac..0000000 --- a/na/arch/riscv/include/arch/extable.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#ifndef __ASSEMBLER__ - -#include <lume/types.h> - -/* - * Exception table entry. - * insn: offset from this struct to the faulting instruction. - * fixup: offset from this struct to the fixup code. - * - * Using relative offsets avoids absolute relocations and saves space. - */ -struct ExceptionTableEntry { - int32 insn; - int32 fixup; -}; - -/* Search the exception table for a given PC. Returns fixup address, or 0 if not found. */ -uint64 search_extable(uint64 pc); - -/* Sorts the exception table at boot for binary search O(log N). */ -void extable_init(); - -#endif /* __ASSEMBLER__ */ - -/* - * EXTABLE macro for assembly usage. - * Appends an entry to the .extable section. - */ -#ifdef __ASSEMBLER__ - -#define EXTABLE(insn, fixup) \ - .pushsection .extable, "a"; \ - .balign 4; \ - .long insn - .; \ - .long fixup - .; \ - .popsection - -#else /* !__ASSEMBLER__ */ - -/* Stringified macro for inline assembly */ -#define _ASM_EXTABLE(insn, fixup) \ - ".pushsection .extable, \"a\"\n" \ - ".balign 4\n" \ - ".long " #insn " - .\n" \ - ".long " #fixup " - .\n" \ - ".popsection\n" - -#define EXTABLE(insn, fixup) _ASM_EXTABLE(insn, fixup) - -#endif /* __ASSEMBLER__ */ diff --git a/na/arch/riscv/include/arch/fpstate.h b/na/arch/riscv/include/arch/fpstate.h deleted file mode 100644 index 4b42a22..0000000 --- a/na/arch/riscv/include/arch/fpstate.h +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FPU State — Floating-Point Register Context - * - * Pre-reserved structure for RISC-V F/D extension floating-point state. - * MUST be allocated in every TaskControlBlock so that future FPU - * save/restore logic has a stable destination without restructuring TCB. - * - * RISC-V F/D extension state: - * - 32 FP registers (f0-f31), each 64 bits for D extension - * - fcsr (Floating-point Control and Status Register) - * - * sstatus.FS field management: - * - FS=0 (Off): FPU disabled, any FP instruction traps - * - FS=1 (Initial): FPU enabled, registers are initial state - * - FS=2 (Clean): FPU enabled, registers not modified since last save - * - FS=3 (Dirty): FPU enabled, registers modified (need save) - * - * Current status: Structure defined, save/restore NOT implemented. - * When implemented, trap_dispatch and swtch.S will use lazy - * save/restore based on sstatus.FS dirty tracking. - * - * Reference: RISC-V ISA Manual §3.1.6.5 (FS field) - */ - -#include <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 deleted file mode 100644 index f4c9b26..0000000 --- a/na/arch/riscv/include/arch/mmu.h +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * MMU Hardware Primitives (RISC-V SV39) - * - * Page table switching, TLB flushing, and fault address reading. - * All functions are static inline — zero call overhead. - * - * Note on SMP: flush_tlb_page() only flushes the LOCAL CPU's TLB. - * Cross-CPU TLB shootdown via IPI is not yet implemented. - * - * Reference: docs/specs/arch_abstraction.md §4.2 - */ - -#include <lume/types.h> - -/* SATP register: mode field for SV39 */ -#define SATP_SV39 (8UL << 60) - -/* SATP PPN mask (bits 43:0) */ -#define SATP_PPN_MASK ((1UL << 44) - 1) - -namespace arch::mmu { - -// Read the faulting virtual address from stval (set by HW on page fault) -static inline uint64 get_fault_address() { - uint64 val; - __asm__ volatile("csrr %0, stval" : "=r"(val)); - return val; -} - -// Switch the top-level page table. -// Takes a PHYSICAL ADDRESS (not PPN); internally shifts right by 12 -// and combines with SV39 mode bits before writing to satp. -static inline void set_page_table(uint64 physical_pgdir_addr) { - uint64 ppn = physical_pgdir_addr >> 12; - uint64 satp_val = SATP_SV39 | ppn; - __asm__ volatile("csrw satp, %0" :: "r"(satp_val)); - // Note: caller should call flush_tlb_all() after this -} - -// Read the current page table physical address. -// Extracts PPN from satp and converts back to physical address. -static inline uint64 get_page_table() { - uint64 satp_val; - __asm__ volatile("csrr %0, satp" : "=r"(satp_val)); - return (satp_val & SATP_PPN_MASK) << 12; -} - -// Read the raw satp CSR value (mode + PPN). -// Used when the caller needs the exact register value (e.g. for TrapFrame metadata). -static inline uint64 read_satp() { - uint64 val; - __asm__ volatile("csrr %0, satp" : "=r"(val)); - return val; -} - -// Flush the entire TLB (all ASIDs, all addresses) -static inline void flush_tlb_all() { - __asm__ volatile("sfence.vma zero, zero" ::: "memory"); -} - -// Flush a single TLB entry for the given virtual address -static inline void flush_tlb_page(uint64 vaddr) { - __asm__ volatile("sfence.vma %0, zero" :: "r"(vaddr) : "memory"); -} - -// Flush the instruction cache. -// Required after loading executable code into memory (e.g. ELF loading). -// sfence.vma only flushes the TLB; fence.i is needed for the I-cache. -static inline void flush_icache() { - __asm__ volatile("fence.i" ::: "memory"); -} - -} // namespace arch::mmu diff --git a/na/arch/riscv/include/arch/pmap.h b/na/arch/riscv/include/arch/pmap.h deleted file mode 100644 index 0b43b9e..0000000 --- a/na/arch/riscv/include/arch/pmap.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * pmap — SV39 page table interface (MD layer) - * - * Namespace functions, zero runtime overhead. - * Implementation: arch/riscv/mm/pmap.cc - * - * Reference: docs/specs/vmm.md §3.2 - */ - -#include <lume/types.h> - -namespace pmap { - -/* PTE permission bits */ -inline constexpr uint64 PTE_V = 1ULL << 0; -inline constexpr uint64 PTE_R = 1ULL << 1; -inline constexpr uint64 PTE_W = 1ULL << 2; -inline constexpr uint64 PTE_X = 1ULL << 3; -inline constexpr uint64 PTE_U = 1ULL << 4; -inline constexpr uint64 PTE_G = 1ULL << 5; -inline constexpr uint64 PTE_A = 1ULL << 6; -inline constexpr uint64 PTE_D = 1ULL << 7; -inline constexpr uint64 PTE_COW = 1ULL << 8; /* Custom RSW bit for Copy-On-Write */ - -uint64 create(); -void destroy(uint64 root_pa); -int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); -int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm); -void unmap(uint64 root_pa, uint64 va); -int protect(uint64 root_pa, uint64 va, uint64 perm); -int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm); -bool lookup(uint64 root_pa, uint64 va, uint64* pa_out); -uint64 lookup_pte(uint64 root_pa, uint64 va); -bool is_cow(uint64 root_pa, uint64 va); -int make_cow(uint64 root_pa, uint64 va); -void activate(uint64 root_pa); - -/* - * Translate MI-layer VM_* permission flags to hardware PTE bits. - * Called by vma.cc when faulting in a page. - */ -uint64 vm_perm_to_pte(uint64 vm_perm); - -} // namespace pmap diff --git a/na/arch/riscv/include/arch/random.h b/na/arch/riscv/include/arch/random.h deleted file mode 100644 index 8c8c051..0000000 --- a/na/arch/riscv/include/arch/random.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -namespace arch { - -/* - * Get a random 64-bit value from the hardware TRNG. - * Returns true if supported and successful, false otherwise. - */ -bool get_hw_random(uint64& value); - -/* - * Get the current high-resolution hardware cycle counter. - * Used as an entropy fallback (Jitter/Timer). - */ -inline uint64 get_cycles() -{ - uint64 cycles; - __asm__ volatile("rdcycle %0" : "=r"(cycles)); - return cycles; -} - -} // namespace arch diff --git a/na/arch/riscv/include/arch/sbi.h b/na/arch/riscv/include/arch/sbi.h deleted file mode 100644 index 80a6d7c..0000000 --- a/na/arch/riscv/include/arch/sbi.h +++ /dev/null @@ -1,111 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * sbi.h — RISC-V Supervisor Binary Interface (SBI) wrappers - * - * Provides inline ecall wrappers for SBI calls from S-mode. - * Uses the SBI v1.0+ extension-based calling convention. - * - * Reference: RISC-V SBI Specification v2.0 - */ - -#include <lume/types.h> - -namespace arch::sbi { - -struct sbiret { - long error; - long value; -}; - -/* SBI error codes */ -inline constexpr long SBI_SUCCESS = 0; -inline constexpr long SBI_ERR_NOT_SUPPORTED = -2; - -/* Extension IDs */ -inline constexpr int SBI_EXT_TIME = 0x54494D45; /* "TIME" */ -inline constexpr int SBI_EXT_IPI = 0x735049; /* "sPI" */ -inline constexpr int SBI_EXT_RFENCE = 0x52464E43; /* "RFNC" */ -inline constexpr int SBI_EXT_HSM = 0x48534D; /* "HSM" */ - -static inline sbiret sbi_ecall(int ext, int fid, uint64 arg0 = 0, - uint64 arg1 = 0, uint64 arg2 = 0, - uint64 arg3 = 0, uint64 arg4 = 0, - uint64 arg5 = 0) { - sbiret ret; - register uint64 a0 asm("a0") = arg0; - register uint64 a1 asm("a1") = arg1; - register uint64 a2 asm("a2") = arg2; - register uint64 a3 asm("a3") = arg3; - register uint64 a4 asm("a4") = arg4; - register uint64 a5 asm("a5") = arg5; - register uint64 a6 asm("a6") = fid; - register uint64 a7 asm("a7") = ext; - __asm__ volatile("ecall" - : "+r"(a0), "+r"(a1) - : "r"(a2), "r"(a3), "r"(a4), "r"(a5), "r"(a6), "r"(a7) - : "memory"); - ret.error = static_cast<long>(a0); - ret.value = static_cast<long>(a1); - return ret; -} - -/* ---- Timer Extension (EID 0x54494D45) ---- */ - -static inline void set_timer(uint64 stime_value) { - sbi_ecall(SBI_EXT_TIME, 0, stime_value); -} - -/* ---- IPI Extension (EID 0x735049) ---- */ - -/* - * sbi_send_ipi — Send S-mode software interrupt to target harts - * - * @hart_mask: bitmask of target harts (relative to hart_mask_base) - * @hart_mask_base: base hart ID for the mask (0 = harts 0-63) - */ -static inline sbiret sbi_send_ipi(uint64 hart_mask, - uint64 hart_mask_base = 0) { - return sbi_ecall(SBI_EXT_IPI, 0, hart_mask, hart_mask_base); -} - -/* ---- Remote Fence Extension (EID 0x52464E43) ---- */ - -/* - * sbi_remote_sfence_vma — Execute SFENCE.VMA on target harts - * - * @hart_mask/base: target harts - * @start_addr: start of VA range to invalidate - * @size: size of range (use ~0UL for full flush) - */ -static inline sbiret sbi_remote_sfence_vma(uint64 hart_mask, - uint64 hart_mask_base, - uint64 start_addr, - uint64 size) { - return sbi_ecall(SBI_EXT_RFENCE, 1, - hart_mask, hart_mask_base, - start_addr, size); -} - -/* ---- HSM Extension (EID 0x48534D) ---- */ - -/* - * sbi_hart_start — Start (wake) a hart that is in STOPPED state - * - * @hartid: target hart ID to start - * @start_addr: PHYSICAL address where the hart should begin execution - * @opaque: passed in a1 register to the hart (e.g. FDT pointer) - * - * OpenSBI v1.0+ parks non-boot harts; they must be explicitly started. - */ -static inline sbiret sbi_hart_start(uint64 hartid, - uint64 start_addr, - uint64 opaque) { - return sbi_ecall(SBI_EXT_HSM, 0, hartid, start_addr, opaque); -} - -} // namespace arch::sbi - diff --git a/na/arch/riscv/include/arch/tlb.h b/na/arch/riscv/include/arch/tlb.h deleted file mode 100644 index 46c6b97..0000000 --- a/na/arch/riscv/include/arch/tlb.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <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 deleted file mode 100644 index 8e55250..0000000 --- a/na/arch/riscv/include/arch/trap.h +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Trap Vector Control Primitives (RISC-V 64) - * - * Set/read the S-mode trap vector (stvec), trap cause (scause), - * and trap value (stval) registers. - * - * Reference: docs/specs/arch_abstraction.md §4.3 - */ - -#include <lume/types.h> - -/* scause interrupt bit (bit 63): 1 = interrupt, 0 = exception */ -#define SCAUSE_INTERRUPT (1UL << 63) - -/* Exception cause codes (scause with bit 63 = 0) */ -#define EXC_INST_MISALIGNED 0 -#define EXC_INST_ACCESS_FAULT 1 -#define EXC_ILLEGAL_INST 2 -#define EXC_BREAKPOINT 3 -#define EXC_LOAD_MISALIGNED 4 -#define EXC_LOAD_ACCESS_FAULT 5 -#define EXC_STORE_MISALIGNED 6 -#define EXC_STORE_ACCESS_FAULT 7 -#define EXC_ECALL_FROM_U 8 -#define EXC_ECALL_FROM_S 9 -#define EXC_INST_PAGE_FAULT 12 -#define EXC_LOAD_PAGE_FAULT 13 -#define EXC_STORE_PAGE_FAULT 15 - -/* Interrupt cause codes (scause with bit 63 = 1, lower bits) */ -#define IRQ_S_SOFTWARE 1 -#define IRQ_S_TIMER 5 -#define IRQ_S_EXTERNAL 9 - -namespace arch::trap { - -// Set S-mode trap vector base address (writes stvec). -// Mode: Direct (all traps go to handler_addr). -static inline void set_vector(uint64 handler_addr) { - // Lowest 2 bits of stvec = mode. 0 = Direct. - __asm__ volatile("csrw stvec, %0" :: "r"(handler_addr)); -} - -// Read current trap vector base address -static inline uint64 get_vector() { - uint64 val; - __asm__ volatile("csrr %0, stvec" : "=r"(val)); - return val; -} - -// Read trap cause register (scause) -// Bit 63: 1=interrupt, 0=exception. Lower bits: cause code. -static inline uint64 get_cause() { - uint64 val; - __asm__ volatile("csrr %0, scause" : "=r"(val)); - return val; -} - -// Read trap value register (stval) -// Contains faulting address (page faults) or faulting instruction. -static inline uint64 get_value() { - uint64 val; - __asm__ volatile("csrr %0, stval" : "=r"(val)); - return val; -} - -// Check if a scause value represents an interrupt (vs exception) -static inline bool is_interrupt(uint64 cause) { - return (cause & SCAUSE_INTERRUPT) != 0; -} - -// Extract the cause code (strip the interrupt bit) -static inline uint64 cause_code(uint64 cause) { - return cause & ~SCAUSE_INTERRUPT; -} - -// Enable Supervisor User Memory access (set SUM bit in sstatus). -// When SUM=1, S-mode can read/write pages marked PTE_U. -// This is needed for syscall handlers to access user-space buffers. -static inline void enable_sum() { - __asm__ volatile("csrs sstatus, %0" :: "r"(1UL << 18)); -} - -// Disable Supervisor User Memory access (clear SUM bit in sstatus). -static inline void disable_sum() { - __asm__ volatile("csrc sstatus, %0" :: "r"(1UL << 18)); -} - -} // namespace arch::trap diff --git a/na/arch/riscv/include/arch/trapframe.h b/na/arch/riscv/include/arch/trapframe.h deleted file mode 100644 index b198646..0000000 --- a/na/arch/riscv/include/arch/trapframe.h +++ /dev/null @@ -1,140 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Trap Frame Structure (RISC-V 64) - * - * Saved/restored by the assembly trap_vector on every interrupt, - * exception, or system call. Contains the full CPU snapshot needed - * to resume execution after trap handling. - * - * Layout must match the assembly offsets in trap_vector.S exactly. - * Reference: docs/specs/arch_abstraction.md §3.2 - */ - -/* - * TrapFrame field offsets for use in assembly. - * These must be kept in sync with the struct layout below. - * Placed before any C++ code so assembly can #include this file. - */ -#define TF_KERNEL_SATP 0 -#define TF_KERNEL_SP 8 -#define TF_KERNEL_TRAP 16 -#define TF_KERNEL_CPUID 24 -#define TF_EPC 32 -#define TF_SSTATUS 40 -#define TF_RA 48 -#define TF_SP 56 -#define TF_GP 64 -#define TF_TP 72 -#define TF_T0 80 -#define TF_T1 88 -#define TF_T2 96 -#define TF_T3 104 -#define TF_T4 112 -#define TF_T5 120 -#define TF_T6 128 -#define TF_S0 136 -#define TF_S1 144 -#define TF_S2 152 -#define TF_S3 160 -#define TF_S4 168 -#define TF_S5 176 -#define TF_S6 184 -#define TF_S7 192 -#define TF_S8 200 -#define TF_S9 208 -#define TF_S10 216 -#define TF_S11 224 -#define TF_A0 232 -#define TF_A1 240 -#define TF_A2 248 -#define TF_A3 256 -#define TF_A4 264 -#define TF_A5 272 -#define TF_A6 280 -#define TF_A7 288 -#define TF_SIZE 296 - -#ifndef __ASSEMBLER__ - -#include <lume/types.h> -#include <arch/cpu.h> - -namespace arch { - -struct TrapFrame { - /* ---- Kernel metadata (offset 0-39) ---- */ - uint64 kernel_satp; // 0: Kernel page table (written to satp on trap entry) - uint64 kernel_sp; // 8: Top of kernel stack for this task - uint64 kernel_trap; // 16: Address of C++ trap_handler function - uint64 kernel_cpu_id; // 24: CPU ID (loaded into tp on trap entry) - uint64 epc; // 32: Interrupted instruction address (sepc) - - /* ---- CSR backup (offset 40-47) ---- */ - uint64 sstatus; // 40: Privilege status (SPP, SPIE bits) - - /* ---- General-purpose registers (offset 48-295) ---- */ - uint64 ra; // 48: x1 - Return address - uint64 sp; // 56: x2 - User stack pointer - uint64 gp; // 64: x3 - Global pointer - uint64 tp; // 72: x4 - Thread pointer - uint64 t[7]; // 80: x5-x7, x28-x31 (t0-t6, caller-saved temporaries) - uint64 s[12]; // 136: x8-x9, x18-x27 (s0-s11, callee-saved) - uint64 a[8]; // 232: x10-x17 (a0-a7, arguments / syscall params) -}; - -static_assert(sizeof(TrapFrame) == 296, "TrapFrame size mismatch with assembly offsets"); - -} // namespace arch - -/* Forward declaration — defined in trap_md.cc. - * Must be after TrapFrame definition. */ -extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame*); - -namespace arch { - -/* - * trapframe_init_user — prepare a TrapFrame for returning to user mode. - * - * Sets arch-specific sstatus: - * SPP = 0 (return to U-mode on sret) - * SPIE = 1 (enable interrupts in U-mode) - * FS = Initial (allow FP instructions) - * - * Called from MI exec.cc instead of setting sstatus directly. - */ -static inline void trapframe_init_user(TrapFrame* tf, uint64 entry, uint64 sp) -{ - /* Clear all registers */ - for (auto& t : tf->t) { t = 0; } - for (auto& s : tf->s) { s = 0; } - for (auto& a : tf->a) { a = 0; } - tf->ra = 0; - tf->gp = 0; - tf->tp = 0; - - tf->epc = entry; - tf->sp = sp; - - /* sstatus: SPP=0 (U-mode), SPIE=1 (interrupts), FS=Initial (FP ok) */ - constexpr uint64 kSPIE = (1ULL << 5); - constexpr uint64 kFS_Initial = (1ULL << 13); - tf->sstatus = kSPIE | kFS_Initial; - - /* Kernel metadata used by trap_vector.S on every trap from U-mode. - * These are RISC-V specific — trap_vector reads them from the - * TrapFrame to switch to kernel context. */ - uint64 satp; - asm volatile("csrr %0, satp" : "=r"(satp)); - tf->kernel_satp = satp; - tf->kernel_sp = reinterpret_cast<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 deleted file mode 100644 index 42f1eb4..0000000 --- a/na/arch/riscv/include/asm/csr.h +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_CSR_H -#define _ASM_RISCV_CSR_H - -/* - * RISC-V CSR (Control & Status Register) definitions used by the early - * boot / MMU-switch path. - * - * LumeOS-native subset of Linux arch/riscv/include/asm/csr.h. Only the - * symbols head.S and mm/init.cc actually reference are defined — adding - * the rest is straightforward when new code needs them, and skipping the - * Linux ALTERNATIVE / vendor-extension machinery keeps the cascade flat. - */ - -/* SATP (Supervisor Address Translation and Protection) layout — RV64 */ -#define SATP_PPN_MASK 0x00000FFFFFFFFFFFUL /* bits [43:0] = root PPN */ -#define SATP_ASID_SHIFT 44 -#define SATP_MODE_SHIFT 60 - -/* SATP MODE encodings (RV64) */ -#define SATP_MODE_BARE (0UL << SATP_MODE_SHIFT) -#define SATP_MODE_SV39 (8UL << SATP_MODE_SHIFT) -#define SATP_MODE_SV48 (9UL << SATP_MODE_SHIFT) - -/* Compose a SATP value from a page-table physical address. - * Result: MODE | (root_ppn) where root_ppn = pgd_pa >> PAGE_SHIFT. */ -#define MAKE_SATP(mode, pgd_pa) ((mode) | (((pgd_pa) >> 12) & SATP_PPN_MASK)) - -/* SSTATUS bits we touch at boot — disable FP / vector at entry so that an - * accidental FP / V instruction in early code traps cleanly rather than - * silently using dirty state from before reset. */ -#define SR_FS_OFF (0UL << 13) -#define SR_VS_OFF (0UL << 9) - -#endif /* _ASM_RISCV_CSR_H */ diff --git a/na/arch/riscv/include/asm/page.h b/na/arch/riscv/include/asm/page.h deleted file mode 100644 index 7b9f155..0000000 --- a/na/arch/riscv/include/asm/page.h +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_PAGE_H -#define _ASM_RISCV_PAGE_H - -/* - * RISC-V page / virtual-memory layout constants. - * - * Mirrors the subset of Linux arch/riscv/include/asm/page.h that LumeOS's - * early boot path needs: - * - PAGE_SIZE / PAGE_SHIFT / PAGE_MASK - * - PAGE_OFFSET (start of the higher-half direct map) - * - KERNEL_LINK_ADDR (link-time VA of _start) - * - SV39 PTE flag bits - * - PTRS_PER_PGD (512 for SV39) - * - * Values match Linux RISC-V SV39 defaults so that struct kernel_mapping - * (filled by setup_vm in arch/riscv/mm/init.cc) carries the same numeric - * relations Linux's mm/init.c maintains. - */ - -#define PAGE_SHIFT 12 -#define PAGE_SIZE (1UL << PAGE_SHIFT) -#define PAGE_MASK (~(PAGE_SIZE - 1)) - -/* Higher-half base of the kernel direct map. Bottom of the SV39 supervisor - * address space minus the unmapped guard region. Identical to Linux's - * CONFIG_PAGE_OFFSET default on RV64. */ -#define PAGE_OFFSET 0xFFFFFFC000000000UL - -/* Where _start was linked. OpenSBI hands control to PHYS_BASE; the kernel - * runs at KERNEL_LINK_ADDR after relocate_enable_mmu jumps to higher-half. */ -#define PHYS_BASE 0x80200000UL -#define KERNEL_LINK_ADDR 0xFFFFFFC080200000UL - -/* SATP mode field — SV39 = 8, shifted to bits [63:60]. */ -#define SATP_MODE_SV39 (8UL << 60) - -#define PTRS_PER_PGD 512 - -/* 1 GB superpage size — entry.S used to build the early page table with - * these; setup_vm in arch/riscv/mm/init.cc now does the same. */ -#define PGDIR_SHIFT 30 -#define PGDIR_SIZE (1UL << PGDIR_SHIFT) -#define PGDIR_MASK (~(PGDIR_SIZE - 1)) - -/* SV39 PTE flag bits (one byte). */ -#define _PAGE_PRESENT (1UL << 0) /* aka VALID — historic Linux name */ -#define _PAGE_READ (1UL << 1) -#define _PAGE_WRITE (1UL << 2) -#define _PAGE_EXEC (1UL << 3) -#define _PAGE_USER (1UL << 4) -#define _PAGE_GLOBAL (1UL << 5) -#define _PAGE_ACCESSED (1UL << 6) -#define _PAGE_DIRTY (1UL << 7) - -#define PAGE_KERNEL_EXEC \ - (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | _PAGE_EXEC | \ - _PAGE_ACCESSED | _PAGE_DIRTY) - -#endif /* _ASM_RISCV_PAGE_H */ diff --git a/na/arch/riscv/include/asm/pgtable.h b/na/arch/riscv/include/asm/pgtable.h deleted file mode 100644 index 28fc4c5..0000000 --- a/na/arch/riscv/include/asm/pgtable.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_PGTABLE_H -#define _ASM_RISCV_PGTABLE_H - -/* - * Early page-table construction API used by arch/riscv/mm/init.cc::setup_vm. - * - * Mirrors the create_pgd_mapping interface from Linux's - * arch/riscv/mm/init.c — same name, same semantics: - * - * create_pgd_mapping(pgdir, va, pa, sz, prot) - * - * pgdir : root of the page table to populate - * va : virtual address to map - * pa : physical address to map to - * sz : mapping size — must be a supported leaf size (PAGE_SIZE, - * 2 MiB megapage, or 1 GiB gigapage) - * prot : PTE flag bits (PAGE_KERNEL_EXEC etc., from <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 deleted file mode 100644 index 454d9de..0000000 --- a/na/arch/riscv/include/asm/sections.h +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _ASM_RISCV_SECTIONS_H -#define _ASM_RISCV_SECTIONS_H - -/* - * Linker-emitted symbols that bracket each section of the kernel image. - * - * The lumeos.lds.S linker script defines these as the start / end - * addresses of contiguous regions. Code that walks a section - * (relocate_kernel walks .rela.dyn, the C++ ctor loop walks - * .init_array, the VMM mapping code walks .text vs .rodata, ...) - * imports the pair via this header. - * - * Mirrors Linux arch/riscv/include/asm/sections.h / asm-generic/sections.h. - * Each name and pair is identical to Linux so that direct ports of mm/init.c - * relocate_kernel logic compile unchanged against LumeOS. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -extern char _start[]; -extern char _end[]; - -extern char _stext[]; -extern char _etext[]; -extern char _srodata[]; -extern char _erodata[]; -extern char _sdata[]; -extern char _edata[]; -extern char _sbss[]; -extern char _ebss[]; - -extern char __init_array_start[]; -extern char __init_array_end[]; - -/* .rela.dyn — array of Elf64_Rela entries emitted by the linker under - * `-shared -Bsymbolic`. relocate_kernel walks this range and patches - * each R_RISCV_RELATIVE slot. */ -extern char __rela_dyn_start[]; -extern char __rela_dyn_end[]; - -#ifdef __cplusplus -} -#endif - -#endif /* _ASM_RISCV_SECTIONS_H */ diff --git a/na/arch/riscv/kernel/asm-offsets.c b/na/arch/riscv/kernel/asm-offsets.c deleted file mode 100644 index edecab7..0000000 --- a/na/arch/riscv/kernel/asm-offsets.c +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// Copyright (C) 2026 Richard Qin -// -// arch/riscv/kernel/asm-offsets.c — emit struct field offsets and -// scalar constants for consumption by assembly sources (head.S etc.). -// -// 1:1 with Linux arch/riscv/kernel/asm-offsets.c. The Makefile -// compiles this to an .s file (not .o) and runs -// scripts/extract-asm-offsets.sed over the output to produce -// include/generated/asm-offsets.h. -// -// Adding a new offset: include the struct's header, add an OFFSET() -// or DEFINE() line below, rebuild. head.S can then reference the -// generated #define directly. - -#include <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 deleted file mode 100644 index e04c44c..0000000 --- a/na/arch/riscv/kernel/head.S +++ /dev/null @@ -1,317 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V kernel entry — Linux-aligned layout. - * - * Flow (mirrors arch/riscv/kernel/head.S in Linux): - * - * _start [.head.text, byte 0 of image] - * · 64-byte Boot Protocol Image Header - * · j real_start - * - * real_start [.text] - * · save a0 (hartid) / a1 (dtb_pa) - * · setup gp / per-hart sp - * · BSP/AP election via amoswap - * · BSP: clear BSS, call setup_vm, then relocate_enable_mmu - * · AP: spin-wait, then relocate_enable_mmu - * - * relocate_enable_mmu [C function, arch/riscv/mm/init.cc] - * · csrw satp <- trampoline_pg_dir - * · jump to higher-half VA - * · csrw satp <- early_pg_dir - * · slide sp/ra/gp to higher-half - * · call relocate_kernel (walks .rela.dyn, patches R_RISCV_RELATIVE) - * - * real_start (continued in higher-half) - * · BSP only: call early_cpp_init (walks .init_array) - * · call kernel_main(hartid, dtb_pa) - * - * Register contract from OpenSBI / U-Boot: - * a0 = hart ID - * a1 = FDT physical base - */ - -#include <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 deleted file mode 100644 index 9bd083b..0000000 --- a/na/arch/riscv/kernel/lumeos.lds.S +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V Kernel Linker Script Template - * - * This is a C-preprocessed linker script (.lds.S). The build system - * runs it through CPP to produce the final vmlinux.lds which is - * passed to the linker. - * - * Architecture-specific definitions: - * - OUTPUT_ARCH, ENTRY - * - PAGE_OFFSET, BASE_ADDRESS, PHYS_BASE - * - .head.text placement - * - GP anchor (__global_pointer$ for RISC-V medany) - * - early_pgdir and boot stacks in .bss - * - * Common section macros come from include/lume/vmlinux.lds.h - * - * Reference: docs/specs/linker_and_entry.md - */ - -#include <lume/lumeos.lds.h> - -/* ===== RISC-V Architecture Constants ===== */ - -OUTPUT_ARCH(riscv) - -PAGE_OFFSET = 0xFFFFFFC000000000; -BASE_ADDRESS = 0xFFFFFFC080200000; -PHYS_BASE = 0x80200000; - -ENTRY(_start) - -/* ===== RISC-V: GP anchor in .data (medany model) ===== */ -#define ARCH_DATA_EXTRA \ - . = ALIGN(16); \ - PROVIDE(__global_pointer$ = . + 0x800); \ - *(.sdata .sdata.*) - -/* ===== RISC-V: page-table storage + per-CPU boot stacks in .bss ===== - * - * Linux RISC-V layout (arch/riscv/kernel/vmlinux.lds.S): - * early_pg_dir — first early page table built by setup_vm, - * used for the rest of boot after MMU enable - * trampoline_pg_dir — single-page table used only during the SATP - * switch from physical to higher-half VA; its - * identity + virt mappings of _start must both - * be valid in the same instruction window - * fixmap_pg_dir — covers the FIXADDR_START range used by the - * early console (UART MMIO before kernel page - * table proper is built) - * - * All three are allocated here so head.S can take their addresses without - * a C definition; their PA == VA mapping covers them during setup_vm. - */ -#define ARCH_BSS_EXTRA \ - . = ALIGN(4096); \ - early_pg_dir = .; \ - . += 4096; \ - trampoline_pg_dir = .; \ - . += 4096; \ - fixmap_pg_dir = .; \ - . += 4096; \ - /* Legacy name retained for any caller still referencing it; */ \ - /* points at the same first 4 KiB as early_pg_dir. */ \ - early_pgdir = early_pg_dir; \ - /* Per-CPU boot stacks: 8KB per CPU, max 8 CPUs */ \ - . = ALIGN(4096); \ - boot_stack = .; \ - . += 8192 * 8; \ - boot_stack_top = .; - -/* ===== Layout ===== */ - -SECTIONS { - . = BASE_ADDRESS; - - /* .head.text: Image Header (must be at byte 0 of the kernel image) */ - .head.text : AT(PHYS_BASE) { - _start = .; - KEEP(*(.head.text)) - } - - TEXT_SECTION - - . = ALIGN(4096); - - /* .rela.dyn lives between .text and .rodata so it ends up in the - * read-only PT_LOAD segment. Mirrors Linux vmlinux.lds.S where - * .rela.dyn sits at the tail of init data, just before RO_DATA. */ - RELA_DYN_SECTION - - . = ALIGN(4096); - - RODATA_SECTION - - . = ALIGN(4096); - - DATA_SECTION - - . = ALIGN(8); - - /* .got lives right after .data in the RW PT_LOAD. All other - * dynamic-linker artefacts (.plt, .dynamic, .dynsym, .dynstr, - * .hash, .gnu.hash, .interp, .dynbss) are discarded by the - * DISCARDS macro — mirrors Linux arch/riscv/kernel/vmlinux.lds.S. */ - GOT_SECTION - - . = ALIGN(4096); - - /* End of file-backed image (BSS follows as NOBITS) */ - __lume_image_end = .; - - BSS_SECTION - - KERNEL_END - - DISCARDS -} diff --git a/na/arch/riscv/kernel/signal.cc b/na/arch/riscv/kernel/signal.cc deleted file mode 100644 index 39fe34e..0000000 --- a/na/arch/riscv/kernel/signal.cc +++ /dev/null @@ -1,163 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * arch/riscv/signal.cc — Signal frame construction and sigreturn - * - * Constructs a signal frame on the user stack before returning to - * the user signal handler. On sigreturn, restores the original - * registers from the saved frame. - * - * Frame layout on user stack (grows downward): - * - * +-----------------------+ <- original sp - * | sigset_t saved_mask | 8 bytes - * | TrapFrame saved_regs | 296 bytes - * | kernel_siginfo info | 128 bytes - * +-----------------------+ <- new sp (16-byte aligned) - * - * Reference: Linux arch/riscv/kernel/signal.c - */ - -#include <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/lib/random.cc b/na/arch/riscv/lib/random.cc deleted file mode 100644 index afc0078..0000000 --- a/na/arch/riscv/lib/random.cc +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <arch/random.h> -#include <lume/klog.h> - -namespace arch { - -bool get_hw_random(uint64& value) -{ - /* - * Real implementation would check for Zkr extension and execute: - * csrr x, seed - * - * Since QEMU virt doesn't guarantee Zkr, we return false to - * force the MI layer to use the timer fallback. - */ - return false; -} - -} // namespace arch diff --git a/na/arch/riscv/mm/init.cc b/na/arch/riscv/mm/init.cc deleted file mode 100644 index 3cebcd9..0000000 --- a/na/arch/riscv/mm/init.cc +++ /dev/null @@ -1,241 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * RISC-V early memory-management bootstrap. - * - * Mirrors Linux arch/riscv/mm/init.c — same file name, same C function - * names (setup_vm / relocate_enable_mmu / relocate_kernel), same global - * state (kernel_map, early_pg_dir, trampoline_pg_dir). head.S calls - * setup_vm immediately after BSS-clear, then later calls - * relocate_enable_mmu, which switches SATP, jumps to higher-half VA, - * and finally calls relocate_kernel to apply .rela.dyn. - * - * Differences from Linux: - * - KASLR offset is hard-wired to 0 for now (no pi/ subsystem) — the - * code path that would consume kernel_map.virt_offset is in place - * so adding pi/-based seed reading later only requires populating - * that one field. - * - Only 1 GiB superpages supported in create_pgd_mapping; sufficient - * for the early page tables. Finer-grained mappings come from - * vmm_init() once slab is up. - */ - -#include <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 deleted file mode 100644 index f0a6848..0000000 --- a/na/arch/riscv/mm/pmap.cc +++ /dev/null @@ -1,361 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pmap.cc — SV39 page table operations (MD layer) - * - * Implements the pmap namespace interface for RISC-V SV39. - * 3-level page table: VPN[2] (9 bits) -> VPN[1] (9 bits) -> VPN[0] (9 bits) - * Each level is a 4KB page containing 512 PTEs of 8 bytes each. - * - * Address conversion: all page table operations work with physical addresses. - * To read/write page table entries we must convert PA to VA via pa_to_va(). - * - * Reference: docs/specs/vmm.md - */ - -#include <lume/types.h> -#include <lume/addr.h> -#include <lume/config.h> -#include <lume/pmm.h> -#include <lume/memblock.h> -#include <arch/mmu.h> -#include <lume/klog.h> -#include <lume/errno.h> - -namespace pmap { - -/* - * Before the first pmap::activate(), CPU runs on entry.S bootstrap page table - * (fixed direct-map base). After activation, runtime (KASLR) base is valid. - */ -static bool g_runtime_mapping_ready = false; - -/* PTE bit definitions */ -inline constexpr uint64 PTE_V = 1ULL << 0; -inline constexpr uint64 PTE_R = 1ULL << 1; -inline constexpr uint64 PTE_W = 1ULL << 2; -inline constexpr uint64 PTE_X = 1ULL << 3; -inline constexpr uint64 PTE_U = 1ULL << 4; -inline constexpr uint64 PTE_G = 1ULL << 5; -inline constexpr uint64 PTE_A = 1ULL << 6; -inline constexpr uint64 PTE_D = 1ULL << 7; -inline constexpr uint64 PTE_COW = 1ULL << 8; - -/* SV39: 3 levels, 9 bits per level, 12-bit page offset */ -inline constexpr int kLevels = 3; -inline constexpr int kPtePerPage = 512; - -/* SV39: upper half of VA space (kernel) starts at L2 index 256 */ -inline constexpr int kKernelPgdStart = kPtePerPage / 2; - -/* SATP PPN mask (44 bits) and page shift */ -inline constexpr uint64 kSatpPpnMask = 0xFFFFFFFFFFFULL; -inline constexpr int kPageShift = 12; - -/* Extract VPN[level] from a virtual address */ -static inline uint64 vpn(uint64 va, int level) -{ - return (va >> (12 + 9 * level)) & 0x1FF; -} - -/* Extract PA from a PTE */ -static inline uint64 pte_to_pa(uint64 pte) -{ - return ((pte >> 10) & 0xFFFFFFFFFFFULL) << 12; -} - -/* Create a PTE from a PA and flags */ -static inline uint64 pa_to_pte(uint64 pa, uint64 flags) -{ - return ((pa >> 12) << 10) | flags; -} - -/* Convert PA to kernel VA for accessing page table contents */ -static inline uint64 *pa_to_ptr(uint64 pa) -{ - uint64 va = g_runtime_mapping_ready - ? pa_to_va(phys_addr(pa)).raw - : boot_pa_to_va(phys_addr(pa)).raw; - return reinterpret_cast<uint64 *>(va); -} - -/* Allocate a zeroed page for a page table level. - * During early boot (before PMM), uses memblock. - * After PMM init, uses the buddy allocator. - * Returns PA of the new page, or 0 on failure. */ -static uint64 alloc_pt_page() -{ - uint64 pa; - if (memblock_is_active()) { - pa = memblock_alloc(kPageSize, kPageSize); - } else { - Page *page = pmm_alloc_page(); - if (!page) { - return 0; - } - pa = page_to_pa(page).raw; - } - if (!pa) { - return 0; - } - /* Zero the page */ - uint64 *ptr = pa_to_ptr(pa); - for (int i = 0; i < kPtePerPage; i++) { - ptr[i] = 0; - } - return pa; -} - -uint64 create() -{ - uint64 new_root = alloc_pt_page(); - if (!new_root) return 0; - - /* Copy kernel mappings from the current kernel page table. - * In SV39, entries 256..511 (VPN[2] >= 256) cover the upper - * half of the 64-bit VA space where the kernel lives. - * Without these, switching to the new page table would - * immediately fault on any kernel VA access. - * - * Only do this after the runtime page table is active — - * during early boot the bootstrap PT doesn't cover all of - * RAM so pa_to_ptr on the kernel root would fault. */ - if (g_runtime_mapping_ready) { - uint64 kernel_satp; - asm volatile("csrr %0, satp" : "=r"(kernel_satp)); - uint64 kernel_root_pa = (kernel_satp & kSatpPpnMask) << kPageShift; - - uint64 *new_l2 = pa_to_ptr(new_root); - uint64 *kern_l2 = pa_to_ptr(kernel_root_pa); - - for (int i = kKernelPgdStart; i < kPtePerPage; i++) { - new_l2[i] = kern_l2[i]; - } - } - - return new_root; -} - -void destroy(uint64 root_pa) -{ - uint64 *l2 = pa_to_ptr(root_pa); - - /* Only free the user half (entries 0..255). - * Entries 256..511 are shared kernel PGD pointers copied - * from the kernel page table — freeing them would corrupt - * the kernel's own mappings. */ - for (int i = 0; i < kKernelPgdStart; i++) { - if (!(l2[i] & PTE_V)) - continue; - /* Non-leaf L2 entry? (no R/W/X bits = pointer to L1 table) */ - if (!(l2[i] & (PTE_R | PTE_W | PTE_X))) { - uint64 l1_pa = pte_to_pa(l2[i]); - uint64 *l1 = pa_to_ptr(l1_pa); - for (int j = 0; j < kPtePerPage; j++) { - if (!(l1[j] & PTE_V)) - continue; - if (!(l1[j] & (PTE_R | PTE_W | PTE_X))) { - /* L0 page table */ - uint64 l0_pa = pte_to_pa(l1[j]); - pmm_free_page(pa_to_page(phys_addr(l0_pa))); - } - } - pmm_free_page(pa_to_page(phys_addr(l1_pa))); - } - } - pmm_free_page(pa_to_page(phys_addr(root_pa))); -} - -int map(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) -{ - uint64 *table = pa_to_ptr(root_pa); - - /* Walk levels 2 -> 1, creating intermediate tables as needed */ - for (int level = 2; level > 0; level--) { - uint64 idx = vpn(va, level); - if (!(table[idx] & PTE_V)) { - uint64 child_pa = alloc_pt_page(); - if (!child_pa) - return -ENOMEM; /* -ENOMEM */ - table[idx] = pa_to_pte(child_pa, PTE_V); - } - table = pa_to_ptr(pte_to_pa(table[idx])); - } - - /* Level 0: install the leaf PTE */ - uint64 idx = vpn(va, 0); - table[idx] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); - return 0; -} - -int map_2mb(uint64 root_pa, uint64 va, uint64 pa, uint64 perm) -{ - /* VA and PA must be 2MB-aligned */ - constexpr uint64 k2MB = 2ULL * 1024 * 1024; - if ((va & (k2MB - 1)) != 0 || (pa & (k2MB - 1)) != 0) { - return -EINVAL; /* -EINVAL */ - } - - uint64 *table = pa_to_ptr(root_pa); - - /* Walk L2: create intermediate table if needed */ - uint64 idx2 = vpn(va, 2); - if (!(table[idx2] & PTE_V)) { - uint64 child_pa = alloc_pt_page(); - if (!child_pa) { - return -ENOMEM; /* -ENOMEM */ - } - table[idx2] = pa_to_pte(child_pa, PTE_V); - } - table = pa_to_ptr(pte_to_pa(table[idx2])); - - /* L1: install leaf PTE (2MB mega page) */ - uint64 idx1 = vpn(va, 1); - table[idx1] = pa_to_pte(pa, perm | PTE_V | PTE_A | PTE_D); - return 0; -} - -void unmap(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level > 0; level--) { - uint64 idx = vpn(va, level); - if (!(table[idx] & PTE_V)) - return; - table = pa_to_ptr(pte_to_pa(table[idx])); - } - table[vpn(va, 0)] = 0; -} - -int protect(uint64 root_pa, uint64 va, uint64 perm) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return -EFAULT; /* -EFAULT */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - uint64 pa = pte_to_pa(pte); - table[idx] = pa_to_pte(pa, perm | PTE_V); - return 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return -EFAULT; -} - -int protect_range(uint64 root_pa, uint64 start, uint64 end, uint64 perm) -{ - if (end <= start) { - return 0; - } - - uint64 va = page_align_down(start); - uint64 va_end = page_align_up(end); - - for (; va < va_end; va += kPageSize) { - int ret = protect(root_pa, va, perm); - if (ret == -14) { - continue; /* unmapped page: skip */ - } - if (ret < 0) { - return ret; - } - } - - arch::mmu::flush_tlb_all(); - return 0; -} - -bool lookup(uint64 root_pa, uint64 va, uint64 *pa_out) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return false; - /* Leaf PTE? (has R, W, or X) */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - if (pa_out) - *pa_out = pte_to_pa(pte); - return true; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return false; -} - -uint64 lookup_pte(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return 0; - if (pte & (PTE_R | PTE_W | PTE_X)) - return pte; // leaf PTE raw value - table = pa_to_ptr(pte_to_pa(pte)); - } - return 0; -} - -bool is_cow(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return false; - if (pte & (PTE_R | PTE_W | PTE_X)) { - return (pte & PTE_COW) != 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return false; -} - -int make_cow(uint64 root_pa, uint64 va) -{ - uint64 *table = pa_to_ptr(root_pa); - for (int level = 2; level >= 0; level--) { - uint64 idx = vpn(va, level); - uint64 pte = table[idx]; - if (!(pte & PTE_V)) - return -EFAULT; /* -EFAULT */ - if (pte & (PTE_R | PTE_W | PTE_X)) { - /* Clear write bit, set COW bit */ - pte &= ~PTE_W; - pte |= PTE_COW; - table[idx] = pte; - return 0; - } - table = pa_to_ptr(pte_to_pa(pte)); - } - return -EFAULT; -} - -void activate(uint64 root_pa) -{ - arch::mmu::set_page_table(root_pa); - arch::mmu::flush_tlb_all(); - g_runtime_mapping_ready = true; -} - -uint64 vm_perm_to_pte(uint64 vm_perm) -{ - /* Translate MI VM_* flags to SV39 PTE bits. - * The MI flags are defined in <lume/vmm.h>. */ - uint64 pte = PTE_V | PTE_A | PTE_D; // Valid + Accessed + Dirty (avoid HW A/D faults) - if (vm_perm & (1ULL << 0)) pte |= PTE_R; // VM_READ - if (vm_perm & (1ULL << 1)) pte |= PTE_W; // VM_WRITE - if (vm_perm & (1ULL << 2)) pte |= PTE_X; // VM_EXEC - if (vm_perm & (1ULL << 3)) pte |= PTE_U; // VM_USER - return pte; -} - -} // namespace pmap diff --git a/na/arch/riscv/mm/tlb_md.cc b/na/arch/riscv/mm/tlb_md.cc deleted file mode 100644 index ed9aa29..0000000 --- a/na/arch/riscv/mm/tlb_md.cc +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <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 deleted file mode 100644 index 97d22de..0000000 --- a/na/arch/riscv/module.mk +++ /dev/null @@ -1,18 +0,0 @@ -# arch/riscv/module.mk -SRCS_CC += arch/riscv/mm/pmap.cc -SRCS_CC += arch/riscv/mm/init.cc -SRCS_CC += arch/riscv/config.cc -SRCS_CC += arch/riscv/trap/trap_md.cc -SRCS_CC += arch/riscv/syscall/syscall_md.cc -SRCS_CC += arch/riscv/lib/random.cc -SRCS_CC += arch/riscv/mm/tlb_md.cc -SRCS_S += arch/riscv/kernel/head.S -SRCS_S += arch/riscv/trap/swtch.S -SRCS_S += arch/riscv/trap/fpu.S -SRCS_S += arch/riscv/trap/trap_vector.S -SRCS_S += arch/riscv/uaccess/uaccess_memcpy.S -SRCS_CC += arch/riscv/trap/extable.cc -SRCS_CC += arch/riscv/kernel/signal.cc - -# Arch-specific headers (added to include path) -INCLUDES += -Iarch/riscv/include diff --git a/na/arch/riscv/syscall/syscall_md.cc b/na/arch/riscv/syscall/syscall_md.cc deleted file mode 100644 index 26320c8..0000000 --- a/na/arch/riscv/syscall/syscall_md.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Syscall MD Layer — RISC-V Register Extraction - * - * Extracts syscall number (a7) and arguments (a0-a5) from the TrapFrame, - * calls the MI dispatcher, and writes the return value back to a0. - * - * Reference: docs/specs/syscall.md §5 - */ - -#include <lume/syscall.h> -#include <lume/syscall_nr.h> -#include <arch/trapframe.h> -#include <arch/cpu.h> - -void syscall_dispatch_md(arch::TrapFrame* tf) -{ - uint64 nr = tf->a[7]; /* RISC-V ABI: syscall number in a7 */ - - uint64 args[6] = { - tf->a[0], tf->a[1], tf->a[2], - tf->a[3], tf->a[4], tf->a[5], - }; - - /* Enable interrupts so timer and preemption work during syscalls */ - arch::cpu::intr_on(); - - int64 ret = syscall_dispatch(nr, args); - - arch::cpu::intr_off(); - - /* sys_rt_sigreturn restores the entire TrapFrame from the signal - * frame on the user stack. Writing ret into a[0] would clobber - * the restored value, so we must skip the writeback. */ - if (nr == static_cast<uint64>(SYS_rt_sigreturn)){ - return; - } - tf->a[0] = static_cast<uint64>(ret); /* Return value in a0 */ -} - diff --git a/na/arch/riscv/trap/extable.cc b/na/arch/riscv/trap/extable.cc deleted file mode 100644 index 57a3dc1..0000000 --- a/na/arch/riscv/trap/extable.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <arch/extable.h> -#include <lume/types.h> - -extern struct ExceptionTableEntry __extable_start[]; -extern struct ExceptionTableEntry __extable_end[]; - -static void swap_extable_entries(struct ExceptionTableEntry *a, struct ExceptionTableEntry *b) { - uint64 a_insn = reinterpret_cast<uint64>(&a->insn) + a->insn; - uint64 a_fixup = reinterpret_cast<uint64>(&a->fixup) + a->fixup; - uint64 b_insn = reinterpret_cast<uint64>(&b->insn) + b->insn; - uint64 b_fixup = reinterpret_cast<uint64>(&b->fixup) + b->fixup; - - a->insn = static_cast<int32>(b_insn - reinterpret_cast<uint64>(&a->insn)); - a->fixup = static_cast<int32>(b_fixup - reinterpret_cast<uint64>(&a->fixup)); - b->insn = static_cast<int32>(a_insn - reinterpret_cast<uint64>(&b->insn)); - b->fixup = static_cast<int32>(a_fixup - reinterpret_cast<uint64>(&b->fixup)); -} - -static void sort_extable(size_t left, size_t right) { - if (left >= right) return; - - size_t mid_idx = left + (right - left) / 2; - struct ExceptionTableEntry *pivot = &__extable_start[mid_idx]; - uint64 pivot_val = reinterpret_cast<uint64>(&pivot->insn) + pivot->insn; - - size_t i = left; - size_t j = right; - - while (i <= j) { - while ((reinterpret_cast<uint64>(&__extable_start[i].insn) + __extable_start[i].insn) < pivot_val) { - i++; - } - while ((reinterpret_cast<uint64>(&__extable_start[j].insn) + __extable_start[j].insn) > pivot_val) { - if (j == 0) { - break; - } - j--; - } - if (i <= j) { - swap_extable_entries(&__extable_start[i], &__extable_start[j]); - i++; - if (j == 0) { - break; - } - j--; - } - } - - if (j > left) { - sort_extable(left, j); - } - if (i < right) { - sort_extable(i, right); - } -} - -void extable_init() { - size_t count = __extable_end - __extable_start; - if (count <= 1) return; - sort_extable(0, count - 1); -} - -uint64 search_extable(uint64 pc) { - size_t count = __extable_end - __extable_start; - if (count == 0) return 0; - - size_t left = 0; - size_t right = count - 1; - - /* Binary search */ - while (left <= right) { - size_t mid = left + (right - left) / 2; - const struct ExceptionTableEntry *entry = &__extable_start[mid]; - uint64 mid_pc = reinterpret_cast<uint64>(&entry->insn) + entry->insn; - - if (mid_pc == pc) { - return reinterpret_cast<uint64>(&entry->fixup) + entry->fixup; - } - - if (mid_pc < pc) { - left = mid + 1; - } else { - if (mid == 0) break; - right = mid - 1; - } - } - - return 0; -} diff --git a/na/arch/riscv/trap/fpu.S b/na/arch/riscv/trap/fpu.S deleted file mode 100644 index 90d96d9..0000000 --- a/na/arch/riscv/trap/fpu.S +++ /dev/null @@ -1,103 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * FPU Save / Restore (RISC-V D extension) - * - * void fpu_save(FpState* state); - * Saves f0-f31 and fcsr to the FpState structure. - * - * void fpu_restore(FpState* state); - * Restores f0-f31 and fcsr from the FpState structure. - * - * FpState layout: f[0..31] at offset 0 (each 8 bytes), fcsr at offset 256. - * - * Called from schedule() when switching between tasks that have - * used the FPU (sstatus.FS == Dirty on save path). - */ - - .section .text - -/* ====================================================================== - * void fpu_save(FpState* state); // a0 = state pointer - * ====================================================================== */ - .globl fpu_save - .align 2 -fpu_save: - fsd f0, 0(a0) - fsd f1, 8(a0) - fsd f2, 16(a0) - fsd f3, 24(a0) - fsd f4, 32(a0) - fsd f5, 40(a0) - fsd f6, 48(a0) - fsd f7, 56(a0) - fsd f8, 64(a0) - fsd f9, 72(a0) - fsd f10, 80(a0) - fsd f11, 88(a0) - fsd f12, 96(a0) - fsd f13, 104(a0) - fsd f14, 112(a0) - fsd f15, 120(a0) - fsd f16, 128(a0) - fsd f17, 136(a0) - fsd f18, 144(a0) - fsd f19, 152(a0) - fsd f20, 160(a0) - fsd f21, 168(a0) - fsd f22, 176(a0) - fsd f23, 184(a0) - fsd f24, 192(a0) - fsd f25, 200(a0) - fsd f26, 208(a0) - fsd f27, 216(a0) - fsd f28, 224(a0) - fsd f29, 232(a0) - fsd f30, 240(a0) - fsd f31, 248(a0) - frcsr t0 - sw t0, 256(a0) - ret - -/* ====================================================================== - * void fpu_restore(FpState* state); // a0 = state pointer - * ====================================================================== */ - .globl fpu_restore - .align 2 -fpu_restore: - fld f0, 0(a0) - fld f1, 8(a0) - fld f2, 16(a0) - fld f3, 24(a0) - fld f4, 32(a0) - fld f5, 40(a0) - fld f6, 48(a0) - fld f7, 56(a0) - fld f8, 64(a0) - fld f9, 72(a0) - fld f10, 80(a0) - fld f11, 88(a0) - fld f12, 96(a0) - fld f13, 104(a0) - fld f14, 112(a0) - fld f15, 120(a0) - fld f16, 128(a0) - fld f17, 136(a0) - fld f18, 144(a0) - fld f19, 152(a0) - fld f20, 160(a0) - fld f21, 168(a0) - fld f22, 176(a0) - fld f23, 184(a0) - fld f24, 192(a0) - fld f25, 200(a0) - fld f26, 208(a0) - fld f27, 216(a0) - fld f28, 224(a0) - fld f29, 232(a0) - fld f30, 240(a0) - fld f31, 248(a0) - lw t0, 256(a0) - fscsr t0 - ret diff --git a/na/arch/riscv/trap/swtch.S b/na/arch/riscv/trap/swtch.S deleted file mode 100644 index dbae43d..0000000 --- a/na/arch/riscv/trap/swtch.S +++ /dev/null @@ -1,96 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Context Switch & Kernel Thread Trampoline (RISC-V 64) - * - * context_switch: saves/restores callee-saved registers. - * kthread_entry_trampoline: entry point for newly created kernel threads. - * After context_switch loads the new Context, execution lands here via - * Context.ra = kthread_entry_trampoline. The entry function and argument - * are in s0 and s1 respectively, set by sched_init/create_tcb. - * - * The trampoline calls the MI-layer kthread_entry(entry, arg). - * - * Reference: docs/specs/scheduler.md §6 - */ - - .section .text - -/* ======================================================================== - * void context_switch(Context* old_ctx, Context* next_ctx); - * a0 = old_ctx_ptr, a1 = next_ctx_ptr - * ======================================================================== */ - .globl context_switch - .align 2 -context_switch: - sd ra, 0(a0) - sd sp, 8(a0) - sd s0, 16(a0) - sd s1, 24(a0) - sd s2, 32(a0) - sd s3, 40(a0) - sd s4, 48(a0) - sd s5, 56(a0) - sd s6, 64(a0) - sd s7, 72(a0) - sd s8, 80(a0) - sd s9, 88(a0) - sd s10, 96(a0) - sd s11, 104(a0) - - ld ra, 0(a1) - ld sp, 8(a1) - ld s0, 16(a1) - ld s1, 24(a1) - ld s2, 32(a1) - ld s3, 40(a1) - ld s4, 48(a1) - ld s5, 56(a1) - ld s6, 64(a1) - ld s7, 72(a1) - ld s8, 80(a1) - ld s9, 88(a1) - ld s10, 96(a1) - ld s11, 104(a1) - - ret - -/* ======================================================================== - * kthread_entry_trampoline - * - * Reached via context_switch when a new kthread is first scheduled. - * Context was set up by create_tcb(): - * s0 = entry function pointer - * s1 = argument pointer - * - * Calls the MI-layer C++ function: - * void kthread_entry(void (*entry)(void*), void* arg); - * ======================================================================== */ - .globl kthread_entry_trampoline - .align 2 -kthread_entry_trampoline: - mv a0, s0 /* a0 = entry function */ - mv a1, s1 /* a1 = argument */ - call kthread_entry /* MI-layer C++ function */ - - /* kthread_entry should never return, but if it does: */ - wfi - j kthread_entry_trampoline + 12 /* loop on wfi */ - -/* ======================================================================== - * ret_from_fork - * - * Trampoline for newly forked processes. - * Context is set up by fork() so that s0 contains the TrapFrame pointer. - * - * Calls the MI-layer C++ function: - * void fork_child_entry(TrapFrame* tf); - * ======================================================================== */ - .globl ret_from_fork - .align 2 -ret_from_fork: - mv a0, s0 /* a0 = TrapFrame pointer */ - call fork_child_entry /* does not return, eventually calls userret */ - wfi - j ret_from_fork + 8
\ No newline at end of file diff --git a/na/arch/riscv/trap/trap_md.cc b/na/arch/riscv/trap/trap_md.cc deleted file mode 100644 index e30aea6..0000000 --- a/na/arch/riscv/trap/trap_md.cc +++ /dev/null @@ -1,241 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Trap MD Layer (RISC-V 64) - * - * All architecture-specific trap logic lives here: - * - scause/stval reading (CSR access) - * - scause → MI TrapCause translation - * - RISC-V specific fixups (ecall epc += 4, ebreak epc += 2) - * - stvec setup (trap_init / trap_init_ap) - * - kernel_trap_handler (traps from S-mode) - * - * MI layer (kernel/trap.cc) contains ZERO arch-specific code. - * - * Reference: docs/specs/trap.md §7 - */ - -#include <lume/trap.h> -#include <lume/irq.h> -#include <lume/klog.h> -#include <lume/console.h> -#include <lume/kprintf.h> -#include <lume/sched.h> -#include <lume/task.h> -#include <arch/trap.h> -#include <arch/trapframe.h> -#include <arch/cpu.h> -#include <arch/extable.h> - -/* PLIC interface (drivers/plic.cc) */ -uint32 plic_claim(); -void plic_complete(uint32 irq); - -/* trap_vector.S: assembly entry point */ -extern "C" void trap_vector(); - -/* ======================================================================== - * scause → TrapCause translation (RISC-V specific) - * ======================================================================== */ - -static TrapCause scause_to_trap_cause(uint64 scause) -{ - if (arch::trap::is_interrupt(scause)) { - switch (arch::trap::cause_code(scause)) { - case IRQ_S_TIMER: return TrapCause::IrqTimer; - case IRQ_S_SOFTWARE: return TrapCause::IrqSoftware; - case IRQ_S_EXTERNAL: return TrapCause::IrqExternal; - default: return TrapCause::Unknown; - } - } - - /* Exception (synchronous) */ - switch (scause) { - case EXC_INST_PAGE_FAULT: return TrapCause::PageFaultExec; - case EXC_LOAD_PAGE_FAULT: return TrapCause::PageFaultLoad; - case EXC_STORE_PAGE_FAULT: return TrapCause::PageFaultStore; - case EXC_ECALL_FROM_U: return TrapCause::Syscall; - case EXC_ILLEGAL_INST: return TrapCause::IllegalInst; - case EXC_BREAKPOINT: return TrapCause::Breakpoint; - - case EXC_INST_ACCESS_FAULT: - case EXC_LOAD_ACCESS_FAULT: - case EXC_STORE_ACCESS_FAULT: - return TrapCause::AccessFault; - - case EXC_INST_MISALIGNED: - case EXC_LOAD_MISALIGNED: - case EXC_STORE_MISALIGNED: - return TrapCause::AlignFault; - - default: - return TrapCause::Unknown; - } -} - -TrapCause trap_translate_scause(uint64 scause) -{ - return scause_to_trap_cause(scause); -} - -static TrapOrigin origin_from_sstatus(uint64 sstatus) -{ - constexpr uint64 kSstatusSPP = (1ULL << 8); - return (sstatus & kSstatusSPP) ? TrapOrigin::FromKernel - : TrapOrigin::FromUser; -} - -static TrapClass classify_trap(uint64 scause, TrapCause cause) -{ - if (arch::trap::is_interrupt(scause)) { - return TrapClass::Interrupt; - } - if (cause == TrapCause::Syscall) { - return TrapClass::Syscall; - } - if (cause == TrapCause::Unknown) { - return TrapClass::Unknown; - } - return TrapClass::Exception; -} - -static TrapInfo build_trap_info(arch::TrapFrame* tf, - uint64 scause, - uint64 stval, - TrapOrigin origin) -{ - TrapInfo info{}; - info.cause = scause_to_trap_cause(scause); - info.cls = classify_trap(scause, info.cause); - info.origin = origin; - info.val = stval; - info.epc = tf ? tf->epc : 0; - info.scause = scause; - return info; -} - -/* ======================================================================== - * trap_dispatch — user-mode trap entry (called from trap_vector.S) - * - * Reads RISC-V CSRs, translates to MI TrapCause, applies arch-specific - * fixups, then calls MI trap_handle(). - * - * After trap_handle returns, we must return to user mode via userret(). - * The assembly jalr t3 that called us has NO return-to-user code after it, - * so trap_dispatch must never return normally. - * ======================================================================== */ - -extern "C" void userret(arch::TrapFrame* tf); - -extern "C" [[noreturn]] void trap_dispatch(arch::TrapFrame* tf) -{ - /* Switch stvec to kernelvec immediately so that any traps during - * kernel execution (timer IRQs, page faults from copy_from_user) - * go through the kernel trap entry, not the user trap entry. - * This matches Linux which sets stvec to handle_exception (kernel) - * at the top of the user trap path. */ - extern void kernelvec(); - arch::trap::set_vector(reinterpret_cast<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) { - curr->trapframe = tf; - } - - /* RISC-V ecall fix: ecall does not increment sepc. - * Advance epc so sret returns to the instruction AFTER ecall. */ - if (info.cause == TrapCause::Syscall) { - tf->epc += 4; - info.epc = tf->epc; - } - - /* RISC-V ebreak fix: advance past the C.EBREAK instruction (2 bytes). - * This is arch-specific because instruction size depends on the ISA. */ - if (info.cause == TrapCause::Breakpoint) { - tf->epc += 2; - info.epc = tf->epc; - } - - /* Call MI-layer dispatcher (zero inline assembly from here on) */ - trap_handle(tf, info); - - /* Return to user mode. - * Set stvec back to trap_vector for the next user-mode trap, - * then jump to userret which restores registers and does sret. */ - arch::trap::set_vector(reinterpret_cast<uint64>(&trap_vector)); - userret(tf); - __builtin_unreachable(); -} - -/* ======================================================================== - * kernel_trap_handler — traps taken while already in S-mode - * - * For Phase 2, only timer and external interrupts are expected. - * Exceptions in S-mode are always fatal. - * - * This belongs in MD because it reads scause directly. - * ======================================================================== */ - -extern "C" void kernel_trap_handler(arch::TrapFrame* tf) -{ - uint64 scause = arch::trap::get_cause(); - uint64 stval = arch::trap::get_value(); - - if (scause == EXC_LOAD_PAGE_FAULT || scause == EXC_STORE_PAGE_FAULT) { - uint64 fixup = search_extable(tf->epc); - if (fixup != 0) { - /* Exception table entry found: redirect execution to fixup code */ - tf->epc = fixup; - return; - } - } - - TrapInfo info = build_trap_info(tf, scause, stval, TrapOrigin::FromKernel); - trap_handle(tf, info); -} - -/* kernelvec assembly is in trap_vector.S */ -extern "C" void kernelvec(); - -/* ======================================================================== - * trap_init() — BSP: set stvec, init IrqManager - * ======================================================================== */ - -void trap_init() -{ - g_irq_manager.init(); - arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec)); -} - -/* ======================================================================== - * trap_init_ap() — AP: set this CPU's stvec - * ======================================================================== */ - -void trap_init_ap() -{ - arch::trap::set_vector(reinterpret_cast<uint64>(&kernelvec)); -} - diff --git a/na/arch/riscv/trap/trap_vector.S b/na/arch/riscv/trap/trap_vector.S deleted file mode 100644 index 8e45cae..0000000 --- a/na/arch/riscv/trap/trap_vector.S +++ /dev/null @@ -1,283 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Trap Vector Entry / Exit (RISC-V 64, S-mode) - * - * On trap entry (interrupt / exception / ecall): - * sscratch holds the TrapFrame pointer (set up by kernel before sret). - * We swap sscratch <-> tp, save all 31 GPRs + sstatus + sepc into TrapFrame, - * then load kernel metadata (sp, satp, tp, trap handler) and jump to C++. - * - * On trap return (userret): - * Restore all GPRs + sstatus + sepc from TrapFrame, swap sscratch <-> tp, - * then sret back to user mode. - * - * Layout must match arch::TrapFrame in arch/riscv/include/arch/trapframe.h - * Reference: docs/specs/arch_abstraction.md §7.2 - */ - -#include <arch/trapframe.h> - - .section .text - .globl trap_vector - .globl userret - .align 4 - -/* - * trap_vector — unified S-mode trap entry point (Direct mode) - * - * Precondition: sscratch = pointer to current task's TrapFrame - */ -trap_vector: - /* Step 1: swap sscratch <-> tp. Now tp = TrapFrame*, sscratch = old tp */ - csrrw tp, sscratch, tp - - /* Step 2: save all 31 general-purpose registers into TrapFrame */ - sd ra, TF_RA(tp) - sd sp, TF_SP(tp) - sd gp, TF_GP(tp) - /* tp (x4) is in sscratch — save it later */ - sd t0, TF_T0(tp) - sd t1, TF_T1(tp) - sd t2, TF_T2(tp) - sd s0, TF_S0(tp) - sd s1, TF_S1(tp) - sd a0, TF_A0(tp) - sd a1, TF_A1(tp) - sd a2, TF_A2(tp) - sd a3, TF_A3(tp) - sd a4, TF_A4(tp) - sd a5, TF_A5(tp) - sd a6, TF_A6(tp) - sd a7, TF_A7(tp) - sd s2, TF_S2(tp) - sd s3, TF_S3(tp) - sd s4, TF_S4(tp) - sd s5, TF_S5(tp) - sd s6, TF_S6(tp) - sd s7, TF_S7(tp) - sd s8, TF_S8(tp) - sd s9, TF_S9(tp) - sd s10, TF_S10(tp) - sd s11, TF_S11(tp) - sd t3, TF_T3(tp) - sd t4, TF_T4(tp) - sd t5, TF_T5(tp) - sd t6, TF_T6(tp) - - /* Save the real tp (user's tp) which is now in sscratch */ - csrr t0, sscratch - sd t0, TF_TP(tp) - - /* Step 3: save sstatus */ - csrr t0, sstatus - sd t0, TF_SSTATUS(tp) - - /* Step 4: save sepc (interrupted PC) */ - csrr t0, sepc - sd t0, TF_EPC(tp) - - /* Step 5: load kernel context from TrapFrame metadata */ - ld sp, TF_KERNEL_SP(tp) /* switch to kernel stack */ - ld t1, TF_KERNEL_SATP(tp) /* kernel page table */ - ld t2, TF_KERNEL_CPUID(tp) /* CPU ID for tp */ - ld t3, TF_KERNEL_TRAP(tp) /* C++ trap_handler address */ - - /* Switch to kernel page table */ - csrw satp, t1 - - /* Step 6: flush TLB after page table switch */ - sfence.vma zero, zero - - /* Step 6.5: restore kernel gp (global pointer). - * The compiler uses gp-relative addressing for global variable access - * (e.g. ld reg, offset(gp)). User mode may have set gp to anything, - * so we MUST reload it before entering any C++ code. */ -.option push -.option norelax - la gp, __global_pointer$ -.option pop - - /* Pass TrapFrame* to C++ handler (a0 = tf) */ - mv a0, tp - - /* Set tp = CPU ID (kernel convention) */ - mv tp, t2 - - /* Step 7: jump to C++ trap_handler */ - jalr t3 - - -/* ======================================================================== - * kernelvec — unified S-mode trap entry point for traps FROM S-MODE - * - * Traps from S-mode already have a valid kernel stack pointer in sp. - * We just allocate a TrapFrame on the stack and save registers. - * ======================================================================== */ - .globl kernelvec - .align 4 -kernelvec: - /* Allocate TrapFrame on stack */ - addi sp, sp, -296 - - /* Save all 31 general-purpose registers into TrapFrame */ - sd ra, TF_RA(sp) - /* Save the OLD sp (before we subtracted 296) */ - addi t0, sp, 296 - sd t0, TF_SP(sp) - sd gp, TF_GP(sp) - sd tp, TF_TP(sp) - sd t0, TF_T0(sp) - sd t1, TF_T1(sp) - sd t2, TF_T2(sp) - sd s0, TF_S0(sp) - sd s1, TF_S1(sp) - sd a0, TF_A0(sp) - sd a1, TF_A1(sp) - sd a2, TF_A2(sp) - sd a3, TF_A3(sp) - sd a4, TF_A4(sp) - sd a5, TF_A5(sp) - sd a6, TF_A6(sp) - sd a7, TF_A7(sp) - sd s2, TF_S2(sp) - sd s3, TF_S3(sp) - sd s4, TF_S4(sp) - sd s5, TF_S5(sp) - sd s6, TF_S6(sp) - sd s7, TF_S7(sp) - sd s8, TF_S8(sp) - sd s9, TF_S9(sp) - sd s10, TF_S10(sp) - sd s11, TF_S11(sp) - sd t3, TF_T3(sp) - sd t4, TF_T4(sp) - sd t5, TF_T5(sp) - sd t6, TF_T6(sp) - - /* Save sstatus and sepc */ - csrr t0, sstatus - sd t0, TF_SSTATUS(sp) - csrr t0, sepc - sd t0, TF_EPC(sp) - - /* Call C++ kernel_trap_handler(TrapFrame* tf) */ - mv a0, sp - call kernel_trap_handler - - /* Restore sstatus and sepc */ - ld t0, TF_SSTATUS(sp) - csrw sstatus, t0 - ld t0, TF_EPC(sp) - csrw sepc, t0 - - /* Restore all 31 general-purpose registers */ - ld ra, TF_RA(sp) - ld gp, TF_GP(sp) - ld tp, TF_TP(sp) - ld t0, TF_T0(sp) - ld t1, TF_T1(sp) - ld t2, TF_T2(sp) - ld s0, TF_S0(sp) - ld s1, TF_S1(sp) - ld a0, TF_A0(sp) - ld a1, TF_A1(sp) - ld a2, TF_A2(sp) - ld a3, TF_A3(sp) - ld a4, TF_A4(sp) - ld a5, TF_A5(sp) - ld a6, TF_A6(sp) - ld a7, TF_A7(sp) - ld s2, TF_S2(sp) - ld s3, TF_S3(sp) - ld s4, TF_S4(sp) - ld s5, TF_S5(sp) - ld s6, TF_S6(sp) - ld s7, TF_S7(sp) - ld s8, TF_S8(sp) - ld s9, TF_S9(sp) - ld s10, TF_S10(sp) - ld s11, TF_S11(sp) - ld t3, TF_T3(sp) - ld t4, TF_T4(sp) - ld t5, TF_T5(sp) - ld t6, TF_T6(sp) - - /* Restore sp last */ - ld sp, TF_SP(sp) - - sret - -/* - * userret — return to user mode - * - * Called from C++ as: userret(TrapFrame* tf) - * a0 = pointer to TrapFrame (with user satp already written by caller) - * - * Precondition: interrupts must be disabled by caller. - */ -userret: - /* a0 = TrapFrame pointer, use it as base */ - - /* Step 1: write back epc -> sepc */ - ld t0, TF_EPC(a0) - csrw sepc, t0 - - /* Step 2: write back sstatus */ - ld t0, TF_SSTATUS(a0) - csrw sstatus, t0 - - /* Step 3: caller is responsible for switching satp to user page table - * and flushing TLB before calling userret. */ - - /* Store TrapFrame address into sscratch for next trap entry */ - csrw sscratch, a0 - - /* Use a0 as base to restore all registers */ - mv tp, a0 - - /* Step 4: restore all 31 general-purpose registers */ - ld ra, TF_RA(tp) - ld sp, TF_SP(tp) - ld gp, TF_GP(tp) - /* defer tp restore — we're using it as base */ - ld t0, TF_T0(tp) - ld t1, TF_T1(tp) - ld t2, TF_T2(tp) - ld s0, TF_S0(tp) - ld s1, TF_S1(tp) - ld a0, TF_A0(tp) - ld a1, TF_A1(tp) - ld a2, TF_A2(tp) - ld a3, TF_A3(tp) - ld a4, TF_A4(tp) - ld a5, TF_A5(tp) - ld a6, TF_A6(tp) - ld a7, TF_A7(tp) - ld s2, TF_S2(tp) - ld s3, TF_S3(tp) - ld s4, TF_S4(tp) - ld s5, TF_S5(tp) - ld s6, TF_S6(tp) - ld s7, TF_S7(tp) - ld s8, TF_S8(tp) - ld s9, TF_S9(tp) - ld s10, TF_S10(tp) - ld s11, TF_S11(tp) - ld t3, TF_T3(tp) - ld t4, TF_T4(tp) - ld t5, TF_T5(tp) - ld t6, TF_T6(tp) - - /* Step 5: restore user tp (swap sscratch <-> tp) */ - /* sscratch currently = TrapFrame address, tp = TrapFrame address */ - /* We need: tp = user's tp, sscratch = TrapFrame address */ - /* Load user tp from TrapFrame first, but we can't use any temp reg now. - * Use the sscratch swap trick: */ - ld tp, TF_TP(tp) - - /* sscratch already holds TrapFrame address (set above with csrw) */ - - /* Step 6: sret — return to user mode */ - sret diff --git a/na/arch/riscv/uaccess/uaccess_memcpy.S b/na/arch/riscv/uaccess/uaccess_memcpy.S deleted file mode 100644 index 615680d..0000000 --- a/na/arch/riscv/uaccess/uaccess_memcpy.S +++ /dev/null @@ -1,117 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <arch/extable.h> - -/* - * extern "C" int __uaccess_memcpy(void* dst, const void* src, uint64 len); - * - * a0: dst - * a1: src - * a2: len - * Returns: 0 on success, -EFAULT (-14) on page fault. - */ -.global __uaccess_memcpy -.type __uaccess_memcpy, @function -__uaccess_memcpy: - /* If len == 0, return 0 */ - beqz a2, .Lmemcpy_done - - /* Enable SUM (Supervisor User Memory) bit: sstatus.SUM is bit 18 */ - li t0, (1 << 18) - csrs sstatus, t0 - -.Lmemcpy_loop: - /* Load byte from src */ -1: lb t1, 0(a1) - /* Store byte to dst */ -2: sb t1, 0(a0) - - addi a0, a0, 1 - addi a1, a1, 1 - addi a2, a2, -1 - bnez a2, .Lmemcpy_loop - -.Lmemcpy_done: - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, 0 - ret - -.Lmemcpy_fault: - /* Fixup routine on fault */ - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, -14 /* -EFAULT */ - ret - -/* Exception table entries */ -EXTABLE(1b, .Lmemcpy_fault) -EXTABLE(2b, .Lmemcpy_fault) - - -/* - * extern "C" int64 __uaccess_strncpy(char* dst, const char* src, uint64 max_len); - * - * a0: dst - * a1: src - * a2: max_len - * Returns: number of bytes copied (excluding null), or -EFAULT (-14) on page fault. - */ -.global __uaccess_strncpy -.type __uaccess_strncpy, @function -__uaccess_strncpy: - /* If max_len == 0, return 0 */ - beqz a2, .Lstrncpy_done_zero - - /* Enable SUM */ - li t0, (1 << 18) - csrs sstatus, t0 - - /* t2 will store the length copied */ - li t2, 0 - -.Lstrncpy_loop: - /* Load byte from src */ -3: lb t1, 0(a1) - /* Store byte to dst */ -4: sb t1, 0(a0) - - /* If it was the null terminator, we're done */ - beqz t1, .Lstrncpy_done - - addi a0, a0, 1 - addi a1, a1, 1 - addi t2, t2, 1 - addi a2, a2, -1 - bnez a2, .Lstrncpy_loop - - /* Reached max_len without finding null */ - /* We must null-terminate at max_len - 1 as per typical strncpy_from_user semantics */ - addi a0, a0, -1 - sb zero, 0(a0) - addi t2, t2, -1 /* length of string without the forced null terminator */ - -.Lstrncpy_done: - /* Disable SUM */ - li t0, (1 << 18) - csrc sstatus, t0 - mv a0, t2 - ret - -.Lstrncpy_done_zero: - li a0, 0 - ret - -.Lstrncpy_fault: - /* Fixup routine on fault */ - li t0, (1 << 18) - csrc sstatus, t0 - li a0, -14 /* -EFAULT */ - ret - -/* Exception table entries */ -EXTABLE(3b, .Lstrncpy_fault) -EXTABLE(4b, .Lstrncpy_fault) diff --git a/na/drivers/base/bus.cc b/na/drivers/base/bus.cc deleted file mode 100644 index 975f676..0000000 --- a/na/drivers/base/bus.cc +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/bus.cc — bus_type registration. - * - * Mirrors Linux drivers/base/bus.c. Trivial in this iteration: - * initialize the device/driver lists and the per-bus spinlock. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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 deleted file mode 100644 index f92b2a4..0000000 --- a/na/drivers/base/core.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/core.cc — device core. - * - * Mirrors Linux drivers/base/core.c (greatly simplified). Provides - * device_initialize, device_register, device_unregister, and the - * try_bind helper shared with driver registration. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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 deleted file mode 100644 index ce15b6d..0000000 --- a/na/drivers/base/driver.cc +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/driver.cc — driver_register / driver_unregister. - * - * Mirrors Linux drivers/base/driver.c. Registers a device_driver on - * its bus and tries to bind any unbound matching devices. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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 deleted file mode 100644 index 1d8fb05..0000000 --- a/na/drivers/base/module.mk +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Driver model core (mirrors Linux drivers/base/) - -SRCS_CC += drivers/base/bus.cc -SRCS_CC += drivers/base/core.cc -SRCS_CC += drivers/base/driver.cc -SRCS_CC += drivers/base/platform.cc diff --git a/na/drivers/base/platform.cc b/na/drivers/base/platform.cc deleted file mode 100644 index 65ec21e..0000000 --- a/na/drivers/base/platform.cc +++ /dev/null @@ -1,231 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * drivers/base/platform.cc — platform bus + FDT-driven device creation. - * - * Mirrors Linux drivers/base/platform.c. Provides: - * - platform_bus — the bus_type instance - * - platform_match — compatible-string matching via of_match_table - * - platform_dispatch — bus->probe forwarder that down-casts and calls - * platform_driver::probe(platform_device*) - * - driver_probe_all() — boot entry: register bus, register linker-set - * drivers, walk FDT, register platform_devices - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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 deleted file mode 100644 index f741dd2..0000000 --- a/na/drivers/console.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * console.cc — Console character device (/dev/console) - * - * Wraps the existing early_putc() UART driver as a file_operations - * chardev. read() is a stub for now (returns 0 = EOF). - * write() sends each byte through early_putc(). - */ - -#include <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/module.mk b/na/drivers/module.mk deleted file mode 100644 index 9326f30..0000000 --- a/na/drivers/module.mk +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# Drivers module - -SRCS_CC += drivers/uart.cc -SRCS_CC += drivers/console.cc - -ifeq ($(ARCH),riscv) -SRCS_CC += drivers/plic.cc -endif diff --git a/na/drivers/plic.cc b/na/drivers/plic.cc deleted file mode 100644 index 2818700..0000000 --- a/na/drivers/plic.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * plic.cc — Platform-Level Interrupt Controller (RISC-V) - * - * QEMU virt platform PLIC: - * Base PA: 0x0C000000 - * Size: 0x04000000 (64MB) - * - * Register layout (per RISC-V PLIC spec): - * 0x000000: Priority registers (source 0..1023, 4 bytes each) - * 0x001000: Pending bits (source 0..1023, bit per source) - * 0x002000: Enable bits per context - * 0x200000: Threshold + claim/complete per context - * - * Context mapping for QEMU virt: - * Context 2*hartid + 1 = S-mode context for hart N - * - * Reference: docs/specs/trap.md §5.1 - */ - -#include <arch/config.h> -#include <arch/cpu.h> -#include <lume/addr.h> -#include <lume/config.h> -#include <lume/fdt.h> -#include <lume/platform_device.h> -#include <lume/irq.h> -#include <lume/types.h> -#include <lume/vmm.h> - -/* PLIC virtual base (after MMIO mapping) */ -static uint64 plic_va; - -extern "C" uint32 bsp_hart_id; - -/* PLIC register offsets */ -static constexpr uint64 PLIC_PRIORITY_BASE = 0x000000; -static constexpr uint64 PLIC_PENDING_BASE = 0x001000; -static constexpr uint64 PLIC_ENABLE_BASE = 0x002000; -static constexpr uint64 PLIC_THRESHOLD_BASE = 0x200000; -static constexpr uint64 PLIC_CLAIM_BASE = 0x200004; - -/* Each context's enable/threshold/claim region is 0x80 bytes apart for enable, - * and 0x1000 bytes apart for threshold/claim. */ -static constexpr uint64 PLIC_ENABLE_STRIDE = 0x80; -static constexpr uint64 PLIC_CONTEXT_STRIDE = 0x1000; - -/* Dynamic S-mode context mapping */ -static uint32 g_s_context[kMaxCpus]; - -static inline uint64 s_context(uint64 hart_id) { - if (hart_id < kMaxCpus) - return g_s_context[hart_id]; - return 2 * hart_id + 1; /* safe fallback */ -} - -static bool str_equals_plic(const char *a, const char *b) { - if (!a || !b) - return false; - while (*a && *a == *b) { - a++; - b++; - } - return *a == *b; -} - -/* Read a 32-bit MMIO register */ -static inline uint32 plic_read32(uint64 offset) { - return *reinterpret_cast<volatile uint32 *>(plic_va + offset); -} - -/* Write a 32-bit MMIO register */ -static inline void plic_write32(uint64 offset, uint32 val) { - *reinterpret_cast<volatile uint32 *>(plic_va + offset) = val; -} - -/* ======================================================================== - * Public API - * ======================================================================== */ - -static void plic_probe(DeviceNode* node) { - uint64 plic_pa = 0; - - /* Initialize default fallback mapping */ - for (uint32 i = 0; i < kMaxCpus; i++) { - g_s_context[i] = 2 * i + 1; - } - - /* Dynamically discover PLIC base address and context mapping from FDT */ - if (node) { - plic_pa = node->get_prop_u64("reg", plic_pa); - - /* Build phandle to hart_id map */ - struct PhandleMap { - uint32 phandle; - uint32 hart_id; - } pmap[kMaxCpus]; - int num_cpus = 0; - - DeviceNode *cpus_node = fdt_get_node_by_path("/cpus"); - if (cpus_node) { - list_node *cur = cpus_node->children_list_head(); - list_node *end = cpus_node->children_list_sentinel(); - while (cur != end && num_cpus < kMaxCpus) { - DeviceNode *cpu = - list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur); - cur = cur->next; - - const char *type = cpu->get_prop_string("device_type"); - if (type && str_equals_plic(type, "cpu")) { - uint32 hart_id = cpu->get_prop_u32("reg"); - DeviceNode *intc = cpu->find_child("interrupt-controller"); - if (intc) { - uint32 phandle = intc->get_prop_u32("phandle"); - if (phandle == 0) - phandle = intc->get_prop_u32("linux,phandle"); - if (phandle != 0) { - pmap[num_cpus].phandle = phandle; - pmap[num_cpus].hart_id = hart_id; - num_cpus++; - } - } - } - } - } - - /* Parse interrupts-extended */ - const FdtProperty *ie_prop = node->get_prop("interrupts-extended"); - if (ie_prop && ie_prop->len > 0) { - uint32 len = ie_prop->len; - const uint8 *val = static_cast<const uint8 *>(ie_prop->value); - int context_idx = 0; - /* Each entry is 2 cells (8 bytes): [phandle, interrupt_type] */ - for (uint32 i = 0; i + 7 < len; i += 8) { - uint32 phandle = (uint32(val[i]) << 24) | (uint32(val[i + 1]) << 16) | - (uint32(val[i + 2]) << 8) | val[i + 3]; - uint32 irq_type = (uint32(val[i + 4]) << 24) | - (uint32(val[i + 5]) << 16) | - (uint32(val[i + 6]) << 8) | val[i + 7]; - - if (irq_type == 9) { /* IRQ_S_EXTERNAL */ - for (int h = 0; h < num_cpus; h++) { - if (pmap[h].phandle == phandle) { - if (pmap[h].hart_id < kMaxCpus) { - g_s_context[pmap[h].hart_id] = context_idx; - } - break; - } - } - } - context_idx++; - } - } - } - - if (plic_pa == 0) return; - - /* Map PLIC MMIO */ - vmm_map_kernel_mmio(plic_pa, 0x4000000); - plic_va = pa_to_va(phys_addr(plic_pa)).raw; - - /* Use the actual BSP hart ID (it may not be hart 0) */ - uint64 ctx = s_context(bsp_hart_id); - - /* Set priority for all sources to 1 (minimum non-zero = enabled) */ - for (uint32 irq = 1; irq < IrqManager::kMaxIrqs; irq++) { - plic_write32(PLIC_PRIORITY_BASE + irq * 4, 1); - } - - /* Set threshold to 0 for BSP context (accept all priorities > 0) */ - plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); -} - -static void plic_init_ap() { - uint64 ctx = s_context(arch::cpu::id()); - - /* Set threshold to 0 for this AP's context */ - plic_write32(PLIC_THRESHOLD_BASE + ctx * PLIC_CONTEXT_STRIDE, 0); -} - -static void plic_enable(uint32 irq) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) { - return; - } - - uint64 ctx = s_context(arch::cpu::id()); - uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; - uint32 word_index = irq / 32; - uint32 bit_index = irq % 32; - - uint32 val = plic_read32(offset + word_index * 4); - val |= (1U << bit_index); - plic_write32(offset + word_index * 4, val); -} - -static void plic_disable(uint32 irq) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) { - return; - } - - uint64 ctx = s_context(arch::cpu::id()); - uint64 offset = PLIC_ENABLE_BASE + ctx * PLIC_ENABLE_STRIDE; - uint32 word_index = irq / 32; - uint32 bit_index = irq % 32; - - uint32 val = plic_read32(offset + word_index * 4); - val &= ~(1U << bit_index); - plic_write32(offset + word_index * 4, val); -} - -static void plic_set_priority(uint32 irq, uint32 priority) { - if (irq == 0 || irq >= IrqManager::kMaxIrqs) - return; - plic_write32(PLIC_PRIORITY_BASE + (irq * 4), priority); -} - -static uint32 plic_claim() { - uint64 ctx = s_context(arch::cpu::id()); - return plic_read32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE); -} - -static void plic_complete(uint32 irq) { - uint64 ctx = s_context(arch::cpu::id()); - plic_write32(PLIC_CLAIM_BASE + ctx * PLIC_CONTEXT_STRIDE, irq); -} - -static const IrqChip plic_chip = { - "riscv,plic0", - plic_claim, - plic_complete, - plic_init_ap, - plic_enable, - plic_disable, - plic_set_priority -}; - -static int plic_platform_probe(platform_device* pdev) { - plic_probe(pdev->of_node); - g_irq_manager.set_chip(&plic_chip); - return 0; -} - -static const of_device_id plic_of_match[] = { - { .compatible = "riscv,plic0", .data = nullptr }, - { .compatible = nullptr, .data = nullptr }, /* sentinel */ -}; - -platform_driver plic_driver = { - .driver = { .name = "plic", .bus = nullptr, - .probe = nullptr, .remove = nullptr, - .bus_node = {}, .devices = {} }, - .probe = plic_platform_probe, - .remove = nullptr, - .of_match_table = plic_of_match, -}; - -PLATFORM_DRIVER(plic_driver); diff --git a/na/drivers/tty/module.mk b/na/drivers/tty/module.mk deleted file mode 100644 index 49190d4..0000000 --- a/na/drivers/tty/module.mk +++ /dev/null @@ -1,5 +0,0 @@ -# drivers/tty/module.mk — TTY subsystem build rules - -SRCS_CC += drivers/tty/tty_core.cc -SRCS_CC += drivers/tty/n_tty.cc -SRCS_CC += drivers/tty/tty_uart.cc diff --git a/na/drivers/tty/n_tty.cc b/na/drivers/tty/n_tty.cc deleted file mode 100644 index 0e312b0..0000000 --- a/na/drivers/tty/n_tty.cc +++ /dev/null @@ -1,539 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * n_tty.cc — N_TTY line discipline - * - * Implements the standard terminal line discipline: - * - * Canonical mode (ICANON): - * Input is buffered until newline/EOF. Backspace (VERASE) and - * kill-line (VKILL) editing are supported. - * - * Raw mode (!ICANON): - * Characters are available immediately (subject to VMIN/VTIME). - * - * Signal generation (ISIG): - * VINTR → SIGINT, VQUIT → SIGQUIT, VSUSP → SIGTSTP - * - * Echo (ECHO): - * Typed characters are reflected to output. - * - * Input processing (c_iflag): - * ICRNL: CR → NL mapping - * INLCR: NL → CR mapping - * IGNCR: ignore CR - * ISTRIP: strip high bit - * - * Output processing (c_oflag): - * OPOST + ONLCR: NL → CR+NL on output - * - * Reference: Linux drivers/tty/n_tty.c - */ - -#include <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 deleted file mode 100644 index 5985bd9..0000000 --- a/na/drivers/tty/tty_core.cc +++ /dev/null @@ -1,536 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tty_core.cc — TTY subsystem core - * - * Provides the tty_struct lifecycle, file_operations dispatch, - * and ioctl routing between the line discipline and driver. - * - * Call chain: - * sys_read → tty_fops.read → tty_read → ldisc.read - * sys_write → tty_fops.write → tty_write → ldisc.write - * sys_ioctl → tty_fops.ioctl → tty_ioctl → {core / ldisc / driver} - */ - -#include <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 deleted file mode 100644 index 973a6e9..0000000 --- a/na/drivers/tty/tty_uart.cc +++ /dev/null @@ -1,251 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tty_uart.cc — UART → tty_driver adapter - * - * Bridges the 16550A UART hardware to the TTY subsystem. - * - * TX: Calls early_putc() for each character (same as before). - * RX: Registers a PLIC IRQ handler. On UART RX interrupt, - * reads available bytes and feeds them to the line discipline - * via tty_receive_buf(). - * - * QEMU virt UART: - * - IRQ 10 on the PLIC - * - 16550A register layout at the base address discovered from FDT - */ - -#include <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 deleted file mode 100644 index 467b496..0000000 --- a/na/drivers/uart.cc +++ /dev/null @@ -1,181 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * uart.cc — 16550a UART driver + console registry - * - * Two roles: - * 1. Early console: early_putc() for pre-init output (panic, kprintf) - * 2. Runtime console: struct console registered with register_console(). - * klog/printk route through console_write_all() → uart_console_write(). - * - * The console registry is a simple linked list of struct console. - * console_write_all() iterates the list and calls each console's write(). - * If no console is registered, it falls back to early_putc(). - * - * Reference: Linux kernel/printk/printk.c, drivers/tty/serial/8250/ - */ - -#include <lume/console.h> -#include <lume/fdt.h> -#include <lume/vmm.h> -#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; -} - -extern "C" void early_putc(char c) { - if (!g_uart_pa) return; - - /* - * Before vmm_init() activates the final page table, UART MMIO is only - * reachable via the bootstrap fixed direct map from entry.S. - * After activation, switch to the runtime (possibly KASLR-slid) base. - */ - uint64 uart_va = 0; - if (g_runtime_mapping_ready) { - uart_va = pa_to_va(phys_addr(g_uart_pa)).raw; - } else if (!g_mmu_enabled) { - uart_va = g_uart_pa; - } else { - uart_va = boot_pa_to_va(phys_addr(g_uart_pa)).raw; - } - - volatile auto* uart = reinterpret_cast<volatile uint8*>(uart_va); - *uart = static_cast<uint8>(c); -} - -extern "C" void early_puts(const char* s) { - while (*s) early_putc(*s++); -} - -void console_early_init(uint64 fdt_paddr) { - uint64 pa = 0; - uint64 size = 0; - FdtManager::early_scan_uart(fdt_paddr, &pa, &size); - if (pa != 0) { - g_uart_pa = pa; - if (size != 0) g_uart_size = size; - } -} - -void console_init() { - /* - * Once vmm_init() activates the new page table, the 1GB identity map is dropped. - * The UART must be explicitly registered into the active page table. - */ - if (g_uart_pa != 0 && g_uart_size != 0) { - vmm_map_kernel_mmio(g_uart_pa, g_uart_size); - } -} - -void console_use_runtime_mapping() { - g_runtime_mapping_ready = true; -} - -/* ================================================================== */ -/* Console registry (Phase 2) — linked list of struct console */ -/* ================================================================== */ - -static list_node g_console_list; -static bool g_console_list_init = false; - -void register_console(console* con) -{ - if (!con) return; - - if (!g_console_list_init) { - g_console_list.init(); - g_console_list_init = true; - } - - con->flags |= CON_ENABLED; - con->list.init(); - - /* Insert at tail */ - list_add_tail(&con->list, &g_console_list); -} - -void unregister_console(console* con) -{ - if (!con) return; - - list_del_init(&con->list); - con->flags &= ~CON_ENABLED; -} - -bool console_drivers_registered() -{ - if (!g_console_list_init) return false; - return !list_empty(&g_console_list); -} - -void console_write_all(const char* s, int count) -{ - if (g_console_list_init && !list_empty(&g_console_list)) { - list_node* pos; - list_for_each(pos, &g_console_list) { - auto* con = container_of(pos, console, list); - if ((con->flags & CON_ENABLED) && con->write) { - con->write(con, s, static_cast<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/fdt/device_node.cc b/na/fdt/device_node.cc deleted file mode 100644 index 0c8ad46..0000000 --- a/na/fdt/device_node.cc +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * device_node.cc — DeviceNode implementation - * - * Provides property lookup and child traversal for the unflattened - * device tree. After construction, the tree is globally read-only. - * - * Reference: docs/specs/fdt.md §3.2 - */ - -#include <lume/device_node.h> - -/* Minimal strcmp — no lib dependency */ -static bool str_eq(const char *a, const char *b) -{ - while (*a && *a == *b) { a++; b++; } - return *a == *b; -} - -/* ------------------------------------------------------------------ */ -/* Construction / Destruction */ -/* ------------------------------------------------------------------ */ - -DeviceNode::DeviceNode(DeviceNode *parent, const char *name) - : name_(name), parent_(parent) -{ - children_list_.init(); - props_list_.init(); - sibling_link_.init(); -} - -DeviceNode::~DeviceNode() -{ - /* Recursively delete children */ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - list_node *next = cur->next; - /* Recover DeviceNode* from sibling_link_ offset */ - DeviceNode *child = list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur); - delete child; - cur = next; - } - - /* Delete properties */ - list_node *pc = props_list_.next; - while (pc != &props_list_) { - list_node *pn = pc->next; - FdtProperty *prop = reinterpret_cast<FdtProperty *>( - reinterpret_cast<uintptr>(pc) - - __builtin_offsetof(FdtProperty, link)); - delete prop; - pc = pn; - } -} - -/* ------------------------------------------------------------------ */ -/* Tree construction (used by FdtManager::unflatten) */ -/* ------------------------------------------------------------------ */ - -void DeviceNode::add_child(DeviceNode *child) -{ - /* Append to end of children list */ - list_node *node = &child->sibling_link_; - node->prev = children_list_.prev; - node->next = &children_list_; - children_list_.prev->next = node; - children_list_.prev = node; -} - -void DeviceNode::add_prop(FdtProperty *prop) -{ - list_node *node = &prop->link; - node->prev = props_list_.prev; - node->next = &props_list_; - props_list_.prev->next = node; - props_list_.prev = node; -} - -/* ------------------------------------------------------------------ */ -/* Property access */ -/* ------------------------------------------------------------------ */ - -const FdtProperty *DeviceNode::get_prop(const char *name) const -{ - const list_node *cur = props_list_.next; - while (cur != &props_list_) { - const FdtProperty *prop = reinterpret_cast<const FdtProperty *>( - reinterpret_cast<uintptr>(cur) - - __builtin_offsetof(FdtProperty, link)); - if (str_eq(prop->name, name)) - return prop; - cur = cur->next; - } - return nullptr; -} - -/* Big-endian helpers for decoding property values */ -static inline uint32 be32(const void *p) -{ - const uint8 *b = static_cast<const uint8 *>(p); - return (uint32(b[0]) << 24) | (uint32(b[1]) << 16) | - (uint32(b[2]) << 8) | uint32(b[3]); -} - -static inline uint64 be64(const void *p) -{ - const uint8 *b = static_cast<const uint8 *>(p); - return (uint64(be32(b)) << 32) | uint64(be32(b + 4)); -} - -uint32 DeviceNode::get_prop_u32(const char *name, uint32 default_val) const -{ - const FdtProperty *p = get_prop(name); - if (!p || p->len < 4) - return default_val; - return be32(p->value); -} - -uint64 DeviceNode::get_prop_u64(const char *name, uint64 default_val) const -{ - const FdtProperty *p = get_prop(name); - if (!p) - return default_val; - if (p->len >= 8) - return be64(p->value); - if (p->len >= 4) - return be32(p->value); - return default_val; -} - -const char *DeviceNode::get_prop_string(const char *name) const -{ - const FdtProperty *p = get_prop(name); - if (!p || p->len == 0) - return nullptr; - return static_cast<const char *>(p->value); -} - -/* ------------------------------------------------------------------ */ -/* Child traversal */ -/* ------------------------------------------------------------------ */ - -DeviceNode *DeviceNode::find_child(const char *name) -{ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - DeviceNode *child = list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur); - if (str_eq(child->name_, name)) - return child; - cur = cur->next; - } - return nullptr; -} - -void DeviceNode::for_each_child(void (*callback)(DeviceNode *)) -{ - list_node *cur = children_list_.next; - while (cur != &children_list_) { - list_node *next = cur->next; - DeviceNode *child = list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur); - callback(child); - cur = next; - } -} diff --git a/na/fdt/fdt.cc b/na/fdt/fdt.cc deleted file mode 100644 index d44ebda..0000000 --- a/na/fdt/fdt.cc +++ /dev/null @@ -1,625 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fdt.cc — Flattened Device Tree parser implementation - * - * Implements the two-phase FDT parsing strategy described in fdt.md: - * - * Phase 1 (Early Scan): - * Static methods that linearly walk the raw FDT blob in-place, - * performing zero memory allocation. Called before PMM/Slab exist. - * - * Phase 2 (Unflatten): - * Builds a persistent DeviceNode C++ object tree using Slab. - * Stub implementation — will be completed when Slab is available. - * - * Reference: docs/specs/fdt.md - */ - -#include <lume/addr.h> -#include <lume/fdt.h> -#include <lume/klog.h> -#include <lume/new.h> - -/* ------------------------------------------------------------------ */ -/* Global singleton (Placement New pattern, see global_init.md §3.1) */ -/* ------------------------------------------------------------------ */ - -alignas(FdtManager) uint8 g_fdt_buf[sizeof(FdtManager)]; -FdtManager *g_fdt = nullptr; - -/* ------------------------------------------------------------------ */ -/* FDT binary format constants (DTSpec v0.4) */ -/* ------------------------------------------------------------------ */ - -struct fdt_header { - uint32 magic; - uint32 totalsize; - uint32 off_dt_struct; - uint32 off_dt_strings; - uint32 off_mem_rsvmap; - uint32 version; - uint32 last_comp_version; - uint32 boot_cpuid_phys; - uint32 size_dt_strings; - uint32 size_dt_struct; -}; - -#define FDT_MAGIC 0xd00dfeed -#define FDT_BEGIN_NODE 1 -#define FDT_END_NODE 2 -#define FDT_PROP 3 -#define FDT_NOP 4 -#define FDT_END 9 - -/* ------------------------------------------------------------------ */ -/* Minimal string helpers (no lib/ dependency for early boot) */ -/* ------------------------------------------------------------------ */ - -static inline bool str_starts_with(const char *str, const char *prefix) { - while (*prefix) { - if (*str++ != *prefix++) - return false; - } - return true; -} - -static inline bool str_equals(const char *s1, const char *s2) { - while (*s1 && *s1 == *s2) { - s1++; - s2++; - } - return *s1 == *s2; -} - -/* - * Check whether a null-separated string list (e.g. the "compatible" - * property) contains @target. - */ -static bool prop_contains_string(const char *prop_val, uint32 len, - const char *target) { - uint32 i = 0; - while (i < len) { - const char *s = prop_val + i; - if (str_equals(s, target)) - return true; - while (i < len && prop_val[i] != '\0') - i++; - i++; // skip '\0' separator - } - return false; -} - -/* ------------------------------------------------------------------ */ -/* Byte-swap helpers */ -/* ------------------------------------------------------------------ */ - -/* - * Manual 32-bit big-endian to little-endian conversion. - * We cannot use __builtin_bswap32 because on RISC-V without the Zbb - * extension the compiler emits a call to libgcc's __bswapsi2, which - * is unavailable in a freestanding (-nostdlib) environment. - */ -static inline uint32 bswap32(uint32 x) { - return ((x & 0xFF000000u) >> 24) | ((x & 0x00FF0000u) >> 8) | - ((x & 0x0000FF00u) << 8) | ((x & 0x000000FFu) << 24); -} - -/* - * Read a big-endian cell value from the FDT struct block. - * @cells: number of 32-bit cells (1 or 2). - * 2-cell values are common for 64-bit addresses/sizes. - */ -static inline uint64 read_cells(const uint8 *data, uint32 cells) { - const uint32 *p = reinterpret_cast<const uint32 *>(data); - if (cells == 2) { - uint64 high = bswap32(p[0]); - uint64 low = bswap32(p[1]); - return (high << 32) | low; - } - return bswap32(p[0]); -} - -/* ------------------------------------------------------------------ */ -/* FdtManager construction */ -/* ------------------------------------------------------------------ */ - -FdtManager::FdtManager(uint64 fdt_paddr) - : fdt_paddr_(fdt_paddr) {} - -/* ------------------------------------------------------------------ */ -/* Phase 1: Early scan — memory layout */ -/* ------------------------------------------------------------------ */ - -/* - * Walk the raw FDT struct block to locate the /memory node and - * extract its "reg" property (base address + size). - * - * Cell sizes (#address-cells, #size-cells) are tracked per depth - * level to correctly decode the "reg" value under any bus topology. - * - * Panics on invalid magic or missing /memory node — there is no - * recovery path this early in boot. - */ -void FdtManager::early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size) { - const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) - kernel_panic("FDT early_scan_mem: invalid magic number", nullptr); - - const uint8 *dt_struct = - reinterpret_cast<const uint8 *>(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast<const char *>(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - - // Per-depth cell sizes (inherit from parent on entry) - uint32 address_cells[16] = {2}; - uint32 size_cells[16] = {1}; - bool in_memory_node = false; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast<const char *>(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; // align to 4 bytes - - depth++; - - // Inherit parent's cell sizes - if (depth > 0 && depth < 16) { - address_cells[depth] = address_cells[depth - 1]; - size_cells[depth] = size_cells[depth - 1]; - } - - // Match top-level "memory" or "memory@..." nodes - in_memory_node = (depth == 1 && str_starts_with(name, "memory")); - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - in_memory_node = false; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const uint8 *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16) { - // Track cell size overrides at the current depth - if (str_equals(prop_name, "#address-cells")) { - address_cells[depth] = - bswap32(*reinterpret_cast<const uint32 *>(prop_val)); - } else if (str_equals(prop_name, "#size-cells")) { - size_cells[depth] = - bswap32(*reinterpret_cast<const uint32 *>(prop_val)); - } - // Decode the "reg" property using the parent's cell sizes - else if (in_memory_node && str_equals(prop_name, "reg")) { - uint32 p_depth = (depth > 0) ? depth - 1 : 0; - *base = read_cells(prop_val, address_cells[p_depth]); - *size = read_cells(prop_val + address_cells[p_depth] * 4, - size_cells[p_depth]); - return; // Success - } - } - - offset = (offset + len + 3) & ~3u; // align to 4 bytes - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - kernel_panic("FDT early_scan_mem: unknown token", nullptr); - } - } - - kernel_panic("FDT early_scan_mem: /memory node not found", nullptr); -} - -/* ------------------------------------------------------------------ */ -/* Phase 1: Early scan — UART address */ -/* ------------------------------------------------------------------ */ - -/* - * Locate a UART node by matching compatible = "ns16550a" and return - * its base address from the "reg" property. - * - * Unlike early_scan_mem, failure here is non-fatal: we fall back to - * the platform's default UART address (0x10000000 for QEMU virt). - */ -void FdtManager::early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, - uint64 *uart_size) { - if (!uart_addr || !uart_size) - return; - - auto set_default_uart = [uart_addr, uart_size]() { - *uart_addr = 0x10000000ULL; - *uart_size = 0x100; - }; - - const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) { - set_default_uart(); - return; - } - - const uint8 *dt_struct = - reinterpret_cast<const uint8 *>(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast<const char *>(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - uint32 address_cells[16] = {2}; - uint32 size_cells[16] = {1}; - bool in_uart_node = false; - bool has_current_node_reg = false; - uint64 current_node_reg = 0; - uint64 current_node_size = 0; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast<const char *>(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth > 0 && depth < 16) { - address_cells[depth] = address_cells[depth - 1]; - size_cells[depth] = size_cells[depth - 1]; - } - in_uart_node = false; - has_current_node_reg = false; - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - in_uart_node = false; - has_current_node_reg = false; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const uint8 *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16) { - if (str_equals(prop_name, "#address-cells")) { - address_cells[depth] = - bswap32(*reinterpret_cast<const uint32 *>(prop_val)); - } else if (str_equals(prop_name, "#size-cells")) { - size_cells[depth] = - bswap32(*reinterpret_cast<const uint32 *>(prop_val)); - } else if (str_equals(prop_name, "compatible")) { - if (prop_contains_string(reinterpret_cast<const char *>(prop_val), - len, "ns16550a")) { - in_uart_node = true; - if (has_current_node_reg) { - *uart_addr = current_node_reg; - *uart_size = current_node_size; - return; - } - } - } else if (str_equals(prop_name, "reg")) { - uint32 p_depth = (depth > 0) ? depth - 1 : 0; - current_node_reg = read_cells(prop_val, address_cells[p_depth]); - current_node_size = read_cells(prop_val + address_cells[p_depth] * 4, - size_cells[p_depth]); - has_current_node_reg = true; - if (in_uart_node) { - *uart_addr = current_node_reg; - *uart_size = current_node_size; - return; // Found it - } - } - } - - offset = (offset + len + 3) & ~3u; - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - break; // Unknown token — give up silently - } - } - - set_default_uart(); -} - -/* ------------------------------------------------------------------ */ -/* Phase 2: Unflatten (stub — requires Slab) */ -/* ------------------------------------------------------------------ */ - -void FdtManager::unflatten() { - const uint64 fdt_va = pa_to_va(phys_addr(fdt_paddr_)).raw; - const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_va); - if (bswap32(hdr->magic) != FDT_MAGIC) - kernel_panic("fdt unflatten: invalid magic", nullptr); - - const uint8 *dt_struct = - reinterpret_cast<const uint8 *>(fdt_va) + bswap32(hdr->off_dt_struct); - const char *dt_strings = - reinterpret_cast<const char *>(fdt_va) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - - /* Stack to track current node at each depth (max 16 levels) */ - DeviceNode *stack[16] = {}; - int depth = -1; - - while (true) { - uint32 token = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast<const char *>(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') - name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth >= 16) - kernel_panic("fdt unflatten: tree too deep", nullptr); - - DeviceNode *parent = (depth > 0) ? stack[depth - 1] : nullptr; - DeviceNode *node = new DeviceNode(parent, name); - if (!node) - kernel_panic("fdt unflatten: OOM creating DeviceNode", nullptr); - - stack[depth] = node; - - if (parent) - parent->add_child(node); - else - root_ = node; /* Root node */ - - } else if (token == FDT_END_NODE) { - if (depth >= 0) - depth--; - - } else if (token == FDT_PROP) { - uint32 len = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - uint32 nameoff = - bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset + 4)); - offset += 8; - - const char *prop_name = dt_strings + nameoff; - const void *prop_val = dt_struct + offset; - - if (depth >= 0 && depth < 16 && stack[depth]) { - FdtProperty *prop = new FdtProperty; - if (!prop) - kernel_panic("fdt unflatten: OOM creating FdtProperty", nullptr); - prop->name = prop_name; - prop->value = prop_val; - prop->len = static_cast<int>(len); - prop->link.init(); - stack[depth]->add_prop(prop); - } - - offset = (offset + len + 3) & ~3u; - - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - kernel_panic("fdt unflatten: unknown token", nullptr); - } - } - - is_unflattened_ = true; -} - -/* Recursive helper: find first node with matching compatible string */ -static DeviceNode *find_compat_recursive(DeviceNode *node, const char *compat) { - const FdtProperty *prop = node->get_prop("compatible"); - if (prop) { - const char *val = static_cast<const char *>(prop->value); - uint32 i = 0; - while (i < static_cast<uint32>(prop->len)) { - const char *s = val + i; - if (str_equals(s, compat)) - return node; - while (i < static_cast<uint32>(prop->len) && val[i] != '\0') - i++; - i++; - } - } - - /* Search children */ - list_node *cur = node->children_list_head(); - list_node *end = node->children_list_sentinel(); - while (cur != end) { - DeviceNode *child = list_entry<DeviceNode, &DeviceNode::sibling_link_>(cur); - DeviceNode *found = find_compat_recursive(child, compat); - if (found) - return found; - cur = cur->next; - } - return nullptr; -} - -DeviceNode *FdtManager::find_compatible(const char *compat_string) { - if (!is_unflattened_) - kernel_panic("fdt: tree not unflattened yet", nullptr); - if (!root_) - return nullptr; - return find_compat_recursive(root_, compat_string); -} - -DeviceNode *FdtManager::get_node_by_path(const char *path) { - if (!is_unflattened_) - kernel_panic("fdt: tree not unflattened yet", nullptr); - if (!root_ || !path) - return nullptr; - - /* Skip leading '/' */ - if (*path == '/') - path++; - if (*path == '\0') - return root_; - - DeviceNode *cur = root_; - while (*path && cur) { - /* Extract next path component */ - const char *end = path; - while (*end && *end != '/') - end++; - - /* Search children for a name matching this component */ - bool found = false; - list_node *lc = cur->children_list_head(); - list_node *le = cur->children_list_sentinel(); - while (lc != le) { - DeviceNode *child = - list_entry<DeviceNode, &DeviceNode::sibling_link_>(lc); - /* Compare name up to the component length */ - const char *cn = child->get_name(); - const char *p = path; - const char *e = end; - bool match = true; - while (p < e) { - if (*cn != *p) { - match = false; - break; - } - cn++; - p++; - } - if (match && *cn == '\0') { - cur = child; - found = true; - break; - } - lc = lc->next; - } - - if (!found) - return nullptr; - - path = (*end == '/') ? end + 1 : end; - } - return cur; -} - -/* ------------------------------------------------------------------ */ -/* Public free-function API */ -/* ------------------------------------------------------------------ */ - -void fdt_init(uint64 fdt_paddr) { - const uint32 *magic_ptr = reinterpret_cast<const uint32 *>(fdt_paddr); - if (bswap32(*magic_ptr) != FDT_MAGIC) - kernel_panic("fdt_init: invalid FDT magic number", nullptr); - - g_fdt = new (g_fdt_buf) FdtManager(fdt_paddr); -} - -void fdt_unflatten() { g_fdt->unflatten(); } - -DeviceNode *fdt_find_compatible(const char *compat_string) { - return g_fdt->find_compatible(compat_string); -} - -DeviceNode *fdt_get_node_by_path(const char *path) { - return g_fdt->get_node_by_path(path); -} - -void fdt_early_get_mem_info(uint64 *base, uint64 *size) { - FdtManager::early_scan_mem(g_fdt->get_paddr(), base, size); -} - -void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size) { - FdtManager::early_scan_uart(g_fdt->get_paddr(), uart_addr, uart_size); -} - -void FdtManager::early_scan_kaslr(uint64 fdt_paddr, uint64 *seed) -{ - if (!seed) return; - *seed = 0; - - const fdt_header *hdr = reinterpret_cast<const fdt_header *>(fdt_paddr); - if (bswap32(hdr->magic) != FDT_MAGIC) return; - - const uint8 *dt_struct = reinterpret_cast<const uint8 *>(fdt_paddr) + bswap32(hdr->off_dt_struct); - const char *dt_strings = reinterpret_cast<const char *>(fdt_paddr) + bswap32(hdr->off_dt_strings); - - uint32 offset = 0; - int depth = -1; - bool in_chosen = false; - - while (true) { - uint32 token = bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - offset += 4; - - if (token == FDT_BEGIN_NODE) { - const char *name = reinterpret_cast<const char *>(dt_struct + offset); - uint32 name_len = 0; - while (name[name_len] != '\0') name_len++; - offset = (offset + name_len + 1 + 3) & ~3u; - - depth++; - if (depth == 1 && str_starts_with(name, "chosen")) { - in_chosen = true; - } else if (depth == 1) { - in_chosen = false; - } - } else if (token == FDT_END_NODE) { - if (depth >= 0) depth--; - if (depth < 1) in_chosen = false; - } else if (token == FDT_PROP) { - uint32 len = bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset)); - uint32 nameoff = bswap32(*reinterpret_cast<const uint32 *>(dt_struct + offset + 4)); - offset += 8; - - if (in_chosen) { - const char *prop_name = dt_strings + nameoff; - if (str_starts_with(prop_name, "kaslr-seed") && len == 8) { - *seed = read_cells(dt_struct + offset, 2); - return; - } - } - offset = (offset + len + 3) & ~3u; - } else if (token == FDT_NOP) { - continue; - } else if (token == FDT_END) { - break; - } else { - break; - } - } -} - -void fdt_early_get_kaslr_seed(uint64 *seed) { - FdtManager::early_scan_kaslr(g_fdt->get_paddr(), seed); -} diff --git a/na/fdt/module.mk b/na/fdt/module.mk deleted file mode 100644 index 3e490ca..0000000 --- a/na/fdt/module.mk +++ /dev/null @@ -1,3 +0,0 @@ -# fdt/module.mk -SRCS_CC += fdt/fdt.cc -SRCS_CC += fdt/device_node.cc diff --git a/na/fs/chardev.cc b/na/fs/chardev.cc deleted file mode 100644 index fa96276..0000000 --- a/na/fs/chardev.cc +++ /dev/null @@ -1,43 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * chardev.cc — Character device registry - * - * Simple linked list of registered cdevs, searched by dev number. - */ - -#include <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 deleted file mode 100644 index 94ab287..0000000 --- a/na/fs/dcache.cc +++ /dev/null @@ -1,287 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * dcache.cc — Directory entry cache - * - * Provides dentry allocation, lookup, instantiation, and d_path. - */ - -#include <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 deleted file mode 100644 index b4b2cf3..0000000 --- a/na/fs/devfs.cc +++ /dev/null @@ -1,137 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * devfs.cc — /dev population and chardev-aware open - * - * Creates /dev as a subdirectory under the ramfs root, then - * populates device nodes. On open(), if the inode is S_IFCHR, - * we look up the cdev by i_rdev and use its fops. - */ - -#include <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 deleted file mode 100644 index 5fc8ab0..0000000 --- a/na/fs/fd.cc +++ /dev/null @@ -1,216 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fd.cc — File descriptor table management - */ - -#include <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 deleted file mode 100644 index 06095c1..0000000 --- a/na/fs/file_table.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * file_table.cc — Open file allocation and reference counting - */ - -#include <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 deleted file mode 100644 index 090819f..0000000 --- a/na/fs/fs_struct.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * fs_struct.cc — Per-task filesystem context (cwd, root) - */ - -#include <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 deleted file mode 100644 index ba10a3e..0000000 --- a/na/fs/initramfs.cc +++ /dev/null @@ -1,285 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * initramfs.cc — Unpack embedded CPIO archive into ramfs - * - * The build system embeds a newc-format CPIO archive into the kernel - * ELF as the section .init.ramfs. The linker script exports - * __initramfs_start / __initramfs_end symbols. - * - * At boot, populate_rootfs() walks the CPIO entries and creates - * directories and files in the already-mounted ramfs root. - * - * Reference: Linux init/initramfs.c - * - * CPIO "newc" header format (110 bytes, ASCII hex fields): - * magic[6] "070701" - * ino[8] mode[8] uid[8] gid[8] nlink[8] mtime[8] - * filesize[8] devmajor[8] devminor[8] rdevmajor[8] rdevminor[8] - * namesize[8] check[8] - * filename (namesize bytes, NUL terminated, padded to 4-byte boundary) - * file data (filesize bytes, padded to 4-byte boundary) - */ - -#include <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 deleted file mode 100644 index 1d3d006..0000000 --- a/na/fs/inode.cc +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * inode.cc — Inode allocation, lifecycle, and generic helpers - */ - -#include <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 deleted file mode 100644 index f1be157..0000000 --- a/na/fs/module.mk +++ /dev/null @@ -1,17 +0,0 @@ -# fs/module.mk — VFS subsystem build rules - -SRCS_CC += fs/super.cc -SRCS_CC += fs/inode.cc -SRCS_CC += fs/dcache.cc -SRCS_CC += fs/file_table.cc -SRCS_CC += fs/fd.cc -SRCS_CC += fs/fs_struct.cc -SRCS_CC += fs/namei.cc -SRCS_CC += fs/ramfs/inode.cc -SRCS_CC += fs/ramfs/file.cc -SRCS_CC += fs/ramfs/super.cc -SRCS_CC += fs/vfs_init.cc -SRCS_CC += fs/chardev.cc -SRCS_CC += fs/devfs.cc -SRCS_CC += fs/pipe.cc -SRCS_CC += fs/initramfs.cc diff --git a/na/fs/namei.cc b/na/fs/namei.cc deleted file mode 100644 index 9124ea9..0000000 --- a/na/fs/namei.cc +++ /dev/null @@ -1,250 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * namei.cc — Pathname resolution (path lookup) - * - * Simplified Phase 1: no symlink, no RCU, no mount traversal. - */ - -#include <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 deleted file mode 100644 index f7708f5..0000000 --- a/na/fs/pipe.cc +++ /dev/null @@ -1,278 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pipe.cc — Pipe implementation - * - * Implements unidirectional byte-stream pipes with blocking I/O. - * Each pipe has a 4KB circular buffer. Reads block when empty, - * writes block when full (unless O_NONBLOCK). - * - * When all writers close, read returns 0 (EOF). - * When all readers close, write returns -EPIPE. - */ - -#include <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 deleted file mode 100644 index 6d9a070..0000000 --- a/na/fs/ramfs/file.cc +++ /dev/null @@ -1,216 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/file.cc — RamFS file operations (read/write via in-memory pages) - * - * Phase 1: simple flat buffer per-inode via i_private. - * Will migrate to page_cache integration later. - */ - -#include <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 deleted file mode 100644 index c5e49bf..0000000 --- a/na/fs/ramfs/inode.cc +++ /dev/null @@ -1,164 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/inode.cc — RamFS inode operations - * - * A simple in-memory filesystem (like Linux tmpfs without swap). - * All data lives in memory via the page cache. - */ - -#include <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 deleted file mode 100644 index 6f929f2..0000000 --- a/na/fs/ramfs/super.cc +++ /dev/null @@ -1,154 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * ramfs/super.cc — RamFS superblock: mount, fill_super, and init - */ - -#include <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 deleted file mode 100644 index 231b57c..0000000 --- a/na/fs/super.cc +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * super.cc — Superblock lifecycle + filesystem type registry - */ - -#include <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 deleted file mode 100644 index ec56e88..0000000 --- a/na/fs/vfs_init.cc +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vfs_init.cc — Bootstrap the VFS: mount ramfs root, set global root - * - * Called from kernel_main() after slab_init() and ramfs_init(). - * This is the moral equivalent of Linux's vfs_caches_init() + - * mnt_init() + init_rootfs(). - */ - -#include <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/libc-shim/bits/alltypes.h b/na/include/libc-shim/bits/alltypes.h deleted file mode 100644 index cf21a5e..0000000 --- a/na/include/libc-shim/bits/alltypes.h +++ /dev/null @@ -1,15 +0,0 @@ -/* Shim: provide types musl string functions need */ -#ifndef _BITS_ALLTYPES_H -#define _BITS_ALLTYPES_H - -#ifndef __cplusplus -typedef unsigned long size_t; -#endif - -typedef long ssize_t; -typedef unsigned long uintptr_t; - -/* locale_t stub — unused in kernel, but declared by some headers */ -typedef void *locale_t; - -#endif diff --git a/na/include/libc-shim/endian.h b/na/include/libc-shim/endian.h deleted file mode 100644 index 8346386..0000000 --- a/na/include/libc-shim/endian.h +++ /dev/null @@ -1,9 +0,0 @@ -/* Shim: endianness for musl string functions */ -#ifndef _ENDIAN_H -#define _ENDIAN_H - -#define __LITTLE_ENDIAN 1234 -#define __BIG_ENDIAN 4321 -#define __BYTE_ORDER __LITTLE_ENDIAN /* RISC-V is LE */ - -#endif diff --git a/na/include/libc-shim/features.h b/na/include/libc-shim/features.h deleted file mode 100644 index a3466c8..0000000 --- a/na/include/libc-shim/features.h +++ /dev/null @@ -1,12 +0,0 @@ -/* Shim: musl expects this header */ -#ifndef _FEATURES_H -#define _FEATURES_H - -/* weak_alias: musl uses this for symbol aliasing (e.g. __memrchr -> memrchr) */ -#define weak_alias(old, new) \ - extern __typeof(old) new __attribute__((__weak__, __alias__(#old))) - -/* hidden attribute used by some musl sources */ -#define hidden __attribute__((__visibility__("hidden"))) - -#endif diff --git a/na/include/libc-shim/limits.h b/na/include/libc-shim/limits.h deleted file mode 100644 index a1d546e..0000000 --- a/na/include/libc-shim/limits.h +++ /dev/null @@ -1,17 +0,0 @@ -/* Shim: limits for musl string functions (memmem uses SIZE_MAX) */ -#ifndef _LIMITS_H -#define _LIMITS_H - -#define CHAR_BIT 8 -#define UCHAR_MAX 255 -#define SCHAR_MAX 127 -#define SCHAR_MIN (-128) -#define INT_MAX 2147483647 -#define INT_MIN (-INT_MAX - 1) -#define UINT_MAX 4294967295U -#define LONG_MAX 9223372036854775807L -#define LONG_MIN (-LONG_MAX - 1L) -#define ULONG_MAX 18446744073709551615UL -#define SIZE_MAX ULONG_MAX - -#endif diff --git a/na/include/libc-shim/stdint.h b/na/include/libc-shim/stdint.h deleted file mode 100644 index 650bdcf..0000000 --- a/na/include/libc-shim/stdint.h +++ /dev/null @@ -1,21 +0,0 @@ -/* Shim: stdint types for musl string functions */ -#ifndef _STDINT_H -#define _STDINT_H - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef unsigned long uint64_t; -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef signed long int64_t; -typedef unsigned long uintptr_t; -typedef long intptr_t; - -#define UINT8_MAX 0xFF -#define UINT16_MAX 0xFFFF -#define UINT32_MAX 0xFFFFFFFFU -#define UINT64_MAX 0xFFFFFFFFFFFFFFFFUL - -#endif diff --git a/na/include/libc-shim/string.h b/na/include/libc-shim/string.h deleted file mode 100644 index 8a86304..0000000 --- a/na/include/libc-shim/string.h +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef _STRING_H -#define _STRING_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <features.h> - -#if __cplusplus >= 201103L -#define NULL nullptr -#elif defined(__cplusplus) -#define NULL 0L -#else -#define NULL ((void*)0) -#endif - -#define __NEED_size_t -#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ - || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -#define __NEED_locale_t -#endif - -#include <bits/alltypes.h> - -void *memcpy (void *__restrict, const void *__restrict, size_t); -void *memmove (void *, const void *, size_t); -void *memset (void *, int, size_t); -int memcmp (const void *, const void *, size_t); -void *memchr (const void *, int, size_t); - -char *strcpy (char *__restrict, const char *__restrict); -char *strncpy (char *__restrict, const char *__restrict, size_t); - -char *strcat (char *__restrict, const char *__restrict); -char *strncat (char *__restrict, const char *__restrict, size_t); - -int strcmp (const char *, const char *); -int strncmp (const char *, const char *, size_t); - -int strcoll (const char *, const char *); -size_t strxfrm (char *__restrict, const char *__restrict, size_t); - -char *strchr (const char *, int); -char *strrchr (const char *, int); - -size_t strcspn (const char *, const char *); -size_t strspn (const char *, const char *); -char *strpbrk (const char *, const char *); -char *strstr (const char *, const char *); -char *strtok (char *__restrict, const char *__restrict); - -size_t strlen (const char *); - -char *strerror (int); - -#if defined(_BSD_SOURCE) || defined(_GNU_SOURCE) -#include <strings.h> -#endif - -#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ - || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -char *strtok_r (char *__restrict, const char *__restrict, char **__restrict); -int strerror_r (int, char *, size_t); -char *stpcpy(char *__restrict, const char *__restrict); -char *stpncpy(char *__restrict, const char *__restrict, size_t); -size_t strnlen (const char *, size_t); -char *strdup (const char *); -char *strndup (const char *, size_t); -char *strsignal(int); -char *strerror_l (int, locale_t); -int strcoll_l (const char *, const char *, locale_t); -size_t strxfrm_l (char *__restrict, const char *__restrict, size_t, locale_t); -void *memmem(const void *, size_t, const void *, size_t); -#endif - -#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ - || defined(_BSD_SOURCE) -void *memccpy (void *__restrict, const void *__restrict, int, size_t); -#endif - -#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) -char *strsep(char **, const char *); -size_t strlcat (char *, const char *, size_t); -size_t strlcpy (char *, const char *, size_t); -void explicit_bzero (void *, size_t); -#endif - -#ifdef _GNU_SOURCE -#define strdupa(x) strcpy(alloca(strlen(x)+1),x) -int strverscmp (const char *, const char *); -char *strchrnul(const char *, int); -char *strcasestr(const char *, const char *); -void *memrchr(const void *, int, size_t); -void *mempcpy(void *, const void *, size_t); -#endif - -/* musl internal symbols used across translation units */ -char *__strchrnul(const char *, int); -void *__memrchr(const void *, int, size_t); -char *__stpcpy(char *__restrict, const char *__restrict); -char *__stpncpy(char *__restrict, const char *__restrict, size_t); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/na/include/libc-shim/strings.h b/na/include/libc-shim/strings.h deleted file mode 100644 index b7a5ea0..0000000 --- a/na/include/libc-shim/strings.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _STRINGS_H -#define _STRINGS_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include <features.h> - -#define __NEED_size_t -#define __NEED_locale_t -#include <bits/alltypes.h> - -#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) || defined(_POSIX_SOURCE) \ - || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE+0 < 200809L) \ - || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE+0 < 700) -int bcmp (const void *, const void *, size_t); -void bcopy (const void *, void *, size_t); -void bzero (void *, size_t); -char *index (const char *, int); -char *rindex (const char *, int); -#endif - -#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) || defined(_BSD_SOURCE) -int ffs (int); -int ffsl (long); -int ffsll (long long); -#endif - -int strcasecmp (const char *, const char *); -int strncasecmp (const char *, const char *, size_t); - -int strcasecmp_l (const char *, const char *, locale_t); -int strncasecmp_l (const char *, const char *, size_t, locale_t); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/na/include/lume/addr.h b/na/include/lume/addr.h deleted file mode 100644 index 572b6fe..0000000 --- a/na/include/lume/addr.h +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Machine-Independent Address Conversion Helpers - * - * All PA↔VA conversions in MI code (mm/, kernel/, etc.) must go through - * these functions. The actual offset is provided by <arch/config.h>. - * - * Reference: docs/specs/00_conventions.md §1.1 (MI/MD separation) - */ - -#include <lume/types.h> -#include <lume/config.h> -#include <lume/addr_types.h> -#include <arch/config.h> - -/* Convert physical address to kernel virtual address (direct map). */ -inline VirtAddr pa_to_va(PhysAddr pa) -{ - return VirtAddr{pa.raw + arch::g_direct_map_base}; -} - -/* Convert kernel virtual address to physical address (direct map). */ -inline PhysAddr va_to_pa(VirtAddr va) -{ - return PhysAddr{va.raw - arch::g_direct_map_base}; -} - -/* - * Early-boot conversions pinned to the bootstrap direct-map base. - * - * Use these only before vmm_init() activates the final kernel page table. - * They are required when KASLR has already randomized g_direct_map_base, - * but the CPU is still running on the bootstrap mapping built in entry.S. - */ -inline VirtAddr boot_pa_to_va(PhysAddr pa) -{ - return VirtAddr{pa.raw + arch::kDirectMapBaseDefault}; -} - -inline PhysAddr boot_va_to_pa(VirtAddr va) -{ - return PhysAddr{va.raw - arch::kDirectMapBaseDefault}; -} - -/* Align up to page boundary */ -inline uint64 page_align_up(uint64 addr) -{ - return (addr + kPageSize - 1) & ~(kPageSize - 1); -} - -/* Align down to page boundary */ -inline uint64 page_align_down(uint64 addr) -{ - return addr & ~(kPageSize - 1); -} - -inline PhysAddr page_align_up(PhysAddr addr) -{ - return PhysAddr{page_align_up(addr.raw)}; -} - -inline PhysAddr page_align_down(PhysAddr addr) -{ - return PhysAddr{page_align_down(addr.raw)}; -} - -inline VirtAddr page_align_up(VirtAddr addr) -{ - return VirtAddr{page_align_up(addr.raw)}; -} - -inline VirtAddr page_align_down(VirtAddr addr) -{ - return VirtAddr{page_align_down(addr.raw)}; -} - -/* Convert physical address to page frame number (PFN) */ -inline Pfn pa_to_pfn(PhysAddr pa) -{ - return Pfn{pa.raw / kPageSize}; -} - -/* Convert page frame number (PFN) to physical address */ -inline PhysAddr pfn_to_pa(Pfn pfn) -{ - return PhysAddr{pfn.raw * kPageSize}; -} diff --git a/na/include/lume/addr_types.h b/na/include/lume/addr_types.h deleted file mode 100644 index 91a0779..0000000 --- a/na/include/lume/addr_types.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -struct PhysAddr { - uint64 raw; -}; - -struct VirtAddr { - uint64 raw; -}; - -struct Pfn { - uint64 raw; -}; - -inline constexpr PhysAddr phys_addr(uint64 raw) { return PhysAddr{raw}; } -inline constexpr VirtAddr virt_addr(uint64 raw) { return VirtAddr{raw}; } -inline constexpr Pfn pfn(uint64 raw) { return Pfn{raw}; } - -inline constexpr bool operator==(PhysAddr a, PhysAddr b) { return a.raw == b.raw; } -inline constexpr bool operator!=(PhysAddr a, PhysAddr b) { return a.raw != b.raw; } -inline constexpr bool operator==(VirtAddr a, VirtAddr b) { return a.raw == b.raw; } -inline constexpr bool operator!=(VirtAddr a, VirtAddr b) { return a.raw != b.raw; } -inline constexpr bool operator==(Pfn a, Pfn b) { return a.raw == b.raw; } -inline constexpr bool operator!=(Pfn a, Pfn b) { return a.raw != b.raw; } diff --git a/na/include/lume/args.h b/na/include/lume/args.h deleted file mode 100644 index 0562dc5..0000000 --- a/na/include/lume/args.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _LINUX_ARGS_H -#define _LINUX_ARGS_H - -/* - * How do these macros work? - * - * In __COUNT_ARGS() _0 to _15 are just placeholders from the start - * in order to make sure _n is positioned over the correct number - * from 15 to 0 (depending on X, which is a variadic argument list). - * They serve no purpose other than occupying a position. Since each - * macro parameter must have a distinct identifier, those identifiers - * are as good as any. - * - * In COUNT_ARGS() we use actual integers, so __COUNT_ARGS() returns - * that as _n. - */ - -/* This counts to 15. Any more, it will return 16th argument. */ -#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n -#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) - -/* Concatenate two parameters, but allow them to be expanded beforehand. */ -#define __CONCAT(a, b) a ## b -#define CONCATENATE(a, b) __CONCAT(a, b) - -#endif /* _LINUX_ARGS_H */ diff --git a/na/include/lume/atomic.h b/na/include/lume/atomic.h deleted file mode 100644 index 6e31129..0000000 --- a/na/include/lume/atomic.h +++ /dev/null @@ -1,76 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * lume::atomic<T> — Thin wrapper over __atomic builtins. - * - * All multi-core shared data that does NOT use a spinlock MUST use - * this wrapper instead of raw __atomic_* builtins. - * - * Reference: docs/specs/00_conventions.md §5.3 - */ - -#include <lume/types.h> - -namespace lume { - -template<typename T> -class atomic { -public: - atomic() = default; - explicit atomic(T val) : val_(val) {} - - /* Prevent copies — an atomic value has identity */ - atomic(const atomic&) = delete; - atomic& operator=(const atomic&) = delete; - - /* Load with specified memory ordering */ - T load(int order = __ATOMIC_SEQ_CST) const { - return __atomic_load_n(&val_, order); - } - - /* Store with specified memory ordering */ - void store(T desired, int order = __ATOMIC_SEQ_CST) { - __atomic_store_n(&val_, desired, order); - } - - /* Atomic exchange, returns old value */ - T exchange(T desired, int order = __ATOMIC_SEQ_CST) { - return __atomic_exchange_n(&val_, desired, order); - } - - /* Fetch-and-add, returns old value */ - T fetch_add(T delta, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_add(&val_, delta, order); - } - - /* Fetch-and-subtract, returns old value */ - T fetch_sub(T delta, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_sub(&val_, delta, order); - } - - /* Fetch-and-or, returns old value */ - T fetch_or(T mask, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_or(&val_, mask, order); - } - - /* Fetch-and-and, returns old value */ - T fetch_and(T mask, int order = __ATOMIC_ACQ_REL) { - return __atomic_fetch_and(&val_, mask, order); - } - - /* Compare-and-swap. Returns true on success. */ - bool compare_exchange(T& expected, T desired, - int success_order = __ATOMIC_ACQ_REL, - int failure_order = __ATOMIC_ACQUIRE) { - return __atomic_compare_exchange_n(&val_, &expected, desired, - false, success_order, failure_order); - } - -private: - T val_ = 0; -}; - -} // namespace lume diff --git a/na/include/lume/bitops.h b/na/include/lume/bitops.h deleted file mode 100644 index 4227ca3..0000000 --- a/na/include/lume/bitops.h +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Bit Operations — portable kernel bit manipulation utilities - * - * Provides ctz, clz, ffs, fls and related operations. - * All implementations are manual (no __builtin_ctz) to avoid - * generating libgcc __ctzdi2/__clzdi2 calls in freestanding. - */ - -#include <lume/types.h> - -namespace lume { - -/* ------------------------------------------------------------------ */ -/* Count Trailing Zeros (ctz) */ -/* Returns the number of trailing 0-bits in val. */ -/* Precondition: val != 0 (undefined if val == 0). */ -/* ------------------------------------------------------------------ */ - -static inline int ctz32(uint32 val) -{ - int n = 0; - if (!(val & 0x0000FFFFU)) { n += 16; val >>= 16; } - if (!(val & 0x000000FFU)) { n += 8; val >>= 8; } - if (!(val & 0x0000000FU)) { n += 4; val >>= 4; } - if (!(val & 0x00000003U)) { n += 2; val >>= 2; } - if (!(val & 0x00000001U)) { n += 1; } - return n; -} - -static inline int ctz64(uint64 val) -{ - if (static_cast<uint32>(val) != 0) { - return ctz32(static_cast<uint32>(val)); - } - return 32 + ctz32(static_cast<uint32>(val >> 32)); -} - -/* ------------------------------------------------------------------ */ -/* Count Leading Zeros (clz) */ -/* Returns the number of leading 0-bits in val. */ -/* Precondition: val != 0 (undefined if val == 0). */ -/* ------------------------------------------------------------------ */ - -static inline int clz32(uint32 val) -{ - int n = 0; - if (!(val & 0xFFFF0000U)) { n += 16; val <<= 16; } - if (!(val & 0xFF000000U)) { n += 8; val <<= 8; } - if (!(val & 0xF0000000U)) { n += 4; val <<= 4; } - if (!(val & 0xC0000000U)) { n += 2; val <<= 2; } - if (!(val & 0x80000000U)) { n += 1; } - return n; -} - -static inline int clz64(uint64 val) -{ - if (static_cast<uint32>(val >> 32) != 0) { - return clz32(static_cast<uint32>(val >> 32)); - } - return 32 + clz32(static_cast<uint32>(val)); -} - -/* ------------------------------------------------------------------ */ -/* Find First Set (ffs) — 1-indexed, returns 0 if val == 0 */ -/* ------------------------------------------------------------------ */ - -static inline int ffs32(uint32 val) -{ - return val ? ctz32(val) + 1 : 0; -} - -static inline int ffs64(uint64 val) -{ - return val ? ctz64(val) + 1 : 0; -} - -/* ------------------------------------------------------------------ */ -/* Find Last Set (fls) — 1-indexed, returns 0 if val == 0 */ -/* Returns the position of the highest set bit. */ -/* ------------------------------------------------------------------ */ - -static inline int fls32(uint32 val) -{ - return val ? 32 - clz32(val) : 0; -} - -static inline int fls64(uint64 val) -{ - return val ? 64 - clz64(val) : 0; -} - -/* ------------------------------------------------------------------ */ -/* Order (log2) — returns floor(log2(val)), val must be > 0 */ -/* ------------------------------------------------------------------ */ - -static inline int ilog2_32(uint32 val) -{ - return fls32(val) - 1; -} - -static inline int ilog2_64(uint64 val) -{ - return fls64(val) - 1; -} - -/* ------------------------------------------------------------------ */ -/* Power-of-two test */ -/* ------------------------------------------------------------------ */ - -static inline bool is_power_of_2(uint64 val) -{ - return val != 0 && (val & (val - 1)) == 0; -} - -} /* namespace lume */ diff --git a/na/include/lume/chardev.h b/na/include/lume/chardev.h deleted file mode 100644 index be81ab9..0000000 --- a/na/include/lume/chardev.h +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * chardev.h — Character device abstraction - * - * Minimal Linux-style cdev registry: each character device has a - * major/minor number and a file_operations table. - */ - -#include <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 deleted file mode 100644 index 156bae4..0000000 --- a/na/include/lume/clone.h +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Linux-compatible clone(2) flags. - * - * Only the flags relevant to current and near-future use are defined. - * Reference: include/uapi/linux/sched.h - */ - -#include <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 deleted file mode 100644 index 243f0de..0000000 --- a/na/include/lume/compiler.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_COMPILER_H -#define _LUME_COMPILER_H - -/* - * Minimal compiler primitives used across LumeOS. - * - * Provides only what current code actually references: - * - READ_ONCE / WRITE_ONCE memory access opacity for concurrent code - * (rbtree.h, rbtree_augmented.h) - * - likely / unlikely branch hints for hot/cold paths - * - * Implementation mirrors Linux's semantics — same surface, same effect — - * without pulling in Linux's compiler_types.h cascade (KASAN/KCSAN/KMSAN - * sanitizer attribute machinery, BTF type tags, hardened-usercopy hooks). - * LumeOS doesn't build with those sanitizers, so the simpler form is - * sufficient and keeps the dependency graph flat. - */ - -/* Branch prediction hints */ -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) - -/* Compile-time barrier — prevent the compiler from reordering memory - * accesses across this point. Not a CPU fence; for CPU ordering use - * the architecture-specific fence intrinsics. */ -#define barrier() __asm__ __volatile__("" ::: "memory") - -/* - * __always_inline — force the compiler to actually inline a function, - * overriding -fno-inline / -Os / size heuristics. Used by rbtree helper - * templates whose performance depends on the call being inlined into - * the iterating loop. - */ -#define __always_inline inline __attribute__((__always_inline__)) - -/* - * READ_ONCE / WRITE_ONCE — opaque single-access memory operations. - * - * The volatile cast prevents the compiler from: - * - merging adjacent reads/writes to the same location, - * - splitting a load into multiple narrower loads, - * - hoisting reads out of a loop (or sinking writes past one). - * - * Used in lock-free / cross-CPU shared data paths (rbtree color writes, - * future RCU pointers, atomic flag polls). Does NOT imply any CPU-side - * memory ordering — pair with smp_mb()/acquire/release for that. - */ -#define READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x)) -#define WRITE_ONCE(x, val) (*(volatile __typeof__(x) *)&(x) = (val)) - -#endif /* _LUME_COMPILER_H */ diff --git a/na/include/lume/config.h b/na/include/lume/config.h deleted file mode 100644 index bde717a..0000000 --- a/na/include/lume/config.h +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * LumeOS Global Compile-Time Configuration - * - * All kernel-wide tuning constants live here. - * Architecture-specific constants live in <arch/config.h>. - * - * This header is safe to include from both C++ and assembly files. - * Assembly-visible constants use #define; C++ code gets typed constexpr. - * - * Values are injected by the build system via -DCONFIG_xxx flags - * (see scripts/Kconfig.mk). The #ifndef guards below provide - * safe defaults so the header is self-contained for IDE analysis. - * - * Reference: docs/specs/00_conventions.md §1.3 - */ - -/* ================================================================ - * Defaults — only used when CONFIG_xxx is NOT supplied by -D flag. - * In a normal build, scripts/Kconfig.mk always provides them. - * ================================================================ */ - -#ifndef CONFIG_NR_CPUS -#define CONFIG_NR_CPUS 8 -#endif - -#ifndef CONFIG_PAGE_SIZE -#define CONFIG_PAGE_SIZE 4096 -#endif - -#ifndef CONFIG_MAX_ORDER -#define CONFIG_MAX_ORDER 11 -#endif - -#ifndef CONFIG_KERNEL_STACK_SIZE -#define CONFIG_KERNEL_STACK_SIZE 16384 -#endif - -#ifndef CONFIG_HZ -#define CONFIG_HZ 100 -#endif - -#ifndef CONFIG_NR_PRIORITIES -#define CONFIG_NR_PRIORITIES 32 -#endif - -#ifndef CONFIG_DEFAULT_PRIORITY -#define CONFIG_DEFAULT_PRIORITY (CONFIG_NR_PRIORITIES / 2) -#endif - -#ifndef CONFIG_IDLE_PRIORITY -#define CONFIG_IDLE_PRIORITY (CONFIG_NR_PRIORITIES - 1) -#endif - -#ifndef CONFIG_DEFAULT_TIMESLICE -#define CONFIG_DEFAULT_TIMESLICE (CONFIG_HZ / 10) -#endif - -#ifndef CONFIG_KASLR_POLICY_ID -#define CONFIG_KASLR_POLICY_ID 0 /* Default: basic KASLR policy */ -#endif - -#ifndef CONFIG_KASLR_LOG_LEVEL -#define CONFIG_KASLR_LOG_LEVEL 1 /* 0=off, 1=minimal, 2=debug */ -#endif - -/* ================================================================ - * Legacy compatibility aliases (shared with assembly) - * ================================================================ */ -#define LUME_MAX_CPUS CONFIG_NR_CPUS -#define LUME_PAGE_SIZE CONFIG_PAGE_SIZE - -#ifndef __ASSEMBLER__ - -#include <lume/types.h> - -/* Maximum number of CPUs supported by the kernel. - * Compile-time limit for static arrays (boot stacks, per-cpu data). - * Actual CPU count is detected at runtime via FDT. */ -inline constexpr int kMaxCpus = CONFIG_NR_CPUS; - -/* Virtual memory page size (4KB, matching SV39 base page). */ -inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE; - -/* Page shift: log2(kPageSize). Used for PFN ↔ PA conversions. */ -inline constexpr int kPageShift = 12; // 4096 = 1 << 12 - -/* KASLR policy ID and logging level (build-time configurable) */ -inline constexpr uint32 kKaslrPolicyId = CONFIG_KASLR_POLICY_ID; -inline constexpr int kKaslrLogLevel = CONFIG_KASLR_LOG_LEVEL; - -#include <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 deleted file mode 100644 index af187a6..0000000 --- a/na/include/lume/console.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * console.h — Kernel console abstraction - * - * Two-phase design following Linux: - * - * Phase 1 (early boot): - * early_putc() → direct UART MMIO write. Used before MMU and - * before the console registry is ready. kprintf() and panic - * use this path. - * - * Phase 2 (runtime): - * struct console registered via register_console(). printk/klog - * emit through the console driver chain. Multiple consoles can - * be registered (e.g., UART + framebuffer). - * - * Reference: Linux include/linux/console.h - */ - -#include <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); -extern "C" void console_set_mmu_enabled(); - -/* Early UART discovery via raw FDT parsing. */ -void console_early_init(uint64 fdt_paddr); - -/* Full console initialization. Maps MMIO. */ -void console_init(); - -/* - * Notify console that the runtime kernel page table is active. - * After this point early_putc() must use runtime direct-map base - * instead of the bootstrap fixed base. - */ -void console_use_runtime_mapping(); - -/* ================================================================== */ -/* struct console (Phase 2 — registered console drivers) */ -/* ================================================================== */ - -inline constexpr int CON_PRINTBUFFER = (1 << 0); /* Replay klog on register */ -inline constexpr int CON_ENABLED = (1 << 1); -inline constexpr int CON_BOOT = (1 << 2); /* Early boot console */ - -struct console { - const char* name; - void (*write)(console* con, const char* s, unsigned int count); - int (*setup)(console* con, char* options); - int flags; - int index; - void* data; /* driver private data */ - list_node list; -}; - -/* Register / unregister a console driver */ -void register_console(console* con); -void unregister_console(console* con); - -/* - * Write to all registered consoles. - * Falls back to early_putc() if no console is registered. - */ -void console_write_all(const char* s, int count); - -/* Check if any console is registered */ -bool console_drivers_registered(); diff --git a/na/include/lume/container_of.h b/na/include/lume/container_of.h deleted file mode 100644 index a6889eb..0000000 --- a/na/include/lume/container_of.h +++ /dev/null @@ -1,17 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * container_of.h — Recover containing struct from member pointer - * - * Equivalent to Linux include/linux/container_of.h. - */ - -#include <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 deleted file mode 100644 index a31402e..0000000 --- a/na/include/lume/dcache.h +++ /dev/null @@ -1,143 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * dcache.h — Directory entry cache - * - * Modeled after Linux include/linux/dcache.h. - * Reference: docs/vfs/03_dentry.md - */ - -#include <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/device.h b/na/include/lume/device.h deleted file mode 100644 index 46e22e4..0000000 --- a/na/include/lume/device.h +++ /dev/null @@ -1,75 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Driver model: device, device_driver, bus_type. - * - * Mirrors Linux include/linux/device.h. Every hardware unit embeds a - * `device` and binds to a `device_driver` via a `bus_type` that knows - * how to match the two. Buses we will have: - * - platform: FDT-described memory-mapped devices (this commit) - * - virtio: virtio-mmio devices (P5 of storage_stack.md) - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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/device_node.h b/na/include/lume/device_node.h deleted file mode 100644 index e35f22d..0000000 --- a/na/include/lume/device_node.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * DeviceNode — FDT object tree node - * - * Each DeviceNode represents a single node in the unflattened device tree. - * Nodes form a parent-children hierarchy via intrusive linked lists. - * After unflattening, the tree is globally read-only and requires no locks. - * - * Created when Slab is available. - * - * Reference: docs/specs/fdt.md §3.2 - */ - -#include <lume/types.h> -#include <lume/list.h> -#include <lume/fdt_prop.h> - -class DeviceNode { -public: - // Construction / destruction (used by FdtManager::unflatten) - DeviceNode(DeviceNode *parent, const char *name); - ~DeviceNode(); - - // --- Property access --- - - // Look up a property by name. Returns nullptr if not found. - const FdtProperty *get_prop(const char *name) const; - - // Convenience helpers that decode the property value. - // Return default_val when the property does not exist. - uint32 get_prop_u32(const char *name, uint32 default_val = 0) const; - uint64 get_prop_u64(const char *name, uint64 default_val = 0) const; - const char *get_prop_string(const char *name) const; - - // --- Child traversal --- - - // Find a direct child by name. Returns nullptr if not found. - DeviceNode *find_child(const char *name); - - // Invoke callback for every direct child. - void for_each_child(void (*callback)(DeviceNode *)); - - // --- Internal (used by FdtManager to build the tree) --- - void add_child(DeviceNode *child); - void add_prop(FdtProperty *prop); - - // --- Accessors for tree traversal --- - const char *get_name() const { return name_; } - list_node *children_list_head() { return children_list_.next; } - list_node *children_list_sentinel() { return &children_list_; } - - // sibling_link_ offset needed by container_of in fdt.cc - list_node sibling_link_; // Link in parent's children_list_ - -private: - const char *name_; // Node name (e.g. "memory@80000000") - DeviceNode *parent_; // Parent node (nullptr for root) - list_node children_list_; // Head of the children linked list - list_node props_list_; // Head of the property linked list -}; diff --git a/na/include/lume/driver.h b/na/include/lume/driver.h deleted file mode 100644 index a8bd787..0000000 --- a/na/include/lume/driver.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * driver.h — boot-time driver probing entry point. - * - * The actual driver-model primitives (kobject, device, bus_type, - * platform_device, platform_driver) live in their own headers: - * <lume/kobject.h>, <lume/device.h>, <lume/platform_device.h>. - * - * This header just exposes the single boot entry point that - * kernel_main() calls after the slab allocator + FDT unflatten are - * ready. The implementation lives in drivers/base/platform.cc. - * - * Reference: docs/specs/storage_stack.md §4 - */ - -/* Register the platform bus, register every linker-set platform_driver, - * walk the FDT, and instantiate a platform_device for each compatible - * match. Each device is bound to its driver via the platform bus - * match/probe path. Returns after all probes have run. */ -void driver_probe_all(); diff --git a/na/include/lume/elf.h b/na/include/lume/elf.h deleted file mode 100644 index 772ad97..0000000 --- a/na/include/lume/elf.h +++ /dev/null @@ -1,150 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * ELF64 Type Definitions — Minimal set for loading executables - * AND for the kernel's own .rela.dyn relocation walk at boot. - * - * Follows the ELF specification (Tool Interface Standard, TIS). - * Only includes what's needed for static ELF64 executable loading. - * - * Reference: https://refspecs.linuxfoundation.org/elf/elf.pdf - */ - -#include <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 deleted file mode 100644 index f3bf09f..0000000 --- a/na/include/lume/errno.h +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_ERRNO_H -#define _LUME_ERRNO_H - -/* - * Error codes — numeric values aligned with Linux's asm-generic/errno-base.h, - * asm-generic/errno.h, and linux/errno.h (kernel-internal codes 512+). - * - * Userspace-visible codes (1..133) match POSIX / Linux exactly so a LumeOS - * binary returning -EINVAL to a user program receives the same number Linux - * would. Kernel-internal codes (>= 512) follow Linux's convention of being - * out of POSIX range; they are translated to a user-visible code (typically - * EINTR) before crossing into user mode. - * - * Defined as plain #define so this header is usable from C, C++, and - * assembly (the macros expand to integer literals, no types). - */ - -/* ===== Base errors (asm-generic/errno-base.h equivalent) ===== */ -#define EPERM 1 /* Operation not permitted */ -#define ENOENT 2 /* No such file or directory */ -#define ESRCH 3 /* No such process */ -#define EINTR 4 /* Interrupted system call */ -#define EIO 5 /* I/O error */ -#define ENXIO 6 /* No such device or address */ -#define E2BIG 7 /* Argument list too long */ -#define ENOEXEC 8 /* Exec format error */ -#define EBADF 9 /* Bad file number */ -#define ECHILD 10 /* No child processes */ -#define EAGAIN 11 /* Try again */ -#define ENOMEM 12 /* Out of memory */ -#define EACCES 13 /* Permission denied */ -#define EFAULT 14 /* Bad address */ -#define ENOTBLK 15 /* Block device required */ -#define EBUSY 16 /* Device or resource busy */ -#define EEXIST 17 /* File exists */ -#define EXDEV 18 /* Cross-device link */ -#define ENODEV 19 /* No such device */ -#define ENOTDIR 20 /* Not a directory */ -#define EISDIR 21 /* Is a directory */ -#define EINVAL 22 /* Invalid argument */ -#define ENFILE 23 /* File table overflow */ -#define EMFILE 24 /* Too many open files */ -#define ENOTTY 25 /* Not a typewriter */ -#define ETXTBSY 26 /* Text file busy */ -#define EFBIG 27 /* File too large */ -#define ENOSPC 28 /* No space left on device */ -#define ESPIPE 29 /* Illegal seek */ -#define EROFS 30 /* Read-only file system */ -#define EMLINK 31 /* Too many links */ -#define EPIPE 32 /* Broken pipe */ -#define EDOM 33 /* Math argument out of domain */ -#define ERANGE 34 /* Math result not representable */ - -/* ===== Extended errors (asm-generic/errno.h equivalent) ===== */ -#define EDEADLK 35 /* Resource deadlock would occur */ -#define ENAMETOOLONG 36 /* File name too long */ -#define ENOLCK 37 /* No record locks available */ -#define ENOSYS 38 /* Invalid system call number */ -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ -#define EDEADLOCK EDEADLK -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Cannot access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Linking in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define ENOTSUP EOPNOTSUPP -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ -#define EOWNERDEAD 130 /* Owner died (for robust mutexes) */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ -#define ERFKILL 132 /* Operation not possible due to RF-kill */ -#define EHWPOISON 133 /* Memory page has hardware error */ - -/* ===== Kernel-internal errors (linux/errno.h equivalent, >= 512) ===== - * These should never be seen by user programs. signal/syscall layer - * translates them to a user-visible code (typically EINTR) before - * returning to userspace. - */ -#define ERESTARTSYS 512 -#define ERESTARTNOINTR 513 -#define ERESTARTNOHAND 514 /* restart if no signal handler */ -#define ENOIOCTLCMD 515 /* No ioctl command */ -#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */ -#define EPROBE_DEFER 517 /* Driver requests probe retry */ -#define EOPENSTALE 518 /* open() found a stale dentry */ -#define ENOPARAM 519 /* Parameter not supported */ - -/* NFSv3 reserved range (521..531) */ -#define EBADHANDLE 521 /* Illegal NFS file handle */ -#define ENOTSYNC 522 /* Update synchronization mismatch */ -#define EBADCOOKIE 523 /* Cookie is stale */ -#define ENOTSUPP 524 /* Operation is not supported */ -#define ETOOSMALL 525 /* Buffer or request is too small */ -#define ESERVERFAULT 526 /* An untranslatable error occurred */ -#define EBADTYPE 527 /* Type not supported by server */ -#define EJUKEBOX 528 /* Request initiated, will not complete before timeout */ -#define EIOCBQUEUED 529 /* iocb queued, will get completion event */ -#define ERECALLCONFLICT 530 /* conflict with recalled state */ -#define ENOGRACE 531 /* NFS file lock reclaim refused */ - -#endif /* _LUME_ERRNO_H */ diff --git a/na/include/lume/fdt.h b/na/include/lume/fdt.h deleted file mode 100644 index 626cc48..0000000 --- a/na/include/lume/fdt.h +++ /dev/null @@ -1,85 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FdtManager — Flattened Device Tree parser - * - * Two-phase design: - * Phase 1 (Early Scan): Static methods that walk the raw FDT blob - * without any memory allocation. Used before PMM is available. - * Phase 2 (Unflatten): Builds a persistent C++ DeviceNode object - * tree once Slab is ready. The tree is then globally read-only. - * - * Concurrency: all init is done by BSP in the single-core window. - * After unflattening the tree is immutable — no locks required. - * - * Reference: docs/specs/fdt.md - */ - -#include <lume/device_node.h> -#include <lume/types.h> - -class FdtManager { -public: - // Construct with the physical address of the FDT blob. - // Called via Placement New in fdt_init(). - FdtManager(uint64 fdt_paddr); - - // --- Phase 1: Early scan (static, allocation-free) --- - - // Extract physical memory base and size from the /memory node. - // Panics if the FDT is invalid or /memory is missing. - static void early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size); - - // Extract the UART base address and size by matching compatible = "ns16550a". - // Falls back to platform default on failure. - static void early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, - uint64 *uart_size); - - static void early_scan_kaslr(uint64 fdt_paddr, uint64 *seed); - - // --- Phase 2: Object tree construction (requires Slab) --- - - // Walk the FDT blob and build the DeviceNode tree. - void unflatten(); - - // --- Query interface (read-only after unflatten) --- - - // Find the first node whose "compatible" property contains the string. - // Returns nullptr if no match. - DeviceNode *find_compatible(const char *compat_string); - - // Look up a node by its full path (e.g. "/soc/uart@10000000"). - // Returns nullptr if not found. - DeviceNode *get_node_by_path(const char *path); - - // Return the stored FDT physical address. - uint64 get_paddr() const { return fdt_paddr_; } - -private: - uint64 fdt_paddr_; // Physical address of the FDT blob - bool is_unflattened_ = false; // True after unflatten() completes - DeviceNode *root_ = nullptr; // Root of the unflattened tree -}; - -/* Global FDT manager singleton (Placement New, see global_init.md §3.1) */ -extern FdtManager *g_fdt; - -// --- Public free-function API --- - -// Save FDT address and validate magic. Called early by BSP. -void fdt_init(uint64 fdt_paddr); - -// Build the DeviceNode object tree. Requires Slab. -void fdt_unflatten(); - -// Driver probing helpers (delegate to g_fdt). -DeviceNode *fdt_find_compatible(const char *compat_string); -DeviceNode *fdt_get_node_by_path(const char *path); - -// Early scan wrappers (delegate to FdtManager static methods). -void fdt_early_get_mem_info(uint64 *base, uint64 *size); -void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size); -void fdt_early_get_kaslr_seed(uint64 *seed); diff --git a/na/include/lume/fdt_prop.h b/na/include/lume/fdt_prop.h deleted file mode 100644 index f8a263c..0000000 --- a/na/include/lume/fdt_prop.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * FDT Property - * - * Represents a single property within a DeviceNode (e.g. "reg", - * "compatible"). The value pointer points directly into the - * original FDT blob — no copies are made. - * - * Reference: docs/specs/fdt.md §3.1 - */ - -#include <lume/types.h> -#include <lume/list.h> - -struct FdtProperty { - const char *name; // Property name (points into FDT strings block) - const void *value; // Raw property data (points into FDT struct block) - int len; // Length of value in bytes - list_node link; // Intrusive link into DeviceNode's property list -}; diff --git a/na/include/lume/fdtable.h b/na/include/lume/fdtable.h deleted file mode 100644 index 6159519..0000000 --- a/na/include/lume/fdtable.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fdtable.h — Per-task file descriptor table - * - * Modeled after Linux include/linux/fdtable.h. - * Reference: docs/vfs/05_fdtable.md - */ - -#include <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/features.h b/na/include/lume/features.h deleted file mode 100644 index 85cfb72..0000000 --- a/na/include/lume/features.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _FEATURES_H -#define _FEATURES_H - -#if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE) -#define _GNU_SOURCE 1 -#endif - -#if defined(_DEFAULT_SOURCE) && !defined(_BSD_SOURCE) -#define _BSD_SOURCE 1 -#endif - -#if !defined(_POSIX_SOURCE) && !defined(_POSIX_C_SOURCE) \ - && !defined(_XOPEN_SOURCE) && !defined(_GNU_SOURCE) \ - && !defined(_BSD_SOURCE) && !defined(__STRICT_ANSI__) -#define _BSD_SOURCE 1 -#define _XOPEN_SOURCE 700 -#endif - -#if __STDC_VERSION__ >= 199901L -#define __restrict restrict -#elif !defined(__GNUC__) -#define __restrict -#endif - -#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) -#define __inline inline -#elif !defined(__GNUC__) -#define __inline -#endif - -#if __STDC_VERSION__ >= 201112L -#elif defined(__GNUC__) -#define _Noreturn __attribute__((__noreturn__)) -#else -#define _Noreturn -#endif - -#define __REDIR(x,y) __typeof__(x) x __asm__(#y) - -#endif diff --git a/na/include/lume/file.h b/na/include/lume/file.h deleted file mode 100644 index 349059b..0000000 --- a/na/include/lume/file.h +++ /dev/null @@ -1,132 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * file.h — Open file description + file_operations + path + vfsmount - * - * Modeled after Linux include/linux/fs.h struct file / file_operations. - * Reference: docs/vfs/04_file.md - */ - -#include <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 deleted file mode 100644 index 8dac552..0000000 --- a/na/include/lume/fs.h +++ /dev/null @@ -1,310 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs.h — Core VFS data structures - * - * Defines super_block, inode, address_space, and their operations. - * Modeled after Linux include/linux/fs.h + include/linux/fs/super_types.h. - * - * Reference: docs/vfs/01_super_block.md, docs/vfs/02_inode.md - */ - -#include <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 deleted file mode 100644 index ba643b5..0000000 --- a/na/include/lume/fs_struct.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs_struct.h — Per-task filesystem context (cwd, root) - * - * Modeled after Linux include/linux/fs_struct.h. - * Reference: docs/vfs/05_fdtable.md - */ - -#include <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 deleted file mode 100644 index 767f37c..0000000 --- a/na/include/lume/fs_types.h +++ /dev/null @@ -1,197 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * fs_types.h — File mode constants, open flags, seek constants - * - * All values match Linux asm-generic so musl/busybox binaries work. - * Reference: docs/vfs/02_inode.md, docs/vfs/04_file.md - */ - -#include <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 deleted file mode 100644 index 541ab96..0000000 --- a/na/include/lume/init.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_INIT_H -#define _LUME_INIT_H - -/* - * Boot-time section markers. - * - * Mirrors the subset of Linux include/linux/init.h that LumeOS uses: - * - * __init mark a function as boot-only — emitted into .init.text, - * which the kernel can later free once setup_arch / - * start_kernel have finished (LumeOS doesn't yet reclaim - * this memory, but the segment lives so future versions - * can without source changes). - * __initdata same for data, emitted into .init.data. - * asmlinkage ABI marker for C functions called from assembly. On - * RISC-V the default C ABI matches the assembly calling - * convention, so this is empty (matches Linux RISC-V). - * - * No attempt is made to mirror Linux's COMMAND_LINE_SIZE / saved_command_line - * machinery — that belongs in a future <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/irq.h b/na/include/lume/irq.h deleted file mode 100644 index f5f34e4..0000000 --- a/na/include/lume/irq.h +++ /dev/null @@ -1,84 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * IRQ Manager — Dynamic Interrupt Dispatch - * - * Drivers register ISR callbacks at init time via register_handler(). - * The MI trap layer calls dispatch() when an external interrupt arrives. - * dispatch() runs in interrupt context — handlers must be short and - * non-blocking. - * - * Lock contract: - * - lock_ protects register/unregister (cold path). - * - dispatch() is lock-free: handler pointers are published with RELEASE - * semantics and read with ACQUIRE in dispatch(). - * - Handlers must not acquire VmSpace::lock or sleep. - * - * Reference: docs/specs/trap.md §3.3 - */ - -#include <lume/atomic.h> -#include <lume/types.h> - -/* IRQ handler callback signature */ -using IrqHandler = void (*)(uint32 irq, void *arg); - -/* Abstract Interrupt Controller Interface */ -struct IrqChip { - const char* name; - uint32 (*claim)(); - void (*complete)(uint32 irq); - void (*init_ap)(); - void (*enable)(uint32 irq); - void (*disable)(uint32 irq); - void (*set_priority)(uint32 irq, uint32 priority); -}; - -class IrqManager { -public: - void init(); - - /* Register an ISR for the given IRQ number. - * Returns 0 on success, -EINVAL if irq out of range, -EBUSY if already - * registered. */ - int register_handler(uint32 irq, IrqHandler handler, void *arg); - - /* Unregister the ISR for the given IRQ number. */ - void unregister_handler(uint32 irq); - - /* Dispatch an interrupt to the registered handler. - * Called from MI trap layer in interrupt context. Lock-free. */ - void dispatch(uint32 irq); - - /* Set the active interrupt controller chip */ - void set_chip(const IrqChip* chip); - const IrqChip* get_chip() const { return chip_; } - - /* Architecture-independent claim and complete for the active IRQ chip */ - uint32 claim() const; - void complete(uint32 irq) const; - void init_ap() const; - void enable(uint32 irq) const; - void disable(uint32 irq) const; - void set_priority(uint32 irq, uint32 priority) const; - - /* Check if a handler is registered for the given IRQ. */ - bool has_handler(uint32 irq) const; - - static constexpr uint32 kMaxIrqs = 64; - -private: - struct IrqEntry { - lume::atomic<uint64> handler_ptr; // Stored as uint64 for atomic access - void *arg = nullptr; - }; - - const IrqChip* chip_ = nullptr; - IrqEntry entries_[kMaxIrqs]; -}; - -/* Global IRQ manager singleton */ -extern IrqManager g_irq_manager; diff --git a/na/include/lume/kbuild.h b/na/include/lume/kbuild.h deleted file mode 100644 index 4c3e12a..0000000 --- a/na/include/lume/kbuild.h +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_KBUILD_H -#define _LUME_KBUILD_H - -/* - * Helpers for arch/<arch>/kernel/asm-offsets.c — 1:1 with Linux - * include/linux/kbuild.h. - * - * Trick: each DEFINE/OFFSET emits an inline-asm ".ascii" directive - * containing a literal "->" prefix. When the file is compiled with - * - * $(CC) -S asm-offsets.c -o asm-offsets.s - * - * the resulting assembly contains lines like - * - * ->KERNEL_MAP_VIRT_ADDR $0 offsetof(struct kernel_mapping, virt_addr) - * - * scripts/extract-asm-offsets.sed then rewrites those into proper - * `#define KERNEL_MAP_VIRT_ADDR 0` lines for inclusion from head.S. - * - * The benefit over hand-written #defines is that struct layout changes - * are caught at build time: rearrange a field and the asm offset - * changes automatically. - */ - -#define DEFINE(sym, val) \ - asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val)) - -#define BLANK() \ - asm volatile("\n.ascii \"->\"" : : ) - -#define OFFSET(sym, str, mem) \ - DEFINE(sym, offsetof(struct str, mem)) - -#define COMMENT(x) \ - asm volatile("\n.ascii \"->#" x "\"") - -#endif /* _LUME_KBUILD_H */ diff --git a/na/include/lume/kernel_map.h b/na/include/lume/kernel_map.h deleted file mode 100644 index 7eafe88..0000000 --- a/na/include/lume/kernel_map.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_KERNEL_MAP_H -#define _LUME_KERNEL_MAP_H - -/* - * struct kernel_mapping — where the kernel image lives in physical and - * virtual memory. Field names and order match Linux's - * arch/riscv/include/asm/page.h struct kernel_mapping so that: - * - * 1. arch/riscv/kernel/head.S can do `la a1, kernel_map` then - * `REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)` and read the virtual - * address. The KERNEL_MAP_* asm constants come from - * <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 deleted file mode 100644 index 69ab269..0000000 --- a/na/include/lume/klog.h +++ /dev/null @@ -1,200 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Kernel Logging Subsystem - * - * All kernel output goes through klog(), which formats messages into - * a fixed-size ring buffer and optionally emits them to the console. - * - * Architecture: - * vsnprintf_() ← pure formatting (mpaland/printf) - * ↓ - * KlogBuffer (ring) ← all messages stored here - * ↓ - * early_putc() (sync) ← current: synchronous UART output - * klogd thread (async) ← future: background flush thread - * /proc/kmsg ← future: userspace dmesg interface - * - * Reference: docs/specs/klog.md - */ - -#include <lume/types.h> - -/* ======================================================================== - * Log Levels (matches Linux kern_levels.h ordering) - * ======================================================================== */ - -enum KlogLevel : uint8 { - KLOG_EMERG = 0, /* System is unusable */ - KLOG_ALERT = 1, /* Action must be taken immediately */ - KLOG_CRIT = 2, /* Critical conditions */ - KLOG_ERR = 3, /* Error conditions */ - KLOG_WARN = 4, /* Warning conditions */ - KLOG_NOTICE = 5, /* Normal but significant condition */ - KLOG_INFO = 6, /* Informational */ - KLOG_DEBUG = 7, /* Debug-level messages */ -}; - -/* Log subsystems (bitmask-friendly) */ -enum KlogSubsys : uint8 { - KLOG_SUBSYS_GENERIC = 0, - KLOG_SUBSYS_SCHED = 1, - KLOG_SUBSYS_TRAP = 2, - KLOG_SUBSYS_IRQ = 3, - KLOG_SUBSYS_TIMER = 4, - KLOG_SUBSYS_VMM = 5, - KLOG_SUBSYS_PMM = 6, - KLOG_SUBSYS_SLAB = 7, - KLOG_SUBSYS_FS = 8, - KLOG_SUBSYS_DRIVER = 9, - KLOG_SUBSYS_TEST = 10, - KLOG_SUBSYS_SYSCALL = 11, /* per-syscall entry trace */ - KLOG_SUBSYS_EXEC = 12, /* ELF load / exec internals */ - KLOG_SUBSYS_SIGNAL = 13, /* signal delivery trace */ - KLOG_SUBSYS_TTY = 14, /* tty / job-control trace */ - KLOG_SUBSYS_MAX = 15, -}; - -/* Default: show everything up to INFO. DEBUG is compile-gated. */ -#ifndef KLOG_DEFAULT_LEVEL -#define KLOG_DEFAULT_LEVEL KLOG_INFO -#endif - -/* Ring buffer size: 2^KLOG_BUF_SHIFT bytes (default 64KB) */ -#ifndef KLOG_BUF_SHIFT -#define KLOG_BUF_SHIFT 16 -#endif - -#define KLOG_BUF_SIZE (1U << KLOG_BUF_SHIFT) - -/* Maximum length of a single log message (stack-allocated) */ -#define KLOG_LINE_MAX 256 - -struct KlogStats { - uint32 head; - uint32 tail; - uint32 console_tail; - uint32 dropped; - uint32 seq; -}; - -struct KlogRateLimit { - uint64 last_ts; - uint32 interval; - uint32 burst; - uint32 printed; - uint32 suppressed; -}; - -#ifdef __cplusplus -extern "C" { -#endif - -/* ======================================================================== - * Initialization - * ======================================================================== */ - -/* Initialize the ring buffer and console backend. Call once from BSP. */ -void klog_init(void); - -/* ======================================================================== - * Core API - * ======================================================================== */ - -/* Write a leveled log message. Formats into ring buffer, then - * synchronously flushes to console if level <= current threshold. */ -__attribute__((format(printf, 2, 3))) -void klog(KlogLevel level, const char* fmt, ...); - -__attribute__((format(printf, 3, 4))) -void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...); - -/* Linux-style printk interface */ -__attribute__((format(printf, 1, 2))) -int printk(const char* fmt, ...); -int vprintk(const char* fmt, __builtin_va_list va); - -/* Set runtime log level filter. Messages above this level are - * still stored in the ring buffer but not printed to console. */ -void klog_set_level(KlogLevel level); - -void klog_set_subsys_mask(uint64 mask); -void klog_enable_subsys(KlogSubsys subsys); -void klog_disable_subsys(KlogSubsys subsys); - -/* ======================================================================== - * Ring Buffer Read (for future dmesg / /proc/kmsg) - * ======================================================================== */ - -/* Read up to 'size' bytes from the ring buffer into 'buf'. - * Returns number of bytes read. Non-destructive peek. */ -int klog_read(char* buf, int size); - -/* Read and consume entries (advances tail). */ -int klog_read_consume(char* buf, int size); - -/* Flush pending entries to console (advances console tail). */ -void klog_flush(void); - -bool klog_ratelimit(KlogRateLimit* rl, uint64 now); - -void klog_stats(KlogStats* out); - -/* ======================================================================== - * Convenience Macros - * ======================================================================== */ - -#define pr_emerg(fmt, ...) klog_ex(KLOG_EMERG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_alert(fmt, ...) klog_ex(KLOG_ALERT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_crit(fmt, ...) klog_ex(KLOG_CRIT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_err(fmt, ...) klog_ex(KLOG_ERR, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_warn(fmt, ...) klog_ex(KLOG_WARN, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_notice(fmt, ...) klog_ex(KLOG_NOTICE, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#define pr_info(fmt, ...) klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) - -#define pr_emerg_s(subsys, fmt, ...) klog_ex(KLOG_EMERG, subsys, fmt, ##__VA_ARGS__) -#define pr_alert_s(subsys, fmt, ...) klog_ex(KLOG_ALERT, subsys, fmt, ##__VA_ARGS__) -#define pr_crit_s(subsys, fmt, ...) klog_ex(KLOG_CRIT, subsys, fmt, ##__VA_ARGS__) -#define pr_err_s(subsys, fmt, ...) klog_ex(KLOG_ERR, subsys, fmt, ##__VA_ARGS__) -#define pr_warn_s(subsys, fmt, ...) klog_ex(KLOG_WARN, subsys, fmt, ##__VA_ARGS__) -#define pr_notice_s(subsys, fmt, ...) klog_ex(KLOG_NOTICE, subsys, fmt, ##__VA_ARGS__) -#define pr_info_s(subsys, fmt, ...) klog_ex(KLOG_INFO, subsys, fmt, ##__VA_ARGS__) - -#ifdef KLOG_ENABLE_DEBUG -#define pr_debug(fmt, ...) klog_ex(KLOG_DEBUG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) -#else -#define pr_debug(fmt, ...) do {} while (0) -#endif - -/* pr_debug_s: subsystem-tagged debug. Always compiled in (subsys is the gate); - * level KLOG_DEBUG ensures messages stay in the ring buffer but are filtered - * out of the console by default (console_level = KLOG_INFO). Toggle a subsys - * with klog_enable_subsys()/klog_disable_subsys() at runtime — or, to make - * the messages also reach the console for debugging, raise console_level - * via klog_set_level(KLOG_DEBUG). */ -#define pr_debug_s(subsys, fmt, ...) \ - klog_ex(KLOG_DEBUG, subsys, fmt, ##__VA_ARGS__) - -/* Linux kernel loglevel prefixes */ -#define KERN_SOH "\001" -#define KERN_EMERG KERN_SOH "0" -#define KERN_ALERT KERN_SOH "1" -#define KERN_CRIT KERN_SOH "2" -#define KERN_ERR KERN_SOH "3" -#define KERN_WARNING KERN_SOH "4" -#define KERN_NOTICE KERN_SOH "5" -#define KERN_INFO KERN_SOH "6" -#define KERN_DEBUG KERN_SOH "7" - -/* Boot stage log (thin wrapper over printk) */ -#define BOOT_STAGE(n, desc) \ - printk(KERN_INFO "STAGE%u %s\n", (unsigned)(n), (desc)) - -extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/kobject.h b/na/include/lume/kobject.h deleted file mode 100644 index 8581d6e..0000000 --- a/na/include/lume/kobject.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * kobject — generic refcounted object with parent/child relationship. - * - * Mirrors Linux include/linux/kobject.h. Embedded into device, gendisk, - * request_queue, block_device, etc. Each kobject has a kobj_type that - * provides the release callback called when the last reference drops. - * - * sysfs integration is not yet implemented — the sd/sysfs_ops fields are - * reserved for future use. - * - * Reference: docs/specs/storage_stack.md §4.2 - */ - -#include <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/kprintf.h b/na/include/lume/kprintf.h deleted file mode 100644 index b158602..0000000 --- a/na/include/lume/kprintf.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Kernel Formatted Output — kprintf - * - * Provides printf-style formatted output for the kernel. - * Built on top of stb_sprintf for full format string support - * without any libc dependency. - * - * Output is synchronous and directed to the console (UART). - * Safe to call from any context (interrupt or process). - * - * Usage: - * kprintf("Hello %s, addr=0x%lx\n", name, addr); - */ - -#include <stdarg.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/* Formatted print to console (like printf). */ -__attribute__((format(printf, 1, 2))) -int kprintf(const char* fmt, ...); - -/* Formatted print with va_list. */ -int kvprintf(const char* fmt, va_list va); - -/* Formatted snprintf into buffer. */ -__attribute__((format(printf, 3, 4))) -int ksnprintf(char* buf, int count, const char* fmt, ...); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/kref.h b/na/include/lume/kref.h deleted file mode 100644 index 13d53f3..0000000 --- a/na/include/lume/kref.h +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * kref — atomic reference counting. - * - * Mirrors Linux include/linux/kref.h. Use kref_init() to set count=1, - * kref_get() / kref_put() to add/drop refs. kref_put() invokes the - * release callback when the final ref is dropped and returns 1 in that - * case. - * - * Reference: docs/specs/storage_stack.md §4.1 - */ - -#include <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 deleted file mode 100644 index e3496e9..0000000 --- a/na/include/lume/libfdt.h +++ /dev/null @@ -1,8 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _INCLUDE_LIBFDT_H_ -#define _INCLUDE_LIBFDT_H_ - -#include <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 deleted file mode 100644 index a95fb1d..0000000 --- a/na/include/lume/libfdt_env.h +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef LIBFDT_ENV_H -#define LIBFDT_ENV_H - -/* - * Kernel-side adapter for the standalone libfdt sources in - * scripts/dtc/libfdt/. libfdt's *.c files start with - * #include "libfdt_env.h" - * and expect this header to provide: - * - byte-swap helpers (cpu_to_fdt16/32/64, fdt16/32/64_to_cpu) - * - fdt16/32/64_t typedefs (just plain uint types — endianness is - * handled in the helpers above) - * - string / mem helpers (memcpy, memmove, memcmp, strlen, strcmp, - * strchr, strnlen, ...) - * - INT_MAX, UINT32_MAX (libfdt bounds-checks props with these) - * - size_t / uintptr_t (everywhere) - * - * LumeOS-native: no Linux <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 deleted file mode 100644 index a275a71..0000000 --- a/na/include/lume/limits.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_LIMITS_H -#define _LUME_LIMITS_H - -/* - * Numeric limit constants — minimal subset needed by LumeOS kernel code. - * - * Values match the standard <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 deleted file mode 100644 index ed1202a..0000000 --- a/na/include/lume/list.h +++ /dev/null @@ -1,346 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> -#include <lume/container_of.h> - -/** - * @brief The node to be embedded in a struct to make it linkable. - * - * An object can be on multiple lists simultaneously by embedding multiple - * list_node members with different names. - */ -struct list_node { - list_node* prev; - list_node* next; - - /** - * @brief Initializes the node to point to itself, forming an empty list head. - */ - void init() { - prev = this; - next = this; - } - - /** - * @brief Checks if the list this node is the head of is empty. - * @return True if the list is empty, false otherwise. - */ - bool is_empty() const { - return next == this; - } -}; - -/* - * Type-safe container_of: given a pointer to a list_node member, - * recover a pointer to the enclosing object. - * Uses pointer-to-member arithmetic — no __builtin_offsetof needed. - */ -template <typename T, list_node T::*Member> -static inline T *list_entry(list_node *node) -{ - const size_t off = (size_t)(uintptr) & (((T *)nullptr)->*Member); - return reinterpret_cast<T *>(reinterpret_cast<uintptr>(node) - off); -} - -/** - * @brief A type-safe, intrusive, doubly-linked list. - * - * This class manages objects of type T that have a `list_node` member. - * It has zero runtime overhead compared to a manual C implementation and - * provides compile-time type safety. - * - * @tparam T The type of the object in the list. - * @tparam NodeMember A pointer-to-member specifying which `list_node` in T to use. - */ -template <typename T, list_node T::*NodeMember> -class List { -private: - static list_node* get_node(T* obj) { - return &(obj->*NodeMember); - } - - // NOTE: `((T*)nullptr)->*NodeMember` is technically undefined behavior in - // standard C++. However, it's a common idiom in low-level programming - // (similar to `offsetof`) that compilers typically handle correctly, - // especially with flags like `-fno-delete-null-pointer-checks`. - // The alternative (using `offsetof`) would sacrifice the compile-time - // type safety of the pointer-to-member template parameter. - static T* get_obj(list_node* node) { - constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); - return reinterpret_cast<T*>(reinterpret_cast<uintptr>(node) - offset); - } - - static const T* get_obj(const list_node* node) { - constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); - return reinterpret_cast<const T*>(reinterpret_cast<uintptr>(node) - offset); - } - -public: - class iterator; - class const_iterator; - - class iterator { - friend class List<T, NodeMember>; - friend class const_iterator; - - public: - iterator(list_node* node) : node_(node) {} - - T& operator*() const { return *get_obj(node_); } - T* operator->() const { return get_obj(node_); } - - iterator& operator++() { - node_ = node_->next; - return *this; - } - iterator operator++(int) { - iterator tmp = *this; - ++(*this); - return tmp; - } - - iterator& operator--() { - node_ = node_->prev; - return *this; - } - iterator operator--(int) { - iterator tmp = *this; - --(*this); - return tmp; - } - - bool operator==(const iterator& other) const { return node_ == other.node_; } - bool operator!=(const iterator& other) const { return !(*this == other); } - - private: - list_node* node_; - }; - - class const_iterator { - public: - const_iterator(const list_node* node) : node_(node) {} - const_iterator(const iterator& other) : node_(other.node_) {} - - const T& operator*() const { return *get_obj(node_); } - const T* operator->() const { return get_obj(node_); } - - const_iterator& operator++() { - node_ = node_->next; - return *this; - } - const_iterator operator++(int) { - const_iterator tmp = *this; - ++(*this); - return tmp; - } - - const_iterator& operator--() { - node_ = node_->prev; - return *this; - } - const_iterator operator--(int) { - const_iterator tmp = *this; - --(*this); - return tmp; - } - - bool operator==(const const_iterator& other) const { return node_ == other.node_; } - bool operator!=(const const_iterator& other) const { return !(*this == other); } - - private: - const list_node* node_; - }; - - List() { head_.init(); } - ~List() = default; - - // This container is intrusive and does not own the objects. - // Copying or moving it is a logical error. - List(const List&) = delete; - List& operator=(const List&) = delete; - - bool empty() const { return head_.is_empty(); } - - void push_front(T* obj) { - list_node* node = get_node(obj); - node->next = head_.next; - node->prev = &head_; - head_.next->prev = node; - head_.next = node; - } - - void push_back(T* obj) { - list_node* node = get_node(obj); - node->prev = head_.prev; - node->next = &head_; - head_.prev->next = node; - head_.prev = node; - } - - /** - * @brief Splices all elements from another list to the front of this list. - * - * Moves all elements from `other` to the beginning of `this` list in O(1) - * time. The `other` list becomes empty after the operation. - * - * @param other The list to splice from. - */ - void splice_front(List& other) { - if (other.empty()) { - return; - } - list_node* this_first = head_.next; - list_node* other_first = other.head_.next; - list_node* other_last = other.head_.prev; - - // Link other's last to this's original first - other_last->next = this_first; - this_first->prev = other_last; - - // Link this's head to other's first - head_.next = other_first; - other_first->prev = &head_; - - // Reset the other list - other.head_.init(); - } - - /** - * @brief Splices all elements from another list to the back of this list. - * - * Moves all elements from `other` to the end of `this` list in O(1) - * time. The `other` list becomes empty after the operation. - * - * @param other The list to splice from. - */ - void splice_tail(List& other) { - if (other.empty()) { - return; - } - list_node* this_last = head_.prev; - list_node* other_first = other.head_.next; - list_node* other_last = other.head_.prev; - - // Link this's last to other's first - this_last->next = other_first; - other_first->prev = this_last; - - // Link other's last to this's head - other_last->next = &head_; - head_.prev = other_last; - - // Reset the other list - other.head_.init(); - } - - T* pop_front() { - if (empty()) { - return nullptr; - } - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - return get_obj(node); - } - - T* pop_back() { - if (empty()) { - return nullptr; - } - list_node* node = head_.prev; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - return get_obj(node); - } - - void remove(T* obj) { - list_node* node = get_node(obj); - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - } - - T* front() { - return empty() ? nullptr : get_obj(head_.next); - } - - const T* front() const { - return empty() ? nullptr : get_obj(head_.next); - } - - T* back() { - return empty() ? nullptr : get_obj(head_.prev); - } - - const T* back() const { - return empty() ? nullptr : get_obj(head_.prev); - } - - iterator begin() { return iterator(head_.next); } - iterator end() { return iterator(&head_); } - const_iterator begin() const { return const_iterator(head_.next); } - const_iterator end() const { return const_iterator(&head_); } - const_iterator cbegin() const { return const_iterator(head_.next); } - const_iterator cend() const { return const_iterator(&head_); } - -private: - list_node head_; -}; - -/* ======================================================================== - * Linux-compatible C-style list macros (for VFS subsystem) - * ======================================================================== */ - -using list_head = list_node; - -inline void list_init(list_node* head) { - head->prev = head; - head->next = head; -} - -inline bool list_empty(const list_node* head) { - return head->next == head; -} - -inline void __list_add(list_node* node, list_node* p, list_node* n) { - n->prev = node; - node->next = n; - node->prev = p; - p->next = node; -} - -inline void list_add(list_node* node, list_node* head) { - __list_add(node, head, head->next); -} - -inline void list_add_tail(list_node* node, list_node* head) { - __list_add(node, head->prev, head); -} - -inline void list_del(list_node* node) { - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = nullptr; - node->prev = nullptr; -} - -inline void list_del_init(list_node* node) { - node->prev->next = node->next; - node->next->prev = node->prev; - list_init(node); -} - -#define list_entry(ptr, type, member) container_of(ptr, type, member) - -#define list_for_each(pos, head) \ - for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) - -#define list_for_each_safe(pos, tmp, head) \ - for ((pos) = (head)->next, (tmp) = (pos)->next; \ - (pos) != (head); \ - (pos) = (tmp), (tmp) = (pos)->next) diff --git a/na/include/lume/lumeos.lds.h b/na/include/lume/lumeos.lds.h deleted file mode 100644 index 58d2f72..0000000 --- a/na/include/lume/lumeos.lds.h +++ /dev/null @@ -1,193 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Common Linker Script Macros (Machine-Independent) - * - * Shared section definitions that every architecture must emit. - * Architecture-specific lumeos.lds.S files #include this header - * and use these macros to avoid duplicating common boilerplate. - * - * Modeled after Linux include/asm-generic/vmlinux.lds.h - * Reference: docs/specs/linker_and_entry.md - */ - -#ifndef _LUME_LUMEOS_LDS_H -#define _LUME_LUMEOS_LDS_H - -/* ===== Text Section: executable code ===== - * - * .init.text is merged into the main .text segment so PC-relative `la` - * from regular code can call __init helpers without a thunk. Linux - * keeps a separate .init.text only to reclaim the pages after boot — - * LumeOS doesn't yet reclaim, so the merge is harmless and avoids an - * extra PT_LOAD entry. */ -#define TEXT_SECTION \ - .text : AT(ADDR(.text) - PAGE_OFFSET) { \ - _stext = .; \ - *(.text .text.*) \ - *(.init.text .init.text.*) \ - *(.fixup) \ - _etext = .; \ - } = 0 - -/* ===== Read-Only Data + C++ init_array + initramfs ===== - * - * Layout invariants: - * - extable / init_array / driver_array / initramfs all live INSIDE - * .rodata between _srodata and _erodata so the VMM has one R-only - * mapping to manage. - * - The relocation table (.lume.reloc) is NOT in here — it lives in - * its own padded output section (see RELOC_SECTION) so that its - * two-pass-build size variance can't shift downstream offsets. - * (Putting it in .rodata caused a heisenbug where pre-link offsets - * used by gen_reloc.py didn't match post-link offsets after the - * reloc.o blob was inserted.) - */ -#define RODATA_SECTION \ - .rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { \ - _srodata = .; \ - *(.rodata .rodata.*) \ - *(.srodata .srodata.* .srodata.cst*) \ - . = ALIGN(8); \ - __extable_start = .; \ - KEEP(*(.extable)) \ - __extable_end = .; \ - INIT_ARRAY \ - DRIVER_ARRAY \ - /* Embedded initramfs CPIO archive */ \ - . = ALIGN(8); \ - __initramfs_start = .; \ - KEEP(*(.init.ramfs)) \ - __initramfs_end = .; \ - . = ALIGN(4096); \ - _erodata = .; \ - } - -/* ===== .rel.dyn / .rela.dyn — Linker-emitted RELA relocations ===== - * - * Mirrors arch/riscv/kernel/vmlinux.lds.S §101-109: the .rela.dyn - * section sits BETWEEN init data and .rodata so it lands in a - * read-only PT_LOAD segment alongside .text/.rodata. The companion - * dynamic-linker bookkeeping (.dynamic, .dynsym, .dynstr, .hash, - * .gnu.hash) lives in the RW segment AFTER .data — Linux does the - * same under #ifdef CONFIG_RELOCATABLE (§139-147). */ -#define RELA_DYN_SECTION \ - .rel.dyn : AT(ADDR(.rel.dyn) - PAGE_OFFSET) { \ - *(.rel.dyn*) \ - } \ - .rela.dyn : AT(ADDR(.rela.dyn) - PAGE_OFFSET) ALIGN(8) {\ - __rela_dyn_start = .; \ - *(.rela .rela*) \ - __rela_dyn_end = .; \ - } - -/* ===== GOT — kept; everything else dynamic-linker is discarded ===== - * - * Strict mirror of arch/riscv/kernel/vmlinux.lds.S in Linux: the only - * dynamic-linker artefact the kernel keeps is `.got` (it's referenced - * by every -fPIE compiled global access). `.plt .dynamic .dynsym - * .dynstr .hash .gnu.hash .interp .dynbss` are all sent to /DISCARD/ - * via the DISCARDS macro below — the kernel never invokes a dynamic - * linker (head.S walks .rela.dyn directly). - * - * Keeping .plt was the cause of two distinct bugs: - * 1. PLT trampoline target for an undefined symbol (selftest_reloc - * after we removed test_reloc.cc) was unresolved → jumps to a - * slot that re-jumps to itself → instruction page fault. - * 2. .plt carries X permission; living in the same PT_LOAD as .data - * forces the load segment to RWX, which `ld` warns about. - * - * Sit in the same PT_LOAD as .data because .got is RW. */ -#define GOT_SECTION \ - .got : AT(ADDR(.got) - PAGE_OFFSET) { *(.got*) } - -/* ===== Embedded initramfs (CPIO archive) ===== */ -#define INITRAMFS_SECTION \ - .init.ramfs : AT(ADDR(.init.ramfs) - PAGE_OFFSET) { \ - . = ALIGN(8); \ - __initramfs_start = .; \ - KEEP(*(.init.ramfs)) \ - __initramfs_end = .; \ - } - -/* ===== C++ Global Constructor Table ===== */ -#define INIT_ARRAY \ - . = ALIGN(8); \ - __init_array_start = .; \ - KEEP(*(.init_array .init_array.*)) \ - __init_array_end = .; - -/* ===== Driver Registration Table ===== */ -#define DRIVER_ARRAY \ - . = ALIGN(8); \ - PROVIDE(__drivers_start = .); \ - KEEP(*(.drivers)) \ - PROVIDE(__drivers_end = .); - -/* ===== Initialized Read-Write Data ===== - * - * .init.data is merged into .data for the same reason TEXT_SECTION - * absorbs .init.text — LumeOS doesn't yet reclaim __init pages, so - * splitting them gains nothing while costing a PT_LOAD entry. */ -#define DATA_SECTION \ - .data : AT(ADDR(.data) - PAGE_OFFSET) { \ - _sdata = .; \ - *(.data .data.*) \ - *(.init.data .init.data.*) \ - ARCH_DATA_EXTRA \ - _edata = .; \ - } - -/* ===== BSS: Uninitialized Data ===== */ -#define BSS_SECTION \ - .bss : AT(ADDR(.bss) - PAGE_OFFSET) { \ - _sbss = .; \ - *(.bss .bss.*) \ - *(.sbss .sbss.*) \ - ARCH_BSS_EXTRA \ - _ebss = .; \ - } - -/* ===== Discard Unwanted Sections ===== - * - * Mirrors arch/riscv/kernel/vmlinux.lds.S in Linux: - * /DISCARD/ : { - * *(.dynbss) - * *(.dynstr*) - * *(.dynamic*) - * *(.plt*) - * *(.interp*) - * *(.eh_frame) - * *(.note.GNU-stack) - * } - * - * Why each is safe to discard: - * .plt — -Bsymbolic resolves all internal refs at link time; - * any remaining PLT entry is an undefined symbol that - * the kernel would have crashed on anyway. - * .dynamic etc. — kernel doesn't invoke a dynamic linker; head.S - * walks __rela_dyn_start..__rela_dyn_end manually. - * .interp — no PT_INTERP at runtime. - * .eh_frame* — no C++ exceptions in kernel (-fno-exceptions). - * .note.* — boot-time identification metadata, unused. */ -#define DISCARDS \ - /DISCARD/ : { \ - *(.dynbss) \ - *(.dynstr*) \ - *(.dynamic*) \ - *(.dynsym*) \ - *(.hash*) *(.gnu.hash*) \ - *(.plt*) \ - *(.interp*) \ - *(.eh_frame .eh_frame_hdr) \ - *(.note .note.*) \ - *(.comment) \ - } - -/* ===== Kernel Image Boundary ===== */ -#define KERNEL_END \ - . = ALIGN(4096); \ - _kernel_end = .; - -#endif /* _LUME_LUMEOS_LDS_H */ diff --git a/na/include/lume/memblock.h b/na/include/lume/memblock.h deleted file mode 100644 index cb9a76b..0000000 --- a/na/include/lume/memblock.h +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * memblock — Boot-time physical memory allocator - * - * A simple region-based allocator active from early boot until - * the buddy system (PMM) takes over. Inspired by Linux's memblock. - * - * Lifetime: - * 1. memblock_init() — discovers RAM via FDT, reserves kernel image - * 2. vmm_init() — uses memblock_alloc for page table pages & page_map - * 3. pmm_init() — absorbs remaining free regions into buddy - * 4. memblock_retire() — marks memblock inactive; all alloc via PMM - * - * This eliminates the need for post-init pointer relocation when - * rebinding page_map to a VMEMMAP virtual address. - */ - -#include <lume/types.h> - -inline constexpr int kMemblockMaxRegions = 128; - -/* Initialize memblock: scan FDT for RAM, reserve kernel image + firmware. */ -void memblock_init(uint64 fdt_paddr); - -/* Mark a physical range as reserved (will not be handed to buddy). */ -void memblock_reserve(uint64 base, uint64 size); - -/* Allocate `size` bytes aligned to `align` from top of free memory. - * Returns PA, or 0 on failure. Used for page_map. */ -uint64 memblock_alloc_top(uint64 size, uint64 align); - -/* Allocate `size` bytes aligned to `align` from bottom of free memory. - * Returns PA, or 0 on failure. Used for page table pages. */ -uint64 memblock_alloc(uint64 size, uint64 align); - -/* Query memory layout (same info as FDT). */ -uint64 memblock_get_mem_base(); -uint64 memblock_get_mem_size(); - -/* Is memblock still the active allocator? */ -bool memblock_is_active(); - -/* Mark memblock as retired (PMM takes over). */ -void memblock_retire(); - -/* Iterate all free (unreserved) regions in ascending order. - * Used by pmm_init() to feed free pages into the buddy system. */ -void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), - void* ctx); diff --git a/na/include/lume/mm_layout.h b/na/include/lume/mm_layout.h deleted file mode 100644 index 7744de0..0000000 --- a/na/include/lume/mm_layout.h +++ /dev/null @@ -1,73 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * mm_layout.h — User/Kernel address space boundary definitions - * - * Single source of truth for the user/kernel VA split. - * All modules (VMM, uaccess, page fault, tests) MUST include this - * header instead of defining their own constants. - * - * Layout (SV39, RISC-V): - * [0x0000_0000_0000, kUserVaLimit) — User space - * [kUserVaLimit, 0xFFFF_FFFF_FFFF) — Kernel space (direct map, vmemmap, etc.) - * - * Reference: docs/specs/linux_abi_mm_plan.md §5 - */ - -#include <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/mman.h b/na/include/lume/mman.h deleted file mode 100644 index d952419..0000000 --- a/na/include/lume/mman.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * POSIX Memory Management Constants - * Reference: docs/specs/syscall.md §9 - */ - -/* mmap / mprotect protection flags */ -inline constexpr int PROT_NONE = 0x0; -inline constexpr int PROT_READ = 0x1; -inline constexpr int PROT_WRITE = 0x2; -inline constexpr int PROT_EXEC = 0x4; - -/* mmap flags */ -inline constexpr int MAP_SHARED = 0x01; -inline constexpr int MAP_PRIVATE = 0x02; -inline constexpr int MAP_FIXED = 0x10; -inline constexpr int MAP_ANONYMOUS = 0x20; diff --git a/na/include/lume/mount.h b/na/include/lume/mount.h deleted file mode 100644 index 13f8e62..0000000 --- a/na/include/lume/mount.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * mount.h — Internal mount structure - */ - -#include <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 deleted file mode 100644 index 9719aa6..0000000 --- a/na/include/lume/mutex.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Mutex — Linux-style sleeping mutex (no priority inheritance). - * - * Semantics mirror Linux kernel/locking/mutex.c: - * - Single owner (nullptr when free). - * - FIFO wait list — first waiter is the next owner. - * - Recursive acquire by the same task is FORBIDDEN (panic). - * - Owner MUST be the one to unlock. - * - lock() may sleep; MUST NOT be called from IRQ context, with - * preemption disabled, or while holding a spinlock. - * - try_lock() never sleeps; may be called from any context. - * - * Priority inheritance lives in a separate primitive (RtMutex, future) - * just like Linux splits struct mutex / struct rt_mutex. - * - * Reference: docs/specs/mutex.md - */ - -#include <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 deleted file mode 100644 index 1764693..0000000 --- a/na/include/lume/namei.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * namei.h — Pathname lookup (path resolution) - */ - -#include <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/new.h b/na/include/lume/new.h deleted file mode 100644 index cdcacb8..0000000 --- a/na/include/lume/new.h +++ /dev/null @@ -1,18 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Placement New for Freestanding Environments - * - * In -ffreestanding -nostdlib mode, <new> may not be available. - * LumeOS provides its own placement new/delete operators. - * - * Usage: g_obj = new (g_obj_buf) MyClass(args...); - * - * Reference: docs/specs/global_init.md §3.1 - */ - -inline void* operator new(decltype(sizeof(0)), void* ptr) noexcept { return ptr; } -inline void operator delete(void*, void*) noexcept {} diff --git a/na/include/lume/page.h b/na/include/lume/page.h deleted file mode 100644 index 61bff95..0000000 --- a/na/include/lume/page.h +++ /dev/null @@ -1,151 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Page — Physical page descriptor - * - * Each Page describes one 4KB physical page. A flat array of Page - * structs (page_map) covers all physical memory discovered via FDT. - * - * Memory layout is compressed using a union: only one of free/slab - * metadata is valid at a time, determined by `type`. - * - * Fields: - * refcount — lifecycle reference count (atomic). Page cannot be - * freed while refcount > 0. - * mapcount — PTE mapping count (atomic). Number of page table - * entries pointing to this page. Used by COW and - * future reclaim. - * flags — page state bits (PG_*). Also serves as a bit-lock - * via PG_LOCKED for short critical sections. - * type — discriminant for the union (Free/Slab/Anon/File). - * mapping — pointer to reverse-mapping context. For anonymous - * pages, points to AnonVma (low bit set). For file - * pages, points to FileMapping (low bit clear). - * index — page offset within the mapping object. - * - * Reference: docs/specs/pmm.md §3.1 - */ - -#include <lume/types.h> -#include <lume/list.h> -#include <lume/atomic.h> - -/* Forward declarations */ -class KmemCache; - -/* ================================================================== */ -/* Page type discriminant */ -/* ================================================================== */ - -enum class PageType : uint8 { - Free, // In Buddy free list - PcpCached, // In Per-CPU cache (invisible to buddy coalescing) - Allocated, // Handed out but not yet typed (PMM default) - Slab, // Backing a Slab cache - Anon, // Anonymous user page - File, // File-backed page (page cache) -}; - -/* ================================================================== */ -/* Page flags (stored as atomic uint32) */ -/* ================================================================== */ - -inline constexpr uint32 kPgLocked = (1U << 0); // Bit-lock for short critical sections -inline constexpr uint32 kPgReferenced = (1U << 1); // Accessed since last check -inline constexpr uint32 kPgDirty = (1U << 2); // Modified, needs writeback -inline constexpr uint32 kPgWriteback = (1U << 3); // IO writeback in progress -inline constexpr uint32 kPgLru = (1U << 4); // On an LRU list -inline constexpr uint32 kPgMapped = (1U << 5); // Has at least one PTE mapping -inline constexpr uint32 kPgSwapcache = (1U << 6); // In swap cache (future) -inline constexpr uint32 kPgCow = (1U << 7); // Involved in COW sharing - -/* ================================================================== */ -/* Page descriptor */ -/* ================================================================== */ - -struct Page { - /* --- Counts (atomic, lock-free) --- */ - lume::atomic<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) - - /* --- Reverse mapping --- */ - void* mapping; // AnonVma* (low bit set) or FileMapping* - uint64 index; // Page offset within mapping object - - /* --- LRU (future) --- */ - list_node lru; // LRU list linkage (for reclaim) - - /* --- Subsystem-specific metadata --- */ - union { - /* Valid when type == Free */ - struct { - list_node free_link; // Link into Buddy or PCP free list - } free; - - /* Valid when type == Slab */ - struct { - KmemCache* cache; // Owning slab cache - uint32 obj_count; // Number of allocated objects on this page - void* freelist; // Head of in-page free object linked list - } slab; - - /* type == Anon / File: no extra union metadata (rmap via mapping) */ - }; -}; - -/* ================================================================== */ -/* Page bit-lock (lightweight, no sleep, used for rmap critical */ -/* sections and short metadata updates) */ -/* ================================================================== */ - -inline bool page_trylock(Page* page) -{ - uint32 old = page->flags.fetch_or(kPgLocked); - return (old & kPgLocked) == 0; -} - -inline void page_lock(Page* page) -{ - while (!page_trylock(page)) { - /* spin — acceptable for short critical sections only */ - } -} - -inline void page_unlock(Page* page) -{ - page->flags.fetch_and(~kPgLocked); -} - -/* ================================================================== */ -/* Mapping pointer helpers (Linux convention: low bit = anon) */ -/* ================================================================== */ - -inline bool page_is_anon(const Page* page) -{ - return (reinterpret_cast<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 deleted file mode 100644 index abdc535..0000000 --- a/na/include/lume/page_cache.h +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * page_cache.h — Page Cache interface - * - * Provides a stable abstraction for caching physical pages indexed by - * (mapping, index) pairs. All file-backed fault paths should go - * through this interface so that upgrading the backing implementation - * (e.g. LRU eviction, writeback, radix tree) does not require - * changing any caller. - * - * Current implementation: simple open-addressing hash table. - * Future: radix tree + LRU lists + writeback integration. - * - * Terminology: - * mapping — opaque pointer identifying the backing object - * (FileMapping*, or any future cache-able object). - * index — page-granularity offset within the mapping. - * - * Reference: docs/specs/linux_abi_mm_plan.md §2.9, §3 - */ - -#include <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 deleted file mode 100644 index 127f3f8..0000000 --- a/na/include/lume/pgrp.h +++ /dev/null @@ -1,125 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Process Group / Session infrastructure. - * - * Linux models pgrp/session via struct pid + PIDTYPE_PGID/PIDTYPE_SID hlists. - * We use a simpler design that supports the same operations cheaply: - * - * - Each thread group owns one signal_struct (refcount-shared with - * CLONE_THREAD siblings). - * - signal_struct carries pgid/sid and is the linked-list node on a - * fixed-size pgrp hash table. - * - kill(pid<=0), tty_signal_pgrp, orphan checks all walk one bucket. - * - * Concurrency: g_pgrp_lock protects every bucket and the pgrp_link of - * every signal_struct on a bucket. signal_struct::lock is a finer-grain - * lock for fields that change without re-hashing (e.g. tty pointer). - * - * Reference: Linux include/linux/pid.h, kernel/pid.c - */ - -#include <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 deleted file mode 100644 index e309a82..0000000 --- a/na/include/lume/pgtable_types.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Page Table Entry Types — strongly typed wrappers. - * - * Layout and macro names mirror Linux's arch/riscv/include/asm/page.h - * and pgtable-64.h so that arch/riscv/mm/init.c can be copied from - * Linux without source edits. The wrapped struct prevents accidental - * mixing of a pte_t with a pgd_t or a raw physical address, which the - * previous bare-uint64 design in pmap.cc could not catch. - * - * Underlying width on RV64 LP64 is `unsigned long` = 64 bits, matching - * Linux exactly — assembly references (e.g. REG_S/REG_L on a pte_t*) - * see the same byte layout. - * - * SV39 uses 3 levels (pgd -> pmd -> pte); SV48 uses 4 (pgd -> pud -> - * pmd -> pte). p4d_t/pud_t are emitted unconditionally so that the - * higher-level abstractions in asm-generic/pgtable-nopud.h and friends - * (which Linux uses to collapse unused levels) work without #ifdef. - */ - -#include <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/pid.h b/na/include/lume/pid.h deleted file mode 100644 index 8503750..0000000 --- a/na/include/lume/pid.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * PID Allocator — Bitmap-based O(1) block search - * Reference: docs/specs/task.md §3.2 - */ - -#include <lume/types.h> -#include <kernel/sync/spinlock.h> - -inline constexpr int32 kMaxPid = 32768; - -class PidAllocator { -public: - void init(); - int32 alloc(); - void free(int32 pid); - -private: - static constexpr int32 kBitmapWords = kMaxPid / 64; - Spinlock lock_; - uint64 bitmap_[kBitmapWords]; - int32 next_hint_ = 2; -}; - -extern PidAllocator g_pid_alloc; diff --git a/na/include/lume/pipe.h b/na/include/lume/pipe.h deleted file mode 100644 index c905c9a..0000000 --- a/na/include/lume/pipe.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * pipe.h — Pipe data structure and API - * - * A pipe is a unidirectional byte stream backed by a fixed-size - * circular buffer. Two file structs (reader + writer) share a - * single pipe_inode_info. - */ - -#include <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 deleted file mode 100644 index ac3e527..0000000 --- a/na/include/lume/platform_device.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * platform_device / platform_driver — FDT-described memory-mapped devices. - * - * Mirrors Linux include/linux/platform_device.h. The platform bus is - * the catch-all bus for devices that don't live on a real bus like PCI - * or USB — every device discovered via the FDT lands here. - * - * Reference: docs/specs/storage_stack.md §4.3 - */ - -#include <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 deleted file mode 100644 index 3e4b819..0000000 --- a/na/include/lume/pmm.h +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Physical Memory Manager (PMM) — Buddy System + Per-CPU Cache - * - * Manages all physical pages discovered via FDT. Built on a - * power-of-two buddy allocator for multi-page requests, with a - * Per-CPU single-page cache (PCP) to reduce lock contention on the - * hot single-page alloc/free path. - * - * Concurrency model: - * - PCP operations run with interrupts disabled (same-CPU safety). - * - Global buddy operations are protected by a single Spinlock. - * - Reference counts use __atomic builtins (lock-free). - * - * Reference: docs/specs/pmm.md - */ - -#include <lume/types.h> -#include <lume/page.h> -#include <lume/addr_types.h> -#include <lume/config.h> - -/* Buddy system constants */ -inline constexpr int kMaxOrder = CONFIG_MAX_ORDER; // Orders 0..(N-1) - -/* Per-CPU cache dynamic parameters (calculated in pmm_init) */ -extern uint32 g_pcp_high_watermark; -extern uint32 g_pcp_batch_size; - -/* ================================================================== */ -/* Public free-function API */ -/* ================================================================== */ - -/* - * Initialize the PMM. Called by BSP after vmm_init() + memblock. - * page_map is already mapped at VMEMMAP VA by vmm_init. - * Populates buddy lists from memblock's free regions, then retires memblock. - * Panics on failure. - */ -void pmm_init(); - -/* --- Single-page fast path (uses PCP) --- */ - -// Allocate one 4KB page. Returns nullptr on OOM. -Page* pmm_alloc_page(); - -// Free one 4KB page. -void pmm_free_page(Page* page); - -/* --- Multi-page slow path (direct buddy) --- */ - -// Allocate 2^order contiguous pages. Returns nullptr on OOM. -Page* pmm_alloc_pages(uint8 order); - -// Free 2^order contiguous pages. -void pmm_free_pages(Page* page, uint8 order); - -/* --- Reference counting (atomic, lock-free) --- */ - -void page_incref(Page* page); -void page_decref(Page* page); - -/* --- Map counting (atomic, lock-free) --- */ - -inline void page_inc_mapcount(Page* page) { - page->mapcount.fetch_add(1); -} - -inline uint32 page_dec_mapcount(Page* page) { - return page->mapcount.fetch_sub(1) - 1; // Returns new value -} - -inline uint32 page_mapcount(const Page* page) { - return page->mapcount.load(__ATOMIC_RELAXED); -} - -/* --- Address conversion --- */ - -// Convert a Page* to its physical address. -PhysAddr page_to_pa(const Page* page); - -// Convert a physical address to its Page*. -Page* pa_to_page(PhysAddr pa); - -// Convert a Page* to a kernel virtual address. -VirtAddr page_to_va(const Page* page); - -/* --- PFN (Page Frame Number) abstraction --- */ -/* - * PFN is the fundamental unit for PA↔Page conversion. - * PA↔PFN converters already exist in addr.h (pa_to_pfn / pfn_to_pa). - * When sparse vmemmap lands, only pfn_to_page/page_to_pfn - * need to change — no callers are affected. - */ - -// Convert PFN to Page*. PFN = pa / PAGE_SIZE. -Page* pfn_to_page(uint64 pfn); - -// Convert Page* to PFN. -uint64 page_to_pfn(const Page* page); - -// Query PMM memory range (for VMM kernel mapping) -PhysAddr pmm_get_mem_base(); -uint64 pmm_get_mem_size(); diff --git a/na/include/lume/poll.h b/na/include/lume/poll.h deleted file mode 100644 index 7d5bca2..0000000 --- a/na/include/lume/poll.h +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * poll.h — POSIX poll() / ppoll() infrastructure. - * - * Linux pattern (include/linux/poll.h): - * - file_operations->poll(file, poll_table) returns a bitmask of - * POLL* events that are currently true on this file. - * - As a side effect, the poll callback calls poll_wait(file, wq, pt) - * to register the caller on every wait queue that might fire when - * state changes. sys_ppoll() then sleeps on those queues until - * one of them fires or a signal/timeout arrives. - * - * Our minimal implementation supports a SINGLE wait queue per poll call - * (sufficient for nfds==1, which is the BusyBox interactive shell case). - * Multi-fd polls work but fall back to busy re-checking; a proper - * per-fd waiter-list upgrade can come later. - */ - -#include <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 deleted file mode 100644 index 687d103..0000000 --- a/na/include/lume/ptrace.h +++ /dev/null @@ -1,76 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * PtRegs — Linux-compatible user register context - * - * This structure MUST match the Linux RISC-V pt_regs layout exactly. - * It is the canonical representation for: - * - Signal frame construction (ucontext / sigcontext) - * - ptrace(GETREGS/SETREGS) - * - /proc/<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/random.h b/na/include/lume/random.h deleted file mode 100644 index 740b060..0000000 --- a/na/include/lume/random.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -enum class RandomSeedSource : uint8 { - Unknown = 0, - FdtProvided, - HwRandom, - CycleFallback, -}; - -/* - * MI: Machine-Independent Random Number Generator API - * - * Provides early PRNG and eventually a full CSPRNG. - */ - -/* - * Initialize early PRNG with a seed (e.g., from FDT). - * If seed is 0, attempts to use hardware TRNG or falls back to timer jitter. - */ -void random_early_init(uint64 seed); - -/* Consume random bits */ -uint64 get_random_u64(); -uint32 get_random_u32(); -void get_random_bytes(void* buf, size_t len); - -/* Inject entropy (for future interrupt/device mixing) */ -void add_device_randomness(const void* buf, size_t len); - -/* Early-seed metadata (for boot diagnostics/KASLR logs). */ -RandomSeedSource random_early_seed_source(); -uint64 random_early_seed_value(); -const char* random_seed_source_name(RandomSeedSource source); diff --git a/na/include/lume/rbtree.h b/na/include/lume/rbtree.h deleted file mode 100644 index 80b0c6c..0000000 --- a/na/include/lume/rbtree.h +++ /dev/null @@ -1,207 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Trees — public API - * Adapted from Linux kernel include/linux/rbtree.h - * - * (C) 1999 Andrea Arcangeli <andrea@suse.de> - */ - -#pragma once - -#include <lume/compiler.h> -#include <lume/rbtree_types.h> -#include <lume/container_of.h> - -/* ------------------------------------------------------------------ */ -/* Core macros */ -/* ------------------------------------------------------------------ */ - -#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3UL)) - -#define rb_entry(ptr, type, member) container_of(ptr, type, member) - -#define RB_EMPTY_ROOT(root) (READ_ONCE((root)->rb_node) == nullptr) - -#define RB_EMPTY_NODE(node) \ - ((node)->__rb_parent_color == (unsigned long)(node)) -#define RB_CLEAR_NODE(node) \ - ((node)->__rb_parent_color = (unsigned long)(node)) - -/* ------------------------------------------------------------------ */ -/* Core API (implemented in lib/rbtree.cc) */ -/* ------------------------------------------------------------------ */ - -#ifdef __cplusplus -extern "C" { -#endif - -void rb_insert_color(struct rb_node *, struct rb_root *); -void rb_erase(struct rb_node *, struct rb_root *); - -struct rb_node *rb_next(const struct rb_node *); -struct rb_node *rb_prev(const struct rb_node *); -struct rb_node *rb_first(const struct rb_root *); -struct rb_node *rb_last(const struct rb_root *); - -struct rb_node *rb_first_postorder(const struct rb_root *); -struct rb_node *rb_next_postorder(const struct rb_node *); - -void rb_replace_node(struct rb_node *victim, struct rb_node *_new, - struct rb_root *root); - -#ifdef __cplusplus -} -#endif - -/* ------------------------------------------------------------------ */ -/* Inline helpers */ -/* ------------------------------------------------------------------ */ - -static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, - struct rb_node **rb_link) -{ - node->__rb_parent_color = (unsigned long)parent; - node->rb_left = node->rb_right = nullptr; - *rb_link = node; -} - -#define rb_entry_safe(ptr, type, member) \ - ({ __typeof__(ptr) ____ptr = (ptr); \ - ____ptr ? rb_entry(____ptr, type, member) : nullptr; \ - }) - -/* ------------------------------------------------------------------ */ -/* Cached (leftmost) tree helpers */ -/* ------------------------------------------------------------------ */ - -#define rb_first_cached(root) (root)->rb_leftmost - -static inline void rb_insert_color_cached(struct rb_node *node, - struct rb_root_cached *root, - bool leftmost) -{ - if (leftmost) - root->rb_leftmost = node; - rb_insert_color(node, &root->rb_root); -} - -static inline void rb_erase_cached(struct rb_node *node, - struct rb_root_cached *root) -{ - if (root->rb_leftmost == node) - root->rb_leftmost = rb_next(node); - rb_erase(node, &root->rb_root); -} - -static inline void rb_replace_node_cached(struct rb_node *victim, - struct rb_node *_new, - struct rb_root_cached *root) -{ - if (root->rb_leftmost == victim) - root->rb_leftmost = _new; - rb_replace_node(victim, _new, &root->rb_root); -} - -static inline void rb_erase_init(struct rb_node *n, struct rb_root *root) -{ - rb_erase(n, root); - RB_CLEAR_NODE(n); -} - -/* ------------------------------------------------------------------ */ -/* Generic insert / find helpers (inline, using function pointers) */ -/* ------------------------------------------------------------------ */ - -static inline void -rb_add(struct rb_node *node, struct rb_root *tree, - bool (*less)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_node; - struct rb_node *parent = nullptr; - - while (*link) { - parent = *link; - if (less(node, parent)) - link = &parent->rb_left; - else - link = &parent->rb_right; - } - - rb_link_node(node, parent, link); - rb_insert_color(node, tree); -} - -static inline void -rb_add_cached(struct rb_node *node, struct rb_root_cached *tree, - bool (*less)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_root.rb_node; - struct rb_node *parent = nullptr; - bool leftmost = true; - - while (*link) { - parent = *link; - if (less(node, parent)) { - link = &parent->rb_left; - } else { - link = &parent->rb_right; - leftmost = false; - } - } - - rb_link_node(node, parent, link); - rb_insert_color_cached(node, tree, leftmost); -} - -static inline struct rb_node * -rb_find(const void *key, const struct rb_root *tree, - int (*cmp)(const void *key, const struct rb_node *)) -{ - struct rb_node *node = tree->rb_node; - - while (node) { - int c = cmp(key, node); - if (c < 0) - node = node->rb_left; - else if (c > 0) - node = node->rb_right; - else - return node; - } - return nullptr; -} - -static inline struct rb_node * -rb_find_add(struct rb_node *node, struct rb_root *tree, - int (*cmp)(struct rb_node *, const struct rb_node *)) -{ - struct rb_node **link = &tree->rb_node; - struct rb_node *parent = nullptr; - int c; - - while (*link) { - parent = *link; - c = cmp(node, parent); - if (c < 0) - link = &parent->rb_left; - else if (c > 0) - link = &parent->rb_right; - else - return parent; - } - - rb_link_node(node, parent, link); - rb_insert_color(node, tree); - return nullptr; -} - -/* ------------------------------------------------------------------ */ -/* Iteration macros */ -/* ------------------------------------------------------------------ */ - -#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ - for (pos = rb_entry_safe(rb_first_postorder(root), __typeof__(*pos), field); \ - pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ - __typeof__(*pos), field); 1; }); \ - pos = n) - diff --git a/na/include/lume/rbtree_augmented.h b/na/include/lume/rbtree_augmented.h deleted file mode 100644 index d4559c4..0000000 --- a/na/include/lume/rbtree_augmented.h +++ /dev/null @@ -1,252 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Trees — augmented operations & internal helpers - * Adapted from Linux kernel include/linux/rbtree_augmented.h - * - * (C) 1999 Andrea Arcangeli <andrea@suse.de> - * (C) 2002 David Woodhouse <dwmw2@infradead.org> - * (C) 2012 Michel Lespinasse <walken@google.com> - */ - -#pragma once - -#include <lume/rbtree.h> - -/* ------------------------------------------------------------------ */ -/* Augmented callback structure */ -/* ------------------------------------------------------------------ */ - -struct rb_augment_callbacks { - void (*propagate)(struct rb_node *node, struct rb_node *stop); - void (*copy)(struct rb_node *old, struct rb_node *_new); - void (*rotate)(struct rb_node *old, struct rb_node *_new); -}; - -extern "C" void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); - -extern "C" void __rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); - -static inline void -rb_insert_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - __rb_insert_augmented(node, root, augment->rotate); -} - -static inline void -rb_insert_augmented_cached(struct rb_node *node, - struct rb_root_cached *root, bool newleft, - const struct rb_augment_callbacks *augment) -{ - if (newleft) - root->rb_leftmost = node; - rb_insert_augmented(node, &root->rb_root, augment); -} - -/* ------------------------------------------------------------------ */ -/* Color / parent encoding helpers */ -/* ------------------------------------------------------------------ */ - -#define RB_RED 0 -#define RB_BLACK 1 - -#define __rb_parent(pc) ((struct rb_node *)(pc & ~3UL)) - -#define __rb_color(pc) ((pc) & 1) -#define __rb_is_black(pc) __rb_color(pc) -#define __rb_is_red(pc) (!__rb_color(pc)) -#define rb_color(rb) __rb_color((rb)->__rb_parent_color) -#define rb_is_red(rb) __rb_is_red((rb)->__rb_parent_color) -#define rb_is_black(rb) __rb_is_black((rb)->__rb_parent_color) - -static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) -{ - rb->__rb_parent_color = rb_color(rb) + (unsigned long)p; -} - -static inline void rb_set_parent_color(struct rb_node *rb, - struct rb_node *p, int color) -{ - rb->__rb_parent_color = (unsigned long)p + color; -} - -static inline void -__rb_change_child(struct rb_node *old, struct rb_node *_new, - struct rb_node *parent, struct rb_root *root) -{ - if (parent) { - if (parent->rb_left == old) - WRITE_ONCE(parent->rb_left, _new); - else - WRITE_ONCE(parent->rb_right, _new); - } else - WRITE_ONCE(root->rb_node, _new); -} - -/* ------------------------------------------------------------------ */ -/* Augmented erase (inline) */ -/* ------------------------------------------------------------------ */ - -static __always_inline struct rb_node * -__rb_erase_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - struct rb_node *child = node->rb_right; - struct rb_node *tmp = node->rb_left; - struct rb_node *parent, *rebalance; - unsigned long pc; - - if (!tmp) { - /* - * Case 1: node to erase has no more than 1 child (easy!) - */ - pc = node->__rb_parent_color; - parent = __rb_parent(pc); - __rb_change_child(node, child, parent, root); - if (child) { - child->__rb_parent_color = pc; - rebalance = nullptr; - } else - rebalance = __rb_is_black(pc) ? parent : nullptr; - tmp = parent; - } else if (!child) { - /* Still case 1, but this time the child is node->rb_left */ - tmp->__rb_parent_color = pc = node->__rb_parent_color; - parent = __rb_parent(pc); - __rb_change_child(node, tmp, parent, root); - rebalance = nullptr; - tmp = parent; - } else { - struct rb_node *successor = child, *child2; - - tmp = child->rb_left; - if (!tmp) { - /* - * Case 2: node's successor is its right child - */ - parent = successor; - child2 = successor->rb_right; - - augment->copy(node, successor); - } else { - /* - * Case 3: node's successor is leftmost under - * node's right child subtree - */ - do { - parent = successor; - successor = tmp; - tmp = tmp->rb_left; - } while (tmp); - child2 = successor->rb_right; - WRITE_ONCE(parent->rb_left, child2); - WRITE_ONCE(successor->rb_right, child); - rb_set_parent(child, successor); - - augment->copy(node, successor); - augment->propagate(parent, successor); - } - - tmp = node->rb_left; - WRITE_ONCE(successor->rb_left, tmp); - rb_set_parent(tmp, successor); - - pc = node->__rb_parent_color; - tmp = __rb_parent(pc); - __rb_change_child(node, successor, tmp, root); - - if (child2) { - rb_set_parent_color(child2, parent, RB_BLACK); - rebalance = nullptr; - } else { - rebalance = rb_is_black(successor) ? parent : nullptr; - } - successor->__rb_parent_color = pc; - tmp = successor; - } - - augment->propagate(tmp, nullptr); - return rebalance; -} - -static __always_inline void -rb_erase_augmented(struct rb_node *node, struct rb_root *root, - const struct rb_augment_callbacks *augment) -{ - struct rb_node *rebalance = __rb_erase_augmented(node, root, augment); - if (rebalance) - __rb_erase_color(rebalance, root, augment->rotate); -} - -static __always_inline void -rb_erase_augmented_cached(struct rb_node *node, struct rb_root_cached *root, - const struct rb_augment_callbacks *augment) -{ - if (root->rb_leftmost == node) - root->rb_leftmost = rb_next(node); - rb_erase_augmented(node, &root->rb_root, augment); -} - -/* ------------------------------------------------------------------ */ -/* Augmented callback declaration macros */ -/* ------------------------------------------------------------------ */ - -#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ -static inline void \ -RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ -{ \ - while (rb != stop) { \ - RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ - if (RBCOMPUTE(node, true)) \ - break; \ - rb = rb_parent(&node->RBFIELD); \ - } \ -} \ -static inline void \ -RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ -{ \ - RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ - RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new_->RBAUGMENTED = old_->RBAUGMENTED; \ -} \ -static void \ -RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ -{ \ - RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ - RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - new_->RBAUGMENTED = old_->RBAUGMENTED; \ - RBCOMPUTE(old_, false); \ -} \ -RBSTATIC const struct rb_augment_callbacks RBNAME = { \ - RBNAME ## _propagate, \ - RBNAME ## _copy, \ - RBNAME ## _rotate \ -}; - -#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ - RBTYPE, RBAUGMENTED, RBCOMPUTE) \ -static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit_early) \ -{ \ - RBSTRUCT *child; \ - RBTYPE max = RBCOMPUTE(node); \ - if (node->RBFIELD.rb_left) { \ - child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (node->RBFIELD.rb_right) { \ - child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (exit_early && node->RBAUGMENTED == max) \ - return true; \ - node->RBAUGMENTED = max; \ - return false; \ -} \ -RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max) - diff --git a/na/include/lume/rbtree_types.h b/na/include/lume/rbtree_types.h deleted file mode 100644 index 688ade4..0000000 --- a/na/include/lume/rbtree_types.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Red-Black Tree type definitions - * Adapted from Linux kernel include/linux/rbtree_types.h - */ - -#pragma once - -#include <lume/types.h> - -struct rb_node { - unsigned long __rb_parent_color; - struct rb_node *rb_right; - struct rb_node *rb_left; -} __attribute__((aligned(sizeof(long)))); - -struct rb_root { - struct rb_node *rb_node; -}; - -struct rb_root_cached { - struct rb_root rb_root; - struct rb_node *rb_leftmost; -}; - -#define RB_ROOT (struct rb_root) { nullptr, } -#define RB_ROOT_CACHED (struct rb_root_cached) { {nullptr, }, nullptr } - diff --git a/na/include/lume/sched.h b/na/include/lume/sched.h deleted file mode 100644 index ab26bac..0000000 --- a/na/include/lume/sched.h +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Scheduler Public Interface - * - * Linux-style sched_class core with per-CPU runqueues. - * Phase 2: O(1) bitmap class is the default scheduling class. - * - * Reference: docs/specs/scheduler.md - */ - -#include <lume/types.h> -#include <lume/config.h> - -struct TaskControlBlock; - -/* ======================================================================== - * Scheduler Constants — driven by CONFIG_xxx (see scripts/Kconfig.mk) - * ======================================================================== */ - -inline constexpr int kNumPriorities = CONFIG_NR_PRIORITIES; -inline constexpr int kDefaultPriority = CONFIG_DEFAULT_PRIORITY; -inline constexpr int kDefaultTimeSlice = CONFIG_DEFAULT_TIMESLICE; -inline constexpr int kIdlePriority = CONFIG_IDLE_PRIORITY; - -/* ======================================================================== - * Initialization - * ======================================================================== */ - -/* BSP: initialize all RunQueues, create BSP idle thread and boot TCB. */ -void sched_init(); - -/* AP: create per-CPU idle thread and boot TCB. */ -void sched_init_ap(); - -/* ======================================================================== - * Core Scheduling - * ======================================================================== */ - -/* Main scheduling entry point. Picks the next task and context-switches. - * Called voluntarily (yield, sleep, exit) or from trap return path. */ -void schedule(); - -/* Timer tick handler. Delegates to current task's sched_class. - * Called from MI trap layer in interrupt context. */ -void sched_tick(); - -/* Check if rescheduling is needed (called before returning to user mode). */ -void sched_check_preempt(); - -/* Voluntarily yield the current time slice. */ -void sched_yield(); - -/* ======================================================================== - * Task Queue Management (used by task.cc, waitqueue.cc) - * ======================================================================== */ - -/* Make a task runnable: add it to its CPU's RunQueue. */ -void sched_enqueue(TaskControlBlock* task); - -/* Mark a task as sleeping: remove it from the RunQueue. */ -void sched_dequeue(TaskControlBlock* task); - -/* ======================================================================== - * Time Slice Policy - * ======================================================================== */ - -/* Convert priority level to time slice length. - * Higher priority (lower number) → longer time slice. */ -int32 priority_to_timeslice(uint8 priority); - -/* ======================================================================== - * Task Migration & Load Balancing - * ======================================================================== */ - -/* - * sched_migrate_task — Move a task from its current CPU to target_cpu. - * - * The task must NOT be the currently running task. It is dequeued from - * its old runqueue and enqueued onto the target. Both runqueue locks - * are acquired in cpu_id order to prevent deadlock. - * - * Returns 0 on success, -EINVAL if task is current or target is invalid. - */ -int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu); - -/* - * sched_balance — Periodic load balancer. - * - * Called from sched_tick(). Checks if any CPU has significantly more - * runnable tasks and pulls work to the local runqueue. This is a - * simplified version of Linux's load_balance(). - */ -void sched_balance(); - -/* - * sched_nr_running — Get the number of runnable tasks on a CPU. - */ -uint32 sched_nr_running(uint32 cpu_id); diff --git a/na/include/lume/sched_class.h b/na/include/lume/sched_class.h deleted file mode 100644 index 08686c1..0000000 --- a/na/include/lume/sched_class.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Scheduler Class Interface (Linux-style) - * - * Each scheduling algorithm implements a sched_class and is chained - * in priority order. The scheduler core walks the chain to select - * the next runnable task. - */ - -#include <lume/types.h> - -struct RunQueue; -struct TaskControlBlock; - -struct SchedClass { - const char* name; - const SchedClass* next; // Linked list order: higher priority first - - void (*init_rq)(RunQueue* rq); - - void (*enqueue_task)(RunQueue* rq, TaskControlBlock* task); - void (*dequeue_task)(RunQueue* rq, TaskControlBlock* task); - TaskControlBlock* (*pick_next_task)(RunQueue* rq); - void (*put_prev_task)(RunQueue* rq, TaskControlBlock* task); - - void (*task_tick)(RunQueue* rq, TaskControlBlock* curr); - void (*yield_task)(RunQueue* rq, TaskControlBlock* curr); -}; - -extern const SchedClass g_sched_class_o1; -extern const SchedClass g_sched_class_idle; diff --git a/na/include/lume/selftest.h b/na/include/lume/selftest.h deleted file mode 100644 index 7040986..0000000 --- a/na/include/lume/selftest.h +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Boot Self-Test Framework - * - * Minimal assertion-based test harness that runs during kernel_main() - * after all subsystems are initialized. Each test is a void function - * that calls ST_ASSERT / ST_ASSERT_EQ macros. On failure, prints - * the failing expression and panics. - * - * Usage: - * void selftest_pmm(); // defined in test/test_pmm.cc - * void selftest_slab(); // defined in test/test_slab.cc - * - * All self-tests run only on BSP in single-core mode. - */ - -#include <lume/types.h> -#include <lume/klog.h> -#include <lume/kprintf.h> - -#define ST_ASSERT(cond) \ - do { \ - if (!(cond)) { \ - kprintf("[FAIL] %s:%d: assertion failed: %s\n", \ - __FILE__, __LINE__, #cond); \ - kernel_panic("selftest failed", #cond); \ - } \ - } while (0) - -#define ST_ASSERT_EQ(a, b) \ - do { \ - auto _a = (a); auto _b = (b); \ - if (_a != _b) { \ - kprintf("[FAIL] %s:%d: %s != %s (0x%llx != 0x%llx)\n", \ - __FILE__, __LINE__, #a, #b, \ - (uint64)(uintptr)(_a), (uint64)(uintptr)(_b)); \ - kernel_panic("selftest failed", #a " != " #b); \ - } \ - } while (0) - -#define ST_ASSERT_NE(a, b) \ - do { \ - auto _a = (a); auto _b = (b); \ - if (_a == _b) { \ - kprintf("[FAIL] %s:%d: %s == %s (both 0x%llx)\n", \ - __FILE__, __LINE__, #a, #b, \ - (uint64)(uintptr)(_a)); \ - kernel_panic("selftest failed", #a " == " #b); \ - } \ - } while (0) - -/* Announce a test suite beginning/end */ -inline void st_begin(const char* name) -{ - kprintf("[test] %s ... ", name); -} - -inline void st_pass() -{ - kprintf("PASS\n"); -} diff --git a/na/include/lume/setup.h b/na/include/lume/setup.h deleted file mode 100644 index f8e6cb6..0000000 --- a/na/include/lume/setup.h +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_SETUP_H -#define _LUME_SETUP_H - -/* - * Boot-time interfaces called between assembly and C. - * - * Mirrors the subset of Linux include/linux/setup.h relevant to LumeOS's - * Linux-aligned RISC-V boot flow: - * - * setup_vm Build early_pg_dir / trampoline_pg_dir before - * the MMU is enabled. Filled kernel_map for - * all subsequent code to consult. - * relocate_enable_mmu Switch SATP, jump to higher-half, apply - * .rela.dyn via relocate_kernel. - * relocate_kernel Walk .rela.dyn, patch R_RISCV_RELATIVE - * slots with addend + kaslr_offset. - * - * kernel_main (LumeOS's equivalent of Linux's start_kernel) is declared - * in kernel/main.cc and re-declared in arch-side glue — kept local to - * that file to avoid creating an artificial public API for the lone - * call site in head.S. - */ - -#include <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 deleted file mode 100644 index 05b7542..0000000 --- a/na/include/lume/signal.h +++ /dev/null @@ -1,60 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Signal Infrastructure — Core signal delivery API - * - * Provides the interface between trap handling, signal delivery, - * and user-space signal handler execution. - * - * Reference: Linux include/linux/sched/signal.h - */ - -#include <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_nr.h b/na/include/lume/signal_nr.h deleted file mode 100644 index 8e595d7..0000000 --- a/na/include/lume/signal_nr.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Linux-compatible signal numbers and related ABI constants. - * - * This mirrors the common Linux UAPI numbering used by asm-generic/signal.h. - * Keep the values stable so user-space binaries see the same numbers. - */ - -inline constexpr int _NSIG = 64; -inline constexpr int _NSIG_BPW = sizeof(unsigned long) * 8; -inline constexpr int _NSIG_WORDS = _NSIG / _NSIG_BPW; - -inline constexpr int SIGHUP = 1; -inline constexpr int SIGINT = 2; -inline constexpr int SIGQUIT = 3; -inline constexpr int SIGILL = 4; -inline constexpr int SIGTRAP = 5; -inline constexpr int SIGABRT = 6; -inline constexpr int SIGIOT = SIGABRT; -inline constexpr int SIGBUS = 7; -inline constexpr int SIGFPE = 8; -inline constexpr int SIGKILL = 9; -inline constexpr int SIGUSR1 = 10; -inline constexpr int SIGSEGV = 11; -inline constexpr int SIGUSR2 = 12; -inline constexpr int SIGPIPE = 13; -inline constexpr int SIGALRM = 14; -inline constexpr int SIGTERM = 15; -inline constexpr int SIGSTKFLT = 16; -inline constexpr int SIGCHLD = 17; -inline constexpr int SIGCONT = 18; -inline constexpr int SIGSTOP = 19; -inline constexpr int SIGTSTP = 20; -inline constexpr int SIGTTIN = 21; -inline constexpr int SIGTTOU = 22; -inline constexpr int SIGURG = 23; -inline constexpr int SIGXCPU = 24; -inline constexpr int SIGXFSZ = 25; -inline constexpr int SIGVTALRM = 26; -inline constexpr int SIGPROF = 27; -inline constexpr int SIGWINCH = 28; -inline constexpr int SIGIO = 29; -inline constexpr int SIGPOLL = SIGIO; -inline constexpr int SIGPWR = 30; -inline constexpr int SIGSYS = 31; -inline constexpr int SIGUNUSED = 31; - -/* Real-time signals start here in Linux UAPI. */ -inline constexpr int SIGRTMIN = 32; -inline constexpr int SIGRTMAX = _NSIG; diff --git a/na/include/lume/signal_types.h b/na/include/lume/signal_types.h deleted file mode 100644 index f66231b..0000000 --- a/na/include/lume/signal_types.h +++ /dev/null @@ -1,263 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Signal Types — Linux-compatible structures for signal delivery - * - * Follows the RISC-V LP64 ABI layout so that user-space programs - * compiled with musl / glibc see the correct struct sizes. - * - * Reference: Linux include/uapi/asm-generic/signal.h - * Linux include/uapi/asm-generic/siginfo.h - */ - -#include <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/slab.h b/na/include/lume/slab.h deleted file mode 100644 index 4ebade1..0000000 --- a/na/include/lume/slab.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * SLUB public API - * - * This header exposes the allocator entry points to the rest of the kernel. - * Implementation details stay in mm/slab.h and mm/slab.cc. - */ - -#include <lume/types.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/* Initialize global slab caches. */ -void slab_init(); - -/* Allocate/free kernel memory. */ -void* kmalloc(uint32 size); -void kfree(void* ptr); - -#ifdef __cplusplus -} -#endif diff --git a/na/include/lume/smp.h b/na/include/lume/smp.h deleted file mode 100644 index a5292b6..0000000 --- a/na/include/lume/smp.h +++ /dev/null @@ -1,74 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * smp.h — Machine-Independent SMP primitives - * - * Provides cross-CPU communication (IPI) and TLB coordination. - * The MI interface calls through to arch-specific backends - * (e.g. SBI on RISC-V, APIC on x86). - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP - */ - -#include <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 deleted file mode 100644 index c8dc7f4..0000000 --- a/na/include/lume/stdarg.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef _LINUX_STDARG_H -#define _LINUX_STDARG_H - -typedef __builtin_va_list va_list; -#define va_start(v, l) __builtin_va_start(v, l) -#define va_end(v) __builtin_va_end(v) -#define va_arg(v, T) __builtin_va_arg(v, T) -#define va_copy(d, s) __builtin_va_copy(d, s) - -#endif diff --git a/na/include/lume/stringify.h b/na/include/lume/stringify.h deleted file mode 100644 index 0e84cbe..0000000 --- a/na/include/lume/stringify.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef __LINUX_STRINGIFY_H -#define __LINUX_STRINGIFY_H - -/* Indirect stringification. Doing two levels allows the parameter to be a - * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) - * converts to "bar". - */ - -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) - -#define FILE_LINE __FILE__ ":" __stringify(__LINE__) - -#endif /* !__LINUX_STRINGIFY_H */ diff --git a/na/include/lume/syscall.h b/na/include/lume/syscall.h deleted file mode 100644 index 8185096..0000000 --- a/na/include/lume/syscall.h +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * System Call Interface — Machine-Independent Layer - * - * Defines the unified syscall handler signature and the MI dispatch function. - * MD layer (arch/riscv/syscall/syscall_md.cc) extracts registers into args[6] - * and calls syscall_dispatch(). - * - * Reference: docs/specs/syscall.md §4 - */ - -#include <lume/types.h> - -/* Unified syscall handler signature. - * All arguments and return value are register-width (uint64). - * Individual sys_* functions cast internally. */ -using SyscallHandler = int64 (*)(uint64, uint64, uint64, - uint64, uint64, uint64); - -/* MI-layer syscall dispatcher. - * Called by MD layer after extracting syscall number and arguments. - * Returns the syscall result (negative errno on error). */ -int64 syscall_dispatch(uint64 nr, uint64 args[6]); - -/* Register all implemented syscall handlers into the jump table. - * Called from kernel_main() after sched_init(). */ -void syscall_init(); - -/* MD-layer entry point — called from trap handler on TrapCause::Syscall. - * Extracts registers from TrapFrame, calls syscall_dispatch, writes result. */ -namespace arch { -struct TrapFrame; -} -void syscall_dispatch_md(arch::TrapFrame* tf); diff --git a/na/include/lume/syscall_nr.h b/na/include/lume/syscall_nr.h deleted file mode 100644 index 29e3904..0000000 --- a/na/include/lume/syscall_nr.h +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * System Call Numbers — RISC-V LP64 (asm-generic/unistd.h compatible) - * - * Numbering strictly follows Linux asm-generic so that musl libc / Busybox - * binaries can be run without modification. - * - * Reference: docs/specs/syscall.md §4.3 - */ - -inline constexpr int SYS_exit = 93; -inline constexpr int SYS_exit_group = 94; -inline constexpr int SYS_set_tid_address = 96; -inline constexpr int SYS_sched_yield = 124; -inline constexpr int SYS_uname = 160; -inline constexpr int SYS_getpid = 172; -inline constexpr int SYS_getppid = 173; -inline constexpr int SYS_getuid = 174; -inline constexpr int SYS_getgid = 176; -inline constexpr int SYS_gettid = 178; -inline constexpr int SYS_brk = 214; -inline constexpr int SYS_munmap = 215; -inline constexpr int SYS_clone = 220; -inline constexpr int SYS_execve = 221; -inline constexpr int SYS_mmap = 222; -inline constexpr int SYS_mprotect = 226; -inline constexpr int SYS_wait4 = 260; - -inline constexpr int SYS_getcwd = 17; -inline constexpr int SYS_dup = 23; -inline constexpr int SYS_dup3 = 24; -inline constexpr int SYS_ioctl = 29; -inline constexpr int SYS_mkdirat = 34; -inline constexpr int SYS_unlinkat = 35; -inline constexpr int SYS_chdir = 49; -inline constexpr int SYS_openat = 56; -inline constexpr int SYS_close = 57; -inline constexpr int SYS_pipe2 = 59; -inline constexpr int SYS_lseek = 62; -inline constexpr int SYS_read = 63; -inline constexpr int SYS_write = 64; -inline constexpr int SYS_writev = 66; -inline constexpr int SYS_ppoll = 73; -inline constexpr int SYS_fstatat = 79; -inline constexpr int SYS_fstat = 80; -inline constexpr int SYS_getdents64 = 61; - -inline constexpr int SYS_fcntl = 25; -inline constexpr int SYS_linkat = 37; -inline constexpr int SYS_mount = 40; -inline constexpr int SYS_umount2 = 39; - -inline constexpr int SYS_kill = 129; -inline constexpr int SYS_tkill = 130; -inline constexpr int SYS_tgkill = 131; -inline constexpr int SYS_sigaltstack = 132; -inline constexpr int SYS_rt_sigaction = 134; -inline constexpr int SYS_rt_sigprocmask = 135; -inline constexpr int SYS_rt_sigreturn = 139; -inline constexpr int SYS_reboot = 142; -inline constexpr int SYS_setpgid = 154; -inline constexpr int SYS_getpgid = 155; -inline constexpr int SYS_getsid = 156; -inline constexpr int SYS_setsid = 157; -inline constexpr int SYS_prctl = 167; -inline constexpr int SYS_geteuid = 175; -inline constexpr int SYS_getegid = 177; - -inline constexpr int SYS_futex = 98; -inline constexpr int SYS_nanosleep = 101; -inline constexpr int SYS_clock_gettime = 113; -inline constexpr int SYS_epoll_create1 = 20; -inline constexpr int SYS_epoll_ctl = 21; -inline constexpr int SYS_epoll_pwait = 22; - -inline constexpr int SYS_rt_sigtimedwait = 137; -inline constexpr int SYS_socket = 198; -inline constexpr int SYS_sendto = 206; diff --git a/na/include/lume/task.h b/na/include/lume/task.h deleted file mode 100644 index e959147..0000000 --- a/na/include/lume/task.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Task Control Block & Task State - * - * Central representation of every execution context in LumeOS, - * whether kernel thread or (future) user process. - * - * Reference: docs/specs/task.md §3.1, docs/specs/scheduler.md §3.1 - */ - -#include <lume/types.h> -#include <lume/config.h> -#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; -struct SchedClass; - -/* ======================================================================== - * Task State Machine - * ======================================================================== */ - -enum class TaskState : uint8 { - Runnable = 0, // In a Per-CPU ready queue - Running = 1, // Currently executing on a CPU - Sleeping = 2, // Blocked on a WaitQueue - Zombie = 3, // Exited, waiting for parent wait() - Dead = 4, // Fully reclaimed, TCB can be freed - Stopped = 5, // Job control: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU -}; - -/* ======================================================================== - * Task Flags - * ======================================================================== */ - -inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user mode) -inline constexpr uint8 TF_IDLE = 0x02; // Idle thread -inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1) - -/* ======================================================================== - * Kernel Stack - * ======================================================================== */ - -inline constexpr uint64 kKernelStackSize = CONFIG_KERNEL_STACK_SIZE; -inline constexpr int kKernelStackOrder = (kKernelStackSize <= 4096) ? 0 - : (kKernelStackSize <= 8192) ? 1 - : (kKernelStackSize <= 16384) ? 2 - : (kKernelStackSize <= 32768) ? 3 : 4; -inline constexpr uint64 kStackCanary = 0xDEAD'BEEF'CAFE'BABEULL; - -/* ======================================================================== - * Task Control Block - * ======================================================================== */ - -struct TaskControlBlock { - /* ===== Scheduler hot fields (cache-line optimized) ===== */ - TaskState state; // Current state - uint8 priority; // Static priority [0, 31] - uint8 cpu_id; // Pinned CPU (Phase 2: no migration) - uint8 flags; // TF_KTHREAD | TF_IDLE | TF_INIT - - int32 time_slice; // Remaining time slice (ticks) - int32 pid; // Unique task ID (Linux: tid) - int32 tgid; // Thread group ID (Linux: pid) - int32 preempt_count; // >0: preemption disabled (holding locks etc.) - - list_node run_link; // RunQueue list node - list_node all_link; // Global task list node - - /* ===== Context ===== */ - arch::Context context; // Callee-saved registers (scheduler switch) - arch::TrapFrame* trapframe; // Trap frame (user-mode entry/exit) - arch::FpState fpstate; // FPU register state (f0-f31 + fcsr) - - /* ===== Kernel Stack ===== */ - uint64 kstack_pa; // Physical address - uint64 kstack_va; // Virtual address (stack bottom) - - /* ===== Address Space ===== */ - VmSpace* mm; // nullptr = kernel thread - - /* ===== Per-task lock ===== - * Protects: parent, children, sibling, exit_code, state transitions. - * Lock ordering: task->lock -> g_task_list_lock (never reverse). */ - Spinlock lock; - - /* ===== Task Hierarchy ===== */ - TaskControlBlock* parent; - list_node children; // Children list head - list_node sibling; // Sibling list node - WaitQueue wait_chld; // Parent waits on this for children to exit - - /* ===== Exit ===== */ - int32 exit_code; - int32 exit_signal; // Linux: signal sent to parent on exit. - // SIGCHLD = notify parent, become zombie - // -1 = auto-reap (kthreads) - uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: write 0 here on exit - - /* ===== Job control reporting ===== - * stopped_code: 0 means "no pending stop notification"; non-zero is the - * Linux wstatus value to hand back from wait4(WUNTRACED). Cleared - * after the parent consumes it via wait4 (or task termination). - * notify_continued: set when SIGCONT resumes us; consumed by - * wait4(WCONTINUED). */ - int32 stopped_code; - bool notify_continued; - - /* ===== Scheduler class metadata ===== */ - const SchedClass* sched_class; - void* sched_entity; - - /* ===== VFS ===== */ - struct files_struct* files; // Per-task fd table (shared via CLONE_FILES) - struct fs_struct* fs; // cwd/root (shared via CLONE_FS) - - /* ===== Signals ===== */ - struct sigpending pending; // Per-task pending signal queue - sigset_t blocked; // Blocked signal mask - struct sighand_struct* sighand; // Signal handler table (shared via CLONE_SIGHAND) - struct signal_struct* signal; // Thread-group identity: pgid/sid/ctty (shared via CLONE_THREAD) - - /* ===== Debug ===== */ - const char* name; -}; - -/* ======================================================================== - * current_task() — fast access to the running task on this CPU - * - * Must be called with interrupts disabled or while holding a spinlock. - * ======================================================================== */ -TaskControlBlock* current_task(); - -/* Terminate the current task with a signal. */ -void task_kill_current(int sig); diff --git a/na/include/lume/timer.h b/na/include/lume/timer.h deleted file mode 100644 index 9bdddd5..0000000 --- a/na/include/lume/timer.h +++ /dev/null @@ -1,43 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> - -/* - * 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; - -void timer_init(); - -// AP per-CPU initialization -void timer_init_ap(); - -// Handle timer interrupt tick (called from TrapHandler) -void timer_tick(); - -// Get configured timebase frequency -uint64 timer_get_freq(); - -void add_timer(TimerList* timer); -int del_timer(TimerList* timer); -int mod_timer(TimerList* timer, uint64 expires); diff --git a/na/include/lume/tlb.h b/na/include/lume/tlb.h deleted file mode 100644 index b4d51ea..0000000 --- a/na/include/lume/tlb.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tlb.h — Machine-Independent TLB management interface - * - * All TLB invalidation in the kernel MUST go through these functions. - * Direct use of arch::mmu::flush_tlb_*() is prohibited outside of - * this file's implementation. - * - * Current implementation: local CPU sfence.vma only (single-core). - * When SMP IPI infrastructure is available, tlb_shootdown_range() - * and tlb_shootdown_all() will broadcast invalidation to remote - * CPUs via IPI — no caller changes required. - * - * Reference: docs/specs/linux_abi_mm_plan.md §4 - */ - -#include <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/trap.h b/na/include/lume/trap.h deleted file mode 100644 index 61a5054..0000000 --- a/na/include/lume/trap.h +++ /dev/null @@ -1,109 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Trap Subsystem — Machine-Independent Interface - * - * Defines TrapCause enum and MI-layer declarations. - * MD layer (arch/riscv/trap/trap_md.cc) translates scause → TrapCause. - * MI layer (kernel/trap.cc) dispatches based on TrapCause alone. - * - * Reference: docs/specs/trap.md §3.2 - */ - -#include <lume/types.h> - -/* MI-layer trap cause classification. - * MD layer is responsible for translating architecture-specific cause codes - * (e.g., RISC-V scause) into these semantic values. MI layer never reads CSRs. */ -enum class TrapCause : uint32 { - /* --- Exceptions (Synchronous) --- */ - PageFaultLoad = 0, // Load page fault - PageFaultStore = 1, // Store/AMO page fault - PageFaultExec = 2, // Instruction page fault - Syscall = 3, // User-mode ecall - IllegalInst = 4, // Illegal instruction - Breakpoint = 5, // Breakpoint (ebreak) - AccessFault = 6, // Bus access fault (not page fault) - AlignFault = 7, // Address misalignment - - /* --- Interrupts (Asynchronous) --- */ - IrqTimer = 16, // S-mode timer interrupt - IrqSoftware = 17, // S-mode software interrupt (IPI) - IrqExternal = 18, // S-mode external interrupt (PLIC) - - /* --- Unknown --- */ - Unknown = 255, -}; - -/* High-level trap class for routing and stats. */ -enum class TrapClass : uint8 { - Exception = 0, - Interrupt = 1, - Syscall = 2, - Unknown = 3, -}; - -/* Origin privilege of the trap. */ -enum class TrapOrigin : uint8 { - FromUser = 0, - FromKernel = 1, -}; - -/* Unified trap context passed from MD to MI. */ -struct TrapInfo { - TrapClass cls; - TrapCause cause; - TrapOrigin origin; - uint64 val; // stval (faulting address or instruction) - uint64 epc; // sepc at trap entry (may be adjusted by MD) - uint64 scause; // raw scause for diagnostics -}; - -/* Trap statistics for debugging/testing. */ -inline constexpr uint32 kTrapCauseCount = 12; - -struct TrapStats { - uint64 total; - uint64 by_cause[kTrapCauseCount]; - TrapCause last_cause; - TrapClass last_class; - TrapOrigin last_origin; - uint64 last_val; - uint64 last_epc; - uint64 last_scause; -}; - -namespace arch { -struct TrapFrame; // Forward declaration (defined in arch/trapframe.h) -} - -/* - * trap_init() — BSP initialization - * Sets stvec to kernel_trap_entry (handles traps while in S-mode). - * Initializes the global IrqManager. - * Called from kernel_main() after VMM and before PLIC. - */ -void trap_init(); - -/* - * trap_init_ap() — AP per-CPU initialization - * Sets this CPU's stvec. - */ -void trap_init_ap(); - -/* - * trap_handle() — MI-layer semantic dispatcher - * Called by MD layer after scause → TrapCause translation. - * Routes to page fault handler, syscall dispatcher, IRQ manager, etc. - * - * tf: pointer to the current TrapFrame - * info: translated trap context from MD layer - */ -void trap_handle(arch::TrapFrame* tf, const TrapInfo& info); - -/* Trap statistics helpers (MI layer). */ -void trap_stats_get(TrapStats* out); -void trap_stats_reset(); diff --git a/na/include/lume/tty.h b/na/include/lume/tty.h deleted file mode 100644 index 34cf7bb..0000000 --- a/na/include/lume/tty.h +++ /dev/null @@ -1,165 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tty.h — TTY subsystem core structures - * - * tty_struct is the central object representing an open terminal. - * It connects a hardware driver (tty_driver) with a line discipline - * (tty_ldisc) to implement the full terminal abstraction. - * - * Architecture: - * user read/write → tty_core → tty_ldisc (n_tty) → tty_driver (uart/pty) - * - * Reference: Linux include/linux/tty.h, include/linux/tty_ldisc.h - */ - -#include <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 deleted file mode 100644 index 87ba59b..0000000 --- a/na/include/lume/tty_ioctl.h +++ /dev/null @@ -1,202 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * tty_ioctl.h — Terminal I/O control constants and structures - * - * Linux-compatible termios, winsize, and TIOC* ioctl definitions. - * Values match the RISC-V/generic Linux ABI. - * - * Reference: include/uapi/asm-generic/termbits.h, - * include/uapi/asm-generic/ioctls.h - */ - -#include <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 deleted file mode 100644 index 2342359..0000000 --- a/na/include/lume/types.h +++ /dev/null @@ -1,78 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#ifndef _LUME_TYPES_H -#define _LUME_TYPES_H - -/* - * LumeOS Base Type Definitions - * - * Three naming layers coexist so that source files imported from - * upstream projects (Linux kernel, libfdt) compile unchanged: - * - * LumeOS native uint8 / int8 / ... / uint64 / int64 - * Linux kernel short names u8 / s8 / ... / u64 / s64 - * POSIX / C99 (with _t) uint8_t / ... / uint64_t / intptr_t - * - * All three resolve to the same underlying compiler builtin, so they - * are binary-compatible. - * - * Declared with C `typedef` (not C++ `using`) so this header may be - * included from .c sources — required for libfdt shims and the - * arch/riscv/kernel/pi/ subsystem, which are pure C imports from - * Linux. C++ accepts `typedef` syntax identically. - */ - -/* ===== LumeOS native names ===== */ -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; -typedef unsigned long long uint64; - -typedef signed char int8; -typedef signed short int16; -typedef signed int int32; -typedef signed long long int64; - -typedef __SIZE_TYPE__ size_t; -typedef __PTRDIFF_TYPE__ ptrdiff_t; -typedef int64 ssize_t; -typedef uint64 uintptr; - -/* ===== Linux kernel short names ===== */ -typedef uint8 u8; typedef int8 s8; -typedef uint16 u16; typedef int16 s16; -typedef uint32 u32; typedef int32 s32; -typedef uint64 u64; typedef int64 s64; - -/* ===== POSIX / C99 aliases ===== - * Guarded so a later #include <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/uaccess.h b/na/include/lume/uaccess.h deleted file mode 100644 index 5a9ed71..0000000 --- a/na/include/lume/uaccess.h +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * User-space memory access — safe copy routines. - * - * Phase 2: Software page-table validation. - * Phase 3+: Exception Table (zero-cost happy path). - * - * Reference: docs/specs/syscall.md §8 - */ - -#include <lume/types.h> - -/* Copy len bytes from user address to kernel buffer. - * Returns 0 on success, -EFAULT if user address is invalid. */ -int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len); - -/* Copy len bytes from kernel buffer to user address. - * Returns 0 on success, -EFAULT if user address is invalid. */ -int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len); - -/* Copy NUL-terminated string from user space. - * Returns string length (excluding NUL) or -EFAULT. */ -int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len); diff --git a/na/include/lume/uuid.h b/na/include/lume/uuid.h deleted file mode 100644 index fcbd4a3..0000000 --- a/na/include/lume/uuid.h +++ /dev/null @@ -1,207 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * UUID/GUID definition - * - * Copyright (C) 2010, 2016 Intel Corp. - * Huang Ying <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 deleted file mode 100644 index 9cbebf1..0000000 --- a/na/include/lume/vmm.h +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * vmm.h — Virtual Memory Manager - * - * Managing kernel and user virtual address spaces. - * - * Reference: docs/specs/vmm.md - */ - -#include <lume/types.h> -#include <lume/rbtree.h> -#include <lume/atomic.h> -#include <kernel/sync/spinlock.h> - -/* ------------------------------------------------------------------ */ -/* MI-layer permission flags (architecture-independent) */ -/* */ -/* These are used by VMA and vmm_map_user / vmm_handle_page_fault. */ -/* The pmap MD layer translates them to hardware PTE bits. */ -/* ------------------------------------------------------------------ */ -inline constexpr uint64 VM_READ = (1ULL << 0); -inline constexpr uint64 VM_WRITE = (1ULL << 1); -inline constexpr uint64 VM_EXEC = (1ULL << 2); -inline constexpr uint64 VM_USER = (1ULL << 3); - -enum class VmFaultCause : uint8 { - Read = 0, - Write = 1, - Exec = 2, -}; - -/* Forward declarations */ -struct VmSpace; -struct VmAreaStruct; - -/* - * Virtual Memory Area (VMA) - * Represents a contiguous range [start, end) of uniform permissions. - */ -struct VmAreaStruct { - uint64 start; // Start VA (page aligned) - uint64 end; // End VA (exclusive, page aligned) - uint64 perm; // VM_READ | VM_WRITE | VM_EXEC | VM_USER - struct rb_node rb; // Linkage in VmSpace::vma_tree - VmSpace* mm; // Back pointer to the parent address space -}; - -/* - * Address Space (VmSpace) - * Represents a user process's complete virtual memory mapping. - * - * Lock contract: - * Holds: lock (internal spinlock) - * May acquire: PMM global lock (via pmm_alloc_page inside page fault) - * Callers must NOT hold: PMM lock (lock ordering: VmSpace -> PMM) - */ -struct VmSpace { - uint64 root_pa; // SV39 root page table physical address - struct rb_root vma_tree; // Red-black tree of VmAreaStructs - Spinlock lock; // Protects vma_tree mutations - lume::atomic<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, refcount=1) - static VmSpace* create(); - - // Increment reference count (for CLONE_VM) - void incref(); - - // Decrement reference count; destroys VmSpace when it reaches 0. - // Call this instead of destroy() directly. - void put(); - - // Destroy VmSpace, unmapping all VMAs and freeing pagetables. - // Internal — called by put() when refcount drops to 0. - void destroy(); -}; - -/* Initialize kernel page table (Phase 1) */ -void vmm_init(); - -/* Switch AP to the kernel page table built by BSP (Phase 1) */ -void vmm_init_ap(); - -/* Map MMIO device region to the active kernel page table */ -void vmm_map_kernel_mmio(uint64 pa, uint64 size); - -/* Query page_map allocation (set by vmm_init, read by pmm_init). - * Returns the PA, byte size, and page count of the vmemmap region. */ -uint64 vmm_get_page_map_pa(); -uint64 vmm_get_page_map_size(); -uint64 vmm_get_num_pages(); - -/* User space memory management (VMA & Demand Paging) */ -int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); -int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len); -int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); - -/* VMA helpers for mmap/mprotect implementations */ -uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align); -bool vma_range_covered(VmSpace* space, uint64 start, uint64 end); - -/* Handle user page fault (lookup VMA -> allocate page -> map) */ -int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause); - -/* Clone an address space for fork() */ -VmSpace* vmm_clone(VmSpace* parent_space); diff --git a/na/include/lume/wait.h b/na/include/lume/wait.h deleted file mode 100644 index 865c1f2..0000000 --- a/na/include/lume/wait.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * POSIX waitpid / wait4 Constants - * Reference: docs/specs/syscall.md §9 - */ - -/* waitpid options */ -inline constexpr int WNOHANG = 1; -inline constexpr int WUNTRACED = 2; -inline constexpr int WCONTINUED = 8; -inline constexpr int WSTOPPED = WUNTRACED; /* POSIX alias */ -inline constexpr int WEXITED = 4; /* POSIX */ -inline constexpr int WNOWAIT = 0x01000000; /* POSIX: peek, don't reap */ - -/* Exit status encoding (Linux-compatible) */ -static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; } -static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; } -static inline bool WIFSIGNALED(int s) { return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; } -static inline int WTERMSIG(int s) { return s & 0x7F; } -static inline bool WIFSTOPPED(int s) { return (s & 0xFF) == 0x7F; } -static inline int WSTOPSIG(int s) { return (s >> 8) & 0xFF; } -static inline bool WIFCONTINUED(int s){ return s == 0xFFFF; } - -/* Encode exit code into wstatus (normal exit) */ -static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; } -static inline int W_STOPCODE(int sig) { return ((sig & 0xFF) << 8) | 0x7F; } -static inline int W_CONTINUED() { return 0xFFFF; } diff --git a/na/include/lume/waitqueue.h b/na/include/lume/waitqueue.h deleted file mode 100644 index 3720cb7..0000000 --- a/na/include/lume/waitqueue.h +++ /dev/null @@ -1,79 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * WaitQueue — blocking synchronization primitive - * - * Supports two usage patterns: - * - * 1. Simple (legacy): wq.sleep() — combines prepare/schedule/finish. - * Suitable for simple cases where no external condition is checked. - * - * 2. Linux-style two-phase (recommended for correctness): - * - * wq.prepare_to_wait(curr, TaskState::Sleeping); - * if (!condition) { - * schedule(); - * } - * wq.finish_wait(curr); - * - * This pattern prevents the "lost wakeup" race: the task is already - * on the wait list (and marked Sleeping) BEFORE checking the condition. - * If a wake_one/wake_all fires between the check and schedule(), the - * task's state will already be set back to Runnable, and schedule() - * will see state == Runnable and simply re-run the task. - * - * Reference: docs/specs/scheduler.md, docs/specs/task.md §5 - */ - -#include <lume/types.h> -#include <lume/list.h> -#include <kernel/sync/spinlock.h> - -struct TaskControlBlock; -enum class TaskState : uint8; - -class WaitQueue { -public: - void init(const char* name = "wq") { - head_.init(); - lock_.init(name); - } - - /* - * Phase 1: Add the task to the wait queue and set its state. - * The task is NOT yet descheduled — the caller must check the - * condition and call schedule() if the condition is still false. - * - * Must be called with interrupts disabled or preemption disabled. - */ - void prepare_to_wait(TaskControlBlock* task, TaskState new_state); - - /* - * Phase 3: Remove the task from the wait queue (if still on it) - * and ensure its state is Runnable. Called after schedule() returns - * (i.e. after the task has been woken up, or if the condition was - * true and schedule() was skipped). - */ - void finish_wait(TaskControlBlock* task); - - /* Legacy convenience: combines prepare_to_wait + schedule + finish_wait. - * If external_lock is provided, it is released AFTER the task is - * added to the wait queue but BEFORE schedule() is called. */ - void sleep(Spinlock* external_lock = nullptr); - - /* Wake the first (oldest) sleeping task. Returns true if a task was woken. */ - bool wake_one(); - - /* Wake all sleeping tasks. Returns the number of tasks woken. */ - int wake_all(); - - /* Check if any tasks are waiting. */ - bool is_empty() const { return head_.is_empty(); } - -private: - list_node head_; - Spinlock lock_; -}; diff --git a/na/kernel/exec.cc b/na/kernel/exec.cc deleted file mode 100644 index 616c0da..0000000 --- a/na/kernel/exec.cc +++ /dev/null @@ -1,659 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * exec.cc — ELF64 binary loader and execve implementation - * - * Loads a static ELF64 executable into a fresh address space, - * constructs the user stack (argc/argv/envp/auxv), and redirects - * the current task's trapframe to begin executing at the ELF entry. - * - * Reference: Linux fs/binfmt_elf.c, fs/exec.c - */ - -#include <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/irq.cc b/na/kernel/irq.cc deleted file mode 100644 index 4de468a..0000000 --- a/na/kernel/irq.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * IRQ Manager Implementation - * - * Lock-free dispatch path for interrupt context safety. - * Registration/unregistration is protected by atomic publish. - * - * Reference: docs/specs/trap.md §3.3 - */ - -#include <lume/console.h> -#include <lume/errno.h> -#include <lume/irq.h> - -/* Global singleton */ -IrqManager g_irq_manager; - -void IrqManager::init() { - for (uint32 i = 0; i < kMaxIrqs; i++) { - entries_[i].handler_ptr.store(0, __ATOMIC_RELAXED); - entries_[i].arg = nullptr; - } -} - -int IrqManager::register_handler(uint32 irq, IrqHandler handler, void *arg) { - if (irq >= kMaxIrqs) - return -EINVAL; - - if (!handler) - return -EINVAL; - - /* Check if already registered (CAS from 0 to handler) */ - uint64 expected = 0; - uint64 desired = reinterpret_cast<uint64>(handler); - - /* Set arg first (before publishing handler) so dispatch sees valid arg */ - entries_[irq].arg = arg; - - if (!entries_[irq].handler_ptr.compare_exchange(expected, desired, - __ATOMIC_ACQ_REL)) { - /* Already registered by someone else */ - return -EBUSY; - } - - return 0; -} - -void IrqManager::unregister_handler(uint32 irq) { - if (irq >= kMaxIrqs) - return; - - /* Clear handler with release semantics */ - entries_[irq].handler_ptr.store(0, __ATOMIC_RELEASE); - entries_[irq].arg = nullptr; -} - -void IrqManager::dispatch(uint32 irq) { - if (irq >= kMaxIrqs) - return; - - /* Load handler with acquire semantics — pairs with register's release */ - uint64 h = entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE); - if (h == 0) { - /* Spurious or unregistered IRQ — silently ignore */ - return; - } - - auto handler = reinterpret_cast<IrqHandler>(h); - handler(irq, entries_[irq].arg); -} - -bool IrqManager::has_handler(uint32 irq) const { - if (irq >= kMaxIrqs) - return false; - return entries_[irq].handler_ptr.load(__ATOMIC_ACQUIRE) != 0; -} - -void IrqManager::set_chip(const IrqChip* chip) { - chip_ = chip; -} - -uint32 IrqManager::claim() const { - return chip_ ? chip_->claim() : 0; -} - -void IrqManager::complete(uint32 irq) const { - if (chip_) chip_->complete(irq); -} - -void IrqManager::init_ap() const { - if (chip_ && chip_->init_ap) chip_->init_ap(); -} - -void IrqManager::enable(uint32 irq) const { - if (chip_ && chip_->enable) chip_->enable(irq); -} - -void IrqManager::disable(uint32 irq) const { - if (chip_ && chip_->disable) chip_->disable(irq); -} - -void IrqManager::set_priority(uint32 irq, uint32 priority) const { - if (chip_ && chip_->set_priority) chip_->set_priority(irq, priority); -} diff --git a/na/kernel/main.cc b/na/kernel/main.cc deleted file mode 100644 index db5843c..0000000 --- a/na/kernel/main.cc +++ /dev/null @@ -1,468 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * LumeOS Kernel Main Entry — Global Initialization Sequence - * - * All CPUs enter kernel_main() after the assembly bootstrap (entry.S) - * has enabled MMU, cleared BSS, and called early_cpp_init(). - * - * BSP (cpu_id == 0) executes the full subsystem initialization DAG. - * APs spin on ap_boot_sync, then perform per-CPU init only. - * - * Boot DAG invariants (docs/specs/linux_abi_mm_plan.md §Boot Sequence): - * - trap_init() MUST precede any allocator (exception safety net) - * - sched_init() MUST precede timer_init() (software before hardware) - * - PID 1 (init) is spawned before entering idle loop - * - * Reference: docs/specs/global_init.md - */ - -#include <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/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> - -// Declared by entry.S: the hart ID that won BSP election -extern "C" uint32 bsp_hart_id; - -// Linker symbol: kernel entry point (physical start address) -extern "C" char _start[]; - -// ============================================================ -// Forward declarations for subsystem init functions. -// Each is implemented by its owning module. On failure they -// call kernel_panic() directly — no error return. -// ============================================================ - -// BSP global init (strict order) -void console_init(); // 1. UART ready; kprintf/panic can output -void fdt_init(uint64 fdt_paddr); // 2. Parse FDT, build device node table -void pmm_init(); // 3. Buddy allocator ready -void slab_init(); // 4. kmem_cache ready; operator new usable -void vmm_init(); // 5. Fine-grained kernel page table; retire early_pgdir -void task_init(); // 6. PID allocator + global task list -void pgrp_init(); // 6.5. Process group hash table -void sched_init(); // 7. Create BSP Idle Task, init ready queues -void timer_init(); // 8. Configure timer interrupt (scheduler tick) -void syscall_init(); // 9. Register syscall jump table -void super_init(); // 10. VFS superblock registry -void ramfs_init(); // 11. RamFS filesystem registration -void vfs_init(); // 12. Mount root filesystem -void cdev_init(); // 13. Character device registry -void console_cdev_init(); // 14. Register console/null/zero cdevs -void devfs_init(); // 15. Create /dev and populate nodes -void tty_init(); // 15.5. TTY subsystem (console TTY + UART RX IRQ) -void populate_rootfs(); // 15.6. Unpack embedded initramfs CPIO into ramfs - -// Boot self-test runner -void selftest_run_all(); - -// AP per-CPU init -void vmm_init_ap(); // Switch to BSP-built kernel page table -void timer_init_ap(); // Start this CPU's timer interrupt -void sched_init_ap(); // Init per-CPU ready queue, create Idle Task - -// kthread_create from task.cc -extern TaskControlBlock *kthread_create(const char *name, void (*entry)(void *), - void *arg, uint8 priority); - -// ============================================================ -// PID 1 — init thread -// ============================================================ -static void init_thread_func(void *) { - printk(KERN_INFO "[init] PID 1 started\n"); - - /* Run post-SMP tests — APs are online by now */ - extern void selftest_smp(); - kprintf("\n========================================\n"); - kprintf(" Post-SMP Test Suite\n"); - kprintf("========================================\n"); - selftest_smp(); - kprintf("========================================\n"); - kprintf(" Post-SMP tests PASSED\n"); - kprintf("========================================\n\n"); - - /* - * Set up init's own VFS context (like Linux kernel_init). - * kthread_create does NOT inherit files/fs, so init creates its own. - */ - TaskControlBlock *self = current_task(); - - if (!self->files) { - self->files = alloc_files_struct(); - if (!self->files) { - printk(KERN_ERR "[init] failed to allocate files_struct\n"); - goto out; - } - } - if (!self->fs) { - self->fs = alloc_fs_struct(); - if (!self->fs) { - printk(KERN_ERR "[init] failed to allocate fs_struct\n"); - goto out; - } - /* Point to the global root filesystem */ - self->fs->root.de = g_root_dentry; - self->fs->root.mnt = g_root_mnt; - self->fs->pwd = self->fs->root; - } - - /* Open /dev/console → fd 0 (stdin), dup to fd 1, 2 */ - { - extern int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); - extern int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); - - int64 fd0 = - sys_openat(static_cast<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. -// ============================================================ -static lume::atomic<uint32> ap_boot_sync; - -// ============================================================ -// kernel_main — C++ entry point for all CPUs -// ============================================================ -extern "C" [[noreturn]] void kernel_main(uint64 cpu_id, uint64 fdt_paddr) { - - if (cpu_id == bsp_hart_id) { - // ======================================================== - // BSP: Absolute single-core period — no locks needed. - // APs are spinning on ap_boot_sync. - // ======================================================== - - uint64 fdt_pa = fdt_paddr; - - // ---- Phase 1: Console + Exception Safety Net ---- - - // 1. Early console setup: discover UART via FDT before ANY output. - console_early_init(fdt_pa); - klog_init(); - - printk("\n"); - printk(KERN_INFO "LumeOS Kernel v0.1\n"); - printk(KERN_INFO "boot: BSP online, cpu_id=%llu\n", cpu_id); - printk(KERN_INFO "boot: higher-half kernel active\n"); - - /* kernel_map filled by arch/riscv/mm/init.cc::setup_vm before MMU - * enable; relocate_kernel has already applied .rela.dyn. */ - printk(KERN_INFO "boot: kimage phys=0x%llx virt=0x%llx size=%llu\n", - static_cast<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) --- - - // 2. Trap handler — install exception vector FIRST. - // Any fault from here on gets a proper panic backtrace - // instead of a silent hang. (Linux: trap_init is in start_kernel - // right after setup_arch.) - trap_init(); - printk(KERN_INFO "init: trap_init done\n"); - - // 3. FDT parsing — make device tree available - fdt_init(fdt_pa); - printk(KERN_INFO "init: fdt_init done\n"); - - // 4. KASLR — seed PRNG and randomise VA layout. - // MUST happen before vmm_init() builds the kernel page table. - { - uint64 kaslr_seed = 0; - fdt_early_get_kaslr_seed(&kaslr_seed); - random_early_init(kaslr_seed); - arch::kaslr_init(); - } - - // Sort exception table before setting RO permissions in vmm_init() - extable_init(); - - // ---- Phase 2: Memory Subsystem (strict cascade) ---- - - // 5. Memblock allocator (Early boot PA allocator) - memblock_init(fdt_pa); - printk(KERN_INFO "init: memblock_init done\n"); - - // 6. Virtual memory manager — builds page table with KASLR'd bases - vmm_init(); - printk(KERN_INFO "init: vmm_init done\n"); - - // 7. Physical memory manager (Buddy) - pmm_init(); - printk(KERN_INFO "init: pmm_init done\n"); - - // 8. Slab allocator — operator new usable after this - slab_init(); - printk(KERN_INFO "init: slab_init done\n"); - - // 9. Page cache (minimal hash table, stable interface for file-backed mmap) - page_cache_init(); - - // 10. FDT Phase 2 — build DeviceNode object tree (needs Slab) - fdt_unflatten(); - printk(KERN_INFO "init: fdt_unflatten done\n"); - - // ---- Phase 3: Task & Scheduler (software state before hardware) ---- - - // 11. Task management (PID allocator + global task list) - task_init(); - printk(KERN_INFO "init: task_init done\n"); - - pgrp_init(); - - sched_init(); - printk(KERN_INFO "init: sched_init done\n"); - - // ---- Phase 3.5: VFS (needs slab + sched) ---- - super_init(); - ramfs_init(); - vfs_init(); - printk(KERN_INFO "init: vfs_init done (ramfs root mounted)\n"); - - // ---- Phase 3.55: Unpack initramfs into root ---- - populate_rootfs(); - - // ---- Phase 3.6: Device nodes ---- - cdev_init(); - console_cdev_init(); - devfs_init(); - printk(KERN_INFO "init: devfs done\n"); - - // ---- Phase 4: Hardware Drivers & Interrupts ---- - - // 13. Hardware Drivers Discovery (Interrupt Controllers, etc) - driver_probe_all(); - printk(KERN_INFO "init: driver_probe_all done\n"); - - // 13.5. TTY subsystem — needs PLIC (driver_probe_all) for UART RX IRQ - tty_init(); - printk(KERN_INFO "init: tty_init done\n"); - - // 14. Timer interrupt (scheduler tick source) - // Safe now: scheduler is fully initialized. - timer_init(); - printk(KERN_INFO "init: timer_init done\n"); - - // 15. Syscall jump table - syscall_init(); - printk(KERN_INFO "init: syscall_init done\n"); - - // 16. SMP / IPI infrastructure - smp_init(); - arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI - arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - - // ---- Phase 5: Self-test & Launch ---- - - // 17. Boot self-tests — verify subsystem correctness. - // Tests that create kthreads must wait() for them (freeing PIDs). - selftest_run_all(); - - // 18. Spawn PID 1 (init) — the first persistent task. - // Like Linux's rest_init() → user_mode_thread(kernel_init). - // All test PIDs have been freed, so PID 1 is available. - TaskControlBlock *init_task = - kthread_create("init", init_thread_func, nullptr, 0); - if (init_task) { - init_task->flags |= TF_INIT; - - /* Init is session and process-group leader: sid = pgid = pid. - * Allocate signal_struct AND sighand_struct eagerly so every - * descendant (created via task_clone) inherits a real identity - * and a real lock object — signal sending must take siglock and - * cannot tolerate a null sighand. */ - init_task->signal = alloc_signal_struct(init_task->pid, init_task); - if (!init_task->signal) { - printk(KERN_ERR "boot: FAILED to allocate init signal_struct\n"); - } - init_task->sighand = alloc_sighand(); - if (!init_task->sighand) { - printk(KERN_ERR "boot: FAILED to allocate init sighand\n"); - } - - /* Publish g_init_task so reparenting paths can find the foster - * parent when other tasks exit before init takes over user space. */ - extern TaskControlBlock* g_init_task; - g_init_task = init_task; - - printk(KERN_INFO "boot: init thread created (pid=%d sid=%d pgid=%d)\n", - init_task->pid, - init_task->signal ? init_task->signal->sid : -1, - init_task->signal ? init_task->signal->pgid : -1); - } else { - printk(KERN_ERR "boot: FAILED to create init thread!\n"); - } - - - BOOT_STAGE(5, "core kernel init"); - printk(KERN_INFO "boot: BSP init complete, releasing APs\n"); - - // Release APs with RELEASE semantics: - // guarantees all global data is visible to APs. - ap_boot_sync.store(1, __ATOMIC_RELEASE); - - // Start AP harts via SBI HSM extension. - // OpenSBI v1.0+ parks non-boot harts in STOPPED state. - // They must be explicitly started with sbi_hart_start(). - // - // kernel_map.phys_addr was filled by setup_vm in arch/riscv/mm/init.cc - // before MMU enable — it's the runtime PA at which the kernel was - // loaded. APs start at the same PA as the BSP. - { - uint64 start_pa = kernel_map.phys_addr; - if (start_pa == 0) { - /* Defensive fallback if setup_vm hasn't filled the field. */ - start_pa = - boot_va_to_pa(virt_addr(reinterpret_cast<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, which will pick up PID 1. - arch::cpu::intr_on(); - - printk(KERN_INFO "boot: BSP entering idle loop\n"); - - } else { - // ======================================================== - // AP: spin until BSP finishes global init - // ======================================================== - // DEBUG (commented out — uncomment for SMP debugging): - // arch::sbi::sbi_ecall(1, 0, '1'); // AP entered kernel_main - - while (ap_boot_sync.load(__ATOMIC_ACQUIRE) == 0) - ; - - // arch::sbi::sbi_ecall(1, 0, '2'); // AP passed ap_boot_sync - - // --- Per-CPU init (must happen before any printk!) --- - // early_pgdir doesn't map UART MMIO, so we MUST switch to - // BSP's kernel page table before any console output. - vmm_init_ap(); // Switch to BSP-built kernel page table - - // arch::sbi::sbi_ecall(1, 0, '3'); // vmm_init_ap done - - trap_init_ap(); // Install stvec on this CPU - - // arch::sbi::sbi_ecall(1, 0, '4'); // trap_init_ap done - - printk(KERN_INFO "boot: AP online, cpu_id=%llu\n", cpu_id); - - sched_init_ap(); // Init per-CPU RunQueue, create Idle Task - - // arch::sbi::sbi_ecall(1, 0, '5'); // sched_init_ap done - - timer_init_ap(); // Start this CPU's timer interrupt - arch::cpu::software_intr_on(); // Enable S-mode software interrupts for IPI - arch::cpu::external_intr_on(); // Enable S-mode external interrupts for PLIC (UART) - - // AP is now a fully functional scheduler CPU. - // Enable interrupts — first timer tick preempts into idle loop. - arch::cpu::intr_on(); - - printk(KERN_INFO "boot: AP cpu_id=%llu fully initialized\n", cpu_id); - } - - // All CPUs reach here. - // The "current task" is the boot stub (bsp_task or ap_task, pid=-1). - // We must hand off to the scheduler's idle task by calling schedule(). - // Without this, the BSP runs as bsp_task (O1 class, timeslice=20 ticks) - // instead of the idle task (which yields immediately when any task is - // runnable). That delays PID 1 by up to 200ms and can stall entirely - // if no timer tick fires before the first WFI. - { - TaskControlBlock *self = current_task(); - if (self) { - self->state = TaskState::Dead; // Boot stub is done; don't re-enqueue - } - schedule(); // Switch to idle task (or init if it's already runnable) - } - - // Should not reach here — schedule() switches away permanently. - // But just in case, spin with WFI as a safety net. - while (true) { - arch::cpu::halt_until_interrupt(); - } -} diff --git a/na/kernel/module.mk b/na/kernel/module.mk deleted file mode 100644 index 754a14c..0000000 --- a/na/kernel/module.mk +++ /dev/null @@ -1,26 +0,0 @@ -# kernel/module.mk -SRCS_CC += kernel/main.cc -SRCS_CC += kernel/sync/spinlock.cc -SRCS_CC += kernel/sync/mutex.cc -SRCS_CC += kernel/trap.cc -SRCS_CC += kernel/irq.cc -SRCS_CC += kernel/sched_core.cc -SRCS_CC += kernel/sched_class_o1.cc -SRCS_CC += kernel/timer.cc -SRCS_CC += kernel/pid.cc -SRCS_CC += kernel/pgrp.cc -SRCS_CC += kernel/task.cc -SRCS_CC += kernel/syscall.cc -SRCS_CC += kernel/sys_process.cc -SRCS_CC += kernel/sys_mm.cc -SRCS_CC += kernel/sys_info.cc -SRCS_CC += kernel/sys_fs.cc -SRCS_CC += kernel/uaccess.cc -SRCS_CC += kernel/waitqueue.cc -SRCS_CC += kernel/signal.cc -SRCS_CC += kernel/sys_signal.cc -SRCS_CC += kernel/sys_misc.cc -SRCS_CC += kernel/exec.cc -SRCS_CC += kernel/poll.cc -SRCS_CC += kernel/ptrace.cc -SRCS_CC += kernel/smp.cc diff --git a/na/kernel/pgrp.cc b/na/kernel/pgrp.cc deleted file mode 100644 index e5b382c..0000000 --- a/na/kernel/pgrp.cc +++ /dev/null @@ -1,148 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Process Group / Session Implementation - * - * Hash table of signal_struct objects keyed by pgid. Lifecycle of - * signal_struct mirrors Linux: one per thread group, refcounted across - * CLONE_THREAD siblings, freed when the last task in the group exits. - * - * Reference: Linux kernel/pid.c, kernel/exit.c - */ - -#include <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 deleted file mode 100644 index 04b846b..0000000 --- a/na/kernel/pid.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * PID Allocator — Bitmap-based O(1) block search - * Reference: docs/specs/task.md §3.2 - */ - -#include <lume/pid.h> - -void PidAllocator::init() -{ - for (int i = 0; i < kBitmapWords; i++) - bitmap_[i] = 0; - next_hint_ = 1; - bitmap_[0] |= 0x1ULL; /* Reserve PID 0 (idle/swapper) only. - * PID 1 (init) is allocated normally — the first - * kthread_create() call gets it, matching Linux. */ -} - -int32 PidAllocator::alloc() -{ - LockGuard guard(lock_); - - int32 word_start = next_hint_ / 64; - - for (int32 passes = 0; passes < kBitmapWords; passes++) { - int32 w = (word_start + passes) % kBitmapWords; - uint64 free_bits = ~bitmap_[w]; - if (free_bits == 0) - continue; - - int bit = 0; - uint64 v = free_bits; - while ((v & 1ULL) == 0) { v >>= 1; bit++; } - int32 pid = w * 64 + bit; - - if (pid >= kMaxPid) - continue; - - bitmap_[w] |= (1ULL << bit); - next_hint_ = pid + 1; - if (next_hint_ >= kMaxPid) - next_hint_ = 2; - - return pid; - } - - return -1; -} - -void PidAllocator::free(int32 pid) -{ - if (pid < 0 || pid >= kMaxPid) - return; - - LockGuard guard(lock_); - int32 w = pid / 64; - int32 bit = pid % 64; - bitmap_[w] &= ~(1ULL << bit); -} - -PidAllocator g_pid_alloc; diff --git a/na/kernel/poll.cc b/na/kernel/poll.cc deleted file mode 100644 index 3674491..0000000 --- a/na/kernel/poll.cc +++ /dev/null @@ -1,250 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_ppoll — POSIX ppoll() implementation. - * - * Linux fs/select.c:do_sys_poll() pattern, simplified for our needs: - * - Two-phase loop: "register-then-check". First pass calls every - * f_op->poll() with a real poll_table so drivers can register their - * wait queues; if any fd is already ready we return immediately. - * Otherwise schedule() until a wq fires (or timeout/signal), then - * re-check with poll_table=nullptr (probe-only). - * - * Limitations vs. Linux: - * - Only a single wait queue per ppoll call is tracked (the first one - * a driver hands to poll_wait). Multi-fd polls work, but only the - * first wq's wake actually re-runs the loop; other fds becoming - * ready won't be noticed until the timer fires or another wake. - * BusyBox interactive shell polls one fd (stdin), so this matches. - * - * Reference: Linux fs/select.c - */ - -#include <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 deleted file mode 100644 index 3280c07..0000000 --- a/na/kernel/ptrace.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * PtRegs ↔ TrapFrame conversion - * - * This is the ONLY file that knows both the internal TrapFrame layout - * and the Linux-ABI PtRegs layout. When TrapFrame is restructured - * (e.g., to embed pt_regs directly), only this file needs to change. - */ - -#include <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 deleted file mode 100644 index 28c693c..0000000 --- a/na/kernel/sched_class_o1.cc +++ /dev/null @@ -1,81 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * O(1) Priority Bitmap Scheduling Class - */ - -#include <lume/sched.h> -#include <lume/sched_class.h> -#include <lume/task.h> -#include <lume/bitops.h> -#include <kernel/sched_internal.h> - -static void o1_init_rq(RunQueue* rq) -{ - rq->o1().init(); -} - -static void o1_enqueue_task(RunQueue* rq, TaskControlBlock* task) -{ - rq->o1().enqueue(task); - rq->inc_running(); -} - -static void o1_dequeue_task(RunQueue* rq, TaskControlBlock* task) -{ - rq->o1().dequeue(task); - rq->dec_running(); -} - -static TaskControlBlock* o1_pick_next_task(RunQueue* rq) -{ - return rq->o1().pick_next(); -} - -static void o1_put_prev_task(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} - -int32 priority_to_timeslice(uint8 priority) -{ - int32 ts = 20 - static_cast<int32>(priority) * 19 / 31; - if (ts < 1) ts = 1; - return ts; -} - -static void o1_task_tick(RunQueue* rq, TaskControlBlock* curr) -{ - if (curr == rq->idle()) { - /* Idle task: if anything is runnable, yield immediately */ - if (rq->nr_running() > 0) { - rq->set_need_resched(true); - } - return; - } - - curr->time_slice--; - if (curr->time_slice <= 0) { - curr->time_slice = priority_to_timeslice(curr->priority); - rq->set_need_resched(true); - } -} - -static void o1_yield_task(RunQueue* rq, TaskControlBlock* curr) -{ - if (curr == rq->idle()) - return; - - curr->time_slice = 0; - rq->set_need_resched(true); -} - -const SchedClass g_sched_class_o1 = { - .name = "o1", - .next = &g_sched_class_idle, - .init_rq = o1_init_rq, - .enqueue_task = o1_enqueue_task, - .dequeue_task = o1_dequeue_task, - .pick_next_task = o1_pick_next_task, - .put_prev_task = o1_put_prev_task, - .task_tick = o1_task_tick, - .yield_task = o1_yield_task, -}; diff --git a/na/kernel/sched_core.cc b/na/kernel/sched_core.cc deleted file mode 100644 index c0e068e..0000000 --- a/na/kernel/sched_core.cc +++ /dev/null @@ -1,571 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Scheduler Core - * - * The core walks sched_class chain to pick the next task. Each class - * owns its own runqueue data and policy. This file owns the per-CPU - * runqueue array and the scheduling state machine. - */ - -#include <lume/sched.h> -#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> - -/* Assembly trampoline symbol (MD layer) */ -extern "C" void kthread_entry_trampoline(); - -/* Per-CPU runqueue array */ -static RunQueue g_rq[kMaxCpus]; - -/* sched_class chain head (highest priority first) */ -static const SchedClass* g_sched_class_head = &g_sched_class_o1; - -/* ======================================================================== - * Idle thread - * ======================================================================== */ - -static void idle_thread_func(void* /*arg*/) -{ - while (true) { - if (g_rq[arch::cpu::id()].need_resched()) { - schedule(); - } - arch::cpu::intr_on(); - arch::cpu::halt_until_interrupt(); - } -} - - -/* ======================================================================== - * Idle class (always returns idle task) - * ======================================================================== */ - -static void idle_init_rq(RunQueue* /*rq*/) {} -static void idle_enqueue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static void idle_dequeue(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static TaskControlBlock* idle_pick_next(RunQueue* rq) { return rq->idle(); } -static void idle_put_prev(RunQueue* /*rq*/, TaskControlBlock* /*task*/) {} -static void idle_task_tick(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} -static void idle_yield(RunQueue* /*rq*/, TaskControlBlock* /*curr*/) {} - -const SchedClass g_sched_class_idle = { - .name = "idle", - .next = nullptr, - .init_rq = idle_init_rq, - .enqueue_task = idle_enqueue, - .dequeue_task = idle_dequeue, - .pick_next_task = idle_pick_next, - .put_prev_task = idle_put_prev, - .task_tick = idle_task_tick, - .yield_task = idle_yield, -}; - -/* ======================================================================== - * Internal helpers - * ======================================================================== */ - -static RunQueue* rq_for_cpu(uint32 cpu_id) -{ - return &g_rq[cpu_id]; -} - -static RunQueue* this_rq() -{ - return rq_for_cpu(arch::cpu::id()); -} - -static TaskControlBlock* pick_next_task(RunQueue* rq) -{ - for (const SchedClass* c = g_sched_class_head; c; c = c->next) { - TaskControlBlock* t = c->pick_next_task(rq); - if (t) { - return t; - } - } - return rq->idle(); -} - -static const char* idle_name_for_cpu(uint32 cpu) -{ - static char names[kMaxCpus][16]; - if (cpu >= kMaxCpus) { - kernel_panic("sched: cpu_id out of range"); - } - - if (names[cpu][0] == '\0') { - ksnprintf(names[cpu], sizeof(names[cpu]), "idle/%u", cpu); - } - return names[cpu]; -} - -/* ======================================================================== - * Kernel thread entry — MI-layer function called by MD trampoline - * ======================================================================== */ - -/* Forward declaration — C++ linkage (task.cc) */ -void task_exit(int code); - -extern "C" void kthread_entry(void (*entry)(void*), void* arg) -{ - RunQueue* rq = this_rq(); - rq->lock().release(); - - arch::cpu::intr_on(); - - entry(arg); - - /* Entry function returned — clean exit (release PID, stack, TCB) */ - task_exit(0); - __builtin_unreachable(); -} - -/* ======================================================================== - * fork_child_entry — called from ret_from_fork assembly - * ======================================================================== */ -extern "C" void userret(arch::TrapFrame* tf); -extern "C" void trap_vector(); - -extern "C" void fork_child_entry(arch::TrapFrame* tf) -{ - RunQueue* rq = this_rq(); - rq->lock().release(); - - /* Set stvec to trap_vector for user-mode traps, then return to user */ - arch::trap::set_vector(reinterpret_cast<uint64>(trap_vector)); - userret(tf); - - __builtin_unreachable(); -} - -/* ======================================================================== - * alloc_task — canonical TCB allocator (defined in task.cc) - * - * Single point of initialization for TaskControlBlock. Ensures all - * fields are properly zeroed when the struct gains new members. - * ======================================================================== */ - -extern TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags, uint8 cpu); - -/* ======================================================================== - * Public API: current_task() - * ======================================================================== */ - -TaskControlBlock* current_task() -{ - return this_rq()->current(); -} - -/* ======================================================================== - * Public API: sched_enqueue / sched_dequeue - * ======================================================================== */ - -void sched_enqueue(TaskControlBlock* task) -{ - RunQueue* rq = rq_for_cpu(task->cpu_id); - LockGuard guard(rq->lock()); - - if (!task->sched_class) { - task->sched_class = &g_sched_class_o1; - } - if (!task->sched_entity) { - task->sched_entity = &task->run_link; - } - - task->sched_class->enqueue_task(rq, task); - - if (rq->current() == rq->idle() || task->priority < rq->current()->priority) { - rq->set_need_resched(true); - } -} - -void sched_dequeue(TaskControlBlock* task) -{ - RunQueue* rq = rq_for_cpu(task->cpu_id); - LockGuard guard(rq->lock()); - - if (!task->sched_class) { - task->sched_class = &g_sched_class_o1; - } - task->sched_class->dequeue_task(rq, task); -} - -/* ======================================================================== - * schedule() — main scheduling entry point - * ======================================================================== */ - -void schedule() -{ - RunQueue* rq = this_rq(); - rq->lock().acquire(); - - TaskControlBlock* prev = rq->current(); - - if (prev && prev->state == TaskState::Running) { - prev->state = TaskState::Runnable; - prev->sched_class->enqueue_task(rq, prev); - } else if (prev) { - prev->sched_class->put_prev_task(rq, prev); - } - - TaskControlBlock* next = pick_next_task(rq); - - if (prev == next) { - /* We picked the same task. It must be Running again. */ - next->state = TaskState::Running; - next->sched_class->dequeue_task(rq, next); - rq->clear_need_resched(); - rq->lock().release(); - return; - } - - if (next != rq->idle()) { - next->sched_class->dequeue_task(rq, next); - } - - next->state = TaskState::Running; - rq->set_current(next); - rq->clear_need_resched(); - - /* Switch address space (satp) if the new task has a different mm. - * Kernel threads (mm == nullptr) borrow the previous task's mm, - * so no satp switch is needed — this matches Linux's lazy TLB behavior. */ - { - VmSpace* prev_mm = prev ? prev->mm : nullptr; - VmSpace* next_mm = next->mm; - uint32 cpu = static_cast<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(); -} - -/* ======================================================================== - * sched_tick() — called from timer interrupt (MI trap layer) - * ======================================================================== */ - -void sched_tick() -{ - RunQueue* rq = this_rq(); - { - LockGuard guard(rq->lock()); - TaskControlBlock* curr = rq->current(); - if (!curr) return; - curr->sched_class->task_tick(rq, curr); - } - - /* Periodic load balancing — runs without holding rq lock */ - sched_balance(); -} - -/* ======================================================================== - * sched_check_preempt() — called from trap return path - * - * Only preempts if: - * 1. need_resched flag is set (a higher-priority task is ready) - * 2. preempt_count == 0 (no spinlocks held, safe to context switch) - * ======================================================================== */ - -void sched_check_preempt() -{ - RunQueue* rq = this_rq(); - if (!rq->need_resched()) return; - - TaskControlBlock* curr = current_task(); - if (curr && curr->preempt_count > 0) { - /* Cannot preempt — caller is in a critical section. - * The preemption will fire when the last lock is released. */ - return; - } - schedule(); -} - -/* ======================================================================== - * sched_yield() — voluntary yield - * ======================================================================== */ - -void sched_yield() -{ - RunQueue* rq = this_rq(); - { - LockGuard guard(rq->lock()); - TaskControlBlock* curr = rq->current(); - if (curr && curr != rq->idle()) { - curr->sched_class->yield_task(rq, curr); - rq->set_need_resched(true); - } - } - schedule(); -} - -/* ======================================================================== - * sched_init() — BSP initialization - * ======================================================================== */ - -void sched_init() -{ - for (int i = 0; i < kMaxCpus; i++) { - RunQueue* rq = &g_rq[i]; - rq->init(static_cast<uint32>(i)); - - g_sched_class_o1.init_rq(rq); - g_sched_class_idle.init_rq(rq); - } - - g_sched_class_head = &g_sched_class_o1; - - uint64 bsp_cpu = arch::cpu::id(); - uint8 bsp_cpu8 = static_cast<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 - - 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[bsp_cpu].set_idle(idle0); - - TaskControlBlock* bsp_task = alloc_task("kernel_main", 0, TF_KTHREAD, bsp_cpu8); - if (!bsp_task) kernel_panic("sched_init: failed to create bsp_task"); - - bsp_task->pid = -1; - bsp_task->tgid = -1; - bsp_task->state = TaskState::Running; - - g_rq[bsp_cpu].set_current(bsp_task); -} - -/* ======================================================================== - * sched_init_ap() — AP per-CPU initialization - * ======================================================================== */ - -void sched_init_ap() -{ - uint64 cpu = arch::cpu::id(); - - const char* name = idle_name_for_cpu(static_cast<uint32>(cpu)); - - 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"); - - idle->pid = 0; - idle->tgid = 0; - idle->sched_class = &g_sched_class_idle; - idle->sched_entity = nullptr; - - uint64 stack_top = idle->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - idle->context = {}; - idle->context.ra = reinterpret_cast<uint64>(kthread_entry_trampoline); - idle->context.sp = stack_top; - idle->context.s[0] = reinterpret_cast<uint64>(idle_thread_func); - idle->context.s[1] = 0; - - g_rq[cpu].set_idle(idle); - - TaskControlBlock* ap_task = alloc_task("ap_boot", 0, TF_KTHREAD, - static_cast<uint8>(cpu)); - if (!ap_task) kernel_panic("sched_init_ap: failed to create ap_task"); - - ap_task->pid = -1; - ap_task->tgid = -1; - ap_task->state = TaskState::Running; - - g_rq[cpu].set_current(ap_task); -} - -/* ======================================================================== - * sched_nr_running() — runnable task count for a CPU - * ======================================================================== */ - -uint32 sched_nr_running(uint32 cpu_id) -{ - if (cpu_id >= kMaxCpus) return 0; - return g_rq[cpu_id].o1().nr_running(); -} - -/* ======================================================================== - * sched_migrate_task() — move a task between CPUs - * - * Locks are acquired in cpu_id order to prevent ABBA deadlock. - * The task must be Runnable (in a runqueue) and not currently running. - * ======================================================================== */ - -int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu) -{ - if (!task || target_cpu >= kMaxCpus) return -22; /* -EINVAL */ - - uint8 src_cpu = task->cpu_id; - if (src_cpu == target_cpu) return 0; /* already there */ - - /* Cannot migrate idle or currently-running tasks */ - if (task->flags & TF_IDLE) return -22; - - RunQueue* src_rq = rq_for_cpu(src_cpu); - RunQueue* dst_rq = rq_for_cpu(target_cpu); - - /* Ordered lock acquisition: always lock lower cpu_id first */ - if (src_cpu < target_cpu) { - src_rq->lock().acquire(); - dst_rq->lock().acquire(); - } else { - dst_rq->lock().acquire(); - src_rq->lock().acquire(); - } - - int ret = -22; - - /* Double-check: task must not be current on either CPU */ - if (task != src_rq->current() && task->state == TaskState::Runnable) { - /* Dequeue from source */ - task->sched_class->dequeue_task(src_rq, task); - - /* Update cpu_id and enqueue on destination */ - task->cpu_id = target_cpu; - task->sched_class->enqueue_task(dst_rq, task); - - /* Signal target CPU to reschedule */ - dst_rq->set_need_resched(true); - ret = 0; - } - - /* Release in reverse order */ - if (src_cpu < target_cpu) { - dst_rq->lock().release(); - src_rq->lock().release(); - } else { - src_rq->lock().release(); - dst_rq->lock().release(); - } - - /* If migration succeeded, send reschedule IPI to target */ - if (ret == 0) { - smp_send_reschedule(target_cpu); - } - - return ret; -} - -/* ======================================================================== - * sched_balance() — periodic load balancer (pull model) - * - * Called from sched_tick() context. Looks at all other CPUs and if any - * has 2+ more runnable tasks than the local CPU, pulls one task. - * This is a simplified Linux-style load_balance(). - * ======================================================================== */ - -void sched_balance() -{ - uint32 this_cpu = static_cast<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/sched_internal.h b/na/kernel/sched_internal.h deleted file mode 100644 index 514b613..0000000 --- a/na/kernel/sched_internal.h +++ /dev/null @@ -1,115 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -#include <lume/types.h> -#include <lume/list.h> -#include <lume/sched.h> -#include <lume/config.h> -#include <lume/bitops.h> -#include <lume/task.h> -#include <kernel/sync/spinlock.h> - -class O1RunQueue { -public: - void init() { - bitmap_ = 0; - nr_running_ = 0; - for (int p = 0; p < kNumPriorities; p++) { - queues_[p].init(); - } - } - - void enqueue(TaskControlBlock* task) { - int prio = task->priority; - - list_node* head = &queues_[prio]; - task->run_link.prev = head->prev; - task->run_link.next = head; - head->prev->next = &task->run_link; - head->prev = &task->run_link; - - bitmap_ |= (1U << prio); - nr_running_++; - - task->state = TaskState::Runnable; - } - - void dequeue(TaskControlBlock* task) { - int prio = task->priority; - - task->run_link.prev->next = task->run_link.next; - task->run_link.next->prev = task->run_link.prev; - task->run_link.init(); - - if (queues_[prio].is_empty()) { - bitmap_ &= ~(1U << prio); - } - - if (nr_running_ > 0) { - nr_running_--; - } - } - - TaskControlBlock* pick_next() const { - if (bitmap_ == 0) { - return nullptr; - } - - int prio = lume::ctz32(bitmap_); - list_node* node = queues_[prio].next; - return list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node); - } - - uint32 nr_running() const { return nr_running_; } - -private: - uint32 bitmap_ = 0; - list_node queues_[kNumPriorities]; - uint32 nr_running_ = 0; -}; - -class RunQueue { -public: - void init(uint32 cpu_id) { - lock_.init("RunQueue"); - cpu_id_ = cpu_id; - nr_running_ = 0; - need_resched_ = false; - current_ = nullptr; - idle_ = nullptr; - } - - Spinlock& lock() { return lock_; } - - uint32 cpu_id() const { return cpu_id_; } - - uint32 nr_running() const { return nr_running_; } - void inc_running() { nr_running_++; } - void dec_running() { if (nr_running_ > 0) nr_running_--; } - - bool need_resched() const { return need_resched_; } - void set_need_resched(bool v) { need_resched_ = v; } - void clear_need_resched() { need_resched_ = false; } - - TaskControlBlock* current() const { return current_; } - void set_current(TaskControlBlock* task) { current_ = task; } - - TaskControlBlock* idle() const { return idle_; } - void set_idle(TaskControlBlock* task) { idle_ = task; } - - O1RunQueue& o1() { return o1_; } - const O1RunQueue& o1() const { return o1_; } - -private: - Spinlock lock_; - uint32 cpu_id_ = 0; - uint32 nr_running_ = 0; - bool need_resched_ = false; - - TaskControlBlock* current_ = nullptr; - TaskControlBlock* idle_ = nullptr; - - O1RunQueue o1_; -}; diff --git a/na/kernel/signal.cc b/na/kernel/signal.cc deleted file mode 100644 index 53d26da..0000000 --- a/na/kernel/signal.cc +++ /dev/null @@ -1,442 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Signal Delivery — Queue, Dequeue, Default Actions, and do_signal - * - * Implements the core signal infrastructure: - * - send_signal(): enqueue a signal to a task's pending set - * - force_sig_fault(): fault-induced signal (trap handler API) - * - do_signal(): called on return-to-user, delivers pending signals - * - next_signal(): dequeue the highest-priority deliverable signal - * - * Signal frame construction (arch-specific) is in arch/riscv/signal.cc. - * - * Reference: Linux kernel/signal.c - */ - -#include <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 deleted file mode 100644 index 1a79ac3..0000000 --- a/na/kernel/smp.cc +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * smp.cc — SMP IPI implementation - * - * Sending side: atomically set reason bits in target CPU's IpiData, - * then invoke arch-specific IPI delivery (SBI ecall on RISC-V). - * - * Receiving side: smp_handle_ipi() is called from the trap handler - * when an S-mode software interrupt arrives. It atomically consumes - * all pending reason bits and dispatches to the appropriate handler. - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.2 - */ - -#include <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/cpu_state.h b/na/kernel/sync/cpu_state.h deleted file mode 100644 index 1f7dde0..0000000 --- a/na/kernel/sync/cpu_state.h +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Per-CPU Interrupt Nesting Tracker - * - * Tracks spinlock nesting depth on each CPU to ensure interrupts - * are only restored when the last spinlock is released. - * - * BSS-segment global array; zero-initialized by entry.S BSS clear. - * Reference: docs/specs/spinlock.md §3.1 - */ - -#include <lume/types.h> -#include <lume/config.h> - -namespace sync { - -struct CpuSyncState { - int lock_depth; // Number of spinlocks held by this CPU - bool intr_was_on; // Were interrupts enabled before the first lock? -}; - -inline constexpr uint64 kInvalidCpuId = ~0ULL; - -// BSS-segment global array, zero-initialized by entry.S -extern CpuSyncState g_cpu_sync_states[kMaxCpus]; - -// Internal API — called only by Spinlock::acquire/release -void push_intr(); -void pop_intr(); - -} // namespace sync diff --git a/na/kernel/sync/mutex.cc b/na/kernel/sync/mutex.cc deleted file mode 100644 index c4d3e9d..0000000 --- a/na/kernel/sync/mutex.cc +++ /dev/null @@ -1,156 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Mutex — Linux-style sleeping mutex (no PI). - * - * Implementation notes (mirroring kernel/locking/mutex.c, simplified): - * - * - Fast path (uncontended): wait_lock_ -> see owner_==null -> claim -> unlock. - * - Slow path (contended): enqueue MutexWaiter on stack, sleep, race for - * ownership after each wake. unlock() wakes only the head waiter. - * - No optimistic spin, no handoff anti-starvation, no interruptible wait. - * These belong to follow-up work (see mutex.md §5). - * - * Reference: docs/specs/mutex.md - */ - -#include <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 deleted file mode 100644 index f649fe7..0000000 --- a/na/kernel/sync/spinlock.cc +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Spinlock Implementation - * - * TTAS (Test-and-Test-and-Set) spinlock with: - * - Acquire: __ATOMIC_ACQUIRE memory ordering - * - Release: __ATOMIC_RELEASE memory ordering - * - Automatic interrupt disable/enable via push_intr/pop_intr - * - Dead-lock and invalid-release detection via kernel_panic - * - * Reference: docs/specs/spinlock.md - */ - -#include "spinlock.h" -#include "cpu_state.h" -#include <arch/cpu.h> -#include <lume/klog.h> -#include <lume/task.h> - -// ============================================================ -// Per-CPU sync state (BSS zero-initialized) -// ============================================================ -namespace sync { - -CpuSyncState g_cpu_sync_states[kMaxCpus]; - -void push_intr() { - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - auto& state = g_cpu_sync_states[arch::cpu::id()]; - if (state.lock_depth == 0) { - state.intr_was_on = was_on; - } - state.lock_depth++; -} - -void pop_intr() { - auto& state = g_cpu_sync_states[arch::cpu::id()]; - state.lock_depth--; - - if (state.lock_depth == 0 && state.intr_was_on) { - arch::cpu::intr_on(); - } -} - -} // namespace sync - -// ============================================================ -// Spinlock methods -// ============================================================ - -void Spinlock::init(const char* name) { - name_ = name; - locked_.store(0, __ATOMIC_RELAXED); - cpu_id_ = sync::kInvalidCpuId; -} - -void Spinlock::acquire() { -#ifdef DEBUG - // Optional: detect interrupt-state leakage - if (arch::cpu::intr_enabled()) { - auto& state = sync::g_cpu_sync_states[arch::cpu::id()]; - if (state.lock_depth > 0) { - kernel_panic("spinlock: interrupts enabled while holding lock"); - } - } -#endif - - // Step 1: disable interrupts, track nesting depth - sync::push_intr(); - - // Step 2: dead-lock detection (same CPU re-acquiring) - if (is_held_by_current_cpu()) { - kernel_panic("spinlock: double lock", name_); - } - - // Step 3: TTAS spin loop - while (true) { - // Outer: spin on local cache copy (no bus traffic) - while (locked_.load(__ATOMIC_RELAXED)) - ; - // Inner: attempt atomic exchange with acquire semantics - if (locked_.exchange(1, __ATOMIC_ACQUIRE) == 0) - break; - } - - // Step 4: record owner - cpu_id_ = arch::cpu::id(); - - // Step 5: bump preempt_count — prevents schedule() from preempting - TaskControlBlock* curr = current_task(); - if (curr) curr->preempt_count++; -} - -void Spinlock::release() { - // Step 1: verify caller is the holder - if (!is_held_by_current_cpu()) { - kernel_panic("spinlock: invalid release", name_); - } - - // Step 2: clear owner - cpu_id_ = sync::kInvalidCpuId; - - // Step 3: release the lock with release semantics - locked_.store(0, __ATOMIC_RELEASE); - - // Step 4: drop preempt_count - TaskControlBlock* curr = current_task(); - if (curr) curr->preempt_count--; - - // Step 5: restore interrupt state if this was the last lock - sync::pop_intr(); -} - -bool Spinlock::is_held_by_current_cpu() const { - return cpu_id_ == arch::cpu::id(); -} diff --git a/na/kernel/sync/spinlock.h b/na/kernel/sync/spinlock.h deleted file mode 100644 index a1369d1..0000000 --- a/na/kernel/sync/spinlock.h +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * Spinlock & LockGuard - * - * Provides mutual exclusion for SMP kernels using TTAS - * (Test-and-Test-and-Set) with acquire/release memory ordering. - * Interrupt disable/enable is managed automatically via push_intr/pop_intr. - * - * Reference: docs/specs/spinlock.md - */ - -#include <lume/atomic.h> -#include <lume/types.h> -#include "cpu_state.h" - -class Spinlock { -public: - // Default: BSS zero is safe. Call init() to give the lock a name. - Spinlock() = default; - - // Explicit initialization (for global objects or Placement New) - void init(const char* name); - - // Non-copyable, non-movable (lock identity is its address) - Spinlock(const Spinlock&) = delete; - Spinlock& operator=(const Spinlock&) = delete; - Spinlock(Spinlock&&) = delete; - Spinlock& operator=(Spinlock&&) = delete; - - void acquire(); - void release(); - bool is_held_by_current_cpu() const; - -private: - lume::atomic<uint32> locked_; // 0 = free, 1 = held - const char* name_ = "uninit"; // Lock name (for panic messages) - uint64 cpu_id_ = sync::kInvalidCpuId; // Holder CPU ID -}; - -class LockGuard { -public: - explicit LockGuard(Spinlock& lock) : lock_(lock) { - lock_.acquire(); - } - - ~LockGuard() { - lock_.release(); - } - - LockGuard(const LockGuard&) = delete; - LockGuard& operator=(const LockGuard&) = delete; - LockGuard(LockGuard&&) = delete; - LockGuard& operator=(LockGuard&&) = delete; - -private: - Spinlock& lock_; -}; diff --git a/na/kernel/sys_fs.cc b/na/kernel/sys_fs.cc deleted file mode 100644 index 1d26fdd..0000000 --- a/na/kernel/sys_fs.cc +++ /dev/null @@ -1,679 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_fs.cc — Filesystem-related syscall handlers - * - * Implements: openat, close, read, write, lseek, fstat, getdents64, - * dup, dup3, getcwd, chdir, mkdirat, unlinkat - */ - -#include <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_info.cc b/na/kernel/sys_info.cc deleted file mode 100644 index 79baab0..0000000 --- a/na/kernel/sys_info.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * System Information Calls - * - * Implements: uname - * - * Reference: docs/specs/syscall.md §7.3 - */ - -#include <lume/types.h> -#include <lume/errno.h> -#include <lume/uaccess.h> - -/* Linux-compatible utsname structure (65-byte fields) */ -struct utsname { - char sysname[65]; - char nodename[65]; - char release[65]; - char version[65]; - char machine[65]; - char domainname[65]; -}; - -/* Simple safe string copy */ -static void safe_strncpy(char* dst, const char* src, uint64 n) -{ - uint64 i = 0; - for (; i < n - 1 && src[i] != '\0'; i++) - dst[i] = src[i]; - dst[i] = '\0'; -} - -int64 sys_uname(uint64 buf_ptr, uint64, uint64, uint64, uint64, uint64) -{ - utsname info = {}; - safe_strncpy(info.sysname, "LumeOS", sizeof(info.sysname)); - safe_strncpy(info.nodename, "lume", sizeof(info.nodename)); - safe_strncpy(info.release, "0.1.0", sizeof(info.release)); - safe_strncpy(info.version, "Phase 2", sizeof(info.version)); - safe_strncpy(info.machine, "riscv64", sizeof(info.machine)); - safe_strncpy(info.domainname, "(none)", sizeof(info.domainname)); - - if (copy_to_user(buf_ptr, &info, sizeof(info)) < 0) - return -EFAULT; - - return 0; -} diff --git a/na/kernel/sys_misc.cc b/na/kernel/sys_misc.cc deleted file mode 100644 index 48eb47b..0000000 --- a/na/kernel/sys_misc.cc +++ /dev/null @@ -1,360 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Miscellaneous System Calls — busybox bootstrap set - * - * Each syscall follows the Linux kernel semantics exactly. - * Reference: Linux kernel/sys.c, kernel/signal.c, kernel/time/posix-timers.c - */ - -#include <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 deleted file mode 100644 index bfa067c..0000000 --- a/na/kernel/sys_mm.cc +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Memory Management System Calls - * - * Implements: brk, mmap (anonymous only), munmap, mprotect. - * - * Reference: docs/specs/syscall.md §7.2 - */ - -#include <lume/types.h> -#include <lume/task.h> -#include <lume/vmm.h> -#include <lume/errno.h> -#include <lume/mman.h> -#include <lume/config.h> -#include <lume/klog.h> - - -/* Page alignment helpers */ -static inline uint64 page_align_up(uint64 x) -{ - return (x + kPageSize - 1) & ~(kPageSize - 1); -} - -static inline uint64 page_align_down(uint64 x) -{ - return x & ~(kPageSize - 1); -} - -/* ====================================================================== - * sys_brk - * ====================================================================== */ - -int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64) -{ - VmSpace* mm = current_task()->mm; - if (!mm) - return -ENOMEM; /* kernel threads have no user address space */ - - if (new_brk == 0) { - return static_cast<int64>(mm->brk_end); - } - - if (mm->brk_start == 0) { - uint64 init_brk = page_align_up(new_brk); - mm->brk_start = init_brk; - mm->brk_end = init_brk; - } - - if (new_brk < mm->brk_start) { - return -EINVAL; - } - - uint64 new_end = page_align_up(new_brk); - if (new_end > mm->brk_end) { - uint64 grow = new_end - mm->brk_end; - int ret = vmm_map_user(mm, mm->brk_end, grow, VM_READ | VM_WRITE); - if (ret < 0) { - return ret; - } - } else if (new_end < mm->brk_end) { - uint64 shrink = mm->brk_end - new_end; - int ret = vmm_unmap_user(mm, new_end, shrink); - if (ret < 0) { - return ret; - } - } - - mm->brk_end = new_end; - return static_cast<int64>(new_brk); -} - -/* ====================================================================== - * sys_mmap - * ====================================================================== */ - -int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, - uint64 flags, uint64 fd, uint64 offset) -{ - printk("[mmap] addr=%llx len=%llx prot=%llx flags=%llx fd=%lld off=%llx\n", - addr, length, prot, flags, static_cast<int64>(fd), offset); - - if (length == 0) return -EINVAL; - length = page_align_up(length); - if (offset % kPageSize != 0) return -EINVAL; - - if ((flags & MAP_PRIVATE) && (flags & MAP_SHARED)) return -EINVAL; - if (!(flags & MAP_PRIVATE) && !(flags & MAP_SHARED)) return -EINVAL; - - VmSpace* mm = current_task()->mm; - if (!mm) return -ENOMEM; - - /* Phase 2: only anonymous private mappings */ - if (!(flags & MAP_ANONYMOUS)) { - printk("[mmap] REJECTED: file-backed mmap not implemented\n"); - return -ENOSYS; /* File-backed mmap is Phase 3 */ - } - if (flags & MAP_SHARED) - return -ENOSYS; /* Shared mappings are Phase 3 */ - - (void)fd; - - /* Convert POSIX prot → MI permission flags */ - uint64 vm_perm = 0; - if (prot & PROT_READ) vm_perm |= VM_READ; - if (prot & PROT_WRITE) vm_perm |= VM_WRITE; - if (prot & PROT_EXEC) vm_perm |= VM_EXEC; - vm_perm |= VM_USER; - - uint64 va = addr; - if (flags & MAP_FIXED) { - if (va % kPageSize != 0) return -EINVAL; - } else if (va != 0) { - va = page_align_down(va); - } - - if (va == 0) { - va = mm_find_free_va(mm, length, kPageSize); - if (!va) return -ENOMEM; - } - - if (flags & MAP_FIXED) { - int ret = vmm_unmap_user(mm, va, length); - if (ret < 0) return ret; - } - - int ret = vmm_map_user(mm, va, length, vm_perm); - if (ret < 0 && !(flags & MAP_FIXED) && addr != 0) { - uint64 fallback = mm_find_free_va(mm, length, kPageSize); - if (!fallback) return ret; - ret = vmm_map_user(mm, fallback, length, vm_perm); - if (ret < 0) return ret; - va = fallback; - } else if (ret < 0) { - return ret; - } - - return static_cast<int64>(va); -} - -/* ====================================================================== - * sys_munmap - * ====================================================================== */ - -int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64) -{ - if (length == 0) return -EINVAL; - if (addr % kPageSize != 0) return -EINVAL; - length = page_align_up(length); - - VmSpace* mm = current_task()->mm; - if (!mm) return -EINVAL; - - return vmm_unmap_user(mm, addr, length); -} - -/* ====================================================================== - * sys_mprotect — Phase 2 stub - * ====================================================================== */ - -int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, - uint64, uint64, uint64) -{ - if (len == 0) return -EINVAL; - if (addr % kPageSize != 0) return -EINVAL; - - VmSpace* mm = current_task()->mm; - if (!mm) return -ENOMEM; - - uint64 vm_perm = 0; - if (prot & PROT_READ) vm_perm |= VM_READ; - if (prot & PROT_WRITE) vm_perm |= VM_WRITE; - if (prot & PROT_EXEC) vm_perm |= VM_EXEC; - - return vmm_protect_user(mm, addr, len, vm_perm); -} diff --git a/na/kernel/sys_process.cc b/na/kernel/sys_process.cc deleted file mode 100644 index 438ad1b..0000000 --- a/na/kernel/sys_process.cc +++ /dev/null @@ -1,185 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Process Control System Calls - * - * Implements: exit, exit_group, getpid, gettid, getppid, - * sched_yield, clone (stub), wait4 (stub), - * set_tid_address, getuid, getgid. - * - * Reference: docs/specs/syscall.md §7.1 - */ - -#include "lume/klog.h" -#include <lume/types.h> -#include <lume/task.h> -#include <lume/sched.h> -#include <lume/errno.h> -#include <lume/uaccess.h> -#include <lume/wait.h> - -/* External: task lifecycle (kernel/task.cc) */ -extern void task_exit(int code); -extern int32 do_wait4(int32 wait_pid, int options, int32* exit_code); -extern int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, - uint64 child_stack, uint64 parent_tidptr, - uint64 child_tidptr, uint64 tls); -extern int32 task_fork(arch::TrapFrame* tf); - -/* ====================================================================== - * sys_exit / sys_exit_group - * ====================================================================== */ - -int64 sys_exit(uint64 code, uint64, uint64, uint64, uint64, uint64) -{ - task_exit(static_cast<int>(code & 0xFF)); - __builtin_unreachable(); -} - -int64 sys_exit_group(uint64 code, uint64, uint64, uint64, uint64, uint64) -{ - /* Phase 2: single-threaded, equivalent to sys_exit. - * Phase 3: kill all threads in tgid then exit. */ - task_exit(static_cast<int>(code & 0xFF)); - __builtin_unreachable(); -} - -/* ====================================================================== - * sys_getpid / sys_gettid / sys_getppid - * ====================================================================== */ - -int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return current_task()->tgid; /* Linux: getpid() returns tgid */ -} - -int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return current_task()->pid; /* Linux: gettid() returns real tid */ -} - -int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - TaskControlBlock* parent = current_task()->parent; - return parent ? parent->tgid : 0; -} - -/* ====================================================================== - * sys_sched_yield - * ====================================================================== */ - -int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64) -{ - sched_yield(); - return 0; -} - -int64 sys_clone_wrapper(uint64 flags, uint64 child_stack, - uint64 ptid, uint64 ctid, uint64 tls, uint64) -{ - arch::TrapFrame* tf = current_task()->trapframe; - if (!tf) return -EINVAL; - - return task_clone(tf, flags, child_stack, ptid, ctid, tls); -} - -/* ======================================================================== - * sys_wait4 — Wait for child to exit - * ======================================================================== */ - -int64 sys_wait4_wrapper(uint64 pid, uint64 wstatus_ptr, - uint64 options, uint64, uint64, uint64) -{ - int32 exit_code = 0; - int32 child_pid = do_wait4(static_cast<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) { - if (copy_to_user(wstatus_ptr, &exit_code, sizeof(int)) < 0) - return -EFAULT; - } - - return child_pid; -} - -/* ====================================================================== - * sys_set_tid_address — musl libc calls this very early - * ====================================================================== */ - -int64 sys_set_tid_address(uint64 tidptr, uint64, uint64, - uint64, uint64, uint64) -{ - /* Store clear_child_tid pointer — on exit, kernel writes 0 here - * and does futex_wake (enables pthread_join). */ - current_task()->clear_child_tid = tidptr; - return current_task()->pid; -} - -/* ====================================================================== - * sys_getuid / sys_getgid — Phase 2: always root - * ====================================================================== */ - -int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return 0; /* root */ -} - -int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64) -{ - return 0; /* root */ -} - -/* ====================================================================== - * sys_nanosleep (SYS_nanosleep = 101) - * ====================================================================== */ -#include <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 deleted file mode 100644 index 8b459d5..0000000 --- a/na/kernel/sys_signal.cc +++ /dev/null @@ -1,351 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * sys_signal.cc — Signal-related system calls - * - * Implements: - * sys_kill (SYS_kill = 129) - * sys_rt_sigaction (SYS_rt_sigaction = 134) - * sys_rt_sigprocmask (SYS_rt_sigprocmask = 135) - * sys_rt_sigreturn (SYS_rt_sigreturn = 139) - * - * Reference: Linux kernel/signal.c, arch/riscv/kernel/signal.c - */ - -#include <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 deleted file mode 100644 index a5b80ec..0000000 --- a/na/kernel/syscall.cc +++ /dev/null @@ -1,208 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * System Call MI Layer — Jump Table & Dispatcher - * - * O(1) function pointer array dispatch. No switch-case. - * syscall_init() registers all implemented handlers. - * - * Reference: docs/specs/syscall.md §6 - */ - -#include <lume/syscall.h> -#include <lume/syscall_nr.h> -#include <lume/errno.h> -#include <lume/kprintf.h> -#include <lume/klog.h> -#include <lume/console.h> -#include <arch/cpu.h> - -/* ====================================================================== - * Jump table - * ====================================================================== */ - -inline constexpr int kNrSyscalls = 512; - -static SyscallHandler sys_call_table[kNrSyscalls]; - -/* ====================================================================== - * MI Dispatcher - * ====================================================================== */ - -int64 syscall_dispatch(uint64 nr, uint64 args[6]) -{ - if (nr >= static_cast<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) { - kprintf("[syscall] unimplemented nr=%llu\n", nr); - return -ENOSYS; - } - - return handler(args[0], args[1], args[2], - args[3], args[4], args[5]); -} - -/* ====================================================================== - * Forward declarations of sys_* handlers - * ====================================================================== */ - -/* sys_process.cc */ -int64 sys_exit(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_exit_group(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getpid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_gettid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getppid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sched_yield_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_clone_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_wait4_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_set_tid_address(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getuid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_nanosleep(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_mm.cc */ -int64 sys_brk(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mmap(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_munmap(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mprotect(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_info.cc */ -int64 sys_uname(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_fs.cc */ -int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_lseek(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getdents64(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_dup3(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getcwd(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_chdir(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_mkdirat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_unlinkat(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_writev(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_ioctl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_ppoll(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_socket(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sendto(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_signal.cc */ -int64 sys_kill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_tkill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_tgkill(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigaction(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigprocmask(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigreturn(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_rt_sigtimedwait(uint64, uint64, uint64, uint64, uint64, uint64); - -/* exec.cc */ -int64 sys_execve_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); - -/* sys_misc.cc */ -int64 sys_reboot(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_prctl(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_setsid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getsid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_setpgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getpgid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_geteuid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_getegid(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_sigaltstack(uint64, uint64, uint64, uint64, uint64, uint64); -int64 sys_clock_gettime(uint64, uint64, uint64, uint64, uint64, uint64); - -/* ====================================================================== - * syscall_init — register all handlers - * ====================================================================== */ - -void syscall_init() -{ - /* Zero the table (redundant for BSS, but explicit) */ - for (int i = 0; i < kNrSyscalls; i++) - sys_call_table[i] = nullptr; - - /* Phase 2: minimal runnable set */ - sys_call_table[SYS_exit] = sys_exit; - sys_call_table[SYS_exit_group] = sys_exit_group; - sys_call_table[SYS_getpid] = sys_getpid; - sys_call_table[SYS_gettid] = sys_gettid; - sys_call_table[SYS_getppid] = sys_getppid; - sys_call_table[SYS_sched_yield] = sys_sched_yield_wrapper; - sys_call_table[SYS_clone] = sys_clone_wrapper; - sys_call_table[SYS_wait4] = sys_wait4_wrapper; - sys_call_table[SYS_set_tid_address] = sys_set_tid_address; - sys_call_table[SYS_getuid] = sys_getuid; - sys_call_table[SYS_getgid] = sys_getgid; - sys_call_table[SYS_brk] = sys_brk; - sys_call_table[SYS_mmap] = sys_mmap; - sys_call_table[SYS_munmap] = sys_munmap; - sys_call_table[SYS_mprotect] = sys_mprotect; - sys_call_table[SYS_uname] = sys_uname; - - /* Phase 3: filesystem */ - sys_call_table[SYS_openat] = sys_openat; - sys_call_table[SYS_close] = sys_close; - sys_call_table[SYS_read] = sys_read; - sys_call_table[SYS_write] = sys_write; - sys_call_table[SYS_lseek] = sys_lseek; - sys_call_table[SYS_fstat] = sys_fstat_wrapper; - sys_call_table[SYS_getdents64] = sys_getdents64; - sys_call_table[SYS_dup] = sys_dup; - sys_call_table[SYS_dup3] = sys_dup3; - sys_call_table[SYS_getcwd] = sys_getcwd; - sys_call_table[SYS_chdir] = sys_chdir; - sys_call_table[SYS_mkdirat] = sys_mkdirat; - sys_call_table[SYS_unlinkat] = sys_unlinkat; - sys_call_table[SYS_writev] = sys_writev; - sys_call_table[SYS_ioctl] = sys_ioctl; - sys_call_table[SYS_ppoll] = sys_ppoll; - - /* Phase 4: signals */ - sys_call_table[SYS_kill] = sys_kill; - sys_call_table[SYS_tkill] = sys_tkill; - sys_call_table[SYS_tgkill] = sys_tgkill; - sys_call_table[SYS_rt_sigaction] = sys_rt_sigaction; - sys_call_table[SYS_rt_sigprocmask] = sys_rt_sigprocmask; - sys_call_table[SYS_rt_sigreturn] = sys_rt_sigreturn; - sys_call_table[SYS_rt_sigtimedwait] = sys_rt_sigtimedwait; - - /* Phase 5: exec */ - sys_call_table[SYS_execve] = sys_execve_wrapper; - - /* Phase 6: pipe + fcntl */ - sys_call_table[SYS_pipe2] = sys_pipe2; - sys_call_table[SYS_fcntl] = sys_fcntl; - - /* Phase 7: busybox bootstrap */ - sys_call_table[SYS_reboot] = sys_reboot; - sys_call_table[SYS_prctl] = sys_prctl; - sys_call_table[SYS_setsid] = sys_setsid; - sys_call_table[SYS_getsid] = sys_getsid; - sys_call_table[SYS_setpgid] = sys_setpgid; - sys_call_table[SYS_getpgid] = sys_getpgid; - sys_call_table[SYS_geteuid] = sys_geteuid; - sys_call_table[SYS_getegid] = sys_getegid; - sys_call_table[SYS_sigaltstack] = sys_sigaltstack; - sys_call_table[SYS_clock_gettime] = sys_clock_gettime; - sys_call_table[SYS_nanosleep] = sys_nanosleep; - sys_call_table[SYS_socket] = sys_socket; - sys_call_table[SYS_sendto] = sys_sendto; - - printk(KERN_INFO "init: syscall_init done (%d handlers registered)\n", 46); -} diff --git a/na/kernel/task.cc b/na/kernel/task.cc deleted file mode 100644 index 9f9b043..0000000 --- a/na/kernel/task.cc +++ /dev/null @@ -1,780 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Task Management — create, exit, destroy, kthread - * - * Implements task lifecycle management: kernel thread creation, - * task exit (zombie transition), and resource reclamation. - * - * Reference: docs/specs/task.md §4-§6 - */ - -#include <lume/task.h> -#include <lume/sched.h> -#include <lume/pmm.h> -#include <lume/addr.h> -#include <lume/new.h> -#include <lume/kprintf.h> -#include <lume/klog.h> -#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> - -/* ====================================================================== - * Forward declarations - * ====================================================================== */ - -extern "C" void kthread_entry_trampoline(); -extern "C" void ret_from_fork(); - -/* ====================================================================== - * Global task list - * ====================================================================== */ - -list_node g_all_tasks; -Spinlock g_task_list_lock; -int32 g_nr_tasks = 0; - -/* Special tasks */ -TaskControlBlock* g_kernel_task = nullptr; /* PID 0 container */ -TaskControlBlock* g_init_task = nullptr; /* PID 1 (future) */ - -/* ====================================================================== - * task_init() — called once from kernel_main before sched_init - * ====================================================================== */ - -void task_init() -{ - g_all_tasks.init(); - g_task_list_lock.init("task_list"); - g_pid_alloc.init(); - - printk(KERN_INFO "init: task_init done (pid_alloc ready)\n"); -} - -/* ====================================================================== - * alloc_task — allocate TCB + kernel stack - * - * This is the SINGLE canonical TCB initializer. Both kthread_create - * (task.cc) and sched_init (sched_core.cc) must use this function. - * NEVER duplicate this logic — adding fields to TaskControlBlock - * requires updating only this function. - * ====================================================================== */ - -TaskControlBlock* alloc_task(const char* name, uint8 priority, - uint8 flags, uint8 cpu) -{ - auto* task = new TaskControlBlock; - if (!task) return nullptr; - - task->context = {}; - - /* Allocate 8KB kernel stack (order-1 = 2 pages) */ - Page* stack_page = pmm_alloc_pages(1); - if (!stack_page) { - delete task; - return nullptr; - } - task->kstack_pa = page_to_pa(stack_page).raw; - task->kstack_va = pa_to_va(phys_addr(task->kstack_pa)).raw; - - /* Write stack canary at bottom */ - *reinterpret_cast<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 = cpu; - task->flags = flags; - task->exit_code = 0; - task->exit_signal = 0; - task->preempt_count = 0; - task->clear_child_tid = 0; - task->stopped_code = 0; - task->notify_continued = false; - task->parent = nullptr; - task->mm = nullptr; - task->fpstate = {}; - task->files = nullptr; - task->fs = nullptr; - task->pending.init(); - task->blocked.clear(); - task->sighand = nullptr; - task->signal = nullptr; - - /* Init all list nodes */ - task->run_link.init(); - task->all_link.init(); - task->children.init(); - task->sibling.init(); - task->lock.init("task"); - task->wait_chld.init("wait_chld"); - - /* Default sched class */ - extern const SchedClass g_sched_class_o1; - task->sched_class = &g_sched_class_o1; - task->sched_entity = &task->run_link; - - return task; -} - -/* Add task to global task list */ -static void task_list_add(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - list_node* head = &g_all_tasks; - task->all_link.prev = head->prev; - task->all_link.next = head; - head->prev->next = &task->all_link; - head->prev = &task->all_link; - - g_nr_tasks++; -} - -/* Remove task from global task list */ -static void task_list_remove(TaskControlBlock* task) -{ - LockGuard guard(g_task_list_lock); - - task->all_link.prev->next = task->all_link.next; - task->all_link.next->prev = task->all_link.prev; - task->all_link.init(); - - if (g_nr_tasks > 0) - g_nr_tasks--; -} - -/* ====================================================================== - * kthread_create — create a kernel thread - * ====================================================================== */ - -TaskControlBlock* kthread_create(const char* name, - void (*entry)(void*), - void* arg, - uint8 priority) -{ - TaskControlBlock* task = alloc_task(name, priority, TF_KTHREAD, - static_cast<uint8>(arch::cpu::id())); - if (!task) return nullptr; - - /* Allocate PID */ - task->pid = g_pid_alloc.alloc(); - if (task->pid < 0) { - /* PID exhausted — clean up */ - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - delete task; - return nullptr; - } - task->tgid = task->pid; - - /* Kernel threads: exit_signal = -1 → auto-reap on exit. - * Linux: kernel_thread() passes (flags & ~CSIGNAL) → exit_signal = 0, - * but kthreads' parent (kthreadd) reaps them. We have no kthreadd, - * so we use -1 which makes exit_notify() call release_task() directly, - * identical to Linux's CLONE_THREAD autoreap path. */ - task->exit_signal = -1; - - /* Set up context for kthread_entry_trampoline. - * SP starts BELOW the TrapFrame reserved at the stack top. - * This ensures the kthread function never clobbers the TrapFrame area. */ - uint64 stack_top = task->kstack_va + kKernelStackSize - - sizeof(arch::TrapFrame); - task->context = {}; - task->context.ra = reinterpret_cast<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) or current caller */ - TaskControlBlock* parent = g_kernel_task ? g_kernel_task : current_task(); - task->parent = parent; - if (parent) { - LockGuard guard(parent->lock); - list_node* head = &parent->children; - task->sibling.prev = head->prev; - task->sibling.next = head; - head->prev->next = &task->sibling; - head->prev = &task->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(task); - sched_enqueue(task); - - return task; -} - -/* ====================================================================== - * task_exit — transition current task to Zombie - * ====================================================================== */ - -void task_exit(int code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_exit: no current task"); - - /* Linux ABI: normal exit encodes status as (code & 0xFF) << 8 */ - curr->exit_code = (code & 0xFF) << 8; - - /* CLONE_CHILD_CLEARTID / set_tid_address: write 0 to user tidptr. - * This is how pthread_join detects thread termination. - * TODO: futex_wake(clear_child_tid, 1) after writing 0. */ - if (curr->clear_child_tid != 0 && curr->mm != nullptr) { - int32 zero = 0; - copy_to_user(curr->clear_child_tid, &zero, sizeof(zero)); - curr->clear_child_tid = 0; - } - - /* Release user address space (refcount-safe). - * MUST switch to kernel page table FIRST — otherwise we're running - * on a freed page table after put() deallocates it. */ - if (curr->mm != nullptr) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children to init (or kernel_task if no init yet) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry<TaskControlBlock, - &TaskControlBlock::sibling>(child_node); - child->parent = foster; - - /* Move sibling link to foster's children */ - child_node->prev->next = child_node->next; - child_node->next->prev = child_node->prev; - - list_node* fhead = &foster->children; - child_node->prev = fhead->prev; - child_node->next = fhead; - fhead->prev->next = child_node; - fhead->prev = child_node; - - child_node = next_sib; - } - curr->children.init(); - } - - - /* ---- exit_notify: Linux kernel/exit.c:exit_notify() ---- - * Determine whether this task should be auto-reaped or become zombie. - * - * Linux logic (simplified): - * - exit_signal == -1 (kthreads, sub-threads): autoreap = true - * - exit_signal >= 1 (user processes): notify parent, become zombie - * - * If autoreap, we call release_task() equivalent inline: - * free PID, remove from task list, then deschedule to Dead. */ - bool autoreap = (curr->exit_signal <= 0); - - if (autoreap) { - /* Auto-reap: release resources immediately (no zombie state). - * Linux: exit_notify() → tsk->exit_state = EXIT_DEAD → release_task() */ - task_list_remove(curr); - g_pid_alloc.free(curr->pid); - - /* Remove from parent's children list */ - curr->sibling.prev->next = curr->sibling.next; - curr->sibling.next->prev = curr->sibling.prev; - curr->sibling.init(); - - /* Drop signal_struct ref: last task in the thread group also - * detaches us from the pgrp hash. */ - if (curr->signal) { - put_signal_struct(curr->signal); - curr->signal = nullptr; - } - if (curr->sighand) { - put_sighand(curr->sighand); - curr->sighand = nullptr; - } - - curr->state = TaskState::Dead; - schedule(); - __builtin_unreachable(); - } - - /* User process path: become Zombie for parent wait() */ - curr->state = TaskState::Zombie; - - /* Wake parent->wait_chld queue */ - if (curr->parent) { - curr->parent->wait_chld.wake_all(); - } - - /* Yield CPU — never returns */ - schedule(); - - __builtin_unreachable(); -} - -void task_kill_current(int sig) -{ - TaskControlBlock* curr = current_task(); - if (!curr) kernel_panic("task_kill_current: no current task"); - - /* Linux ABI: signal-killed encodes status as (signo & 0x7F) */ - curr->exit_code = sig & 0x7F; - - /* Release user address space (refcount-safe). - * Switch to kernel page table first (same as task_exit). */ - if (curr->mm) { - extern uint64 g_kernel_pgtbl; - pmap::activate(g_kernel_pgtbl); - curr->mm->put(); - curr->mm = nullptr; - } - - /* Reparent children (same as task_exit) */ - TaskControlBlock* foster = g_init_task ? g_init_task : g_kernel_task; - if (foster && !curr->children.is_empty()) { - LockGuard guard(curr->lock); - - list_node* child_node = curr->children.next; - while (child_node != &curr->children) { - list_node* next_sib = child_node->next; - auto* child = list_entry<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(); -} - -/* ====================================================================== - * task_destroy — fully reclaim a Zombie task (called by waitpid) - * ====================================================================== */ - -void task_destroy(TaskControlBlock* task) -{ - /* Remove from global list */ - task_list_remove(task); - - /* Remove from parent's children */ - task->sibling.prev->next = task->sibling.next; - task->sibling.next->prev = task->sibling.prev; - task->sibling.init(); - - /* Free PID */ - g_pid_alloc.free(task->pid); - - /* Drop signal_struct + sighand refs. For zombies these are delayed - * until reap (here) so a waitpid() walker can still inspect - * pgid/sid; Linux defers __exit_signal/__cleanup_sighand to - * release_task() for the same reason. */ - if (task->signal) { - put_signal_struct(task->signal); - task->signal = nullptr; - } - if (task->sighand) { - put_sighand(task->sighand); - task->sighand = nullptr; - } - - /* Free kernel stack */ - if (task->kstack_pa != 0) { - pmm_free_pages(pa_to_page(phys_addr(task->kstack_pa)), 1); - } - - /* Free TCB */ - delete task; -} - -/* ====================================================================== - * do_wait4 — wait for a child to exit (Linux-compatible) - * - * @wait_pid: >0 = specific child PID - * -1 = any child - * 0 = any child in same process group (TODO) - * <-1 = any child in specific process group (TODO) - * @options: WNOHANG = return immediately if no zombie - * @exit_code: output parameter (already in Linux wstatus format) - * - * Returns: PID of exited child, -ECHILD, or -EAGAIN (WNOHANG). - * ====================================================================== */ - -int32 do_wait4(int32 wait_pid, int options, int32* exit_code) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - while (true) { - TaskControlBlock* zombie = nullptr; - TaskControlBlock* report = nullptr; /* Stopped/Continued report */ - int32 report_status = 0; - bool has_children = false; - - /* - * Two-phase wait: put ourselves on the wait queue BEFORE - * checking the condition. This closes the race window where - * a child exits between our check and schedule(). - * - * If a child exits and calls wake_all() while we're checking, - * our state will be flipped back to Runnable, and schedule() - * will simply return immediately. - */ - curr->wait_chld.prepare_to_wait(curr, TaskState::Sleeping); - - /* Scan children */ - curr->lock.acquire(); - - if (curr->children.is_empty()) { - curr->lock.release(); - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - has_children = true; - - list_node* node = curr->children.next; - while (node != &curr->children) { - auto* child = list_entry<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; - } - curr->lock.release(); - - if (zombie) { - curr->wait_chld.finish_wait(curr); - int32 pid = zombie->pid; - if (exit_code) *exit_code = zombie->exit_code; - task_destroy(zombie); - return pid; - } - - if (report) { - /* Status-only report: clear the one-shot flag, leave the - * child alive. WNOWAIT (peek) would skip the clear, but - * we don't yet honour it. */ - curr->wait_chld.finish_wait(curr); - int32 pid = report->pid; - if (exit_code) *exit_code = report_status; - if (W_CONTINUED() == report_status) { - report->notify_continued = false; - } else { - report->stopped_code = 0; - } - return pid; - } - - /* No event to report */ - if (!has_children) { - curr->wait_chld.finish_wait(curr); - return -ECHILD; - } - if (options & WNOHANG) { - curr->wait_chld.finish_wait(curr); - return 0; - } - - /* Condition still false — deschedule. - * We are already on the wait queue and marked Sleeping. */ - schedule(); - - /* Woken up — finish_wait removes us from queue, restores Runnable */ - curr->wait_chld.finish_wait(curr); - - /* Loop back to re-check the condition */ - } -} - -/* Legacy wrapper for internal callers */ -int32 task_wait(int32* exit_code) -{ - return do_wait4(-1, 0, exit_code); -} - -/* ====================================================================== - * task_clone — clone current task with Linux-compatible flags - * - * @tf: trap frame to copy to child - * @clone_flags: CLONE_VM | CLONE_THREAD | CSIGNAL etc. - * @child_stack: if non-zero, set as child's user SP (for pthread) - * @parent_tidptr: write child tid here in parent (CLONE_PARENT_SETTID) - * @child_tidptr: write child tid here in child (CLONE_CHILD_SETTID) - * @tls: TLS pointer (CLONE_SETTLS → tp register) - * - * Returns: child PID in parent, 0 in child, or negative errno. - * ====================================================================== */ - -int32 task_clone(arch::TrapFrame* tf, uint64 clone_flags, - uint64 child_stack, uint64 parent_tidptr, - uint64 child_tidptr, uint64 tls) -{ - TaskControlBlock* curr = current_task(); - if (!curr) return -EINVAL; - - TaskControlBlock* child = alloc_task("cloned", curr->priority, 0, - static_cast<uint8>(arch::cpu::id())); - if (!child) return -ENOMEM; - - /* Allocate PID */ - child->pid = g_pid_alloc.alloc(); - if (child->pid < 0) { - pmm_free_pages(pa_to_page(phys_addr(child->kstack_pa)), 1); - delete child; - return -ENOMEM; - } - - /* exit_signal + tgid: from clone_flags. - * Linux: fork.c line 2418/2424 */ - if (clone_flags & CLONE_THREAD) { - child->tgid = curr->tgid; - child->exit_signal = -1; /* Linux: sub-threads auto-reap */ - } else { - child->tgid = child->pid; - child->exit_signal = static_cast<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; - } - } - } - - /* Copy TrapFrame to the child's kernel stack */ - uint64 tf_addr = child->kstack_va + kKernelStackSize - sizeof(arch::TrapFrame); - child->trapframe = reinterpret_cast<arch::TrapFrame*>(tf_addr); - *child->trapframe = *tf; - - /* 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; - child->context = {}; - child->context.ra = reinterpret_cast<uint64>(ret_from_fork); - child->context.sp = stack_top; - child->context.s[0] = tf_addr; - - /* ---- Copy fs_struct (Linux: copy_fs in fork.c) ---- - * CLONE_FS: share the same fs_struct (increment refcount). - * Otherwise: allocate a new fs_struct copying root/pwd. */ - if (curr->fs != nullptr) { - if (clone_flags & CLONE_FS) { - curr->fs->count.fetch_add(1, __ATOMIC_RELAXED); - child->fs = curr->fs; - } else { - child->fs = copy_fs_struct(curr->fs); - } - } - - /* ---- Copy files_struct (Linux: copy_files in fork.c) ---- - * CLONE_FILES: share the same fd table. - * Otherwise: duplicate the fd table. */ - if (curr->files != nullptr) { - if (clone_flags & CLONE_FILES) { - curr->files->count.fetch_add(1, __ATOMIC_RELAXED); - child->files = curr->files; - } else { - child->files = dup_fd(curr->files); - } - } - - /* ---- Copy sighand (Linux kernel/fork.c:copy_sighand) ---- - * CLONE_SIGHAND: share signal handlers (incref parent's). - * Otherwise: deep copy of the action table; child gets its own - * sighand_struct (and its own siglock). Both branches MUST yield - * a non-null sighand — every user task needs one so signal paths - * can take the siglock without lazy-allocation races. */ - if (clone_flags & CLONE_SIGHAND) { - if (!curr->sighand) { - /* CLONE_SIGHAND requires the parent to have a sighand. */ - return -EINVAL; - } - get_sighand(curr->sighand); - child->sighand = curr->sighand; - } else { - child->sighand = copy_sighand(curr->sighand); - if (!child->sighand) { - /* OOM — same best-effort policy as fs/files/signal above. - * A separate cleanup-on-OOM pass is on the to-do list. */ - } - } - - /* ---- Copy signal_struct (Linux: copy_signal in fork.c) ---- - * CLONE_THREAD shares the parent's signal_struct (incref). - * Otherwise allocate a new one that inherits pgid/sid/tty from the - * parent so the child stays in the same process group and session — - * this is what makes fork() participate in the existing job-control - * identity without explicit setpgid() calls. */ - if (curr->signal != nullptr) { - if (clone_flags & CLONE_THREAD) { - get_signal_struct(curr->signal); - child->signal = curr->signal; - } else { - child->signal = fork_signal_struct(curr->signal, child); - } - } - - /* Add to task tree */ - child->parent = curr; - { - LockGuard guard(curr->lock); - list_node* head = &curr->children; - child->sibling.prev = head->prev; - child->sibling.next = head; - head->prev->next = &child->sibling; - head->prev = &child->sibling; - } - - /* Add to global list and enqueue */ - task_list_add(child); - sched_enqueue(child); - - /* CLONE_PARENT_SETTID: write child tid to parent's user address */ - if ((clone_flags & CLONE_PARENT_SETTID) && parent_tidptr != 0) { - copy_to_user(parent_tidptr, &child->pid, sizeof(child->pid)); - } - - /* CLONE_CHILD_SETTID: write child tid into child's user address. - * Note: this runs in parent context, but the address is in the - * child's mm. For CLONE_VM (threads), parent and child share mm - * so this works directly. For fork, the page was just CoW-copied. */ - if ((clone_flags & CLONE_CHILD_SETTID) && child_tidptr != 0) { - if (clone_flags & CLONE_VM) { - /* Same address space — write directly */ - copy_to_user(child_tidptr, &child->pid, sizeof(child->pid)); - } - /* For non-CLONE_VM fork, the child's mm is separate. - * The write would need to go into the child's pagetable. - * Linux handles this in the child's first schedule via - * a special flag. For now, threads (CLONE_VM) are the - * primary consumer. */ - } - - /* CLONE_CHILD_CLEARTID: kernel writes 0 to child_tidptr on exit */ - if (clone_flags & CLONE_CHILD_CLEARTID) { - child->clear_child_tid = child_tidptr; - } - - return child->pid; -} - -/* Legacy wrapper — plain fork() semantics */ -int32 task_fork(arch::TrapFrame* tf) -{ - return task_clone(tf, SIGCHLD, 0, 0, 0, 0); -} - - -/* ====================================================================== - * task_check_stack_canary - * ====================================================================== */ - -void task_check_stack_canary(TaskControlBlock* task) -{ - if (!task || task->kstack_va == 0) return; - - auto* canary = reinterpret_cast<uint64*>(task->kstack_va); - if (*canary != kStackCanary) { - kernel_panic("kernel stack overflow detected"); - } -} diff --git a/na/kernel/timer.cc b/na/kernel/timer.cc deleted file mode 100644 index 3cf03cf..0000000 --- a/na/kernel/timer.cc +++ /dev/null @@ -1,118 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/timer.h> -#include <lume/fdt.h> -#include <lume/sched.h> -#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); -} - -void timer_init() { - DeviceNode* cpus = fdt_get_node_by_path("/cpus"); - if (cpus) { - g_timer_freq = cpus->get_prop_u32("timebase-frequency", g_timer_freq); - } - - // Default 100 Hz (10ms per tick) - g_tick_interval = g_timer_freq / 100; - - g_timer_lock.init("timer_list"); - g_timer_list.init(); - - timer_set_next(); - - arch::cpu::timer_intr_on(); -} - - -void timer_init_ap() { - timer_set_next(); - arch::cpu::timer_intr_on(); -} - -extern "C" uint32 bsp_hart_id; - -void timer_tick() { - if (arch::cpu::id() == bsp_hart_id) { - g_jiffies++; - run_timers(); - } - - timer_set_next(); - sched_tick(); -} - - -uint64 timer_get_freq() { - return g_timer_freq; -} diff --git a/na/kernel/trap.cc b/na/kernel/trap.cc deleted file mode 100644 index 2116d91..0000000 --- a/na/kernel/trap.cc +++ /dev/null @@ -1,258 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Trap MI Layer — Machine-Independent Semantic Dispatcher - * - * Receives a translated TrapCause from the MD layer and routes to - * the appropriate handler. - * - * *** MI INVARIANT: This file contains ZERO inline assembly, - * ZERO arch-specific constants (no CSR reads, no scause codes). *** - * arch/trapframe.h is included because TrapFrame is the cross-layer - * data contract defined in docs/specs/arch_abstraction.md §3.2. - * - * Reference: docs/specs/trap.md §4 - */ - -#include <arch/cpu.h> -#include <lume/trap.h> -#include <lume/irq.h> -#include <lume/console.h> -#include <lume/kprintf.h> -#include <lume/errno.h> -#include <lume/sched.h> -#include <lume/timer.h> -#include <lume/klog.h> -#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 - * - * Called by MD layer after scause → TrapInfo translation. - * ======================================================================== */ - -static TrapStats g_trap_stats = {}; - -static uint32 trap_cause_index(TrapCause cause) -{ - 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) -{ - uint32 idx = trap_cause_index(info.cause); - __atomic_fetch_add(&g_trap_stats.total, 1, __ATOMIC_RELAXED); - __atomic_fetch_add(&g_trap_stats.by_cause[idx], 1, __ATOMIC_RELAXED); - g_trap_stats.last_cause = info.cause; - g_trap_stats.last_class = info.cls; - g_trap_stats.last_origin = info.origin; - g_trap_stats.last_val = info.val; - g_trap_stats.last_epc = info.epc; - g_trap_stats.last_scause = info.scause; -} - -static VmFaultCause vm_fault_cause_from_trap(TrapCause cause) -{ - switch (cause) { - case TrapCause::PageFaultStore: - return VmFaultCause::Write; - case TrapCause::PageFaultExec: - return VmFaultCause::Exec; - case TrapCause::PageFaultLoad: - default: - return VmFaultCause::Read; - } -} - -void trap_stats_get(TrapStats* out) -{ - if (!out) { - return; - } - *out = g_trap_stats; -} - -void trap_stats_reset() -{ - g_trap_stats = {}; -} - -void trap_handle(arch::TrapFrame* tf, const TrapInfo& info) -{ - trap_record(info); - - /* ---- Kernel exception handling ---- - * Interrupts are always allowed through. For exceptions: - * - Page faults: handled in the switch below (extable fixup path) - * - Other exceptions: try extable, then panic. - * - * This replaces the old blanket kernel_panic, enabling - * copy_from_user/copy_to_user to safely fault on user pages. */ - if (info.origin == TrapOrigin::FromKernel && - info.cls != TrapClass::Interrupt) { - - /* Page faults are dispatched in the switch — let them through */ - bool is_page_fault = - (info.cause == TrapCause::PageFaultLoad || - info.cause == TrapCause::PageFaultStore || - info.cause == TrapCause::PageFaultExec); - - if (!is_page_fault) { - /* Non-page-fault kernel exception: try extable fixup */ - uint64 fixup = search_extable(info.epc); - if (fixup != 0) { - tf->epc = fixup; - return; - } - kprintf("[trap] fatal kernel exception: cause=%u epc=0x%llx val=0x%llx\n", - static_cast<unsigned>(info.cause), info.epc, info.val); - kernel_panic("unrecoverable kernel exception"); - } - } - - switch (info.cause) { - /* ---- Page Faults ---- */ - case TrapCause::PageFaultLoad: - case TrapCause::PageFaultStore: - case TrapCause::PageFaultExec: - if (info.origin == TrapOrigin::FromUser) { - TaskControlBlock* curr = current_task(); - if (!curr || !curr->mm) { - kernel_panic("user page fault with no current mm"); - } - - int ret = vmm_handle_page_fault(curr->mm, info.val, - vm_fault_cause_from_trap(info.cause)); - if (ret == -EFAULT) { - kprintf("[trap] user SIGSEGV (MAPERR) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGSEGV, 1 /* SEGV_MAPERR */, info.val); - } else if (ret == -ENOMEM) { - kprintf("[trap] user SIGKILL (OOM) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGKILL, 0, info.val); - } else if (ret < 0) { - kprintf("[trap] user SIGSEGV (ACCERR) epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGSEGV, 2 /* SEGV_ACCERR */, info.val); - } - break; - } - - /* Kernel page fault — try exception table fixup. - * This is the path used by copy_from_user/copy_to_user when - * accessing unmapped or faulting user addresses from S-mode. */ - { - uint64 fixup = search_extable(info.epc); - if (fixup != 0) { - tf->epc = fixup; - break; - } - } - kprintf("[trap] kernel page fault: epc=0x%llx addr=0x%llx\n", - info.epc, info.val); - { - TaskControlBlock* curr = current_task(); - if (curr) { - kprintf("[trap] pid=%d state=%d\n", - curr->pid, static_cast<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 ---- */ - case TrapCause::Syscall: - syscall_dispatch_md(tf); - break; - - /* ---- Interrupts ---- */ - case TrapCause::IrqTimer: - /* Timer tick */ - timer_tick(); - break; - - case TrapCause::IrqExternal: { - /* External interrupt: generic claim → dispatch → complete */ - uint32 irq = g_irq_manager.claim(); - if (irq != 0) { - g_irq_manager.dispatch(irq); - g_irq_manager.complete(irq); - } - break; - } - - case TrapCause::IrqSoftware: - /* S-mode software interrupt = IPI from another hart */ - smp_handle_ipi(); - break; - - /* ---- Fatal exceptions ---- */ - case TrapCause::IllegalInst: - if (info.origin == TrapOrigin::FromUser) { - kprintf("[trap] user illegal instruction at epc=0x%llx stval=0x%llx\n", - info.epc, info.val); - force_sig_fault(SIGILL, 1 /* ILL_ILLOPC */, info.epc); - } else { - kprintf("[trap] illegal instruction at epc=0x%llx\n", info.epc); - kernel_panic("illegal instruction in kernel mode"); - } - break; - - case TrapCause::AccessFault: - kprintf("[trap] access fault\n"); - kernel_panic("access fault"); - break; - - case TrapCause::AlignFault: - kprintf("[trap] alignment fault\n"); - kernel_panic("alignment fault"); - break; - - case TrapCause::Breakpoint: - kprintf("[trap] breakpoint\n"); - /* ebreak advance is handled by MD layer (arch-specific instruction size) */ - break; - - case TrapCause::Unknown: - default: - kprintf("[trap] unknown trap\n"); - kernel_panic("unknown trap"); - break; - } - - /* Process pending signals before returning to user mode */ - if (info.origin == TrapOrigin::FromUser) { - do_signal(tf); - } - - /* Preemption check on return from any interrupt. - * This is the kernel's equivalent of Linux's retint_kernel path: - * if a timer tick (or IPI) set need_resched, we must call schedule() - * before returning — otherwise the idle loop never yields to - * runnable tasks like PID 1. */ - if (info.cls == TrapClass::Interrupt) { - sched_check_preempt(); - } -} diff --git a/na/kernel/uaccess.cc b/na/kernel/uaccess.cc deleted file mode 100644 index 14c5fb7..0000000 --- a/na/kernel/uaccess.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * User-Kernel Memory Copy — Phase 3 (Exception Tables) - * - * Validates user pointers by checking they fall within user address space - * (below KERNEL_VA_BASE). Uses assembly-level safe copy functions that - * integrate with the kernel's Exception Table mechanism. - * - * Reference: docs/specs/syscall.md §8 - */ - -#include <lume/uaccess.h> -#include <lume/task.h> -#include <lume/errno.h> -#include <lume/config.h> -#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); - -int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len) -{ - if (len == 0) return 0; - if (!is_user_range(user_src, len)) return -EFAULT; - - return __uaccess_memcpy(kernel_dst, reinterpret_cast<const void*>(user_src), len); -} - -int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len) -{ - if (len == 0) return 0; - if (!is_user_range(user_dst, len)) return -EFAULT; - - return __uaccess_memcpy(reinterpret_cast<void*>(user_dst), kernel_src, len); -} - -int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len) -{ - if (!is_user_range(user_src, 1)) return -EFAULT; - - /* We must also check that the string does not cross into kernel space. - This is safely handled by the assembly routine which will fault if it hits - an unmapped page. However, to strictly respect kKernelVaBase, we should - restrict max_len to the distance to kKernelVaBase. */ - uint64 max_allowed = kKernelVaBase - user_src; - if (max_len > max_allowed) { - max_len = max_allowed; - } - - return __uaccess_strncpy(kernel_dst, reinterpret_cast<const char*>(user_src), max_len); -} diff --git a/na/kernel/waitqueue.cc b/na/kernel/waitqueue.cc deleted file mode 100644 index 9689417..0000000 --- a/na/kernel/waitqueue.cc +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * WaitQueue — blocking synchronization implementation - * - * Two-phase wait protocol: - * - * prepare_to_wait(): task → Sleeping, link into queue - * [caller checks condition — if still false, calls schedule()] - * finish_wait(): remove from queue, restore Runnable - * - * Legacy sleep() wraps the two-phase pattern for simple callers. - * - * wake_one()/wake_all(): dequeue waiters → Runnable, sched_enqueue() - * - * The internal spinlock protects the wait list only. - * schedule() is called AFTER releasing all spinlocks to avoid - * holding a lock across a context switch. - */ - -#include <lume/waitqueue.h> -#include <lume/task.h> -#include <lume/sched.h> -#include <lume/kprintf.h> -#include <arch/cpu.h> - -/* ------------------------------------------------------------------ */ -/* Two-phase wait API */ -/* ------------------------------------------------------------------ */ - -void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state) -{ - LockGuard guard(lock_); - - /* Set state BEFORE linking — this is the key to preventing lost wakeups. - * If wake_one fires between now and schedule(), it will set state back - * to Runnable, and schedule() will see Runnable and not block. */ - task->state = new_state; - - /* Only add to queue if not already linked (idempotent) */ - if (task->run_link.next == &task->run_link) { - /* Link into wait queue tail */ - list_node* node = &task->run_link; - node->prev = head_.prev; - node->next = &head_; - head_.prev->next = node; - head_.prev = node; - } -} - -void WaitQueue::finish_wait(TaskControlBlock* task) -{ - LockGuard guard(lock_); - - /* Remove from wait queue if still linked */ - if (task->run_link.next != &task->run_link) { - list_node* node = &task->run_link; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - } - - /* Ensure task is Runnable (it may have been woken by wake_one/wake_all - * already, or it may never have slept if the condition was true). */ - if (task->state == TaskState::Sleeping) { - task->state = TaskState::Runnable; - } -} - -/* ------------------------------------------------------------------ */ -/* Legacy convenience API */ -/* ------------------------------------------------------------------ */ - -void WaitQueue::sleep(Spinlock* external_lock) -{ - TaskControlBlock* curr = current_task(); - if (!curr) { - if (external_lock) external_lock->release(); - return; - } - - /* Phase 1: add to queue, mark Sleeping */ - prepare_to_wait(curr, TaskState::Sleeping); - - /* Release external lock AFTER we are on the queue (critical ordering) */ - if (external_lock) { - external_lock->release(); - } - - /* Phase 2: context switch away — will return here when woken */ - schedule(); - - /* Phase 3: clean up */ - finish_wait(curr); -} - -/* ------------------------------------------------------------------ */ -/* Wakeup */ -/* ------------------------------------------------------------------ */ - -bool WaitQueue::wake_one() -{ - LockGuard guard(lock_); - - if (head_.is_empty()) - return false; - - /* Dequeue the first (oldest) waiter */ - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - - TaskControlBlock* task = - list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node); - - task->state = TaskState::Runnable; - sched_enqueue(task); - - return true; -} - -int WaitQueue::wake_all() -{ - LockGuard guard(lock_); - - int count = 0; - while (!head_.is_empty()) { - list_node* node = head_.next; - node->prev->next = node->next; - node->next->prev = node->prev; - node->init(); - - TaskControlBlock* task = - list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node); - - task->state = TaskState::Runnable; - sched_enqueue(task); - count++; - } - - return count; -} diff --git a/na/lib/cxx_abi.cc b/na/lib/cxx_abi.cc deleted file mode 100644 index 872b4a2..0000000 --- a/na/lib/cxx_abi.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * cxx_abi.cc — Minimal C++ ABI stubs for freestanding kernel - * - * Provides symbols that the compiler/linker expects from the C++ - * runtime library. All error paths are fatal (kernel_panic). - * - * Reference: docs/specs/cpp_runtime.md - */ - -#include <lume/klog.h> - -/* Pure virtual function call trap. - * Generated by the compiler in vtable entries for pure virtual methods. - * Reaching this means a derived class failed to implement a pure virtual. */ -extern "C" void __cxa_pure_virtual() -{ - kernel_panic("pure virtual function call"); -} - -/* Global destructor registration. - * The kernel does not support global object destruction — all global - * objects use Placement New with explicit lifetime management. - * If the compiler emits a call to this, it indicates a code violation. */ -extern "C" int __cxa_atexit(void (*)(void*), void*, void*) -{ - kernel_panic("__cxa_atexit called in kernel"); - return 0; // unreachable -} - -/* DSO handle — required by __cxa_atexit ABI. - * The kernel is statically linked; this symbol just needs to exist. */ -extern "C" { -void* __dso_handle __attribute__((visibility("hidden"))) = nullptr; -} diff --git a/na/lib/fdt.c b/na/lib/fdt.c deleted file mode 100644 index 1b20383..0000000 --- a/na/lib/fdt.c +++ /dev/null @@ -1,2 +0,0 @@ -#include <lume/libfdt_env.h> -#include "../scripts/dtc/libfdt/fdt.c" diff --git a/na/lib/fdt_addresses.c b/na/lib/fdt_addresses.c deleted file mode 100644 index d58f896..0000000 --- a/na/lib/fdt_addresses.c +++ /dev/null @@ -1,2 +0,0 @@ -#include <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 deleted file mode 100644 index 37f8ef8..0000000 --- a/na/lib/fdt_empty_tree.c +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index 3599d49..0000000 --- a/na/lib/fdt_ro.c +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index 65d7a71..0000000 --- a/na/lib/fdt_rw.c +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index dae35ea..0000000 --- a/na/lib/fdt_strerror.c +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index d04f220..0000000 --- a/na/lib/fdt_sw.c +++ /dev/null @@ -1,2 +0,0 @@ -#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 deleted file mode 100644 index ebfb3c1..0000000 --- a/na/lib/fdt_wip.c +++ /dev/null @@ -1,2 +0,0 @@ -#include <lume/libfdt_env.h> -#include "../scripts/dtc/libfdt/fdt_wip.c" diff --git a/na/lib/klog.cc b/na/lib/klog.cc deleted file mode 100644 index 86c67f8..0000000 --- a/na/lib/klog.cc +++ /dev/null @@ -1,574 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * klog.cc — Kernel logging subsystem implementation - * - * Implements the ring buffer, spinlock-protected writes, log level - * filtering, and synchronous console output. - * - * Phase 2 TODO: async flush via kernel thread (see docs/specs/klog.md) - */ - -#include <lume/klog.h> -#include <lume/kprintf.h> -#include <lume/console.h> -#include <lume/atomic.h> -#include <kernel/sync/spinlock.h> -#include <arch/cpu.h> - -/* Forward declaration — mpaland/printf */ -extern "C" int vsnprintf_(char* buffer, size_t count, - const char* format, __builtin_va_list va); - -/* ======================================================================== - * Ring Buffer State - * ======================================================================== */ - -struct KlogEntryHdr { - uint32 seq; - uint32 len; - uint64 ts; - uint16 cpu; - uint8 level; - uint8 subsys; - uint16 reserved; -}; - -static struct { - char data[KLOG_BUF_SIZE]; - uint32 head; /* Next write position */ - uint32 tail; /* Next read position (for /proc/kmsg) */ - uint32 console_tail; /* Next position to flush to console */ - uint32 dropped; - lume::atomic<uint32> seq; - uint64 subsys_mask; - Spinlock lock; - KlogLevel console_level; /* Messages <= this level go to UART */ - bool initialized; - bool panic_mode; -} g_klog; - -/* ======================================================================== - * Level name table - * ======================================================================== */ - -static const char* klog_level_name(KlogLevel level) -{ - static const char* names[] = { - "EMERG", "ALERT", "CRIT", "ERR", - "WARN", "NOTICE", "INFO", "DEBUG" - }; - if (level <= KLOG_DEBUG) { - return names[level]; - } - return "?"; -} - -static const char* klog_subsys_name(KlogSubsys subsys) -{ - static const char* names[] = { - "GEN", - "SCHED", - "TRAP", - "IRQ", - "TIMER", - "VMM", - "PMM", - "SLAB", - "FS", - "DRV", - "TEST", - }; - if (subsys < KLOG_SUBSYS_MAX) { - return names[subsys]; - } - return "?"; -} - -static const char* parse_kern_level(const char* fmt, KlogLevel* level) -{ - if (!fmt || !level) { - return fmt; - } - - /* Linux-style "\001n" prefix (KERN_SOH) */ - if (fmt[0] == '\001' && fmt[1] >= '0' && fmt[1] <= '7') { - *level = static_cast<KlogLevel>(fmt[1] - '0'); - return fmt + 2; - } - - /* Alternate "<n>" prefix */ - if (fmt[0] == '<' && fmt[1] >= '0' && fmt[1] <= '7' && fmt[2] == '>') { - *level = static_cast<KlogLevel>(fmt[1] - '0'); - return fmt + 3; - } - - return fmt; -} - -/* ======================================================================== - * Ring Buffer Operations - * ======================================================================== */ - -/* Write a byte to the ring buffer. Overwrites oldest data on overflow. */ -static inline uint32 min_u32(uint32 a, uint32 b) -{ - return (a < b) ? a : b; -} - -static void ring_write_bytes(const void* src, uint32 len) -{ - const char* p = reinterpret_cast<const char*>(src); - for (uint32 i = 0; i < len; ++i) { - g_klog.data[(g_klog.head + i) & (KLOG_BUF_SIZE - 1)] = p[i]; - } - g_klog.head += len; -} - -static void ring_read_bytes(uint32 pos, void* dst, uint32 len) -{ - char* out = reinterpret_cast<char*>(dst); - for (uint32 i = 0; i < len; ++i) { - out[i] = g_klog.data[(pos + i) & (KLOG_BUF_SIZE - 1)]; - } -} - -static void ring_drop_oldest(uint32 needed) -{ - uint32 min_tail = min_u32(g_klog.tail, g_klog.console_tail); - - while (KLOG_BUF_SIZE - (g_klog.head - min_tail) < needed) { - KlogEntryHdr hdr; - ring_read_bytes(min_tail, &hdr, sizeof(hdr)); - - uint32 entry = sizeof(hdr) + hdr.len; - if (entry == 0 || entry > KLOG_BUF_SIZE) { - min_tail++; - g_klog.dropped++; - continue; - } - min_tail += entry; - g_klog.dropped++; - } - - if (g_klog.tail < min_tail) { - g_klog.tail = min_tail; - } - if (g_klog.console_tail < min_tail) { - g_klog.console_tail = min_tail; - } -} - -/* ======================================================================== - * Console Output (synchronous) - * ======================================================================== */ - -static void console_emit(const char* s, int len) -{ - console_write_all(s, len); -} - -static int klog_format_line(char* out, int out_size, - const KlogEntryHdr& hdr, - const char* msg, int msg_len) -{ - const char* lvl = klog_level_name(static_cast<KlogLevel>(hdr.level)); - const char* subsys = klog_subsys_name(static_cast<KlogSubsys>(hdr.subsys)); - - int prefix = ksnprintf(out, out_size, - "[%s][cpu%u][%llu][%s] ", - lvl, (uint32)hdr.cpu, - (unsigned long long)hdr.ts, subsys); - if (prefix < 0) { - return 0; - } - - int total = prefix; - int avail = out_size - 1 - total; - if (avail > 0) { - int to_copy = (msg_len < avail) ? msg_len : avail; - for (int i = 0; i < to_copy; ++i) { - out[total + i] = msg[i]; - } - total += to_copy; - } - - if (total > 0 && out[total - 1] != '\n' && total < out_size - 1) { - out[total++] = '\n'; - } - - out[total] = '\0'; - return total; -} - -static int klog_read_internal(char* buf, int size, uint32* tail_ptr, bool consume) -{ - if (size <= 0) { - return 0; - } - - int written = 0; - uint32 pos = *tail_ptr; - - while (pos != g_klog.head) { - KlogEntryHdr hdr; - ring_read_bytes(pos, &hdr, sizeof(hdr)); - uint32 raw_len = hdr.len; - if (raw_len > KLOG_BUF_SIZE) { - break; - } - uint32 copy_len = raw_len; - if (copy_len > KLOG_LINE_MAX) { - copy_len = KLOG_LINE_MAX; - } - - char msg[KLOG_LINE_MAX + 1]; - ring_read_bytes(pos + sizeof(hdr), msg, copy_len); - msg[copy_len] = '\0'; - - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), hdr, msg, - static_cast<int>(copy_len)); - - if (line_len <= 0 || written + line_len > size) { - break; - } - - for (int i = 0; i < line_len; ++i) { - buf[written + i] = line[i]; - } - written += line_len; - - pos += sizeof(hdr) + raw_len; - } - - if (consume) { - *tail_ptr = pos; - } - - return written; -} - -static void klog_vex(KlogLevel level, KlogSubsys subsys, - const char* fmt, __builtin_va_list va); - -/* ======================================================================== - * Public API - * ======================================================================== */ - -void klog_init(void) -{ - g_klog.head = 0; - g_klog.tail = 0; - g_klog.console_tail = 0; - g_klog.dropped = 0; - g_klog.seq.store(0, __ATOMIC_RELAXED); - g_klog.subsys_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; - g_klog.console_level = static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL); - g_klog.initialized = true; - g_klog.panic_mode = false; - g_klog.lock.init("klog"); -} - -void klog(KlogLevel level, const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - klog_vex(level, KLOG_SUBSYS_GENERIC, fmt, va); - __builtin_va_end(va); -} - -void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - klog_vex(level, subsys, fmt, va); - __builtin_va_end(va); -} - -static void klog_vex(KlogLevel level, KlogSubsys subsys, - const char* fmt, __builtin_va_list va) -{ - if (subsys >= KLOG_SUBSYS_MAX) { - subsys = KLOG_SUBSYS_GENERIC; - } - - if (g_klog.initialized && ((g_klog.subsys_mask & (1ULL << subsys)) == 0)) { - return; - } - - char msg[KLOG_LINE_MAX + 1]; - int msg_len = vsnprintf_(msg, KLOG_LINE_MAX, fmt, va); - - if (msg_len < 0) { - return; - } - if (msg_len > KLOG_LINE_MAX) { - msg_len = KLOG_LINE_MAX; - } - msg[msg_len] = '\0'; - - KlogEntryHdr hdr; - hdr.seq = g_klog.seq.fetch_add(1, __ATOMIC_RELAXED); - hdr.len = static_cast<uint32>(msg_len); - hdr.ts = arch::cpu::read_time(); - hdr.cpu = static_cast<uint16>(arch::cpu::id()); - hdr.level = static_cast<uint8>(level); - hdr.subsys = static_cast<uint8>(subsys); - hdr.reserved = 0; - - bool do_console = (!g_klog.initialized || level <= g_klog.console_level); - - if (g_klog.initialized && !g_klog.panic_mode) { - LockGuard guard(g_klog.lock); - ring_drop_oldest(sizeof(hdr) + hdr.len); - ring_write_bytes(&hdr, sizeof(hdr)); - ring_write_bytes(msg, hdr.len); - if (do_console) { - g_klog.console_tail = g_klog.head; - /* Emit to UART while holding the lock to prevent - * interleaved output from concurrent CPUs. */ - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), - hdr, msg, hdr.len); - if (line_len > 0) { - console_emit(line, line_len); - } - } - } else if (do_console) { - /* Pre-init or panic mode: emit without lock */ - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), - hdr, msg, hdr.len); - if (line_len > 0) { - console_emit(line, line_len); - } - } -} - -int vprintk(const char* fmt, __builtin_va_list va) -{ - KlogLevel level = KLOG_INFO; - const char* real_fmt = parse_kern_level(fmt, &level); - - __builtin_va_list va_copy; - __builtin_va_copy(va_copy, va); - - char tmp[KLOG_LINE_MAX + 1]; - int len = vsnprintf_(tmp, KLOG_LINE_MAX, real_fmt, va_copy); - __builtin_va_end(va_copy); - - klog_vex(level, KLOG_SUBSYS_GENERIC, real_fmt, va); - - if (len < 0) { - return 0; - } - return len; -} - -int printk(const char* fmt, ...) -{ - __builtin_va_list va; - __builtin_va_start(va, fmt); - int ret = vprintk(fmt, va); - __builtin_va_end(va); - return ret; -} - -void klog_set_level(KlogLevel level) -{ - g_klog.console_level = level; -} - -void klog_set_subsys_mask(uint64 mask) -{ - g_klog.subsys_mask = mask; -} - -void klog_enable_subsys(KlogSubsys subsys) -{ - if (subsys < KLOG_SUBSYS_MAX) { - g_klog.subsys_mask |= (1ULL << subsys); - } -} - -void klog_disable_subsys(KlogSubsys subsys) -{ - if (subsys < KLOG_SUBSYS_MAX) { - g_klog.subsys_mask &= ~(1ULL << subsys); - } -} - -int klog_read(char* buf, int size) -{ - if (!g_klog.initialized || size <= 0) { - return 0; - } - - LockGuard guard(g_klog.lock); - return klog_read_internal(buf, size, &g_klog.tail, false); -} - -int klog_read_consume(char* buf, int size) -{ - if (!g_klog.initialized || size <= 0) { - return 0; - } - - LockGuard guard(g_klog.lock); - return klog_read_internal(buf, size, &g_klog.tail, true); -} - -void klog_flush(void) -{ - if (!g_klog.initialized) { - return; - } - - LockGuard guard(g_klog.lock); - - uint32 pos = g_klog.console_tail; - while (pos != g_klog.head) { - KlogEntryHdr hdr; - ring_read_bytes(pos, &hdr, sizeof(hdr)); - uint32 raw_len = hdr.len; - if (raw_len > KLOG_BUF_SIZE) { - break; - } - uint32 copy_len = raw_len; - if (copy_len > KLOG_LINE_MAX) { - copy_len = KLOG_LINE_MAX; - } - - char msg[KLOG_LINE_MAX + 1]; - ring_read_bytes(pos + sizeof(hdr), msg, copy_len); - msg[copy_len] = '\0'; - - char line[KLOG_LINE_MAX + 64]; - int line_len = klog_format_line(line, sizeof(line), hdr, msg, - static_cast<int>(copy_len)); - if (line_len > 0) { - console_emit(line, line_len); - } - - pos += sizeof(hdr) + raw_len; - } - - g_klog.console_tail = pos; -} - -bool klog_ratelimit(KlogRateLimit* rl, uint64 now) -{ - if (!rl) { - return true; - } - - if (rl->interval == 0 || rl->burst == 0) { - return true; - } - - if (now - rl->last_ts >= rl->interval) { - rl->last_ts = now; - rl->printed = 0; - rl->suppressed = 0; - } - - if (rl->printed < rl->burst) { - rl->printed++; - return true; - } - - rl->suppressed++; - return false; -} - -void klog_stats(KlogStats* out) -{ - if (!out) { - return; - } - - out->head = g_klog.head; - out->tail = g_klog.tail; - out->console_tail = g_klog.console_tail; - out->dropped = g_klog.dropped; - out->seq = g_klog.seq.load(__ATOMIC_RELAXED); -} - -/* ======================================================================== - * Kernel Panic — Fatal Error Handler - * - * Dumps all critical S-mode CSRs and the stack pointer to the console, - * then halts the CPU. Uses kprintf (NOT klog) to avoid deadlocking - * if the panic was triggered while holding the klog spinlock. - * ======================================================================== */ - -extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) { - /* Disable interrupts immediately */ - arch::cpu::intr_off(); - - g_klog.panic_mode = true; - - /* ---- Banner ---- */ - kprintf("\n\n"); - kprintf("========================================\n"); - kprintf(" *** KERNEL PANIC ***\n"); - kprintf("========================================\n"); - - if (msg != nullptr) { - kprintf(" Reason : %s", msg); - if (detail != nullptr) { - kprintf(": %s", detail); - } - kprintf("\n"); - } - - /* ---- Read S-mode CSRs ---- */ - uint64 sepc, scause, stval, sstatus, satp, stvec, sscratch, sp_val; - - __asm__ volatile("csrr %0, sepc" : "=r"(sepc)); - __asm__ volatile("csrr %0, scause" : "=r"(scause)); - __asm__ volatile("csrr %0, stval" : "=r"(stval)); - __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus)); - __asm__ volatile("csrr %0, satp" : "=r"(satp)); - __asm__ volatile("csrr %0, stvec" : "=r"(stvec)); - __asm__ volatile("csrr %0, sscratch" : "=r"(sscratch)); - __asm__ volatile("mv %0, sp" : "=r"(sp_val)); - - kprintf("\n--- S-mode CSR Dump ---\n"); - kprintf(" sepc = 0x%016llx\n", sepc); - kprintf(" scause = 0x%016llx", scause); - - /* Decode scause for convenience */ - bool is_interrupt = (scause >> 63) != 0; - uint64 cause_code = scause & 0x7FFFFFFFFFFFFFFFULL; - if (is_interrupt) { - kprintf(" (interrupt, code=%llu)\n", cause_code); - } else { - kprintf(" (exception, code=%llu)\n", cause_code); - } - - kprintf(" stval = 0x%016llx\n", stval); - kprintf(" sstatus = 0x%016llx", sstatus); - kprintf(" [SIE=%llu SPP=%llu SPIE=%llu]\n", - (sstatus >> 1) & 1, /* SIE */ - (sstatus >> 8) & 1, /* SPP */ - (sstatus >> 5) & 1); /* SPIE */ - kprintf(" satp = 0x%016llx", satp); - uint64 satp_mode = satp >> 60; - kprintf(" [mode=%llu]\n", satp_mode); - kprintf(" stvec = 0x%016llx\n", stvec); - kprintf(" sscratch = 0x%016llx\n", sscratch); - kprintf(" sp = 0x%016llx\n", sp_val); - - kprintf(" cpu_id = %llu\n", arch::cpu::id()); - kprintf(" time = %llu\n", arch::cpu::read_time()); - - kprintf("========================================\n\n"); - - /* Halt — spin forever with WFI to save power */ - while (true) { - arch::cpu::halt_until_interrupt(); - } -} diff --git a/na/lib/kobject.cc b/na/lib/kobject.cc deleted file mode 100644 index 8e8329e..0000000 --- a/na/lib/kobject.cc +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * kobject — implementation. - * - * Lives in lib/ (mirroring Linux's lib/kobject.c) because kobject is a - * generic primitive, not tied to the driver model. - * - * Reference: docs/specs/storage_stack.md §4.2 - */ - -#include <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 deleted file mode 100644 index a98de71..0000000 --- a/na/lib/kprintf.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * kprintf.cc — Kernel formatted output - * - * Uses mpaland/printf's vsnprintf_ as the core formatting engine. - * - * Architecture: - * vsnprintf_() ← pure formatting, writes to buffer - * ↓ - * kprintf() ← format + emit via console_write_all() - * kvprintf() ← va_list variant - * ksnprintf() ← format into caller's buffer - * - * Output path: - * Pre-registration: console_write_all() → early_putc() fallback - * Post-registration: console_write_all() → struct console chain - */ - -#include <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_() */ -/* ------------------------------------------------------------------ */ - -void _putchar(char character) -{ - early_putc(character); -} - -/* ------------------------------------------------------------------ */ -/* Public API */ -/* ------------------------------------------------------------------ */ - -extern "C" int kprintf(const char* fmt, ...) -{ - char buf[256]; - va_list va; - va_start(va, fmt); - int len = vsnprintf_(buf, sizeof(buf), fmt, va); - va_end(va); - - /* Emit to console under lock */ - console_lock_acquire(); - console_write_all(buf, len); - console_lock_release(); - return len; -} - -extern "C" int kvprintf(const char* fmt, va_list va) -{ - char buf[256]; - int len = vsnprintf_(buf, sizeof(buf), fmt, va); - - console_lock_acquire(); - console_write_all(buf, len); - console_lock_release(); - return len; -} - -extern "C" int ksnprintf(char* buf, int count, const char* fmt, ...) -{ - va_list va; - va_start(va, fmt); - int ret = vsnprintf_(buf, (size_t)count, fmt, va); - va_end(va); - return ret; -} diff --git a/na/lib/module.mk b/na/lib/module.mk deleted file mode 100644 index 0b178f8..0000000 --- a/na/lib/module.mk +++ /dev/null @@ -1,66 +0,0 @@ -# lib/module.mk -SRCS_CC += lib/cxx_abi.cc -SRCS_CC += lib/rbtree.cc -SRCS_CC += lib/kobject.cc -SRCS_CC += lib/kprintf.cc -SRCS_CC += lib/klog.cc -SRCS_CC += lib/random.cc -SRCS_C += lib/printf.c - -# ============================================================ -# musl string library (cross-platform with arch asm overrides) -# ============================================================ - -# Map LumeOS ARCH to musl string arch directory -# riscv has no musl asm optimizations → pure C fallback -MUSL_STRING_ARCH_riscv := -MUSL_STRING_ARCH_x86_64 := x86_64 -MUSL_STRING_ARCH_i386 := i386 -MUSL_STRING_ARCH_aarch64:= aarch64 -MUSL_STRING_ARCH_arm := arm - -MUSL_STRING_ARCH := $(MUSL_STRING_ARCH_$(ARCH)) - -# Collect arch-specific asm sources (if any for this arch) -MUSL_ASM_SRCS := -ifneq ($(MUSL_STRING_ARCH),) - MUSL_ASM_SRCS += $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.S) \ - $(wildcard lib/string/$(MUSL_STRING_ARCH)/*.s) -endif - -# Derive which C files are overridden by asm (strip path+ext to get base name) -MUSL_ASM_BASENAMES := $(sort $(basename $(notdir $(MUSL_ASM_SRCS)))) - -# All C sources, then filter out those with asm replacements -MUSL_ALL_C := $(wildcard lib/string/*.c) -MUSL_EXCLUDED_C := $(foreach name,$(MUSL_ASM_BASENAMES),lib/string/$(name).c) -MUSL_C_SRCS := $(filter-out $(MUSL_EXCLUDED_C),$(MUSL_ALL_C)) - -SRCS_C += $(MUSL_C_SRCS) -SRCS_S += $(filter %.S,$(MUSL_ASM_SRCS)) -SRCS_s += $(filter %.s,$(MUSL_ASM_SRCS)) - -# ============================================================ -# libfdt — kernel adapter shims around scripts/dtc/libfdt/. -# -# Each shim is two lines: -# #include <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/lib/printf.c b/na/lib/printf.c deleted file mode 100644 index 8a700ad..0000000 --- a/na/lib/printf.c +++ /dev/null @@ -1,914 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////
-// \author (c) Marco Paland (info@paland.com)
-// 2014-2019, PALANDesign Hannover, Germany
-//
-// \license The MIT License (MIT)
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
-// embedded systems with a very limited resources. These routines are thread
-// safe and reentrant!
-// Use this instead of the bloated standard/newlib printf cause these use
-// malloc for printf (and may not be thread safe).
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "printf.h"
-
-
-// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
-// printf_config.h header file
-// default: undefined
-#ifdef PRINTF_INCLUDE_CONFIG_H
-#include "printf_config.h"
-#endif
-
-
-// 'ntoa' conversion buffer size, this must be big enough to hold one converted
-// numeric number including padded zeros (dynamically created on stack)
-// default: 32 byte
-#ifndef PRINTF_NTOA_BUFFER_SIZE
-#define PRINTF_NTOA_BUFFER_SIZE 32U
-#endif
-
-// 'ftoa' conversion buffer size, this must be big enough to hold one converted
-// float number including padded zeros (dynamically created on stack)
-// default: 32 byte
-#ifndef PRINTF_FTOA_BUFFER_SIZE
-#define PRINTF_FTOA_BUFFER_SIZE 32U
-#endif
-
-// support for the floating point type (%f)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
-#define PRINTF_SUPPORT_FLOAT
-#endif
-
-// support for exponential floating point notation (%e/%g)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
-#define PRINTF_SUPPORT_EXPONENTIAL
-#endif
-
-// define the default floating point precision
-// default: 6 digits
-#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
-#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
-#endif
-
-// define the largest float suitable to print with %f
-// default: 1e9
-#ifndef PRINTF_MAX_FLOAT
-#define PRINTF_MAX_FLOAT 1e9
-#endif
-
-// support for the long long types (%llu or %p)
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
-#define PRINTF_SUPPORT_LONG_LONG
-#endif
-
-// support for the ptrdiff_t type (%t)
-// ptrdiff_t is normally defined in <stddef.h> as long or long long type
-// default: activated
-#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
-#define PRINTF_SUPPORT_PTRDIFF_T
-#endif
-
-///////////////////////////////////////////////////////////////////////////////
-
-// internal flag definitions
-#define FLAGS_ZEROPAD (1U << 0U)
-#define FLAGS_LEFT (1U << 1U)
-#define FLAGS_PLUS (1U << 2U)
-#define FLAGS_SPACE (1U << 3U)
-#define FLAGS_HASH (1U << 4U)
-#define FLAGS_UPPERCASE (1U << 5U)
-#define FLAGS_CHAR (1U << 6U)
-#define FLAGS_SHORT (1U << 7U)
-#define FLAGS_LONG (1U << 8U)
-#define FLAGS_LONG_LONG (1U << 9U)
-#define FLAGS_PRECISION (1U << 10U)
-#define FLAGS_ADAPT_EXP (1U << 11U)
-
-
-// import float.h for DBL_MAX
-#if defined(PRINTF_SUPPORT_FLOAT)
-#include <float.h>
-#endif
-
-
-// output function type
-typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
-
-
-// wrapper (used as buffer) for output function type
-typedef struct {
- void (*fct)(char character, void* arg);
- void* arg;
-} out_fct_wrap_type;
-
-
-// internal buffer output
-static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
-{
- if (idx < maxlen) {
- ((char*)buffer)[idx] = character;
- }
-}
-
-
-// internal null output
-static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)character; (void)buffer; (void)idx; (void)maxlen;
-}
-
-
-// internal _putchar wrapper
-static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)buffer; (void)idx; (void)maxlen;
- if (character) {
- _putchar(character);
- }
-}
-
-
-// internal output function wrapper
-static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
-{
- (void)idx; (void)maxlen;
- if (character) {
- // buffer is the output fct pointer
- ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
- }
-}
-
-
-// internal secure strlen
-// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
-static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
-{
- const char* s;
- for (s = str; *s && maxsize--; ++s);
- return (unsigned int)(s - str);
-}
-
-
-// internal test if char is a digit (0-9)
-// \return true if char is a digit
-static inline bool _is_digit(char ch)
-{
- return (ch >= '0') && (ch <= '9');
-}
-
-
-// internal ASCII string to unsigned int conversion
-static unsigned int _atoi(const char** str)
-{
- unsigned int i = 0U;
- while (_is_digit(**str)) {
- i = i * 10U + (unsigned int)(*((*str)++) - '0');
- }
- return i;
-}
-
-
-// output the specified string in reverse, taking care of any zero-padding
-static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
-{
- const size_t start_idx = idx;
-
- // pad spaces up to given width
- if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
- for (size_t i = len; i < width; i++) {
- out(' ', buffer, idx++, maxlen);
- }
- }
-
- // reverse string
- while (len) {
- out(buf[--len], buffer, idx++, maxlen);
- }
-
- // append pad spaces up to given width
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
-
- return idx;
-}
-
-
-// internal itoa format
-static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // pad leading zeros
- if (!(flags & FLAGS_LEFT)) {
- if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
- }
- while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- }
-
- // handle hash
- if (flags & FLAGS_HASH) {
- if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
- len--;
- if (len && (base == 16U)) {
- len--;
- }
- }
- if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'x';
- }
- else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'X';
- }
- else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
- buf[len++] = 'b';
- }
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- buf[len++] = '0';
- }
- }
-
- if (len < PRINTF_NTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
- }
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
- }
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
- }
- }
-
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
-}
-
-
-// internal itoa for 'long' type
-static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
-}
-
-
-// internal itoa for 'long long' type
-#if defined(PRINTF_SUPPORT_LONG_LONG)
-static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_NTOA_BUFFER_SIZE];
- size_t len = 0U;
-
- // no hash for 0 values
- if (!value) {
- flags &= ~FLAGS_HASH;
- }
-
- // write if precision != 0 and value is != 0
- if (!(flags & FLAGS_PRECISION) || value) {
- do {
- const char digit = (char)(value % base);
- buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
- value /= base;
- } while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
- }
-
- return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
-}
-#endif // PRINTF_SUPPORT_LONG_LONG
-
-
-#if defined(PRINTF_SUPPORT_FLOAT)
-
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
-#endif
-
-
-// internal ftoa for fixed decimal floating point
-static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- char buf[PRINTF_FTOA_BUFFER_SIZE];
- size_t len = 0U;
- double diff = 0.0;
-
- // powers of 10
- static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
-
- // test for special values
- if (value != value)
- return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
- if (value < -DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
- if (value > DBL_MAX)
- return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
-
- // test for very large values
- // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
- if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
-#else
- return 0U;
-#endif
- }
-
- // test for negative
- bool negative = false;
- if (value < 0) {
- negative = true;
- value = 0 - value;
- }
-
- // set default precision, if not set explicitly
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
- // limit precision to 9, cause a prec >= 10 can lead to overflow errors
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
- buf[len++] = '0';
- prec--;
- }
-
- int whole = (int)value;
- double tmp = (value - whole) * pow10[prec];
- unsigned long frac = (unsigned long)tmp;
- diff = tmp - frac;
-
- if (diff > 0.5) {
- ++frac;
- // handle rollover, e.g. case 0.99 with prec 1 is 1.0
- if (frac >= pow10[prec]) {
- frac = 0;
- ++whole;
- }
- }
- else if (diff < 0.5) {
- }
- else if ((frac == 0U) || (frac & 1U)) {
- // if halfway, round up if odd OR if last digit is 0
- ++frac;
- }
-
- if (prec == 0U) {
- diff = value - (double)whole;
- if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
- // exactly 0.5 and ODD, then round up
- // 1.5 -> 2, but 2.5 -> 2
- ++whole;
- }
- }
- else {
- unsigned int count = prec;
- // now do fractional part, as an unsigned number
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- --count;
- buf[len++] = (char)(48U + (frac % 10U));
- if (!(frac /= 10U)) {
- break;
- }
- }
- // add extra 0s
- while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
- buf[len++] = '0';
- }
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- // add decimal
- buf[len++] = '.';
- }
- }
-
- // do whole part, number is reversed
- while (len < PRINTF_FTOA_BUFFER_SIZE) {
- buf[len++] = (char)(48 + (whole % 10));
- if (!(whole /= 10)) {
- break;
- }
- }
-
- // pad leading zeros
- if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
- if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
- width--;
- }
- while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
- buf[len++] = '0';
- }
- }
-
- if (len < PRINTF_FTOA_BUFFER_SIZE) {
- if (negative) {
- buf[len++] = '-';
- }
- else if (flags & FLAGS_PLUS) {
- buf[len++] = '+'; // ignore the space if the '+' exists
- }
- else if (flags & FLAGS_SPACE) {
- buf[len++] = ' ';
- }
- }
-
- return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
-}
-
-
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
-// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
-static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
-{
- // check for NaN and special values
- if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
- return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
- }
-
- // determine the sign
- const bool negative = value < 0;
- if (negative) {
- value = -value;
- }
-
- // default precision
- if (!(flags & FLAGS_PRECISION)) {
- prec = PRINTF_DEFAULT_FLOAT_PRECISION;
- }
-
- // determine the decimal exponent
- // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
- union {
- uint64_t U;
- double F;
- } conv;
-
- conv.F = value;
- int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
- conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
- // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
- int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
- // now we want to compute 10^expval but we want to be sure it won't overflow
- exp2 = (int)(expval * 3.321928094887362 + 0.5);
- const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
- const double z2 = z * z;
- conv.U = (uint64_t)(exp2 + 1023) << 52U;
- // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
- conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
- // correct for rounding errors
- if (value < conv.F) {
- expval--;
- conv.F /= 10;
- }
-
- // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
- unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
-
- // in "%g" mode, "prec" is the number of *significant figures* not decimals
- if (flags & FLAGS_ADAPT_EXP) {
- // do we want to fall-back to "%f" mode?
- if ((value >= 1e-4) && (value < 1e6)) {
- if ((int)prec > expval) {
- prec = (unsigned)((int)prec - expval - 1);
- }
- else {
- prec = 0;
- }
- flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
- // no characters in exponent
- minwidth = 0U;
- expval = 0;
- }
- else {
- // we use one sigfig for the whole part
- if ((prec > 0) && (flags & FLAGS_PRECISION)) {
- --prec;
- }
- }
- }
-
- // will everything fit?
- unsigned int fwidth = width;
- if (width > minwidth) {
- // we didn't fall-back so subtract the characters required for the exponent
- fwidth -= minwidth;
- } else {
- // not enough characters, so go back to default sizing
- fwidth = 0U;
- }
- if ((flags & FLAGS_LEFT) && minwidth) {
- // if we're padding on the right, DON'T pad the floating part
- fwidth = 0U;
- }
-
- // rescale the float value
- if (expval) {
- value /= conv.F;
- }
-
- // output the floating part
- const size_t start_idx = idx;
- idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
-
- // output the exponent part
- if (minwidth) {
- // output the exponential symbol
- out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
- // output the exponent value
- idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
- // might need to right-pad spaces
- if (flags & FLAGS_LEFT) {
- while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
- }
- }
- return idx;
-}
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
-
-
-// internal vsnprintf
-static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
-{
- unsigned int flags, width, precision, n;
- size_t idx = 0U;
-
- if (!buffer) {
- // use null output function
- out = _out_null;
- }
-
- while (*format)
- {
- // format specifier? %[flags][width][.precision][length]
- if (*format != '%') {
- // no
- out(*format, buffer, idx++, maxlen);
- format++;
- continue;
- }
- else {
- // yes, evaluate it
- format++;
- }
-
- // evaluate flags
- flags = 0U;
- do {
- switch (*format) {
- case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
- case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
- case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
- case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
- case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
- default : n = 0U; break;
- }
- } while (n);
-
- // evaluate width field
- width = 0U;
- if (_is_digit(*format)) {
- width = _atoi(&format);
- }
- else if (*format == '*') {
- const int w = va_arg(va, int);
- if (w < 0) {
- flags |= FLAGS_LEFT; // reverse padding
- width = (unsigned int)-w;
- }
- else {
- width = (unsigned int)w;
- }
- format++;
- }
-
- // evaluate precision field
- precision = 0U;
- if (*format == '.') {
- flags |= FLAGS_PRECISION;
- format++;
- if (_is_digit(*format)) {
- precision = _atoi(&format);
- }
- else if (*format == '*') {
- const int prec = (int)va_arg(va, int);
- precision = prec > 0 ? (unsigned int)prec : 0U;
- format++;
- }
- }
-
- // evaluate length field
- switch (*format) {
- case 'l' :
- flags |= FLAGS_LONG;
- format++;
- if (*format == 'l') {
- flags |= FLAGS_LONG_LONG;
- format++;
- }
- break;
- case 'h' :
- flags |= FLAGS_SHORT;
- format++;
- if (*format == 'h') {
- flags |= FLAGS_CHAR;
- format++;
- }
- break;
-#if defined(PRINTF_SUPPORT_PTRDIFF_T)
- case 't' :
- flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
-#endif
- case 'j' :
- flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- case 'z' :
- flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
- format++;
- break;
- default :
- break;
- }
-
- // evaluate specifier
- switch (*format) {
- case 'd' :
- case 'i' :
- case 'u' :
- case 'x' :
- case 'X' :
- case 'o' :
- case 'b' : {
- // set the base
- unsigned int base;
- if (*format == 'x' || *format == 'X') {
- base = 16U;
- }
- else if (*format == 'o') {
- base = 8U;
- }
- else if (*format == 'b') {
- base = 2U;
- }
- else {
- base = 10U;
- flags &= ~FLAGS_HASH; // no hash for dec format
- }
- // uppercase
- if (*format == 'X') {
- flags |= FLAGS_UPPERCASE;
- }
-
- // no plus or space flag for u, x, X, o, b
- if ((*format != 'i') && (*format != 'd')) {
- flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
- }
-
- // ignore '0' flag when precision is given
- if (flags & FLAGS_PRECISION) {
- flags &= ~FLAGS_ZEROPAD;
- }
-
- // convert the integer
- if ((*format == 'i') || (*format == 'd')) {
- // signed
- if (flags & FLAGS_LONG_LONG) {
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- const long long value = va_arg(va, long long);
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
-#endif
- }
- else if (flags & FLAGS_LONG) {
- const long value = va_arg(va, long);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- else {
- const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
- }
- }
- else {
- // unsigned
- if (flags & FLAGS_LONG_LONG) {
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
-#endif
- }
- else if (flags & FLAGS_LONG) {
- idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
- }
- else {
- const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
- idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
- }
- }
- format++;
- break;
- }
-#if defined(PRINTF_SUPPORT_FLOAT)
- case 'f' :
- case 'F' :
- if (*format == 'F') flags |= FLAGS_UPPERCASE;
- idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
-#if defined(PRINTF_SUPPORT_EXPONENTIAL)
- case 'e':
- case 'E':
- case 'g':
- case 'G':
- if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
- if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
- idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
- format++;
- break;
-#endif // PRINTF_SUPPORT_EXPONENTIAL
-#endif // PRINTF_SUPPORT_FLOAT
- case 'c' : {
- unsigned int l = 1U;
- // pre padding
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // char output
- out((char)va_arg(va, int), buffer, idx++, maxlen);
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 's' : {
- const char* p = va_arg(va, char*);
- unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
- // pre padding
- if (flags & FLAGS_PRECISION) {
- l = (l < precision ? l : precision);
- }
- if (!(flags & FLAGS_LEFT)) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- // string output
- while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
- out(*(p++), buffer, idx++, maxlen);
- }
- // post padding
- if (flags & FLAGS_LEFT) {
- while (l++ < width) {
- out(' ', buffer, idx++, maxlen);
- }
- }
- format++;
- break;
- }
-
- case 'p' : {
- width = sizeof(void*) * 2U;
- flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
- if (is_ll) {
- idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
- }
- else {
-#endif
- idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
-#if defined(PRINTF_SUPPORT_LONG_LONG)
- }
-#endif
- format++;
- break;
- }
-
- case '%' :
- out('%', buffer, idx++, maxlen);
- format++;
- break;
-
- default :
- out(*format, buffer, idx++, maxlen);
- format++;
- break;
- }
- }
-
- // termination
- out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
-
- // return written chars without terminating \0
- return (int)idx;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-int printf_(const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- char buffer[1];
- const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
-
-
-int sprintf_(char* buffer, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
-
-
-int snprintf_(char* buffer, size_t count, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
- va_end(va);
- return ret;
-}
-
-
-int vprintf_(const char* format, va_list va)
-{
- char buffer[1];
- return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
-}
-
-
-int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
-{
- return _vsnprintf(_out_buffer, buffer, count, format, va);
-}
-
-
-int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
-{
- va_list va;
- va_start(va, format);
- const out_fct_wrap_type out_fct_wrap = { out, arg };
- const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
- va_end(va);
- return ret;
-}
diff --git a/na/lib/printf.h b/na/lib/printf.h deleted file mode 100644 index 8aa23cf..0000000 --- a/na/lib/printf.h +++ /dev/null @@ -1,129 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////
-// \author (c) Marco Paland (info@paland.com)
-// 2014-2019, PALANDesign Hannover, Germany
-//
-// \license The MIT License (MIT)
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
-// embedded systems with a very limited resources.
-// Use this instead of bloated standard/newlib printf.
-// These routines are thread safe and reentrant.
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _PRINTF_H_
-#define _PRINTF_H_
-
-#include <stdarg.h>
-
-/* LumeOS: avoid stddef.h — define types from compiler builtins directly.
- * This works in both C and C++ and matches our types.h definition. */
-#ifndef _SIZE_T_DEFINED
-#define _SIZE_T_DEFINED
-typedef __SIZE_TYPE__ size_t;
-typedef __PTRDIFF_TYPE__ ptrdiff_t;
-typedef __INTMAX_TYPE__ intmax_t;
-#endif
-
-/* LumeOS: kernel does not need floating-point formatted output */
-#define PRINTF_DISABLE_SUPPORT_FLOAT
-#define PRINTF_DISABLE_SUPPORT_EXPONENTIAL
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-/**
- * Output a character to a custom device like UART, used by the printf() function
- * This function is declared here only. You have to write your custom implementation somewhere
- * \param character Character to output
- */
-void _putchar(char character);
-
-
-/**
- * Tiny printf implementation
- * You have to implement _putchar if you use printf()
- * To avoid conflicts with the regular printf() API it is overridden by macro defines
- * and internal underscore-appended functions like printf_() are used
- * \param format A string that specifies the format of the output
- * \return The number of characters that are written into the array, not counting the terminating null character
- */
-#define printf printf_
-int printf_(const char* format, ...);
-
-
-/**
- * Tiny sprintf implementation
- * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
- * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
- * \param format A string that specifies the format of the output
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
- */
-#define sprintf sprintf_
-int sprintf_(char* buffer, const char* format, ...);
-
-
-/**
- * Tiny snprintf/vsnprintf implementation
- * \param buffer A pointer to the buffer where to store the formatted string
- * \param count The maximum number of characters to store in the buffer, including a terminating null character
- * \param format A string that specifies the format of the output
- * \param va A value identifying a variable arguments list
- * \return The number of characters that COULD have been written into the buffer, not counting the terminating
- * null character. A value equal or larger than count indicates truncation. Only when the returned value
- * is non-negative and less than count, the string has been completely written.
- */
-#define snprintf snprintf_
-#define vsnprintf vsnprintf_
-int snprintf_(char* buffer, size_t count, const char* format, ...);
-int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
-
-
-/**
- * Tiny vprintf implementation
- * \param format A string that specifies the format of the output
- * \param va A value identifying a variable arguments list
- * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
- */
-#define vprintf vprintf_
-int vprintf_(const char* format, va_list va);
-
-
-/**
- * printf with output function
- * You may use this as dynamic alternative to printf() with its fixed _putchar() output
- * \param out An output function which takes one character and an argument pointer
- * \param arg An argument pointer for user data passed to output function
- * \param format A string that specifies the format of the output
- * \return The number of characters that are sent to the output function, not counting the terminating null character
- */
-int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
-
-
-#ifdef __cplusplus
-}
-#endif
-
-
-#endif // _PRINTF_H_
diff --git a/na/lib/random.cc b/na/lib/random.cc deleted file mode 100644 index 428c75c..0000000 --- a/na/lib/random.cc +++ /dev/null @@ -1,152 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/random.h> -#include <arch/random.h> -#include <lume/klog.h> - -#include <lume/kprintf.h> - -/* - * Early Boot PRNG (xoshiro256**) - * State size: 256 bits (4 * 64-bit words) - * Extremely fast, excellent statistical properties. - */ - -static uint64 g_state[4]; -static bool g_initialized = false; -static uint64 g_init_seed = 0; -static RandomSeedSource g_seed_source = RandomSeedSource::Unknown; - -static inline uint64 rotl(const uint64 x, int k) { - return (x << k) | (x >> (64 - k)); -} - -/* SplitMix64 used to initialize xoshiro state from a single 64-bit seed */ -static uint64 splitmix64(uint64& state) { - uint64 z = (state += 0x9e3779b97f4a7c15ULL); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; - z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; - return z ^ (z >> 31); -} - -void random_early_init(uint64 seed) -{ - if (g_initialized) return; - - RandomSeedSource source = RandomSeedSource::Unknown; - - /* If seed is 0 (not provided by bootloader), try hardware */ - if (seed == 0) { - uint64 hw_val; - if (arch::get_hw_random(hw_val)) { - seed = hw_val; - source = RandomSeedSource::HwRandom; - } else { - /* Fallback: use boot cycles mixed with a constant */ - seed = arch::get_cycles() ^ 0xdeadbeefbadc0ffeULL; - source = RandomSeedSource::CycleFallback; - } - } else { - source = RandomSeedSource::FdtProvided; - } - - uint64 sm_state = seed; - g_state[0] = splitmix64(sm_state); - g_state[1] = splitmix64(sm_state); - g_state[2] = splitmix64(sm_state); - g_state[3] = splitmix64(sm_state); - - g_init_seed = seed; - g_seed_source = source; - g_initialized = true; - kprintf("[random] early PRNG initialized, seed=0x%llx, source=%s\n", - seed, random_seed_source_name(source)); -} - -uint64 get_random_u64() -{ - if (!g_initialized) { - random_early_init(0); - } - - const uint64 result = rotl(g_state[1] * 5, 7) * 9; - - const uint64 t = g_state[1] << 17; - - g_state[2] ^= g_state[0]; - g_state[3] ^= g_state[1]; - g_state[1] ^= g_state[2]; - g_state[0] ^= g_state[3]; - - g_state[2] ^= t; - g_state[3] = rotl(g_state[3], 45); - - /* Mix with current cycles for slight additional unpredictability */ - return result ^ arch::get_cycles(); -} - -uint32 get_random_u32() -{ - return static_cast<uint32>(get_random_u64() >> 32); -} - -void get_random_bytes(void* buf, size_t len) -{ - uint8* p = static_cast<uint8*>(buf); - while (len >= 8) { - uint64 val = get_random_u64(); - for (int i = 0; i < 8; i++) { - p[i] = (val >> (i * 8)) & 0xFF; - } - p += 8; - len -= 8; - } - if (len > 0) { - uint64 val = get_random_u64(); - for (size_t i = 0; i < len; i++) { - p[i] = (val >> (i * 8)) & 0xFF; - } - } -} - -void add_device_randomness(const void* buf, size_t len) -{ - /* - * For now, just mix into the xoshiro state using a simple XOR. - * In the future, this will feed the CSPRNG entropy pool. - */ - const uint8* p = static_cast<const uint8*>(buf); - uint64 mix = arch::get_cycles(); - for (size_t i = 0; i < len; i++) { - mix = (mix << 5) ^ (mix >> 59) ^ p[i]; - } - - g_state[0] ^= mix; - g_state[1] ^= arch::get_cycles(); -} - -RandomSeedSource random_early_seed_source() -{ - return g_seed_source; -} - -uint64 random_early_seed_value() -{ - return g_init_seed; -} - -const char* random_seed_source_name(RandomSeedSource source) -{ - switch (source) { - case RandomSeedSource::FdtProvided: - return "fdt"; - case RandomSeedSource::HwRandom: - return "hw"; - case RandomSeedSource::CycleFallback: - return "cycle"; - case RandomSeedSource::Unknown: - default: - return "unknown"; - } -} diff --git a/na/lib/rbtree.cc b/na/lib/rbtree.cc deleted file mode 100644 index 6718a1a..0000000 --- a/na/lib/rbtree.cc +++ /dev/null @@ -1,596 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - Red Black Trees - (C) 1999 Andrea Arcangeli <andrea@suse.de> - (C) 2002 David Woodhouse <dwmw2@infradead.org> - (C) 2012 Michel Lespinasse <walken@google.com> - - - linux/lib/rbtree.c -*/ - -#include <lume/rbtree_augmented.h> - - -/* - * red-black trees properties: https://en.wikipedia.org/wiki/Rbtree - * - * 1) A node is either red or black - * 2) The root is black - * 3) All leaves (nullptr) are black - * 4) Both children of every red node are black - * 5) Every simple path from root to leaves contains the same number - * of black nodes. - * - * 4 and 5 give the O(log n) guarantee, since 4 implies you cannot have two - * consecutive red nodes in a path and every red node is therefore followed by - * a black. So if B is the number of black nodes on every simple path (as per - * 5), then the longest possible path due to 4 is 2B. - * - * We shall indicate color with case, where black nodes are uppercase and red - * nodes will be lowercase. Unknown color nodes shall be drawn as red within - * parentheses and have some accompanying text comment. - */ - -/* - * Notes on lockless lookups: - * - * All stores to the tree structure (rb_left and rb_right) must be done using - * WRITE_ONCE(). And we must not inadvertently cause (temporary) loops in the - * tree structure as seen in program order. - * - * These two requirements will allow lockless iteration of the tree -- not - * correct iteration mind you, tree rotations are not atomic so a lookup might - * miss entire subtrees. - * - * But they do guarantee that any such traversal will only see valid elements - * and that it will indeed complete -- does not get stuck in a loop. - * - * It also guarantees that if the lookup returns an element it is the 'correct' - * one. But not returning an element does _NOT_ mean it's not present. - * - * NOTE: - * - * Stores to __rb_parent_color are not important for simple lookups so those - * are left undone as of now. Nor did I check for loops involving parent - * pointers. - */ - -static inline void rb_set_black(struct rb_node *rb) -{ - rb->__rb_parent_color += RB_BLACK; -} - -static inline struct rb_node *rb_red_parent(struct rb_node *red) -{ - return (struct rb_node *)red->__rb_parent_color; -} - -/* - * Helper function for rotations: - * - old's parent and color get assigned to _new - * - old gets assigned _new as a parent and 'color' as a color. - */ -static inline void -__rb_rotate_set_parents(struct rb_node *old, struct rb_node *_new, - struct rb_root *root, int color) -{ - struct rb_node *parent = rb_parent(old); - _new->__rb_parent_color = old->__rb_parent_color; - rb_set_parent_color(old, _new, color); - __rb_change_child(old, _new, parent, root); -} - -static __always_inline void -__rb_insert(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - struct rb_node *parent = rb_red_parent(node), *gparent, *tmp; - - while (true) { - /* - * Loop invariant: node is red. - */ - if (unlikely(!parent)) { - /* - * The inserted node is root. Either this is the - * first node, or we recursed at Case 1 below and - * are no longer violating 4). - */ - rb_set_parent_color(node, nullptr, RB_BLACK); - break; - } - - /* - * If there is a black parent, we are done. - * Otherwise, take some corrective action as, - * per 4), we don't want a red root or two - * consecutive red nodes. - */ - if(rb_is_black(parent)) - break; - - gparent = rb_red_parent(parent); - - tmp = gparent->rb_right; - if (parent != tmp) { /* parent == gparent->rb_left */ - if (tmp && rb_is_red(tmp)) { - /* - * Case 1 - node's uncle is red (color flips). - * - * G g - * / \ / \ - * p u --> P U - * / / - * n n - * - * However, since g's parent might be red, and - * 4) does not allow this, we need to recurse - * at g. - */ - rb_set_parent_color(tmp, gparent, RB_BLACK); - rb_set_parent_color(parent, gparent, RB_BLACK); - node = gparent; - parent = rb_parent(node); - rb_set_parent_color(node, parent, RB_RED); - continue; - } - - tmp = parent->rb_right; - if (node == tmp) { - /* - * Case 2 - node's uncle is black and node is - * the parent's right child (left rotate at parent). - * - * G G - * / \ / \ - * p U --> n U - * \ / - * n p - * - * This still leaves us in violation of 4), the - * continuation into Case 3 will fix that. - */ - tmp = node->rb_left; - WRITE_ONCE(parent->rb_right, tmp); - WRITE_ONCE(node->rb_left, parent); - if (tmp) - rb_set_parent_color(tmp, parent, - RB_BLACK); - rb_set_parent_color(parent, node, RB_RED); - augment_rotate(parent, node); - parent = node; - tmp = node->rb_right; - } - - /* - * Case 3 - node's uncle is black and node is - * the parent's left child (right rotate at gparent). - * - * G P - * / \ / \ - * p U --> n g - * / \ - * n U - */ - WRITE_ONCE(gparent->rb_left, tmp); /* == parent->rb_right */ - WRITE_ONCE(parent->rb_right, gparent); - if (tmp) - rb_set_parent_color(tmp, gparent, RB_BLACK); - __rb_rotate_set_parents(gparent, parent, root, RB_RED); - augment_rotate(gparent, parent); - break; - } else { - tmp = gparent->rb_left; - if (tmp && rb_is_red(tmp)) { - /* Case 1 - color flips */ - rb_set_parent_color(tmp, gparent, RB_BLACK); - rb_set_parent_color(parent, gparent, RB_BLACK); - node = gparent; - parent = rb_parent(node); - rb_set_parent_color(node, parent, RB_RED); - continue; - } - - tmp = parent->rb_left; - if (node == tmp) { - /* Case 2 - right rotate at parent */ - tmp = node->rb_right; - WRITE_ONCE(parent->rb_left, tmp); - WRITE_ONCE(node->rb_right, parent); - if (tmp) - rb_set_parent_color(tmp, parent, - RB_BLACK); - rb_set_parent_color(parent, node, RB_RED); - augment_rotate(parent, node); - parent = node; - tmp = node->rb_left; - } - - /* Case 3 - left rotate at gparent */ - WRITE_ONCE(gparent->rb_right, tmp); /* == parent->rb_left */ - WRITE_ONCE(parent->rb_left, gparent); - if (tmp) - rb_set_parent_color(tmp, gparent, RB_BLACK); - __rb_rotate_set_parents(gparent, parent, root, RB_RED); - augment_rotate(gparent, parent); - break; - } - } -} - -/* - * Inline version for rb_erase() use - we want to be able to inline - * and eliminate the dummy_rotate callback there - */ -static __always_inline void -____rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - struct rb_node *node = nullptr, *sibling, *tmp1, *tmp2; - - while (true) { - /* - * Loop invariants: - * - node is black (or nullptr on first iteration) - * - node is not the root (parent is not nullptr) - * - All leaf paths going through parent and node have a - * black node count that is 1 lower than other leaf paths. - */ - sibling = parent->rb_right; - if (node != sibling) { /* node == parent->rb_left */ - if (rb_is_red(sibling)) { - /* - * Case 1 - left rotate at parent - * - * P S - * / \ / \ - * N s --> p Sr - * / \ / \ - * Sl Sr N Sl - */ - tmp1 = sibling->rb_left; - WRITE_ONCE(parent->rb_right, tmp1); - WRITE_ONCE(sibling->rb_left, parent); - rb_set_parent_color(tmp1, parent, RB_BLACK); - __rb_rotate_set_parents(parent, sibling, root, - RB_RED); - augment_rotate(parent, sibling); - sibling = tmp1; - } - tmp1 = sibling->rb_right; - if (!tmp1 || rb_is_black(tmp1)) { - tmp2 = sibling->rb_left; - if (!tmp2 || rb_is_black(tmp2)) { - /* - * Case 2 - sibling color flip - * (p could be either color here) - * - * (p) (p) - * / \ / \ - * N S --> N s - * / \ / \ - * Sl Sr Sl Sr - * - * This leaves us violating 5) which - * can be fixed by flipping p to black - * if it was red, or by recursing at p. - * p is red when coming from Case 1. - */ - rb_set_parent_color(sibling, parent, - RB_RED); - if (rb_is_red(parent)) - rb_set_black(parent); - else { - node = parent; - parent = rb_parent(node); - if (parent) - continue; - } - break; - } - /* - * Case 3 - right rotate at sibling - * (p could be either color here) - * - * (p) (p) - * / \ / \ - * N S --> N sl - * / \ \ - * sl Sr S - * \ - * Sr - * - * Note: p might be red, and then both - * p and sl are red after rotation(which - * breaks property 4). This is fixed in - * Case 4 (in __rb_rotate_set_parents() - * which set sl the color of p - * and set p RB_BLACK) - * - * (p) (sl) - * / \ / \ - * N sl --> P S - * \ / \ - * S N Sr - * \ - * Sr - */ - tmp1 = tmp2->rb_right; - WRITE_ONCE(sibling->rb_left, tmp1); - WRITE_ONCE(tmp2->rb_right, sibling); - WRITE_ONCE(parent->rb_right, tmp2); - if (tmp1) - rb_set_parent_color(tmp1, sibling, - RB_BLACK); - augment_rotate(sibling, tmp2); - tmp1 = sibling; - sibling = tmp2; - } - /* - * Case 4 - left rotate at parent + color flips - * (p and sl could be either color here. - * After rotation, p becomes black, s acquires - * p's color, and sl keeps its color) - * - * (p) (s) - * / \ / \ - * N S --> P Sr - * / \ / \ - * (sl) sr N (sl) - */ - tmp2 = sibling->rb_left; - WRITE_ONCE(parent->rb_right, tmp2); - WRITE_ONCE(sibling->rb_left, parent); - rb_set_parent_color(tmp1, sibling, RB_BLACK); - if (tmp2) - rb_set_parent(tmp2, parent); - __rb_rotate_set_parents(parent, sibling, root, - RB_BLACK); - augment_rotate(parent, sibling); - break; - } else { - sibling = parent->rb_left; - if (rb_is_red(sibling)) { - /* Case 1 - right rotate at parent */ - tmp1 = sibling->rb_right; - WRITE_ONCE(parent->rb_left, tmp1); - WRITE_ONCE(sibling->rb_right, parent); - rb_set_parent_color(tmp1, parent, RB_BLACK); - __rb_rotate_set_parents(parent, sibling, root, - RB_RED); - augment_rotate(parent, sibling); - sibling = tmp1; - } - tmp1 = sibling->rb_left; - if (!tmp1 || rb_is_black(tmp1)) { - tmp2 = sibling->rb_right; - if (!tmp2 || rb_is_black(tmp2)) { - /* Case 2 - sibling color flip */ - rb_set_parent_color(sibling, parent, - RB_RED); - if (rb_is_red(parent)) - rb_set_black(parent); - else { - node = parent; - parent = rb_parent(node); - if (parent) - continue; - } - break; - } - /* Case 3 - left rotate at sibling */ - tmp1 = tmp2->rb_left; - WRITE_ONCE(sibling->rb_right, tmp1); - WRITE_ONCE(tmp2->rb_left, sibling); - WRITE_ONCE(parent->rb_left, tmp2); - if (tmp1) - rb_set_parent_color(tmp1, sibling, - RB_BLACK); - augment_rotate(sibling, tmp2); - tmp1 = sibling; - sibling = tmp2; - } - /* Case 4 - right rotate at parent + color flips */ - tmp2 = sibling->rb_right; - WRITE_ONCE(parent->rb_left, tmp2); - WRITE_ONCE(sibling->rb_right, parent); - rb_set_parent_color(tmp1, sibling, RB_BLACK); - if (tmp2) - rb_set_parent(tmp2, parent); - __rb_rotate_set_parents(parent, sibling, root, - RB_BLACK); - augment_rotate(parent, sibling); - break; - } - } -} - -/* Non-inline version for rb_erase_augmented() use */ -void __rb_erase_color(struct rb_node *parent, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - ____rb_erase_color(parent, root, augment_rotate); -} - -/* - * Non-augmented rbtree manipulation functions. - * - * We use dummy augmented callbacks here, and have the compiler optimize them - * out of the rb_insert_color() and rb_erase() function definitions. - */ - -static inline void dummy_propagate(struct rb_node *node, struct rb_node *stop) {} -static inline void dummy_copy(struct rb_node *old, struct rb_node *_new) {} -static inline void dummy_rotate(struct rb_node *old, struct rb_node *_new) {} - -static const struct rb_augment_callbacks dummy_callbacks = { - .propagate = dummy_propagate, - .copy = dummy_copy, - .rotate = dummy_rotate -}; - -void rb_insert_color(struct rb_node *node, struct rb_root *root) -{ - __rb_insert(node, root, dummy_rotate); -} - -void rb_erase(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *rebalance; - rebalance = __rb_erase_augmented(node, root, &dummy_callbacks); - if (rebalance) - ____rb_erase_color(rebalance, root, dummy_rotate); -} - - -/* - * Augmented rbtree manipulation functions. - * - * This instantiates the same __always_inline functions as in the non-augmented - * case, but this time with user-defined callbacks. - */ - -void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, - void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)) -{ - __rb_insert(node, root, augment_rotate); -} - -struct rb_node *rb_next(const struct rb_node *node) -{ - struct rb_node *parent; - - if (RB_EMPTY_NODE(node)) - return nullptr; - - /* - * If we have a right-hand child, go down and then left as far - * as we can. - */ - if (node->rb_right) { - node = node->rb_right; - while (node->rb_left) - node = node->rb_left; - return (struct rb_node *)node; - } - - /* - * No right-hand children. Everything down and left is smaller than us, - * so any 'next' node must be in the general direction of our parent. - * Go up the tree; any time the ancestor is a right-hand child of its - * parent, keep going up. First time it's a left-hand child of its - * parent, said parent is our 'next' node. - */ - while ((parent = rb_parent(node)) && node == parent->rb_right) - node = parent; - - return parent; -} - -struct rb_node *rb_prev(const struct rb_node *node) -{ - struct rb_node *parent; - - if (RB_EMPTY_NODE(node)) - return nullptr; - - /* - * If we have a left-hand child, go down and then right as far - * as we can. - */ - if (node->rb_left) { - node = node->rb_left; - while (node->rb_right) - node = node->rb_right; - return (struct rb_node *)node; - } - - /* - * No left-hand children. Go up till we find an ancestor which - * is a right-hand child of its parent. - */ - while ((parent = rb_parent(node)) && node == parent->rb_left) - node = parent; - - return parent; -} - -void rb_replace_node(struct rb_node *victim, struct rb_node *_new, - struct rb_root *root) -{ - struct rb_node *parent = rb_parent(victim); - - /* Copy the pointers/colour from the victim to the replacement */ - *_new = *victim; - - /* Set the surrounding nodes to point to the replacement */ - if (victim->rb_left) - rb_set_parent(victim->rb_left, _new); - if (victim->rb_right) - rb_set_parent(victim->rb_right, _new); - __rb_change_child(victim, _new, parent, root); -} - - -static struct rb_node *rb_left_deepest_node(const struct rb_node *node) -{ - for (;;) { - if (node->rb_left) - node = node->rb_left; - else if (node->rb_right) - node = node->rb_right; - else - return (struct rb_node *)node; - } -} - -struct rb_node *rb_next_postorder(const struct rb_node *node) -{ - const struct rb_node *parent; - if (!node) - return nullptr; - parent = rb_parent(node); - - /* If we're sitting on node, we've already seen our children */ - if (parent && node == parent->rb_left && parent->rb_right) { - /* If we are the parent's left node, go to the parent's right - * node then all the way down to the left */ - return rb_left_deepest_node(parent->rb_right); - } else - /* Otherwise we are the parent's right node, and the parent - * should be next */ - return (struct rb_node *)parent; -} - -struct rb_node *rb_first_postorder(const struct rb_root *root) -{ - if (!root->rb_node) - return nullptr; - - return rb_left_deepest_node(root->rb_node); -} - -struct rb_node *rb_first(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return nullptr; - while (n->rb_left) - n = n->rb_left; - return n; -} - -struct rb_node *rb_last(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return nullptr; - while (n->rb_right) - n = n->rb_right; - return n; -} diff --git a/na/lib/string/COPYRIGHT b/na/lib/string/COPYRIGHT deleted file mode 100644 index 2f15edc..0000000 --- a/na/lib/string/COPYRIGHT +++ /dev/null @@ -1,193 +0,0 @@ -musl as a whole is licensed under the following standard MIT license: - ----------------------------------------------------------------------- -Copyright © 2005-2020 Rich Felker, et al. - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------- - -Authors/contributors include: - -A. Wilcox -Ada Worcester -Alex Dowad -Alex Suykov -Alexander Monakov -Andre McCurdy -Andrew Kelley -Anthony G. Basile -Aric Belsito -Arvid Picciani -Bartosz Brachaczek -Benjamin Peterson -Bobby Bingham -Boris Brezillon -Brent Cook -Chris Spiegel -Clément Vasseur -Daniel Micay -Daniel Sabogal -Daurnimator -David Carlier -David Edelsohn -Denys Vlasenko -Dmitry Ivanov -Dmitry V. Levin -Drew DeVault -Emil Renner Berthing -Fangrui Song -Felix Fietkau -Felix Janda -Gianluca Anzolin -Hauke Mehrtens -He X -Hiltjo Posthuma -Isaac Dunham -Jaydeep Patil -Jens Gustedt -Jeremy Huntwork -Jo-Philipp Wich -Joakim Sindholt -John Spencer -Julien Ramseier -Justin Cormack -Kaarle Ritvanen -Khem Raj -Kylie McClain -Leah Neukirchen -Luca Barbato -Luka Perkov -Lynn Ochs -M Farkas-Dyck (Strake) -Mahesh Bodapati -Markus Wichmann -Masanori Ogino -Michael Clark -Michael Forney -Mikhail Kremnyov -Natanael Copa -Nicholas J. Kain -orc -Pascal Cuoq -Patrick Oppenlander -Petr Hosek -Petr Skocik -Pierre Carrier -Reini Urban -Rich Felker -Richard Pennington -Ryan Fairfax -Samuel Holland -Segev Finer -Shiz -sin -Solar Designer -Stefan Kristiansson -Stefan O'Rear -Szabolcs Nagy -Timo Teräs -Trutz Behn -Will Dietz -William Haddon -William Pitcock - -Portions of this software are derived from third-party works licensed -under terms compatible with the above MIT license: - -The TRE regular expression implementation (src/regex/reg* and -src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed -under a 2-clause BSD license (license text in the source files). The -included version has been heavily modified by Rich Felker in 2012, in -the interests of size, simplicity, and namespace cleanliness. - -Much of the math library code (src/math/* and src/complex/*) is -Copyright © 1993,2004 Sun Microsystems or -Copyright © 2003-2011 David Schultz or -Copyright © 2003-2009 Steven G. Kargl or -Copyright © 2003-2009 Bruce D. Evans or -Copyright © 2008 Stephen L. Moshier or -Copyright © 2017-2018 Arm Limited -and labelled as such in comments in the individual source files. All -have been licensed under extremely permissive terms. - -The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 -The Android Open Source Project and is licensed under a two-clause BSD -license. It was taken from Bionic libc, used on Android. - -The AArch64 memcpy and memset code (src/string/aarch64/*) are -Copyright © 1999-2019, Arm Limited. - -The implementation of DES for crypt (src/crypt/crypt_des.c) is -Copyright © 1994 David Burren. It is licensed under a BSD license. - -The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was -originally written by Solar Designer and placed into the public -domain. The code also comes with a fallback permissive license for use -in jurisdictions that may not recognize the public domain. - -The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 -Lynn Ochs and is licensed under an MIT-style license. - -The x86_64 port was written by Nicholas J. Kain and is licensed under -the standard MIT terms. - -The mips and microblaze ports were originally written by Richard -Pennington for use in the ellcc project. The original code was adapted -by Rich Felker for build system and code conventions during upstream -integration. It is licensed under the standard MIT terms. - -The mips64 port was contributed by Imagination Technologies and is -licensed under the standard MIT terms. - -The powerpc port was also originally written by Richard Pennington, -and later supplemented and integrated by John Spencer. It is licensed -under the standard MIT terms. - -All other files which have no copyright comments are original works -produced specifically for use as part of this library, written either -by Rich Felker, the main author of the library, or by one or more -contibutors listed above. Details on authorship of individual files -can be found in the git version control history of the project. The -omission of copyright and license comments in each file is in the -interest of source tree size. - -In addition, permission is hereby granted for all public header files -(include/* and arch/*/bits/*) and crt files intended to be linked into -applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit -the copyright notice and permission notice otherwise required by the -license, and to use these files without any requirement of -attribution. These files include substantial contributions from: - -Bobby Bingham -John Spencer -Nicholas J. Kain -Rich Felker -Richard Pennington -Stefan Kristiansson -Szabolcs Nagy - -all of whom have explicitly granted such permission. - -This file previously contained text expressing a belief that most of -the files covered by the above exception were sufficiently trivial not -to be subject to copyright, resulting in confusion over whether it -negated the permissions granted in the license. In the spirit of -permissive licensing, and of not having licensing issues being an -obstacle to adoption, that text has been removed. diff --git a/na/lib/string/aarch64/memcpy.S b/na/lib/string/aarch64/memcpy.S deleted file mode 100644 index 48bb8a8..0000000 --- a/na/lib/string/aarch64/memcpy.S +++ /dev/null @@ -1,186 +0,0 @@ -/* - * memcpy - copy memory area - * - * Copyright (c) 2012-2020, Arm Limited. - * SPDX-License-Identifier: MIT - */ - -/* Assumptions: - * - * ARMv8-a, AArch64, unaligned accesses. - * - */ - -#define dstin x0 -#define src x1 -#define count x2 -#define dst x3 -#define srcend x4 -#define dstend x5 -#define A_l x6 -#define A_lw w6 -#define A_h x7 -#define B_l x8 -#define B_lw w8 -#define B_h x9 -#define C_l x10 -#define C_lw w10 -#define C_h x11 -#define D_l x12 -#define D_h x13 -#define E_l x14 -#define E_h x15 -#define F_l x16 -#define F_h x17 -#define G_l count -#define G_h dst -#define H_l src -#define H_h srcend -#define tmp1 x14 - -/* This implementation of memcpy uses unaligned accesses and branchless - sequences to keep the code small, simple and improve performance. - - Copies are split into 3 main cases: small copies of up to 32 bytes, medium - copies of up to 128 bytes, and large copies. The overhead of the overlap - check is negligible since it is only required for large copies. - - Large copies use a software pipelined loop processing 64 bytes per iteration. - The destination pointer is 16-byte aligned to minimize unaligned accesses. - The loop tail is handled by always copying 64 bytes from the end. -*/ - -.global memcpy -.type memcpy,%function -memcpy: - add srcend, src, count - add dstend, dstin, count - cmp count, 128 - b.hi .Lcopy_long - cmp count, 32 - b.hi .Lcopy32_128 - - /* Small copies: 0..32 bytes. */ - cmp count, 16 - b.lo .Lcopy16 - ldp A_l, A_h, [src] - ldp D_l, D_h, [srcend, -16] - stp A_l, A_h, [dstin] - stp D_l, D_h, [dstend, -16] - ret - - /* Copy 8-15 bytes. */ -.Lcopy16: - tbz count, 3, .Lcopy8 - ldr A_l, [src] - ldr A_h, [srcend, -8] - str A_l, [dstin] - str A_h, [dstend, -8] - ret - - .p2align 3 - /* Copy 4-7 bytes. */ -.Lcopy8: - tbz count, 2, .Lcopy4 - ldr A_lw, [src] - ldr B_lw, [srcend, -4] - str A_lw, [dstin] - str B_lw, [dstend, -4] - ret - - /* Copy 0..3 bytes using a branchless sequence. */ -.Lcopy4: - cbz count, .Lcopy0 - lsr tmp1, count, 1 - ldrb A_lw, [src] - ldrb C_lw, [srcend, -1] - ldrb B_lw, [src, tmp1] - strb A_lw, [dstin] - strb B_lw, [dstin, tmp1] - strb C_lw, [dstend, -1] -.Lcopy0: - ret - - .p2align 4 - /* Medium copies: 33..128 bytes. */ -.Lcopy32_128: - ldp A_l, A_h, [src] - ldp B_l, B_h, [src, 16] - ldp C_l, C_h, [srcend, -32] - ldp D_l, D_h, [srcend, -16] - cmp count, 64 - b.hi .Lcopy128 - stp A_l, A_h, [dstin] - stp B_l, B_h, [dstin, 16] - stp C_l, C_h, [dstend, -32] - stp D_l, D_h, [dstend, -16] - ret - - .p2align 4 - /* Copy 65..128 bytes. */ -.Lcopy128: - ldp E_l, E_h, [src, 32] - ldp F_l, F_h, [src, 48] - cmp count, 96 - b.ls .Lcopy96 - ldp G_l, G_h, [srcend, -64] - ldp H_l, H_h, [srcend, -48] - stp G_l, G_h, [dstend, -64] - stp H_l, H_h, [dstend, -48] -.Lcopy96: - stp A_l, A_h, [dstin] - stp B_l, B_h, [dstin, 16] - stp E_l, E_h, [dstin, 32] - stp F_l, F_h, [dstin, 48] - stp C_l, C_h, [dstend, -32] - stp D_l, D_h, [dstend, -16] - ret - - .p2align 4 - /* Copy more than 128 bytes. */ -.Lcopy_long: - - /* Copy 16 bytes and then align dst to 16-byte alignment. */ - - ldp D_l, D_h, [src] - and tmp1, dstin, 15 - bic dst, dstin, 15 - sub src, src, tmp1 - add count, count, tmp1 /* Count is now 16 too large. */ - ldp A_l, A_h, [src, 16] - stp D_l, D_h, [dstin] - ldp B_l, B_h, [src, 32] - ldp C_l, C_h, [src, 48] - ldp D_l, D_h, [src, 64]! - subs count, count, 128 + 16 /* Test and readjust count. */ - b.ls .Lcopy64_from_end - -.Lloop64: - stp A_l, A_h, [dst, 16] - ldp A_l, A_h, [src, 16] - stp B_l, B_h, [dst, 32] - ldp B_l, B_h, [src, 32] - stp C_l, C_h, [dst, 48] - ldp C_l, C_h, [src, 48] - stp D_l, D_h, [dst, 64]! - ldp D_l, D_h, [src, 64]! - subs count, count, 64 - b.hi .Lloop64 - - /* Write the last iteration and copy 64 bytes from the end. */ -.Lcopy64_from_end: - ldp E_l, E_h, [srcend, -64] - stp A_l, A_h, [dst, 16] - ldp A_l, A_h, [srcend, -48] - stp B_l, B_h, [dst, 32] - ldp B_l, B_h, [srcend, -32] - stp C_l, C_h, [dst, 48] - ldp C_l, C_h, [srcend, -16] - stp D_l, D_h, [dst, 64] - stp E_l, E_h, [dstend, -64] - stp A_l, A_h, [dstend, -48] - stp B_l, B_h, [dstend, -32] - stp C_l, C_h, [dstend, -16] - ret - -.size memcpy,.-memcpy diff --git a/na/lib/string/aarch64/memset.S b/na/lib/string/aarch64/memset.S deleted file mode 100644 index f0d29b7..0000000 --- a/na/lib/string/aarch64/memset.S +++ /dev/null @@ -1,115 +0,0 @@ -/* - * memset - fill memory with a constant byte - * - * Copyright (c) 2012-2020, Arm Limited. - * SPDX-License-Identifier: MIT - */ - -/* Assumptions: - * - * ARMv8-a, AArch64, Advanced SIMD, unaligned accesses. - * - */ - -#define dstin x0 -#define val x1 -#define valw w1 -#define count x2 -#define dst x3 -#define dstend x4 -#define zva_val x5 - -.global memset -.type memset,%function -memset: - - dup v0.16B, valw - add dstend, dstin, count - - cmp count, 96 - b.hi .Lset_long - cmp count, 16 - b.hs .Lset_medium - mov val, v0.D[0] - - /* Set 0..15 bytes. */ - tbz count, 3, 1f - str val, [dstin] - str val, [dstend, -8] - ret - nop -1: tbz count, 2, 2f - str valw, [dstin] - str valw, [dstend, -4] - ret -2: cbz count, 3f - strb valw, [dstin] - tbz count, 1, 3f - strh valw, [dstend, -2] -3: ret - - /* Set 17..96 bytes. */ -.Lset_medium: - str q0, [dstin] - tbnz count, 6, .Lset96 - str q0, [dstend, -16] - tbz count, 5, 1f - str q0, [dstin, 16] - str q0, [dstend, -32] -1: ret - - .p2align 4 - /* Set 64..96 bytes. Write 64 bytes from the start and - 32 bytes from the end. */ -.Lset96: - str q0, [dstin, 16] - stp q0, q0, [dstin, 32] - stp q0, q0, [dstend, -32] - ret - - .p2align 4 -.Lset_long: - and valw, valw, 255 - bic dst, dstin, 15 - str q0, [dstin] - cmp count, 160 - ccmp valw, 0, 0, hs - b.ne .Lno_zva - -#ifndef SKIP_ZVA_CHECK - mrs zva_val, dczid_el0 - and zva_val, zva_val, 31 - cmp zva_val, 4 /* ZVA size is 64 bytes. */ - b.ne .Lno_zva -#endif - str q0, [dst, 16] - stp q0, q0, [dst, 32] - bic dst, dst, 63 - sub count, dstend, dst /* Count is now 64 too large. */ - sub count, count, 128 /* Adjust count and bias for loop. */ - - .p2align 4 -.Lzva_loop: - add dst, dst, 64 - dc zva, dst - subs count, count, 64 - b.hi .Lzva_loop - stp q0, q0, [dstend, -64] - stp q0, q0, [dstend, -32] - ret - -.Lno_zva: - sub count, dstend, dst /* Count is 16 too large. */ - sub dst, dst, 16 /* Dst is biased by -32. */ - sub count, count, 64 + 16 /* Adjust count and bias for loop. */ -.Lno_zva_loop: - stp q0, q0, [dst, 32] - stp q0, q0, [dst, 64]! - subs count, count, 64 - b.hi .Lno_zva_loop - stp q0, q0, [dstend, -64] - stp q0, q0, [dstend, -32] - ret - -.size memset,.-memset - diff --git a/na/lib/string/arm/__aeabi_memcpy.s b/na/lib/string/arm/__aeabi_memcpy.s deleted file mode 100644 index 3a527e4..0000000 --- a/na/lib/string/arm/__aeabi_memcpy.s +++ /dev/null @@ -1,45 +0,0 @@ -.syntax unified - -.global __aeabi_memcpy8 -.global __aeabi_memcpy4 -.global __aeabi_memcpy -.global __aeabi_memmove8 -.global __aeabi_memmove4 -.global __aeabi_memmove - -.type __aeabi_memcpy8,%function -.type __aeabi_memcpy4,%function -.type __aeabi_memcpy,%function -.type __aeabi_memmove8,%function -.type __aeabi_memmove4,%function -.type __aeabi_memmove,%function - -__aeabi_memmove8: -__aeabi_memmove4: -__aeabi_memmove: - cmp r0, r1 - bls 3f - cmp r2, #0 - beq 2f - adds r0, r0, r2 - adds r2, r1, r2 -1: subs r2, r2, #1 - ldrb r3, [r2] - subs r0, r0, #1 - strb r3, [r0] - cmp r1, r2 - bne 1b -2: bx lr -__aeabi_memcpy8: -__aeabi_memcpy4: -__aeabi_memcpy: -3: cmp r2, #0 - beq 2f - adds r2, r1, r2 -1: ldrb r3, [r1] - adds r1, r1, #1 - strb r3, [r0] - adds r0, r0, #1 - cmp r1, r2 - bne 1b -2: bx lr diff --git a/na/lib/string/arm/__aeabi_memset.s b/na/lib/string/arm/__aeabi_memset.s deleted file mode 100644 index f9f6058..0000000 --- a/na/lib/string/arm/__aeabi_memset.s +++ /dev/null @@ -1,31 +0,0 @@ -.syntax unified - -.global __aeabi_memclr8 -.global __aeabi_memclr4 -.global __aeabi_memclr -.global __aeabi_memset8 -.global __aeabi_memset4 -.global __aeabi_memset - -.type __aeabi_memclr8,%function -.type __aeabi_memclr4,%function -.type __aeabi_memclr,%function -.type __aeabi_memset8,%function -.type __aeabi_memset4,%function -.type __aeabi_memset,%function - -__aeabi_memclr8: -__aeabi_memclr4: -__aeabi_memclr: - movs r2, #0 -__aeabi_memset8: -__aeabi_memset4: -__aeabi_memset: - cmp r1, #0 - beq 2f - adds r1, r0, r1 -1: strb r2, [r0] - adds r0, r0, #1 - cmp r1, r0 - bne 1b -2: bx lr diff --git a/na/lib/string/arm/memcpy.S b/na/lib/string/arm/memcpy.S deleted file mode 100644 index 869e344..0000000 --- a/na/lib/string/arm/memcpy.S +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - - -/* - * Optimized memcpy() for ARM. - * - * note that memcpy() always returns the destination pointer, - * so we have to preserve R0. - */ - -/* - * This file has been modified from the original for use in musl libc. - * The main changes are: addition of .type memcpy,%function to make the - * code safely callable from thumb mode, adjusting the return - * instructions to be compatible with pre-thumb ARM cpus, removal of - * prefetch code that is not compatible with older cpus and support for - * building as thumb 2 and big-endian. - */ - -.syntax unified - -.global memcpy -.type memcpy,%function -memcpy: - /* The stack must always be 64-bits aligned to be compliant with the - * ARM ABI. Since we have to save R0, we might as well save R4 - * which we can use for better pipelining of the reads below - */ - .fnstart - .save {r0, r4, lr} - stmfd sp!, {r0, r4, lr} - /* Making room for r5-r11 which will be spilled later */ - .pad #28 - sub sp, sp, #28 - - /* it simplifies things to take care of len<4 early */ - cmp r2, #4 - blo copy_last_3_and_return - - /* compute the offset to align the source - * offset = (4-(src&3))&3 = -src & 3 - */ - rsb r3, r1, #0 - ands r3, r3, #3 - beq src_aligned - - /* align source to 32 bits. We need to insert 2 instructions between - * a ldr[b|h] and str[b|h] because byte and half-word instructions - * stall 2 cycles. - */ - movs r12, r3, lsl #31 - sub r2, r2, r3 /* we know that r3 <= r2 because r2 >= 4 */ - ldrbmi r3, [r1], #1 - ldrbcs r4, [r1], #1 - ldrbcs r12,[r1], #1 - strbmi r3, [r0], #1 - strbcs r4, [r0], #1 - strbcs r12,[r0], #1 - -src_aligned: - - /* see if src and dst are aligned together (congruent) */ - eor r12, r0, r1 - tst r12, #3 - bne non_congruent - - /* Use post-incriment mode for stm to spill r5-r11 to reserved stack - * frame. Don't update sp. - */ - stmea sp, {r5-r11} - - /* align the destination to a cache-line */ - rsb r3, r0, #0 - ands r3, r3, #0x1C - beq congruent_aligned32 - cmp r3, r2 - andhi r3, r2, #0x1C - - /* conditionnaly copies 0 to 7 words (length in r3) */ - movs r12, r3, lsl #28 - ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ - ldmmi r1!, {r8, r9} /* 8 bytes */ - stmcs r0!, {r4, r5, r6, r7} - stmmi r0!, {r8, r9} - tst r3, #0x4 - ldrne r10,[r1], #4 /* 4 bytes */ - strne r10,[r0], #4 - sub r2, r2, r3 - -congruent_aligned32: - /* - * here source is aligned to 32 bytes. - */ - -cached_aligned32: - subs r2, r2, #32 - blo less_than_32_left - - /* - * We preload a cache-line up to 64 bytes ahead. On the 926, this will - * stall only until the requested world is fetched, but the linefill - * continues in the the background. - * While the linefill is going, we write our previous cache-line - * into the write-buffer (which should have some free space). - * When the linefill is done, the writebuffer will - * start dumping its content into memory - * - * While all this is going, we then load a full cache line into - * 8 registers, this cache line should be in the cache by now - * (or partly in the cache). - * - * This code should work well regardless of the source/dest alignment. - * - */ - - /* Align the preload register to a cache-line because the cpu does - * "critical word first" (the first word requested is loaded first). - */ - @ bic r12, r1, #0x1F - @ add r12, r12, #64 - -1: ldmia r1!, { r4-r11 } - subs r2, r2, #32 - - /* - * NOTE: if r12 is more than 64 ahead of r1, the following ldrhi - * for ARM9 preload will not be safely guarded by the preceding subs. - * When it is safely guarded the only possibility to have SIGSEGV here - * is because the caller overstates the length. - */ - @ ldrhi r3, [r12], #32 /* cheap ARM9 preload */ - stmia r0!, { r4-r11 } - bhs 1b - - add r2, r2, #32 - -less_than_32_left: - /* - * less than 32 bytes left at this point (length in r2) - */ - - /* skip all this if there is nothing to do, which should - * be a common case (if not executed the code below takes - * about 16 cycles) - */ - tst r2, #0x1F - beq 1f - - /* conditionnaly copies 0 to 31 bytes */ - movs r12, r2, lsl #28 - ldmcs r1!, {r4, r5, r6, r7} /* 16 bytes */ - ldmmi r1!, {r8, r9} /* 8 bytes */ - stmcs r0!, {r4, r5, r6, r7} - stmmi r0!, {r8, r9} - movs r12, r2, lsl #30 - ldrcs r3, [r1], #4 /* 4 bytes */ - ldrhmi r4, [r1], #2 /* 2 bytes */ - strcs r3, [r0], #4 - strhmi r4, [r0], #2 - tst r2, #0x1 - ldrbne r3, [r1] /* last byte */ - strbne r3, [r0] - - /* we're done! restore everything and return */ -1: ldmfd sp!, {r5-r11} - ldmfd sp!, {r0, r4, lr} - bx lr - - /********************************************************************/ - -non_congruent: - /* - * here source is aligned to 4 bytes - * but destination is not. - * - * in the code below r2 is the number of bytes read - * (the number of bytes written is always smaller, because we have - * partial words in the shift queue) - */ - cmp r2, #4 - blo copy_last_3_and_return - - /* Use post-incriment mode for stm to spill r5-r11 to reserved stack - * frame. Don't update sp. - */ - stmea sp, {r5-r11} - - /* compute shifts needed to align src to dest */ - rsb r5, r0, #0 - and r5, r5, #3 /* r5 = # bytes in partial words */ - mov r12, r5, lsl #3 /* r12 = right */ - rsb lr, r12, #32 /* lr = left */ - - /* read the first word */ - ldr r3, [r1], #4 - sub r2, r2, #4 - - /* write a partial word (0 to 3 bytes), such that destination - * becomes aligned to 32 bits (r5 = nb of words to copy for alignment) - */ - movs r5, r5, lsl #31 - -#if __ARMEB__ - movmi r3, r3, ror #24 - strbmi r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 -#else - strbmi r3, [r0], #1 - movmi r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 -#endif - - cmp r2, #4 - blo partial_word_tail - -#if __ARMEB__ - mov r3, r3, lsr r12 - mov r3, r3, lsl r12 -#endif - - /* Align destination to 32 bytes (cache line boundary) */ -1: tst r0, #0x1c - beq 2f - ldr r5, [r1], #4 - sub r2, r2, #4 -#if __ARMEB__ - mov r4, r5, lsr lr - orr r4, r4, r3 - mov r3, r5, lsl r12 -#else - mov r4, r5, lsl lr - orr r4, r4, r3 - mov r3, r5, lsr r12 -#endif - str r4, [r0], #4 - cmp r2, #4 - bhs 1b - blo partial_word_tail - - /* copy 32 bytes at a time */ -2: subs r2, r2, #32 - blo less_than_thirtytwo - - /* Use immediate mode for the shifts, because there is an extra cycle - * for register shifts, which could account for up to 50% of - * performance hit. - */ - - cmp r12, #24 - beq loop24 - cmp r12, #8 - beq loop8 - -loop16: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #16 - mov r4, r4, lsl #16 - orr r4, r4, r5, lsr #16 - mov r5, r5, lsl #16 - orr r5, r5, r6, lsr #16 - mov r6, r6, lsl #16 - orr r6, r6, r7, lsr #16 - mov r7, r7, lsl #16 - orr r7, r7, r8, lsr #16 - mov r8, r8, lsl #16 - orr r8, r8, r9, lsr #16 - mov r9, r9, lsl #16 - orr r9, r9, r10, lsr #16 - mov r10, r10, lsl #16 - orr r10, r10, r11, lsr #16 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #16 -#else - orr r3, r3, r4, lsl #16 - mov r4, r4, lsr #16 - orr r4, r4, r5, lsl #16 - mov r5, r5, lsr #16 - orr r5, r5, r6, lsl #16 - mov r6, r6, lsr #16 - orr r6, r6, r7, lsl #16 - mov r7, r7, lsr #16 - orr r7, r7, r8, lsl #16 - mov r8, r8, lsr #16 - orr r8, r8, r9, lsl #16 - mov r9, r9, lsr #16 - orr r9, r9, r10, lsl #16 - mov r10, r10, lsr #16 - orr r10, r10, r11, lsl #16 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #16 -#endif - bhs 1b - b less_than_thirtytwo - -loop8: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #24 - mov r4, r4, lsl #8 - orr r4, r4, r5, lsr #24 - mov r5, r5, lsl #8 - orr r5, r5, r6, lsr #24 - mov r6, r6, lsl #8 - orr r6, r6, r7, lsr #24 - mov r7, r7, lsl #8 - orr r7, r7, r8, lsr #24 - mov r8, r8, lsl #8 - orr r8, r8, r9, lsr #24 - mov r9, r9, lsl #8 - orr r9, r9, r10, lsr #24 - mov r10, r10, lsl #8 - orr r10, r10, r11, lsr #24 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #8 -#else - orr r3, r3, r4, lsl #24 - mov r4, r4, lsr #8 - orr r4, r4, r5, lsl #24 - mov r5, r5, lsr #8 - orr r5, r5, r6, lsl #24 - mov r6, r6, lsr #8 - orr r6, r6, r7, lsl #24 - mov r7, r7, lsr #8 - orr r7, r7, r8, lsl #24 - mov r8, r8, lsr #8 - orr r8, r8, r9, lsl #24 - mov r9, r9, lsr #8 - orr r9, r9, r10, lsl #24 - mov r10, r10, lsr #8 - orr r10, r10, r11, lsl #24 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #8 -#endif - bhs 1b - b less_than_thirtytwo - -loop24: - ldr r12, [r1], #4 -1: mov r4, r12 - ldmia r1!, { r5,r6,r7, r8,r9,r10,r11} - subs r2, r2, #32 - ldrhs r12, [r1], #4 -#if __ARMEB__ - orr r3, r3, r4, lsr #8 - mov r4, r4, lsl #24 - orr r4, r4, r5, lsr #8 - mov r5, r5, lsl #24 - orr r5, r5, r6, lsr #8 - mov r6, r6, lsl #24 - orr r6, r6, r7, lsr #8 - mov r7, r7, lsl #24 - orr r7, r7, r8, lsr #8 - mov r8, r8, lsl #24 - orr r8, r8, r9, lsr #8 - mov r9, r9, lsl #24 - orr r9, r9, r10, lsr #8 - mov r10, r10, lsl #24 - orr r10, r10, r11, lsr #8 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsl #24 -#else - orr r3, r3, r4, lsl #8 - mov r4, r4, lsr #24 - orr r4, r4, r5, lsl #8 - mov r5, r5, lsr #24 - orr r5, r5, r6, lsl #8 - mov r6, r6, lsr #24 - orr r6, r6, r7, lsl #8 - mov r7, r7, lsr #24 - orr r7, r7, r8, lsl #8 - mov r8, r8, lsr #24 - orr r8, r8, r9, lsl #8 - mov r9, r9, lsr #24 - orr r9, r9, r10, lsl #8 - mov r10, r10, lsr #24 - orr r10, r10, r11, lsl #8 - stmia r0!, {r3,r4,r5,r6, r7,r8,r9,r10} - mov r3, r11, lsr #24 -#endif - bhs 1b - -less_than_thirtytwo: - /* copy the last 0 to 31 bytes of the source */ - rsb r12, lr, #32 /* we corrupted r12, recompute it */ - add r2, r2, #32 - cmp r2, #4 - blo partial_word_tail - -1: ldr r5, [r1], #4 - sub r2, r2, #4 -#if __ARMEB__ - mov r4, r5, lsr lr - orr r4, r4, r3 - mov r3, r5, lsl r12 -#else - mov r4, r5, lsl lr - orr r4, r4, r3 - mov r3, r5, lsr r12 -#endif - str r4, [r0], #4 - cmp r2, #4 - bhs 1b - -partial_word_tail: - /* we have a partial word in the input buffer */ - movs r5, lr, lsl #(31-3) -#if __ARMEB__ - movmi r3, r3, ror #24 - strbmi r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 - movcs r3, r3, ror #24 - strbcs r3, [r0], #1 -#else - strbmi r3, [r0], #1 - movmi r3, r3, lsr #8 - strbcs r3, [r0], #1 - movcs r3, r3, lsr #8 - strbcs r3, [r0], #1 -#endif - - /* Refill spilled registers from the stack. Don't update sp. */ - ldmfd sp, {r5-r11} - -copy_last_3_and_return: - movs r2, r2, lsl #31 /* copy remaining 0, 1, 2 or 3 bytes */ - ldrbmi r2, [r1], #1 - ldrbcs r3, [r1], #1 - ldrbcs r12,[r1] - strbmi r2, [r0], #1 - strbcs r3, [r0], #1 - strbcs r12,[r0] - - /* we're done! restore sp and spilled registers and return */ - add sp, sp, #28 - ldmfd sp!, {r0, r4, lr} - bx lr - diff --git a/na/lib/string/bcmp.c b/na/lib/string/bcmp.c deleted file mode 100644 index d4c093f..0000000 --- a/na/lib/string/bcmp.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <strings.h> - -int bcmp(const void *s1, const void *s2, size_t n) -{ - return memcmp(s1, s2, n); -} diff --git a/na/lib/string/bcopy.c b/na/lib/string/bcopy.c deleted file mode 100644 index 25e98a2..0000000 --- a/na/lib/string/bcopy.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <strings.h> - -void bcopy(const void *s1, void *s2, size_t n) -{ - memmove(s2, s1, n); -} diff --git a/na/lib/string/bzero.c b/na/lib/string/bzero.c deleted file mode 100644 index 08f1398..0000000 --- a/na/lib/string/bzero.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <strings.h> - -void bzero(void *s, size_t n) -{ - memset(s, 0, n); -} diff --git a/na/lib/string/explicit_bzero.c b/na/lib/string/explicit_bzero.c deleted file mode 100644 index c94f25f..0000000 --- a/na/lib/string/explicit_bzero.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> - -void explicit_bzero(void *d, size_t n) -{ - d = memset(d, 0, n); - __asm__ __volatile__ ("" : : "r"(d) : "memory"); -} diff --git a/na/lib/string/i386/memcpy.s b/na/lib/string/i386/memcpy.s deleted file mode 100644 index 0608dd8..0000000 --- a/na/lib/string/i386/memcpy.s +++ /dev/null @@ -1,32 +0,0 @@ -.global memcpy -.global __memcpy_fwd -.hidden __memcpy_fwd -.type memcpy,@function -memcpy: -__memcpy_fwd: - push %esi - push %edi - mov 12(%esp),%edi - mov 16(%esp),%esi - mov 20(%esp),%ecx - mov %edi,%eax - cmp $4,%ecx - jc 1f - test $3,%edi - jz 1f -2: movsb - dec %ecx - test $3,%edi - jnz 2b -1: mov %ecx,%edx - shr $2,%ecx - rep - movsl - and $3,%edx - jz 1f -2: movsb - dec %edx - jnz 2b -1: pop %edi - pop %esi - ret diff --git a/na/lib/string/i386/memmove.s b/na/lib/string/i386/memmove.s deleted file mode 100644 index 2a6a504..0000000 --- a/na/lib/string/i386/memmove.s +++ /dev/null @@ -1,22 +0,0 @@ -.global memmove -.type memmove,@function -memmove: - mov 4(%esp),%eax - sub 8(%esp),%eax - cmp 12(%esp),%eax -.hidden __memcpy_fwd - jae __memcpy_fwd - push %esi - push %edi - mov 12(%esp),%edi - mov 16(%esp),%esi - mov 20(%esp),%ecx - lea -1(%edi,%ecx),%edi - lea -1(%esi,%ecx),%esi - std - rep movsb - cld - lea 1(%edi),%eax - pop %edi - pop %esi - ret diff --git a/na/lib/string/i386/memset.s b/na/lib/string/i386/memset.s deleted file mode 100644 index d00422c..0000000 --- a/na/lib/string/i386/memset.s +++ /dev/null @@ -1,76 +0,0 @@ -.global memset -.type memset,@function -memset: - mov 12(%esp),%ecx - cmp $62,%ecx - ja 2f - - mov 8(%esp),%dl - mov 4(%esp),%eax - test %ecx,%ecx - jz 1f - - mov %dl,%dh - - mov %dl,(%eax) - mov %dl,-1(%eax,%ecx) - cmp $2,%ecx - jbe 1f - - mov %dx,1(%eax) - mov %dx,(-1-2)(%eax,%ecx) - cmp $6,%ecx - jbe 1f - - shl $16,%edx - mov 8(%esp),%dl - mov 8(%esp),%dh - - mov %edx,(1+2)(%eax) - mov %edx,(-1-2-4)(%eax,%ecx) - cmp $14,%ecx - jbe 1f - - mov %edx,(1+2+4)(%eax) - mov %edx,(1+2+4+4)(%eax) - mov %edx,(-1-2-4-8)(%eax,%ecx) - mov %edx,(-1-2-4-4)(%eax,%ecx) - cmp $30,%ecx - jbe 1f - - mov %edx,(1+2+4+8)(%eax) - mov %edx,(1+2+4+8+4)(%eax) - mov %edx,(1+2+4+8+8)(%eax) - mov %edx,(1+2+4+8+12)(%eax) - mov %edx,(-1-2-4-8-16)(%eax,%ecx) - mov %edx,(-1-2-4-8-12)(%eax,%ecx) - mov %edx,(-1-2-4-8-8)(%eax,%ecx) - mov %edx,(-1-2-4-8-4)(%eax,%ecx) - -1: ret - -2: movzbl 8(%esp),%eax - mov %edi,12(%esp) - imul $0x1010101,%eax - mov 4(%esp),%edi - test $15,%edi - mov %eax,-4(%edi,%ecx) - jnz 2f - -1: shr $2, %ecx - rep - stosl - mov 4(%esp),%eax - mov 12(%esp),%edi - ret - -2: xor %edx,%edx - sub %edi,%edx - and $15,%edx - mov %eax,(%edi) - mov %eax,4(%edi) - mov %eax,8(%edi) - mov %eax,12(%edi) - sub %edx,%ecx - add %edx,%edi - jmp 1b diff --git a/na/lib/string/index.c b/na/lib/string/index.c deleted file mode 100644 index 8f6894c..0000000 --- a/na/lib/string/index.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <strings.h> - -char *index(const char *s, int c) -{ - return strchr(s, c); -} diff --git a/na/lib/string/memccpy.c b/na/lib/string/memccpy.c deleted file mode 100644 index 99f146f..0000000 --- a/na/lib/string/memccpy.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -void *memccpy(void *restrict dest, const void *restrict src, int c, size_t n) -{ - unsigned char *d = dest; - const unsigned char *s = src; - - c = (unsigned char)c; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s)!=c; n--, s++, d++); - if ((uintptr_t)s & ALIGN) goto tail; - size_t k = ONES * c; - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws^k); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; n && (*d=*s)!=c; n--, s++, d++); -tail: - if (n) return d+1; - return 0; -} diff --git a/na/lib/string/memchr.c b/na/lib/string/memchr.c deleted file mode 100644 index d0f670f..0000000 --- a/na/lib/string/memchr.c +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define SS (sizeof(size_t)) -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -void *memchr(const void *src, int c, size_t n) -{ - const unsigned char *s = src; - c = (unsigned char)c; -#ifdef __GNUC__ - for (; ((uintptr_t)s & ALIGN) && n && *s != c; s++, n--); - if (n && *s != c) { - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - size_t k = ONES * c; - for (w = (const void *)s; n>=SS && !HASZERO(*w^k); w++, n-=SS); - s = (const void *)w; - } -#endif - for (; n && *s != c; s++, n--); - return n ? (void *)s : 0; -} diff --git a/na/lib/string/memcmp.c b/na/lib/string/memcmp.c deleted file mode 100644 index 15606f4..0000000 --- a/na/lib/string/memcmp.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -int memcmp(const void *vl, const void *vr, size_t n) -{ - const unsigned char *l=vl, *r=vr; - for (; n && *l == *r; n--, l++, r++); - return n ? *l-*r : 0; -} diff --git a/na/lib/string/memcpy.c b/na/lib/string/memcpy.c deleted file mode 100644 index de26a00..0000000 --- a/na/lib/string/memcpy.c +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <endian.h> - -void *memcpy(void *restrict dest, const void *restrict src, size_t n) -{ - unsigned char *d = dest; - const unsigned char *s = src; - -#ifdef __GNUC__ - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define LS >> -#define RS << -#else -#define LS << -#define RS >> -#endif - - typedef uint32_t __attribute__((__may_alias__)) u32; - uint32_t w, x; - - for (; (uintptr_t)s % 4 && n; n--) *d++ = *s++; - - if ((uintptr_t)d % 4 == 0) { - for (; n>=16; s+=16, d+=16, n-=16) { - *(u32 *)(d+0) = *(u32 *)(s+0); - *(u32 *)(d+4) = *(u32 *)(s+4); - *(u32 *)(d+8) = *(u32 *)(s+8); - *(u32 *)(d+12) = *(u32 *)(s+12); - } - if (n&8) { - *(u32 *)(d+0) = *(u32 *)(s+0); - *(u32 *)(d+4) = *(u32 *)(s+4); - d += 8; s += 8; - } - if (n&4) { - *(u32 *)(d+0) = *(u32 *)(s+0); - d += 4; s += 4; - } - if (n&2) { - *d++ = *s++; *d++ = *s++; - } - if (n&1) { - *d = *s; - } - return dest; - } - - if (n >= 32) switch ((uintptr_t)d % 4) { - case 1: - w = *(u32 *)s; - *d++ = *s++; - *d++ = *s++; - *d++ = *s++; - n -= 3; - for (; n>=17; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+1); - *(u32 *)(d+0) = (w LS 24) | (x RS 8); - w = *(u32 *)(s+5); - *(u32 *)(d+4) = (x LS 24) | (w RS 8); - x = *(u32 *)(s+9); - *(u32 *)(d+8) = (w LS 24) | (x RS 8); - w = *(u32 *)(s+13); - *(u32 *)(d+12) = (x LS 24) | (w RS 8); - } - break; - case 2: - w = *(u32 *)s; - *d++ = *s++; - *d++ = *s++; - n -= 2; - for (; n>=18; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+2); - *(u32 *)(d+0) = (w LS 16) | (x RS 16); - w = *(u32 *)(s+6); - *(u32 *)(d+4) = (x LS 16) | (w RS 16); - x = *(u32 *)(s+10); - *(u32 *)(d+8) = (w LS 16) | (x RS 16); - w = *(u32 *)(s+14); - *(u32 *)(d+12) = (x LS 16) | (w RS 16); - } - break; - case 3: - w = *(u32 *)s; - *d++ = *s++; - n -= 1; - for (; n>=19; s+=16, d+=16, n-=16) { - x = *(u32 *)(s+3); - *(u32 *)(d+0) = (w LS 8) | (x RS 24); - w = *(u32 *)(s+7); - *(u32 *)(d+4) = (x LS 8) | (w RS 24); - x = *(u32 *)(s+11); - *(u32 *)(d+8) = (w LS 8) | (x RS 24); - w = *(u32 *)(s+15); - *(u32 *)(d+12) = (x LS 8) | (w RS 24); - } - break; - } - if (n&16) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&8) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&4) { - *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; - } - if (n&2) { - *d++ = *s++; *d++ = *s++; - } - if (n&1) { - *d = *s; - } - return dest; -#endif - - for (; n; n--) *d++ = *s++; - return dest; -} diff --git a/na/lib/string/memmem.c b/na/lib/string/memmem.c deleted file mode 100644 index cfd23bb..0000000 --- a/na/lib/string/memmem.c +++ /dev/null @@ -1,152 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include <string.h> -#include <stdint.h> - -static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; - for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-2; - return hw == nw ? (char *)h-2 : 0; -} - -static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; - for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) - if (hw == nw) return (char *)h-3; - return hw == nw ? (char *)h-3 : 0; -} - -static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; - for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) - if (hw == nw) return (char *)h-4; - return hw == nw ? (char *)h-4 : 0; -} - -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) -{ - size_t i, ip, jp, k, p, ms, p0, mem, mem0; - size_t byteset[32 / sizeof(size_t)] = { 0 }; - size_t shift[256]; - - /* Computing length of needle and fill shift table */ - for (i=0; i<l; i++) - BITOP(byteset, n[i], |=), shift[n[i]] = i+1; - - /* Compute maximal suffix */ - ip = -1; jp = 0; k = p = 1; - while (jp+k<l) { - if (n[ip+k] == n[jp+k]) { - if (k == p) { - jp += p; - k = 1; - } else k++; - } else if (n[ip+k] > n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - ms = ip; - p0 = p; - - /* And with the opposite comparison */ - ip = -1; jp = 0; k = p = 1; - while (jp+k<l) { - if (n[ip+k] == n[jp+k]) { - if (k == p) { - jp += p; - k = 1; - } else k++; - } else if (n[ip+k] < n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - if (ip+1 > ms+1) ms = ip; - else p = p0; - - /* Periodic needle? */ - if (memcmp(n, n+p, ms+1)) { - mem0 = 0; - p = MAX(ms, l-ms-1) + 1; - } else mem0 = l-p; - mem = 0; - - /* Search loop */ - for (;;) { - /* If remainder of haystack is shorter than needle, done */ - if (z-h < l) return 0; - - /* Check last byte first; advance by shift on mismatch */ - if (BITOP(byteset, h[l-1], &)) { - k = l-shift[h[l-1]]; - if (k) { - if (k < mem) k = mem; - h += k; - mem = 0; - continue; - } - } else { - h += l; - mem = 0; - continue; - } - - /* Compare right half */ - for (k=MAX(ms+1,mem); k<l && n[k] == h[k]; k++); - if (k < l) { - h += k-ms; - mem = 0; - continue; - } - /* Compare left half */ - for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); - if (k <= mem) return (char *)h; - h += p; - mem = mem0; - } -} - -void *memmem(const void *h0, size_t k, const void *n0, size_t l) -{ - const unsigned char *h = h0, *n = n0; - - /* Return immediately on empty needle */ - if (!l) return (void *)h; - - /* Return immediately when needle is longer than haystack */ - if (k<l) return 0; - - /* Use faster algorithms for short needles */ - h = memchr(h0, *n, k); - if (!h || l==1) return (void *)h; - k -= h - (const unsigned char *)h0; - if (k<l) return 0; - if (l==2) return twobyte_memmem(h, k, n); - if (l==3) return threebyte_memmem(h, k, n); - if (l==4) return fourbyte_memmem(h, k, n); - - return twoway_memmem(h, h+k, n, l); -} diff --git a/na/lib/string/memmove.c b/na/lib/string/memmove.c deleted file mode 100644 index 56e1a81..0000000 --- a/na/lib/string/memmove.c +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> - -#ifdef __GNUC__ -typedef __attribute__((__may_alias__)) size_t WT; -#define WS (sizeof(WT)) -#endif - -void *memmove(void *dest, const void *src, size_t n) -{ - char *d = dest; - const char *s = src; - - if (d==s) return d; - if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n); - - if (d<s) { -#ifdef __GNUC__ - if ((uintptr_t)s % WS == (uintptr_t)d % WS) { - while ((uintptr_t)d % WS) { - if (!n--) return dest; - *d++ = *s++; - } - for (; n>=WS; n-=WS, d+=WS, s+=WS) *(WT *)d = *(WT *)s; - } -#endif - for (; n; n--) *d++ = *s++; - } else { -#ifdef __GNUC__ - if ((uintptr_t)s % WS == (uintptr_t)d % WS) { - while ((uintptr_t)(d+n) % WS) { - if (!n--) return dest; - d[n] = s[n]; - } - while (n>=WS) n-=WS, *(WT *)(d+n) = *(WT *)(s+n); - } -#endif - while (n) n--, d[n] = s[n]; - } - - return dest; -} diff --git a/na/lib/string/mempcpy.c b/na/lib/string/mempcpy.c deleted file mode 100644 index 1f33e45..0000000 --- a/na/lib/string/mempcpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include <string.h> - -void *mempcpy(void *dest, const void *src, size_t n) -{ - return (char *)memcpy(dest, src, n) + n; -} diff --git a/na/lib/string/memrchr.c b/na/lib/string/memrchr.c deleted file mode 100644 index 8d5c742..0000000 --- a/na/lib/string/memrchr.c +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -void *__memrchr(const void *m, int c, size_t n) -{ - const unsigned char *s = m; - c = (unsigned char)c; - while (n--) if (s[n]==c) return (void *)(s+n); - return 0; -} - -weak_alias(__memrchr, memrchr); diff --git a/na/lib/string/memset.c b/na/lib/string/memset.c deleted file mode 100644 index e780c71..0000000 --- a/na/lib/string/memset.c +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> - -void *memset(void *dest, int c, size_t n) -{ - unsigned char *s = dest; - size_t k; - - /* Fill head and tail with minimal branching. Each - * conditional ensures that all the subsequently used - * offsets are well-defined and in the dest region. */ - - if (!n) return dest; - s[0] = c; - s[n-1] = c; - if (n <= 2) return dest; - s[1] = c; - s[2] = c; - s[n-2] = c; - s[n-3] = c; - if (n <= 6) return dest; - s[3] = c; - s[n-4] = c; - if (n <= 8) return dest; - - /* Advance pointer to align it at a 4-byte boundary, - * and truncate n to a multiple of 4. The previous code - * already took care of any head/tail that get cut off - * by the alignment. */ - - k = -(uintptr_t)s & 3; - s += k; - n -= k; - n &= -4; - -#ifdef __GNUC__ - typedef uint32_t __attribute__((__may_alias__)) u32; - typedef uint64_t __attribute__((__may_alias__)) u64; - - u32 c32 = ((u32)-1)/255 * (unsigned char)c; - - /* In preparation to copy 32 bytes at a time, aligned on - * an 8-byte bounary, fill head/tail up to 28 bytes each. - * As in the initial byte-based head/tail fill, each - * conditional below ensures that the subsequent offsets - * are valid (e.g. !(n<=24) implies n>=28). */ - - *(u32 *)(s+0) = c32; - *(u32 *)(s+n-4) = c32; - if (n <= 8) return dest; - *(u32 *)(s+4) = c32; - *(u32 *)(s+8) = c32; - *(u32 *)(s+n-12) = c32; - *(u32 *)(s+n-8) = c32; - if (n <= 24) return dest; - *(u32 *)(s+12) = c32; - *(u32 *)(s+16) = c32; - *(u32 *)(s+20) = c32; - *(u32 *)(s+24) = c32; - *(u32 *)(s+n-28) = c32; - *(u32 *)(s+n-24) = c32; - *(u32 *)(s+n-20) = c32; - *(u32 *)(s+n-16) = c32; - - /* Align to a multiple of 8 so we can fill 64 bits at a time, - * and avoid writing the same bytes twice as much as is - * practical without introducing additional branching. */ - - k = 24 + ((uintptr_t)s & 4); - s += k; - n -= k; - - /* If this loop is reached, 28 tail bytes have already been - * filled, so any remainder when n drops below 32 can be - * safely ignored. */ - - u64 c64 = c32 | ((u64)c32 << 32); - for (; n >= 32; n-=32, s+=32) { - *(u64 *)(s+0) = c64; - *(u64 *)(s+8) = c64; - *(u64 *)(s+16) = c64; - *(u64 *)(s+24) = c64; - } -#else - /* Pure C fallback with no aliasing violations. */ - for (; n; n--, s++) *s = c; -#endif - - return dest; -} diff --git a/na/lib/string/rindex.c b/na/lib/string/rindex.c deleted file mode 100644 index 897e19e..0000000 --- a/na/lib/string/rindex.c +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <strings.h> - -char *rindex(const char *s, int c) -{ - return strrchr(s, c); -} diff --git a/na/lib/string/stpcpy.c b/na/lib/string/stpcpy.c deleted file mode 100644 index 0c1f1b4..0000000 --- a/na/lib/string/stpcpy.c +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__stpcpy(char *restrict d, const char *restrict s) -{ -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { - for (; (uintptr_t)s % ALIGN; s++, d++) - if (!(*d=*s)) return d; - wd=(void *)d; ws=(const void *)s; - for (; !HASZERO(*ws); *wd++ = *ws++); - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; (*d=*s); s++, d++); - - return d; -} - -weak_alias(__stpcpy, stpcpy); diff --git a/na/lib/string/stpncpy.c b/na/lib/string/stpncpy.c deleted file mode 100644 index ca2ef78..0000000 --- a/na/lib/string/stpncpy.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__stpncpy(char *restrict d, const char *restrict s, size_t n) -{ -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - word *wd; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); - if (!n || !*s) goto tail; - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } -#endif - for (; n && (*d=*s); n--, s++, d++); -tail: - memset(d, 0, n); - return d; -} - -weak_alias(__stpncpy, stpncpy); - diff --git a/na/lib/string/strcat.c b/na/lib/string/strcat.c deleted file mode 100644 index 723c2ff..0000000 --- a/na/lib/string/strcat.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strcat(char *restrict dest, const char *restrict src) -{ - strcpy(dest + strlen(dest), src); - return dest; -} diff --git a/na/lib/string/strchr.c b/na/lib/string/strchr.c deleted file mode 100644 index ddeca01..0000000 --- a/na/lib/string/strchr.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strchr(const char *s, int c) -{ - char *r = __strchrnul(s, c); - return *(unsigned char *)r == (unsigned char)c ? r : 0; -} diff --git a/na/lib/string/strchrnul.c b/na/lib/string/strchrnul.c deleted file mode 100644 index 6fc86d1..0000000 --- a/na/lib/string/strchrnul.c +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -char *__strchrnul(const char *s, int c) -{ - c = (unsigned char)c; - if (!c) return (char *)s + strlen(s); - -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - for (; (uintptr_t)s % ALIGN; s++) - if (!*s || *(unsigned char *)s == c) return (char *)s; - size_t k = ONES * c; - for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); - s = (void *)w; -#endif - for (; *s && *(unsigned char *)s != c; s++); - return (char *)s; -} - -weak_alias(__strchrnul, strchrnul); diff --git a/na/lib/string/strcmp.c b/na/lib/string/strcmp.c deleted file mode 100644 index afcb1b7..0000000 --- a/na/lib/string/strcmp.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -int strcmp(const char *l, const char *r) -{ - for (; *l==*r && *l; l++, r++); - return *(unsigned char *)l - *(unsigned char *)r; -} diff --git a/na/lib/string/strcpy.c b/na/lib/string/strcpy.c deleted file mode 100644 index a761577..0000000 --- a/na/lib/string/strcpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strcpy(char *restrict dest, const char *restrict src) -{ - __stpcpy(dest, src); - return dest; -} diff --git a/na/lib/string/strcspn.c b/na/lib/string/strcspn.c deleted file mode 100644 index ba55fe6..0000000 --- a/na/lib/string/strcspn.c +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -size_t strcspn(const char *s, const char *c) -{ - const char *a = s; - size_t byteset[32/sizeof(size_t)]; - - if (!c[0] || !c[1]) return __strchrnul(s, *c)-a; - - memset(byteset, 0, sizeof byteset); - for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); - for (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++); - return s-a; -} diff --git a/na/lib/string/strlcat.c b/na/lib/string/strlcat.c deleted file mode 100644 index a61821d..0000000 --- a/na/lib/string/strlcat.c +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> - -size_t strlcat(char *d, const char *s, size_t n) -{ - size_t l = strnlen(d, n); - if (l == n) return l + strlen(s); - return l + strlcpy(d+l, s, n-l); -} diff --git a/na/lib/string/strlcpy.c b/na/lib/string/strlcpy.c deleted file mode 100644 index c2a5142..0000000 --- a/na/lib/string/strlcpy.c +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _BSD_SOURCE -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)-1) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -size_t strlcpy(char *d, const char *s, size_t n) -{ - char *d0 = d; - size_t *wd; - - if (!n--) goto finish; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *ws; - if (((uintptr_t)s & ALIGN) == ((uintptr_t)d & ALIGN)) { - for (; ((uintptr_t)s & ALIGN) && n && (*d=*s); n--, s++, d++); - if (n && *s) { - wd=(void *)d; ws=(const void *)s; - for (; n>=sizeof(size_t) && !HASZERO(*ws); - n-=sizeof(size_t), ws++, wd++) *wd = *ws; - d=(void *)wd; s=(const void *)ws; - } - } -#endif - for (; n && (*d=*s); n--, s++, d++); - *d = 0; -finish: - return d-d0 + strlen(s); -} diff --git a/na/lib/string/strlen.c b/na/lib/string/strlen.c deleted file mode 100644 index 76e14f2..0000000 --- a/na/lib/string/strlen.c +++ /dev/null @@ -1,25 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> -#include <limits.h> - -#define ALIGN (sizeof(size_t)) -#define ONES ((size_t)-1/UCHAR_MAX) -#define HIGHS (ONES * (UCHAR_MAX/2+1)) -#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS) - -size_t strlen(const char *s) -{ - const char *a = s; -#ifdef __GNUC__ - typedef size_t __attribute__((__may_alias__)) word; - const word *w; - for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a; - for (w = (const void *)s; !HASZERO(*w); w++); - s = (const void *)w; -#endif - for (; *s; s++); - return s-a; -} diff --git a/na/lib/string/strncat.c b/na/lib/string/strncat.c deleted file mode 100644 index a144e37..0000000 --- a/na/lib/string/strncat.c +++ /dev/null @@ -1,13 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strncat(char *restrict d, const char *restrict s, size_t n) -{ - char *a = d; - d += strlen(d); - while (n && *s) n--, *d++ = *s++; - *d++ = 0; - return a; -} diff --git a/na/lib/string/strncmp.c b/na/lib/string/strncmp.c deleted file mode 100644 index b98473b..0000000 --- a/na/lib/string/strncmp.c +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -int strncmp(const char *_l, const char *_r, size_t n) -{ - const unsigned char *l=(void *)_l, *r=(void *)_r; - if (!n--) return 0; - for (; *l && *r && n && *l == *r ; l++, r++, n--); - return *l - *r; -} diff --git a/na/lib/string/strncpy.c b/na/lib/string/strncpy.c deleted file mode 100644 index a828465..0000000 --- a/na/lib/string/strncpy.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strncpy(char *restrict d, const char *restrict s, size_t n) -{ - __stpncpy(d, s, n); - return d; -} diff --git a/na/lib/string/strnlen.c b/na/lib/string/strnlen.c deleted file mode 100644 index 8931c52..0000000 --- a/na/lib/string/strnlen.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -size_t strnlen(const char *s, size_t n) -{ - const char *p = memchr(s, 0, n); - return p ? p-s : n; -} diff --git a/na/lib/string/strpbrk.c b/na/lib/string/strpbrk.c deleted file mode 100644 index 82fbc92..0000000 --- a/na/lib/string/strpbrk.c +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strpbrk(const char *s, const char *b) -{ - s += strcspn(s, b); - return *s ? (char *)s : 0; -} diff --git a/na/lib/string/strrchr.c b/na/lib/string/strrchr.c deleted file mode 100644 index a45b459..0000000 --- a/na/lib/string/strrchr.c +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strrchr(const char *s, int c) -{ - return __memrchr(s, c, strlen(s) + 1); -} diff --git a/na/lib/string/strsep.c b/na/lib/string/strsep.c deleted file mode 100644 index 2d513f4..0000000 --- a/na/lib/string/strsep.c +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#define _GNU_SOURCE -#include <string.h> - -char *strsep(char **str, const char *sep) -{ - char *s = *str, *end; - if (!s) return NULL; - end = s + strcspn(s, sep); - if (*end) *end++ = 0; - else end = 0; - *str = end; - return s; -} diff --git a/na/lib/string/strspn.c b/na/lib/string/strspn.c deleted file mode 100644 index f27710d..0000000 --- a/na/lib/string/strspn.c +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -size_t strspn(const char *s, const char *c) -{ - const char *a = s; - size_t byteset[32/sizeof(size_t)] = { 0 }; - - if (!c[0]) return 0; - if (!c[1]) { - for (; *s == *c; s++); - return s-a; - } - - for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); - for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++); - return s-a; -} diff --git a/na/lib/string/strstr.c b/na/lib/string/strstr.c deleted file mode 100644 index 07e2982..0000000 --- a/na/lib/string/strstr.c +++ /dev/null @@ -1,157 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> -#include <stdint.h> - -static char *twobyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; - for (h++; *h && hw != nw; hw = hw<<8 | *++h); - return *h ? (char *)h-1 : 0; -} - -static char *threebyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8; - for (h+=2; *h && hw != nw; hw = (hw|*++h)<<8); - return *h ? (char *)h-2 : 0; -} - -static char *fourbyte_strstr(const unsigned char *h, const unsigned char *n) -{ - uint32_t nw = (uint32_t)n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; - uint32_t hw = (uint32_t)h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; - for (h+=3; *h && hw != nw; hw = hw<<8 | *++h); - return *h ? (char *)h-3 : 0; -} - -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define MIN(a,b) ((a)<(b)?(a):(b)) - -#define BITOP(a,b,op) \ - ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) - -static char *twoway_strstr(const unsigned char *h, const unsigned char *n) -{ - const unsigned char *z; - size_t l, ip, jp, k, p, ms, p0, mem, mem0; - size_t byteset[32 / sizeof(size_t)] = { 0 }; - size_t shift[256]; - - /* Computing length of needle and fill shift table */ - for (l=0; n[l] && h[l]; l++) - BITOP(byteset, n[l], |=), shift[n[l]] = l+1; - if (n[l]) return 0; /* hit the end of h */ - - /* Compute maximal suffix */ - ip = -1; jp = 0; k = p = 1; - while (jp+k<l) { - if (n[ip+k] == n[jp+k]) { - if (k == p) { - jp += p; - k = 1; - } else k++; - } else if (n[ip+k] > n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - ms = ip; - p0 = p; - - /* And with the opposite comparison */ - ip = -1; jp = 0; k = p = 1; - while (jp+k<l) { - if (n[ip+k] == n[jp+k]) { - if (k == p) { - jp += p; - k = 1; - } else k++; - } else if (n[ip+k] < n[jp+k]) { - jp += k; - k = 1; - p = jp - ip; - } else { - ip = jp++; - k = p = 1; - } - } - if (ip+1 > ms+1) ms = ip; - else p = p0; - - /* Periodic needle? */ - if (memcmp(n, n+p, ms+1)) { - mem0 = 0; - p = MAX(ms, l-ms-1) + 1; - } else mem0 = l-p; - mem = 0; - - /* Initialize incremental end-of-haystack pointer */ - z = h; - - /* Search loop */ - for (;;) { - /* Update incremental end-of-haystack pointer */ - if (z-h < l) { - /* Fast estimate for MAX(l,63) */ - size_t grow = l | 63; - const unsigned char *z2 = memchr(z, 0, grow); - if (z2) { - z = z2; - if (z-h < l) return 0; - } else z += grow; - } - - /* Check last byte first; advance by shift on mismatch */ - if (BITOP(byteset, h[l-1], &)) { - k = l-shift[h[l-1]]; - if (k) { - if (k < mem) k = mem; - h += k; - mem = 0; - continue; - } - } else { - h += l; - mem = 0; - continue; - } - - /* Compare right half */ - for (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++); - if (n[k]) { - h += k-ms; - mem = 0; - continue; - } - /* Compare left half */ - for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); - if (k <= mem) return (char *)h; - h += p; - mem = mem0; - } -} - -char *strstr(const char *h, const char *n) -{ - /* Return immediately on empty needle */ - if (!n[0]) return (char *)h; - - /* Use faster algorithms for short needles */ - h = strchr(h, *n); - if (!h || !n[1]) return (char *)h; - if (!h[1]) return 0; - if (!n[2]) return twobyte_strstr((void *)h, (void *)n); - if (!h[2]) return 0; - if (!n[3]) return threebyte_strstr((void *)h, (void *)n); - if (!h[3]) return 0; - if (!n[4]) return fourbyte_strstr((void *)h, (void *)n); - - return twoway_strstr((void *)h, (void *)n); -} diff --git a/na/lib/string/strtok.c b/na/lib/string/strtok.c deleted file mode 100644 index 86d9c97..0000000 --- a/na/lib/string/strtok.c +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strtok(char *restrict s, const char *restrict sep) -{ - static char *p; - if (!s && !(s = p)) return NULL; - s += strspn(s, sep); - if (!*s) return p = 0; - p = s + strcspn(s, sep); - if (*p) *p++ = 0; - else p = 0; - return s; -} diff --git a/na/lib/string/strtok_r.c b/na/lib/string/strtok_r.c deleted file mode 100644 index a702703..0000000 --- a/na/lib/string/strtok_r.c +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* Derived from musl libc (Copyright © 2005-2020 Rich Felker, et al.) */ - -#include <string.h> - -char *strtok_r(char *restrict s, const char *restrict sep, char **restrict p) -{ - if (!s && !(s = *p)) return NULL; - s += strspn(s, sep); - if (!*s) return *p = 0; - *p = s + strcspn(s, sep); - if (**p) *(*p)++ = 0; - else *p = 0; - return s; -} diff --git a/na/lib/string/x86_64/memcpy.s b/na/lib/string/x86_64/memcpy.s deleted file mode 100644 index 3d960ef..0000000 --- a/na/lib/string/x86_64/memcpy.s +++ /dev/null @@ -1,25 +0,0 @@ -.global memcpy -.global __memcpy_fwd -.hidden __memcpy_fwd -.type memcpy,@function -memcpy: -__memcpy_fwd: - mov %rdi,%rax - cmp $8,%rdx - jc 1f - test $7,%edi - jz 1f -2: movsb - dec %rdx - test $7,%edi - jnz 2b -1: mov %rdx,%rcx - shr $3,%rcx - rep - movsq - and $7,%edx - jz 1f -2: movsb - dec %edx - jnz 2b -1: ret diff --git a/na/lib/string/x86_64/memmove.s b/na/lib/string/x86_64/memmove.s deleted file mode 100644 index 172c025..0000000 --- a/na/lib/string/x86_64/memmove.s +++ /dev/null @@ -1,16 +0,0 @@ -.global memmove -.type memmove,@function -memmove: - mov %rdi,%rax - sub %rsi,%rax - cmp %rdx,%rax -.hidden __memcpy_fwd - jae __memcpy_fwd - mov %rdx,%rcx - lea -1(%rdi,%rdx),%rdi - lea -1(%rsi,%rdx),%rsi - std - rep movsb - cld - lea 1(%rdi),%rax - ret diff --git a/na/lib/string/x86_64/memset.s b/na/lib/string/x86_64/memset.s deleted file mode 100644 index 2d3f5e5..0000000 --- a/na/lib/string/x86_64/memset.s +++ /dev/null @@ -1,72 +0,0 @@ -.global memset -.type memset,@function -memset: - movzbq %sil,%rax - mov $0x101010101010101,%r8 - imul %r8,%rax - - cmp $126,%rdx - ja 2f - - test %edx,%edx - jz 1f - - mov %sil,(%rdi) - mov %sil,-1(%rdi,%rdx) - cmp $2,%edx - jbe 1f - - mov %ax,1(%rdi) - mov %ax,(-1-2)(%rdi,%rdx) - cmp $6,%edx - jbe 1f - - mov %eax,(1+2)(%rdi) - mov %eax,(-1-2-4)(%rdi,%rdx) - cmp $14,%edx - jbe 1f - - mov %rax,(1+2+4)(%rdi) - mov %rax,(-1-2-4-8)(%rdi,%rdx) - cmp $30,%edx - jbe 1f - - mov %rax,(1+2+4+8)(%rdi) - mov %rax,(1+2+4+8+8)(%rdi) - mov %rax,(-1-2-4-8-16)(%rdi,%rdx) - mov %rax,(-1-2-4-8-8)(%rdi,%rdx) - cmp $62,%edx - jbe 1f - - mov %rax,(1+2+4+8+16)(%rdi) - mov %rax,(1+2+4+8+16+8)(%rdi) - mov %rax,(1+2+4+8+16+16)(%rdi) - mov %rax,(1+2+4+8+16+24)(%rdi) - mov %rax,(-1-2-4-8-16-32)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-24)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-16)(%rdi,%rdx) - mov %rax,(-1-2-4-8-16-8)(%rdi,%rdx) - -1: mov %rdi,%rax - ret - -2: test $15,%edi - mov %rdi,%r8 - mov %rax,-8(%rdi,%rdx) - mov %rdx,%rcx - jnz 2f - -1: shr $3,%rcx - rep - stosq - mov %r8,%rax - ret - -2: xor %edx,%edx - sub %edi,%edx - and $15,%edx - mov %rax,(%rdi) - mov %rax,8(%rdi) - sub %rdx,%rcx - add %rdx,%rdi - jmp 1b diff --git a/na/mm/memblock.cc b/na/mm/memblock.cc deleted file mode 100644 index aee0245..0000000 --- a/na/mm/memblock.cc +++ /dev/null @@ -1,257 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * memblock.cc — Boot-time physical memory allocator - * - * Simple region tracker: one memory region (from FDT) plus an array - * of reserved regions. Allocation searches for gaps between reserved - * regions. All operations are O(n_reserved), which is fine for the - * ~20 allocations that happen during early boot. - * - * Reference: inspired by Linux kernel's mm/memblock.c - */ - -#include <lume/memblock.h> -#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[]; - -/* ------------------------------------------------------------------ */ -/* Module state */ -/* ------------------------------------------------------------------ */ - -namespace { - -constexpr uint64 kMB = 1024ULL * 1024ULL; - -struct { - uint64 mem_base; - uint64 mem_size; - - struct { - uint64 base; - uint64 size; - } reserved[kMemblockMaxRegions]; - int reserved_count; - bool active; -} g_mb; - -/* ------------------------------------------------------------------ */ -/* Internal helpers */ -/* ------------------------------------------------------------------ */ - -/* Insert a reserved region, keeping the array sorted by base. */ -void insert_reserved(uint64 base, uint64 size) -{ - if (g_mb.reserved_count >= kMemblockMaxRegions) { - kernel_panic("memblock: too many reserved regions"); - } - - int i = g_mb.reserved_count; - while (i > 0 && g_mb.reserved[i - 1].base > base) { - g_mb.reserved[i] = g_mb.reserved[i - 1]; - i--; - } - g_mb.reserved[i] = {base, size}; - g_mb.reserved_count++; -} - -/* Check if [base, base+size) overlaps any reserved region. */ -bool overlaps_reserved(uint64 base, uint64 size) -{ - uint64 end = base + size; - for (int i = 0; i < g_mb.reserved_count; i++) { - uint64 rb = g_mb.reserved[i].base; - uint64 re = rb + g_mb.reserved[i].size; - if (base < re && end > rb) { - return true; - } - } - return false; -} - -/* Find the end of the first reserved region that covers `addr`. */ -uint64 reserved_end_covering(uint64 addr) -{ - for (int i = 0; i < g_mb.reserved_count; i++) { - uint64 rb = g_mb.reserved[i].base; - uint64 re = rb + g_mb.reserved[i].size; - if (addr >= rb && addr < re) { - return re; - } - } - return addr; -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Public API */ -/* ------------------------------------------------------------------ */ - -void memblock_init(uint64 fdt_paddr) -{ - - /* 1. Get memory layout from FDT */ - uint64 mem_base = 0; - uint64 mem_size = 0; - fdt_early_get_mem_info(&mem_base, &mem_size); - if (mem_size == 0) { - kernel_panic("memblock_init: FDT reports zero memory"); - } - - g_mb.mem_base = mem_base; - g_mb.mem_size = mem_size; - g_mb.reserved_count = 0; - g_mb.active = true; - - /* 2. Reserve firmware region [mem_base, kPhysStart). - * - * Everything below the kernel's linked load address is firmware - * territory (OpenSBI), which is PMP-protected. */ - { - uint64 fw_end = kPhysStart; - if (fw_end > mem_base) { - memblock_reserve(mem_base, fw_end - mem_base); - } - } - - /* 2b. Reserve stale pre-relocation kernel image. - * - * After physical KASLR, the kernel is relocated from its original - * load address (kPhysStart) to a new PA. The old copy at - * [kPhysStart, kPhysStart + kernel_size) must not be allocated — - * it contains stale code/data that would corrupt page tables or - * other structures if reused. */ - { - uint64 ks_pa = boot_va_to_pa(virt_addr( - reinterpret_cast<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 / kMB); - kprintf("[memblock] kernel reserved [0x%llx, 0x%llx)\n", ks_pa, ke_pa); -} - -void memblock_reserve(uint64 base, uint64 size) -{ - uint64 aligned_base = page_align_down(base); - uint64 aligned_end = page_align_up(base + size); - insert_reserved(aligned_base, aligned_end - aligned_base); -} - -uint64 memblock_alloc_top(uint64 size, uint64 align) -{ - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - - /* Search from top of RAM downward */ - uint64 end = page_align_down(mem_end); - uint64 start = (end - size) & ~(align - 1); - - while (start >= g_mb.mem_base && start + size <= mem_end) { - if (!overlaps_reserved(start, size)) { - memblock_reserve(start, size); - return start; - } - if (start < align) { - break; - } - start -= align; - } - - kernel_panic("memblock_alloc_top: out of memory"); - return 0; -} - -uint64 memblock_alloc(uint64 size, uint64 align) -{ - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - uint64 start = page_align_up(g_mb.mem_base); - - while (start + size <= mem_end) { - start = (start + align - 1) & ~(align - 1); - if (start + size > mem_end) { - break; - } - - if (!overlaps_reserved(start, size)) { - memblock_reserve(start, size); - return start; - } - - /* Skip past the overlapping reserved region */ - uint64 skip = reserved_end_covering(start); - if (skip <= start) { - start += align; - } else { - start = skip; - } - } - - kernel_panic("memblock_alloc: out of memory"); - return 0; -} - -uint64 memblock_get_mem_base() { return g_mb.mem_base; } -uint64 memblock_get_mem_size() { return g_mb.mem_size; } - -bool memblock_is_active() { return g_mb.active; } -void memblock_retire() { g_mb.active = false; } - -void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), - void* ctx) -{ - uint64 pos = g_mb.mem_base; - uint64 mem_end = g_mb.mem_base + g_mb.mem_size; - - for (int i = 0; i < g_mb.reserved_count && pos < mem_end; i++) { - uint64 rb = g_mb.reserved[i].base; - if (rb < pos) { - uint64 re = g_mb.reserved[i].base + g_mb.reserved[i].size; - if (re > pos) { - pos = re; - } - continue; - } - if (rb > pos) { - cb(pos, rb - pos, ctx); - } - pos = g_mb.reserved[i].base + g_mb.reserved[i].size; - } - - if (pos < mem_end) { - cb(pos, mem_end - pos, ctx); - } -} diff --git a/na/mm/module.mk b/na/mm/module.mk deleted file mode 100644 index 93cef80..0000000 --- a/na/mm/module.mk +++ /dev/null @@ -1,7 +0,0 @@ -SRCS_CC += mm/memblock.cc -SRCS_CC += mm/pmm.cc -SRCS_CC += mm/slab.cc -SRCS_CC += mm/vmm.cc -SRCS_CC += mm/vma.cc -SRCS_CC += mm/page_cache.cc -SRCS_CC += mm/tlb.cc diff --git a/na/mm/page_cache.cc b/na/mm/page_cache.cc deleted file mode 100644 index 25c6a60..0000000 --- a/na/mm/page_cache.cc +++ /dev/null @@ -1,252 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * page_cache.cc — Minimal page cache implementation - * - * Open-addressing hash table indexed by (mapping, index) pairs. - * Fixed-size bucket array allocated at init time via slab. - * - * This is intentionally simple: no LRU, no writeback, no eviction. - * The interface in page_cache.h is designed so that upgrading to a - * radix tree + LRU scheme later only changes this file. - * - * Concurrency: protected by a single spinlock. Acceptable for - * initial bring-up; will be replaced by per-bucket or RCU-based - * locking when contention becomes measurable. - */ - -#include <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 deleted file mode 100644 index b7cf611..0000000 --- a/na/mm/pmm.cc +++ /dev/null @@ -1,528 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * pmm.cc — Physical Memory Manager implementation - * - * Buddy allocator with Per-CPU single-page caches (PCP). - * - * Initialization sequence (called by BSP in single-core period): - * 1. memblock + vmm_init allocate and map the page_map at VMEMMAP. - * 2. Zero and mark all Page descriptors reserved by default. - * 3. Carve free regions from memblock into the buddy system. - * - * The page_map is a flat array indexed by physical page frame number (PFN). - * PFN = (pa - mem_base) / PAGE_SIZE. This keeps address conversion O(1). - * - * Reference: docs/specs/pmm.md - */ - -#include <arch/config.h> -#include <arch/cpu.h> -#include <kernel/sync/spinlock.h> -#include <lume/addr.h> -#include <lume/config.h> -#include <lume/klog.h> -#include <lume/kprintf.h> -#include <lume/memblock.h> -#include <lume/new.h> -#include <lume/pmm.h> -#include <lume/types.h> -#include <lume/vmm.h> - -/* ------------------------------------------------------------------ */ -/* Per-CPU page cache */ -/* ------------------------------------------------------------------ */ - -struct PerCpuCache { - list_node free_list; // Single-page free list - uint32 count; // Number of cached pages -}; - -/* ------------------------------------------------------------------ */ -/* Module-private state */ -/* ------------------------------------------------------------------ */ - -namespace { - -/* PCP Cache tuning parameters */ -constexpr uint32 kPcpBatchDivisor = 1024; -constexpr uint32 kPcpMinBatchSize = 16; -constexpr uint64 kMB = 1024ULL * 1024ULL; - -Spinlock g_pmm_lock; -list_node g_free_areas[kMaxOrder]; // Buddy free lists [0..10] -PerCpuCache g_pcp[kMaxCpus]; - -Page *g_page_map = nullptr; // Flat array of Page descriptors -uint64 g_page_map_pa = 0; // Physical base of page_map storage -uint64 g_page_map_bytes = 0; // Byte size of page_map storage -uint64 g_mem_base = 0; // Physical memory base (from FDT) -uint64 g_mem_size = 0; // Total physical memory size -uint64 g_num_pages = 0; // Total managed pages - -} // namespace - -uint32 g_pcp_high_watermark = 0; -uint32 g_pcp_batch_size = 0; - -/* ------------------------------------------------------------------ */ -/* Address conversion helpers */ -/* ------------------------------------------------------------------ */ - -/* - * PFN (Page Frame Number) — fundamental conversion unit. - * When sparse vmemmap is implemented, only these two functions change. - * All other PA↔Page conversions go through them. - */ -uint64 page_to_pfn(const Page *page) { - uintptr base = reinterpret_cast<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_pfn: page outside page_map bounds"); - } - - uint64 idx = (addr - base) / sizeof(Page); - return (g_mem_base / kPageSize) + idx; -} - -Page *pfn_to_page(uint64 pfn) { - uint64 base_pfn = g_mem_base / kPageSize; - if (pfn < base_pfn || pfn >= base_pfn + g_num_pages) { - kernel_panic("pfn_to_page: PFN outside managed range"); - } - return &g_page_map[pfn - base_pfn]; -} - -PhysAddr page_to_pa(const Page *page) { - return pfn_to_pa(Pfn{page_to_pfn(page)}); -} - -Page *pa_to_page(PhysAddr pa) { - return pfn_to_page(pa_to_pfn(pa).raw); -} - -VirtAddr page_to_va(const Page *page) { - return pa_to_va(page_to_pa(page)); -} - - -/* ------------------------------------------------------------------ */ -/* Buddy system internals (caller must hold g_pmm_lock) */ -/* ------------------------------------------------------------------ */ - -namespace { - -/* - * Find a page's buddy at a given order. - * Buddy PFN = PFN ^ (1 << order). - */ -inline Page *buddy_of(Page *page, uint8 order) { - uint64 pfn = static_cast<uint64>(page - g_page_map); - uint64 buddy_pfn = pfn ^ (1ULL << order); - if (buddy_pfn >= g_num_pages) { - return nullptr; - } - return &g_page_map[buddy_pfn]; -} - -/* - * Insert a free block into the buddy free list at the given order. - */ -void buddy_list_add(Page *page, uint8 order) { - page->type = PageType::Free; - page->order = order; - list_node *head = &g_free_areas[order]; - list_node *node = &page->free.free_link; - node->next = head->next; - node->prev = head; - head->next->prev = node; - head->next = node; -} - -/* - * Remove a block from whatever buddy free list it is on. - */ -void buddy_list_del(Page *page) { - list_node *node = &page->free.free_link; - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = node; - node->prev = node; -} - -/* - * Allocate a block of 2^order contiguous pages from the buddy system. - * Splits higher-order blocks if no exact match is available. - * Returns nullptr if no memory available. - */ -Page *buddy_alloc(uint8 order) { - for (int cur = order; cur < kMaxOrder; cur++) { - list_node *head = &g_free_areas[cur]; - if (head->next == head) { - continue; // Empty at this order - } - - // Pop the first block from this order's free list - list_node *node = head->next; - // Compute Page* from the free_link member offset - Page *page = - reinterpret_cast<Page *>(reinterpret_cast<uintptr>(node) - - __builtin_offsetof(Page, free.free_link)); - buddy_list_del(page); - - // Split down to the requested order - while (cur > order) { - cur--; - // The upper half becomes a free buddy - Page *split = &g_page_map[(page - g_page_map) + (1ULL << cur)]; - buddy_list_add(split, static_cast<uint8>(cur)); - } - - page->type = PageType::Allocated; - page->order = order; - page->refcount.store(1, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->mapping = nullptr; - page->index = 0; - return page; - } - return nullptr; -} - -/* - * Return a block of 2^order pages to the buddy system. - * Coalesces with its buddy recursively up to kMaxOrder-1. - */ -void buddy_free(Page *page, uint8 order) { - uint64 pfn = static_cast<uint64>(page - g_page_map); - - while (order < kMaxOrder - 1) { - Page *buddy = buddy_of(page, order); - if (!buddy) { - break; - } - // Buddy must be free AND at the same order to coalesce - if (buddy->type != PageType::Free || buddy->order != order) { - break; - } - - // Remove buddy from its free list and merge - buddy_list_del(buddy); - - // The merged block starts at the lower PFN - uint64 buddy_pfn = static_cast<uint64>(buddy - g_page_map); - if (buddy_pfn < pfn) { - page = buddy; - pfn = buddy_pfn; - } - order++; - } - - buddy_list_add(page, order); -} - -/* ------------------------------------------------------------------ */ -/* Per-CPU cache operations (interrupts must be disabled by caller) */ -/* ------------------------------------------------------------------ */ - -/* - * Refill the PCP from the global buddy system. - * Acquires g_pmm_lock internally. - */ -void refill_pcp(PerCpuCache *pcp) { - LockGuard guard(g_pmm_lock); - for (uint32 i = 0; i < g_pcp_batch_size; i++) { - Page *page = buddy_alloc(0); - if (!page) { - break; - } - // Put onto PCP list (reuse the free_link for PCP chain) - page->type = PageType::PcpCached; - list_node *node = &page->free.free_link; - node->next = pcp->free_list.next; - node->prev = &pcp->free_list; - pcp->free_list.next->prev = node; - pcp->free_list.next = node; - pcp->count++; - } -} - -/* - * Drain half the PCP back to the global buddy system. - * Acquires g_pmm_lock internally. - */ -void drain_pcp(PerCpuCache *pcp) { - LockGuard guard(g_pmm_lock); - int to_drain = pcp->count / 2; - for (int i = 0; i < to_drain; i++) { - list_node *head = &pcp->free_list; - if (head->next == head) { - break; - } - list_node *node = head->next; - // Unlink from PCP - node->prev->next = node->next; - node->next->prev = node->prev; - pcp->count--; - - Page *page = - reinterpret_cast<Page *>(reinterpret_cast<uintptr>(node) - - __builtin_offsetof(Page, free.free_link)); - buddy_free(page, 0); - } -} - -} // namespace - -/* ------------------------------------------------------------------ */ -/* Public API: single-page (fast path via PCP) */ -/* ------------------------------------------------------------------ */ - -Page *pmm_alloc_page() { - /* - * Disable interrupts to prevent reentry on the same CPU - * (e.g. timer IRQ handler calling pmm_alloc_page while we - * are mid-way through manipulating the PCP list). - */ - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - PerCpuCache *pcp = &g_pcp[cpu]; - - if (pcp->count == 0) { - refill_pcp(pcp); - } - - Page *page = nullptr; - if (pcp->count > 0) { - list_node *node = pcp->free_list.next; - // Unlink - node->prev->next = node->next; - node->next->prev = node->prev; - pcp->count--; - - page = - reinterpret_cast<Page *>(reinterpret_cast<uintptr>(node) - - __builtin_offsetof(Page, free.free_link)); - - // Validate the page is within the page_map bounds - if (page < g_page_map || page >= g_page_map + g_num_pages) { - kernel_panic("pmm_alloc_page: PCP returned out-of-bounds page"); - } - - page->type = PageType::Allocated; - page->order = 0; - page->refcount.store(1, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->mapping = nullptr; - page->index = 0; - } - - if (was_on) { - arch::cpu::intr_on(); - } - return page; -} - -void pmm_free_page(Page *page) { - if (!page) { - kernel_panic("pmm_free_page: null page"); - } - - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - PerCpuCache *pcp = &g_pcp[cpu]; - - // Push onto PCP - page->type = PageType::PcpCached; - page->order = 0; - list_node *node = &page->free.free_link; - node->next = pcp->free_list.next; - node->prev = &pcp->free_list; - pcp->free_list.next->prev = node; - pcp->free_list.next = node; - pcp->count++; - - // Drain if over high watermark - if (pcp->count >= g_pcp_high_watermark) { - drain_pcp(pcp); - } - - if (was_on) { - arch::cpu::intr_on(); - } -} - -/* ------------------------------------------------------------------ */ -/* Public API: multi-page (slow path, direct buddy) */ -/* ------------------------------------------------------------------ */ - -Page *pmm_alloc_pages(uint8 order) { - if (order >= kMaxOrder) { - return nullptr; - } - LockGuard guard(g_pmm_lock); - return buddy_alloc(order); -} - -void pmm_free_pages(Page *page, uint8 order) { - if (!page) { - kernel_panic("pmm_free_pages: null page"); - } - if (order >= kMaxOrder) { - kernel_panic("pmm_free_pages: order out of range"); - } - LockGuard guard(g_pmm_lock); - buddy_free(page, order); -} - -/* ------------------------------------------------------------------ */ -/* Public API: reference counting */ -/* ------------------------------------------------------------------ */ - -void page_incref(Page *page) { page->refcount.fetch_add(1); } - -void page_decref(Page *page) { - uint32 old = page->refcount.fetch_sub(1); - if (old == 1) { - // Refcount dropped to 0 — return to pool based on originally allocated - // order - if (page->order == 0) { - pmm_free_page(page); - } else { - pmm_free_pages(page, page->order); - } - } -} - -/* ------------------------------------------------------------------ */ -/* Initialization */ -/* ------------------------------------------------------------------ */ - -/* - * Helper: zero out a range of bytes. We cannot use memset here - * because lib/string may not be linked yet. - */ -namespace { -void zero_range(void *start, uint64 bytes) { - uint8 *p = static_cast<uint8 *>(start); - for (uint64 i = 0; i < bytes; i++) { - p[i] = 0; - } -} -} // namespace - -void pmm_init() { - /* 1. Get memory layout from memblock (already scanned FDT) */ - g_mem_base = memblock_get_mem_base(); - g_mem_size = memblock_get_mem_size(); - g_num_pages = vmm_get_num_pages(); - - if (g_mem_size == 0) { - kernel_panic("pmm_init: memblock reports zero memory"); - } - - /* 2. PCP parameters */ - uint32 calc_batch = static_cast<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. */ - g_page_map_pa = vmm_get_page_map_pa(); - g_page_map_bytes = vmm_get_page_map_size(); - g_page_map = reinterpret_cast<Page *>(arch::g_vmemmap_base); - - /* 4. Zero out the entire page_map */ - zero_range(g_page_map, g_page_map_bytes); - - /* 4b. Default every page descriptor to reserved/non-free. */ - for (uint64 i = 0; i < g_num_pages; i++) { - Page *page = &g_page_map[i]; - page->refcount.store(0, __ATOMIC_RELAXED); - page->mapcount.store(0, __ATOMIC_RELAXED); - page->flags.store(0, __ATOMIC_RELAXED); - page->type = PageType::Allocated; - page->order = 0; - page->mapping = nullptr; - page->index = 0; - page->lru.init(); - page->free.free_link.init(); - } - - /* 5. Initialize buddy free list heads */ - g_pmm_lock.init("pmm"); - for (int i = 0; i < kMaxOrder; i++) { - g_free_areas[i].init(); - } - - /* 6. Initialize PCP list heads */ - for (int i = 0; i < kMaxCpus; i++) { - g_pcp[i].free_list.init(); - g_pcp[i].count = 0; - } - - /* 7. Feed free regions from memblock into the buddy system. - * memblock knows about all reserved regions (firmware, kernel, - * page table pages, page_map). Everything else is free. */ - struct FeedCtx { - uint64 total_pages; - }; - FeedCtx ctx{0}; - - memblock_for_each_free( - [](uint64 base, uint64 size, void *arg) { - auto *c = static_cast<FeedCtx *>(arg); - uint64 start_pfn = (base - memblock_get_mem_base()) / kPageSize; - uint64 end_pfn = start_pfn + size / kPageSize; - - /* Access page_map via the globals directly (they are in anon namespace now) */ - - uint64 pfn = start_pfn; - while (pfn < end_pfn) { - uint8 order = 0; - for (int o = kMaxOrder - 1; o >= 0; o--) { - uint64 block_size = 1ULL << o; - if ((pfn & (block_size - 1)) == 0 && pfn + block_size <= end_pfn) { - order = static_cast<uint8>(o); - break; - } - } - - Page *page = &g_page_map[pfn]; - page->type = PageType::Free; - page->order = order; - page->refcount.store(0, __ATOMIC_RELAXED); - page->free.free_link.init(); - - /* Inline buddy_list_add since we're inside a lambda */ - list_node *head = &g_free_areas[order]; - list_node *node = &page->free.free_link; - node->next = head->next; - node->prev = head; - head->next->prev = node; - head->next = node; - - pfn += (1ULL << order); - c->total_pages += (1ULL << order); - } - }, - &ctx); - - kprintf("[pmm] buddy: %llu free pages (%llu MB)\n", ctx.total_pages, - (ctx.total_pages * kPageSize) / kMB); - - /* 8. Retire memblock — all future allocations go through PMM */ - memblock_retire(); -} - -PhysAddr pmm_get_mem_base() { return phys_addr(g_mem_base); } -uint64 pmm_get_mem_size() { return g_mem_size; } diff --git a/na/mm/slab.cc b/na/mm/slab.cc deleted file mode 100644 index 43d8c4a..0000000 --- a/na/mm/slab.cc +++ /dev/null @@ -1,321 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * slab.cc — SLUB allocator implementation - * - * Key design points: - * - Free object list is embedded in the objects themselves (first 8 bytes). - * - Per-page metadata lives in Page.slab (cache, obj_count, freelist). - * - Per-CPU "active page" avoids lock contention on the fast path. - * - partial_ list protected by per-KmemCache Spinlock. - * - PMM calls happen outside the lock to avoid lock ordering issues. - * - * Address note: the kernel may be running at physical addresses via - * identity mapping (before vmm_init). We handle PA/VA carefully: - * PMM gives us Page*, page_to_pa() gives PA, and we access page - * contents via pa_to_va() (higher-half VA, always mapped). - * - * Reference: docs/specs/slab.md - */ - -#include "slab.h" -#include <lume/pmm.h> -#include <lume/addr.h> -#include <lume/klog.h> -#include <lume/new.h> -#include <arch/cpu.h> - -/* ------------------------------------------------------------------ */ -/* Helpers */ -/* ------------------------------------------------------------------ */ - -/* - * Convert a Page* to the usable virtual address of the page's contents. - * Uses the higher-half mapping which is always active. - */ -static inline void* page_to_data_va(Page* page) -{ - return reinterpret_cast<void*>(page_to_va(page).raw); -} - -/* Round up x to the next multiple of align (align must be power of 2) */ -static inline uint32 align_up(uint32 x, uint32 align) -{ - return (x + align - 1) & ~(align - 1); -} - -/* ------------------------------------------------------------------ */ -/* Global size-class caches */ -/* ------------------------------------------------------------------ */ - -KmemCache g_size_caches[kNumSizeClasses]; - -/* ------------------------------------------------------------------ */ -/* KmemCache::init */ -/* ------------------------------------------------------------------ */ - -void KmemCache::init(const char* name, uint32 obj_size, uint32 obj_align) -{ - name_ = name; - obj_align_ = (obj_align < 8) ? 8 : obj_align; // minimum 8 for freelist ptr - obj_size_ = align_up((obj_size < 8) ? 8 : obj_size, obj_align_); - objs_per_slab_ = kPageSize / obj_size_; - - lock_.init(name); - partial_.init(); - - for (int i = 0; i < kMaxCpus; i++) - cpu_slab_[i].active = nullptr; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::new_slab — allocate and format a fresh slab page */ -/* ------------------------------------------------------------------ */ - -Page* KmemCache::new_slab() -{ - Page* page = pmm_alloc_page(); - if (!page) - return nullptr; - - page->type = PageType::Slab; - page->slab.cache = this; - page->slab.obj_count = 0; - - /* Build the embedded free list across all object slots */ - uint8* base = static_cast<uint8*>(page_to_data_va(page)); - void* head = nullptr; - - /* Chain objects from last to first so freelist order = low addr first */ - for (int i = static_cast<int>(objs_per_slab_) - 1; i >= 0; i--) { - void* obj = base + i * obj_size_; - *reinterpret_cast<void**>(obj) = head; - head = obj; - } - page->slab.freelist = head; - - return page; -} - -/* ------------------------------------------------------------------ */ -/* Partial list management (lock must be held) */ -/* ------------------------------------------------------------------ */ - -Page* KmemCache::get_partial() -{ - /* - * Phase 1 simplification: no partial list tracking. - * The slow path always allocates a fresh page from PMM. - * Full pages stay alive (Page.slab.cache is valid for kfree). - * Empty pages are returned to PMM immediately. - * - * TODO: add list_node to Page.slab and implement partial tracking - * to avoid wasting partially-filled pages. - */ - (void)partial_; - return nullptr; -} - -void KmemCache::put_partial(Page* page) -{ - (void)page; -} - -void KmemCache::remove_partial(Page* page) -{ - (void)page; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::alloc */ -/* ------------------------------------------------------------------ */ - -void* KmemCache::alloc() -{ - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - uint64 cpu = arch::cpu::id(); - Page* active = cpu_slab_[cpu].active; - - /* Fast path: pop from active page's freelist */ - if (active && active->slab.freelist) { - void* obj = active->slab.freelist; - active->slab.freelist = *reinterpret_cast<void**>(obj); - active->slab.obj_count++; - if (was_on) arch::cpu::intr_on(); - return obj; - } - - /* Slow path: need a new active page */ - - /* If current active is full, detach it (it stays as Slab state, - * cache pointer still valid for kfree to find it) */ - if (active && !active->slab.freelist) { - cpu_slab_[cpu].active = nullptr; - } - - if (was_on) arch::cpu::intr_on(); - - /* Try to get a partial page or allocate a fresh one. - * PMM call happens without any lock held. */ - Page* new_page = new_slab(); - if (!new_page) - return nullptr; - - /* Install as new active page */ - was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - /* Another interrupt handler might have set a new active while we - * were allocating. If so, put ours aside (simplified: just use ours). */ - cpu_slab_[cpu].active = new_page; - - void* obj = new_page->slab.freelist; - new_page->slab.freelist = *reinterpret_cast<void**>(obj); - new_page->slab.obj_count++; - - if (was_on) arch::cpu::intr_on(); - return obj; -} - -/* ------------------------------------------------------------------ */ -/* KmemCache::free */ -/* ------------------------------------------------------------------ */ - -void KmemCache::free(void* obj) -{ - if (!obj) - return; - - /* Find the Page for this object's page */ - uint64 obj_addr = reinterpret_cast<uint64>(obj); - - /* Convert VA to PA. Like pmm.cc, handle both PA and VA cases. */ - uint64 obj_pa = va_to_pa(virt_addr(obj_addr)).raw; - uint64 page_pa = obj_pa & ~(kPageSize - 1); - Page* page = pa_to_page(phys_addr(page_pa)); - - if (page->type != PageType::Slab || page->slab.cache != this) - kernel_panic("slab free: object does not belong to this cache"); - - bool was_on = arch::cpu::intr_enabled(); - arch::cpu::intr_off(); - - /* Push object back onto the page's freelist */ - *reinterpret_cast<void**>(obj) = page->slab.freelist; - page->slab.freelist = obj; - page->slab.obj_count--; - - /* If page is now completely empty, return it to PMM */ - if (page->slab.obj_count == 0) { - /* If this page is our active, clear it */ - uint64 cpu = arch::cpu::id(); - if (cpu_slab_[cpu].active == page) - cpu_slab_[cpu].active = nullptr; - - if (was_on) arch::cpu::intr_on(); - - /* Return page to PMM */ - page->slab.cache = nullptr; - page->slab.freelist = nullptr; - pmm_free_page(page); - return; - } - - if (was_on) arch::cpu::intr_on(); -} - -/* ------------------------------------------------------------------ */ -/* Public free-function API */ -/* ------------------------------------------------------------------ */ - -/* Find the smallest size class >= size */ -static KmemCache* find_cache(uint32 size) -{ - for (int i = 0; i < kNumSizeClasses; i++) { - if (kSizeClasses[i] >= size) - return &g_size_caches[i]; - } - return nullptr; // Too large for slab -} - -void* kmalloc(uint32 size) -{ - if (size == 0) - return nullptr; - - /* Large allocation: fall back to PMM directly */ - if (size > kSizeClasses[kNumSizeClasses - 1]) { - uint8 order = 0; - uint64 needed = (size + kPageSize - 1) / kPageSize; - while ((1ULL << order) < needed) - order++; - Page* page = pmm_alloc_pages(order); - if (!page) - return nullptr; - return reinterpret_cast<void*>(pa_to_va(page_to_pa(page)).raw); - } - - KmemCache* cache = find_cache(size); - if (!cache) - return nullptr; - return cache->alloc(); -} - -void kfree(void* ptr) -{ - if (!ptr) - return; - - uint64 addr = reinterpret_cast<uint64>(ptr); - uint64 pa = va_to_pa(virt_addr(addr)).raw; - uint64 page_pa = pa & ~(kPageSize - 1); - Page* page = pa_to_page(phys_addr(page_pa)); - - if (page->type == PageType::Slab) { - KmemCache* cache = page->slab.cache; - if (!cache) - kernel_panic("kfree: slab page has null cache"); - cache->free(ptr); - } else if (page->type == PageType::Allocated) { - /* Large allocation — return directly to PMM */ - pmm_free_pages(page, page->order); - } else { - kernel_panic("kfree: invalid page state"); - } -} - -/* ------------------------------------------------------------------ */ -/* C++ operator new / delete */ -/* ------------------------------------------------------------------ */ - -/* Use decltype(sizeof(0)) instead of size_t to match the compiler's - * built-in expectation for operator new (unsigned long vs unsigned long long). */ -using __sz = decltype(sizeof(0)); - -void* operator new(__sz size) { return kmalloc(static_cast<uint32>(size)); } -void* operator new[](__sz size) { return kmalloc(static_cast<uint32>(size)); } - -void operator delete(void* ptr) noexcept { kfree(ptr); } -void operator delete[](void* ptr) noexcept { kfree(ptr); } -void operator delete(void* ptr, __sz) noexcept { kfree(ptr); } -void operator delete[](void* ptr, __sz) noexcept { kfree(ptr); } - -/* ------------------------------------------------------------------ */ -/* Initialization */ -/* ------------------------------------------------------------------ */ - -void slab_init() -{ - for (int i = 0; i < kNumSizeClasses; i++) { - /* Build a name string like "kmalloc-32" */ - static const char* names[kNumSizeClasses] = { - "kmalloc-8", "kmalloc-16", "kmalloc-32", - "kmalloc-64", "kmalloc-128", "kmalloc-256", - "kmalloc-512", "kmalloc-1024","kmalloc-2048" - }; - g_size_caches[i].init(names[i], kSizeClasses[i], 8); - } -} diff --git a/na/mm/slab.h b/na/mm/slab.h deleted file mode 100644 index 909ee62..0000000 --- a/na/mm/slab.h +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#pragma once - -/* - * SLUB Allocator — module-private header - * - * SLUB = Simple List of Unfull Blocks. - * Metadata lives in Page descriptors (no per-page Slab header). - * Free object list is embedded inside the objects themselves. - * Per-CPU "active page" eliminates lock contention on the fast path. - * - * Reference: docs/specs/slab.md - */ - -#include <lume/types.h> -#include <lume/config.h> -#include <lume/page.h> -#include <lume/list.h> -#include <kernel/sync/spinlock.h> - -/* Size classes: 8, 16, 32, 64, 128, 256, 512, 1024, 2048 */ -inline constexpr int kNumSizeClasses = 9; -inline constexpr uint32 kSizeClasses[kNumSizeClasses] = { - 8, 16, 32, 64, 128, 256, 512, 1024, 2048 -}; - -/* Per-CPU active slab page pointer */ -struct PerCpuSlab { - Page* active; // Currently used slab page (nullptr if none) -}; - -/* - * KmemCache — manages a pool of fixed-size objects. - * - * Lock contract: - * lock_ protects partial_ list only. - * Per-CPU cpu_slab_ access requires interrupts disabled. - * PMM calls happen outside lock_ to avoid lock ordering issues. - */ -class KmemCache { -public: - KmemCache() = default; - - // Explicit init (Placement New / BSS pattern) - void init(const char* name, uint32 obj_size, uint32 obj_align); - - // Allocate one object. Returns nullptr on OOM. - void* alloc(); - - // Free one object back to its owning page. - void free(void* obj); - - const char* name() const { return name_; } - uint32 obj_size() const { return obj_size_; } - -private: - // Get a partially-filled page from the partial list (lock must be held) - Page* get_partial(); - - // Put a page onto the partial list (lock must be held) - void put_partial(Page* page); - - // Remove a page from the partial list (lock must be held) - void remove_partial(Page* page); - - // Allocate a new page from PMM and format it as a slab - Page* new_slab(); - - Spinlock lock_; // Protects partial_ only - list_node partial_; // Partially-filled slab pages - - PerCpuSlab cpu_slab_[kMaxCpus]; // Per-CPU active page - - const char* name_ = "uninit"; - uint32 obj_size_ = 0; // Aligned object size (bytes) - uint32 obj_align_ = 0; // Alignment requirement - uint32 objs_per_slab_ = 0; // Objects per 4KB page -}; - -/* Global size-class caches */ -extern KmemCache g_size_caches[kNumSizeClasses]; diff --git a/na/mm/tlb.cc b/na/mm/tlb.cc deleted file mode 100644 index b7a8ba7..0000000 --- a/na/mm/tlb.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * tlb.cc — TLB shootdown implementation - * - * Uses RISC-V SBI Remote Fence extension to perform cross-hart - * TLB invalidation. SBI firmware handles the IPI delivery and - * synchronization — the call blocks until all target harts have - * completed their local sfence.vma. - * - * When a VmSpace is provided, we use its cpu_mask to target only - * the CPUs that have this address space loaded in satp. This avoids - * unnecessary IPI storms on unrelated cores. - * - * Reference: docs/specs/linux_abi_mm_plan.md §SMP §2.1 - */ - -#include <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 deleted file mode 100644 index da89e08..0000000 --- a/na/mm/vma.cc +++ /dev/null @@ -1,640 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vma.cc — Virtual Memory Area management (MI layer) - * - * Manages user address spaces using a red-black tree of VMA regions. - * Implements demand paging: vmm_map_user records intent only; - * physical pages are allocated on page fault via vmm_handle_page_fault. - * - * MI/MD separation: this file NEVER references pmap::PTE_* constants. - * Permission translation is delegated to pmap::vm_perm_to_pte(). - * - * Reference: docs/specs/vmm.md - */ - -#include <lume/vmm.h> -#include <lume/addr.h> -#include <lume/config.h> -#include <lume/errno.h> -#include <lume/pmm.h> -#include <lume/new.h> -#include <lume/klog.h> -#include <arch/pmap.h> -#include <lume/tlb.h> -#include <lume/mm_layout.h> - -/* ------------------------------------------------------------------ */ -/* Rbtree callbacks for VMA interval tree */ -/* ------------------------------------------------------------------ */ - -static bool vma_less(struct rb_node* node, const struct rb_node* parent) { - VmAreaStruct* n = reinterpret_cast<VmAreaStruct*>( - reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - const VmAreaStruct* p = reinterpret_cast<const VmAreaStruct*>( - reinterpret_cast<const char*>(parent) - __builtin_offsetof(VmAreaStruct, rb)); - return n->start < p->start; -} - -static int vma_cmp(const void* key, const struct rb_node* node) { - uint64 addr = *static_cast<const uint64*>(key); - const VmAreaStruct* n = reinterpret_cast<const VmAreaStruct*>( - reinterpret_cast<const char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - if (addr < n->start) return -1; - if (addr >= n->end) return 1; - return 0; -} - -static VmAreaStruct* node_to_vma(struct rb_node* node) -{ - return reinterpret_cast<VmAreaStruct*>( - reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); -} - -static uint64 align_up_any(uint64 value, uint64 align) -{ - if (align == 0) { - return value; - } - return ((value + align - 1) / align) * align; -} - -static bool range_overlaps(uint64 a_start, uint64 a_end, - uint64 b_start, uint64 b_end) -{ - return a_start < b_end && b_start < a_end; -} - - - -static bool vma_overlaps_locked(VmSpace* space, uint64 start, uint64 end) -{ - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (range_overlaps(start, end, vma->start, vma->end)) { - return true; - } - node = rb_next(node); - } - return false; -} - -static bool vma_range_covered_locked(VmSpace* space, uint64 start, uint64 end) -{ - uint64 cursor = start; - struct rb_node* node = rb_first(&space->vma_tree); - while (node && cursor < end) { - VmAreaStruct* vma = node_to_vma(node); - if (vma->end <= cursor) { - node = rb_next(node); - continue; - } - if (vma->start > cursor) { - return false; - } - if (vma->end > cursor) { - cursor = vma->end; - } - node = rb_next(node); - } - return cursor >= end; -} - -static void vma_try_merge_neighbors_locked(VmSpace* space, VmAreaStruct* vma) -{ - struct rb_node* prev_node = rb_prev(&vma->rb); - if (prev_node) { - VmAreaStruct* prev = node_to_vma(prev_node); - if (prev->end == vma->start && prev->perm == vma->perm) { - prev->end = vma->end; - rb_erase(&vma->rb, &space->vma_tree); - delete vma; - vma = prev; - } - } - - struct rb_node* next_node = rb_next(&vma->rb); - if (next_node) { - VmAreaStruct* next = node_to_vma(next_node); - if (vma->end == next->start && vma->perm == next->perm) { - vma->end = next->end; - rb_erase(&next->rb, &space->vma_tree); - delete next; - } - } -} - -static int vma_split_locked(VmSpace* space, VmAreaStruct* vma, uint64 split) -{ - if (split <= vma->start || split >= vma->end) { - return 0; - } - - VmAreaStruct* right = new VmAreaStruct; - if (!right) { - return -ENOMEM; - } - - right->start = split; - right->end = vma->end; - right->perm = vma->perm; - right->mm = space; - - vma->end = split; - rb_add(&right->rb, &space->vma_tree, vma_less); - return 0; -} - -static int vma_split_range_locked(VmSpace* space, uint64 start, uint64 end) -{ - if (end <= start) { - return 0; - } - - struct rb_node* start_node = rb_find(&start, &space->vma_tree, vma_cmp); - if (!start_node) { - return -ENOMEM; - } - VmAreaStruct* start_vma = node_to_vma(start_node); - int ret = vma_split_locked(space, start_vma, start); - if (ret < 0) { - return ret; - } - - uint64 end_key = end - 1; - struct rb_node* end_node = rb_find(&end_key, &space->vma_tree, vma_cmp); - if (!end_node) { - return -ENOMEM; - } - VmAreaStruct* end_vma = node_to_vma(end_node); - return vma_split_locked(space, end_vma, end); -} - -/* ------------------------------------------------------------------ */ -/* VmSpace lifecycle */ -/* ------------------------------------------------------------------ */ - -VmSpace* VmSpace::create() { - VmSpace* space = new VmSpace; - if (!space) return nullptr; - - space->root_pa = pmap::create(); - if (!space->root_pa) { - delete space; - return nullptr; - } - - space->vma_tree = RB_ROOT; - space->lock.init("VmSpace"); - space->refcount.store(1, __ATOMIC_RELAXED); - space->brk_start = 0; - space->brk_end = 0; - return space; -} - -void VmSpace::incref() { - refcount.fetch_add(1, __ATOMIC_RELAXED); -} - -void VmSpace::put() { - if (refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) { - destroy(); - } -} - -void VmSpace::destroy() { - /* Drain VMA tree under lock, then release BEFORE delete this */ - lock.acquire(); - uint64 root = root_pa; - struct rb_node* node = rb_first(&vma_tree); - while (node) { - VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( - reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - - /* Decref and dec mapcount for all mapped physical pages */ - for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { - uint64 pa; - if (pmap::lookup(root, addr, &pa)) { - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - } - } - - node = rb_next(node); - rb_erase(&vma->rb, &vma_tree); - delete vma; - } - lock.release(); - - /* Destroy hardware page tables outside the lock */ - pmap::destroy(root); - - /* Finally free VmSpace itself */ - delete this; -} - -/* ------------------------------------------------------------------ */ -/* Public API: VMA manipulation */ -/* ------------------------------------------------------------------ */ - -int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) { - if (!space) return -EINVAL; - - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return 0; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - - VmAreaStruct* vma = new VmAreaStruct; - if (!vma) return -ENOMEM; - vma->start = va; - vma->end = va + len; - vma->perm = perm | VM_USER; // Enforce user-mode access (MI flag) - vma->mm = space; - - LockGuard guard(space->lock); - if (vma_overlaps_locked(space, vma->start, vma->end)) { - delete vma; - return -EINVAL; - } - rb_add(&vma->rb, &space->vma_tree, vma_less); - vma_try_merge_neighbors_locked(space, vma); - return 0; -} - -int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len) { - if (!space) return -EINVAL; - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return 0; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - uint64 end = va + len; - - LockGuard guard(space->lock); - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - struct rb_node* next = rb_next(node); - - if (vma->end <= va) { - node = next; - continue; - } - if (vma->start >= end) { - break; - } - - uint64 unmap_start = (vma->start > va) ? vma->start : va; - uint64 unmap_end = (vma->end < end) ? vma->end : end; - - if (unmap_start < unmap_end) { - for (uint64 addr = unmap_start; addr < unmap_end; addr += kPageSize) { - uint64 pa; - if (pmap::lookup(space->root_pa, addr, &pa)) { - pmap::unmap(space->root_pa, addr); - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - } - } - } - - if (unmap_start == vma->start && unmap_end == vma->end) { - rb_erase(&vma->rb, &space->vma_tree); - delete vma; - } else if (unmap_start == vma->start) { - rb_erase(&vma->rb, &space->vma_tree); - vma->start = unmap_end; - rb_add(&vma->rb, &space->vma_tree, vma_less); - } else if (unmap_end == vma->end) { - vma->end = unmap_start; - } else { - VmAreaStruct* right = new VmAreaStruct; - if (!right) { - return -ENOMEM; - } - right->start = unmap_end; - right->end = vma->end; - right->perm = vma->perm; - right->mm = space; - - vma->end = unmap_start; - rb_add(&right->rb, &space->vma_tree, vma_less); - break; - } - - node = next; - } - return 0; -} - -int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm) -{ - if (!space) return -EINVAL; - va = page_align_down(va); - len = page_align_up(len); - if (len == 0) return -EINVAL; - if (va < kUserVaBase) return -EINVAL; - if (va >= kUserVaLimit) return -EINVAL; - if (va + len < va || va + len > kUserVaLimit) return -EINVAL; - - uint64 end = va + len; - - LockGuard guard(space->lock); - if (!vma_range_covered_locked(space, va, end)) { - return -ENOMEM; - } - int ret = vma_split_range_locked(space, va, end); - if (ret < 0) { - return ret; - } - - bool tlb_flush_needed = false; - struct rb_node* node = rb_find(&va, &space->vma_tree, vma_cmp); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (vma->start >= end) { - break; - } - if (vma->end <= va) { - node = rb_next(node); - continue; - } - - vma->perm = (perm | VM_USER); - - for (uint64 addr = vma->start; addr < vma->end; addr += kPageSize) { - uint64 pa; - if (!pmap::lookup(space->root_pa, addr, &pa)) { - continue; - } - - if (perm == 0) { - Page* pg = pa_to_page(phys_addr(pa)); - page_dec_mapcount(pg); - page_decref(pg); - pmap::unmap(space->root_pa, addr); - tlb_flush_needed = true; - continue; - } - - uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); - if ((vma->perm & VM_WRITE) && pmap::is_cow(space->root_pa, addr)) { - pte_perm &= ~pmap::PTE_W; - pte_perm |= pmap::PTE_COW; - } - - int ret = pmap::protect(space->root_pa, addr, pte_perm); - if (ret < 0) { - return ret; - } - tlb_flush_needed = true; - } - - node = rb_next(node); - } - - if (tlb_flush_needed) { - tlb_shootdown_range(space, va, end); - } - return 0; -} - -uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align) -{ - if (!space) return 0; - length = page_align_up(length); - if (length == 0) return 0; - if (align < kPageSize) align = kPageSize; - - LockGuard guard(space->lock); - - /* Linux: arch_get_unmapped_area() starts from mm->mmap_base. - * RISC-V: TASK_UNMAPPED_BASE = PAGE_ALIGN(TASK_SIZE / 3). - * Fall back to kUserVaBase if mmap_base not set (kernel VmSpaces). */ - uint64 base = space->mmap_base ? space->mmap_base : kUserVaBase; - uint64 cursor = align_up_any(base, align); - struct rb_node* node = rb_first(&space->vma_tree); - while (node) { - VmAreaStruct* vma = node_to_vma(node); - if (cursor <= vma->start) { - if (cursor > kUserVaLimit - length) { - return 0; - } - if (cursor + length <= vma->start) { - return cursor; - } - } - if (cursor < vma->end) { - cursor = vma->end; - } - cursor = align_up_any(cursor, align); - node = rb_next(node); - } - - if (cursor > kUserVaLimit - length) { - return 0; - } - return cursor; -} - -bool vma_range_covered(VmSpace* space, uint64 start, uint64 end) -{ - if (!space) return false; - start = page_align_down(start); - end = page_align_up(end); - if (end <= start) return true; - - LockGuard guard(space->lock); - return vma_range_covered_locked(space, start, end); -} - -/* ------------------------------------------------------------------ */ -/* Page fault handler (demand paging) */ -/* ------------------------------------------------------------------ */ - -static bool vma_allows(const VmAreaStruct* vma, VmFaultCause cause) -{ - switch (cause) { - case VmFaultCause::Read: - return (vma->perm & VM_READ) != 0; - case VmFaultCause::Write: - return (vma->perm & VM_WRITE) != 0; - case VmFaultCause::Exec: - return (vma->perm & VM_EXEC) != 0; - default: - return false; - } -} - -int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause) { - if (!space) return -EINVAL; - - uint64 aligned_addr = page_align_down(fault_addr); - - LockGuard guard(space->lock); - struct rb_node* node = rb_find(&fault_addr, &space->vma_tree, vma_cmp); - if (!node) { - return -EFAULT; // No VMA covers this address → segfault - } - - VmAreaStruct* vma = reinterpret_cast<VmAreaStruct*>( - reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - - if (!vma_allows(vma, cause)) { - return -EFAULT; - } - - /* Check if page is already mapped */ - uint64 paddr; - if (pmap::lookup(space->root_pa, aligned_addr, &paddr)) { - if (cause == VmFaultCause::Write && pmap::is_cow(space->root_pa, aligned_addr)) { - /* Store Page Fault on a COW page */ - Page* page = pmm_alloc_page(); - if (!page) return -ENOMEM; - uint64 new_pa = page_to_pa(page).raw; - - /* Copy page contents */ - uint8* dst = reinterpret_cast<uint8*>(pa_to_va(phys_addr(new_pa)).raw); - const uint8* src = reinterpret_cast<const uint8*>(pa_to_va(phys_addr(paddr)).raw); - for (uint64 i = 0; i < kPageSize; i++) { - dst[i] = src[i]; - } - - /* Remap with write permission, clearing COW */ - uint64 pte_perm = pmap::vm_perm_to_pte(vma->perm); - if (pmap::map(space->root_pa, aligned_addr, new_pa, pte_perm) != 0) { - pmm_free_page(page); - return -ENOMEM; - } - - /* New page gets mapcount = 1 (we just mapped it) */ - page_inc_mapcount(page); - - /* Decrement mapcount and refcount of old page */ - Page* old_page = pa_to_page(phys_addr(paddr)); - page_dec_mapcount(old_page); - page_decref(old_page); - - /* Flush TLB to remove old read-only cached translation */ - tlb_shootdown_page(space, aligned_addr); - return 0; - } - /* DEBUG: page already mapped but still faulting — dump PTE */ - { - uint64 cur_satp; - asm volatile("csrr %0, satp" : "=r"(cur_satp)); - printk(KERN_ERR "fault: va=%llx pa=%llx cause=%d pte=%llx satp=%llx root=%llx\n", - aligned_addr, paddr, static_cast<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 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); - - if (pmap::map(space->root_pa, aligned_addr, pa, pte_perm) != 0) { - pmm_free_page(page); - return -ENOMEM; - } - - /* Initial mapping: refcount is already 1 from pmm_alloc_page. - * Set mapcount = 1 to track this PTE mapping. */ - page_inc_mapcount(page); - - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Fork / Clone (COW infrastructure) */ -/* ------------------------------------------------------------------ */ - -VmSpace* vmm_clone(VmSpace* parent) { - if (!parent) return nullptr; - - VmSpace* child = VmSpace::create(); - if (!child) return nullptr; - - child->brk_start = parent->brk_start; - child->brk_end = parent->brk_end; - - LockGuard parent_guard(parent->lock); - - struct rb_node* node = rb_first(&parent->vma_tree); - while (node) { - VmAreaStruct* parent_vma = reinterpret_cast<VmAreaStruct*>( - reinterpret_cast<char*>(node) - __builtin_offsetof(VmAreaStruct, rb)); - - VmAreaStruct* child_vma = new VmAreaStruct; - if (!child_vma) { - child->destroy(); - return nullptr; - } - child_vma->start = parent_vma->start; - child_vma->end = parent_vma->end; - child_vma->perm = parent_vma->perm; - child_vma->mm = child; - - /* Copy pages and mark COW if writable */ - for (uint64 va = child_vma->start; va < child_vma->end; va += kPageSize) { - uint64 pa; - if (pmap::lookup(parent->root_pa, va, &pa)) { - /* Make parent COW if it was writable */ - if (child_vma->perm & VM_WRITE) { - pmap::make_cow(parent->root_pa, va); - /* Flush TLB for parent since we reduced permissions */ - tlb_shootdown_page(parent, va); - } - - /* Map in child as COW or read-only */ - uint64 pte_perm = pmap::vm_perm_to_pte(child_vma->perm); - if (child_vma->perm & VM_WRITE) { - pte_perm &= ~pmap::PTE_W; - pte_perm |= pmap::PTE_COW; - } - - if (pmap::map(child->root_pa, va, pa, pte_perm) != 0) { - /* If map fails, we leak what we did in this loop for now, - * but child->destroy() will clean it up safely since - * we increment refcounts as we go. */ - delete child_vma; - child->destroy(); - return nullptr; - } - - /* Increment physical page refcount and mapcount */ - Page* shared_page = pa_to_page(phys_addr(pa)); - page_incref(shared_page); - page_inc_mapcount(shared_page); - } - } - - rb_add(&child_vma->rb, &child->vma_tree, vma_less); - node = rb_next(node); - } - - return child; -} diff --git a/na/mm/vmm.cc b/na/mm/vmm.cc deleted file mode 100644 index 51617e3..0000000 --- a/na/mm/vmm.cc +++ /dev/null @@ -1,235 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * vmm.cc — Virtual Memory Manager (Phase 1: Kernel Page Table) - * - * Builds a fine-grained kernel page table, activates it, and retires - * the early_pgdir identity mapping from entry.S. - * - * With memblock integration, the initialization order is: - * memblock_init → vmm_init (allocates page_map from memblock, - * maps vmemmap, activates page table) → pmm_init (buddy system - * starts with page_map already at vmemmap VA — no relocation). - * - * Reference: docs/specs/vmm.md - */ - -#include <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> -#include <lume/page.h> -#include <lume/klog.h> -#include <lume/kprintf.h> -#include <lume/memblock.h> -#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[]; -extern "C" char _srodata[], _erodata[]; -extern "C" char _sdata[], _edata[]; -extern "C" char _sbss[], _ebss[]; -extern "C" char _kernel_end[]; - -/* Global kernel page table root PA — external linkage for task_exit */ -uint64 g_kernel_pgtbl = 0; - -namespace { - -/* VMM exports for PMM: page_map location after vmemmap mapping. */ -uint64 g_vmemmap_page_map_pa = 0; -uint64 g_vmemmap_page_map_size = 0; -uint64 g_vmemmap_num_pages = 0; - -/* Convert a linker symbol to PA (kernel is executing in higher-half VA). */ -inline uint64 sym_to_pa(const void *sym) { - return boot_va_to_pa(virt_addr(reinterpret_cast<uint64>(sym))).raw; -} - -/* Map a PA range into the kernel page table via the direct map. */ -void map_range(uint64 root, uint64 pa_start, uint64 pa_end, - uint64 perm) { - for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { - uint64 va = pa_to_va(phys_addr(pa)).raw; - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map failed"); - } - } -} - -/* Map a PA range into the kernel page table at the fixed linked VA. */ -void map_range_linked(uint64 root, uint64 pa_start, uint64 pa_end, - uint64 perm) { - for (uint64 pa = pa_start; pa < pa_end; pa += kPageSize) { - uint64 va = boot_pa_to_va(phys_addr(pa)).raw; - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map (linked) failed"); - } - } -} - -/* Map a PA range to an explicit VA base, using 2MB pages where possible. */ -void map_range_at_huge(uint64 root, uint64 va_base, uint64 pa_start, - uint64 pa_end, uint64 perm) { - constexpr uint64 k2MB = 2ULL * 1024 * 1024; - uint64 pa = pa_start; - uint64 va = va_base; - - /* Map 2MB-aligned chunks with mega pages */ - while (pa + k2MB <= pa_end) { - if ((va & (k2MB - 1)) == 0 && (pa & (k2MB - 1)) == 0) { - int ret = pmap::map_2mb(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: pmap::map_2mb failed"); - } - pa += k2MB; - va += k2MB; - } else { - break; - } - } - - /* Map remaining 4KB tail */ - while (pa < pa_end) { - int ret = pmap::map(root, va, pa, perm | pmap::PTE_G); - if (ret != 0) { - kernel_panic("vmm_init: vmemmap 4KB pmap::map failed"); - } - pa += kPageSize; - va += kPageSize; - } -} - -} // namespace - -uint64 vmm_get_page_map_pa() { return g_vmemmap_page_map_pa; } -uint64 vmm_get_page_map_size() { return g_vmemmap_page_map_size; } -uint64 vmm_get_num_pages() { return g_vmemmap_num_pages; } - -void vmm_init() { - /* 1. Allocate new root page table (from memblock) */ - uint64 root = pmap::create(); - if (!root) { - kernel_panic("vmm_init: failed to create root page table"); - } - - /* 2. Compute PA boundaries of each kernel section */ - uint64 text_start = page_align_down(sym_to_pa(_stext)); - uint64 text_end = page_align_up(sym_to_pa(_etext)); - uint64 ro_start = page_align_down(sym_to_pa(_srodata)); - uint64 ro_end = page_align_up(sym_to_pa(_erodata)); - uint64 data_start = page_align_down(sym_to_pa(_sdata)); - uint64 bss_end = page_align_up(sym_to_pa(_ebss)); - - /* Memory extent from memblock */ - uint64 mem_base = memblock_get_mem_base(); - uint64 mem_size = memblock_get_mem_size(); - uint64 mem_end = mem_base + mem_size; - - /* 3. Map kernel image at FIXED linked VA. - * - * Kernel text/data are linked at the bootstrap higher-half addresses - * (kDirectMapBaseDefault + PA). They must remain executable/readable at - * those addresses until full kernel-image relocation is implemented. - */ - map_range_linked(root, text_start, text_end, pmap::PTE_R | pmap::PTE_X); - - /* 4. Map .rodata (R) at fixed linked VA. - * We map [_srodata, _sdata) so that the .lume.reloc section (which - * sits in its own padded output section between _erodata and _sdata) - * is also covered. Relocator only reads it during early boot, but - * mapping it keeps post-vmm accesses to __lume_reloc_* symbols sane. */ - map_range_linked(root, ro_start, page_align_down(sym_to_pa(_sdata)), - pmap::PTE_R); - - /* 5. Map .data + .bss (RW) at fixed linked VA */ - map_range_linked(root, data_start, bss_end, pmap::PTE_R | pmap::PTE_W); - - /* 6. Map usable physical RAM into the KASLR-slid direct map. - * - * The direct map provides pa_to_va() / va_to_pa() for the physical - * address space. We map from kPhysStart (the kernel's linked load - * address), skipping [mem_base, kPhysStart) because that region is - * PMP-protected firmware (OpenSBI) — S-mode cannot access it. - * - * Linux does the same on RISC-V: the linear map covers only the - * usable RAM range, not firmware-reserved regions. - * - * Uses 2MB huge pages for efficiency (64 entries for 128MB vs 32K). - */ - uint64 dm_start = kPhysStart; - if (dm_start < mem_base) { - dm_start = mem_base; - } - uint64 dm_va_base = pa_to_va(phys_addr(dm_start)).raw; - map_range_at_huge(root, dm_va_base, dm_start, mem_end, - pmap::PTE_R | pmap::PTE_W); - - /* 7. Allocate page_map from memblock (top of RAM) and map at VMEMMAP. - * - * Since memblock is active (PMM not yet initialized), pmap::map - * allocates page table pages from memblock too. page_map starts - * life at the vmemmap VA — no relocation ever needed. - */ - uint64 num_pages = mem_size / kPageSize; - uint64 page_map_bytes = num_pages * sizeof(Page); - uint64 page_map_span = page_align_up(page_map_bytes); - uint64 page_map_pa = memblock_alloc_top(page_map_span, kPageSize); - - if (!page_map_pa) { - kernel_panic("vmm_init: memblock failed to allocate page_map"); - } - - kprintf("[vmm] vmemmap: %llu pages, %llu KB at PA 0x%llx\n", num_pages, - page_map_span / 1024, page_map_pa); - - /* Map page_map at vmemmap_base from KaslrLayout (randomised by KASLR), using 2MB pages where possible */ - map_range_at_huge(root, arch::kaslr_layout_get().vmemmap_base, page_map_pa, - page_map_pa + page_map_span, pmap::PTE_R | pmap::PTE_W); - - /* Export for pmm_init() */ - g_vmemmap_page_map_pa = page_map_pa; - g_vmemmap_page_map_size = page_map_bytes; - g_vmemmap_num_pages = num_pages; - - /* 8. Pre-map console MMIO before page table switch */ - g_kernel_pgtbl = root; - console_init(); - - /* 9. Activate the new page table */ - pmap::activate(root); - - /* Console MMIO access must now use runtime (KASLR) direct-map base. */ - console_use_runtime_mapping(); - - kprintf("[vmm] vmm_init complete (vmemmap at 0x%llx)\n", arch::kaslr_layout_get().vmemmap_base); -} - -void vmm_init_ap() { - if (!g_kernel_pgtbl) { - kernel_panic("vmm_init_ap: kernel page table not ready"); - } - pmap::activate(g_kernel_pgtbl); -} - -void vmm_map_kernel_mmio(uint64 pa, uint64 size) { - if (!g_kernel_pgtbl) { - kernel_panic("vmm_map_kernel_mmio: kernel page table not ready"); - } - - uint64 aligned_pa = page_align_down(pa); - uint64 aligned_size = page_align_up(pa + size) - aligned_pa; - - map_range(g_kernel_pgtbl, aligned_pa, aligned_pa + aligned_size, - pmap::PTE_R | pmap::PTE_W | pmap::PTE_G); - tlb_shootdown_all(); -} diff --git a/na/scripts/Kbuild.include b/na/scripts/Kbuild.include deleted file mode 100644 index 76466bd..0000000 --- a/na/scripts/Kbuild.include +++ /dev/null @@ -1,110 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# scripts/Kbuild.include — shared Make helpers for LumeOS. -# -# Mirrors a small, useful subset of Linux's scripts/Kbuild.include. -# Provides: -# -# - Q / KBUILD_VERBOSE : `make V=1` shows full commands, -# default V=0 prints a one-line summary -# - $(call cmd,<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 deleted file mode 100644 index 2bdc934..0000000 --- a/na/scripts/Kconfig.mk +++ /dev/null @@ -1,238 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin -# -# scripts/Kconfig.mk — Kernel Configuration Infrastructure -# -# This file is the heart of the LumeOS build-time configuration system. -# It mirrors the role of Linux's Kconfig + auto.conf pipeline: -# -# 1. Developer edits `.config` (or copies from arch/$(ARCH)/defconfig) -# 2. This file reads `.config`, applies defaults for anything missing, -# validates constraints, and exports two things: -# a) KCPPFLAGS — a set of -DCONFIG_xxx=value flags for the compiler -# b) $(BUILD)/generated/autoconf.h — the same values as C #defines -# -# Design Principles (matching Linux conventions): -# - Every CONFIG_ symbol has a default defined HERE, so a bare `make` -# with no .config still builds a sane kernel. -# - Boolean options use 'y' / 'n'. Numeric options are plain integers. -# - The generated autoconf.h uses the exact same names so that -# `#ifdef CONFIG_DEBUG` works identically in Makefile and C. -# ====================================================================== - -# ------------------------------------------------------------------ -# §1 Read user configuration (if it exists) -# ------------------------------------------------------------------ --include .config - -# ------------------------------------------------------------------ -# §2 Provide defaults for every CONFIG_ symbol -# -# ?= means "set only if not already set (by .config or CLI)". -# This is the single source of truth for default values. -# ------------------------------------------------------------------ - -# --- Platform ------------------------------------------------------- -CONFIG_ARCH ?= riscv - -# --- CPU / SMP ------------------------------------------------------ -CONFIG_NR_CPUS ?= 8 - -# --- Scheduler ------------------------------------------------------ -CONFIG_HZ ?= 100 -CONFIG_NR_PRIORITIES ?= 32 - -# --- Memory Management ---------------------------------------------- -CONFIG_PAGE_SIZE ?= 4096 -CONFIG_MAX_ORDER ?= 11 -CONFIG_KERNEL_STACK_SIZE ?= 8192 - -# --- Security / Hardening ------------------------------------------- -CONFIG_KASLR ?= y -CONFIG_KASLR_POLICY_ID ?= 0 -CONFIG_KASLR_LOG_LEVEL ?= 1 -CONFIG_STACK_CANARY ?= y -CONFIG_KERNEL_COMPRESSED ?= y - -# --- Debug ----------------------------------------------------------- -CONFIG_DEBUG ?= y -CONFIG_DEBUG_LOCKS ?= y -CONFIG_SELFTEST ?= y -CONFIG_KLOG ?= y - -# --- Initramfs ------------------------------------------------------ -# Source for the initramfs image. Accepted forms (auto-detected by -# scripts/gen_initramfs.sh): -# - a listing file (gen_init_cpio format) → built into a cpio -# - a directory → packed recursively -# - a *.cpio file → used as-is -# Override on the command line or pin in .config: -# make all CONFIG_INITRAMFS_SOURCE=/path/to/rootfs -CONFIG_INITRAMFS_SOURCE ?= usr/default_cpio_list -CONFIG_INITRAMFS_ROOT_UID ?= 0 -CONFIG_INITRAMFS_ROOT_GID ?= 0 - -# ------------------------------------------------------------------ -# §3 Derived constants (computed from CONFIG_ symbols) -# -# These follow Linux's pattern of expressing dependent values -# so that C code never has to do its own derivation at runtime. -# ------------------------------------------------------------------ - -# Default priority = middle of the range -CONFIG_DEFAULT_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) / 2 ))) - -# Idle priority = lowest = NR_PRIORITIES - 1 -CONFIG_IDLE_PRIORITY = $(shell echo $$(( $(CONFIG_NR_PRIORITIES) - 1 ))) - -# Default time slice in ticks: 100ms worth of ticks -# (same as Linux CFS's sysctl_sched_min_granularity default) -CONFIG_DEFAULT_TIMESLICE = $(shell echo $$(( $(CONFIG_HZ) / 10 ))) - -# ------------------------------------------------------------------ -# §4 Export to C/C++ via -D flags -# -# KCPPFLAGS is appended to CFLAGS/CXXFLAGS in the main Makefile. -# Every CONFIG_xxx=<value> becomes -DCONFIG_xxx=<value>. -# Boolean 'y' options become -DCONFIG_xxx=1. -# ------------------------------------------------------------------ -KCPPFLAGS := - -# Numeric options — pass through as-is -KCPPFLAGS += -DCONFIG_NR_CPUS=$(CONFIG_NR_CPUS) -KCPPFLAGS += -DCONFIG_HZ=$(CONFIG_HZ) -KCPPFLAGS += -DCONFIG_NR_PRIORITIES=$(CONFIG_NR_PRIORITIES) -KCPPFLAGS += -DCONFIG_DEFAULT_PRIORITY=$(CONFIG_DEFAULT_PRIORITY) -KCPPFLAGS += -DCONFIG_IDLE_PRIORITY=$(CONFIG_IDLE_PRIORITY) -KCPPFLAGS += -DCONFIG_DEFAULT_TIMESLICE=$(CONFIG_DEFAULT_TIMESLICE) -KCPPFLAGS += -DCONFIG_PAGE_SIZE=$(CONFIG_PAGE_SIZE) -KCPPFLAGS += -DCONFIG_MAX_ORDER=$(CONFIG_MAX_ORDER) -KCPPFLAGS += -DCONFIG_KERNEL_STACK_SIZE=$(CONFIG_KERNEL_STACK_SIZE) - -# Boolean options — translate 'y' → 1 -ifeq ($(CONFIG_KASLR),y) -KCPPFLAGS += -DCONFIG_KASLR=1 -endif -KCPPFLAGS += -DCONFIG_KASLR_POLICY_ID=$(CONFIG_KASLR_POLICY_ID) -KCPPFLAGS += -DCONFIG_KASLR_LOG_LEVEL=$(CONFIG_KASLR_LOG_LEVEL) -ifeq ($(CONFIG_STACK_CANARY),y) -KCPPFLAGS += -DCONFIG_STACK_CANARY=1 -endif -ifeq ($(CONFIG_DEBUG),y) -KCPPFLAGS += -DCONFIG_DEBUG=1 -endif -ifeq ($(CONFIG_DEBUG_LOCKS),y) -KCPPFLAGS += -DCONFIG_DEBUG_LOCKS=1 -endif -ifeq ($(CONFIG_SELFTEST),y) -KCPPFLAGS += -DCONFIG_SELFTEST=1 -endif -ifeq ($(CONFIG_KLOG),y) -KCPPFLAGS += -DCONFIG_KLOG=1 -endif -ifeq ($(CONFIG_KERNEL_COMPRESSED),y) -KCPPFLAGS += -DCONFIG_KERNEL_COMPRESSED=1 -endif - -# ------------------------------------------------------------------ -# §5 Generate autoconf.h (analogous to Linux's include/generated/autoconf.h) -# -# This header is auto-generated at build time so that C code can -# #include <generated/autoconf.h> and see CONFIG_xxx macros without -# relying on -D flags alone. This is important for IDE integration. -# ------------------------------------------------------------------ -AUTOCONF_H := $(BUILD)/generated/autoconf.h - -define generate-autoconf - @mkdir -p $(dir $(AUTOCONF_H)) - @echo " GEN $(AUTOCONF_H)" - @echo "/* AUTO-GENERATED — DO NOT EDIT */" > $(AUTOCONF_H) - @echo "/* Generated by scripts/Kconfig.mk */" >> $(AUTOCONF_H) - @echo "#pragma once" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Platform */" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_CPUS $(CONFIG_NR_CPUS)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Scheduler */" >> $(AUTOCONF_H) - @echo "#define CONFIG_HZ $(CONFIG_HZ)" >> $(AUTOCONF_H) - @echo "#define CONFIG_NR_PRIORITIES $(CONFIG_NR_PRIORITIES)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_PRIORITY $(CONFIG_DEFAULT_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_IDLE_PRIORITY $(CONFIG_IDLE_PRIORITY)" >> $(AUTOCONF_H) - @echo "#define CONFIG_DEFAULT_TIMESLICE $(CONFIG_DEFAULT_TIMESLICE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Memory Management */" >> $(AUTOCONF_H) - @echo "#define CONFIG_PAGE_SIZE $(CONFIG_PAGE_SIZE)" >> $(AUTOCONF_H) - @echo "#define CONFIG_MAX_ORDER $(CONFIG_MAX_ORDER)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KERNEL_STACK_SIZE $(CONFIG_KERNEL_STACK_SIZE)" >> $(AUTOCONF_H) - @echo "" >> $(AUTOCONF_H) - @echo "/* Security */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_KASLR)), @echo "#define CONFIG_KASLR 1" >> $(AUTOCONF_H)) - @echo "#define CONFIG_KASLR_POLICY_ID $(CONFIG_KASLR_POLICY_ID)" >> $(AUTOCONF_H) - @echo "#define CONFIG_KASLR_LOG_LEVEL $(CONFIG_KASLR_LOG_LEVEL)" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_STACK_CANARY)), @echo "#define CONFIG_STACK_CANARY 1" >> $(AUTOCONF_H)) - @echo "" >> $(AUTOCONF_H) - @echo "/* Debug */" >> $(AUTOCONF_H) - $(if $(filter y,$(CONFIG_DEBUG)), @echo "#define CONFIG_DEBUG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_DEBUG_LOCKS)), @echo "#define CONFIG_DEBUG_LOCKS 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_SELFTEST)), @echo "#define CONFIG_SELFTEST 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KLOG)), @echo "#define CONFIG_KLOG 1" >> $(AUTOCONF_H)) - $(if $(filter y,$(CONFIG_KERNEL_COMPRESSED)), @echo "#define CONFIG_KERNEL_COMPRESSED 1" >> $(AUTOCONF_H)) -endef - -# ------------------------------------------------------------------ -# §6 Utility targets -# ------------------------------------------------------------------ - -# 'make defconfig' — reset .config to the arch default -.PHONY: defconfig savedefconfig showconfig - -defconfig: - @echo " DEFCFG arch/$(CONFIG_ARCH)/defconfig → .config" - @cp arch/$(CONFIG_ARCH)/defconfig .config - -# 'make savedefconfig' — save current effective config -savedefconfig: - @echo " SAVE .config" - @echo "# LumeOS Kernel Configuration (saved $$(date +%Y-%m-%d))" > .config - @echo "CONFIG_ARCH = $(CONFIG_ARCH)" >> .config - @echo "CONFIG_NR_CPUS = $(CONFIG_NR_CPUS)" >> .config - @echo "CONFIG_HZ = $(CONFIG_HZ)" >> .config - @echo "CONFIG_NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" >> .config - @echo "CONFIG_PAGE_SIZE = $(CONFIG_PAGE_SIZE)" >> .config - @echo "CONFIG_MAX_ORDER = $(CONFIG_MAX_ORDER)" >> .config - @echo "CONFIG_KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" >> .config - @echo "CONFIG_KASLR = $(CONFIG_KASLR)" >> .config - @echo "CONFIG_KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" >> .config - @echo "CONFIG_KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" >> .config - @echo "CONFIG_STACK_CANARY = $(CONFIG_STACK_CANARY)" >> .config - @echo "CONFIG_DEBUG = $(CONFIG_DEBUG)" >> .config - @echo "CONFIG_DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" >> .config - @echo "CONFIG_SELFTEST = $(CONFIG_SELFTEST)" >> .config - @echo "CONFIG_KLOG = $(CONFIG_KLOG)" >> .config - @echo "CONFIG_KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" >> .config - -# 'make showconfig' — print effective configuration -showconfig: - @echo "========================================" - @echo " LumeOS Effective Configuration" - @echo "========================================" - @echo " ARCH = $(CONFIG_ARCH)" - @echo " NR_CPUS = $(CONFIG_NR_CPUS)" - @echo " HZ = $(CONFIG_HZ)" - @echo " NR_PRIORITIES = $(CONFIG_NR_PRIORITIES)" - @echo " DEFAULT_PRIORITY = $(CONFIG_DEFAULT_PRIORITY)" - @echo " IDLE_PRIORITY = $(CONFIG_IDLE_PRIORITY)" - @echo " DEFAULT_TIMESLICE = $(CONFIG_DEFAULT_TIMESLICE)" - @echo " PAGE_SIZE = $(CONFIG_PAGE_SIZE)" - @echo " MAX_ORDER = $(CONFIG_MAX_ORDER)" - @echo " KERNEL_STACK_SIZE = $(CONFIG_KERNEL_STACK_SIZE)" - @echo " KASLR = $(CONFIG_KASLR)" - @echo " KASLR_POLICY_ID = $(CONFIG_KASLR_POLICY_ID)" - @echo " KASLR_LOG_LEVEL = $(CONFIG_KASLR_LOG_LEVEL)" - @echo " STACK_CANARY = $(CONFIG_STACK_CANARY)" - @echo " DEBUG = $(CONFIG_DEBUG)" - @echo " DEBUG_LOCKS = $(CONFIG_DEBUG_LOCKS)" - @echo " SELFTEST = $(CONFIG_SELFTEST)" - @echo " KLOG = $(CONFIG_KLOG)" - @echo " KERNEL_COMPRESSED = $(CONFIG_KERNEL_COMPRESSED)" - @echo "========================================" diff --git a/na/scripts/dtc/libfdt/fdt.c b/na/scripts/dtc/libfdt/fdt.c deleted file mode 100644 index 56d4dcb..0000000 --- a/na/scripts/dtc/libfdt/fdt.c +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index a07abfc..0000000 --- a/na/scripts/dtc/libfdt/fdt.h +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef FDT_H -#define FDT_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * Copyright 2012 Kim Phillips, Freescale Semiconductor. - */ - -#ifndef __ASSEMBLER__ - -struct fdt_header { - fdt32_t magic; /* magic word FDT_MAGIC */ - fdt32_t totalsize; /* total size of DT block */ - fdt32_t off_dt_struct; /* offset to structure */ - fdt32_t off_dt_strings; /* offset to strings */ - fdt32_t off_mem_rsvmap; /* offset to memory reserve map */ - fdt32_t version; /* format version */ - fdt32_t last_comp_version; /* last compatible version */ - - /* version 2 fields below */ - fdt32_t boot_cpuid_phys; /* Which physical CPU id we're - booting on */ - /* version 3 fields below */ - fdt32_t size_dt_strings; /* size of the strings block */ - - /* version 17 fields below */ - fdt32_t size_dt_struct; /* size of the structure block */ -}; - -struct fdt_reserve_entry { - fdt64_t address; - fdt64_t size; -}; - -struct fdt_node_header { - fdt32_t tag; - char name[]; -}; - -struct fdt_property { - fdt32_t tag; - fdt32_t len; - fdt32_t nameoff; - char data[]; -}; - -#endif /* !__ASSEMBLER__ */ - -#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */ -#define FDT_TAGSIZE sizeof(fdt32_t) - -#define FDT_BEGIN_NODE 0x1 /* Start node: full name */ -#define FDT_END_NODE 0x2 /* End node */ -#define FDT_PROP 0x3 /* Property: name off, - size, content */ -#define FDT_NOP 0x4 /* nop */ -#define FDT_END 0x9 - -#define FDT_V1_SIZE (7*sizeof(fdt32_t)) -#define FDT_V2_SIZE (FDT_V1_SIZE + sizeof(fdt32_t)) -#define FDT_V3_SIZE (FDT_V2_SIZE + sizeof(fdt32_t)) -#define FDT_V16_SIZE FDT_V3_SIZE -#define FDT_V17_SIZE (FDT_V16_SIZE + sizeof(fdt32_t)) - -#endif /* FDT_H */ diff --git a/na/scripts/dtc/libfdt/fdt_addresses.c b/na/scripts/dtc/libfdt/fdt_addresses.c deleted file mode 100644 index c40ba09..0000000 --- a/na/scripts/dtc/libfdt/fdt_addresses.c +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2014 David Gibson <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 deleted file mode 100644 index 49d54d4..0000000 --- a/na/scripts/dtc/libfdt/fdt_empty_tree.c +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2012 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index 51a3859..0000000 --- a/na/scripts/dtc/libfdt/fdt_overlay.c +++ /dev/null @@ -1,1101 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2016 Free Electrons - * Copyright (C) 2016 NextThing Co. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index 63494fb..0000000 --- a/na/scripts/dtc/libfdt/fdt_ro.c +++ /dev/null @@ -1,888 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index 90ea14e..0000000 --- a/na/scripts/dtc/libfdt/fdt_rw.c +++ /dev/null @@ -1,512 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index d852b77..0000000 --- a/na/scripts/dtc/libfdt/fdt_strerror.c +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index 4c569ee..0000000 --- a/na/scripts/dtc/libfdt/fdt_sw.c +++ /dev/null @@ -1,384 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index c2d7566..0000000 --- a/na/scripts/dtc/libfdt/fdt_wip.c +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include "libfdt_env.h" - -#include <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 deleted file mode 100644 index 81aaf7c..0000000 --- a/na/scripts/dtc/libfdt/libfdt.h +++ /dev/null @@ -1,2540 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_H -#define LIBFDT_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ - -#include "libfdt_env.h" -#include "fdt.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define FDT_FIRST_SUPPORTED_VERSION 0x02 -#define FDT_LAST_COMPATIBLE_VERSION 0x10 -#define FDT_LAST_SUPPORTED_VERSION 0x11 - -/* Error codes: informative error codes */ -#define FDT_ERR_NOTFOUND 1 - /* FDT_ERR_NOTFOUND: The requested node or property does not exist */ -#define FDT_ERR_EXISTS 2 - /* FDT_ERR_EXISTS: Attempted to create a node or property which - * already exists */ -#define FDT_ERR_NOSPACE 3 - /* FDT_ERR_NOSPACE: Operation needed to expand the device - * tree, but its buffer did not have sufficient space to - * contain the expanded tree. Use fdt_open_into() to move the - * device tree to a buffer with more space. */ - -/* Error codes: codes for bad parameters */ -#define FDT_ERR_BADOFFSET 4 - /* FDT_ERR_BADOFFSET: Function was passed a structure block - * offset which is out-of-bounds, or which points to an - * unsuitable part of the structure for the operation. */ -#define FDT_ERR_BADPATH 5 - /* FDT_ERR_BADPATH: Function was passed a badly formatted path - * (e.g. missing a leading / for a function which requires an - * absolute path) */ -#define FDT_ERR_BADPHANDLE 6 - /* FDT_ERR_BADPHANDLE: Function was passed an invalid phandle. - * This can be caused either by an invalid phandle property - * length, or the phandle value was either 0 or -1, which are - * not permitted. */ -#define FDT_ERR_BADSTATE 7 - /* FDT_ERR_BADSTATE: Function was passed an incomplete device - * tree created by the sequential-write functions, which is - * not sufficiently complete for the requested operation. */ - -/* Error codes: codes for bad device tree blobs */ -#define FDT_ERR_TRUNCATED 8 - /* FDT_ERR_TRUNCATED: FDT or a sub-block is improperly - * terminated (overflows, goes outside allowed bounds, or - * isn't properly terminated). */ -#define FDT_ERR_BADMAGIC 9 - /* FDT_ERR_BADMAGIC: Given "device tree" appears not to be a - * device tree at all - it is missing the flattened device - * tree magic number. */ -#define FDT_ERR_BADVERSION 10 - /* FDT_ERR_BADVERSION: Given device tree has a version which - * can't be handled by the requested operation. For - * read-write functions, this may mean that fdt_open_into() is - * required to convert the tree to the expected version. */ -#define FDT_ERR_BADSTRUCTURE 11 - /* FDT_ERR_BADSTRUCTURE: Given device tree has a corrupt - * structure block or other serious error (e.g. misnested - * nodes, or subnodes preceding properties). */ -#define FDT_ERR_BADLAYOUT 12 - /* FDT_ERR_BADLAYOUT: For read-write functions, the given - * device tree has it's sub-blocks in an order that the - * function can't handle (memory reserve map, then structure, - * then strings). Use fdt_open_into() to reorganize the tree - * into a form suitable for the read-write operations. */ - -/* "Can't happen" error indicating a bug in libfdt */ -#define FDT_ERR_INTERNAL 13 - /* FDT_ERR_INTERNAL: libfdt has failed an internal assertion. - * Should never be returned, if it is, it indicates a bug in - * libfdt itself. */ - -/* Errors in device tree content */ -#define FDT_ERR_BADNCELLS 14 - /* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells - * or similar property with a bad format or value */ - -#define FDT_ERR_BADVALUE 15 - /* FDT_ERR_BADVALUE: Device tree has a property with an unexpected - * value. For example: a property expected to contain a string list - * is not NUL-terminated within the length of its value. */ - -#define FDT_ERR_BADOVERLAY 16 - /* FDT_ERR_BADOVERLAY: The device tree overlay, while - * correctly structured, cannot be applied due to some - * unexpected or missing value, property or node. */ - -#define FDT_ERR_NOPHANDLES 17 - /* FDT_ERR_NOPHANDLES: The device tree doesn't have any - * phandle available anymore without causing an overflow */ - -#define FDT_ERR_BADFLAGS 18 - /* FDT_ERR_BADFLAGS: The function was passed a flags field that - * contains invalid flags or an invalid combination of flags. */ - -#define FDT_ERR_ALIGNMENT 19 - /* FDT_ERR_ALIGNMENT: The device tree base address is not 8-byte - * aligned. */ - -#define FDT_ERR_MAX 19 - -/* constants */ -#define FDT_MAX_PHANDLE 0xfffffffe - /* Valid values for phandles range from 1 to 2^32-2. */ - -/**********************************************************************/ -/* Low-level functions (you probably don't need these) */ -/**********************************************************************/ - -/** - * fdt_offset_ptr - safely get a byte range within the device tree blob - * @fdt: Pointer to the device tree blob - * @offset: Offset within the blob to the desired byte range - * @checklen: Required length of the byte range - * - * fdt_offset_ptr() returns a pointer to the byte range of length @checklen at - * the given @offset within the device tree blob, after verifying that the byte - * range fits entirely within the blob and does not overflow. - * - * returns: - * pointer to the byte range, on success - * NULL, if the requested range does not fit within the blob - */ -#ifndef SWIG /* This function is not useful in Python */ -const void *fdt_offset_ptr(const void *fdt, int offset, unsigned int checklen); -#endif -static inline void *fdt_offset_ptr_w(void *fdt, int offset, int checklen) -{ - return (void *)(uintptr_t)fdt_offset_ptr(fdt, offset, checklen); -} - -/** - * fdt_next_tag - get next tag in the device tree - * @fdt: Pointer to the device tree blob - * @offset: Offset within the blob to start searching - * @nextoffset: Pointer to variable to store the offset of the next tag - * - * fdt_next_tag() returns the tag type of the next tag in the device tree - * blob starting from the given @offset. If @nextoffset is non-NULL, it will - * be set to the offset immediately following the tag. - * - * returns: - * the tag type (FDT_BEGIN_NODE, FDT_END_NODE, FDT_PROP, FDT_NOP, FDT_END), - * FDT_END, if offset is out of bounds - */ -uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset); - -/* - * External helpers to access words from a device tree blob. They're built - * to work even with unaligned pointers on platforms (such as ARMv5) that don't - * like unaligned loads and stores. - */ -static inline uint16_t fdt16_ld(const fdt16_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint16_t)bp[0] << 8) | bp[1]; -} - -static inline uint32_t fdt32_ld(const fdt32_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint32_t)bp[0] << 24) - | ((uint32_t)bp[1] << 16) - | ((uint32_t)bp[2] << 8) - | bp[3]; -} - -static inline void fdt32_st(void *property, uint32_t value) -{ - uint8_t *bp = (uint8_t *)property; - - bp[0] = value >> 24; - bp[1] = (value >> 16) & 0xff; - bp[2] = (value >> 8) & 0xff; - bp[3] = value & 0xff; -} - -static inline uint64_t fdt64_ld(const fdt64_t *p) -{ - const uint8_t *bp = (const uint8_t *)p; - - return ((uint64_t)bp[0] << 56) - | ((uint64_t)bp[1] << 48) - | ((uint64_t)bp[2] << 40) - | ((uint64_t)bp[3] << 32) - | ((uint64_t)bp[4] << 24) - | ((uint64_t)bp[5] << 16) - | ((uint64_t)bp[6] << 8) - | bp[7]; -} - -static inline void fdt64_st(void *property, uint64_t value) -{ - uint8_t *bp = (uint8_t *)property; - - bp[0] = value >> 56; - bp[1] = (value >> 48) & 0xff; - bp[2] = (value >> 40) & 0xff; - bp[3] = (value >> 32) & 0xff; - bp[4] = (value >> 24) & 0xff; - bp[5] = (value >> 16) & 0xff; - bp[6] = (value >> 8) & 0xff; - bp[7] = value & 0xff; -} - -/**********************************************************************/ -/* Traversal functions */ -/**********************************************************************/ - -int fdt_next_node(const void *fdt, int offset, int *depth); - -/** - * fdt_first_subnode() - get offset of first direct subnode - * @fdt: FDT blob - * @offset: Offset of node to check - * - * Return: offset of first subnode, or -FDT_ERR_NOTFOUND if there is none - */ -int fdt_first_subnode(const void *fdt, int offset); - -/** - * fdt_next_subnode() - get offset of next direct subnode - * @fdt: FDT blob - * @offset: Offset of previous subnode - * - * After first calling fdt_first_subnode(), call this function repeatedly to - * get direct subnodes of a parent node. - * - * Return: offset of next subnode, or -FDT_ERR_NOTFOUND if there are no more - * subnodes - */ -int fdt_next_subnode(const void *fdt, int offset); - -/** - * fdt_for_each_subnode - iterate over all subnodes of a parent - * - * @node: child node (int, lvalue) - * @fdt: FDT blob (const void *) - * @parent: parent node (int) - * - * This is actually a wrapper around a for loop and would be used like so: - * - * fdt_for_each_subnode(node, fdt, parent) { - * Use node - * ... - * } - * - * if ((node < 0) && (node != -FDT_ERR_NOTFOUND)) { - * Error handling - * } - * - * Note that this is implemented as a macro and @node is used as - * iterator in the loop. The parent variable be constant or even a - * literal. - */ -#define fdt_for_each_subnode(node, fdt, parent) \ - for (node = fdt_first_subnode(fdt, parent); \ - node >= 0; \ - node = fdt_next_subnode(fdt, node)) - -/**********************************************************************/ -/* General functions */ -/**********************************************************************/ -#define fdt_get_header(fdt, field) \ - (fdt32_ld(&((const struct fdt_header *)(fdt))->field)) -#define fdt_magic(fdt) (fdt_get_header(fdt, magic)) -#define fdt_totalsize(fdt) (fdt_get_header(fdt, totalsize)) -#define fdt_off_dt_struct(fdt) (fdt_get_header(fdt, off_dt_struct)) -#define fdt_off_dt_strings(fdt) (fdt_get_header(fdt, off_dt_strings)) -#define fdt_off_mem_rsvmap(fdt) (fdt_get_header(fdt, off_mem_rsvmap)) -#define fdt_version(fdt) (fdt_get_header(fdt, version)) -#define fdt_last_comp_version(fdt) (fdt_get_header(fdt, last_comp_version)) -#define fdt_boot_cpuid_phys(fdt) (fdt_get_header(fdt, boot_cpuid_phys)) -#define fdt_size_dt_strings(fdt) (fdt_get_header(fdt, size_dt_strings)) -#define fdt_size_dt_struct(fdt) (fdt_get_header(fdt, size_dt_struct)) - -#define fdt_set_hdr_(name) \ - static inline void fdt_set_##name(void *fdt, uint32_t val) \ - { \ - struct fdt_header *fdth = (struct fdt_header *)fdt; \ - fdth->name = cpu_to_fdt32(val); \ - } -fdt_set_hdr_(magic) -fdt_set_hdr_(totalsize) -fdt_set_hdr_(off_dt_struct) -fdt_set_hdr_(off_dt_strings) -fdt_set_hdr_(off_mem_rsvmap) -fdt_set_hdr_(version) -fdt_set_hdr_(last_comp_version) -fdt_set_hdr_(boot_cpuid_phys) -fdt_set_hdr_(size_dt_strings) -fdt_set_hdr_(size_dt_struct) -#undef fdt_set_hdr_ - -/** - * fdt_header_size - return the size of the tree's header - * @fdt: pointer to a flattened device tree - * - * Return: size of DTB header in bytes - */ -size_t fdt_header_size(const void *fdt); - -/** - * fdt_header_size_ - internal function to get header size from a version number - * @version: device tree version number - * - * Return: size of DTB header in bytes - */ -size_t fdt_header_size_(uint32_t version); - -/** - * fdt_check_header - sanity check a device tree header - * @fdt: pointer to data which might be a flattened device tree - * - * fdt_check_header() checks that the given buffer contains what - * appears to be a flattened device tree, and that the header contains - * valid information (to the extent that can be determined from the - * header alone). - * - * returns: - * 0, if the buffer appears to contain a valid device tree - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_TRUNCATED, standard meanings, as above - */ -int fdt_check_header(const void *fdt); - -/** - * fdt_move - move a device tree around in memory - * @fdt: pointer to the device tree to move - * @buf: pointer to memory where the device is to be moved - * @bufsize: size of the memory space at buf - * - * fdt_move() relocates, if possible, the device tree blob located at - * fdt to the buffer at buf of size bufsize. The buffer may overlap - * with the existing device tree blob at fdt. Therefore, - * fdt_move(fdt, fdt, fdt_totalsize(fdt)) - * should always succeed. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient to contain the device tree - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_move(const void *fdt, void *buf, int bufsize); - -/**********************************************************************/ -/* Read-only functions */ -/**********************************************************************/ - -/** - * fdt_check_full - check device tree validity - * @fdt: pointer to the device tree blob - * @bufsize: size of the buffer containing the device tree - * - * fdt_check_full() checks that the given buffer contains a valid - * flattened device tree and that the tree structure is internally - * consistent. This is a more thorough check than fdt_check_header(). - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_check_full(const void *fdt, size_t bufsize); - -/** - * fdt_get_string - retrieve a string from the strings block of a device tree - * @fdt: pointer to the device tree blob - * @stroffset: offset of the string within the strings block (native endian) - * @lenp: optional pointer to return the string's length - * - * fdt_get_string() retrieves a pointer to a single string from the - * strings block of the device tree blob at fdt, and optionally also - * returns the string's length in *lenp. - * - * returns: - * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string - */ -const char *fdt_get_string(const void *fdt, int stroffset, int *lenp); - -/** - * fdt_string - retrieve a string from the strings block of a device tree - * @fdt: pointer to the device tree blob - * @stroffset: offset of the string within the strings block (native endian) - * - * fdt_string() retrieves a pointer to a single string from the - * strings block of the device tree blob at fdt. - * - * returns: - * a pointer to the string, on success - * NULL, if stroffset is out of bounds, or doesn't point to a valid string - */ -const char *fdt_string(const void *fdt, int stroffset); - -/** - * fdt_find_max_phandle - find and return the highest phandle in a tree - * @fdt: pointer to the device tree blob - * @phandle: return location for the highest phandle value found in the tree - * - * fdt_find_max_phandle() finds the highest phandle value in the given device - * tree. The value returned in @phandle is only valid if the function returns - * success. - * - * returns: - * 0 on success or a negative error code on failure - */ -int fdt_find_max_phandle(const void *fdt, uint32_t *phandle); - -/** - * fdt_get_max_phandle - retrieves the highest phandle in a tree - * @fdt: pointer to the device tree blob - * - * fdt_get_max_phandle retrieves the highest phandle in the given - * device tree. This will ignore badly formatted phandles, or phandles - * with a value of 0 or -1. - * - * This function is deprecated in favour of fdt_find_max_phandle(). - * - * returns: - * the highest phandle on success - * 0, if no phandle was found in the device tree - * -1, if an error occurred - */ -static inline uint32_t fdt_get_max_phandle(const void *fdt) -{ - uint32_t phandle; - int err; - - err = fdt_find_max_phandle(fdt, &phandle); - if (err < 0) - return (uint32_t)-1; - - return phandle; -} - -/** - * fdt_generate_phandle - return a new, unused phandle for a device tree blob - * @fdt: pointer to the device tree blob - * @phandle: return location for the new phandle - * - * Walks the device tree blob and looks for the highest phandle value. On - * success, the new, unused phandle value (one higher than the previously - * highest phandle value in the device tree blob) will be returned in the - * @phandle parameter. - * - * Return: 0 on success or a negative error-code on failure - */ -int fdt_generate_phandle(const void *fdt, uint32_t *phandle); - -/** - * fdt_num_mem_rsv - retrieve the number of memory reserve map entries - * @fdt: pointer to the device tree blob - * - * Returns the number of entries in the device tree blob's memory - * reservation map. This does not include the terminating 0,0 entry - * or any other (0,0) entries reserved for expansion. - * - * returns: - * the number of entries - */ -int fdt_num_mem_rsv(const void *fdt); - -/** - * fdt_get_mem_rsv - retrieve one memory reserve map entry - * @fdt: pointer to the device tree blob - * @n: index of reserve map entry - * @address: pointer to 64-bit variable to hold the start address - * @size: pointer to 64-bit variable to hold the size of the entry - * - * On success, @address and @size will contain the address and size of - * the n-th reserve map entry from the device tree blob, in - * native-endian format. - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_get_mem_rsv(const void *fdt, int n, uint64_t *address, uint64_t *size); - -/** - * fdt_subnode_offset_namelen - find a subnode based on substring - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * @namelen: number of characters of name to consider - * - * Identical to fdt_subnode_offset(), but only examine the first - * namelen characters of name for matching the subnode name. This is - * useful for finding subnodes based on a portion of a larger string, - * such as a full path. - * - * Return: offset of the subnode or -FDT_ERR_NOTFOUND if name not found. - */ -#ifndef SWIG /* Not available in Python */ -int fdt_subnode_offset_namelen(const void *fdt, int parentoffset, - const char *name, int namelen); -#endif -/** - * fdt_subnode_offset - find a subnode of a given node - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * - * fdt_subnode_offset() finds a subnode of the node at structure block - * offset parentoffset with the given name. name may include a unit - * address, in which case fdt_subnode_offset() will find the subnode - * with that unit address, or the unit address may be omitted, in - * which case fdt_subnode_offset() will find an arbitrary subnode - * whose name excluding unit address matches the given name. - * - * returns: - * structure block offset of the requested subnode (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested subnode does not exist - * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_subnode_offset(const void *fdt, int parentoffset, const char *name); - -/** - * fdt_path_offset_namelen - find a tree node by its full path - * @fdt: pointer to the device tree blob - * @path: full path of the node to locate - * @namelen: number of characters of path to consider - * - * Identical to fdt_path_offset(), but only consider the first namelen - * characters of path as the path name. - * - * Return: offset of the node or negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_path_offset_namelen(const void *fdt, const char *path, int namelen); -#endif - -/** - * fdt_path_offset - find a tree node by its full path - * @fdt: pointer to the device tree blob - * @path: full path of the node to locate - * - * fdt_path_offset() finds a node of a given path in the device tree. - * Each path component may omit the unit address portion, but the - * results of this are undefined if any such path component is - * ambiguous (that is if there are multiple nodes at the relevant - * level matching the given component, differentiated only by unit - * address). - * - * If the path is not absolute (i.e. does not begin with '/'), the - * first component is treated as an alias. That is, the property by - * that name is looked up in the /aliases node, and the value of that - * property used in place of that first component. - * - * For example, for this small fragment - * - * / { - * aliases { - * i2c2 = &foo; // RHS compiles to "/soc@0/i2c@30a40000/eeprom@52" - * }; - * soc@0 { - * foo: i2c@30a40000 { - * bar: eeprom@52 { - * }; - * }; - * }; - * }; - * - * these would be equivalent: - * - * /soc@0/i2c@30a40000/eeprom@52 - * i2c2/eeprom@52 - * - * returns: - * structure block offset of the node with the requested path (>=0), on - * success - * -FDT_ERR_BADPATH, given path does not begin with '/' and the first - * component is not a valid alias - * -FDT_ERR_NOTFOUND, if the requested node does not exist - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_path_offset(const void *fdt, const char *path); - -/** - * fdt_get_name - retrieve the name of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of the starting node - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_name() retrieves the name (including unit address) of the - * device tree node at structure block offset nodeoffset. If lenp is - * non-NULL, the length of this name is also returned, in the integer - * pointed to by lenp. - * - * returns: - * pointer to the node's name, on success - * If lenp is non-NULL, *lenp contains the length of that name - * (>=0) - * NULL, on error - * if lenp is non-NULL *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -const char *fdt_get_name(const void *fdt, int nodeoffset, int *lenp); - -/** - * fdt_first_property_offset - find the offset of a node's first property - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of a node - * - * fdt_first_property_offset() finds the first property of the node at - * the given structure block offset. - * - * returns: - * structure block offset of the property (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested node has no properties - * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_first_property_offset(const void *fdt, int nodeoffset); - -/** - * fdt_next_property_offset - step through a node's properties - * @fdt: pointer to the device tree blob - * @offset: structure block offset of a property - * - * fdt_next_property_offset() finds the property immediately after the - * one at the given structure block offset. This will be a property - * of the same node as the given property. - * - * returns: - * structure block offset of the next property (>=0), on success - * -FDT_ERR_NOTFOUND, if the given property is the last in its node - * -FDT_ERR_BADOFFSET, if nodeoffset did not point to an FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_next_property_offset(const void *fdt, int offset); - -/** - * fdt_for_each_property_offset - iterate over all properties of a node - * - * @property: property offset (int, lvalue) - * @fdt: FDT blob (const void *) - * @node: node offset (int) - * - * This is actually a wrapper around a for loop and would be used like so: - * - * fdt_for_each_property_offset(property, fdt, node) { - * Use property - * ... - * } - * - * if ((property < 0) && (property != -FDT_ERR_NOTFOUND)) { - * Error handling - * } - * - * Note that this is implemented as a macro and property is used as - * iterator in the loop. The node variable can be constant or even a - * literal. - */ -#define fdt_for_each_property_offset(property, fdt, node) \ - for (property = fdt_first_property_offset(fdt, node); \ - property >= 0; \ - property = fdt_next_property_offset(fdt, property)) - -/** - * fdt_get_property_by_offset - retrieve the property at a given offset - * @fdt: pointer to the device tree blob - * @offset: offset of the property to retrieve - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_property_by_offset() retrieves a pointer to the - * fdt_property structure within the device tree blob at the given - * offset. If lenp is non-NULL, the length of the property value is - * also returned, in the integer pointed to by lenp. - * - * Note that this code only works on device tree versions >= 16. fdt_getprop() - * works on all versions. - * - * returns: - * pointer to the structure representing the property - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const struct fdt_property *fdt_get_property_by_offset(const void *fdt, - int offset, - int *lenp); -static inline struct fdt_property *fdt_get_property_by_offset_w(void *fdt, - int offset, - int *lenp) -{ - return (struct fdt_property *)(uintptr_t) - fdt_get_property_by_offset(fdt, offset, lenp); -} - -/** - * fdt_get_property_namelen - find a property based on substring - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @namelen: number of characters of name to consider - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * Identical to fdt_get_property(), but only examine the first namelen - * characters of name for matching the property name. - * - * Return: pointer to the structure representing the property, or NULL - * if not found - */ -#ifndef SWIG /* Not available in Python */ -const struct fdt_property *fdt_get_property_namelen(const void *fdt, - int nodeoffset, - const char *name, - int namelen, int *lenp); -static inline struct fdt_property * -fdt_get_property_namelen_w(void *fdt, int nodeoffset, const char *name, - int namelen, int *lenp) -{ - return (struct fdt_property *)(uintptr_t)fdt_get_property_namelen( - fdt, nodeoffset, name, namelen, lenp); -} -#endif - -/** - * fdt_get_property - find a given property in a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_get_property() retrieves a pointer to the fdt_property - * structure within the device tree blob corresponding to the property - * named 'name' of the node at offset nodeoffset. If lenp is - * non-NULL, the length of the property value is also returned, in the - * integer pointed to by lenp. - * - * returns: - * pointer to the structure representing the property - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_NOTFOUND, node does not have named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const struct fdt_property *fdt_get_property(const void *fdt, int nodeoffset, - const char *name, int *lenp); -static inline struct fdt_property *fdt_get_property_w(void *fdt, int nodeoffset, - const char *name, - int *lenp) -{ - return (struct fdt_property *)(uintptr_t) - fdt_get_property(fdt, nodeoffset, name, lenp); -} - -/** - * fdt_getprop_by_offset - retrieve the value of a property at a given offset - * @fdt: pointer to the device tree blob - * @offset: offset of the property to read - * @namep: pointer to a string variable (will be overwritten) or NULL - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_getprop_by_offset() retrieves a pointer to the value of the - * property at structure block offset 'offset' (this will be a pointer - * to within the device blob itself, not a copy of the value). If - * lenp is non-NULL, the length of the property value is also - * returned, in the integer pointed to by lenp. If namep is non-NULL, - * the property's name will also be returned in the char * pointed to - * by namep (this will be a pointer to within the device tree's string - * block, not a new copy of the name). - * - * returns: - * pointer to the property's value - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * if namep is non-NULL *namep contains a pointer to the property - * name. - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_PROP tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#ifndef SWIG /* This function is not useful in Python */ -const void *fdt_getprop_by_offset(const void *fdt, int offset, - const char **namep, int *lenp); -#endif - -/** - * fdt_getprop_namelen - get property value based on substring - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @namelen: number of characters of name to consider - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * Identical to fdt_getprop(), but only examine the first namelen - * characters of name for matching the property name. - * - * Return: pointer to the property's value or NULL on error - */ -#ifndef SWIG /* Not available in Python */ -const void *fdt_getprop_namelen(const void *fdt, int nodeoffset, - const char *name, int namelen, int *lenp); -static inline void *fdt_getprop_namelen_w(void *fdt, int nodeoffset, - const char *name, int namelen, - int *lenp) -{ - return (void *)(uintptr_t)fdt_getprop_namelen(fdt, nodeoffset, name, - namelen, lenp); -} -#endif - -/** - * fdt_getprop - retrieve the value of a given property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to find - * @name: name of the property to find - * @lenp: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_getprop() retrieves a pointer to the value of the property - * named @name of the node at offset @nodeoffset (this will be a - * pointer to within the device blob itself, not a copy of the value). - * If @lenp is non-NULL, the length of the property value is also - * returned, in the integer pointed to by @lenp. - * - * returns: - * pointer to the property's value - * if lenp is non-NULL, *lenp contains the length of the property - * value (>=0) - * NULL, on error - * if lenp is non-NULL, *lenp contains an error code (<0): - * -FDT_ERR_NOTFOUND, node does not have named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE - * tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -const void *fdt_getprop(const void *fdt, int nodeoffset, - const char *name, int *lenp); -static inline void *fdt_getprop_w(void *fdt, int nodeoffset, - const char *name, int *lenp) -{ - return (void *)(uintptr_t)fdt_getprop(fdt, nodeoffset, name, lenp); -} - -/** - * fdt_get_phandle - retrieve the phandle of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of the node - * - * fdt_get_phandle() retrieves the phandle of the device tree node at - * structure block offset nodeoffset. - * - * returns: - * the phandle of the node at nodeoffset, on success (!= 0, != -1) - * 0, if the node has no phandle, or another error occurs - */ -uint32_t fdt_get_phandle(const void *fdt, int nodeoffset); - -/** - * fdt_get_alias_namelen - get alias based on substring - * @fdt: pointer to the device tree blob - * @name: name of the alias to look up - * @namelen: number of characters of name to consider - * - * Identical to fdt_get_alias(), but only examine the first @namelen - * characters of @name for matching the alias name. - * - * Return: a pointer to the expansion of the alias named @name, if it exists, - * NULL otherwise - */ -#ifndef SWIG /* Not available in Python */ -const char *fdt_get_alias_namelen(const void *fdt, - const char *name, int namelen); -#endif - -/** - * fdt_get_alias - retrieve the path referenced by a given alias - * @fdt: pointer to the device tree blob - * @name: name of the alias to look up - * - * fdt_get_alias() retrieves the value of a given alias. That is, the - * value of the property named @name in the node /aliases. - * - * returns: - * a pointer to the expansion of the alias named 'name', if it exists - * NULL, if the given alias or the /aliases node does not exist - */ -const char *fdt_get_alias(const void *fdt, const char *name); - -/** - * fdt_get_symbol_namelen - get symbol based on substring - * @fdt: pointer to the device tree blob - * @name: name of the symbol to look up - * @namelen: number of characters of name to consider - * - * Identical to fdt_get_symbol(), but only examine the first @namelen - * characters of @name for matching the symbol name. - * - * Return: a pointer to the expansion of the symbol named @name, if it exists, - * NULL otherwise - */ -#ifndef SWIG /* Not available in Python */ -const char *fdt_get_symbol_namelen(const void *fdt, - const char *name, int namelen); -#endif - -/** - * fdt_get_symbol - retrieve the path referenced by a given symbol - * @fdt: pointer to the device tree blob - * @name: name of the symbol to look up - * - * fdt_get_symbol() retrieves the value of a given symbol. That is, - * the value of the property named @name in the node - * /__symbols__. Such a node exists only for a device tree blob that - * has been compiled with the -@ dtc option. Each property corresponds - * to a label appearing in the device tree source, with the name of - * the property being the label and the value being the full path of - * the node it is attached to. - * - * returns: - * a pointer to the expansion of the symbol named 'name', if it exists - * NULL, if the given symbol or the /__symbols__ node does not exist - */ -const char *fdt_get_symbol(const void *fdt, const char *name); - -/** - * fdt_get_path - determine the full path of a node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose path to find - * @buf: character buffer to contain the returned path (will be overwritten) - * @buflen: size of the character buffer at buf - * - * fdt_get_path() computes the full path of the node at offset - * nodeoffset, and records that path in the buffer at buf. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * 0, on success - * buf contains the absolute path of the node at - * nodeoffset, as a NUL-terminated string. - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_NOSPACE, the path of the given node is longer than (bufsize-1) - * characters and will not fit in the given buffer. - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_get_path(const void *fdt, int nodeoffset, char *buf, int buflen); - -/** - * fdt_supernode_atdepth_offset - find a specific ancestor of a node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * @supernodedepth: depth of the ancestor to find - * @nodedepth: pointer to an integer variable (will be overwritten) or NULL - * - * fdt_supernode_atdepth_offset() finds an ancestor of the given node - * at a specific depth from the root (where the root itself has depth - * 0, its immediate subnodes depth 1 and so forth). So - * fdt_supernode_atdepth_offset(fdt, nodeoffset, 0, NULL); - * will always return 0, the offset of the root node. If the node at - * nodeoffset has depth D, then: - * fdt_supernode_atdepth_offset(fdt, nodeoffset, D, NULL); - * will return nodeoffset itself. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * structure block offset of the node at node offset's ancestor - * of depth supernodedepth (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_NOTFOUND, supernodedepth was greater than the depth of - * nodeoffset - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_supernode_atdepth_offset(const void *fdt, int nodeoffset, - int supernodedepth, int *nodedepth); - -/** - * fdt_node_depth - find the depth of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * - * fdt_node_depth() finds the depth of a given node. The root node - * has depth 0, its immediate subnodes depth 1 and so forth. - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset. - * - * returns: - * depth of the node at nodeoffset (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_depth(const void *fdt, int nodeoffset); - -/** - * fdt_parent_offset - find the parent of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose parent to find - * - * fdt_parent_offset() locates the parent node of a given node (that - * is, it finds the offset of the node which contains the node at - * nodeoffset as a subnode). - * - * NOTE: This function is expensive, as it must scan the device tree - * structure from the start to nodeoffset, *twice*. - * - * returns: - * structure block offset of the parent of the node at nodeoffset - * (>=0), on success - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_parent_offset(const void *fdt, int nodeoffset); - -/** - * fdt_node_offset_by_prop_value - find nodes with a given property value - * @fdt: pointer to the device tree blob - * @startoffset: only find nodes after this offset - * @propname: property name to check - * @propval: property value to search for - * @proplen: length of the value in propval - * - * fdt_node_offset_by_prop_value() returns the offset of the first - * node after startoffset, which has a property named propname whose - * value is of length proplen and has value equal to propval; or if - * startoffset is -1, the very first such node in the tree. - * - * To iterate through all nodes matching the criterion, the following - * idiom can be used: - * offset = fdt_node_offset_by_prop_value(fdt, -1, propname, - * propval, proplen); - * while (offset != -FDT_ERR_NOTFOUND) { - * // other code here - * offset = fdt_node_offset_by_prop_value(fdt, offset, propname, - * propval, proplen); - * } - * - * Note the -1 in the first call to the function, if 0 is used here - * instead, the function will never locate the root node, even if it - * matches the criterion. - * - * returns: - * structure block offset of the located node (>= 0, >startoffset), - * on success - * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the - * tree after startoffset - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_prop_value(const void *fdt, int startoffset, - const char *propname, - const void *propval, int proplen); - -/** - * fdt_node_offset_by_phandle - find the node with a given phandle - * @fdt: pointer to the device tree blob - * @phandle: phandle value - * - * fdt_node_offset_by_phandle() returns the offset of the node - * which has the given phandle value. If there is more than one node - * in the tree with the given phandle (an invalid tree), results are - * undefined. - * - * returns: - * structure block offset of the located node (>= 0), on success - * -FDT_ERR_NOTFOUND, no node with that phandle exists - * -FDT_ERR_BADPHANDLE, given phandle value was invalid (0 or -1) - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_phandle(const void *fdt, uint32_t phandle); - -/** - * fdt_node_check_compatible - check a node's compatible property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @compatible: string to match against - * - * fdt_node_check_compatible() returns 0 if the given node contains a - * @compatible property with the given string as one of its elements, - * it returns non-zero otherwise, or on error. - * - * returns: - * 0, if the node has a 'compatible' property listing the given string - * 1, if the node has a 'compatible' property, but it does not list - * the given string - * -FDT_ERR_NOTFOUND, if the given node has no 'compatible' property - * -FDT_ERR_BADOFFSET, if nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_check_compatible(const void *fdt, int nodeoffset, - const char *compatible); - -/** - * fdt_node_offset_by_compatible - find nodes with a given 'compatible' value - * @fdt: pointer to the device tree blob - * @startoffset: only find nodes after this offset - * @compatible: 'compatible' string to match against - * - * fdt_node_offset_by_compatible() returns the offset of the first - * node after startoffset, which has a 'compatible' property which - * lists the given compatible string; or if startoffset is -1, the - * very first such node in the tree. - * - * To iterate through all nodes matching the criterion, the following - * idiom can be used: - * offset = fdt_node_offset_by_compatible(fdt, -1, compatible); - * while (offset != -FDT_ERR_NOTFOUND) { - * // other code here - * offset = fdt_node_offset_by_compatible(fdt, offset, compatible); - * } - * - * Note the -1 in the first call to the function, if 0 is used here - * instead, the function will never locate the root node, even if it - * matches the criterion. - * - * returns: - * structure block offset of the located node (>= 0, >startoffset), - * on success - * -FDT_ERR_NOTFOUND, no node matching the criterion exists in the - * tree after startoffset - * -FDT_ERR_BADOFFSET, nodeoffset does not refer to a BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, standard meanings - */ -int fdt_node_offset_by_compatible(const void *fdt, int startoffset, - const char *compatible); - -/** - * fdt_stringlist_contains - check a string list property for a string - * @strlist: Property containing a list of strings to check - * @listlen: Length of property - * @str: String to search for - * - * This is a utility function provided for convenience. The list contains - * one or more strings, each terminated by \0, as is found in a device tree - * "compatible" property. - * - * Return: 1 if the string is found in the list, 0 not found, or invalid list - */ -int fdt_stringlist_contains(const char *strlist, int listlen, const char *str); - -/** - * fdt_stringlist_count - count the number of strings in a string list - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * - * Return: - * the number of strings in the given property - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist - */ -int fdt_stringlist_count(const void *fdt, int nodeoffset, const char *property); - -/** - * fdt_stringlist_search - find a string in a string list and return its index - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * @string: string to look up in the string list - * - * Note that it is possible for this function to succeed on property values - * that are not NUL-terminated. That's because the function will stop after - * finding the first occurrence of @string. This can for example happen with - * small-valued cell properties, such as #address-cells, when searching for - * the empty string. - * - * return: - * the index of the string in the list of strings - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist or does not contain - * the given string - */ -int fdt_stringlist_search(const void *fdt, int nodeoffset, const char *property, - const char *string); - -/** - * fdt_stringlist_get() - obtain the string at a given index in a string list - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of a tree node - * @property: name of the property containing the string list - * @index: index of the string to return - * @lenp: return location for the string length or an error code on failure - * - * Note that this will successfully extract strings from properties with - * non-NUL-terminated values. For example on small-valued cell properties - * this function will return the empty string. - * - * If non-NULL, the length of the string (on success) or a negative error-code - * (on failure) will be stored in the integer pointer to by lenp. - * - * Return: - * A pointer to the string at the given index in the string list or NULL on - * failure. On success the length of the string will be stored in the memory - * location pointed to by the lenp parameter, if non-NULL. On failure one of - * the following negative error codes will be returned in the lenp parameter - * (if non-NULL): - * -FDT_ERR_BADVALUE if the property value is not NUL-terminated - * -FDT_ERR_NOTFOUND if the property does not exist - */ -const char *fdt_stringlist_get(const void *fdt, int nodeoffset, - const char *property, int index, - int *lenp); - -/**********************************************************************/ -/* Read-only functions (addressing related) */ -/**********************************************************************/ - -/** - * FDT_MAX_NCELLS - maximum value for #address-cells and #size-cells - * - * This is the maximum value for #address-cells, #size-cells and - * similar properties that will be processed by libfdt. IEE1275 - * requires that OF implementations handle values up to 4. - * Implementations may support larger values, but in practice higher - * values aren't used. - */ -#define FDT_MAX_NCELLS 4 - -/** - * fdt_address_cells - retrieve address size for a bus represented in the tree - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to find the address size for - * - * When the node has a valid #address-cells property, returns its value. - * - * returns: - * 0 <= n < FDT_MAX_NCELLS, on success - * 2, if the node has no #address-cells property - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #address-cells property - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_address_cells(const void *fdt, int nodeoffset); - -/** - * fdt_size_cells - retrieve address range size for a bus represented in the - * tree - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to find the address range size for - * - * When the node has a valid #size-cells property, returns its value. - * - * returns: - * 0 <= n < FDT_MAX_NCELLS, on success - * 1, if the node has no #size-cells property - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #size-cells property - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_size_cells(const void *fdt, int nodeoffset); - - -/**********************************************************************/ -/* Write-in-place functions */ -/**********************************************************************/ - -/** - * fdt_setprop_inplace_namelen_partial - change a property's value, - * but not its size - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: number of characters of name to consider - * @idx: index of the property to change in the array - * @val: pointer to data to replace the property value with - * @len: length of the property value - * - * Identical to fdt_setprop_inplace(), but modifies the given property - * starting from the given index, and using only the first characters - * of the name. It is useful when you want to manipulate only one value of - * an array and you have a string that doesn't end with \0. - * - * Return: 0 on success, negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_setprop_inplace_namelen_partial(void *fdt, int nodeoffset, - const char *name, int namelen, - uint32_t idx, const void *val, - int len); -#endif - -/** - * fdt_setprop_inplace - change a property's value, but not its size - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: pointer to data to replace the property value with - * @len: length of the property value - * - * fdt_setprop_inplace() replaces the value of a given property with - * the data in val, of length len. This function cannot change the - * size of a property, and so will only work if len is equal to the - * current length of the property. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if len is not equal to the property's current length - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#ifndef SWIG /* Not available in Python */ -int fdt_setprop_inplace(void *fdt, int nodeoffset, const char *name, - const void *val, int len); -#endif - -/** - * fdt_setprop_inplace_u32 - change the value of a 32-bit integer property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to replace the property with - * - * fdt_setprop_inplace_u32() replaces the value of a given property - * with the 32-bit integer value in val, converting val to big-endian - * if necessary. This function cannot change the size of a property, - * and so will only work if the property already exists and has length - * 4. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if the property's length is not equal to 4 - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_inplace_u32(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_inplace_u64 - change the value of a 64-bit integer property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value to replace the property with - * - * fdt_setprop_inplace_u64() replaces the value of a given property - * with the 64-bit integer value in val, converting val to big-endian - * if necessary. This function cannot change the size of a property, - * and so will only work if the property already exists and has length - * 8. - * - * This function will alter only the bytes in the blob which contain - * the given property value, and will not alter or move any other part - * of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if the property's length is not equal to 8 - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_inplace_u64(void *fdt, int nodeoffset, - const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_setprop_inplace(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_inplace_cell - change the value of a single-cell property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node containing the property - * @name: name of the property to change the value of - * @val: new value of the 32-bit cell - * - * This is an alternative name for fdt_setprop_inplace_u32() - * Return: 0 on success, negative libfdt error number otherwise. - */ -static inline int fdt_setprop_inplace_cell(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - return fdt_setprop_inplace_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_nop_property - replace a property with nop tags - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to nop - * @name: name of the property to nop - * - * fdt_nop_property() will replace a given property's representation - * in the blob with FDT_NOP tags, effectively removing it from the - * tree. - * - * This function will alter only the bytes in the blob which contain - * the property, and will not alter or move any other part of the - * tree. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_nop_property(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_nop_node - replace a node (subtree) with nop tags - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to nop - * - * fdt_nop_node() will replace a given node's representation in the - * blob, including all its subnodes, if any, with FDT_NOP tags, - * effectively removing it from the tree. - * - * This function will alter only the bytes in the blob which contain - * the node and its properties and subnodes, and will not alter or - * move any other part of the tree. - * - * returns: - * 0, on success - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_nop_node(void *fdt, int nodeoffset); - -/**********************************************************************/ -/* Sequential write functions */ -/**********************************************************************/ - -/* fdt_create_with_flags flags */ -#define FDT_CREATE_FLAG_NO_NAME_DEDUP 0x1 - /* FDT_CREATE_FLAG_NO_NAME_DEDUP: Do not try to de-duplicate property - * names in the fdt. This can result in faster creation times, but - * a larger fdt. */ - -#define FDT_CREATE_FLAGS_ALL (FDT_CREATE_FLAG_NO_NAME_DEDUP) - -/** - * fdt_create_with_flags - begin creation of a new fdt - * @buf: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * @flags: a valid combination of FDT_CREATE_FLAG_ flags, or 0. - * - * fdt_create_with_flags() begins the process of creating a new fdt with - * the sequential write interface. - * - * fdt creation process must end with fdt_finish() to produce a valid fdt. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - * -FDT_ERR_BADFLAGS, flags is not valid - */ -int fdt_create_with_flags(void *buf, int bufsize, uint32_t flags); - -/** - * fdt_create - begin creation of a new fdt - * @buf: pointer to memory allocated where fdt will be created - * @bufsize: size of the memory space at fdt - * - * fdt_create() is equivalent to fdt_create_with_flags() with flags=0. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, bufsize is insufficient for a minimal fdt - */ -int fdt_create(void *buf, int bufsize); - -/** - * fdt_resize - move and resize a device tree in sequential write state - * @fdt: Pointer to the device tree to resize - * @buf: Buffer where resized tree should be placed - * @bufsize: Size of the buffer at @buf - * - * fdt_resize() moves the device tree blob from @fdt to @buf and - * resizes it to fit in the new buffer size. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_resize(void *fdt, void *buf, int bufsize); - -/** - * fdt_add_reservemap_entry - add an entry to the memory reserve map - * @fdt: Pointer to the device tree blob - * @addr: Start address of the reserve map entry - * @size: Size of the reserved region - * - * fdt_add_reservemap_entry() adds a memory reserve map entry to the - * device tree blob during the sequential write process. This function - * can only be called after fdt_create() and before fdt_finish_reservemap(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_add_reservemap_entry(void *fdt, uint64_t addr, uint64_t size); - -/** - * fdt_finish_reservemap - complete the memory reserve map - * @fdt: Pointer to the device tree blob - * - * fdt_finish_reservemap() completes the memory reserve map section - * of the device tree blob during sequential write. After calling this - * function, no more reserve map entries can be added and the blob - * moves to the structure creation phase. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_finish_reservemap(void *fdt); - -/** - * fdt_begin_node - start creation of a new node - * @fdt: Pointer to the device tree blob - * @name: Name of the node to create - * - * fdt_begin_node() starts the creation of a new node with the given - * @name during sequential write. After calling this function, properties - * can be added with fdt_property() and subnodes can be created with - * additional fdt_begin_node() calls. The node must be completed with - * fdt_end_node(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not in the correct sequential write state - */ -int fdt_begin_node(void *fdt, const char *name); - -/** - * fdt_property - add a property to the current node - * @fdt: Pointer to the device tree blob - * @name: Name of the property to add - * @val: Pointer to the property value - * @len: Length of the property value in bytes - * - * fdt_property() adds a property with the given @name and value to - * the current node during sequential write. This function can only - * be called between fdt_begin_node() and fdt_end_node(). - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if there is insufficient space in the blob - * -FDT_ERR_BADSTATE, if not currently within a node - */ -int fdt_property(void *fdt, const char *name, const void *val, int len); -static inline int fdt_property_u32(void *fdt, const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_property(fdt, name, &tmp, sizeof(tmp)); -} -static inline int fdt_property_u64(void *fdt, const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_property(fdt, name, &tmp, sizeof(tmp)); -} - -#ifndef SWIG /* Not available in Python */ -static inline int fdt_property_cell(void *fdt, const char *name, uint32_t val) -{ - return fdt_property_u32(fdt, name, val); -} -#endif - -/** - * fdt_property_placeholder - add a new property and return a ptr to its value - * - * @fdt: pointer to the device tree blob - * @name: name of property to add - * @len: length of property value in bytes - * @valp: returns a pointer to where the value should be placed - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_NOSPACE, standard meanings - */ -int fdt_property_placeholder(void *fdt, const char *name, int len, void **valp); - -#define fdt_property_string(fdt, name, str) \ - fdt_property(fdt, name, str, strlen(str)+1) - -/** - * fdt_end_node - complete the current node - * @fdt: Pointer to the device tree blob - * - * fdt_end_node() completes the current node during sequential write. This - * function must be called to close each node started with - * fdt_begin_node(). After calling this function, no more properties or subnodes - * can be added to the node. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if not currently within a node - */ -int fdt_end_node(void *fdt); - -/** - * fdt_finish - complete device tree creation - * @fdt: Pointer to the device tree blob - * - * fdt_finish() completes the device tree creation process started with - * fdt_create(). This function finalizes the device tree blob and makes it ready - * for use. After calling this function, the blob is complete and can be used - * with libfdt read-only and read-write functions, but not with sequential write - * functions. - * - * returns: - * 0, on success - * -FDT_ERR_BADSTATE, if the sequential write process is incomplete - */ -int fdt_finish(void *fdt); - -/**********************************************************************/ -/* Read-write functions */ -/**********************************************************************/ - -/** - * fdt_create_empty_tree - create an empty device tree - * @buf: Buffer where the empty tree should be created - * @bufsize: Size of the buffer at @buf - * - * fdt_create_empty_tree() creates a minimal empty device tree blob - * in the given buffer. The tree contains only a root node with no - * properties or subnodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small for even an empty tree - */ -int fdt_create_empty_tree(void *buf, int bufsize); - -/** - * fdt_open_into - move a device tree into a new buffer and make editable - * @fdt: Pointer to the device tree to move - * @buf: Buffer where the editable tree should be placed - * @bufsize: Size of the buffer at @buf - * - * fdt_open_into() moves and reorganizes the device tree blob from @fdt - * into @buf, converting it to a format suitable for read-write operations. - * The new buffer should allow space for modifications. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, if @bufsize is too small - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_open_into(const void *fdt, void *buf, int bufsize); - -/** - * fdt_pack - pack a device tree blob - * @fdt: Pointer to the device tree blob - * - * fdt_pack() reorganizes the device tree blob to eliminate any free space - * and pack it into the minimum possible size. This is useful after making - * modifications that might have left gaps in the blob. - * - * returns: - * 0, on success - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, standard meanings - */ -int fdt_pack(void *fdt); - -/** - * fdt_add_mem_rsv - add one memory reserve map entry - * @fdt: pointer to the device tree blob - * @address: 64-bit start address of the reserve map entry - * @size: 64-bit size of the reserved region - * - * Adds a reserve map entry to the given blob reserving a region at - * address address of length size. - * - * This function will insert data into the reserve map and will - * therefore change the indexes of some entries in the table. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new reservation entry - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_add_mem_rsv(void *fdt, uint64_t address, uint64_t size); - -/** - * fdt_del_mem_rsv - remove a memory reserve map entry - * @fdt: pointer to the device tree blob - * @n: entry to remove - * - * fdt_del_mem_rsv() removes the n-th memory reserve map entry from - * the blob. - * - * This function will delete data from the reservation table and will - * therefore change the indexes of some entries in the table. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, there is no entry of the given index (i.e. there - * are less than n+1 reserve map entries) - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_del_mem_rsv(void *fdt, int n); - -/** - * fdt_set_name - change the name of a given node - * @fdt: pointer to the device tree blob - * @nodeoffset: structure block offset of a node - * @name: name to give the node - * - * fdt_set_name() replaces the name (including unit address, if any) - * of the given node with the given string. NOTE: this function can't - * efficiently check if the new name is unique amongst the given - * node's siblings; results are undefined if this function is invoked - * with a name equal to one of the given node's siblings. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob - * to contain the new name - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, standard meanings - */ -int fdt_set_name(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_setprop_namelen - create or change a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: length of the name - * @val: pointer to data to set the property value to - * @len: length of the property value - * - * fdt_setprop_namelen() sets the value of the named property in the given - * node to the given value and length, creating the property if it - * does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_setprop_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, const void *val, int len); - -/** - * fdt_setprop - create or change a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: pointer to data to set the property value to - * @len: length of the property value - * - * fdt_setprop() sets the value of the named property in the given - * node to the given value and length, creating the property if it - * does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop(void *fdt, int nodeoffset, const char *name, - const void *val, int len) -{ - return fdt_setprop_namelen(fdt, nodeoffset, name, strlen(name), val, - len); -} - -/** - * fdt_setprop_placeholder_namelen - allocate space for a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: length of the name - * @len: length of the property value - * @prop_data: return pointer to property data - * - * fdt_setprop_placeholder_namelen() allocates the named property in the given node. - * If the property exists it is resized. In either case a pointer to the - * property data is returned. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_setprop_placeholder_namelen(void *fdt, int nodeoffset, const char *name, - int namelen, int len, void **prop_data); - -/** - * fdt_setprop_placeholder - allocate space for a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @len: length of the property value - * @prop_data: return pointer to property data - * - * fdt_setprop_placeholder() allocates the named property in the given node. - * If the property exists it is resized. In either case a pointer to the - * property data is returned. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_placeholder(void *fdt, int nodeoffset, - const char *name, int len, - void **prop_data) -{ - return fdt_setprop_placeholder_namelen(fdt, nodeoffset, name, - strlen(name), len, prop_data); -} - -/** - * fdt_setprop_u32 - set a property to a 32-bit integer - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value for the property (native endian) - * - * fdt_setprop_u32() sets the value of the named property in the given - * node to the given 32-bit integer value (converting to big-endian if - * necessary), or creates a new property with that value if it does - * not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_u32(void *fdt, int nodeoffset, const char *name, - uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_u64 - set a property to a 64-bit integer - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value for the property (native endian) - * - * fdt_setprop_u64() sets the value of the named property in the given - * node to the given 64-bit integer value (converting to big-endian if - * necessary), or creates a new property with that value if it does - * not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_setprop_u64(void *fdt, int nodeoffset, const char *name, - uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_setprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_setprop_cell - set a property to a single cell value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value for the property (native endian) - * - * This is an alternative name for fdt_setprop_u32() - * - * Return: 0 on success, negative libfdt error value otherwise. - */ -static inline int fdt_setprop_cell(void *fdt, int nodeoffset, const char *name, - uint32_t val) -{ - return fdt_setprop_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_setprop_string - set a property to a string value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @str: string value for the property - * - * fdt_setprop_string() sets the value of the named property in the - * given node to the given string value (using the length of the - * string to determine the new length of the property), or creates a - * new property with that value if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_string(fdt, nodeoffset, name, str) \ - fdt_setprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) - -/** - * fdt_setprop_namelen_string - set a property to a string value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @namelen: number of characters of name to consider - * @str: string value for the property - * - * fdt_setprop_namelen_string() sets the value of the named property in the - * given node to the given string value (using the length of the - * string to determine the new length of the property), or creates a - * new property with that value if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_namelen_string(fdt, nodeoffset, name, namelen, str) \ - fdt_setprop_namelen((fdt), (nodeoffset), (name), (namelen), (str), \ - strlen(str) + 1) - -/** - * fdt_setprop_empty - set a property to an empty value - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * - * fdt_setprop_empty() sets the value of the named property in the - * given node to an empty (zero length) value, or creates a new empty - * property if it does not already exist. - * - * This function may insert or delete data from the blob, and will - * therefore change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_setprop_empty(fdt, nodeoffset, name) \ - fdt_setprop((fdt), (nodeoffset), (name), NULL, 0) - -/** - * fdt_appendprop - append to or create a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to append to - * @val: pointer to data to append to the property value - * @len: length of the data to append to the property value - * - * fdt_appendprop() appends the value to the named property in the - * given node, creating the property if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_appendprop(void *fdt, int nodeoffset, const char *name, - const void *val, int len); - -/** - * fdt_appendprop_u32 - append a 32-bit integer value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to append to the property (native endian) - * - * fdt_appendprop_u32() appends the given 32-bit integer value - * (converting to big-endian if necessary) to the value of the named - * property in the given node, or creates a new property with that - * value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_appendprop_u32(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - fdt32_t tmp = cpu_to_fdt32(val); - return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_appendprop_u64 - append a 64-bit integer value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 64-bit integer value to append to the property (native endian) - * - * fdt_appendprop_u64() appends the given 64-bit integer value - * (converting to big-endian if necessary) to the value of the named - * property in the given node, or creates a new property with that - * value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -static inline int fdt_appendprop_u64(void *fdt, int nodeoffset, - const char *name, uint64_t val) -{ - fdt64_t tmp = cpu_to_fdt64(val); - return fdt_appendprop(fdt, nodeoffset, name, &tmp, sizeof(tmp)); -} - -/** - * fdt_appendprop_cell - append a single cell value to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @val: 32-bit integer value to append to the property (native endian) - * - * This is an alternative name for fdt_appendprop_u32() - * - * Return: 0 on success, negative libfdt error value otherwise. - */ -static inline int fdt_appendprop_cell(void *fdt, int nodeoffset, - const char *name, uint32_t val) -{ - return fdt_appendprop_u32(fdt, nodeoffset, name, val); -} - -/** - * fdt_appendprop_string - append a string to a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to change - * @name: name of the property to change - * @str: string value to append to the property - * - * fdt_appendprop_string() appends the given string to the value of - * the named property in the given node, or creates a new property - * with that value if it does not already exist. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain the new property value - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_TRUNCATED, standard meanings - */ -#define fdt_appendprop_string(fdt, nodeoffset, name, str) \ - fdt_appendprop((fdt), (nodeoffset), (name), (str), strlen(str)+1) - -/** - * fdt_appendprop_addrrange - append a address range property - * @fdt: pointer to the device tree blob - * @parent: offset of the parent node - * @nodeoffset: offset of the node to add a property at - * @name: name of property - * @addr: start address of a given range - * @size: size of a given range - * - * fdt_appendprop_addrrange() appends an address range value (start - * address and size) to the value of the named property in the given - * node, or creates a new property with that value if it does not - * already exist. - * - * Cell sizes are determined by parent's #address-cells and #size-cells. - * - * This function may insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADNCELLS, if the node has a badly formatted or invalid - * #address-cells property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADVALUE, addr or size doesn't fit to respective cells size - * -FDT_ERR_NOSPACE, there is insufficient free space in the blob to - * contain a new property - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_appendprop_addrrange(void *fdt, int parent, int nodeoffset, - const char *name, uint64_t addr, uint64_t size); - -/** - * fdt_delprop - delete a property - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node whose property to nop - * @name: name of the property to nop - * - * fdt_delprop() will delete the given property. - * - * This function will delete data from the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_NOTFOUND, node does not have the named property - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_delprop(void *fdt, int nodeoffset, const char *name); - -/** - * fdt_add_subnode_namelen - creates a new node based on substring - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to create - * @namelen: number of characters of name to consider - * - * Identical to fdt_add_subnode(), but use only the first @namelen - * characters of @name as the name of the new node. This is useful for - * creating subnodes based on a portion of a larger string, such as a - * full path. - * - * Return: structure block offset of the created subnode (>=0), - * negative libfdt error value otherwise - */ -#ifndef SWIG /* Not available in Python */ -int fdt_add_subnode_namelen(void *fdt, int parentoffset, - const char *name, int namelen); -#endif - -/** - * fdt_add_subnode - creates a new node - * @fdt: pointer to the device tree blob - * @parentoffset: structure block offset of a node - * @name: name of the subnode to locate - * - * fdt_add_subnode() creates a new node as a subnode of the node at - * structure block offset parentoffset, with the given name (which - * should include the unit address, if any). - * - * This function will insert data into the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * structure block offset of the created subnode (>=0), on success - * -FDT_ERR_NOTFOUND, if the requested subnode does not exist - * -FDT_ERR_BADOFFSET, if parentoffset did not point to an FDT_BEGIN_NODE - * tag - * -FDT_ERR_EXISTS, if the node at parentoffset already has a subnode of - * the given name - * -FDT_ERR_NOSPACE, if there is insufficient free space in the - * blob to contain the new node - * -FDT_ERR_NOSPACE - * -FDT_ERR_BADLAYOUT - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings. - */ -int fdt_add_subnode(void *fdt, int parentoffset, const char *name); - -/** - * fdt_del_node - delete a node (subtree) - * @fdt: pointer to the device tree blob - * @nodeoffset: offset of the node to nop - * - * fdt_del_node() will remove the given node, including all its - * subnodes if any, from the blob. - * - * This function will delete data from the blob, and will therefore - * change the offsets of some existing nodes. - * - * returns: - * 0, on success - * -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_del_node(void *fdt, int nodeoffset); - -/** - * fdt_overlay_apply - Applies a DT overlay on a base DT - * @fdt: pointer to the base device tree blob - * @fdto: pointer to the device tree overlay blob - * - * fdt_overlay_apply() will apply the given device tree overlay on the - * given base device tree. - * - * Expect the base device tree to be modified, even if the function - * returns an error. - * - * returns: - * 0, on success - * -FDT_ERR_NOSPACE, there's not enough space in the base device tree - * -FDT_ERR_NOTFOUND, the overlay points to some nonexistent nodes or - * properties in the base DT - * -FDT_ERR_BADPHANDLE, - * -FDT_ERR_BADOVERLAY, - * -FDT_ERR_NOPHANDLES, - * -FDT_ERR_INTERNAL, - * -FDT_ERR_BADLAYOUT, - * -FDT_ERR_BADMAGIC, - * -FDT_ERR_BADOFFSET, - * -FDT_ERR_BADPATH, - * -FDT_ERR_BADVERSION, - * -FDT_ERR_BADSTRUCTURE, - * -FDT_ERR_BADSTATE, - * -FDT_ERR_TRUNCATED, standard meanings - */ -int fdt_overlay_apply(void *fdt, void *fdto); - -/** - * fdt_overlay_target_offset - retrieves the offset of a fragment's target - * @fdt: Base device tree blob - * @fdto: Device tree overlay blob - * @fragment_offset: node offset of the fragment in the overlay - * @pathp: pointer which receives the path of the target (or NULL) - * - * fdt_overlay_target_offset() retrieves the target offset in the base - * device tree of a fragment, no matter how the actual targeting is - * done (through a phandle or a path) - * - * returns: - * the targeted node offset in the base device tree - * Negative error code on error - */ -int fdt_overlay_target_offset(const void *fdt, const void *fdto, - int fragment_offset, char const **pathp); - -/**********************************************************************/ -/* Debugging / informational functions */ -/**********************************************************************/ - -/** - * fdt_strerror - return string description of error code - * @errval: Error code returned by a libfdt function - * - * fdt_strerror() returns a string description of the error code passed - * in @errval. - * - * returns: - * pointer to a string describing the error code - */ -const char *fdt_strerror(int errval); - -#ifdef __cplusplus -} -#endif - -#endif /* LIBFDT_H */ diff --git a/na/scripts/dtc/libfdt/libfdt_env.h b/na/scripts/dtc/libfdt/libfdt_env.h deleted file mode 100644 index 5580b48..0000000 --- a/na/scripts/dtc/libfdt/libfdt_env.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_ENV_H -#define LIBFDT_ENV_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - * Copyright 2012 Kim Phillips, Freescale Semiconductor. - */ - -#include <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 deleted file mode 100644 index 0e103ca..0000000 --- a/na/scripts/dtc/libfdt/libfdt_internal.h +++ /dev/null @@ -1,202 +0,0 @@ -/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */ -#ifndef LIBFDT_INTERNAL_H -#define LIBFDT_INTERNAL_H -/* - * libfdt - Flat Device Tree manipulation - * Copyright (C) 2006 David Gibson, IBM Corporation. - */ -#include <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 deleted file mode 100644 index c97ec2c..0000000 --- a/na/scripts/extract-asm-offsets.sed +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Extract `->SYM val COMMENT` markers emitted by inline asm in -# asm-offsets.c into proper `#define SYM val /* COMMENT */` lines. -# 1:1 with Linux scripts/Makefile.lib `sed-offsets`. -# -# Input : assembly source produced by `gcc -S asm-offsets.c` — -# inline asm emits lines like -# .ascii "->KERNEL_MAP_VIRT_ADDR 0 offsetof(...)" -# Output : header fragment for include/generated/asm-offsets.h -# -# Step 1 unwraps the `.ascii "..."` directive so the actual payload -# (starting with `->`) is exposed. Steps 2-4 then rewrite into a -# proper #define. -s:^[[:space:]]*\.ascii[[:space:]]*"\(.*\)".*:\1: -/^->/{s:->#\(.*\):/* \1 */:; - s:^->\([^ ]*\) [\$#]*\([^ ]*\) \(.*\):#define \1 \2 /* \3 */:; - s:->::; - p;} diff --git a/na/scripts/gen_initramfs.sh b/na/scripts/gen_initramfs.sh deleted file mode 100755 index 7eba2fd..0000000 --- a/na/scripts/gen_initramfs.sh +++ /dev/null @@ -1,250 +0,0 @@ -#!/bin/sh -# Copyright (C) Martin Schlemmer <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/test/module.mk b/na/test/module.mk deleted file mode 100644 index 47a693c..0000000 --- a/na/test/module.mk +++ /dev/null @@ -1,30 +0,0 @@ -# test/module.mk -SRCS_CC += test/selftest.cc -SRCS_CC += test/test_pmm.cc -SRCS_CC += test/test_slab.cc -SRCS_CC += test/test_spinlock.cc -SRCS_CC += test/test_mutex.cc -SRCS_CC += test/test_driver_model.cc -SRCS_CC += test/test_rbtree.cc -SRCS_CC += test/test_vmm.cc -SRCS_CC += test/test_vma.cc -SRCS_CC += test/test_atomic.cc -SRCS_CC += test/test_fdt.cc -SRCS_CC += test/test_trap.cc -SRCS_CC += test/test_sched.cc -SRCS_CC += test/test_klog.cc -SRCS_CC += test/test_random.cc -SRCS_CC += test/test_waitqueue.cc -SRCS_CC += test/test_uaccess.cc -SRCS_CC += test/test_sys_mm.cc -SRCS_CC += test/test_smp.cc -SRCS_CC += test/test_dcache.cc -SRCS_CC += test/test_namei.cc -SRCS_CC += test/test_vfs_io.cc -SRCS_CC += test/test_exec.cc -SRCS_CC += test/test_pipe_devfs.cc -SRCS_CC += test/test_sys_process.cc -SRCS_CC += test/test_signal.cc -SRCS_CC += test/test_pid.cc -SRCS_CC += test/test_sys_fs_extra.cc -SRCS_CC += test/test_page_cache.cc diff --git a/na/test/selftest.cc b/na/test/selftest.cc deleted file mode 100644 index e1bb2b4..0000000 --- a/na/test/selftest.cc +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test Runner - * - * Entry point that invokes all registered self-test suites. - * Called from kernel_main() after all subsystem initialization. - */ - -#include <lume/selftest.h> - -/* Individual test suite entry points */ -void selftest_pmm(); -void selftest_slab(); -void selftest_spinlock(); -void selftest_mutex(); -void selftest_driver_model(); -void selftest_rbtree(); -void selftest_vmm(); -void selftest_fdt(); -void selftest_vma(); -void selftest_atomic(); -void selftest_trap(); -void selftest_sched(); -void selftest_klog(); -void selftest_random(); -void selftest_waitqueue(); -void selftest_uaccess(); -void selftest_sys_mm(); -void selftest_dcache(); -void selftest_namei(); -void selftest_vfs_io(); -void selftest_exec(); -void selftest_devfs(); -void selftest_pipe(); -void selftest_fcntl(); -void selftest_sys_process(); -void selftest_signal(); -void selftest_pid(); -void selftest_sys_fs_extra(); -void selftest_page_cache(); - -void selftest_run_all() -{ - kprintf("\n========================================\n"); - kprintf(" Boot Self-Test Suite\n"); - kprintf("========================================\n"); - - selftest_spinlock(); - selftest_mutex(); - selftest_driver_model(); - selftest_pmm(); - selftest_slab(); - selftest_rbtree(); - selftest_vmm(); - selftest_vma(); - selftest_atomic(); - selftest_fdt(); - selftest_trap(); - selftest_sched(); - selftest_klog(); - selftest_random(); - selftest_waitqueue(); - selftest_uaccess(); - selftest_sys_mm(); - selftest_dcache(); - selftest_namei(); - selftest_vfs_io(); - selftest_devfs(); - selftest_pipe(); - selftest_fcntl(); - selftest_sys_process(); - selftest_signal(); - selftest_pid(); - selftest_sys_fs_extra(); - selftest_page_cache(); - selftest_exec(); - - kprintf("========================================\n"); - kprintf(" All self-tests PASSED\n"); - kprintf("========================================\n\n"); -} diff --git a/na/test/test_atomic.cc b/na/test/test_atomic.cc deleted file mode 100644 index 9851e7b..0000000 --- a/na/test/test_atomic.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: lume::atomic<T> - * - * Tests: - * 1. Default construction (zero-initialized) - * 2. Explicit construction - * 3. store / load round-trip with different memory orders - * 4. exchange returns old value - * 5. fetch_add / fetch_sub arithmetic - * 6. compare_exchange success and failure paths - */ - -#include <lume/selftest.h> -#include <lume/atomic.h> - -void selftest_atomic() { - st_begin("atomic: default construction is zero"); - { - lume::atomic<uint32> a; - ST_ASSERT_EQ(a.load(), 0U); - } - st_pass(); - - st_begin("atomic: explicit construction"); - { - lume::atomic<uint64> a(42); - ST_ASSERT_EQ(a.load(), 42ULL); - } - st_pass(); - - st_begin("atomic: store/load round-trip"); - { - lume::atomic<uint32> a; - a.store(0xDEAD, __ATOMIC_RELEASE); - ST_ASSERT_EQ(a.load(__ATOMIC_ACQUIRE), 0xDEADU); - - a.store(0, __ATOMIC_SEQ_CST); - ST_ASSERT_EQ(a.load(__ATOMIC_SEQ_CST), 0U); - } - st_pass(); - - st_begin("atomic: exchange returns old value"); - { - lume::atomic<uint32> a(10); - uint32 old = a.exchange(20); - ST_ASSERT_EQ(old, 10U); - ST_ASSERT_EQ(a.load(), 20U); - } - st_pass(); - - st_begin("atomic: fetch_add / fetch_sub"); - { - lume::atomic<uint32> a(100); - - uint32 old = a.fetch_add(5); - ST_ASSERT_EQ(old, 100U); - ST_ASSERT_EQ(a.load(), 105U); - - old = a.fetch_sub(10); - ST_ASSERT_EQ(old, 105U); - ST_ASSERT_EQ(a.load(), 95U); - - // Underflow wraps (unsigned) - lume::atomic<uint32> b(0); - old = b.fetch_sub(1); - ST_ASSERT_EQ(old, 0U); - ST_ASSERT_EQ(b.load(), 0xFFFFFFFFU); - } - st_pass(); - - st_begin("atomic: compare_exchange success and failure"); - { - lume::atomic<uint32> a(42); - - // Success path: expected matches, swap happens - uint32 expected = 42; - bool ok = a.compare_exchange(expected, 99); - ST_ASSERT(ok); - ST_ASSERT_EQ(a.load(), 99U); - ST_ASSERT_EQ(expected, 42U); // expected unchanged on success - - // Failure path: expected doesn't match, expected gets updated - expected = 0; // wrong - ok = a.compare_exchange(expected, 200); - ST_ASSERT(!ok); - ST_ASSERT_EQ(a.load(), 99U); // value unchanged - ST_ASSERT_EQ(expected, 99U); // expected updated to actual value - } - st_pass(); - - st_begin("atomic: 64-bit fetch_add correctness"); - { - lume::atomic<uint64> a(0xFFFFFFFF00000000ULL); - uint64 old = a.fetch_add(1); - ST_ASSERT_EQ(old, 0xFFFFFFFF00000000ULL); - ST_ASSERT_EQ(a.load(), 0xFFFFFFFF00000001ULL); - } - st_pass(); -} diff --git a/na/test/test_dcache.cc b/na/test/test_dcache.cc deleted file mode 100644 index 9e0eec5..0000000 --- a/na/test/test_dcache.cc +++ /dev/null @@ -1,232 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_dcache.cc — Self-tests for the dentry cache layer - */ - -#include <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 deleted file mode 100644 index 8f105b3..0000000 --- a/na/test/test_driver_model.cc +++ /dev/null @@ -1,209 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_driver_model — exercises kref, kobject, and the bus/device/driver - * core. Uses a dummy bus + dummy driver + dummy device so the test is - * self-contained and doesn't depend on FDT/platform_device. - */ - -#include <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 deleted file mode 100644 index bbfa3b6..0000000 --- a/na/test/test_exec.cc +++ /dev/null @@ -1,447 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_exec.cc — Self-tests for execve / ELF loader - * - * Tests the ELF validation, binary loading, and user stack construction - * by creating synthetic ELF binaries in RamFS and invoking do_execve - * in a controlled environment. - * - * We cannot test the actual user-space transition (that requires - * sret to U-mode), but we CAN test everything up to and including - * the trapframe setup. - */ - -#include <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_fdt.cc b/na/test/test_fdt.cc deleted file mode 100644 index cf09029..0000000 --- a/na/test/test_fdt.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: FDT (Flattened Device Tree) - */ - -#include <lume/selftest.h> -#include <lume/fdt.h> - -// Manually declare what we need to avoid header conflicts with toolchain libc -extern "C" int strcmp(const char *s1, const char *s2); - -void selftest_fdt() { - st_begin("fdt: tree root access"); - { - DeviceNode* root = fdt_get_node_by_path("/"); - ST_ASSERT(root != nullptr); - ST_ASSERT_EQ(strcmp(root->get_name(), ""), 0); - } - st_pass(); - - st_begin("fdt: find existing compatible node (UART)"); - { - // QEMU virt RISC-V platform always has a ns16550a UART - DeviceNode* uart = fdt_find_compatible("ns16550a"); - ST_ASSERT(uart != nullptr); - - // Use get_prop_string which is convenience helper - const char* compat = uart->get_prop_string("compatible"); - ST_ASSERT(compat != nullptr); - - // Check raw property access - const FdtProperty* reg = uart->get_prop("reg"); - ST_ASSERT(reg != nullptr); - ST_ASSERT(reg->value != nullptr); - ST_ASSERT(reg->len > 0); - } - st_pass(); - - st_begin("fdt: non-existent node/compatible queries"); - { - DeviceNode* missing1 = fdt_get_node_by_path("/definitely_does_not_exist_node_12345"); - ST_ASSERT(missing1 == nullptr); - - DeviceNode* missing2 = fdt_find_compatible("invalid_compatible_string_xyz"); - ST_ASSERT(missing2 == nullptr); - } - st_pass(); -} diff --git a/na/test/test_klog.cc b/na/test/test_klog.cc deleted file mode 100644 index 896e7ed..0000000 --- a/na/test/test_klog.cc +++ /dev/null @@ -1,175 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: klog - * - * Tests: - * 1. read vs read_consume semantics - * 2. subsystem mask filtering - * 3. ratelimit bookkeeping - * 4. flush advances console tail when console filtering is active - * 5. seq counter increments on writes - */ - -#include <lume/selftest.h> -#include <lume/klog.h> -#include <string.h> - -static void drain_klog() -{ - static char buf[512]; - while (true) { - int n = klog_read_consume(buf, sizeof(buf)); - if (n <= 0) { - break; - } - } -} - -static bool test_read_peek_and_consume() -{ - drain_klog(); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_a"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_test_b"); - - KlogStats s0; - klog_stats(&s0); - - char buf[1024]; - int n = klog_read(buf, sizeof(buf) - 1); - if (n <= 0) { - return false; - } - buf[n] = '\0'; - if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { - return false; - } - - KlogStats s1; - klog_stats(&s1); - if (s1.tail != s0.tail) { - return false; - } - - int n2 = klog_read_consume(buf, sizeof(buf) - 1); - if (n2 <= 0) { - return false; - } - buf[n2] = '\0'; - if (!strstr(buf, "klog_test_a") || !strstr(buf, "klog_test_b")) { - return false; - } - - KlogStats s2; - klog_stats(&s2); - return s2.tail != s0.tail; -} - -static bool test_subsys_mask() -{ - drain_klog(); - - const uint64 all_mask = (1ULL << KLOG_SUBSYS_MAX) - 1; - klog_set_subsys_mask(1ULL << KLOG_SUBSYS_TEST); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_subsys_ok"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, "klog_subsys_drop"); - - char buf[1024]; - int n = klog_read_consume(buf, sizeof(buf) - 1); - klog_set_subsys_mask(all_mask); - - if (n <= 0) { - return false; - } - buf[n] = '\0'; - return strstr(buf, "klog_subsys_ok") && !strstr(buf, "klog_subsys_drop"); -} - -static bool test_ratelimit() -{ - KlogRateLimit rl = {}; - rl.interval = 100; - rl.burst = 2; - - bool a = klog_ratelimit(&rl, 10); - bool b = klog_ratelimit(&rl, 20); - bool c = klog_ratelimit(&rl, 30); - - if (!a || !b || c) { - return false; - } - if (rl.printed != 2 || rl.suppressed != 1) { - return false; - } - - bool d = klog_ratelimit(&rl, 200); - if (!d) { - return false; - } - return rl.printed == 1 && rl.suppressed == 0; -} - -static bool test_flush_advances_console_tail() -{ - drain_klog(); - - klog_set_level(KLOG_ERR); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_flush_msg"); - - KlogStats s1; - klog_stats(&s1); - if (s1.console_tail == s1.head) { - klog_set_level(static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL)); - return false; - } - - klog_flush(); - - KlogStats s2; - klog_stats(&s2); - - klog_set_level(static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL)); - return s2.console_tail == s2.head; -} - -static bool test_seq_increments() -{ - drain_klog(); - - KlogStats s0; - klog_stats(&s0); - - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_1"); - klog_ex(KLOG_INFO, KLOG_SUBSYS_TEST, "klog_seq_2"); - - KlogStats s1; - klog_stats(&s1); - - return s1.seq >= s0.seq + 2; -} - -void selftest_klog() -{ - st_begin("klog: read vs read_consume"); - ST_ASSERT(test_read_peek_and_consume()); - st_pass(); - - st_begin("klog: subsys mask filters"); - ST_ASSERT(test_subsys_mask()); - st_pass(); - - st_begin("klog: ratelimit bookkeeping"); - ST_ASSERT(test_ratelimit()); - st_pass(); - - st_begin("klog: flush advances console tail"); - ST_ASSERT(test_flush_advances_console_tail()); - st_pass(); - - st_begin("klog: seq increments on write"); - ST_ASSERT(test_seq_increments()); - st_pass(); -} diff --git a/na/test/test_mutex.cc b/na/test/test_mutex.cc deleted file mode 100644 index e86c984..0000000 --- a/na/test/test_mutex.cc +++ /dev/null @@ -1,183 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_mutex — self-tests for the Mutex sleeping lock. - * - * Coverage: - * - basic lock/unlock single-thread - * - try_lock when free and when held - * - mutual exclusion across kthreads (no lost increments) - * - FIFO wake order (A acquires, B/C queue; release lets B in then C) - */ - -#include <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 deleted file mode 100644 index 8e4ecb0..0000000 --- a/na/test/test_namei.cc +++ /dev/null @@ -1,173 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_namei.cc — Self-tests for pathname resolution (namei.cc) - */ - -#include <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 deleted file mode 100644 index ee0e0e2..0000000 --- a/na/test/test_page_cache.cc +++ /dev/null @@ -1,180 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_page_cache.cc — Self-tests for the page cache hash table - * - * Tests: insert, get, remove, duplicate insert, invalidate, - * get_or_alloc, null args. - */ - -#include <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 deleted file mode 100644 index e79eb1e..0000000 --- a/na/test/test_pid.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_pid.cc — Self-tests for PID allocator - * - * Tests: alloc, free, re-use, exhaustion guard, reserved PIDs. - */ - -#include <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 deleted file mode 100644 index ed375ed..0000000 --- a/na/test/test_pipe_devfs.cc +++ /dev/null @@ -1,420 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_pipe_devfs.cc — Self-tests for pipe, /dev/null, /dev/zero, fcntl - */ - -#include <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 deleted file mode 100644 index 3330866..0000000 --- a/na/test/test_pmm.cc +++ /dev/null @@ -1,283 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Physical Memory Manager (PMM) - * - * Tests: - * 1. Single page alloc/free round-trip - * 2. Page address alignment (4KB) - * 3. No duplicate pages from consecutive allocs - * 4. Free then re-alloc returns the same page (PCP cache) - * 5. Multi-page (order > 0) alloc/free - * 6. Bulk alloc stress test (alloc many, free all) - * 7. Reference counting (incref/decref) - * 8. page_to_pa / pa_to_page round-trip - */ - -#include <lume/selftest.h> -#include <lume/pmm.h> -#include <lume/addr.h> - -void selftest_pmm() -{ - st_begin("pmm: single page alloc/free"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->type == PageType::Allocated); - ST_ASSERT(page->order == 0); - - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % kPageSize, 0ULL); // 4KB aligned - - uint64 va = page_to_va(page).raw; - ST_ASSERT_EQ(va, pa_to_va(phys_addr(pa)).raw); - - pmm_free_page(page); - } - st_pass(); - - st_begin("pmm: no duplicate pages"); - { - Page* p1 = pmm_alloc_page(); - Page* p2 = pmm_alloc_page(); - ST_ASSERT(p1 != nullptr); - ST_ASSERT(p2 != nullptr); - ST_ASSERT_NE(p1, p2); - ST_ASSERT_NE(page_to_pa(p1).raw, page_to_pa(p2).raw); - pmm_free_page(p2); - pmm_free_page(p1); - } - st_pass(); - - st_begin("pmm: free then re-alloc (PCP)"); - { - Page* p1 = pmm_alloc_page(); - uint64 pa1 = page_to_pa(p1).raw; - pmm_free_page(p1); - - // PCP should return the most recently freed page - Page* p2 = pmm_alloc_page(); - uint64 pa2 = page_to_pa(p2).raw; - ST_ASSERT_EQ(pa1, pa2); - pmm_free_page(p2); - } - st_pass(); - - st_begin("pmm: multi-page alloc (order 2 = 4 pages)"); - { - Page* page = pmm_alloc_pages(2); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->order == 2); - - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % (kPageSize * 4), 0ULL); // Naturally aligned - - pmm_free_pages(page, 2); - } - st_pass(); - - st_begin("pmm: bulk alloc/free stress (256 pages)"); - { - constexpr int N = 256; - Page* pages[N]; - - // Allocate all - for (int i = 0; i < N; i++) { - pages[i] = pmm_alloc_page(); - ST_ASSERT(pages[i] != nullptr); - } - - // Verify no duplicates (spot check: consecutive pairs) - for (int i = 1; i < N; i++) { - ST_ASSERT_NE(pages[i], pages[i - 1]); - } - - // Free all - for (int i = 0; i < N; i++) { - pmm_free_page(pages[i]); - } - } - st_pass(); - - st_begin("pmm: page_to_pa / pa_to_page round-trip"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - uint64 pa = page_to_pa(page).raw; - Page* page2 = pa_to_page(phys_addr(pa)); - ST_ASSERT_EQ(page, page2); - pmm_free_page(page); - } - st_pass(); - - st_begin("pmm: refcount incref/decref"); - { - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - ST_ASSERT(page->refcount.load() == 1); - - page_incref(page); - ST_ASSERT(page->refcount.load() == 2); - - page_decref(page); // 2 -> 1, should NOT free - ST_ASSERT(page->refcount.load() == 1); - ST_ASSERT(page->type == PageType::Allocated); - - page_decref(page); // 1 -> 0, should free to pool - // After decref to 0, page is returned to pool. - // We can't safely check page->type because it might be reused. - } - st_pass(); - st_begin("pmm: pmm_alloc_pages natural alignment (order-3)"); - { - Page* page = pmm_alloc_pages(3); - ST_ASSERT(page != nullptr); - uint64 pa = page_to_pa(page).raw; - ST_ASSERT_EQ(pa % (kPageSize * 8), 0ULL); - pmm_free_pages(page, 3); - } - st_pass(); - - st_begin("pmm: page_decref to 0 and reuse (order-0)"); - { - Page* page = pmm_alloc_page(); - uint64 pa = page_to_pa(page).raw; - page_decref(page); // drops 1 -> 0, freeing it - - // Next alloc from PCP should give back the same page - Page* page2 = pmm_alloc_page(); - ST_ASSERT_EQ(page_to_pa(page2).raw, pa); - pmm_free_page(page2); - } - st_pass(); - - st_begin("pmm: page_decref to 0 on multi-page blocks (order-3)"); - { - Page* page = pmm_alloc_pages(3); - uint64 pa = page_to_pa(page).raw; - page_decref(page); // Should internally route to pmm_free_pages(page, 3) - - // Re-allocate order-3 and see if we get it back (buddy coalescing was respected) - Page* page2 = pmm_alloc_pages(3); - ST_ASSERT_EQ(page_to_pa(page2).raw, pa); - pmm_free_pages(page2, 3); - } - st_pass(); - - st_begin("pmm: buddy split and block coalescing correctness"); - { - // Force split: allocate order-1, free it, allocate two order-0 - Page* page_ord1 = pmm_alloc_pages(1); - uint64 pa1 = page_to_pa(page_ord1).raw; - pmm_free_pages(page_ord1, 1); - - Page* p0_a = pmm_alloc_page(); - Page* p0_b = pmm_alloc_page(); - - // Free them to trigger coalesce - pmm_free_page(p0_a); - pmm_free_page(p0_b); - - // Allocate order-1 again to verify successful coalescing - Page* page_ord1_again = pmm_alloc_pages(1); - ST_ASSERT_EQ(page_to_pa(page_ord1_again).raw, pa1); - pmm_free_pages(page_ord1_again, 1); - } - st_pass(); - - st_begin("pmm: mixed order cross allocation"); - { - Page* p0 = pmm_alloc_page(); - Page* p2 = pmm_alloc_pages(2); - Page* p0b = pmm_alloc_page(); - - ST_ASSERT(p0 != nullptr); - ST_ASSERT(p2 != nullptr); - ST_ASSERT(p0b != nullptr); - ST_ASSERT_NE(page_to_pa(p0).raw, page_to_pa(p2).raw); - - pmm_free_page(p0); - pmm_free_pages(p2, 2); - pmm_free_page(p0b); - } - st_pass(); - - st_begin("pmm: PCP drain/refill boundaries"); - { - // kPcpHighWatermark = 64. Allocating and freeing 65 blocks triggers drain. - Page* pages[65]; - for (int i=0; i<65; i++) pages[i] = pmm_alloc_page(); - for (int i=0; i<65; i++) pmm_free_page(pages[i]); - - // The system shouldn't crash and PCP logic should handle this. - Page* verify_page = pmm_alloc_page(); - ST_ASSERT(verify_page != nullptr); - pmm_free_page(verify_page); - } - st_pass(); - - st_begin("pmm: OOM exhaustion stability"); - { - Page* head = nullptr; - // First allocate large chunks to be fast - while (true) { - Page* page = pmm_alloc_pages(5); - if (!page) break; - uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw); - *ptr = reinterpret_cast<uint64>(head); - // encode order in the second word - *(ptr + 1) = 5; - head = page; - } - // Then allocate single frames until absolute zero - while (true) { - Page* page = pmm_alloc_page(); - if (!page) break; - uint64* ptr = reinterpret_cast<uint64*>(page_to_va(page).raw); - *ptr = reinterpret_cast<uint64>(head); - *(ptr + 1) = 0; - head = page; - } - - // We hit nullptr. System is completely exhausted. - ST_ASSERT(pmm_alloc_page() == nullptr); - - // Free everything - while (head) { - uint64* ptr = reinterpret_cast<uint64*>(page_to_va(head).raw); - Page* nxt = reinterpret_cast<Page*>(*ptr); - uint64 order = *(ptr + 1); - if (order == 5) pmm_free_pages(head, 5); - else pmm_free_page(head); - head = nxt; - } - } - st_pass(); - - st_begin("pmm: out of bounds order allocation gracefully fails"); - { - // kMaxOrder is 11, so passing 11 or higher should safely return nullptr. - Page* page = pmm_alloc_pages(11); - ST_ASSERT(page == nullptr); - page = pmm_alloc_pages(255); - ST_ASSERT(page == nullptr); - } - st_pass(); - - st_begin("pmm: pmm_get_mem_base / pmm_get_mem_size sanity"); - { - uint64 base = pmm_get_mem_base().raw; - uint64 size = pmm_get_mem_size(); - - // We no longer assert on QEMU specific memory address - // ST_ASSERT(base >= 0x80000000ULL); - ST_ASSERT(size > 0); - // Size must be page-aligned - ST_ASSERT_EQ(size % kPageSize, 0ULL); - // Base must be page-aligned - ST_ASSERT_EQ(base % kPageSize, 0ULL); - } - st_pass(); -} diff --git a/na/test/test_random.cc b/na/test/test_random.cc deleted file mode 100644 index d546410..0000000 --- a/na/test/test_random.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/random.h> -#include <lume/kprintf.h> - -void selftest_random() -{ - /* Test 1: Basic 64-bit generation */ - kprintf("[test] random: basic 64-bit generation ... "); - uint64 v1 = get_random_u64(); - uint64 v2 = get_random_u64(); - if (v1 == v2) { - kernel_panic("random: two consecutive generations were identical!"); - } - kprintf("PASS\n"); - - /* Test 2: Basic 32-bit generation */ - kprintf("[test] random: basic 32-bit generation ... "); - uint32 v3 = get_random_u32(); - uint32 v4 = get_random_u32(); - if (v3 == v4) { - kernel_panic("random: two consecutive 32-bit generations were identical!"); - } - kprintf("PASS\n"); - - /* Test 3: Byte generation */ - kprintf("[test] random: byte generation ... "); - uint8 buf1[16]; - uint8 buf2[16]; - get_random_bytes(buf1, sizeof(buf1)); - get_random_bytes(buf2, sizeof(buf2)); - - bool all_zero1 = true; - bool all_zero2 = true; - bool same = true; - - for (int i = 0; i < 16; i++) { - if (buf1[i] != 0) all_zero1 = false; - if (buf2[i] != 0) all_zero2 = false; - if (buf1[i] != buf2[i]) same = false; - } - - if (all_zero1 || all_zero2 || same) { - kernel_panic("random: byte generation statistical failure!"); - } - kprintf("PASS\n"); -} diff --git a/na/test/test_rbtree.cc b/na/test/test_rbtree.cc deleted file mode 100644 index 27f049a..0000000 --- a/na/test/test_rbtree.cc +++ /dev/null @@ -1,119 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/rbtree.h> - -struct TestNode { - int key; - struct rb_node rb; -}; - -static bool node_less(struct rb_node *node, const struct rb_node *parent) { - TestNode *n = rb_entry(node, TestNode, rb); - const TestNode *p = reinterpret_cast<const TestNode*>( - reinterpret_cast<const char*>(parent) - __builtin_offsetof(TestNode, rb)); - return n->key < p->key; -} - -static int node_cmp(const void *key, const struct rb_node *node) { - int k = *static_cast<const int*>(key); - const TestNode *n = reinterpret_cast<const TestNode*>( - reinterpret_cast<const char*>(node) - __builtin_offsetof(TestNode, rb)); - if (k < n->key) return -1; - if (k > n->key) return 1; - return 0; -} - -void selftest_rbtree() { - st_begin("rbtree: basic insert, find, erase"); - { - struct rb_root tree = RB_ROOT; - TestNode n1{10, {}}, n2{20, {}}, n3{5, {}}; - - rb_add(&n1.rb, &tree, node_less); - rb_add(&n2.rb, &tree, node_less); - rb_add(&n3.rb, &tree, node_less); - - int search_key = 20; - struct rb_node *found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == &n2.rb); - - search_key = 99; - found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == nullptr); - - rb_erase(&n2.rb, &tree); - // Verify erased - search_key = 20; - found = rb_find(&search_key, &tree, node_cmp); - ST_ASSERT(found == nullptr); - } - st_pass(); - - st_begin("rbtree: ordered iteration"); - { - struct rb_root tree = RB_ROOT; - TestNode nodes[5] = { {50, {}}, {10, {}}, {40, {}}, {20, {}}, {30, {}} }; - for (int i=0; i<5; i++) { - rb_add(&nodes[i].rb, &tree, node_less); - } - - int expected[] = {10, 20, 30, 40, 50}; - int idx = 0; - struct rb_node *node = rb_first(&tree); - while (node) { - TestNode *n = rb_entry(node, TestNode, rb); - ST_ASSERT_EQ(n->key, expected[idx]); - idx++; - node = rb_next(node); - } - ST_ASSERT_EQ(idx, 5); - } - st_pass(); - - st_begin("rbtree: large scale insert/erase balance"); - { - struct rb_root tree = RB_ROOT; - constexpr int N = 200; - static TestNode nodes[N]; // Static to avoid blowing up the BSP stack - for (int i=0; i<N; i++) { - nodes[i].key = (i * 17) % N; // Pseudo-random insertions - rb_add(&nodes[i].rb, &tree, node_less); - } - - int c = 0; - for (struct rb_node *n = rb_first(&tree); n; n = rb_next(n)) c++; - ST_ASSERT_EQ(c, N); - - for (int i=0; i<N; i+=2) { - rb_erase(&nodes[i].rb, &tree); - } - c = 0; - for (struct rb_node *n = rb_first(&tree); n; n = rb_next(n)) c++; - ST_ASSERT_EQ(c, N / 2); - } - st_pass(); - - st_begin("rbtree: empty tree operations"); - { - struct rb_root tree = RB_ROOT; - ST_ASSERT(RB_EMPTY_ROOT(&tree)); - ST_ASSERT(rb_first(&tree) == nullptr); - ST_ASSERT(rb_last(&tree) == nullptr); - } - st_pass(); - - st_begin("rbtree: rb_erase_init after effects"); - { - struct rb_root tree = RB_ROOT; - TestNode n1{100, {}}; - rb_add(&n1.rb, &tree, node_less); - ST_ASSERT(!RB_EMPTY_NODE(&n1.rb)); - - rb_erase_init(&n1.rb, &tree); - ST_ASSERT(RB_EMPTY_NODE(&n1.rb)); - ST_ASSERT(RB_EMPTY_ROOT(&tree)); - } - st_pass(); -} diff --git a/na/test/test_sched.cc b/na/test/test_sched.cc deleted file mode 100644 index d90c30a..0000000 --- a/na/test/test_sched.cc +++ /dev/null @@ -1,257 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot self-tests for the Scheduler subsystem. - * - * Tests RunQueue bitmap operations, priority selection, - * round-robin ordering, current_task(), and time slice policy. - */ - -#include <lume/types.h> -#include <lume/task.h> -#include <lume/sched.h> -#include <lume/sched_class.h> -#include <lume/config.h> -#include <lume/selftest.h> -#include <lume/pmm.h> -#include <lume/addr.h> -#include <lume/new.h> -#include <kernel/sched_internal.h> - -/* ======================================================================== - * Helper: create a minimal test TCB (no real kernel stack, just metadata) - * ======================================================================== */ - -static TaskControlBlock g_test_tcbs[8]; - -static TaskControlBlock* make_test_tcb(int index, const char* name, - uint8 priority, uint8 cpu) -{ - TaskControlBlock* t = &g_test_tcbs[index]; - t->context = {}; - t->wait_chld.init("test_wq"); - t->name = name; - t->priority = priority; - t->cpu_id = cpu; - t->pid = 100 + index; - t->tgid = 100 + index; - t->state = TaskState::Runnable; - t->time_slice = priority_to_timeslice(priority); - t->run_link.init(); - t->all_link.init(); - t->children.init(); - t->sibling.init(); - t->mm = nullptr; - t->trapframe = nullptr; - t->kstack_pa = 0; - t->kstack_va = 0; - t->flags = TF_KTHREAD; - t->sched_class = &g_sched_class_o1; - t->sched_entity = &t->run_link; - return t; -} - -/* ======================================================================== - * Test cases - * ======================================================================== */ - -static bool test_current_task() -{ - /* After sched_init, current_task() should be the BSP boot TCB */ - TaskControlBlock* curr = current_task(); - return curr != nullptr && curr->state == TaskState::Running; -} - -static bool test_priority_to_timeslice() -{ - /* Higher priority (lower number) should get longer time slice */ - int32 ts_high = priority_to_timeslice(0); - int32 ts_mid = priority_to_timeslice(15); - int32 ts_low = priority_to_timeslice(31); - - return ts_high > ts_mid && ts_mid > ts_low && ts_low >= 1; -} - -static bool test_o1_pick_next_priority() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t_lo = make_test_tcb(0, "test_p5", 5, 0); - TaskControlBlock* t_hi = make_test_tcb(1, "test_p2", 2, 0); - - g_sched_class_o1.enqueue_task(&rq, t_lo); - g_sched_class_o1.enqueue_task(&rq, t_hi); - - TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); - return next == t_hi; -} - -static bool test_o1_fifo_order() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t0 = make_test_tcb(0, "test_q0", 10, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_q1", 10, 0); - TaskControlBlock* t2 = make_test_tcb(2, "test_q2", 10, 0); - - g_sched_class_o1.enqueue_task(&rq, t0); - g_sched_class_o1.enqueue_task(&rq, t1); - g_sched_class_o1.enqueue_task(&rq, t2); - - TaskControlBlock* next = g_sched_class_o1.pick_next_task(&rq); - if (next != t0) return false; - g_sched_class_o1.dequeue_task(&rq, t0); - - next = g_sched_class_o1.pick_next_task(&rq); - if (next != t1) return false; - g_sched_class_o1.dequeue_task(&rq, t1); - - next = g_sched_class_o1.pick_next_task(&rq); - if (next != t2) return false; - - return true; -} - -static bool test_o1_nr_running() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - TaskControlBlock* t0 = make_test_tcb(0, "test_n0", 8, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_n1", 9, 0); - - if (rq.nr_running() != 0 || rq.o1().nr_running() != 0) return false; - - g_sched_class_o1.enqueue_task(&rq, t0); - if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; - - g_sched_class_o1.enqueue_task(&rq, t1); - if (rq.nr_running() != 2 || rq.o1().nr_running() != 2) return false; - - g_sched_class_o1.dequeue_task(&rq, t0); - if (rq.nr_running() != 1 || rq.o1().nr_running() != 1) return false; - - g_sched_class_o1.dequeue_task(&rq, t1); - return rq.nr_running() == 0 && rq.o1().nr_running() == 0; -} - -static bool test_o1_pick_next_empty() -{ - RunQueue rq; - rq.init(0); - g_sched_class_o1.init_rq(&rq); - - return g_sched_class_o1.pick_next_task(&rq) == nullptr; -} - -static bool test_priority_to_timeslice_bounds() -{ - /* Boundary values */ - int32 ts0 = priority_to_timeslice(0); - int32 ts31 = priority_to_timeslice(31); - - return ts0 == 20 && ts31 >= 1; -} - -static bool test_enqueue_dequeue() -{ - /* Create a test task and enqueue/dequeue it */ - TaskControlBlock* t = make_test_tcb(0, "test_ed", 10, 0); - - sched_enqueue(t); - if (t->state != TaskState::Runnable) return false; - - sched_dequeue(t); - /* After dequeue, run_link should be self-pointing */ - return t->run_link.next == &t->run_link; -} - -static bool test_multiple_enqueue_dequeue() -{ - /* Enqueue 3 tasks at different priorities, dequeue all */ - TaskControlBlock* t0 = make_test_tcb(0, "test_m0", 5, 0); - TaskControlBlock* t1 = make_test_tcb(1, "test_m1", 10, 0); - TaskControlBlock* t2 = make_test_tcb(2, "test_m2", 20, 0); - - sched_enqueue(t0); - sched_enqueue(t1); - sched_enqueue(t2); - - sched_dequeue(t0); - sched_dequeue(t1); - sched_dequeue(t2); - - return t0->run_link.is_empty() && - t1->run_link.is_empty() && - t2->run_link.is_empty(); -} - -static bool test_current_task_name() -{ - TaskControlBlock* curr = current_task(); - if (!curr || !curr->name) return false; - - /* BSP boot TCB should be named "kernel_main" */ - const char* expected = "kernel_main"; - const char* actual = curr->name; - while (*expected && *actual) { - if (*expected != *actual) return false; - expected++; - actual++; - } - return *expected == *actual; -} - -/* ======================================================================== - * Test runner - * ======================================================================== */ - -void selftest_sched() -{ - st_begin("sched: current_task is valid after init"); - ST_ASSERT(test_current_task()); - st_pass(); - - st_begin("sched: current_task name is kernel_main"); - ST_ASSERT(test_current_task_name()); - st_pass(); - - st_begin("sched: priority_to_timeslice ordering"); - ST_ASSERT(test_priority_to_timeslice()); - st_pass(); - - st_begin("sched: priority_to_timeslice bounds"); - ST_ASSERT(test_priority_to_timeslice_bounds()); - st_pass(); - - st_begin("sched: enqueue/dequeue round-trip"); - ST_ASSERT(test_enqueue_dequeue()); - st_pass(); - - st_begin("sched: multiple enqueue/dequeue"); - ST_ASSERT(test_multiple_enqueue_dequeue()); - st_pass(); - - st_begin("sched: o1 pick_next priority"); - ST_ASSERT(test_o1_pick_next_priority()); - st_pass(); - - st_begin("sched: o1 FIFO order"); - ST_ASSERT(test_o1_fifo_order()); - st_pass(); - - st_begin("sched: o1 nr_running accounting"); - ST_ASSERT(test_o1_nr_running()); - st_pass(); - - st_begin("sched: o1 pick_next empty"); - ST_ASSERT(test_o1_pick_next_empty()); - st_pass(); -} - diff --git a/na/test/test_signal.cc b/na/test/test_signal.cc deleted file mode 100644 index 360b0e6..0000000 --- a/na/test/test_signal.cc +++ /dev/null @@ -1,193 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_signal.cc — Self-tests for signal infrastructure - * - * Tests the kernel-internal signal APIs directly (not via syscalls, - * since syscalls use copy_to_user which rejects kernel-space buffers). - */ - -#include <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 deleted file mode 100644 index 8a448b4..0000000 --- a/na/test/test_slab.cc +++ /dev/null @@ -1,255 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Slab Allocator (kmalloc/kfree) - * - * Tests: - * 1. Small allocation (8 bytes) and free - * 2. Multiple size classes - * 3. No aliasing between consecutive allocs - * 4. Alignment check (minimum 8-byte aligned) - * 5. Bulk alloc/free stress - * 6. operator new / operator delete - * 7. Large allocation (> 2048, falls back to PMM) - * 8. Zero-size and null-free edge cases - */ - -#include <lume/selftest.h> -#include <lume/new.h> -#include <lume/pmm.h> -#include <lume/addr.h> - -/* Forward declarations — defined in slab.cc */ -void* kmalloc(uint32 size); -void kfree(void* ptr); - -void selftest_slab() -{ - st_begin("slab: small alloc (8 bytes)"); - { - void* p = kmalloc(8); - ST_ASSERT(p != nullptr); - // Write and read back to verify the memory is usable - *static_cast<uint64*>(p) = 0xDEADBEEFCAFEBABEULL; - ST_ASSERT_EQ(*static_cast<uint64*>(p), 0xDEADBEEFCAFEBABEULL); - kfree(p); - } - st_pass(); - - st_begin("slab: multiple size classes"); - { - uint32 sizes[] = {8, 16, 32, 64, 128, 256, 512, 1024, 2048}; - void* ptrs[9]; - for (int i = 0; i < 9; i++) { - ptrs[i] = kmalloc(sizes[i]); - ST_ASSERT(ptrs[i] != nullptr); - // Write a marker - *static_cast<uint8*>(ptrs[i]) = static_cast<uint8>(i); - } - // Verify markers (no cross-contamination) - for (int i = 0; i < 9; i++) { - ST_ASSERT_EQ(*static_cast<uint8*>(ptrs[i]), static_cast<uint8>(i)); - } - for (int i = 0; i < 9; i++) { - kfree(ptrs[i]); - } - } - st_pass(); - - st_begin("slab: no aliasing"); - { - void* a = kmalloc(32); - void* b = kmalloc(32); - ST_ASSERT(a != nullptr); - ST_ASSERT(b != nullptr); - ST_ASSERT_NE(a, b); - kfree(b); - kfree(a); - } - st_pass(); - - st_begin("slab: alignment (8-byte minimum)"); - { - for (int i = 0; i < 16; i++) { - void* p = kmalloc(7); // odd size, should still be 8-aligned - ST_ASSERT(p != nullptr); - ST_ASSERT_EQ(reinterpret_cast<uint64>(p) % 8, 0ULL); - kfree(p); - } - } - st_pass(); - - st_begin("slab: bulk alloc/free stress (128 x 64B)"); - { - constexpr int N = 128; - void* ptrs[N]; - for (int i = 0; i < N; i++) { - ptrs[i] = kmalloc(64); - ST_ASSERT(ptrs[i] != nullptr); - } - // Free in reverse order - for (int i = N - 1; i >= 0; i--) { - kfree(ptrs[i]); - } - } - st_pass(); - - st_begin("slab: operator new/delete"); - { - struct TestObj { - uint64 a; - uint64 b; - }; - TestObj* obj = new TestObj{42, 99}; - ST_ASSERT(obj != nullptr); - ST_ASSERT_EQ(obj->a, 42ULL); - ST_ASSERT_EQ(obj->b, 99ULL); - delete obj; - } - st_pass(); - - st_begin("slab: large alloc (4096 bytes, PMM fallback)"); - { - void* p = kmalloc(4096); - ST_ASSERT(p != nullptr); - ST_ASSERT_EQ(reinterpret_cast<uint64>(p) % kPageSize, 0ULL); - kfree(p); - } - st_pass(); - - st_begin("slab: edge cases (zero size, null free)"); - { - void* p = kmalloc(0); - ST_ASSERT(p == nullptr); - kfree(nullptr); // should not crash - } - st_pass(); - st_begin("slab: LIFO freelist reallocation (same address)"); - { - void* a = kmalloc(32); - kfree(a); - void* b = kmalloc(32); - ST_ASSERT_EQ(a, b); - kfree(b); - } - st_pass(); - - st_begin("slab: boundary conditions (2048 bytes size class)"); - { - // 2048 bytes should still use slab (2 objects per page) - // We allocate 3 to guarantee at least two fall in the exact same slab page - void* p1 = kmalloc(2048); - void* p2 = kmalloc(2048); - void* p3 = kmalloc(2048); - - auto diff = [](void* a, void* b) -> uint64 { - uint64 ua = reinterpret_cast<uint64>(a); - uint64 ub = reinterpret_cast<uint64>(b); - return ua > ub ? ua - ub : ub - ua; - }; - - // At least one adjacent pair MUST be exactly separated by 2048 bytes - bool found_2048 = (diff(p1, p2) == 2048ULL) || (diff(p2, p3) == 2048ULL); - ST_ASSERT(found_2048); - - kfree(p1); - kfree(p2); - kfree(p3); - } - st_pass(); - - st_begin("slab: cross-page allocation (> 1 page equivalent objects)"); - { - // Allocating more than one page worth of 32-byte objects (4096 / 32 = 128) - constexpr int NUM = 150; - void* ptrs[NUM]; - for (int i=0; i<NUM; i++) { - ptrs[i] = kmalloc(32); - ST_ASSERT(ptrs[i] != nullptr); - } - for (int i=0; i<NUM; i++) { - kfree(ptrs[i]); - } - } - st_pass(); - - st_begin("slab: empty page reclamation (verifiable by page availability)"); - { - // Allocate 128-byte objects until we hit the start of a completely new page. - void* base_ptr = nullptr; - void* drain[64]; - int drain_count = 0; - - while (true) { - void* p = kmalloc(128); - // Because 128 divides 4096 evenly, a new page starts at an exact multiple of kPageSize. - if (reinterpret_cast<uint64>(p) % kPageSize == 0) { - base_ptr = p; - break; - } - drain[drain_count++] = p; - } - - // base_ptr is the very first object in a fresh slab page! - // The page holds exactly 32 objects (4096 / 128 = 32). We already hold 1 (base_ptr). - void* ptrs[32]; - ptrs[0] = base_ptr; - for (int i = 1; i < 32; i++) { - ptrs[i] = kmalloc(128); - } - - // At this point, we completely own all 32 objects of this slab page. - uint64 page_pa = va_to_pa(virt_addr(reinterpret_cast<uint64>(base_ptr))).raw; - Page* page = pa_to_page(phys_addr(page_pa)); - - ST_ASSERT(page->type == PageType::Slab); - ST_ASSERT(page->slab.obj_count == 32); - - // Free all items in this isolated slab page - for (int i = 0; i < 32; i++) { - kfree(ptrs[i]); - } - - // The page MUST have been returned to the PMM (either global Free or Per-CPU Cached). - ST_ASSERT(page->type == PageType::Free || page->type == PageType::PcpCached); - - // Clean up the drain array - for (int i = 0; i < drain_count; i++) { - kfree(drain[i]); - } - } - st_pass(); - - st_begin("slab: continuous operator new/delete memory leak loop"); - { - struct BigObj { uint8 data[512]; }; - BigObj* ptr = nullptr; - // 10K allocations to ensure no memory leak causes OOM - for (int i = 0; i < 10000; i++) { - ptr = new BigObj; - ST_ASSERT(ptr != nullptr); - delete ptr; - } - } - st_pass(); - - st_begin("slab: allocation pressure (mixed sizes)"); - { - constexpr int MAX_ITEMS = 100; - void* ptrs[MAX_ITEMS]; - uint32 sizes[MAX_ITEMS]; - - for (int i = 0; i < MAX_ITEMS; i++) { - sizes[i] = (i * 17) % 2000 + 8; // Pseudo-random sizes between 8 and 2008 - ptrs[i] = kmalloc(sizes[i]); - ST_ASSERT(ptrs[i] != nullptr); - ST_ASSERT(reinterpret_cast<uintptr>(ptrs[i]) % 8 == 0); - } - - for (int i = 0; i < MAX_ITEMS; i++) { - kfree(ptrs[i]); - } - } - st_pass(); -} diff --git a/na/test/test_smp.cc b/na/test/test_smp.cc deleted file mode 100644 index f934526..0000000 --- a/na/test/test_smp.cc +++ /dev/null @@ -1,136 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_smp.cc — Post-SMP boot tests - * - * These tests run after all APs are online. They verify: - * 1. IPI delivery — BSP sends IPI to each AP, AP increments counter. - * 2. Task migration — a task moves from one runqueue to another. - * 3. TLB shootdown IPI — cross-CPU flush. - * - * Called from the init thread (PID 1) context, NOT from the pre-SMP - * self-test suite. - */ - -#include <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_spinlock.cc b/na/test/test_spinlock.cc deleted file mode 100644 index be794a0..0000000 --- a/na/test/test_spinlock.cc +++ /dev/null @@ -1,135 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: Spinlock - * - * Tests: - * 1. Basic acquire/release - * 2. LockGuard RAII acquire/release - * 3. Nested locking (two different locks) - * 4. Interrupt state preservation across lock/unlock - * 5. is_held_by_current_cpu correctness - * - * Note: We cannot test multi-core contention in BSP-only self-test. - * That requires AP participation (future Phase 2 test). - */ - -#include <lume/selftest.h> -#include <kernel/sync/spinlock.h> -#include <kernel/sync/cpu_state.h> -#include <arch/cpu.h> - -void selftest_spinlock() -{ - st_begin("spinlock: basic acquire/release"); - { - Spinlock lock; - lock.init("test_basic"); - - ST_ASSERT(!lock.is_held_by_current_cpu()); - - lock.acquire(); - ST_ASSERT(lock.is_held_by_current_cpu()); - - lock.release(); - ST_ASSERT(!lock.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: LockGuard RAII"); - { - Spinlock lock; - lock.init("test_guard"); - - { - LockGuard guard(lock); - ST_ASSERT(lock.is_held_by_current_cpu()); - } - // After scope exit, lock should be released - ST_ASSERT(!lock.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: nested locking (two locks)"); - { - Spinlock lock_a, lock_b; - lock_a.init("test_nest_a"); - lock_b.init("test_nest_b"); - - lock_a.acquire(); - ST_ASSERT(lock_a.is_held_by_current_cpu()); - - lock_b.acquire(); - ST_ASSERT(lock_b.is_held_by_current_cpu()); - - lock_b.release(); - ST_ASSERT(!lock_b.is_held_by_current_cpu()); - ST_ASSERT(lock_a.is_held_by_current_cpu()); // A still held - - lock_a.release(); - ST_ASSERT(!lock_a.is_held_by_current_cpu()); - } - st_pass(); - - st_begin("spinlock: interrupt state preservation"); - { - Spinlock lock; - lock.init("test_intr"); - - // Interrupts are currently off (BSP init period), but let's - // verify the lock doesn't corrupt the state. - bool before = arch::cpu::intr_enabled(); - - lock.acquire(); - // Inside lock, interrupts must be disabled - ST_ASSERT(!arch::cpu::intr_enabled()); - lock.release(); - - // After release, interrupt state should be restored - ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); - } - st_pass(); - st_begin("spinlock: deep nesting tracking (> 2)"); - { - Spinlock l1, l2, l3; - l1.init("l1"); - l2.init("l2"); - l3.init("l3"); - - bool before = arch::cpu::intr_enabled(); - - l1.acquire(); - l2.acquire(); - l3.acquire(); - - ST_ASSERT(!arch::cpu::intr_enabled()); - - l3.release(); - ST_ASSERT(!arch::cpu::intr_enabled()); - - l2.release(); - ST_ASSERT(!arch::cpu::intr_enabled()); - - l1.release(); - ST_ASSERT_EQ(arch::cpu::intr_enabled(), before); - } - st_pass(); - - st_begin("spinlock: interrupt leak detection logic (lock_depth metadata)"); - { - Spinlock l; - l.init("l"); - - auto& state = sync::g_cpu_sync_states[arch::cpu::id()]; - int initial_depth = state.lock_depth; - - l.acquire(); - // The lock depth should strictly increase - ST_ASSERT_EQ(state.lock_depth, initial_depth + 1); - l.release(); - - ST_ASSERT_EQ(state.lock_depth, initial_depth); - } - st_pass(); -} diff --git a/na/test/test_sys_fs_extra.cc b/na/test/test_sys_fs_extra.cc deleted file mode 100644 index 705ce63..0000000 --- a/na/test/test_sys_fs_extra.cc +++ /dev/null @@ -1,238 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_sys_fs_extra.cc — Self-tests for additional filesystem syscalls - * - * Tests: dup3, chdir/getcwd, writev, ioctl (stub), unlinkat (stub). - * Complements test_vfs_io.cc which covers the core read/write/open/close. - */ - -#include <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_mm.cc b/na/test/test_sys_mm.cc deleted file mode 100644 index 2c1a7dc..0000000 --- a/na/test/test_sys_mm.cc +++ /dev/null @@ -1,178 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/task.h> -#include <lume/vmm.h> -#include <lume/mman.h> -#include <lume/errno.h> - -/* sys_mm.cc APIs */ -int64 sys_brk(uint64 new_brk, uint64, uint64, uint64, uint64, uint64); -int64 sys_mmap(uint64 addr, uint64 length, uint64 prot, - uint64 flags, uint64 fd, uint64 offset); -int64 sys_munmap(uint64 addr, uint64 length, uint64, uint64, uint64, uint64); -int64 sys_mprotect(uint64 addr, uint64 len, uint64 prot, - uint64, uint64, uint64); - -void selftest_sys_mm() -{ - st_begin("sys_mm: mmap invalid arguments"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_mmap(0, 0, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 1), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_SHARED, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mmap(0x123, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), -EINVAL); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mmap/munmap/mprotect basic flow"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - int64 addr = sys_mmap(0, 0x3000, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - ST_ASSERT(addr > 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), 0); - - int64 mp = sys_mprotect(addr + kPageSize, kPageSize, PROT_READ, 0, 0, 0); - ST_ASSERT_EQ(mp, 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr + kPageSize, VmFaultCause::Write), -EFAULT); - - int64 mu = sys_munmap(addr, 0x3000, 0, 0, 0, 0); - ST_ASSERT_EQ(mu, 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, addr, VmFaultCause::Read), -EFAULT); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: MAP_FIXED replaces existing mapping"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - uint64 base = 0x600000; - ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast<int64>(base)); - - ST_ASSERT_EQ(sys_mmap(base, 0x1000, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast<int64>(base)); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, base, VmFaultCause::Write), 0); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mmap hint fallback on overlap"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - uint64 base = 0x700000; - ST_ASSERT_EQ(sys_mmap(base, 0x2000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0), - static_cast<int64>(base)); - - int64 hint = sys_mmap(base, 0x1000, PROT_READ, - MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); - ST_ASSERT(hint > 0); - ST_ASSERT(hint != static_cast<int64>(base)); - ST_ASSERT_EQ(vmm_handle_page_fault(space, hint, VmFaultCause::Read), 0); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: brk growth and shrink"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - int64 brk0 = sys_brk(0x400000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk0, 0x400000); - - int64 brk1 = sys_brk(0x402000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk1, 0x402000); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x400000, VmFaultCause::Read), 0); - - int64 brk2 = sys_brk(0x400000, 0, 0, 0, 0, 0); - ST_ASSERT_EQ(brk2, 0x400000); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x401000, VmFaultCause::Read), -EFAULT); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: brk invalid shrink below start"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_brk(0x500000, 0, 0, 0, 0, 0), 0x500000); - ST_ASSERT_EQ(sys_brk(0x4FF000, 0, 0, 0, 0, 0), -EINVAL); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); - - st_begin("sys_mm: mprotect invalid range"); - { - TaskControlBlock* curr = current_task(); - VmSpace* old_mm = curr->mm; - - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - curr->mm = space; - - ST_ASSERT_EQ(sys_mmap(0, 0x1000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0) > 0, true); - ST_ASSERT_EQ(sys_mprotect(0x123, 0x1000, PROT_READ, 0, 0, 0), -EINVAL); - ST_ASSERT_EQ(sys_mprotect(0x900000, 0x1000, PROT_READ, 0, 0, 0), -ENOMEM); - - curr->mm = old_mm; - space->destroy(); - } - st_pass(); -} diff --git a/na/test/test_sys_process.cc b/na/test/test_sys_process.cc deleted file mode 100644 index 686bed6..0000000 --- a/na/test/test_sys_process.cc +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_sys_process.cc — Self-tests for process-control syscalls - * - * Tests: getpid, gettid, getppid, getuid, getgid, set_tid_address, - * sched_yield, uname. - */ - -#include <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_trap.cc b/na/test/test_trap.cc deleted file mode 100644 index c91948a..0000000 --- a/na/test/test_trap.cc +++ /dev/null @@ -1,197 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot self-tests for the Trap/IRQ subsystem. - * - * Tests MD→MI translation, IrqManager register/dispatch/unregister, - * and stvec installation verification. - */ - -#include <lume/types.h> -#include <lume/selftest.h> -#include <lume/trap.h> -#include <lume/irq.h> -#include <lume/errno.h> -#include <lume/console.h> -#include <arch/trap.h> - -TrapCause trap_translate_scause(uint64 scause); - -/* ======================================================================== - * IrqManager test helpers - * ======================================================================== */ - -static volatile uint32 g_test_irq_fired = 0; -static volatile void* g_test_irq_arg = nullptr; - -static void test_irq_handler(uint32 irq, void* arg) -{ - g_test_irq_fired = irq; - g_test_irq_arg = arg; -} - -/* ======================================================================== - * Test cases - * ======================================================================== */ - -static bool test_stvec_installed() { - uint64 vec = arch::trap::get_vector(); - return vec != 0; -} - -static bool test_scause_exception_translation() { - /* Verify all exception causes map correctly */ - bool ok = true; - ok = ok && (trap_translate_scause(EXC_LOAD_PAGE_FAULT) == TrapCause::PageFaultLoad); - ok = ok && (trap_translate_scause(EXC_STORE_PAGE_FAULT) == TrapCause::PageFaultStore); - ok = ok && (trap_translate_scause(EXC_INST_PAGE_FAULT) == TrapCause::PageFaultExec); - ok = ok && (trap_translate_scause(EXC_ECALL_FROM_U) == TrapCause::Syscall); - ok = ok && (trap_translate_scause(EXC_ILLEGAL_INST) == TrapCause::IllegalInst); - ok = ok && (trap_translate_scause(EXC_BREAKPOINT) == TrapCause::Breakpoint); - ok = ok && (trap_translate_scause(EXC_LOAD_ACCESS_FAULT) == TrapCause::AccessFault); - ok = ok && (trap_translate_scause(EXC_INST_MISALIGNED) == TrapCause::AlignFault); - return ok; -} - -static bool test_scause_interrupt_translation() { - bool ok = true; - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_TIMER) == TrapCause::IrqTimer); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_SOFTWARE) == TrapCause::IrqSoftware); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | IRQ_S_EXTERNAL) == TrapCause::IrqExternal); - ok = ok && (trap_translate_scause(SCAUSE_INTERRUPT | 99) == TrapCause::Unknown); - return ok; -} - -static bool test_scause_unknown() { - /* ecall from S-mode should map to Unknown (we only handle U-mode ecall) */ - return trap_translate_scause(EXC_ECALL_FROM_S) == TrapCause::Unknown; -} - -static bool test_irqmanager_register_dispatch() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - g_test_irq_arg = nullptr; - - int dummy_arg = 42; - int ret = mgr.register_handler(10, test_irq_handler, &dummy_arg); - if (ret != 0) return false; - - /* Dispatch IRQ 10 — handler should be called */ - mgr.dispatch(10); - - if (g_test_irq_fired != 10) return false; - if (g_test_irq_arg != &dummy_arg) return false; - - return true; -} - -static bool test_irqmanager_unregister() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - - mgr.register_handler(5, test_irq_handler, nullptr); - mgr.unregister_handler(5); - - /* Dispatch after unregister — handler should NOT be called */ - mgr.dispatch(5); - - return g_test_irq_fired == 0; -} - -static bool test_irqmanager_double_register() { - IrqManager mgr; - mgr.init(); - - int ret1 = mgr.register_handler(7, test_irq_handler, nullptr); - int ret2 = mgr.register_handler(7, test_irq_handler, nullptr); - - return ret1 == 0 && ret2 == -EBUSY; -} - -static bool test_irqmanager_invalid_irq() { - IrqManager mgr; - mgr.init(); - - int ret = mgr.register_handler(999, test_irq_handler, nullptr); - return ret == -EINVAL; -} - -static bool test_irqmanager_has_handler() { - IrqManager mgr; - mgr.init(); - - if (mgr.has_handler(10)) return false; /* not yet registered */ - - mgr.register_handler(10, test_irq_handler, nullptr); - if (!mgr.has_handler(10)) return false; /* now registered */ - - mgr.unregister_handler(10); - if (mgr.has_handler(10)) return false; /* unregistered */ - - return true; -} - -static bool test_irqmanager_spurious_dispatch() { - IrqManager mgr; - mgr.init(); - - g_test_irq_fired = 0; - - /* Dispatch an unregistered IRQ — should not crash or fire */ - mgr.dispatch(0); - mgr.dispatch(63); - - return g_test_irq_fired == 0; -} - -/* ======================================================================== - * Test runner - * ======================================================================== */ - -void selftest_trap() -{ - st_begin("trap: stvec is installed"); - ST_ASSERT(test_stvec_installed()); - st_pass(); - - st_begin("trap: scause exception translation"); - ST_ASSERT(test_scause_exception_translation()); - st_pass(); - - st_begin("trap: scause interrupt translation"); - ST_ASSERT(test_scause_interrupt_translation()); - st_pass(); - - st_begin("trap: scause unknown (ecall from S)"); - ST_ASSERT(test_scause_unknown()); - st_pass(); - - st_begin("trap: IrqManager register/dispatch"); - ST_ASSERT(test_irqmanager_register_dispatch()); - st_pass(); - - st_begin("trap: IrqManager unregister"); - ST_ASSERT(test_irqmanager_unregister()); - st_pass(); - - st_begin("trap: IrqManager double register"); - ST_ASSERT(test_irqmanager_double_register()); - st_pass(); - - st_begin("trap: IrqManager invalid IRQ"); - ST_ASSERT(test_irqmanager_invalid_irq()); - st_pass(); - - st_begin("trap: IrqManager has_handler"); - ST_ASSERT(test_irqmanager_has_handler()); - st_pass(); - - st_begin("trap: IrqManager spurious dispatch"); - ST_ASSERT(test_irqmanager_spurious_dispatch()); - st_pass(); -} diff --git a/na/test/test_uaccess.cc b/na/test/test_uaccess.cc deleted file mode 100644 index c5a8c6c..0000000 --- a/na/test/test_uaccess.cc +++ /dev/null @@ -1,97 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/uaccess.h> -#include <lume/errno.h> - -#include <lume/vmm.h> -#include <lume/pmm.h> -#include <lume/tlb.h> -#include <lume/mm_layout.h> -#include <arch/pmap.h> - -void selftest_uaccess() -{ - /* To test valid user addresses (< kKernelVaBase), we need a writable page mapped there. - * We cannot use VmSpace::create() because it does not yet inherit kernel mappings, - * so switching to it hangs. We also cannot map it with the U bit because S-mode - * would need the SUM bit enabled. - * So we manually allocate a physical page and map it into the CURRENT page table - * at 0x100000 without the U bit, just for this test. - */ - uint64 mock_user_addr = 0x100000; - - uint64 satp; - asm volatile("csrr %0, satp" : "=r"(satp)); - uint64 root_pa = (satp & 0xFFFFFFFFFFFULL) << 12; - - Page* page = pmm_alloc_page(); - ST_ASSERT(page != nullptr); - uint64 paddr = page_to_pa(page).raw; - - pmap::map(root_pa, mock_user_addr, paddr, pmap::PTE_R | pmap::PTE_W | pmap::PTE_V | pmap::PTE_A | pmap::PTE_D); - - /* Flush TLB to see the new mapping */ - tlb_shootdown_all(); - - st_begin("uaccess: copy_to_user valid range"); - char kernel_buf[32] = "Hello User Space!"; - int ret = copy_to_user(mock_user_addr, kernel_buf, 18); - ST_ASSERT_EQ(ret, 0); - st_pass(); - - st_begin("uaccess: copy_from_user valid range"); - char read_buf[32] = {0}; - /* We wrote "Hello User Space!" to mock_user_addr previously */ - ret = copy_from_user(read_buf, mock_user_addr, 18); - ST_ASSERT_EQ(ret, 0); - ST_ASSERT(read_buf[0] == 'H'); - ST_ASSERT(read_buf[1] == 'e'); - ST_ASSERT(read_buf[6] == 'U'); - st_pass(); - - st_begin("uaccess: strncpy_from_user valid range"); - char str_buf[32] = {0}; - int64 len = strncpy_from_user(str_buf, mock_user_addr, 32); - ST_ASSERT_EQ(len, 17); /* "Hello User Space!" is 17 chars long */ - ST_ASSERT(str_buf[0] == 'H'); - st_pass(); - - st_begin("uaccess: strncpy_from_user max_len truncation"); - char trunc_buf[32] = {0}; - len = strncpy_from_user(trunc_buf, mock_user_addr, 6); - ST_ASSERT_EQ(len, 5); /* Will copy 5 chars and NUL terminate */ - ST_ASSERT(trunc_buf[4] == 'o'); - ST_ASSERT(trunc_buf[5] == '\0'); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (crosses boundary)"); - ret = copy_to_user(kKernelVaBase - 10, kernel_buf, 20); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (entirely in kernel)"); - ret = copy_to_user(kKernelVaBase + 0x1000, kernel_buf, 20); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_to_user invalid range (overflow)"); - ret = copy_to_user(0xFFFFFFFFFFFFFFF0ULL, kernel_buf, 0x100); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: copy_from_user invalid ranges"); - ret = copy_from_user(read_buf, kKernelVaBase - 5, 10); - ST_ASSERT_EQ(ret, -EFAULT); - - ret = copy_from_user(read_buf, kKernelVaBase, 10); - ST_ASSERT_EQ(ret, -EFAULT); - st_pass(); - - st_begin("uaccess: strncpy_from_user invalid range"); - uint64 invalid_addr = kKernelVaBase + 0x1000; - len = strncpy_from_user(str_buf, invalid_addr, 10); - ST_ASSERT_EQ(len, -EFAULT); - st_pass(); -} diff --git a/na/test/test_vfs_io.cc b/na/test/test_vfs_io.cc deleted file mode 100644 index 3d3b125..0000000 --- a/na/test/test_vfs_io.cc +++ /dev/null @@ -1,345 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * test_vfs_io.cc — Self-tests for VFS file I/O via syscalls - * - * Tests openat, read, write, close, lseek, dup, fstat, getdents64, - * getcwd, chdir, and mkdirat through the kernel syscall layer. - */ - -#include <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_vma.cc b/na/test/test_vma.cc deleted file mode 100644 index 5278914..0000000 --- a/na/test/test_vma.cc +++ /dev/null @@ -1,282 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/vmm.h> -#include <lume/addr.h> -#include <lume/errno.h> -#include <arch/pmap.h> - -void selftest_vma() { - st_begin("vma: create and destroy VmSpace"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT(space != nullptr); - ST_ASSERT(space->root_pa != 0); - - // Add a few regions to ensure destruction works - vmm_map_user(space, 0x1000, 0x1000, VM_READ); - vmm_map_user(space, 0x3000, 0x1000, VM_READ | VM_WRITE); - - space->destroy(); - } - st_pass(); - - st_begin("vma: map user region and handle demand paging"); - { - VmSpace* space = VmSpace::create(); - - // Map 8KB (2 pages) at 0x400000 using MI-layer flags - int ret = vmm_map_user(space, 0x400000, 0x2000, VM_READ | VM_WRITE); - ST_ASSERT_EQ(ret, 0); - - // Fault in the first page - ret = vmm_handle_page_fault(space, 0x400500, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Verify mapping in hardware page table - uint64 pa = 0; - bool mapped = pmap::lookup(space->root_pa, 0x400000, &pa); - ST_ASSERT(mapped); - ST_ASSERT_NE(pa, 0ULL); - - // Fault in the second page - ret = vmm_handle_page_fault(space, 0x401FFF, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Should reject fault outside VMA with -EFAULT - ret = vmm_handle_page_fault(space, 0x402000, VmFaultCause::Read); - ST_ASSERT_EQ(ret, -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: unmap user region prevents page fault"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x10000, 0x2000, VM_READ); - - // Inside the region — should succeed - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), 0); - - // Unmap exactly - vmm_unmap_user(space, 0x10000, 0x2000); - - // Should fail now with -EFAULT - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x10500, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: duplicate page fault on same page returns 0"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x200000, 0x1000, VM_READ | VM_WRITE); - - // First fault allocates the physical page - int ret = vmm_handle_page_fault(space, 0x200000, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Second fault on the same page should hit "already mapped" path - ret = vmm_handle_page_fault(space, 0x200100, VmFaultCause::Read); - ST_ASSERT_EQ(ret, 0); - - // Verify both land on the same physical page - uint64 pa1 = 0, pa2 = 0; - pmap::lookup(space->root_pa, 0x200000, &pa1); - pmap::lookup(space->root_pa, 0x200100, &pa2); - // Same page, so PA for both addresses should be the same base page - ST_ASSERT_EQ(pa1, pa2); - - space->destroy(); - } - st_pass(); - - st_begin("vma: vm_perm_to_pte flag translation correctness"); - { - // Test each MI flag maps to correct hardware PTE bit - uint64 pte_r = pmap::vm_perm_to_pte(VM_READ); - ST_ASSERT(pte_r & pmap::PTE_R); - ST_ASSERT(!(pte_r & pmap::PTE_W)); - ST_ASSERT(!(pte_r & pmap::PTE_X)); - ST_ASSERT(!(pte_r & pmap::PTE_U)); - - uint64 pte_rw = pmap::vm_perm_to_pte(VM_READ | VM_WRITE); - ST_ASSERT(pte_rw & pmap::PTE_R); - ST_ASSERT(pte_rw & pmap::PTE_W); - - uint64 pte_rx = pmap::vm_perm_to_pte(VM_READ | VM_EXEC); - ST_ASSERT(pte_rx & pmap::PTE_R); - ST_ASSERT(pte_rx & pmap::PTE_X); - ST_ASSERT(!(pte_rx & pmap::PTE_W)); - - uint64 pte_u = pmap::vm_perm_to_pte(VM_READ | VM_USER); - ST_ASSERT(pte_u & pmap::PTE_R); - ST_ASSERT(pte_u & pmap::PTE_U); - - // All combined - uint64 pte_all = pmap::vm_perm_to_pte(VM_READ | VM_WRITE | VM_EXEC | VM_USER); - ST_ASSERT(pte_all & pmap::PTE_R); - ST_ASSERT(pte_all & pmap::PTE_W); - ST_ASSERT(pte_all & pmap::PTE_X); - ST_ASSERT(pte_all & pmap::PTE_U); - ST_ASSERT(pte_all & pmap::PTE_V); // Always valid - } - st_pass(); - - st_begin("vma: unmap subset only removes contained VMAs"); - { - VmSpace* space = VmSpace::create(); - // Map three separate regions - vmm_map_user(space, 0x50000, 0x1000, VM_READ); - vmm_map_user(space, 0x60000, 0x1000, VM_READ); - vmm_map_user(space, 0x70000, 0x1000, VM_READ); - - // Unmap only the middle one (range covers 0x60000-0x61000) - vmm_unmap_user(space, 0x60000, 0x1000); - - // First and third should still be reachable - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x50000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x70000, VmFaultCause::Read), 0); - - // Middle should fail - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x60000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: null space returns -EINVAL"); - { - ST_ASSERT_EQ(vmm_map_user(nullptr, 0x1000, 0x1000, VM_READ), -EINVAL); - ST_ASSERT_EQ(vmm_unmap_user(nullptr, 0x1000, 0x1000), -EINVAL); - ST_ASSERT_EQ(vmm_handle_page_fault(nullptr, 0x1000, VmFaultCause::Read), -EINVAL); - } - st_pass(); - - st_begin("vma: clone and copy-on-write"); - { - VmSpace* parent = VmSpace::create(); - vmm_map_user(parent, 0x80000, 0x1000, VM_READ | VM_WRITE); - - /* Trigger read fault in parent to allocate physical page */ - ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Read), 0); - uint64 parent_pa_initial; - ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_initial)); - - /* Clone the address space */ - VmSpace* child = vmm_clone(parent); - ST_ASSERT(child != nullptr); - - /* Verify child has the same physical page mapped but COW */ - uint64 child_pa; - ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa)); - ST_ASSERT_EQ(child_pa, parent_pa_initial); - ST_ASSERT(pmap::is_cow(child->root_pa, 0x80000)); - ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); /* Parent also marked COW */ - - /* Trigger Store Page Fault (cause=15) in child */ - ST_ASSERT_EQ(vmm_handle_page_fault(child, 0x80000, VmFaultCause::Write), 0); - - /* Child should now have a new physical page, no longer COW */ - uint64 child_pa_new; - ST_ASSERT(pmap::lookup(child->root_pa, 0x80000, &child_pa_new)); - ST_ASSERT(child_pa_new != parent_pa_initial); - ST_ASSERT(!pmap::is_cow(child->root_pa, 0x80000)); - - /* Parent is still mapped to the old page, still COW because it hasn't written */ - uint64 parent_pa_current; - ST_ASSERT(pmap::lookup(parent->root_pa, 0x80000, &parent_pa_current)); - ST_ASSERT_EQ(parent_pa_current, parent_pa_initial); - ST_ASSERT(pmap::is_cow(parent->root_pa, 0x80000)); - - /* Trigger Store Page Fault in parent */ - ST_ASSERT_EQ(vmm_handle_page_fault(parent, 0x80000, VmFaultCause::Write), 0); - ST_ASSERT(!pmap::is_cow(parent->root_pa, 0x80000)); - - child->destroy(); - parent->destroy(); - } - st_pass(); - - st_begin("vma: gap search finds next free range"); - { - VmSpace* space = VmSpace::create(); - vmm_map_user(space, 0x1000, 0x3000, VM_READ); - - uint64 va = mm_find_free_va(space, 0x1000, kPageSize); - ST_ASSERT_EQ(va, 0x4000ULL); - - space->destroy(); - } - st_pass(); - - st_begin("vma: reject overlapping mapping"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x20000, 0x2000, VM_READ), 0); - ST_ASSERT_EQ(vmm_map_user(space, 0x21000, 0x1000, VM_READ), -EINVAL); - space->destroy(); - } - st_pass(); - - st_begin("vma: partial unmap splits a single VMA"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x80000, 0x3000, VM_READ | VM_WRITE), 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_unmap_user(space, 0x81000, 0x1000), 0); - - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x80000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x82000, VmFaultCause::Read), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x81000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: mprotect read-only blocks write faults"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0x90000, 0x2000, VM_READ | VM_WRITE), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_protect_user(space, 0x90000, 0x1000, VM_READ), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x90000, VmFaultCause::Write), -EFAULT); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0x91000, VmFaultCause::Write), 0); - - space->destroy(); - } - st_pass(); - - st_begin("vma: PROT_NONE unmaps existing pages"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0xA0000, 0x1000, VM_READ | VM_WRITE), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), 0); - - ST_ASSERT_EQ(vmm_protect_user(space, 0xA0000, 0x1000, 0), 0); - ST_ASSERT_EQ(vmm_handle_page_fault(space, 0xA0000, VmFaultCause::Read), -EFAULT); - - space->destroy(); - } - st_pass(); - - st_begin("vma: range covered check"); - { - VmSpace* space = VmSpace::create(); - ST_ASSERT_EQ(vmm_map_user(space, 0xB0000, 0x2000, VM_READ), 0); - - ST_ASSERT(vma_range_covered(space, 0xB0000, 0xB1000)); - ST_ASSERT(!vma_range_covered(space, 0xB0000, 0xB3000)); - - space->destroy(); - } - st_pass(); -} diff --git a/na/test/test_vmm.cc b/na/test/test_vmm.cc deleted file mode 100644 index 635e3f3..0000000 --- a/na/test/test_vmm.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -/* - * Boot Self-Test: VMM and Address Translation - */ - -#include <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> - -namespace { -constexpr uint64 kUartBase = 0x10000000ULL; -constexpr uint64 kPageOffsetMask = kPageSize - 1; -} - -void selftest_vmm() { - st_begin("vmm: virtual/physical address translation"); - { - PhysAddr pa = pmm_get_mem_base(); - VirtAddr va = pa_to_va(pa); - ST_ASSERT_EQ(va.raw, arch::g_direct_map_base + pa.raw); - ST_ASSERT_EQ(va_to_pa(va).raw, pa.raw); - - Pfn p = pa_to_pfn(pa); - ST_ASSERT_EQ(p.raw, pa.raw / kPageSize); - ST_ASSERT_EQ(pfn_to_pa(p).raw, pa.raw); - } - st_pass(); - - st_begin("vmm: page alignment macros"); - { - uint64 base = pmm_get_mem_base().raw; - uint64 addr = base + kPageOffsetMask; - ST_ASSERT_EQ(page_align_down(addr), base); - ST_ASSERT_EQ(page_align_up(addr), base + kPageSize); - ST_ASSERT_EQ(page_align_down(base), base); - ST_ASSERT_EQ(page_align_up(base), base); - } - st_pass(); - - st_begin("vmm: mapping kernel MMIO (driver API)"); - { - /* Use UART MMIO address (0x10000000 on RISC-V virt). - * Must NOT use pmm_get_mem_base() — that's PMP-protected firmware. */ - uint64 mmio_pa = kUartBase; - vmm_map_kernel_mmio(mmio_pa, kPageSize); - - // Verify it doesn't crash and address is reachable in higher half - uint64 va = pa_to_va(phys_addr(mmio_pa)).raw; - ST_ASSERT(va >= arch::g_direct_map_base); - } - st_pass(); -} diff --git a/na/test/test_waitqueue.cc b/na/test/test_waitqueue.cc deleted file mode 100644 index 7f50d6b..0000000 --- a/na/test/test_waitqueue.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Copyright (C) 2026 Richard Qin */ - -#include <lume/selftest.h> -#include <lume/waitqueue.h> -#include <lume/task.h> -#include <lume/sched.h> - -static WaitQueue g_test_wq; -static volatile bool g_thread_woken = false; -static volatile bool g_thread_started = false; -static volatile int g_wake_count = 0; - -extern TaskControlBlock* kthread_create(const char* name, void (*entry)(void*), void* arg, uint8 priority); - -static void waitqueue_test_thread(void*) -{ - g_thread_started = true; - g_test_wq.sleep(); - g_thread_woken = true; - - extern void task_exit(int code); - task_exit(0); -} - -static void waitqueue_test_thread_all(void*) -{ - g_test_wq.sleep(); - __sync_fetch_and_add(&g_wake_count, 1); - - extern void task_exit(int code); - task_exit(0); -} - -void selftest_waitqueue() -{ - st_begin("waitqueue: sleep and wake_one"); - - g_test_wq.init("test_wq"); - g_thread_started = false; - g_thread_woken = false; - - TaskControlBlock* child = kthread_create("test_wq_child", waitqueue_test_thread, nullptr, 0); - ST_ASSERT(child != nullptr); - - sched_yield(); - - ST_ASSERT(g_thread_started == true); - ST_ASSERT(g_thread_woken == false); - ST_ASSERT(!g_test_wq.is_empty()); - - kprintf("[test_wq] waking child\n"); - bool woken = g_test_wq.wake_one(); - ST_ASSERT(woken == true); - ST_ASSERT(g_test_wq.is_empty()); - - kprintf("[test_wq] yielding to child again\n"); - sched_yield(); - kprintf("[test_wq] returned from yield 2\n"); - - ST_ASSERT(g_thread_woken == true); - - st_pass(); - - st_begin("waitqueue: wake_all"); - g_wake_count = 0; - for (int i = 0; i < 3; i++) { - kthread_create("test_wq_all", waitqueue_test_thread_all, nullptr, 0); - } - - /* Give all 3 threads a chance to start and sleep */ - kprintf("[test_wq] yielding 1 for wake_all\n"); - sched_yield(); - sched_yield(); - sched_yield(); - - ST_ASSERT(!g_test_wq.is_empty()); - - int woken_count = g_test_wq.wake_all(); - - ST_ASSERT_EQ(woken_count, 3); - ST_ASSERT(g_test_wq.is_empty()); - - /* Give all 3 threads a chance to wake up and run */ - sched_yield(); - sched_yield(); - sched_yield(); - - ST_ASSERT_EQ(g_wake_count, 3); - st_pass(); -} diff --git a/na/usr/default_cpio_list b/na/usr/default_cpio_list deleted file mode 100644 index 6721365..0000000 --- a/na/usr/default_cpio_list +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Default initramfs source — minimal, no userland. -# -# Mirrors Linux's usr/default_cpio_list: just enough nodes for the kernel -# to mount a root and panic cleanly on "no init found". Real workloads -# override CONFIG_INITRAMFS_SOURCE. -# -# Format (matches gen_init_cpio): -# dir <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 deleted file mode 100644 index b7296ed..0000000 --- a/na/usr/gen_init_cpio.c +++ /dev/null @@ -1,781 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#define _GNU_SOURCE -#include <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); -} |
