summaryrefslogtreecommitdiff
path: root/na/include
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:24:56 +0800
commit1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (patch)
tree0f5e01cd7c7a05abb6b1c7472d7f00f49caf02cc /na/include
parent63dc025705f6533b91fb1a7d26e6ed864efd9900 (diff)
downloadlume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.gz
lume-1767d390fbf3d0c65389de46caf0b6d43ef3b6ad.tar.xz
feat: establish bootable new architecture checkpoint
Replace the compressed boot path with a relocatable higher-half RISC-V entry, expand memory and SMP support, and add the driver, TTY, VFS, exec, signal, syscall, initramfs, and self-test infrastructure needed for a bootable kernel checkpoint.
Diffstat (limited to 'na/include')
-rw-r--r--na/include/lume/args.h28
-rw-r--r--na/include/lume/atomic.h10
-rw-r--r--na/include/lume/boot_params.h85
-rw-r--r--na/include/lume/chardev.h45
-rw-r--r--na/include/lume/clone.h34
-rw-r--r--na/include/lume/compiler.h61
-rw-r--r--na/include/lume/compressed.h37
-rw-r--r--na/include/lume/config.h9
-rw-r--r--na/include/lume/console.h54
-rw-r--r--na/include/lume/container_of.h17
-rw-r--r--na/include/lume/dcache.h143
-rw-r--r--na/include/lume/decompress.h11
-rw-r--r--na/include/lume/device.h75
-rw-r--r--na/include/lume/driver.h35
-rw-r--r--na/include/lume/elf.h150
-rw-r--r--na/include/lume/errno.h319
-rw-r--r--na/include/lume/fdtable.h49
-rw-r--r--na/include/lume/file.h132
-rw-r--r--na/include/lume/fs.h310
-rw-r--r--na/include/lume/fs_struct.h39
-rw-r--r--na/include/lume/fs_types.h197
-rw-r--r--na/include/lume/init.h33
-rw-r--r--na/include/lume/kbuild.h40
-rw-r--r--na/include/lume/kernel_map.h55
-rw-r--r--na/include/lume/klog.h15
-rw-r--r--na/include/lume/kobject.h57
-rw-r--r--na/include/lume/kref.h46
-rw-r--r--na/include/lume/libfdt.h8
-rw-r--r--na/include/lume/libfdt_env.h78
-rw-r--r--na/include/lume/limits.h48
-rw-r--r--na/include/lume/list.h56
-rw-r--r--na/include/lume/lumeos.lds.h129
-rw-r--r--na/include/lume/mm_layout.h73
-rw-r--r--na/include/lume/mount.h52
-rw-r--r--na/include/lume/mutex.h63
-rw-r--r--na/include/lume/namei.h32
-rw-r--r--na/include/lume/page.h132
-rw-r--r--na/include/lume/page_cache.h82
-rw-r--r--na/include/lume/pgrp.h125
-rw-r--r--na/include/lume/pgtable_types.h69
-rw-r--r--na/include/lume/pipe.h52
-rw-r--r--na/include/lume/platform_device.h64
-rw-r--r--na/include/lume/pmm.h28
-rw-r--r--na/include/lume/poll.h68
-rw-r--r--na/include/lume/ptrace.h76
-rw-r--r--na/include/lume/rbtree.h11
-rw-r--r--na/include/lume/relocate.h53
-rw-r--r--na/include/lume/sched.h29
-rw-r--r--na/include/lume/setup.h42
-rw-r--r--na/include/lume/signal.h54
-rw-r--r--na/include/lume/signal_types.h263
-rw-r--r--na/include/lume/smp.h74
-rw-r--r--na/include/lume/stdarg.h11
-rw-r--r--na/include/lume/stringify.h14
-rw-r--r--na/include/lume/syscall_nr.h21
-rw-r--r--na/include/lume/task.h39
-rw-r--r--na/include/lume/timer.h21
-rw-r--r--na/include/lume/tlb.h48
-rw-r--r--na/include/lume/tty.h165
-rw-r--r--na/include/lume/tty_ioctl.h202
-rw-r--r--na/include/lume/types.h83
-rw-r--r--na/include/lume/uuid.h207
-rw-r--r--na/include/lume/vmm.h16
-rw-r--r--na/include/lume/wait.h19
-rw-r--r--na/include/lume/waitqueue.h45
65 files changed, 4283 insertions, 455 deletions
diff --git a/na/include/lume/args.h b/na/include/lume/args.h
new file mode 100644
index 0000000..0562dc5
--- /dev/null
+++ b/na/include/lume/args.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_ARGS_H
+#define _LINUX_ARGS_H
+
+/*
+ * How do these macros work?
+ *
+ * In __COUNT_ARGS() _0 to _15 are just placeholders from the start
+ * in order to make sure _n is positioned over the correct number
+ * from 15 to 0 (depending on X, which is a variadic argument list).
+ * They serve no purpose other than occupying a position. Since each
+ * macro parameter must have a distinct identifier, those identifiers
+ * are as good as any.
+ *
+ * In COUNT_ARGS() we use actual integers, so __COUNT_ARGS() returns
+ * that as _n.
+ */
+
+/* This counts to 15. Any more, it will return 16th argument. */
+#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n
+#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+
+/* Concatenate two parameters, but allow them to be expanded beforehand. */
+#define __CONCAT(a, b) a ## b
+#define CONCATENATE(a, b) __CONCAT(a, b)
+
+#endif /* _LINUX_ARGS_H */
diff --git a/na/include/lume/atomic.h b/na/include/lume/atomic.h
index e6331c2..6e31129 100644
--- a/na/include/lume/atomic.h
+++ b/na/include/lume/atomic.h
@@ -51,6 +51,16 @@ public:
return __atomic_fetch_sub(&val_, delta, order);
}
+ /* Fetch-and-or, returns old value */
+ T fetch_or(T mask, int order = __ATOMIC_ACQ_REL) {
+ return __atomic_fetch_or(&val_, mask, order);
+ }
+
+ /* Fetch-and-and, returns old value */
+ T fetch_and(T mask, int order = __ATOMIC_ACQ_REL) {
+ return __atomic_fetch_and(&val_, mask, order);
+ }
+
/* Compare-and-swap. Returns true on success. */
bool compare_exchange(T& expected, T desired,
int success_order = __ATOMIC_ACQ_REL,
diff --git a/na/include/lume/boot_params.h b/na/include/lume/boot_params.h
deleted file mode 100644
index 7877468..0000000
--- a/na/include/lume/boot_params.h
+++ /dev/null
@@ -1,85 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-#include <lume/types.h>
-
-/*
- * Boot parameters shared between bootloader/firmware and kernel.
- */
-
-inline constexpr uint64 kBootParamsMagic = 0x4C554D45424F4F54ULL; /* LUMEBOOT */
-inline constexpr uint32 kBootParamsVersion = 2;
-
-enum BootMemType : uint32 {
- BOOT_MEM_USABLE = 1,
- BOOT_MEM_RESERVED = 2,
- BOOT_MEM_ACPI = 3,
- BOOT_MEM_MMIO = 4,
-};
-
-enum BootSeedSource : uint32 {
- BOOT_SEED_NONE = 0,
- BOOT_SEED_FDT = 1,
- BOOT_SEED_HW = 2,
- BOOT_SEED_CYCLE = 3,
-};
-
-enum BootImageFlags : uint32 {
- BOOT_IMAGE_COMPRESSED = 1u << 0,
- BOOT_IMAGE_RELOCATED = 1u << 1,
- BOOT_IMAGE_DECOMP_FAILED = 1u << 2,
- BOOT_IMAGE_RELOC_FAILED = 1u << 3,
-};
-
-struct BootMemRegion {
- uint64 base;
- uint64 size;
- uint32 type;
- uint32 reserved;
-};
-
-struct BootParams {
- uint64 magic;
- uint32 version;
- uint32 size;
-
- uint64 cmdline_ptr;
- uint32 cmdline_len;
- uint32 memmap_count;
- uint64 memmap_ptr;
- uint32 memmap_entry_size;
- uint32 reserved0;
-
- uint64 initrd_start;
- uint64 initrd_end;
-
- uint64 acpi_ptr;
- uint64 fdt_ptr;
-
- uint64 kaslr_seed;
- uint32 seed_source;
- uint32 flags;
-
- uint64 kimage_phys_base;
- uint64 kimage_size;
- uint64 kimage_reloc_offset;
- uint64 kimage_reloc_size;
- uint32 kimage_flags;
- uint32 kimage_comp;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-void boot_params_init_fdt(uint64 fdt_paddr);
-const BootParams* boot_params_get();
-void boot_params_set_image_info(uint64 phys_base, uint64 image_size,
- uint64 reloc_offset, uint64 reloc_size,
- uint32 comp, uint32 image_flags);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/na/include/lume/chardev.h b/na/include/lume/chardev.h
new file mode 100644
index 0000000..be81ab9
--- /dev/null
+++ b/na/include/lume/chardev.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * chardev.h — Character device abstraction
+ *
+ * Minimal Linux-style cdev registry: each character device has a
+ * major/minor number and a file_operations table.
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+
+struct file_operations;
+
+/* ================================================================== */
+/* MKDEV / MAJOR / MINOR */
+/* ================================================================== */
+
+inline constexpr uint32 MKDEV(uint32 ma, uint32 mi) {
+ return (ma << 8) | (mi & 0xFF);
+}
+inline constexpr uint32 MAJOR(uint32 dev) { return dev >> 8; }
+inline constexpr uint32 MINOR(uint32 dev) { return dev & 0xFF; }
+
+/* ================================================================== */
+/* cdev — character device descriptor */
+/* ================================================================== */
+
+struct cdev {
+ const char* name;
+ uint32 dev; /* MKDEV(major, minor) */
+ const file_operations* fops;
+ list_node list; /* global cdev list */
+};
+
+/* ================================================================== */
+/* API */
+/* ================================================================== */
+
+void cdev_init();
+void cdev_register(cdev* dev);
+cdev* cdev_find(uint32 dev);
diff --git a/na/include/lume/clone.h b/na/include/lume/clone.h
new file mode 100644
index 0000000..156bae4
--- /dev/null
+++ b/na/include/lume/clone.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Linux-compatible clone(2) flags.
+ *
+ * Only the flags relevant to current and near-future use are defined.
+ * Reference: include/uapi/linux/sched.h
+ */
+
+#include <lume/types.h>
+
+/* Signal mask stored in low byte of clone flags */
+inline constexpr uint64 CSIGNAL = 0x000000ffULL;
+
+/* Sharing flags */
+inline constexpr uint64 CLONE_VM = 0x00000100ULL; // Share address space
+inline constexpr uint64 CLONE_FS = 0x00000200ULL; // Share fs info
+inline constexpr uint64 CLONE_FILES = 0x00000400ULL; // Share file descriptors
+inline constexpr uint64 CLONE_SIGHAND = 0x00000800ULL; // Share signal handlers
+inline constexpr uint64 CLONE_PIDFD = 0x00001000ULL; // pidfd
+inline constexpr uint64 CLONE_PTRACE = 0x00002000ULL; // Trace child
+inline constexpr uint64 CLONE_VFORK = 0x00004000ULL; // Parent blocks until child exits/execs
+inline constexpr uint64 CLONE_PARENT = 0x00008000ULL; // Same parent as cloner
+inline constexpr uint64 CLONE_THREAD = 0x00010000ULL; // Same thread group
+inline constexpr uint64 CLONE_NEWNS = 0x00020000ULL; // New mount namespace
+inline constexpr uint64 CLONE_SYSVSEM = 0x00040000ULL; // Share SysV semaphore undo
+inline constexpr uint64 CLONE_SETTLS = 0x00080000ULL; // Set TLS
+inline constexpr uint64 CLONE_PARENT_SETTID = 0x00100000ULL; // Write TID to parent
+inline constexpr uint64 CLONE_CHILD_CLEARTID = 0x00200000ULL; // Clear TID on child exit
+inline constexpr uint64 CLONE_DETACHED = 0x00400000ULL; // Unused, historical
+inline constexpr uint64 CLONE_CHILD_SETTID = 0x01000000ULL; // Write TID to child
diff --git a/na/include/lume/compiler.h b/na/include/lume/compiler.h
index fa05363..243f0de 100644
--- a/na/include/lume/compiler.h
+++ b/na/include/lume/compiler.h
@@ -1,27 +1,54 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
-#pragma once
+#ifndef _LUME_COMPILER_H
+#define _LUME_COMPILER_H
-/**
- * compiler.h - Centralized management of compiler-specific macros and built-in wrappers.
+/*
+ * Minimal compiler primitives used across LumeOS.
+ *
+ * Provides only what current code actually references:
+ * - READ_ONCE / WRITE_ONCE memory access opacity for concurrent code
+ * (rbtree.h, rbtree_augmented.h)
+ * - likely / unlikely branch hints for hot/cold paths
+ *
+ * Implementation mirrors Linux's semantics — same surface, same effect —
+ * without pulling in Linux's compiler_types.h cascade (KASAN/KCSAN/KMSAN
+ * sanitizer attribute machinery, BTF type tags, hardened-usercopy hooks).
+ * LumeOS doesn't build with those sanitizers, so the simpler form is
+ * sufficient and keeps the dependency graph flat.
*/
-/* Memory barrier macros */
-#define WRITE_ONCE(x, val) \
- do { \
- __asm__ volatile("" ::: "memory"); \
- (x) = (val); \
- __asm__ volatile("" ::: "memory"); \
- } while (0)
+/* Branch prediction hints */
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
-#define READ_ONCE(x) (*(volatile __typeof__(x) *)&(x))
+/* Compile-time barrier — prevent the compiler from reordering memory
+ * accesses across this point. Not a CPU fence; for CPU ordering use
+ * the architecture-specific fence intrinsics. */
+#define barrier() __asm__ __volatile__("" ::: "memory")
-#define barrier() __asm__ volatile("" ::: "memory")
+/*
+ * __always_inline — force the compiler to actually inline a function,
+ * overriding -fno-inline / -Os / size heuristics. Used by rbtree helper
+ * templates whose performance depends on the call being inlined into
+ * the iterating loop.
+ */
+#define __always_inline inline __attribute__((__always_inline__))
-/* Branch prediction hints */
-#define unlikely(x) __builtin_expect(!!(x), 0)
-#define likely(x) __builtin_expect(!!(x), 1)
+/*
+ * READ_ONCE / WRITE_ONCE — opaque single-access memory operations.
+ *
+ * The volatile cast prevents the compiler from:
+ * - merging adjacent reads/writes to the same location,
+ * - splitting a load into multiple narrower loads,
+ * - hoisting reads out of a loop (or sinking writes past one).
+ *
+ * Used in lock-free / cross-CPU shared data paths (rbtree color writes,
+ * future RCU pointers, atomic flag polls). Does NOT imply any CPU-side
+ * memory ordering — pair with smp_mb()/acquire/release for that.
+ */
+#define READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x))
+#define WRITE_ONCE(x, val) (*(volatile __typeof__(x) *)&(x) = (val))
-/* Function attributes */
-#define __always_inline inline __attribute__((always_inline))
+#endif /* _LUME_COMPILER_H */
diff --git a/na/include/lume/compressed.h b/na/include/lume/compressed.h
deleted file mode 100644
index 8ca775d..0000000
--- a/na/include/lume/compressed.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-#include <lume/types.h>
-
-inline constexpr uint64 kCompressedKernelMagic = 0x4C554D45434F4D50ULL; /* LUMECOMP */
-inline constexpr uint32 kCompressedKernelVersion = 1;
-
-enum KernelCompressionType : uint32 {
- KERNEL_COMP_NONE = 0,
- KERNEL_COMP_LZ4 = 1,
-};
-
-enum CompressedHeaderFlags : uint16 {
- LUME_HDR_FLAG_STAGE3_DONE = 1u << 0,
-};
-
-struct CompressedKernelHeader {
- uint64 magic;
- uint32 version;
- uint16 header_size;
- uint16 flags;
- uint32 compression;
- uint32 reserved0;
- uint64 uncompressed_size;
- uint64 compressed_size;
- uint64 load_phys_base;
- uint64 reloc_offset;
- uint64 reloc_size;
- uint64 payload_offset;
- uint32 header_crc32;
- uint32 payload_crc32;
-};
-
-static_assert(sizeof(CompressedKernelHeader) == 80);
diff --git a/na/include/lume/config.h b/na/include/lume/config.h
index d3539c5..bde717a 100644
--- a/na/include/lume/config.h
+++ b/na/include/lume/config.h
@@ -37,7 +37,7 @@
#endif
#ifndef CONFIG_KERNEL_STACK_SIZE
-#define CONFIG_KERNEL_STACK_SIZE 8192
+#define CONFIG_KERNEL_STACK_SIZE 16384
#endif
#ifndef CONFIG_HZ
@@ -86,8 +86,15 @@ inline constexpr int kMaxCpus = CONFIG_NR_CPUS;
/* Virtual memory page size (4KB, matching SV39 base page). */
inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE;
+/* Page shift: log2(kPageSize). Used for PFN ↔ PA conversions. */
+inline constexpr int kPageShift = 12; // 4096 = 1 << 12
+
/* KASLR policy ID and logging level (build-time configurable) */
inline constexpr uint32 kKaslrPolicyId = CONFIG_KASLR_POLICY_ID;
inline constexpr int kKaslrLogLevel = CONFIG_KASLR_LOG_LEVEL;
+#include <arch/config.h>
+
+using arch::kPhysStart;
+
#endif /* __ASSEMBLER__ */ \ No newline at end of file
diff --git a/na/include/lume/console.h b/na/include/lume/console.h
index a4fdf3f..af187a6 100644
--- a/na/include/lume/console.h
+++ b/na/include/lume/console.h
@@ -3,7 +3,30 @@
#pragma once
+/*
+ * console.h — Kernel console abstraction
+ *
+ * Two-phase design following Linux:
+ *
+ * Phase 1 (early boot):
+ * early_putc() → direct UART MMIO write. Used before MMU and
+ * before the console registry is ready. kprintf() and panic
+ * use this path.
+ *
+ * Phase 2 (runtime):
+ * struct console registered via register_console(). printk/klog
+ * emit through the console driver chain. Multiple consoles can
+ * be registered (e.g., UART + framebuffer).
+ *
+ * Reference: Linux include/linux/console.h
+ */
+
#include <lume/types.h>
+#include <lume/list.h>
+
+/* ================================================================== */
+/* Early console (Phase 1 — direct UART access) */
+/* ================================================================== */
extern "C" void early_putc(char c);
extern "C" void early_puts(const char* s);
@@ -21,3 +44,34 @@ void console_init();
* instead of the bootstrap fixed base.
*/
void console_use_runtime_mapping();
+
+/* ================================================================== */
+/* struct console (Phase 2 — registered console drivers) */
+/* ================================================================== */
+
+inline constexpr int CON_PRINTBUFFER = (1 << 0); /* Replay klog on register */
+inline constexpr int CON_ENABLED = (1 << 1);
+inline constexpr int CON_BOOT = (1 << 2); /* Early boot console */
+
+struct console {
+ const char* name;
+ void (*write)(console* con, const char* s, unsigned int count);
+ int (*setup)(console* con, char* options);
+ int flags;
+ int index;
+ void* data; /* driver private data */
+ list_node list;
+};
+
+/* Register / unregister a console driver */
+void register_console(console* con);
+void unregister_console(console* con);
+
+/*
+ * Write to all registered consoles.
+ * Falls back to early_putc() if no console is registered.
+ */
+void console_write_all(const char* s, int count);
+
+/* Check if any console is registered */
+bool console_drivers_registered();
diff --git a/na/include/lume/container_of.h b/na/include/lume/container_of.h
new file mode 100644
index 0000000..a6889eb
--- /dev/null
+++ b/na/include/lume/container_of.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * container_of.h — Recover containing struct from member pointer
+ *
+ * Equivalent to Linux include/linux/container_of.h.
+ */
+
+#include <lume/types.h>
+
+#define container_of(ptr, type, member) \
+ reinterpret_cast<type*>( \
+ reinterpret_cast<uintptr>(ptr) - \
+ reinterpret_cast<uintptr>(&(reinterpret_cast<type*>(0)->member)))
diff --git a/na/include/lume/dcache.h b/na/include/lume/dcache.h
new file mode 100644
index 0000000..a31402e
--- /dev/null
+++ b/na/include/lume/dcache.h
@@ -0,0 +1,143 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * dcache.h — Directory entry cache
+ *
+ * Modeled after Linux include/linux/dcache.h.
+ * Reference: docs/vfs/03_dentry.md
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <lume/atomic.h>
+#include <lume/fs_types.h>
+#include <kernel/sync/spinlock.h>
+
+struct inode;
+struct super_block;
+struct dentry;
+struct dentry_operations;
+struct path;
+
+/* ================================================================== */
+/* qstr — quick string (name + precomputed hash + length) */
+/* ================================================================== */
+
+struct qstr {
+ union {
+ struct {
+ uint32 hash;
+ uint32 len;
+ };
+ uint64 hash_len;
+ };
+ const char* name;
+};
+
+uint32 full_name_hash(const char* name, uint32 len);
+
+/* ================================================================== */
+/* dentry */
+/* ================================================================== */
+
+inline constexpr int DNAME_INLINE_LEN = 40;
+
+struct dentry {
+ /* ---- RCU / lookup hot fields ---- */
+ unsigned int d_flags;
+ dentry* d_parent;
+ qstr d_name;
+ inode* d_inode;
+ unsigned char d_iname[DNAME_INLINE_LEN];
+
+ /* ---- Ref lookup ---- */
+ const dentry_operations* d_op;
+ super_block* d_sb;
+ unsigned long d_time;
+ void* d_fsdata;
+
+ /* ---- Refcount + lock ---- */
+ lume::atomic<int32> d_lockref;
+ Spinlock d_lock;
+
+ /* ---- Tree linkage ---- */
+ list_node d_lru;
+ list_node d_child; /* Linkage in parent->d_children */
+ list_head d_children; /* Children of this dentry */
+
+ /* ---- Inode alias ---- */
+ list_node d_alias; /* Linkage in inode->i_dentry */
+};
+
+/* ================================================================== */
+/* dentry_operations */
+/* ================================================================== */
+
+struct dentry_operations {
+ int (*d_revalidate)(dentry* de, unsigned int flags);
+ int (*d_hash)(const dentry* de, qstr* name);
+ int (*d_compare)(const dentry* parent, unsigned int len,
+ const char* str, const qstr* name);
+ int (*d_delete)(const dentry* de);
+ int (*d_init)(dentry* de);
+ void (*d_release)(dentry* de);
+ void (*d_iput)(dentry* de, inode* ip);
+};
+
+/* ================================================================== */
+/* Dentry helpers */
+/* ================================================================== */
+
+inline bool d_is_negative(const dentry* de) {
+ return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_MISS_TYPE;
+}
+inline bool d_is_positive(const dentry* de) {
+ return !d_is_negative(de);
+}
+inline bool d_is_dir(const dentry* de) {
+ return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_DIRECTORY_TYPE;
+}
+inline bool d_is_reg(const dentry* de) {
+ return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_REGULAR_TYPE;
+}
+inline bool d_is_symlink(const dentry* de) {
+ return (de->d_flags & DCACHE_ENTRY_TYPE) == DCACHE_SYMLINK_TYPE;
+}
+inline bool d_mountpoint(const dentry* de) {
+ return (de->d_flags & DCACHE_MOUNTED) != 0;
+}
+inline bool d_is_root(const dentry* de) {
+ return de == de->d_parent;
+}
+
+inline inode* d_inode_ptr(const dentry* de) {
+ return de->d_inode;
+}
+
+/* ================================================================== */
+/* Dcache API */
+/* ================================================================== */
+
+dentry* d_alloc(dentry* parent, const qstr* name);
+dentry* d_alloc_anon(super_block* sb);
+void d_instantiate(dentry* de, inode* ip);
+dentry* d_make_root(inode* root_inode);
+void d_add(dentry* de, inode* ip);
+dentry* d_lookup(const dentry* parent, const qstr* name);
+dentry* dget(dentry* de);
+void dput(dentry* de);
+void d_drop(dentry* de);
+void d_delete(dentry* de);
+char* d_path(const path* p, char* buf, int buflen);
+
+/* Set dentry type flags based on inode mode */
+void d_set_type(dentry* de, uint32 i_mode);
+
+/* ================================================================== */
+/* Dcache init */
+/* ================================================================== */
+
+void dcache_init();
diff --git a/na/include/lume/decompress.h b/na/include/lume/decompress.h
deleted file mode 100644
index 04082a9..0000000
--- a/na/include/lume/decompress.h
+++ /dev/null
@@ -1,11 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-#include <lume/types.h>
-
-/*
- * LZ4 block decompressor (no frame headers). Returns bytes written or -1.
- */
-int lz4_decompress(const uint8* src, uint32 src_len, uint8* dst, uint32 dst_cap);
diff --git a/na/include/lume/device.h b/na/include/lume/device.h
new file mode 100644
index 0000000..46e22e4
--- /dev/null
+++ b/na/include/lume/device.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Driver model: device, device_driver, bus_type.
+ *
+ * Mirrors Linux include/linux/device.h. Every hardware unit embeds a
+ * `device` and binds to a `device_driver` via a `bus_type` that knows
+ * how to match the two. Buses we will have:
+ * - platform: FDT-described memory-mapped devices (this commit)
+ * - virtio: virtio-mmio devices (P5 of storage_stack.md)
+ *
+ * Reference: docs/specs/storage_stack.md §4.3
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <lume/kobject.h>
+#include <kernel/sync/spinlock.h>
+
+struct bus_type;
+struct device_driver;
+
+struct device {
+ kobject kobj; // embedded; first member by convention
+ device* parent;
+ const char* init_name; // used until kobj.name is set
+ bus_type* bus;
+ device_driver* driver;
+ void* driver_data;
+ void* platform_data;
+ uint32 devt; // for char/block dev_t allocation
+ list_node bus_list; // node in bus->devices
+ list_node driver_list; // node in driver->devices
+};
+
+struct device_driver {
+ const char* name;
+ bus_type* bus;
+ int (*probe)(device*);
+ void (*remove)(device*);
+ list_node bus_node; // node in bus->drivers
+ list_node devices; // head of devices bound to this driver
+};
+
+struct bus_type {
+ const char* name;
+ int (*match)(device*, device_driver*);
+ /* If non-null, this is the bus-level probe dispatcher. It is called
+ * before drv->probe and is responsible for downcasting device/driver
+ * to the concrete bus type and forwarding (e.g. platform_dispatch). */
+ int (*probe)(device*);
+ list_node devices;
+ list_node drivers;
+ Spinlock lock;
+ bool registered;
+};
+
+int bus_register (bus_type* bus);
+
+/* device_initialize: kobject_init the embedded kobj with the generic
+ * device_ktype. device_register calls this automatically if the caller
+ * hasn't already. Separate entry point for callers that want a half-
+ * initialized device (so they can take refs before adding). */
+void device_initialize(device* dev);
+int device_register (device* dev);
+void device_unregister(device* dev);
+
+int driver_register (device_driver* drv);
+void driver_unregister(device_driver* drv);
+
+/* Convenience accessor — name from kobject if set, else init_name. */
+const char* dev_name(const device* dev);
diff --git a/na/include/lume/driver.h b/na/include/lume/driver.h
index c9d5669..a8bd787 100644
--- a/na/include/lume/driver.h
+++ b/na/include/lume/driver.h
@@ -4,30 +4,21 @@
#pragma once
/*
- * Driver Registration Subsystem
+ * driver.h — boot-time driver probing entry point.
*
- * Provides a linker-set based registration mechanism for hardware drivers.
- * Drivers define a probe function and register it with the REGISTER_DRIVER macro.
- * During boot, driver_probe_all() scans the Device Tree (FDT) and invokes the
- * probe function for any matching compatible strings.
+ * The actual driver-model primitives (kobject, device, bus_type,
+ * platform_device, platform_driver) live in their own headers:
+ * <lume/kobject.h>, <lume/device.h>, <lume/platform_device.h>.
*
- * This decouples the MI kernel initialization from MD driver specifics.
+ * This header just exposes the single boot entry point that
+ * kernel_main() calls after the slab allocator + FDT unflatten are
+ * ready. The implementation lives in drivers/base/platform.cc.
+ *
+ * Reference: docs/specs/storage_stack.md §4
*/
-#include <lume/types.h>
-
-class DeviceNode;
-
-using DriverProbeFunc = void (*)(DeviceNode* node);
-
-struct DriverEntry {
- const char* compatible;
- DriverProbeFunc probe;
-};
-
-#define REGISTER_DRIVER(compat_str, probe_fn) \
- static const DriverEntry __driver_##probe_fn \
- __attribute__((used, section(".drivers"))) = { compat_str, probe_fn }
-
-/* MI initialization function called from kernel_main() */
+/* Register the platform bus, register every linker-set platform_driver,
+ * walk the FDT, and instantiate a platform_device for each compatible
+ * match. Each device is bound to its driver via the platform bus
+ * match/probe path. Returns after all probes have run. */
void driver_probe_all();
diff --git a/na/include/lume/elf.h b/na/include/lume/elf.h
new file mode 100644
index 0000000..772ad97
--- /dev/null
+++ b/na/include/lume/elf.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * ELF64 Type Definitions — Minimal set for loading executables
+ * AND for the kernel's own .rela.dyn relocation walk at boot.
+ *
+ * Follows the ELF specification (Tool Interface Standard, TIS).
+ * Only includes what's needed for static ELF64 executable loading.
+ *
+ * Reference: https://refspecs.linuxfoundation.org/elf/elf.pdf
+ */
+
+#include <lume/types.h>
+
+/* ================================================================== */
+/* Basic ELF types */
+/* ================================================================== */
+
+using Elf64_Addr = uint64;
+using Elf64_Off = uint64;
+using Elf64_Half = uint16;
+using Elf64_Word = uint32;
+using Elf64_Sword = int32;
+using Elf64_Xword = uint64;
+using Elf64_Sxword = int64;
+
+/* ================================================================== */
+/* ELF Header */
+/* ================================================================== */
+
+inline constexpr int EI_NIDENT = 16;
+
+struct Elf64_Ehdr {
+ uint8 e_ident[EI_NIDENT]; /* ELF identification */
+ Elf64_Half e_type; /* Object file type */
+ Elf64_Half e_machine; /* Machine type */
+ Elf64_Word e_version; /* Object file version */
+ Elf64_Addr e_entry; /* Entry point address */
+ Elf64_Off e_phoff; /* Program header table offset */
+ Elf64_Off e_shoff; /* Section header table offset */
+ Elf64_Word e_flags; /* Processor-specific flags */
+ Elf64_Half e_ehsize; /* ELF header size */
+ Elf64_Half e_phentsize; /* Program header entry size */
+ Elf64_Half e_phnum; /* Number of program headers */
+ Elf64_Half e_shentsize; /* Section header entry size */
+ Elf64_Half e_shnum; /* Number of section headers */
+ Elf64_Half e_shstrndx; /* Section name string table index */
+};
+
+/* e_ident[] indices */
+inline constexpr int EI_MAG0 = 0;
+inline constexpr int EI_MAG1 = 1;
+inline constexpr int EI_MAG2 = 2;
+inline constexpr int EI_MAG3 = 3;
+inline constexpr int EI_CLASS = 4;
+inline constexpr int EI_DATA = 5;
+inline constexpr int EI_VERSION = 6;
+inline constexpr int EI_OSABI = 7;
+
+/* e_ident[] magic */
+inline constexpr uint8 ELFMAG0 = 0x7f;
+inline constexpr uint8 ELFMAG1 = 'E';
+inline constexpr uint8 ELFMAG2 = 'L';
+inline constexpr uint8 ELFMAG3 = 'F';
+
+/* e_ident[EI_CLASS] */
+inline constexpr uint8 ELFCLASS64 = 2;
+
+/* e_ident[EI_DATA] */
+inline constexpr uint8 ELFDATA2LSB = 1; /* Little-endian */
+
+/* e_type */
+inline constexpr Elf64_Half ET_EXEC = 2; /* Executable */
+inline constexpr Elf64_Half ET_DYN = 3; /* Shared object (PIE) */
+
+/* e_machine */
+inline constexpr Elf64_Half EM_RISCV = 243;
+
+/* ================================================================== */
+/* Program Header */
+/* ================================================================== */
+
+struct Elf64_Phdr {
+ Elf64_Word p_type; /* Segment type */
+ Elf64_Word p_flags; /* Segment flags */
+ Elf64_Off p_offset; /* File offset */
+ Elf64_Addr p_vaddr; /* Virtual address */
+ Elf64_Addr p_paddr; /* Physical address (unused) */
+ Elf64_Xword p_filesz; /* Size in file */
+ Elf64_Xword p_memsz; /* Size in memory (>= p_filesz, diff is BSS) */
+ Elf64_Xword p_align; /* Alignment */
+};
+
+/* p_type values */
+inline constexpr Elf64_Word PT_NULL = 0;
+inline constexpr Elf64_Word PT_LOAD = 1;
+inline constexpr Elf64_Word PT_DYNAMIC = 2;
+inline constexpr Elf64_Word PT_INTERP = 3;
+inline constexpr Elf64_Word PT_NOTE = 4;
+inline constexpr Elf64_Word PT_PHDR = 6;
+inline constexpr Elf64_Word PT_GNU_STACK = 0x6474e551;
+
+/* p_flags values */
+inline constexpr Elf64_Word PF_X = 0x1; /* Execute */
+inline constexpr Elf64_Word PF_W = 0x2; /* Write */
+inline constexpr Elf64_Word PF_R = 0x4; /* Read */
+
+/* ================================================================== */
+/* Auxiliary Vector types */
+/* ================================================================== */
+
+struct Elf64_auxv_t {
+ uint64 a_type;
+ uint64 a_val;
+};
+
+inline constexpr uint64 AT_NULL = 0;
+inline constexpr uint64 AT_PHDR = 3;
+inline constexpr uint64 AT_PHENT = 4;
+inline constexpr uint64 AT_PHNUM = 5;
+inline constexpr uint64 AT_PAGESZ = 6;
+inline constexpr uint64 AT_ENTRY = 9;
+inline constexpr uint64 AT_UID = 11;
+inline constexpr uint64 AT_EUID = 12;
+inline constexpr uint64 AT_GID = 13;
+inline constexpr uint64 AT_EGID = 14;
+inline constexpr uint64 AT_RANDOM = 25;
+inline constexpr uint64 AT_BASE = 7; /* PIE load base address */
+
+/* ================================================================== */
+/* RELA relocation entry — used by the kernel's own .rela.dyn walk */
+/* at boot (relocate_kernel in arch/riscv/mm/init.cc). */
+/* ================================================================== */
+
+struct Elf64_Rela {
+ Elf64_Addr r_offset; /* link-time VA of the slot to patch */
+ Elf64_Xword r_info; /* r_sym << 32 | r_type */
+ Elf64_Sxword r_addend; /* link-time value of the target */
+};
+
+#define ELF64_R_TYPE(info) ((info) & 0xFFFFFFFFULL)
+
+/* RISC-V relocation types — only the ones a PIE-linked kernel emits
+ * into .rela.dyn under `-shared -Bsymbolic`. */
+inline constexpr uint32 R_RISCV_NONE = 0;
+inline constexpr uint32 R_RISCV_64 = 2;
+inline constexpr uint32 R_RISCV_RELATIVE = 3;
diff --git a/na/include/lume/errno.h b/na/include/lume/errno.h
index 089aa94..f3bf09f 100644
--- a/na/include/lume/errno.h
+++ b/na/include/lume/errno.h
@@ -1,151 +1,186 @@
-/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_ERRNO_H
+#define _LUME_ERRNO_H
+
/*
- * Standard error codes — asm-generic/errno-base.h + asm-generic/errno.h
+ * Error codes — numeric values aligned with Linux's asm-generic/errno-base.h,
+ * asm-generic/errno.h, and linux/errno.h (kernel-internal codes 512+).
+ *
+ * Userspace-visible codes (1..133) match POSIX / Linux exactly so a LumeOS
+ * binary returning -EINVAL to a user program receives the same number Linux
+ * would. Kernel-internal codes (>= 512) follow Linux's convention of being
+ * out of POSIX range; they are translated to a user-visible code (typically
+ * EINTR) before crossing into user mode.
*
- * LumeOS targets only asm-generic architectures (RISC-V, AArch64, LoongArch),
- * so a single unified file is sufficient. No per-arch overrides needed.
+ * Defined as plain #define so this header is usable from C, C++, and
+ * assembly (the macros expand to integer literals, no types).
*/
-#pragma once
+/* ===== Base errors (asm-generic/errno-base.h equivalent) ===== */
+#define EPERM 1 /* Operation not permitted */
+#define ENOENT 2 /* No such file or directory */
+#define ESRCH 3 /* No such process */
+#define EINTR 4 /* Interrupted system call */
+#define EIO 5 /* I/O error */
+#define ENXIO 6 /* No such device or address */
+#define E2BIG 7 /* Argument list too long */
+#define ENOEXEC 8 /* Exec format error */
+#define EBADF 9 /* Bad file number */
+#define ECHILD 10 /* No child processes */
+#define EAGAIN 11 /* Try again */
+#define ENOMEM 12 /* Out of memory */
+#define EACCES 13 /* Permission denied */
+#define EFAULT 14 /* Bad address */
+#define ENOTBLK 15 /* Block device required */
+#define EBUSY 16 /* Device or resource busy */
+#define EEXIST 17 /* File exists */
+#define EXDEV 18 /* Cross-device link */
+#define ENODEV 19 /* No such device */
+#define ENOTDIR 20 /* Not a directory */
+#define EISDIR 21 /* Is a directory */
+#define EINVAL 22 /* Invalid argument */
+#define ENFILE 23 /* File table overflow */
+#define EMFILE 24 /* Too many open files */
+#define ENOTTY 25 /* Not a typewriter */
+#define ETXTBSY 26 /* Text file busy */
+#define EFBIG 27 /* File too large */
+#define ENOSPC 28 /* No space left on device */
+#define ESPIPE 29 /* Illegal seek */
+#define EROFS 30 /* Read-only file system */
+#define EMLINK 31 /* Too many links */
+#define EPIPE 32 /* Broken pipe */
+#define EDOM 33 /* Math argument out of domain */
+#define ERANGE 34 /* Math result not representable */
-/* ====== errno-base.h (1-34) ====== */
+/* ===== Extended errors (asm-generic/errno.h equivalent) ===== */
+#define EDEADLK 35 /* Resource deadlock would occur */
+#define ENAMETOOLONG 36 /* File name too long */
+#define ENOLCK 37 /* No record locks available */
+#define ENOSYS 38 /* Invalid system call number */
+#define ENOTEMPTY 39 /* Directory not empty */
+#define ELOOP 40 /* Too many symbolic links encountered */
+#define EWOULDBLOCK EAGAIN /* Operation would block */
+#define ENOMSG 42 /* No message of desired type */
+#define EIDRM 43 /* Identifier removed */
+#define ECHRNG 44 /* Channel number out of range */
+#define EL2NSYNC 45 /* Level 2 not synchronized */
+#define EL3HLT 46 /* Level 3 halted */
+#define EL3RST 47 /* Level 3 reset */
+#define ELNRNG 48 /* Link number out of range */
+#define EUNATCH 49 /* Protocol driver not attached */
+#define ENOCSI 50 /* No CSI structure available */
+#define EL2HLT 51 /* Level 2 halted */
+#define EBADE 52 /* Invalid exchange */
+#define EBADR 53 /* Invalid request descriptor */
+#define EXFULL 54 /* Exchange full */
+#define ENOANO 55 /* No anode */
+#define EBADRQC 56 /* Invalid request code */
+#define EBADSLT 57 /* Invalid slot */
+#define EDEADLOCK EDEADLK
+#define EBFONT 59 /* Bad font file format */
+#define ENOSTR 60 /* Device not a stream */
+#define ENODATA 61 /* No data available */
+#define ETIME 62 /* Timer expired */
+#define ENOSR 63 /* Out of streams resources */
+#define ENONET 64 /* Machine is not on the network */
+#define ENOPKG 65 /* Package not installed */
+#define EREMOTE 66 /* Object is remote */
+#define ENOLINK 67 /* Link has been severed */
+#define EADV 68 /* Advertise error */
+#define ESRMNT 69 /* Srmount error */
+#define ECOMM 70 /* Communication error on send */
+#define EPROTO 71 /* Protocol error */
+#define EMULTIHOP 72 /* Multihop attempted */
+#define EDOTDOT 73 /* RFS specific error */
+#define EBADMSG 74 /* Not a data message */
+#define EOVERFLOW 75 /* Value too large for defined data type */
+#define ENOTUNIQ 76 /* Name not unique on network */
+#define EBADFD 77 /* File descriptor in bad state */
+#define EREMCHG 78 /* Remote address changed */
+#define ELIBACC 79 /* Cannot access a needed shared library */
+#define ELIBBAD 80 /* Accessing a corrupted shared library */
+#define ELIBSCN 81 /* .lib section in a.out corrupted */
+#define ELIBMAX 82 /* Linking in too many shared libraries */
+#define ELIBEXEC 83 /* Cannot exec a shared library directly */
+#define EILSEQ 84 /* Illegal byte sequence */
+#define ERESTART 85 /* Interrupted system call should be restarted */
+#define ESTRPIPE 86 /* Streams pipe error */
+#define EUSERS 87 /* Too many users */
+#define ENOTSOCK 88 /* Socket operation on non-socket */
+#define EDESTADDRREQ 89 /* Destination address required */
+#define EMSGSIZE 90 /* Message too long */
+#define EPROTOTYPE 91 /* Protocol wrong type for socket */
+#define ENOPROTOOPT 92 /* Protocol not available */
+#define EPROTONOSUPPORT 93 /* Protocol not supported */
+#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
+#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
+#define ENOTSUP EOPNOTSUPP
+#define EPFNOSUPPORT 96 /* Protocol family not supported */
+#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
+#define EADDRINUSE 98 /* Address already in use */
+#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
+#define ENETDOWN 100 /* Network is down */
+#define ENETUNREACH 101 /* Network is unreachable */
+#define ENETRESET 102 /* Network dropped connection because of reset */
+#define ECONNABORTED 103 /* Software caused connection abort */
+#define ECONNRESET 104 /* Connection reset by peer */
+#define ENOBUFS 105 /* No buffer space available */
+#define EISCONN 106 /* Transport endpoint is already connected */
+#define ENOTCONN 107 /* Transport endpoint is not connected */
+#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
+#define ETOOMANYREFS 109 /* Too many references: cannot splice */
+#define ETIMEDOUT 110 /* Connection timed out */
+#define ECONNREFUSED 111 /* Connection refused */
+#define EHOSTDOWN 112 /* Host is down */
+#define EHOSTUNREACH 113 /* No route to host */
+#define EALREADY 114 /* Operation already in progress */
+#define EINPROGRESS 115 /* Operation now in progress */
+#define ESTALE 116 /* Stale file handle */
+#define EUCLEAN 117 /* Structure needs cleaning */
+#define ENOTNAM 118 /* Not a XENIX named type file */
+#define ENAVAIL 119 /* No XENIX semaphores available */
+#define EISNAM 120 /* Is a named type file */
+#define EREMOTEIO 121 /* Remote I/O error */
+#define EDQUOT 122 /* Quota exceeded */
+#define ENOMEDIUM 123 /* No medium found */
+#define EMEDIUMTYPE 124 /* Wrong medium type */
+#define ECANCELED 125 /* Operation Canceled */
+#define ENOKEY 126 /* Required key not available */
+#define EKEYEXPIRED 127 /* Key has expired */
+#define EKEYREVOKED 128 /* Key has been revoked */
+#define EKEYREJECTED 129 /* Key was rejected by service */
+#define EOWNERDEAD 130 /* Owner died (for robust mutexes) */
+#define ENOTRECOVERABLE 131 /* State not recoverable */
+#define ERFKILL 132 /* Operation not possible due to RF-kill */
+#define EHWPOISON 133 /* Memory page has hardware error */
-#define EPERM 1 /* Operation not permitted */
-#define ENOENT 2 /* No such file or directory */
-#define ESRCH 3 /* No such process */
-#define EINTR 4 /* Interrupted system call */
-#define EIO 5 /* I/O error */
-#define ENXIO 6 /* No such device or address */
-#define E2BIG 7 /* Argument list too long */
-#define ENOEXEC 8 /* Exec format error */
-#define EBADF 9 /* Bad file number */
-#define ECHILD 10 /* No child processes */
-#define EAGAIN 11 /* Try again */
-#define ENOMEM 12 /* Out of memory */
-#define EACCES 13 /* Permission denied */
-#define EFAULT 14 /* Bad address */
-#define ENOTBLK 15 /* Block device required */
-#define EBUSY 16 /* Device or resource busy */
-#define EEXIST 17 /* File exists */
-#define EXDEV 18 /* Cross-device link */
-#define ENODEV 19 /* No such device */
-#define ENOTDIR 20 /* Not a directory */
-#define EISDIR 21 /* Is a directory */
-#define EINVAL 22 /* Invalid argument */
-#define ENFILE 23 /* File table overflow */
-#define EMFILE 24 /* Too many open files */
-#define ENOTTY 25 /* Not a typewriter */
-#define ETXTBSY 26 /* Text file busy */
-#define EFBIG 27 /* File too large */
-#define ENOSPC 28 /* No space left on device */
-#define ESPIPE 29 /* Illegal seek */
-#define EROFS 30 /* Read-only file system */
-#define EMLINK 31 /* Too many links */
-#define EPIPE 32 /* Broken pipe */
-#define EDOM 33 /* Math argument out of domain of func */
-#define ERANGE 34 /* Math result not representable */
-
-/* ====== errno.h (35-133) ====== */
+/* ===== Kernel-internal errors (linux/errno.h equivalent, >= 512) =====
+ * These should never be seen by user programs. signal/syscall layer
+ * translates them to a user-visible code (typically EINTR) before
+ * returning to userspace.
+ */
+#define ERESTARTSYS 512
+#define ERESTARTNOINTR 513
+#define ERESTARTNOHAND 514 /* restart if no signal handler */
+#define ENOIOCTLCMD 515 /* No ioctl command */
+#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */
+#define EPROBE_DEFER 517 /* Driver requests probe retry */
+#define EOPENSTALE 518 /* open() found a stale dentry */
+#define ENOPARAM 519 /* Parameter not supported */
-#define EDEADLK 35 /* Resource deadlock would occur */
-#define ENAMETOOLONG 36 /* File name too long */
-#define ENOLCK 37 /* No record locks available */
-#define ENOSYS 38 /* Invalid system call number */
-#define ENOTEMPTY 39 /* Directory not empty */
-#define ELOOP 40 /* Too many symbolic links encountered */
-#define EWOULDBLOCK EAGAIN /* Operation would block */
-#define ENOMSG 42 /* No message of desired type */
-#define EIDRM 43 /* Identifier removed */
-#define ECHRNG 44 /* Channel number out of range */
-#define EL2NSYNC 45 /* Level 2 not synchronized */
-#define EL3HLT 46 /* Level 3 halted */
-#define EL3RST 47 /* Level 3 reset */
-#define ELNRNG 48 /* Link number out of range */
-#define EUNATCH 49 /* Protocol driver not attached */
-#define ENOCSI 50 /* No CSI structure available */
-#define EL2HLT 51 /* Level 2 halted */
-#define EBADE 52 /* Invalid exchange */
-#define EBADR 53 /* Invalid request descriptor */
-#define EXFULL 54 /* Exchange full */
-#define ENOANO 55 /* No anode */
-#define EBADRQC 56 /* Invalid request code */
-#define EBADSLT 57 /* Invalid slot */
-#define EDEADLOCK EDEADLK
-#define EBFONT 59 /* Bad font file format */
-#define ENOSTR 60 /* Device not a stream */
-#define ENODATA 61 /* No data available */
-#define ETIME 62 /* Timer expired */
-#define ENOSR 63 /* Out of streams resources */
-#define ENONET 64 /* Machine is not on the network */
-#define ENOPKG 65 /* Package not installed */
-#define EREMOTE 66 /* Object is remote */
-#define ENOLINK 67 /* Link has been severed */
-#define EADV 68 /* Advertise error */
-#define ESRMNT 69 /* Srmount error */
-#define ECOMM 70 /* Communication error on send */
-#define EPROTO 71 /* Protocol error */
-#define EMULTIHOP 72 /* Multihop attempted */
-#define EDOTDOT 73 /* RFS specific error */
-#define EBADMSG 74 /* Not a data message */
-#define EOVERFLOW 75 /* Value too large for defined data type */
-#define ENOTUNIQ 76 /* Name not unique on network */
-#define EBADFD 77 /* File descriptor in bad state */
-#define EREMCHG 78 /* Remote address changed */
-#define ELIBACC 79 /* Can not access a needed shared library */
-#define ELIBBAD 80 /* Accessing a corrupted shared library */
-#define ELIBSCN 81 /* .lib section in a.out corrupted */
-#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
-#define ELIBEXEC 83 /* Cannot exec a shared library directly */
-#define EILSEQ 84 /* Illegal byte sequence */
-#define ERESTART 85 /* Interrupted system call should be restarted */
-#define ESTRPIPE 86 /* Streams pipe error */
-#define EUSERS 87 /* Too many users */
-#define ENOTSOCK 88 /* Socket operation on non-socket */
-#define EDESTADDRREQ 89 /* Destination address required */
-#define EMSGSIZE 90 /* Message too long */
-#define EPROTOTYPE 91 /* Protocol wrong type for socket */
-#define ENOPROTOOPT 92 /* Protocol not available */
-#define EPROTONOSUPPORT 93 /* Protocol not supported */
-#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
-#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
-#define EPFNOSUPPORT 96 /* Protocol family not supported */
-#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
-#define EADDRINUSE 98 /* Address already in use */
-#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
-#define ENETDOWN 100 /* Network is down */
-#define ENETUNREACH 101 /* Network is unreachable */
-#define ENETRESET 102 /* Network dropped connection because of reset */
-#define ECONNABORTED 103 /* Software caused connection abort */
-#define ECONNRESET 104 /* Connection reset by peer */
-#define ENOBUFS 105 /* No buffer space available */
-#define EISCONN 106 /* Transport endpoint is already connected */
-#define ENOTCONN 107 /* Transport endpoint is not connected */
-#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
-#define ETOOMANYREFS 109 /* Too many references: cannot splice */
-#define ETIMEDOUT 110 /* Connection timed out */
-#define ECONNREFUSED 111 /* Connection refused */
-#define EHOSTDOWN 112 /* Host is down */
-#define EHOSTUNREACH 113 /* No route to host */
-#define EALREADY 114 /* Operation already in progress */
-#define EINPROGRESS 115 /* Operation now in progress */
-#define ESTALE 116 /* Stale file handle */
-#define EUCLEAN 117 /* Structure needs cleaning */
-#define ENOTNAM 118 /* Not a XENIX named type file */
-#define ENAVAIL 119 /* No XENIX semaphores available */
-#define EISNAM 120 /* Is a named type file */
-#define EREMOTEIO 121 /* Remote I/O error */
-#define EDQUOT 122 /* Quota exceeded */
-#define ENOMEDIUM 123 /* No medium found */
-#define EMEDIUMTYPE 124 /* Wrong medium type */
-#define ECANCELED 125 /* Operation Canceled */
-#define ENOKEY 126 /* Required key not available */
-#define EKEYEXPIRED 127 /* Key has expired */
-#define EKEYREVOKED 128 /* Key has been revoked */
-#define EKEYREJECTED 129 /* Key was rejected by service */
+/* NFSv3 reserved range (521..531) */
+#define EBADHANDLE 521 /* Illegal NFS file handle */
+#define ENOTSYNC 522 /* Update synchronization mismatch */
+#define EBADCOOKIE 523 /* Cookie is stale */
+#define ENOTSUPP 524 /* Operation is not supported */
+#define ETOOSMALL 525 /* Buffer or request is too small */
+#define ESERVERFAULT 526 /* An untranslatable error occurred */
+#define EBADTYPE 527 /* Type not supported by server */
+#define EJUKEBOX 528 /* Request initiated, will not complete before timeout */
+#define EIOCBQUEUED 529 /* iocb queued, will get completion event */
+#define ERECALLCONFLICT 530 /* conflict with recalled state */
+#define ENOGRACE 531 /* NFS file lock reclaim refused */
-/* for robust mutexes */
-#define EOWNERDEAD 130 /* Owner died */
-#define ENOTRECOVERABLE 131 /* State not recoverable */
-#define ERFKILL 132 /* Operation not possible due to RF-kill */
-#define EHWPOISON 133 /* Memory page has hardware error */ \ No newline at end of file
+#endif /* _LUME_ERRNO_H */
diff --git a/na/include/lume/fdtable.h b/na/include/lume/fdtable.h
new file mode 100644
index 0000000..6159519
--- /dev/null
+++ b/na/include/lume/fdtable.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * fdtable.h — Per-task file descriptor table
+ *
+ * Modeled after Linux include/linux/fdtable.h.
+ * Reference: docs/vfs/05_fdtable.md
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+#include <kernel/sync/spinlock.h>
+
+struct file;
+
+/* ================================================================== */
+/* files_struct — per-task open file table */
+/* ================================================================== */
+
+inline constexpr int NR_OPEN_DEFAULT = 256;
+
+struct files_struct {
+ lume::atomic<int32> count; /* Reference count (CLONE_FILES) */
+ Spinlock file_lock;
+
+ int next_fd; /* Hint for next free fd */
+ int max_fds; /* Current max (= NR_OPEN_DEFAULT) */
+ file* fd_array[NR_OPEN_DEFAULT];
+ uint64 close_on_exec_init[(NR_OPEN_DEFAULT + 63) / 64];
+ uint64 open_fds_init[(NR_OPEN_DEFAULT + 63) / 64];
+};
+
+/* ================================================================== */
+/* API */
+/* ================================================================== */
+
+files_struct* alloc_files_struct();
+void put_files_struct(files_struct* files);
+files_struct* dup_fd(files_struct* old_files);
+
+int alloc_fd(files_struct* files, unsigned int start);
+void fd_install(files_struct* files, int fd, file* fp);
+file* fcheck(files_struct* files, int fd);
+int close_fd(files_struct* files, int fd);
+int do_dup2(files_struct* files, int oldfd, int newfd, int flags);
+void do_close_on_exec(files_struct* files);
diff --git a/na/include/lume/file.h b/na/include/lume/file.h
new file mode 100644
index 0000000..349059b
--- /dev/null
+++ b/na/include/lume/file.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * file.h — Open file description + file_operations + path + vfsmount
+ *
+ * Modeled after Linux include/linux/fs.h struct file / file_operations.
+ * Reference: docs/vfs/04_file.md
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+#include <lume/fs_types.h>
+#include <kernel/sync/spinlock.h>
+
+struct inode;
+struct dentry;
+struct super_block;
+struct address_space;
+struct dir_context;
+struct VmAreaStruct;
+struct kstat;
+
+/* ================================================================== */
+/* vfsmount (public part) */
+/* ================================================================== */
+
+struct vfsmount {
+ dentry* mnt_root;
+ super_block* mnt_sb;
+ int mnt_flags;
+};
+
+vfsmount* mntget(vfsmount* mnt);
+void mntput(vfsmount* mnt);
+
+/* ================================================================== */
+/* path */
+/* ================================================================== */
+
+struct path {
+ vfsmount* mnt;
+ dentry* de; /* Linux calls this 'dentry' but that conflicts in C++ */
+};
+
+void path_get(const path* p);
+void path_put(const path* p);
+
+/* ================================================================== */
+/* file */
+/* ================================================================== */
+
+struct file_operations;
+
+struct file {
+ Spinlock f_lock;
+ uint32 f_mode; /* FMODE_* */
+ const file_operations* f_op;
+ address_space* f_mapping;
+ void* private_data;
+ inode* f_inode;
+ unsigned int f_flags; /* O_* */
+
+ path f_path;
+
+ int64 f_pos;
+ lume::atomic<int32> f_count;
+};
+
+/* ================================================================== */
+/* Accessors */
+/* ================================================================== */
+
+inline inode* file_inode(const file* f) { return f->f_inode; }
+inline dentry* file_dentry(const file* f) { return f->f_path.de; }
+
+file* get_file(file* f);
+file* alloc_empty_file(int flags);
+void fput(file* fp);
+
+/* ================================================================== */
+/* dir_context (for iterate_shared / getdents64) */
+/* ================================================================== */
+
+struct dir_context;
+using filldir_t = bool (*)(dir_context* ctx, const char* name, int namelen,
+ int64 offset, uint64 ino, unsigned int d_type);
+
+struct dir_context {
+ filldir_t actor;
+ int64 pos;
+};
+
+/* ================================================================== */
+/* file_operations */
+/* ================================================================== */
+
+struct file_operations {
+ int64 (*llseek)(file* fp, int64 offset, int whence);
+ int64 (*read)(file* fp, char* buf, uint64 count, int64* pos);
+ int64 (*write)(file* fp, const char* buf, uint64 count, int64* pos);
+ int (*iterate_shared)(file* fp, dir_context* ctx);
+ uint32 (*poll)(file* fp, void* pt);
+ long (*unlocked_ioctl)(file* fp, unsigned int cmd, uint64 arg);
+ int (*mmap)(file* fp, VmAreaStruct* vma);
+ int (*open)(inode* ip, file* fp);
+ int (*flush)(file* fp);
+ int (*release)(inode* ip, file* fp);
+ int (*fsync)(file* fp, int64 start, int64 end, int datasync);
+};
+
+/* ================================================================== */
+/* VFS read/write/seek */
+/* ================================================================== */
+
+int64 vfs_read(file* fp, char* buf, uint64 count, int64* pos);
+int64 vfs_write(file* fp, const char* buf, uint64 count, int64* pos);
+int64 vfs_llseek(file* fp, int64 offset, int whence);
+int vfs_fstat(file* fp, kstat* stat);
+
+/* Generic helpers for simple fs */
+int64 generic_file_llseek(file* fp, int64 offset, int whence);
+
+/* ================================================================== */
+/* path helpers */
+/* ================================================================== */
+
+inline bool path_equal(const path* a, const path* b) {
+ return a->mnt == b->mnt && a->de == b->de;
+}
diff --git a/na/include/lume/fs.h b/na/include/lume/fs.h
new file mode 100644
index 0000000..8dac552
--- /dev/null
+++ b/na/include/lume/fs.h
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * fs.h — Core VFS data structures
+ *
+ * Defines super_block, inode, address_space, and their operations.
+ * Modeled after Linux include/linux/fs.h + include/linux/fs/super_types.h.
+ *
+ * Reference: docs/vfs/01_super_block.md, docs/vfs/02_inode.md
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <lume/atomic.h>
+#include <lume/fs_types.h>
+#include <kernel/sync/spinlock.h>
+
+/* Forward declarations */
+struct super_block;
+struct inode;
+struct dentry;
+struct file;
+struct vfsmount;
+struct path;
+struct kstat;
+struct iattr;
+struct file_operations;
+struct inode_operations;
+struct dentry_operations;
+struct dir_context;
+struct pipe_inode_info;
+struct VmAreaStruct;
+struct Page;
+
+/* ================================================================== */
+/* address_space_operations */
+/* ================================================================== */
+
+struct address_space_operations {
+ int (*read_folio)(file* fp, Page* page);
+ int (*write_begin)(struct address_space* mapping, int64 pos,
+ unsigned len, Page** pagep, void** fsdata);
+ int (*write_end)(struct address_space* mapping, int64 pos,
+ unsigned len, unsigned copied,
+ Page* page, void* fsdata);
+};
+
+/* ================================================================== */
+/* address_space */
+/* ================================================================== */
+
+struct address_space {
+ inode* host;
+ const address_space_operations* a_ops;
+ uint64 nrpages;
+ uint64 flags;
+ Spinlock i_private_lock;
+};
+
+/* ================================================================== */
+/* super_operations */
+/* ================================================================== */
+
+struct super_operations {
+ inode* (*alloc_inode)(super_block* sb);
+ void (*destroy_inode)(inode* ip);
+ void (*free_inode)(inode* ip);
+
+ void (*dirty_inode)(inode* ip, int flags);
+ int (*write_inode)(inode* ip, bool sync);
+ int (*drop_inode)(inode* ip);
+ void (*evict_inode)(inode* ip);
+ void (*put_super)(super_block* sb);
+ int (*sync_fs)(super_block* sb, int wait);
+ int (*statfs)(dentry* de, struct kstatfs* buf);
+ void (*umount_begin)(super_block* sb);
+};
+
+/* ================================================================== */
+/* file_system_type */
+/* ================================================================== */
+
+struct file_system_type {
+ const char* name;
+ int fs_flags;
+ super_block* (*mount)(file_system_type* type, int flags,
+ const char* dev_name, void* data);
+ void (*kill_sb)(super_block* sb);
+ list_node fs_list;
+};
+
+int register_filesystem(file_system_type* fs);
+file_system_type* get_fs_type(const char* name);
+
+/* ================================================================== */
+/* super_block */
+/* ================================================================== */
+
+struct mount;
+
+struct super_block {
+ list_node s_list;
+ uint64 s_dev;
+ uint8 s_blocksize_bits;
+ uint64 s_blocksize;
+ int64 s_maxbytes;
+ file_system_type* s_type;
+ const super_operations* s_op;
+
+ uint64 s_flags;
+ uint64 s_magic;
+ dentry* s_root;
+
+ char s_id[32];
+ void* s_fs_info;
+
+ uint32 s_time_gran;
+ int64 s_time_min;
+ int64 s_time_max;
+
+ Spinlock s_inode_list_lock;
+ list_head s_inodes;
+
+ lume::atomic<int32> s_active;
+ int s_count;
+
+ mount* s_mounts;
+ uint32 s_max_links;
+ const dentry_operations* s_d_op;
+};
+
+/* ================================================================== */
+/* inode */
+/* ================================================================== */
+
+struct inode {
+ /* ---- Path-walk hot fields ---- */
+ uint32 i_mode;
+ uint32 i_flags;
+ uint32 i_uid;
+ uint32 i_gid;
+
+ const inode_operations* i_op;
+ super_block* i_sb;
+ address_space* i_mapping;
+
+ /* ---- Stat data ---- */
+ uint64 i_ino;
+ uint32 i_nlink; /* Use set_nlink/inc_nlink/drop_nlink */
+ uint32 i_rdev;
+ int64 i_size;
+
+ int64 i_atime_sec;
+ int64 i_mtime_sec;
+ int64 i_ctime_sec;
+ uint32 i_atime_nsec;
+ uint32 i_mtime_nsec;
+ uint32 i_ctime_nsec;
+ uint32 i_generation;
+
+ Spinlock i_lock;
+ unsigned short i_bytes;
+ uint8 i_blkbits;
+ int64 i_blocks;
+
+ /* ---- State ---- */
+ uint32 i_state;
+
+ /* ---- Lists ---- */
+ list_node i_sb_list;
+ list_head i_dentry;
+
+ /* ---- Counters ---- */
+ lume::atomic<int32> i_count;
+ lume::atomic<int32> i_writecount;
+
+ /* ---- File ops ---- */
+ const file_operations* i_fop;
+
+ /* ---- Embedded address_space ---- */
+ address_space i_data;
+
+ /* ---- Type-specific ---- */
+ union {
+ pipe_inode_info* i_pipe;
+ char* i_link;
+ uint32 i_dir_seq;
+ };
+
+ void* i_private;
+};
+
+/* ================================================================== */
+/* inode_operations */
+/* ================================================================== */
+
+struct inode_operations {
+ dentry* (*lookup)(inode* dir, dentry* de, unsigned int flags);
+ int (*create)(inode* dir, dentry* de, uint32 mode, bool excl);
+ int (*link)(dentry* old_de, inode* dir, dentry* new_de);
+ int (*unlink)(inode* dir, dentry* de);
+ int (*symlink)(inode* dir, dentry* de, const char* target);
+ int (*mkdir)(inode* dir, dentry* de, uint32 mode);
+ int (*rmdir)(inode* dir, dentry* de);
+ int (*mknod)(inode* dir, dentry* de, uint32 mode, uint32 rdev);
+ int (*rename)(inode* old_dir, dentry* old_de,
+ inode* new_dir, dentry* new_de, unsigned int flags);
+ int (*setattr)(dentry* de, iattr* attr);
+ int (*getattr)(const path* p, kstat* stat, uint32 request_mask,
+ unsigned int query_flags);
+ const char* (*get_link)(dentry* de, inode* ip);
+ int (*permission)(inode* ip, int mask);
+};
+
+/* ================================================================== */
+/* Inode API */
+/* ================================================================== */
+
+inode* new_inode(super_block* sb);
+inode* iget(super_block* sb, uint64 ino);
+void iput(inode* ip);
+void ihold(inode* ip);
+void mark_inode_dirty(inode* ip);
+
+void set_nlink(inode* ip, uint32 nlink);
+void inc_nlink(inode* ip);
+void drop_nlink(inode* ip);
+void clear_nlink(inode* ip);
+
+void inode_init_owner(inode* ip, const inode* dir, uint32 mode);
+
+/* Generic helpers used by simple filesystems */
+void generic_fillattr(inode* ip, kstat* stat);
+
+/* ================================================================== */
+/* kstat (matches oscomp ABI layout exactly) */
+/* ================================================================== */
+
+struct kstat {
+ uint64 st_dev;
+ uint64 st_ino;
+ uint32 st_mode;
+ uint32 st_nlink;
+ uint32 st_uid;
+ uint32 st_gid;
+ uint64 st_rdev;
+ uint64 __pad;
+ int64 st_size;
+ int32 st_blksize;
+ int32 __pad2;
+ int64 st_blocks;
+ int64 st_atime_sec;
+ int64 st_atime_nsec;
+ int64 st_mtime_sec;
+ int64 st_mtime_nsec;
+ int64 st_ctime_sec;
+ int64 st_ctime_nsec;
+ uint32 __unused[2];
+};
+
+struct kstatfs {
+ int64 f_type;
+ int64 f_bsize;
+ uint64 f_blocks;
+ uint64 f_bfree;
+ uint64 f_bavail;
+ uint64 f_files;
+ uint64 f_ffree;
+ int32 f_fsid[2];
+ int64 f_namelen;
+ int64 f_frsize;
+ int64 f_flags;
+ int64 f_spare[4];
+};
+
+/* ================================================================== */
+/* iattr (for setattr) */
+/* ================================================================== */
+
+struct iattr {
+ unsigned int ia_valid; /* ATTR_* flags */
+ uint32 ia_mode;
+ uint32 ia_uid;
+ uint32 ia_gid;
+ int64 ia_size;
+ int64 ia_atime_sec;
+ int64 ia_atime_nsec;
+ int64 ia_mtime_sec;
+ int64 ia_mtime_nsec;
+ int64 ia_ctime_sec;
+ int64 ia_ctime_nsec;
+};
+
+/* ATTR_* flags */
+inline constexpr uint32 ATTR_MODE = (1U << 0);
+inline constexpr uint32 ATTR_UID = (1U << 1);
+inline constexpr uint32 ATTR_GID = (1U << 2);
+inline constexpr uint32 ATTR_SIZE = (1U << 3);
+inline constexpr uint32 ATTR_ATIME = (1U << 4);
+inline constexpr uint32 ATTR_MTIME = (1U << 5);
+inline constexpr uint32 ATTR_CTIME = (1U << 6);
+
+/* ================================================================== */
+/* VFS init */
+/* ================================================================== */
+
+void vfs_init();
diff --git a/na/include/lume/fs_struct.h b/na/include/lume/fs_struct.h
new file mode 100644
index 0000000..ba643b5
--- /dev/null
+++ b/na/include/lume/fs_struct.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * fs_struct.h — Per-task filesystem context (cwd, root)
+ *
+ * Modeled after Linux include/linux/fs_struct.h.
+ * Reference: docs/vfs/05_fdtable.md
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+#include <lume/file.h> /* struct path */
+#include <kernel/sync/spinlock.h>
+
+/* ================================================================== */
+/* fs_struct */
+/* ================================================================== */
+
+struct fs_struct {
+ lume::atomic<int32> count; /* Reference count (CLONE_FS) */
+ Spinlock lock;
+ int umask; /* File creation mask */
+ path root; /* Root directory */
+ path pwd; /* Current working directory */
+};
+
+/* ================================================================== */
+/* API */
+/* ================================================================== */
+
+fs_struct* alloc_fs_struct();
+void put_fs_struct(fs_struct* fs);
+fs_struct* copy_fs_struct(const fs_struct* old_fs);
+
+void set_fs_root(fs_struct* fs, const path* p);
+void set_fs_pwd(fs_struct* fs, const path* p);
diff --git a/na/include/lume/fs_types.h b/na/include/lume/fs_types.h
new file mode 100644
index 0000000..767f37c
--- /dev/null
+++ b/na/include/lume/fs_types.h
@@ -0,0 +1,197 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * fs_types.h — File mode constants, open flags, seek constants
+ *
+ * All values match Linux asm-generic so musl/busybox binaries work.
+ * Reference: docs/vfs/02_inode.md, docs/vfs/04_file.md
+ */
+
+#include <lume/types.h>
+
+/* ================================================================== */
+/* S_IF* — file type in inode::i_mode upper bits */
+/* ================================================================== */
+
+inline constexpr uint32 S_IFMT = 0170000;
+inline constexpr uint32 S_IFSOCK = 0140000;
+inline constexpr uint32 S_IFLNK = 0120000;
+inline constexpr uint32 S_IFREG = 0100000;
+inline constexpr uint32 S_IFBLK = 0060000;
+inline constexpr uint32 S_IFDIR = 0040000;
+inline constexpr uint32 S_IFCHR = 0020000;
+inline constexpr uint32 S_IFIFO = 0010000;
+
+inline constexpr uint32 S_ISUID = 04000;
+inline constexpr uint32 S_ISGID = 02000;
+inline constexpr uint32 S_ISVTX = 01000;
+
+inline constexpr uint32 S_IRWXU = 00700;
+inline constexpr uint32 S_IRUSR = 00400;
+inline constexpr uint32 S_IWUSR = 00200;
+inline constexpr uint32 S_IXUSR = 00100;
+inline constexpr uint32 S_IRWXG = 00070;
+inline constexpr uint32 S_IRGRP = 00040;
+inline constexpr uint32 S_IWGRP = 00020;
+inline constexpr uint32 S_IXGRP = 00010;
+inline constexpr uint32 S_IRWXO = 00007;
+inline constexpr uint32 S_IROTH = 00004;
+inline constexpr uint32 S_IWOTH = 00002;
+inline constexpr uint32 S_IXOTH = 00001;
+
+inline bool S_ISREG(uint32 m) { return (m & S_IFMT) == S_IFREG; }
+inline bool S_ISDIR(uint32 m) { return (m & S_IFMT) == S_IFDIR; }
+inline bool S_ISCHR(uint32 m) { return (m & S_IFMT) == S_IFCHR; }
+inline bool S_ISBLK(uint32 m) { return (m & S_IFMT) == S_IFBLK; }
+inline bool S_ISFIFO(uint32 m) { return (m & S_IFMT) == S_IFIFO; }
+inline bool S_ISLNK(uint32 m) { return (m & S_IFMT) == S_IFLNK; }
+inline bool S_ISSOCK(uint32 m) { return (m & S_IFMT) == S_IFSOCK; }
+
+/* ================================================================== */
+/* O_* — open(2) flags */
+/* ================================================================== */
+
+inline constexpr int O_RDONLY = 00;
+inline constexpr int O_WRONLY = 01;
+inline constexpr int O_RDWR = 02;
+inline constexpr int O_ACCMODE = 03;
+inline constexpr int O_CREAT = 0100;
+inline constexpr int O_EXCL = 0200;
+inline constexpr int O_NOCTTY = 0400;
+inline constexpr int O_TRUNC = 01000;
+inline constexpr int O_APPEND = 02000;
+inline constexpr int O_NONBLOCK = 04000;
+inline constexpr int O_DSYNC = 010000;
+inline constexpr int O_DIRECTORY = 0200000;
+inline constexpr int O_NOFOLLOW = 0400000;
+inline constexpr int O_CLOEXEC = 02000000;
+
+/* ================================================================== */
+/* AT_* — *at() syscall dirfd constants */
+/* ================================================================== */
+
+inline constexpr int AT_FDCWD = -100;
+inline constexpr int AT_REMOVEDIR = 0x200;
+inline constexpr int AT_SYMLINK_NOFOLLOW = 0x100;
+inline constexpr int AT_EMPTY_PATH = 0x1000;
+
+/* ================================================================== */
+/* SEEK_* — lseek(2) whence */
+/* ================================================================== */
+
+inline constexpr int SEEK_SET = 0;
+inline constexpr int SEEK_CUR = 1;
+inline constexpr int SEEK_END = 2;
+
+/* ================================================================== */
+/* FMODE_* — internal file mode flags */
+/* ================================================================== */
+
+inline constexpr uint32 FMODE_READ = (1U << 0);
+inline constexpr uint32 FMODE_WRITE = (1U << 1);
+inline constexpr uint32 FMODE_LSEEK = (1U << 2);
+inline constexpr uint32 FMODE_PREAD = (1U << 3);
+inline constexpr uint32 FMODE_PWRITE = (1U << 4);
+inline constexpr uint32 FMODE_EXEC = (1U << 5);
+inline constexpr uint32 FMODE_CAN_READ = (1U << 17);
+inline constexpr uint32 FMODE_CAN_WRITE = (1U << 18);
+inline constexpr uint32 FMODE_OPENED = (1U << 19);
+inline constexpr uint32 FMODE_CREATED = (1U << 20);
+inline constexpr uint32 FMODE_STREAM = (1U << 21);
+
+/* ================================================================== */
+/* MAY_* — permission check flags */
+/* ================================================================== */
+
+inline constexpr int MAY_EXEC = 0x00000001;
+inline constexpr int MAY_WRITE = 0x00000002;
+inline constexpr int MAY_READ = 0x00000004;
+inline constexpr int MAY_OPEN = 0x00000020;
+inline constexpr int MAY_CHDIR = 0x00000040;
+
+/* ================================================================== */
+/* DT_* — directory entry types (for getdents64) */
+/* ================================================================== */
+
+inline constexpr uint8 DT_UNKNOWN = 0;
+inline constexpr uint8 DT_FIFO = 1;
+inline constexpr uint8 DT_CHR = 2;
+inline constexpr uint8 DT_DIR = 4;
+inline constexpr uint8 DT_BLK = 6;
+inline constexpr uint8 DT_REG = 8;
+inline constexpr uint8 DT_LNK = 10;
+inline constexpr uint8 DT_SOCK = 12;
+
+/* ================================================================== */
+/* SB_* — super_block flags */
+/* ================================================================== */
+
+inline constexpr uint64 SB_RDONLY = (1ULL << 0);
+inline constexpr uint64 SB_NOSUID = (1ULL << 1);
+inline constexpr uint64 SB_NODEV = (1ULL << 2);
+inline constexpr uint64 SB_NOEXEC = (1ULL << 3);
+inline constexpr uint64 SB_SYNCHRONOUS = (1ULL << 4);
+inline constexpr uint64 SB_NOATIME = (1ULL << 10);
+inline constexpr uint64 SB_NODIRATIME = (1ULL << 11);
+inline constexpr uint64 SB_SILENT = (1ULL << 15);
+inline constexpr uint64 SB_KERNMOUNT = (1ULL << 22);
+inline constexpr uint64 SB_LAZYTIME = (1ULL << 25);
+inline constexpr uint64 SB_ACTIVE = (1ULL << 30);
+
+/* ================================================================== */
+/* MNT_* — mount flags */
+/* ================================================================== */
+
+inline constexpr int MNT_NOSUID = 0x01;
+inline constexpr int MNT_NODEV = 0x02;
+inline constexpr int MNT_NOEXEC = 0x04;
+inline constexpr int MNT_NOATIME = 0x08;
+inline constexpr int MNT_RELATIME = 0x20;
+inline constexpr int MNT_READONLY = 0x40;
+inline constexpr int MNT_INTERNAL = 0x4000;
+
+/* ================================================================== */
+/* Inode state flags */
+/* ================================================================== */
+
+inline constexpr uint32 I_NEW = (1U << 0);
+inline constexpr uint32 I_DIRTY_SYNC = (1U << 4);
+inline constexpr uint32 I_DIRTY_DATASYNC = (1U << 5);
+inline constexpr uint32 I_DIRTY_PAGES = (1U << 6);
+inline constexpr uint32 I_WILL_FREE = (1U << 7);
+inline constexpr uint32 I_FREEING = (1U << 8);
+inline constexpr uint32 I_CLEAR = (1U << 9);
+inline constexpr uint32 I_REFERENCED = (1U << 10);
+inline constexpr uint32 I_DIRTY_TIME = (1U << 12);
+inline constexpr uint32 I_CREATING = (1U << 15);
+
+inline constexpr uint32 I_DIRTY_INODE = I_DIRTY_SYNC | I_DIRTY_DATASYNC;
+inline constexpr uint32 I_DIRTY = I_DIRTY_INODE | I_DIRTY_PAGES;
+
+/* ================================================================== */
+/* DCACHE_* — dentry flags */
+/* ================================================================== */
+
+inline constexpr uint32 DCACHE_OP_HASH = (1U << 0);
+inline constexpr uint32 DCACHE_OP_COMPARE = (1U << 1);
+inline constexpr uint32 DCACHE_OP_REVALIDATE = (1U << 2);
+inline constexpr uint32 DCACHE_DISCONNECTED = (1U << 5);
+inline constexpr uint32 DCACHE_REFERENCED = (1U << 6);
+inline constexpr uint32 DCACHE_CANT_MOUNT = (1U << 8);
+inline constexpr uint32 DCACHE_MOUNTED = (1U << 15);
+inline constexpr uint32 DCACHE_ENTRY_TYPE = (7U << 19);
+inline constexpr uint32 DCACHE_MISS_TYPE = (0U << 19);
+inline constexpr uint32 DCACHE_DIRECTORY_TYPE = (2U << 19);
+inline constexpr uint32 DCACHE_REGULAR_TYPE = (4U << 19);
+inline constexpr uint32 DCACHE_SPECIAL_TYPE = (5U << 19);
+inline constexpr uint32 DCACHE_SYMLINK_TYPE = (6U << 19);
+
+/* ================================================================== */
+/* FS_* — file_system_type flags */
+/* ================================================================== */
+
+inline constexpr int FS_REQUIRES_DEV = 1;
+inline constexpr int FS_NO_DEVICE = 2;
diff --git a/na/include/lume/init.h b/na/include/lume/init.h
new file mode 100644
index 0000000..541ab96
--- /dev/null
+++ b/na/include/lume/init.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_INIT_H
+#define _LUME_INIT_H
+
+/*
+ * Boot-time section markers.
+ *
+ * Mirrors the subset of Linux include/linux/init.h that LumeOS uses:
+ *
+ * __init mark a function as boot-only — emitted into .init.text,
+ * which the kernel can later free once setup_arch /
+ * start_kernel have finished (LumeOS doesn't yet reclaim
+ * this memory, but the segment lives so future versions
+ * can without source changes).
+ * __initdata same for data, emitted into .init.data.
+ * asmlinkage ABI marker for C functions called from assembly. On
+ * RISC-V the default C ABI matches the assembly calling
+ * convention, so this is empty (matches Linux RISC-V).
+ *
+ * No attempt is made to mirror Linux's COMMAND_LINE_SIZE / saved_command_line
+ * machinery — that belongs in a future <lume/setup.h>.
+ */
+
+#define __init __attribute__((section(".init.text")))
+#define __initdata __attribute__((section(".init.data")))
+
+#ifndef asmlinkage
+#define asmlinkage /* RISC-V: empty, ABI already matches */
+#endif
+
+#endif /* _LUME_INIT_H */
diff --git a/na/include/lume/kbuild.h b/na/include/lume/kbuild.h
new file mode 100644
index 0000000..4c3e12a
--- /dev/null
+++ b/na/include/lume/kbuild.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_KBUILD_H
+#define _LUME_KBUILD_H
+
+/*
+ * Helpers for arch/<arch>/kernel/asm-offsets.c — 1:1 with Linux
+ * include/linux/kbuild.h.
+ *
+ * Trick: each DEFINE/OFFSET emits an inline-asm ".ascii" directive
+ * containing a literal "->" prefix. When the file is compiled with
+ *
+ * $(CC) -S asm-offsets.c -o asm-offsets.s
+ *
+ * the resulting assembly contains lines like
+ *
+ * ->KERNEL_MAP_VIRT_ADDR $0 offsetof(struct kernel_mapping, virt_addr)
+ *
+ * scripts/extract-asm-offsets.sed then rewrites those into proper
+ * `#define KERNEL_MAP_VIRT_ADDR 0` lines for inclusion from head.S.
+ *
+ * The benefit over hand-written #defines is that struct layout changes
+ * are caught at build time: rearrange a field and the asm offset
+ * changes automatically.
+ */
+
+#define DEFINE(sym, val) \
+ asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
+
+#define BLANK() \
+ asm volatile("\n.ascii \"->\"" : : )
+
+#define OFFSET(sym, str, mem) \
+ DEFINE(sym, offsetof(struct str, mem))
+
+#define COMMENT(x) \
+ asm volatile("\n.ascii \"->#" x "\"")
+
+#endif /* _LUME_KBUILD_H */
diff --git a/na/include/lume/kernel_map.h b/na/include/lume/kernel_map.h
new file mode 100644
index 0000000..7eafe88
--- /dev/null
+++ b/na/include/lume/kernel_map.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_KERNEL_MAP_H
+#define _LUME_KERNEL_MAP_H
+
+/*
+ * struct kernel_mapping — where the kernel image lives in physical and
+ * virtual memory. Field names and order match Linux's
+ * arch/riscv/include/asm/page.h struct kernel_mapping so that:
+ *
+ * 1. arch/riscv/kernel/head.S can do `la a1, kernel_map` then
+ * `REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)` and read the virtual
+ * address. The KERNEL_MAP_* asm constants come from
+ * <generated/asm-offsets.h>, produced by arch/riscv/kernel/
+ * asm-offsets.c at build time — re-order the struct and the
+ * asm offsets follow automatically.
+ *
+ * 2. arch/riscv/mm/init.c (Linux origin) populates the same fields
+ * with the same semantics so no source edits are needed.
+ */
+
+#ifndef __ASSEMBLY__
+
+#include <lume/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct kernel_mapping {
+ unsigned long virt_addr;
+ unsigned long virt_offset;
+ uintptr_t phys_addr;
+ uintptr_t size;
+ unsigned long va_pa_offset;
+ unsigned long page_offset;
+ unsigned long va_kernel_pa_offset;
+};
+
+extern struct kernel_mapping kernel_map;
+
+#ifdef __cplusplus
+}
+#endif
+
+#else /* __ASSEMBLY__ */
+
+/* Assembly sees only the field offsets, picked up from the generated
+ * header. Including the C struct above would be a parse error. */
+#include <generated/asm-offsets.h>
+
+#endif /* __ASSEMBLY__ */
+
+#endif /* _LUME_KERNEL_MAP_H */
diff --git a/na/include/lume/klog.h b/na/include/lume/klog.h
index b63810f..69ab269 100644
--- a/na/include/lume/klog.h
+++ b/na/include/lume/klog.h
@@ -51,7 +51,11 @@ enum KlogSubsys : uint8 {
KLOG_SUBSYS_FS = 8,
KLOG_SUBSYS_DRIVER = 9,
KLOG_SUBSYS_TEST = 10,
- KLOG_SUBSYS_MAX = 11,
+ KLOG_SUBSYS_SYSCALL = 11, /* per-syscall entry trace */
+ KLOG_SUBSYS_EXEC = 12, /* ELF load / exec internals */
+ KLOG_SUBSYS_SIGNAL = 13, /* signal delivery trace */
+ KLOG_SUBSYS_TTY = 14, /* tty / job-control trace */
+ KLOG_SUBSYS_MAX = 15,
};
/* Default: show everything up to INFO. DEBUG is compile-gated. */
@@ -165,6 +169,15 @@ void klog_stats(KlogStats* out);
#define pr_debug(fmt, ...) do {} while (0)
#endif
+/* pr_debug_s: subsystem-tagged debug. Always compiled in (subsys is the gate);
+ * level KLOG_DEBUG ensures messages stay in the ring buffer but are filtered
+ * out of the console by default (console_level = KLOG_INFO). Toggle a subsys
+ * with klog_enable_subsys()/klog_disable_subsys() at runtime — or, to make
+ * the messages also reach the console for debugging, raise console_level
+ * via klog_set_level(KLOG_DEBUG). */
+#define pr_debug_s(subsys, fmt, ...) \
+ klog_ex(KLOG_DEBUG, subsys, fmt, ##__VA_ARGS__)
+
/* Linux kernel loglevel prefixes */
#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0"
diff --git a/na/include/lume/kobject.h b/na/include/lume/kobject.h
new file mode 100644
index 0000000..8581d6e
--- /dev/null
+++ b/na/include/lume/kobject.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * kobject — generic refcounted object with parent/child relationship.
+ *
+ * Mirrors Linux include/linux/kobject.h. Embedded into device, gendisk,
+ * request_queue, block_device, etc. Each kobject has a kobj_type that
+ * provides the release callback called when the last reference drops.
+ *
+ * sysfs integration is not yet implemented — the sd/sysfs_ops fields are
+ * reserved for future use.
+ *
+ * Reference: docs/specs/storage_stack.md §4.2
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <lume/kref.h>
+#include <kernel/sync/spinlock.h>
+
+struct kobj_type;
+
+struct kobject {
+ const char* name;
+ kobject* parent;
+ kref refcount;
+ const kobj_type* ktype;
+ list_node entry; // node in parent->children
+ list_node children; // head of children list
+ Spinlock lock;
+ bool state_in_sysfs; // reserved, always false for now
+};
+
+struct kobj_type {
+ void (*release)(kobject*);
+ /* sysfs_ops + default_attrs: reserved for future sysfs implementation. */
+};
+
+/* Initialize a kobject in place; refcount starts at 1.
+ * Caller must provide a non-null ktype with a valid release(). */
+void kobject_init(kobject* k, const kobj_type* type);
+
+/* Link `k` under `parent` (which may be null for a detached root).
+ * Caller passes a stable `name` pointer (e.g. a static string or an
+ * already-strdup'd buffer); kobject does not own the name string. */
+int kobject_add(kobject* k, kobject* parent, const char* name);
+
+/* Unlink from parent. Does NOT drop the final ref — caller decides
+ * when to kobject_put(). */
+void kobject_del(kobject* k);
+
+/* Bump / drop reference. */
+void kobject_get(kobject* k);
+void kobject_put(kobject* k);
diff --git a/na/include/lume/kref.h b/na/include/lume/kref.h
new file mode 100644
index 0000000..13d53f3
--- /dev/null
+++ b/na/include/lume/kref.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * kref — atomic reference counting.
+ *
+ * Mirrors Linux include/linux/kref.h. Use kref_init() to set count=1,
+ * kref_get() / kref_put() to add/drop refs. kref_put() invokes the
+ * release callback when the final ref is dropped and returns 1 in that
+ * case.
+ *
+ * Reference: docs/specs/storage_stack.md §4.1
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+
+struct kref {
+ lume::atomic<int32> refcount;
+};
+
+inline void kref_init(kref* k)
+{
+ k->refcount.store(1, __ATOMIC_RELEASE);
+}
+
+inline void kref_get(kref* k)
+{
+ k->refcount.fetch_add(1, __ATOMIC_RELAXED);
+}
+
+inline int kref_put(kref* k, void (*release)(kref*))
+{
+ if (k->refcount.fetch_sub(1, __ATOMIC_ACQ_REL) == 1) {
+ release(k);
+ return 1;
+ }
+ return 0;
+}
+
+inline int32 kref_read(const kref* k)
+{
+ return k->refcount.load(__ATOMIC_RELAXED);
+}
diff --git a/na/include/lume/libfdt.h b/na/include/lume/libfdt.h
new file mode 100644
index 0000000..e3496e9
--- /dev/null
+++ b/na/include/lume/libfdt.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INCLUDE_LIBFDT_H_
+#define _INCLUDE_LIBFDT_H_
+
+#include <lume/libfdt_env.h>
+#include "../../scripts/dtc/libfdt/libfdt.h"
+
+#endif /* _INCLUDE_LIBFDT_H_ */
diff --git a/na/include/lume/libfdt_env.h b/na/include/lume/libfdt_env.h
new file mode 100644
index 0000000..a95fb1d
--- /dev/null
+++ b/na/include/lume/libfdt_env.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef LIBFDT_ENV_H
+#define LIBFDT_ENV_H
+
+/*
+ * Kernel-side adapter for the standalone libfdt sources in
+ * scripts/dtc/libfdt/. libfdt's *.c files start with
+ * #include "libfdt_env.h"
+ * and expect this header to provide:
+ * - byte-swap helpers (cpu_to_fdt16/32/64, fdt16/32/64_to_cpu)
+ * - fdt16/32/64_t typedefs (just plain uint types — endianness is
+ * handled in the helpers above)
+ * - string / mem helpers (memcpy, memmove, memcmp, strlen, strcmp,
+ * strchr, strnlen, ...)
+ * - INT_MAX, UINT32_MAX (libfdt bounds-checks props with these)
+ * - size_t / uintptr_t (everywhere)
+ *
+ * LumeOS-native: no Linux <linux/...> includes. Provides the surface
+ * libfdt needs and nothing else.
+ */
+
+#include <lume/types.h> /* uint16_t / uint32_t / uint64_t / size_t */
+#include <lume/limits.h> /* INT_MAX */
+#include <string.h> /* libc-shim memcpy / memcmp / strlen ... */
+
+/* libfdt uses these names; both come from <lume/limits.h>. */
+#define INT32_MAX 2147483647
+#define UINT32_MAX 4294967295U
+
+/* libfdt's "FDT word" types — opaque big-endian-stored u16/u32/u64.
+ * The byte-swap helpers below are the only access path. */
+typedef uint16_t fdt16_t;
+typedef uint32_t fdt32_t;
+typedef uint64_t fdt64_t;
+
+/*
+ * Endianness helpers. FDT (Device Tree Blob) is big-endian by spec.
+ * LumeOS runs on RISC-V (little-endian) and x86 (little-endian); we
+ * unconditionally byte-swap.
+ *
+ * Open-coded shifts rather than __builtin_bswapN — on RISC-V without
+ * the Zbb extension, gcc lowers __builtin_bswap32 to a libgcc call
+ * (__bswapsi2 / __bswapdi2) which isn't present in a freestanding
+ * kernel link. Mirrors Linux's scripts/dtc/libfdt/libfdt_env.h
+ * which uses ___constant_swab32-style explicit shifts. */
+static inline uint16_t __lumefdt_bswap16(uint16_t x)
+{
+ return (uint16_t)((x << 8) | (x >> 8));
+}
+static inline uint32_t __lumefdt_bswap32(uint32_t x)
+{
+ return ((x & 0x000000FFu) << 24) |
+ ((x & 0x0000FF00u) << 8) |
+ ((x & 0x00FF0000u) >> 8) |
+ ((x & 0xFF000000u) >> 24);
+}
+static inline uint64_t __lumefdt_bswap64(uint64_t x)
+{
+ return ((x & 0x00000000000000FFull) << 56) |
+ ((x & 0x000000000000FF00ull) << 40) |
+ ((x & 0x0000000000FF0000ull) << 24) |
+ ((x & 0x00000000FF000000ull) << 8) |
+ ((x & 0x000000FF00000000ull) >> 8) |
+ ((x & 0x0000FF0000000000ull) >> 24) |
+ ((x & 0x00FF000000000000ull) >> 40) |
+ ((x & 0xFF00000000000000ull) >> 56);
+}
+
+#define fdt16_to_cpu(x) __lumefdt_bswap16((uint16_t)(x))
+#define cpu_to_fdt16(x) ((fdt16_t)__lumefdt_bswap16((uint16_t)(x)))
+#define fdt32_to_cpu(x) __lumefdt_bswap32((uint32_t)(x))
+#define cpu_to_fdt32(x) ((fdt32_t)__lumefdt_bswap32((uint32_t)(x)))
+#define fdt64_to_cpu(x) __lumefdt_bswap64((uint64_t)(x))
+#define cpu_to_fdt64(x) ((fdt64_t)__lumefdt_bswap64((uint64_t)(x)))
+
+#endif /* LIBFDT_ENV_H */
diff --git a/na/include/lume/limits.h b/na/include/lume/limits.h
new file mode 100644
index 0000000..a275a71
--- /dev/null
+++ b/na/include/lume/limits.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_LIMITS_H
+#define _LUME_LIMITS_H
+
+/*
+ * Numeric limit constants — minimal subset needed by LumeOS kernel code.
+ *
+ * Values match the standard <limits.h> on all 64-bit LP64 targets, which
+ * is the only ABI LumeOS supports. Defined as plain integer literals so
+ * this header is usable from C, C++, and assembly without dragging in
+ * the toolchain's <limits.h> (which on hosted builds references libc
+ * internals we don't link against).
+ */
+
+/* Signed/unsigned char */
+#define SCHAR_MIN (-128)
+#define SCHAR_MAX 127
+#define UCHAR_MAX 255
+#define CHAR_MIN SCHAR_MIN
+#define CHAR_MAX SCHAR_MAX
+
+/* Short */
+#define SHRT_MIN (-32768)
+#define SHRT_MAX 32767
+#define USHRT_MAX 65535
+
+/* Int */
+#define INT_MIN (-INT_MAX - 1)
+#define INT_MAX 2147483647
+#define UINT_MAX 4294967295U
+
+/* Long (LP64: 64-bit) */
+#define LONG_MIN (-LONG_MAX - 1L)
+#define LONG_MAX 9223372036854775807L
+#define ULONG_MAX 18446744073709551615UL
+
+/* Long long */
+#define LLONG_MIN (-LLONG_MAX - 1LL)
+#define LLONG_MAX 9223372036854775807LL
+#define ULLONG_MAX 18446744073709551615ULL
+
+/* size_t (LP64) */
+#define SIZE_MAX ULONG_MAX
+#define SSIZE_MAX LONG_MAX
+
+#endif /* _LUME_LIMITS_H */
diff --git a/na/include/lume/list.h b/na/include/lume/list.h
index 7c020a0..ed1202a 100644
--- a/na/include/lume/list.h
+++ b/na/include/lume/list.h
@@ -4,6 +4,7 @@
#pragma once
#include <lume/types.h>
+#include <lume/container_of.h>
/**
* @brief The node to be embedded in a struct to make it linkable.
@@ -289,4 +290,57 @@ public:
private:
list_node head_;
-}; \ No newline at end of file
+};
+
+/* ========================================================================
+ * Linux-compatible C-style list macros (for VFS subsystem)
+ * ======================================================================== */
+
+using list_head = list_node;
+
+inline void list_init(list_node* head) {
+ head->prev = head;
+ head->next = head;
+}
+
+inline bool list_empty(const list_node* head) {
+ return head->next == head;
+}
+
+inline void __list_add(list_node* node, list_node* p, list_node* n) {
+ n->prev = node;
+ node->next = n;
+ node->prev = p;
+ p->next = node;
+}
+
+inline void list_add(list_node* node, list_node* head) {
+ __list_add(node, head, head->next);
+}
+
+inline void list_add_tail(list_node* node, list_node* head) {
+ __list_add(node, head->prev, head);
+}
+
+inline void list_del(list_node* node) {
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->next = nullptr;
+ node->prev = nullptr;
+}
+
+inline void list_del_init(list_node* node) {
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ list_init(node);
+}
+
+#define list_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define list_for_each(pos, head) \
+ for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next)
+
+#define list_for_each_safe(pos, tmp, head) \
+ for ((pos) = (head)->next, (tmp) = (pos)->next; \
+ (pos) != (head); \
+ (pos) = (tmp), (tmp) = (pos)->next)
diff --git a/na/include/lume/lumeos.lds.h b/na/include/lume/lumeos.lds.h
index a6b2b5f..58d2f72 100644
--- a/na/include/lume/lumeos.lds.h
+++ b/na/include/lume/lumeos.lds.h
@@ -15,36 +15,100 @@
#ifndef _LUME_LUMEOS_LDS_H
#define _LUME_LUMEOS_LDS_H
-/* ===== Text Section: executable code ===== */
+/* ===== Text Section: executable code =====
+ *
+ * .init.text is merged into the main .text segment so PC-relative `la`
+ * from regular code can call __init helpers without a thunk. Linux
+ * keeps a separate .init.text only to reclaim the pages after boot —
+ * LumeOS doesn't yet reclaim, so the merge is harmless and avoids an
+ * extra PT_LOAD entry. */
#define TEXT_SECTION \
.text : AT(ADDR(.text) - PAGE_OFFSET) { \
_stext = .; \
*(.text .text.*) \
+ *(.init.text .init.text.*) \
*(.fixup) \
_etext = .; \
} = 0
-/* ===== Read-Only Data + C++ init_array ===== */
+/* ===== Read-Only Data + C++ init_array + initramfs =====
+ *
+ * Layout invariants:
+ * - extable / init_array / driver_array / initramfs all live INSIDE
+ * .rodata between _srodata and _erodata so the VMM has one R-only
+ * mapping to manage.
+ * - The relocation table (.lume.reloc) is NOT in here — it lives in
+ * its own padded output section (see RELOC_SECTION) so that its
+ * two-pass-build size variance can't shift downstream offsets.
+ * (Putting it in .rodata caused a heisenbug where pre-link offsets
+ * used by gen_reloc.py didn't match post-link offsets after the
+ * reloc.o blob was inserted.)
+ */
#define RODATA_SECTION \
.rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { \
_srodata = .; \
*(.rodata .rodata.*) \
+ *(.srodata .srodata.* .srodata.cst*) \
. = ALIGN(8); \
__extable_start = .; \
KEEP(*(.extable)) \
__extable_end = .; \
INIT_ARRAY \
DRIVER_ARRAY \
+ /* Embedded initramfs CPIO archive */ \
+ . = ALIGN(8); \
+ __initramfs_start = .; \
+ KEEP(*(.init.ramfs)) \
+ __initramfs_end = .; \
+ . = ALIGN(4096); \
_erodata = .; \
}
-/* ===== Relocation Table (optional, stays in file image) ===== */
-#define RELOC_SECTION \
- .reloc : AT(ADDR(.reloc) - PAGE_OFFSET) { \
- . = ALIGN(8); \
- __lume_reloc_start = .; \
- KEEP(*(.lume.reloc .lume.reloc.*)) \
- __lume_reloc_end = .; \
+/* ===== .rel.dyn / .rela.dyn — Linker-emitted RELA relocations =====
+ *
+ * Mirrors arch/riscv/kernel/vmlinux.lds.S §101-109: the .rela.dyn
+ * section sits BETWEEN init data and .rodata so it lands in a
+ * read-only PT_LOAD segment alongside .text/.rodata. The companion
+ * dynamic-linker bookkeeping (.dynamic, .dynsym, .dynstr, .hash,
+ * .gnu.hash) lives in the RW segment AFTER .data — Linux does the
+ * same under #ifdef CONFIG_RELOCATABLE (§139-147). */
+#define RELA_DYN_SECTION \
+ .rel.dyn : AT(ADDR(.rel.dyn) - PAGE_OFFSET) { \
+ *(.rel.dyn*) \
+ } \
+ .rela.dyn : AT(ADDR(.rela.dyn) - PAGE_OFFSET) ALIGN(8) {\
+ __rela_dyn_start = .; \
+ *(.rela .rela*) \
+ __rela_dyn_end = .; \
+ }
+
+/* ===== GOT — kept; everything else dynamic-linker is discarded =====
+ *
+ * Strict mirror of arch/riscv/kernel/vmlinux.lds.S in Linux: the only
+ * dynamic-linker artefact the kernel keeps is `.got` (it's referenced
+ * by every -fPIE compiled global access). `.plt .dynamic .dynsym
+ * .dynstr .hash .gnu.hash .interp .dynbss` are all sent to /DISCARD/
+ * via the DISCARDS macro below — the kernel never invokes a dynamic
+ * linker (head.S walks .rela.dyn directly).
+ *
+ * Keeping .plt was the cause of two distinct bugs:
+ * 1. PLT trampoline target for an undefined symbol (selftest_reloc
+ * after we removed test_reloc.cc) was unresolved → jumps to a
+ * slot that re-jumps to itself → instruction page fault.
+ * 2. .plt carries X permission; living in the same PT_LOAD as .data
+ * forces the load segment to RWX, which `ld` warns about.
+ *
+ * Sit in the same PT_LOAD as .data because .got is RW. */
+#define GOT_SECTION \
+ .got : AT(ADDR(.got) - PAGE_OFFSET) { *(.got*) }
+
+/* ===== Embedded initramfs (CPIO archive) ===== */
+#define INITRAMFS_SECTION \
+ .init.ramfs : AT(ADDR(.init.ramfs) - PAGE_OFFSET) { \
+ . = ALIGN(8); \
+ __initramfs_start = .; \
+ KEEP(*(.init.ramfs)) \
+ __initramfs_end = .; \
}
/* ===== C++ Global Constructor Table ===== */
@@ -61,11 +125,16 @@
KEEP(*(.drivers)) \
PROVIDE(__drivers_end = .);
-/* ===== Initialized Read-Write Data ===== */
+/* ===== Initialized Read-Write Data =====
+ *
+ * .init.data is merged into .data for the same reason TEXT_SECTION
+ * absorbs .init.text — LumeOS doesn't yet reclaim __init pages, so
+ * splitting them gains nothing while costing a PT_LOAD entry. */
#define DATA_SECTION \
.data : AT(ADDR(.data) - PAGE_OFFSET) { \
_sdata = .; \
*(.data .data.*) \
+ *(.init.data .init.data.*) \
ARCH_DATA_EXTRA \
_edata = .; \
}
@@ -80,12 +149,40 @@
_ebss = .; \
}
-/* ===== Discard Unwanted Sections ===== */
-#define DISCARDS \
- /DISCARD/ : { \
- *(.eh_frame .eh_frame_hdr) \
- *(.note .note.*) \
- *(.comment) \
+/* ===== Discard Unwanted Sections =====
+ *
+ * Mirrors arch/riscv/kernel/vmlinux.lds.S in Linux:
+ * /DISCARD/ : {
+ * *(.dynbss)
+ * *(.dynstr*)
+ * *(.dynamic*)
+ * *(.plt*)
+ * *(.interp*)
+ * *(.eh_frame)
+ * *(.note.GNU-stack)
+ * }
+ *
+ * Why each is safe to discard:
+ * .plt — -Bsymbolic resolves all internal refs at link time;
+ * any remaining PLT entry is an undefined symbol that
+ * the kernel would have crashed on anyway.
+ * .dynamic etc. — kernel doesn't invoke a dynamic linker; head.S
+ * walks __rela_dyn_start..__rela_dyn_end manually.
+ * .interp — no PT_INTERP at runtime.
+ * .eh_frame* — no C++ exceptions in kernel (-fno-exceptions).
+ * .note.* — boot-time identification metadata, unused. */
+#define DISCARDS \
+ /DISCARD/ : { \
+ *(.dynbss) \
+ *(.dynstr*) \
+ *(.dynamic*) \
+ *(.dynsym*) \
+ *(.hash*) *(.gnu.hash*) \
+ *(.plt*) \
+ *(.interp*) \
+ *(.eh_frame .eh_frame_hdr) \
+ *(.note .note.*) \
+ *(.comment) \
}
/* ===== Kernel Image Boundary ===== */
diff --git a/na/include/lume/mm_layout.h b/na/include/lume/mm_layout.h
new file mode 100644
index 0000000..7744de0
--- /dev/null
+++ b/na/include/lume/mm_layout.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * mm_layout.h — User/Kernel address space boundary definitions
+ *
+ * Single source of truth for the user/kernel VA split.
+ * All modules (VMM, uaccess, page fault, tests) MUST include this
+ * header instead of defining their own constants.
+ *
+ * Layout (SV39, RISC-V):
+ * [0x0000_0000_0000, kUserVaLimit) — User space
+ * [kUserVaLimit, 0xFFFF_FFFF_FFFF) — Kernel space (direct map, vmemmap, etc.)
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §5
+ */
+
+#include <lume/types.h>
+#include <lume/config.h>
+
+/* User VA range: [kUserVaBase, kUserVaLimit)
+ * kUserVaBase avoids mapping the null page (catch NULL derefs).
+ *
+ * SV39: user space is the lower half [0, 2^38). The upper half
+ * [0xFFFFFFC000000000, 0xFFFFFFFFFFFFFFFF] is kernel space.
+ * Addresses between 2^38 and 0xFFFFFFC000000000 are non-canonical
+ * and will always fault — they must never be used.
+ *
+ * This value MUST match the page table mode selected by the MD layer.
+ * If we ever support SV48/SV57, update this accordingly. */
+inline constexpr uint64 kUserVaBase = kPageSize;
+inline constexpr uint64 kUserVaLimit = 0x4000000000ULL; /* 2^38 = 256 GB */
+
+/* Kernel VA starts at the sign-extended upper half */
+inline constexpr uint64 kKernelVaBase = 0xFFFFFFC000000000ULL;
+
+/* User stack: sits at the top of the user VA range.
+ * Matches Linux STACK_TOP = TASK_SIZE = kUserVaLimit.
+ * One guard page below the top, 8 MB stack. */
+inline constexpr uint64 kUserStackSize = 8ULL * 1024 * 1024;
+inline constexpr uint64 kUserStackTop = kUserVaLimit - kPageSize; /* guard page */
+inline constexpr uint64 kUserStackBase = kUserStackTop - kUserStackSize;
+
+/*
+ * Check whether an address range [addr, addr+len) is entirely
+ * within user space. Returns true if valid.
+ */
+inline bool is_user_range(uint64 addr, uint64 len)
+{
+ if (addr < kUserVaBase) {
+ return false;
+ }
+ if (addr >= kUserVaLimit) {
+ return false;
+ }
+ if (addr + len < addr) {
+ return false; // Overflow
+ }
+ if (addr + len > kUserVaLimit) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Check whether a single address is in user space.
+ */
+inline bool is_user_address(uint64 addr)
+{
+ return addr >= kUserVaBase && addr < kUserVaLimit;
+}
diff --git a/na/include/lume/mount.h b/na/include/lume/mount.h
new file mode 100644
index 0000000..13f8e62
--- /dev/null
+++ b/na/include/lume/mount.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * mount.h — Internal mount structure
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <lume/atomic.h>
+#include <lume/file.h>
+
+struct dentry;
+struct super_block;
+struct file_system_type;
+
+/* ================================================================== */
+/* mount (VFS-internal) */
+/* ================================================================== */
+
+struct mount {
+ vfsmount mnt;
+ mount* mnt_parent;
+ dentry* mnt_mountpoint;
+ list_node mnt_child;
+ list_head mnt_mounts;
+ list_node mnt_list;
+ lume::atomic<int32> mnt_count;
+};
+
+inline mount* real_mount(vfsmount* m) {
+ return reinterpret_cast<mount*>(m);
+}
+
+/* ================================================================== */
+/* Mount API */
+/* ================================================================== */
+
+int do_mount(const char* dev, const char* dir,
+ const char* fstype, uint64 flags, void* data);
+int do_umount(vfsmount* mnt, int flags);
+
+vfsmount* vfs_kern_mount(file_system_type* type, int flags,
+ const char* name, void* data);
+
+/* Global root */
+extern vfsmount* g_root_mnt;
+extern dentry* g_root_dentry;
+
+void mount_init();
diff --git a/na/include/lume/mutex.h b/na/include/lume/mutex.h
new file mode 100644
index 0000000..9719aa6
--- /dev/null
+++ b/na/include/lume/mutex.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Mutex — Linux-style sleeping mutex (no priority inheritance).
+ *
+ * Semantics mirror Linux kernel/locking/mutex.c:
+ * - Single owner (nullptr when free).
+ * - FIFO wait list — first waiter is the next owner.
+ * - Recursive acquire by the same task is FORBIDDEN (panic).
+ * - Owner MUST be the one to unlock.
+ * - lock() may sleep; MUST NOT be called from IRQ context, with
+ * preemption disabled, or while holding a spinlock.
+ * - try_lock() never sleeps; may be called from any context.
+ *
+ * Priority inheritance lives in a separate primitive (RtMutex, future)
+ * just like Linux splits struct mutex / struct rt_mutex.
+ *
+ * Reference: docs/specs/mutex.md
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <kernel/sync/spinlock.h>
+
+struct TaskControlBlock;
+
+class Mutex {
+public:
+ void init(const char* name);
+
+ /* Blocking acquire (uninterruptible). */
+ void lock();
+
+ /* Non-blocking attempt. Returns true if acquired. */
+ bool try_lock();
+
+ /* Release; only the owner may call (else panic). */
+ void unlock();
+
+ bool is_locked() const { return owner_ != nullptr; }
+ bool is_owned_by_current() const;
+ TaskControlBlock* owner() const { return owner_; }
+
+private:
+ TaskControlBlock* owner_ = nullptr;
+ Spinlock wait_lock_;
+ list_node wait_list_{};
+ const char* name_ = nullptr;
+};
+
+/* RAII guard. */
+class MutexGuard {
+public:
+ explicit MutexGuard(Mutex& m) : m_(m) { m_.lock(); }
+ ~MutexGuard() { m_.unlock(); }
+ MutexGuard(const MutexGuard&) = delete;
+ MutexGuard& operator=(const MutexGuard&) = delete;
+private:
+ Mutex& m_;
+};
diff --git a/na/include/lume/namei.h b/na/include/lume/namei.h
new file mode 100644
index 0000000..1764693
--- /dev/null
+++ b/na/include/lume/namei.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * namei.h — Pathname lookup (path resolution)
+ */
+
+#include <lume/types.h>
+#include <lume/file.h> /* struct path */
+#include <lume/dcache.h> /* struct qstr */
+
+/* ================================================================== */
+/* LOOKUP_* flags */
+/* ================================================================== */
+
+inline constexpr uint32 LOOKUP_FOLLOW = 0x0001;
+inline constexpr uint32 LOOKUP_DIRECTORY = 0x0002;
+inline constexpr uint32 LOOKUP_CREATE = 0x0200;
+inline constexpr uint32 LOOKUP_EXCL = 0x0400;
+inline constexpr uint32 LOOKUP_PARENT = 0x0010;
+
+/* ================================================================== */
+/* Path lookup API */
+/* ================================================================== */
+
+int kern_path(const char* name, unsigned int flags, path* result);
+int filename_lookup(int dfd, const char* name, unsigned int flags, path* result);
+dentry* lookup_one_len(const char* name, dentry* parent, int len);
+
+void namei_init();
diff --git a/na/include/lume/page.h b/na/include/lume/page.h
index 7f3f8d1..61bff95 100644
--- a/na/include/lume/page.h
+++ b/na/include/lume/page.h
@@ -10,8 +10,21 @@
* structs (page_map) covers all physical memory discovered via FDT.
*
* Memory layout is compressed using a union: only one of free/slab
- * metadata is valid at a time, determined by `state`. The refcount
- * field is independent of the union to support COW sharing.
+ * metadata is valid at a time, determined by `type`.
+ *
+ * Fields:
+ * refcount — lifecycle reference count (atomic). Page cannot be
+ * freed while refcount > 0.
+ * mapcount — PTE mapping count (atomic). Number of page table
+ * entries pointing to this page. Used by COW and
+ * future reclaim.
+ * flags — page state bits (PG_*). Also serves as a bit-lock
+ * via PG_LOCKED for short critical sections.
+ * type — discriminant for the union (Free/Slab/Anon/File).
+ * mapping — pointer to reverse-mapping context. For anonymous
+ * pages, points to AnonVma (low bit set). For file
+ * pages, points to FileMapping (low bit clear).
+ * index — page offset within the mapping object.
*
* Reference: docs/specs/pmm.md §3.1
*/
@@ -20,40 +33,119 @@
#include <lume/list.h>
#include <lume/atomic.h>
-/* Forward declaration — defined in slab module (Phase 2) */
+/* Forward declarations */
class KmemCache;
-enum class PageState : uint8 {
+/* ================================================================== */
+/* Page type discriminant */
+/* ================================================================== */
+
+enum class PageType : uint8 {
Free, // In Buddy free list
PcpCached, // In Per-CPU cache (invisible to buddy coalescing)
- Allocated, // Handed out to Slab / VMM / user
+ Allocated, // Handed out but not yet typed (PMM default)
Slab, // Backing a Slab cache
+ Anon, // Anonymous user page
+ File, // File-backed page (page cache)
};
+/* ================================================================== */
+/* Page flags (stored as atomic uint32) */
+/* ================================================================== */
+
+inline constexpr uint32 kPgLocked = (1U << 0); // Bit-lock for short critical sections
+inline constexpr uint32 kPgReferenced = (1U << 1); // Accessed since last check
+inline constexpr uint32 kPgDirty = (1U << 2); // Modified, needs writeback
+inline constexpr uint32 kPgWriteback = (1U << 3); // IO writeback in progress
+inline constexpr uint32 kPgLru = (1U << 4); // On an LRU list
+inline constexpr uint32 kPgMapped = (1U << 5); // Has at least one PTE mapping
+inline constexpr uint32 kPgSwapcache = (1U << 6); // In swap cache (future)
+inline constexpr uint32 kPgCow = (1U << 7); // Involved in COW sharing
+
+/* ================================================================== */
+/* Page descriptor */
+/* ================================================================== */
+
struct Page {
- /*
- * Reference count (atomic).
- * Starts at 1 on allocation. Incremented by fork() for COW pages.
- * When decremented to 0, the page is returned to the free pool.
- */
- lume::atomic<uint32> refcount;
+ /* --- Counts (atomic, lock-free) --- */
+ lume::atomic<uint32> refcount; // Lifecycle ref: 0 = reclaimable
+ lume::atomic<uint32> mapcount; // PTE mapping count: 0 = no user mapping
+
+ /* --- State --- */
+ lume::atomic<uint32> flags; // PG_* bits (also serves as bit-lock)
+ PageType type; // Discriminant for union
+ uint8 order; // Buddy order (0–10, i.e. 4KB–4MB)
- PageState state; // Current usage state
- uint8 order; // Buddy order (0–10, i.e. 4KB–4MB)
+ /* --- Reverse mapping --- */
+ void* mapping; // AnonVma* (low bit set) or FileMapping*
+ uint64 index; // Page offset within mapping object
+ /* --- LRU (future) --- */
+ list_node lru; // LRU list linkage (for reclaim)
+
+ /* --- Subsystem-specific metadata --- */
union {
- /* Valid when state == Free */
+ /* Valid when type == Free */
struct {
- list_node free_link; // Link into Buddy or PCP free list
+ list_node free_link; // Link into Buddy or PCP free list
} free;
- /* Valid when state == Slab */
+ /* Valid when type == Slab */
struct {
- KmemCache* cache; // Owning slab cache
- uint32 obj_count; // Number of allocated objects on this page
- void* freelist; // Head of in-page free object linked list
+ KmemCache* cache; // Owning slab cache
+ uint32 obj_count; // Number of allocated objects on this page
+ void* freelist; // Head of in-page free object linked list
} slab;
- /* state == Allocated: no extra metadata (yet) */
+ /* type == Anon / File: no extra union metadata (rmap via mapping) */
};
};
+
+/* ================================================================== */
+/* Page bit-lock (lightweight, no sleep, used for rmap critical */
+/* sections and short metadata updates) */
+/* ================================================================== */
+
+inline bool page_trylock(Page* page)
+{
+ uint32 old = page->flags.fetch_or(kPgLocked);
+ return (old & kPgLocked) == 0;
+}
+
+inline void page_lock(Page* page)
+{
+ while (!page_trylock(page)) {
+ /* spin — acceptable for short critical sections only */
+ }
+}
+
+inline void page_unlock(Page* page)
+{
+ page->flags.fetch_and(~kPgLocked);
+}
+
+/* ================================================================== */
+/* Mapping pointer helpers (Linux convention: low bit = anon) */
+/* ================================================================== */
+
+inline bool page_is_anon(const Page* page)
+{
+ return (reinterpret_cast<uint64>(page->mapping) & 1ULL) != 0;
+}
+
+inline void* page_mapping_raw(const Page* page)
+{
+ return reinterpret_cast<void*>(
+ reinterpret_cast<uint64>(page->mapping) & ~1ULL);
+}
+
+inline void page_set_anon_mapping(Page* page, void* anon_vma)
+{
+ page->mapping = reinterpret_cast<void*>(
+ reinterpret_cast<uint64>(anon_vma) | 1ULL);
+}
+
+inline void page_set_file_mapping(Page* page, void* file_mapping)
+{
+ page->mapping = file_mapping;
+}
diff --git a/na/include/lume/page_cache.h b/na/include/lume/page_cache.h
new file mode 100644
index 0000000..abdc535
--- /dev/null
+++ b/na/include/lume/page_cache.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * page_cache.h — Page Cache interface
+ *
+ * Provides a stable abstraction for caching physical pages indexed by
+ * (mapping, index) pairs. All file-backed fault paths should go
+ * through this interface so that upgrading the backing implementation
+ * (e.g. LRU eviction, writeback, radix tree) does not require
+ * changing any caller.
+ *
+ * Current implementation: simple open-addressing hash table.
+ * Future: radix tree + LRU lists + writeback integration.
+ *
+ * Terminology:
+ * mapping — opaque pointer identifying the backing object
+ * (FileMapping*, or any future cache-able object).
+ * index — page-granularity offset within the mapping.
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §2.9, §3
+ */
+
+#include <lume/types.h>
+
+struct Page;
+
+/*
+ * Look up a cached page by (mapping, index).
+ * Returns the Page* if found (with refcount incremented), or nullptr.
+ * Caller must call page_cache_put() when done.
+ */
+Page* page_cache_get(void* mapping, uint64 index);
+
+/*
+ * Insert a page into the cache, associated with (mapping, index).
+ * The page's refcount is incremented by the cache.
+ * If a page already exists at (mapping, index), returns -EEXIST.
+ * Returns 0 on success, negative errno on failure.
+ */
+int page_cache_insert(void* mapping, uint64 index, Page* page);
+
+/*
+ * Release a reference obtained via page_cache_get().
+ * This decrements the page's refcount. If the page was removed
+ * from the cache and refcount drops to 0, the page is freed.
+ */
+void page_cache_put(Page* page);
+
+/*
+ * Remove a page from the cache by (mapping, index).
+ * Decrements the cache's reference on the page.
+ * Returns the removed Page* (caller does NOT own a new ref),
+ * or nullptr if not found.
+ */
+Page* page_cache_remove(void* mapping, uint64 index);
+
+/*
+ * Remove all pages associated with a given mapping.
+ * Used during file close / inode eviction / truncate.
+ * Each removed page has its cache reference decremented.
+ */
+void page_cache_invalidate(void* mapping);
+
+/*
+ * Look up a page; if not found, allocate one, insert it, and return it.
+ * The returned page has its refcount incremented (caller must put).
+ * Returns nullptr on OOM.
+ *
+ * This is the primary interface for file-backed fault paths:
+ * the caller gets a page and then fills it with file data if kPgUptodate
+ * is not set.
+ */
+Page* page_cache_get_or_alloc(void* mapping, uint64 index);
+
+/*
+ * Initialize the page cache subsystem.
+ * Called once during boot after slab_init().
+ */
+void page_cache_init();
diff --git a/na/include/lume/pgrp.h b/na/include/lume/pgrp.h
new file mode 100644
index 0000000..127f3f8
--- /dev/null
+++ b/na/include/lume/pgrp.h
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Process Group / Session infrastructure.
+ *
+ * Linux models pgrp/session via struct pid + PIDTYPE_PGID/PIDTYPE_SID hlists.
+ * We use a simpler design that supports the same operations cheaply:
+ *
+ * - Each thread group owns one signal_struct (refcount-shared with
+ * CLONE_THREAD siblings).
+ * - signal_struct carries pgid/sid and is the linked-list node on a
+ * fixed-size pgrp hash table.
+ * - kill(pid<=0), tty_signal_pgrp, orphan checks all walk one bucket.
+ *
+ * Concurrency: g_pgrp_lock protects every bucket and the pgrp_link of
+ * every signal_struct on a bucket. signal_struct::lock is a finer-grain
+ * lock for fields that change without re-hashing (e.g. tty pointer).
+ *
+ * Reference: Linux include/linux/pid.h, kernel/pid.c
+ */
+
+#include <lume/types.h>
+#include <lume/list.h>
+#include <kernel/sync/spinlock.h>
+
+struct signal_struct;
+struct TaskControlBlock;
+
+/* ================================================================== */
+/* Subsystem init */
+/* ================================================================== */
+
+void pgrp_init();
+
+/* ================================================================== */
+/* signal_struct lifecycle */
+/* ================================================================== */
+
+/*
+ * Allocate a new signal_struct for `leader`, with sid=pgid=leader_pid,
+ * refcount=1, attached to pgrp `leader_pid`. Returns nullptr on OOM.
+ */
+signal_struct* alloc_signal_struct(int32 leader_pid,
+ TaskControlBlock* leader);
+
+/*
+ * Allocate a child signal_struct inheriting pgid/sid/tty from `parent`.
+ * The new struct is attached to the parent's pgrp and starts with
+ * refcount=1. Used by fork (non-CLONE_THREAD).
+ */
+signal_struct* fork_signal_struct(signal_struct* parent,
+ TaskControlBlock* child_leader);
+
+/* Increment refcount (CLONE_THREAD share). */
+void get_signal_struct(signal_struct* sig);
+
+/*
+ * Decrement refcount; on last drop, detach from pgrp hash and free.
+ * Pass the task that's dropping the reference so we can manage
+ * leader pointers if the leader exits while sub-threads remain.
+ */
+void put_signal_struct(signal_struct* sig);
+
+/* ================================================================== */
+/* Pgrp operations (assume signal_struct is allocated) */
+/* ================================================================== */
+
+/*
+ * Move a signal_struct from its current pgrp to `new_pgid`.
+ * Used by setpgid / setsid. Takes g_pgrp_lock internally.
+ */
+void change_pgid(signal_struct* sig, int32 new_pgid);
+
+/*
+ * Invoke `fn(sig)` for every signal_struct whose pgid == target.
+ * Holds g_pgrp_lock for the entire walk — fn must not block, sleep,
+ * or take locks that order before g_pgrp_lock.
+ *
+ * Template-y but kept in header to avoid a function-ptr indirection;
+ * keep callsites lean.
+ */
+template <typename F>
+void for_each_pgrp_member(int32 target_pgid, F&& fn);
+
+/*
+ * Count the number of signal_structs in `pgid`. Used by setsid
+ * preflight (can't create a session if you're already a pgrp leader
+ * with members) and orphan checks.
+ */
+int pgrp_member_count(int32 pgid);
+
+/* ================================================================== */
+/* Implementation details exposed for the template above */
+/* ================================================================== */
+
+constexpr int kPgrpHashShift = 6;
+constexpr int kPgrpHashSize = 1 << kPgrpHashShift; /* 64 buckets */
+constexpr int kPgrpHashMask = kPgrpHashSize - 1;
+
+extern list_node g_pgrp_hash[kPgrpHashSize];
+extern Spinlock g_pgrp_lock;
+
+inline int pgrp_hash_idx(int32 pgid) {
+ return static_cast<int>(static_cast<uint32>(pgid)) & kPgrpHashMask;
+}
+
+#include <lume/signal_types.h>
+
+template <typename F>
+void for_each_pgrp_member(int32 target_pgid, F&& fn)
+{
+ LockGuard guard(g_pgrp_lock);
+ list_node* bucket = &g_pgrp_hash[pgrp_hash_idx(target_pgid)];
+ list_node* node = bucket->next;
+ while (node != bucket) {
+ list_node* nxt = node->next; /* fn may not unlink, but be safe */
+ auto* sig = list_entry<signal_struct, &signal_struct::pgrp_link>(node);
+ if (sig->pgid == target_pgid)
+ fn(sig);
+ node = nxt;
+ }
+}
diff --git a/na/include/lume/pgtable_types.h b/na/include/lume/pgtable_types.h
new file mode 100644
index 0000000..e309a82
--- /dev/null
+++ b/na/include/lume/pgtable_types.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Page Table Entry Types — strongly typed wrappers.
+ *
+ * Layout and macro names mirror Linux's arch/riscv/include/asm/page.h
+ * and pgtable-64.h so that arch/riscv/mm/init.c can be copied from
+ * Linux without source edits. The wrapped struct prevents accidental
+ * mixing of a pte_t with a pgd_t or a raw physical address, which the
+ * previous bare-uint64 design in pmap.cc could not catch.
+ *
+ * Underlying width on RV64 LP64 is `unsigned long` = 64 bits, matching
+ * Linux exactly — assembly references (e.g. REG_S/REG_L on a pte_t*)
+ * see the same byte layout.
+ *
+ * SV39 uses 3 levels (pgd -> pmd -> pte); SV48 uses 4 (pgd -> pud ->
+ * pmd -> pte). p4d_t/pud_t are emitted unconditionally so that the
+ * higher-level abstractions in asm-generic/pgtable-nopud.h and friends
+ * (which Linux uses to collapse unused levels) work without #ifdef.
+ */
+
+#include <lume/types.h>
+
+typedef struct {
+ unsigned long pte;
+} pte_t;
+
+typedef struct {
+ unsigned long pmd;
+} pmd_t;
+
+typedef struct {
+ unsigned long pud;
+} pud_t;
+
+typedef struct {
+ unsigned long p4d;
+} p4d_t;
+
+typedef struct {
+ unsigned long pgd;
+} pgd_t;
+
+typedef struct {
+ unsigned long pgprot;
+} pgprot_t;
+
+typedef struct page *pgtable_t;
+
+/* Field accessors */
+#define pte_val(x) ((x).pte)
+#define pmd_val(x) ((x).pmd)
+#define pud_val(x) ((x).pud)
+#define p4d_val(x) ((x).p4d)
+#define pgd_val(x) ((x).pgd)
+#define pgprot_val(x) ((x).pgprot)
+
+/* Constructors (cast raw uint64 to wrapped type) */
+#define __pte(x) ((pte_t) { (x) })
+#define __pmd(x) ((pmd_t) { (x) })
+#define __pud(x) ((pud_t) { (x) })
+#define __p4d(x) ((p4d_t) { (x) })
+#define __pgd(x) ((pgd_t) { (x) })
+#define __pgprot(x) ((pgprot_t) { (x) })
+
+#define PTE_FMT "%016lx"
diff --git a/na/include/lume/pipe.h b/na/include/lume/pipe.h
new file mode 100644
index 0000000..c905c9a
--- /dev/null
+++ b/na/include/lume/pipe.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * pipe.h — Pipe data structure and API
+ *
+ * A pipe is a unidirectional byte stream backed by a fixed-size
+ * circular buffer. Two file structs (reader + writer) share a
+ * single pipe_inode_info.
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+#include <lume/waitqueue.h>
+#include <kernel/sync/spinlock.h>
+
+struct file;
+struct files_struct;
+
+/* ================================================================== */
+/* pipe_inode_info */
+/* ================================================================== */
+
+inline constexpr int PIPE_BUF_SIZE = 4096;
+
+struct pipe_inode_info {
+ char buf[PIPE_BUF_SIZE];
+ int rd_pos; /* Read position in circular buffer */
+ int wr_pos; /* Write position in circular buffer */
+ int len; /* Bytes currently in buffer */
+ Spinlock lock;
+ WaitQueue rd_wait; /* Readers sleep here when empty */
+ WaitQueue wr_wait; /* Writers sleep here when full */
+
+ lume::atomic<int32> readers; /* Number of open read ends */
+ lume::atomic<int32> writers; /* Number of open write ends */
+ lume::atomic<int32> refcount; /* Total references */
+};
+
+/* ================================================================== */
+/* API */
+/* ================================================================== */
+
+/* Create a pipe, install read/write fds into the given files_struct.
+ * Returns 0 on success, negative errno on failure. */
+int do_pipe2(files_struct* files, int fds[2], int flags);
+
+/* Allocate a fresh pipe_inode_info */
+pipe_inode_info* alloc_pipe_info();
+void free_pipe_info(pipe_inode_info* pipe);
diff --git a/na/include/lume/platform_device.h b/na/include/lume/platform_device.h
new file mode 100644
index 0000000..ac3e527
--- /dev/null
+++ b/na/include/lume/platform_device.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * platform_device / platform_driver — FDT-described memory-mapped devices.
+ *
+ * Mirrors Linux include/linux/platform_device.h. The platform bus is
+ * the catch-all bus for devices that don't live on a real bus like PCI
+ * or USB — every device discovered via the FDT lands here.
+ *
+ * Reference: docs/specs/storage_stack.md §4.3
+ */
+
+#include <lume/types.h>
+#include <lume/device.h>
+
+class DeviceNode;
+
+/* Compatible-string -> driver-private data table (Linux's of_device_id). */
+struct of_device_id {
+ const char* compatible;
+ const void* data;
+};
+
+struct platform_device {
+ device dev;
+ DeviceNode* of_node; // FDT node this device came from
+ uint64 mmio_base; // first `reg` cell from FDT (0 if none)
+ uint64 mmio_size; // second `reg` cell (0 if none)
+ int irq; // first interrupt (-1 if none / not parsed)
+};
+
+struct platform_driver {
+ device_driver driver;
+ int (*probe)(platform_device*);
+ void (*remove)(platform_device*);
+ const of_device_id* of_match_table; // NULL-terminated array
+};
+
+extern bus_type platform_bus;
+
+void platform_bus_init();
+int platform_driver_register(platform_driver* drv);
+int platform_device_register(platform_device* pdev);
+
+/* Helper: pull current driver out of a probed device. */
+inline platform_driver* to_platform_driver(device_driver* drv)
+{
+ return container_of(drv, platform_driver, driver);
+}
+inline platform_device* to_platform_device(device* dev)
+{
+ return container_of(dev, platform_device, dev);
+}
+
+/* Linker-set registration macro — drop a pointer to your platform_driver
+ * into the .drivers section. driver_probe_all() iterates this set at
+ * boot. Use a unique token in CONCAT to avoid name collisions. */
+#define PLATFORM_DRIVER(_drv) \
+ extern platform_driver _drv; \
+ static platform_driver* const __pdrv_ptr_##_drv \
+ __attribute__((used, section(".drivers"))) = &_drv
diff --git a/na/include/lume/pmm.h b/na/include/lume/pmm.h
index f02a146..3e4b819 100644
--- a/na/include/lume/pmm.h
+++ b/na/include/lume/pmm.h
@@ -64,6 +64,20 @@ void pmm_free_pages(Page* page, uint8 order);
void page_incref(Page* page);
void page_decref(Page* page);
+/* --- Map counting (atomic, lock-free) --- */
+
+inline void page_inc_mapcount(Page* page) {
+ page->mapcount.fetch_add(1);
+}
+
+inline uint32 page_dec_mapcount(Page* page) {
+ return page->mapcount.fetch_sub(1) - 1; // Returns new value
+}
+
+inline uint32 page_mapcount(const Page* page) {
+ return page->mapcount.load(__ATOMIC_RELAXED);
+}
+
/* --- Address conversion --- */
// Convert a Page* to its physical address.
@@ -75,6 +89,20 @@ Page* pa_to_page(PhysAddr pa);
// Convert a Page* to a kernel virtual address.
VirtAddr page_to_va(const Page* page);
+/* --- PFN (Page Frame Number) abstraction --- */
+/*
+ * PFN is the fundamental unit for PA↔Page conversion.
+ * PA↔PFN converters already exist in addr.h (pa_to_pfn / pfn_to_pa).
+ * When sparse vmemmap lands, only pfn_to_page/page_to_pfn
+ * need to change — no callers are affected.
+ */
+
+// Convert PFN to Page*. PFN = pa / PAGE_SIZE.
+Page* pfn_to_page(uint64 pfn);
+
+// Convert Page* to PFN.
+uint64 page_to_pfn(const Page* page);
+
// Query PMM memory range (for VMM kernel mapping)
PhysAddr pmm_get_mem_base();
uint64 pmm_get_mem_size();
diff --git a/na/include/lume/poll.h b/na/include/lume/poll.h
new file mode 100644
index 0000000..7d5bca2
--- /dev/null
+++ b/na/include/lume/poll.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * poll.h — POSIX poll() / ppoll() infrastructure.
+ *
+ * Linux pattern (include/linux/poll.h):
+ * - file_operations->poll(file, poll_table) returns a bitmask of
+ * POLL* events that are currently true on this file.
+ * - As a side effect, the poll callback calls poll_wait(file, wq, pt)
+ * to register the caller on every wait queue that might fire when
+ * state changes. sys_ppoll() then sleeps on those queues until
+ * one of them fires or a signal/timeout arrives.
+ *
+ * Our minimal implementation supports a SINGLE wait queue per poll call
+ * (sufficient for nfds==1, which is the BusyBox interactive shell case).
+ * Multi-fd polls work but fall back to busy re-checking; a proper
+ * per-fd waiter-list upgrade can come later.
+ */
+
+#include <lume/types.h>
+
+struct file;
+class WaitQueue;
+
+/* ================================================================== */
+/* UAPI: pollfd struct + POLL* event bits (Linux asm-generic) */
+/* ================================================================== */
+
+struct pollfd {
+ int32 fd;
+ int16 events;
+ int16 revents;
+};
+
+inline constexpr int16 POLLIN = 0x0001;
+inline constexpr int16 POLLPRI = 0x0002;
+inline constexpr int16 POLLOUT = 0x0004;
+inline constexpr int16 POLLERR = 0x0008;
+inline constexpr int16 POLLHUP = 0x0010;
+inline constexpr int16 POLLNVAL = 0x0020;
+inline constexpr int16 POLLRDNORM = 0x0040;
+inline constexpr int16 POLLRDBAND = 0x0080;
+inline constexpr int16 POLLWRNORM = 0x0100;
+inline constexpr int16 POLLWRBAND = 0x0200;
+
+/* ================================================================== */
+/* Kernel-side poll_table */
+/* ================================================================== */
+
+/*
+ * Carried into file_operations->poll(). Drivers call poll_wait() with
+ * the wait queue they'd like the poller to sleep on; we record the
+ * first one. When `wq == nullptr` on entry, no real registration is
+ * needed (the caller is just probing readiness).
+ */
+struct poll_table {
+ WaitQueue* wq; /* first wait queue registered (single-wq design) */
+};
+
+/*
+ * Called from f_op->poll() implementations to register a wait queue.
+ * If `pt` is null we're in a probe-only pass — caller doesn't want to
+ * sleep, just to read current revents.
+ */
+void poll_wait(file* fp, WaitQueue* wq, poll_table* pt);
diff --git a/na/include/lume/ptrace.h b/na/include/lume/ptrace.h
new file mode 100644
index 0000000..687d103
--- /dev/null
+++ b/na/include/lume/ptrace.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * PtRegs — Linux-compatible user register context
+ *
+ * This structure MUST match the Linux RISC-V pt_regs layout exactly.
+ * It is the canonical representation for:
+ * - Signal frame construction (ucontext / sigcontext)
+ * - ptrace(GETREGS/SETREGS)
+ * - /proc/<pid>/regs
+ *
+ * The internal TrapFrame (arch/trapframe.h) has a DIFFERENT layout
+ * optimized for fast trap entry/exit. Conversion between the two
+ * is done via trapframe_to_pt_regs() / pt_regs_to_trapframe().
+ *
+ * Reference: Linux arch/riscv/include/asm/ptrace.h
+ */
+
+#include <lume/types.h>
+
+struct PtRegs {
+ uint64 epc; // Program counter at trap
+ uint64 ra; // x1
+ uint64 sp; // x2
+ uint64 gp; // x3
+ uint64 tp; // x4
+ uint64 t0; // x5
+ uint64 t1; // x6
+ uint64 t2; // x7
+ uint64 s0; // x8
+ uint64 s1; // x9
+ uint64 a0; // x10
+ uint64 a1; // x11
+ uint64 a2; // x12
+ uint64 a3; // x13
+ uint64 a4; // x14
+ uint64 a5; // x15
+ uint64 a6; // x16
+ uint64 a7; // x17
+ uint64 s2; // x18
+ uint64 s3; // x19
+ uint64 s4; // x20
+ uint64 s5; // x21
+ uint64 s6; // x22
+ uint64 s7; // x23
+ uint64 s8; // x24
+ uint64 s9; // x25
+ uint64 s10; // x26
+ uint64 s11; // x27
+ uint64 t3; // x28
+ uint64 t4; // x29
+ uint64 t5; // x30
+ uint64 t6; // x31
+ /* CSRs */
+ uint64 status; // sstatus
+ uint64 badaddr; // stval
+ uint64 cause; // scause
+ /* Syscall bookkeeping */
+ uint64 orig_a0; // a0 value before syscall (for restart)
+};
+
+static_assert(sizeof(PtRegs) == 36 * 8, "PtRegs must be 288 bytes (Linux ABI)");
+
+/*
+ * Convert between internal TrapFrame and Linux-ABI PtRegs.
+ * These are the ONLY places that know both layouts.
+ * Future TrapFrame restructuring only needs to update these two functions.
+ */
+namespace arch { struct TrapFrame; }
+
+void trapframe_to_pt_regs(PtRegs* dst, const arch::TrapFrame* src,
+ uint64 scause, uint64 stval);
+void pt_regs_to_trapframe(arch::TrapFrame* dst, const PtRegs* src);
diff --git a/na/include/lume/rbtree.h b/na/include/lume/rbtree.h
index 8bc8104..80b0c6c 100644
--- a/na/include/lume/rbtree.h
+++ b/na/include/lume/rbtree.h
@@ -10,16 +10,7 @@
#include <lume/compiler.h>
#include <lume/rbtree_types.h>
-
-/* ------------------------------------------------------------------ */
-/* Linux-compat macros for freestanding kernel */
-/* ------------------------------------------------------------------ */
-
-#ifndef container_of
-#define container_of(ptr, type, member) \
- reinterpret_cast<type *>( \
- reinterpret_cast<char *>(ptr) - __builtin_offsetof(type, member))
-#endif
+#include <lume/container_of.h>
/* ------------------------------------------------------------------ */
/* Core macros */
diff --git a/na/include/lume/relocate.h b/na/include/lume/relocate.h
deleted file mode 100644
index 120824f..0000000
--- a/na/include/lume/relocate.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-#include <lume/types.h>
-
-inline constexpr uint64 kRelocTableMagic = 0x4C554D45524C4F43ULL; /* LUMERLOC */
-inline constexpr uint16 kRelocTableVersion = 1;
-
-enum RelocArch : uint16 {
- RELOC_ARCH_RISCV = 1,
- RELOC_ARCH_AARCH64 = 2,
- RELOC_ARCH_X86_64 = 3,
-};
-
-enum RelocType : uint32 {
- RELOC_RISCV_RELATIVE = 1,
- RELOC_RISCV_64 = 2,
-};
-
-struct RelocTableHeader {
- uint64 magic;
- uint16 version;
- uint16 arch;
- uint32 entry_size;
- uint32 entry_count;
- uint32 reserved;
-};
-
-struct RelocEntry {
- uint64 offset;
- uint64 addend;
- uint32 type;
- uint32 reserved;
-};
-
-enum RelocStatus : uint32 {
- RELOC_OK = 0,
- RELOC_ERR_MAGIC,
- RELOC_ERR_VERSION,
- RELOC_ERR_ARCH,
- RELOC_ERR_SIZE,
- RELOC_ERR_RANGE,
- RELOC_ERR_UNSUPPORTED,
-};
-
-/* Apply relocations to the image loaded at load_base. */
-RelocStatus relocate_apply(uint64 load_base, uint64 image_size,
- uint64 link_base, const void* reloc_blob,
- uint64 reloc_size);
-
-const char* reloc_status_str(RelocStatus st);
diff --git a/na/include/lume/sched.h b/na/include/lume/sched.h
index 0f25e58..ab26bac 100644
--- a/na/include/lume/sched.h
+++ b/na/include/lume/sched.h
@@ -71,3 +71,32 @@ void sched_dequeue(TaskControlBlock* task);
/* Convert priority level to time slice length.
* Higher priority (lower number) → longer time slice. */
int32 priority_to_timeslice(uint8 priority);
+
+/* ========================================================================
+ * Task Migration & Load Balancing
+ * ======================================================================== */
+
+/*
+ * sched_migrate_task — Move a task from its current CPU to target_cpu.
+ *
+ * The task must NOT be the currently running task. It is dequeued from
+ * its old runqueue and enqueued onto the target. Both runqueue locks
+ * are acquired in cpu_id order to prevent deadlock.
+ *
+ * Returns 0 on success, -EINVAL if task is current or target is invalid.
+ */
+int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu);
+
+/*
+ * sched_balance — Periodic load balancer.
+ *
+ * Called from sched_tick(). Checks if any CPU has significantly more
+ * runnable tasks and pulls work to the local runqueue. This is a
+ * simplified version of Linux's load_balance().
+ */
+void sched_balance();
+
+/*
+ * sched_nr_running — Get the number of runnable tasks on a CPU.
+ */
+uint32 sched_nr_running(uint32 cpu_id);
diff --git a/na/include/lume/setup.h b/na/include/lume/setup.h
new file mode 100644
index 0000000..f8e6cb6
--- /dev/null
+++ b/na/include/lume/setup.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#ifndef _LUME_SETUP_H
+#define _LUME_SETUP_H
+
+/*
+ * Boot-time interfaces called between assembly and C.
+ *
+ * Mirrors the subset of Linux include/linux/setup.h relevant to LumeOS's
+ * Linux-aligned RISC-V boot flow:
+ *
+ * setup_vm Build early_pg_dir / trampoline_pg_dir before
+ * the MMU is enabled. Filled kernel_map for
+ * all subsequent code to consult.
+ * relocate_enable_mmu Switch SATP, jump to higher-half, apply
+ * .rela.dyn via relocate_kernel.
+ * relocate_kernel Walk .rela.dyn, patch R_RISCV_RELATIVE
+ * slots with addend + kaslr_offset.
+ *
+ * kernel_main (LumeOS's equivalent of Linux's start_kernel) is declared
+ * in kernel/main.cc and re-declared in arch-side glue — kept local to
+ * that file to avoid creating an artificial public API for the lone
+ * call site in head.S.
+ */
+
+#include <lume/types.h>
+#include <asm/pgtable.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void setup_vm(uintptr_t load_pa, uintptr_t dtb_pa);
+void relocate_enable_mmu(pgd_t *pgdir);
+void relocate_kernel(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LUME_SETUP_H */
diff --git a/na/include/lume/signal.h b/na/include/lume/signal.h
index 5ff6ed0..05b7542 100644
--- a/na/include/lume/signal.h
+++ b/na/include/lume/signal.h
@@ -4,5 +4,57 @@
#pragma once
/*
- * Signal subsystem interface.
+ * Signal Infrastructure — Core signal delivery API
+ *
+ * Provides the interface between trap handling, signal delivery,
+ * and user-space signal handler execution.
+ *
+ * Reference: Linux include/linux/sched/signal.h
*/
+
+#include <lume/types.h>
+
+struct TaskControlBlock;
+struct sighand_struct;
+namespace arch { struct TrapFrame; }
+
+/* ------------------------------------------------------------------
+ * sighand_struct lifecycle (Linux kernel/fork.c:copy_sighand pattern)
+ * ------------------------------------------------------------------ */
+
+sighand_struct* alloc_sighand();
+sighand_struct* copy_sighand(sighand_struct* parent);
+void get_sighand(sighand_struct* sh);
+void put_sighand(sighand_struct* sh);
+
+/*
+ * force_sig_fault — Deliver a fault-induced signal to the current task.
+ *
+ * Called from trap handler when a user-mode exception cannot be resolved.
+ * Bypasses the blocked mask and enqueues the signal.
+ *
+ * @signo: Signal number (SIGSEGV, SIGBUS, SIGILL, SIGFPE, etc.)
+ * @code: si_code for siginfo_t (e.g., SEGV_MAPERR, SEGV_ACCERR)
+ * @addr: Fault address (stval / badaddr)
+ */
+void force_sig_fault(int signo, int code, uint64 addr);
+
+/*
+ * do_signal — Process pending signals before returning to user mode.
+ *
+ * Called at the end of trap_handle() when origin == FromUser.
+ * If a signal is pending and a handler is registered, constructs
+ * a signal frame on the user stack and redirects epc.
+ *
+ * @tf: TrapFrame to modify if signal delivery is needed.
+ */
+void do_signal(arch::TrapFrame* tf);
+
+/*
+ * signal_pending — Check if the task has unblocked pending signals.
+ *
+ * Used by blocking syscalls (e.g., n_tty_read) to detect whether
+ * they should return -EINTR.
+ */
+bool signal_pending(TaskControlBlock* task);
+
diff --git a/na/include/lume/signal_types.h b/na/include/lume/signal_types.h
new file mode 100644
index 0000000..f66231b
--- /dev/null
+++ b/na/include/lume/signal_types.h
@@ -0,0 +1,263 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Signal Types — Linux-compatible structures for signal delivery
+ *
+ * Follows the RISC-V LP64 ABI layout so that user-space programs
+ * compiled with musl / glibc see the correct struct sizes.
+ *
+ * Reference: Linux include/uapi/asm-generic/signal.h
+ * Linux include/uapi/asm-generic/siginfo.h
+ */
+
+#include <lume/types.h>
+#include <lume/signal_nr.h>
+#include <lume/list.h>
+#include <kernel/sync/spinlock.h>
+
+/* ================================================================== */
+/* Signal set (64-bit bitmap) */
+/* ================================================================== */
+
+struct sigset_t {
+ uint64 sig[_NSIG_WORDS]; /* _NSIG_WORDS = 1 on LP64 */
+
+ void clear() { for (auto& s : sig) s = 0; }
+ void fill() { for (auto& s : sig) s = ~0ULL; }
+ void add(int signo) { sig[(signo-1)/64] |= (1ULL << ((signo-1)%64)); }
+ void del(int signo) { sig[(signo-1)/64] &= ~(1ULL << ((signo-1)%64)); }
+ bool has(int signo) const { return (sig[(signo-1)/64] >> ((signo-1)%64)) & 1; }
+ bool is_empty() const { for (auto s : sig) if (s) return false; return true; }
+
+ sigset_t operator&(const sigset_t& o) const {
+ sigset_t r;
+ for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] & o.sig[i];
+ return r;
+ }
+ sigset_t operator|(const sigset_t& o) const {
+ sigset_t r;
+ for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = sig[i] | o.sig[i];
+ return r;
+ }
+ sigset_t operator~() const {
+ sigset_t r;
+ for (int i = 0; i < _NSIG_WORDS; i++) r.sig[i] = ~sig[i];
+ return r;
+ }
+};
+
+/* ================================================================== */
+/* siginfo_t (simplified, Linux UAPI compatible layout) */
+/* ================================================================== */
+
+struct kernel_siginfo {
+ int si_signo;
+ int si_errno;
+ int si_code;
+ int _pad0; /* alignment */
+
+ union {
+ /* kill() */
+ struct {
+ int32 _pid;
+ int32 _uid;
+ } _kill;
+
+ /* SIGCHLD */
+ struct {
+ int32 _pid;
+ int32 _uid;
+ int _status;
+ long _utime;
+ long _stime;
+ } _sigchld;
+
+ /* SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP */
+ struct {
+ uint64 _addr;
+ } _sigfault;
+
+ /* Pad to 128 bytes total (Linux SI_MAX_SIZE) */
+ char _pad[128 - 4*sizeof(int)];
+ } _sifields;
+};
+
+/* ================================================================== */
+/* Signal action (kernel representation) */
+/* ================================================================== */
+
+/* sa_flags bits (Linux UAPI) */
+inline constexpr uint64 SA_NOCLDSTOP = 0x00000001;
+inline constexpr uint64 SA_NOCLDWAIT = 0x00000002;
+inline constexpr uint64 SA_SIGINFO = 0x00000004;
+inline constexpr uint64 SA_RESTORER = 0x04000000;
+inline constexpr uint64 SA_ONSTACK = 0x08000000;
+inline constexpr uint64 SA_RESTART = 0x10000000;
+inline constexpr uint64 SA_NODEFER = 0x40000000;
+inline constexpr uint64 SA_RESETHAND = 0x80000000;
+
+/* Special handler values */
+inline constexpr uint64 SIG_DFL = 0;
+inline constexpr uint64 SIG_IGN = 1;
+inline constexpr uint64 SIG_ERR = static_cast<uint64>(-1);
+
+struct kernel_sigaction {
+ uint64 sa_handler; /* handler address or SIG_DFL/SIG_IGN */
+ uint64 sa_flags;
+ uint64 sa_restorer; /* sigreturn trampoline (SA_RESTORER) */
+ sigset_t sa_mask; /* signals blocked during handler */
+};
+
+/* ================================================================== */
+/* Per-task signal state */
+/* ================================================================== */
+
+/* Queued signal entry */
+struct sigqueue {
+ list_node list;
+ kernel_siginfo info;
+};
+
+/* Pending signal set + queue */
+struct sigpending {
+ list_node list; /* list of sigqueue entries */
+ sigset_t signal; /* bitmap of pending signals */
+
+ void init() {
+ list.init();
+ signal.clear();
+ }
+};
+
+/* Signal handler table (shared between threads via CLONE_SIGHAND).
+ *
+ * Linux pattern (kernel/fork.c:copy_sighand): one sighand_struct per
+ * thread group by default, shared across CLONE_SIGHAND siblings,
+ * refcounted, freed when the last referencing task exits. Its
+ * `siglock` guards BOTH the action table and every per-task `pending`
+ * list of every task that references this sighand. That single-lock
+ * model is what lets send/dequeue/discard race-free without per-list
+ * locks. */
+struct sighand_struct {
+ Spinlock siglock;
+ uint32 refcount; /* # tasks sharing this sighand (≥1) */
+ kernel_sigaction action[_NSIG];
+
+ void init() {
+ siglock.init("sighand");
+ refcount = 1;
+ for (auto& a : action) {
+ a.sa_handler = SIG_DFL;
+ a.sa_flags = 0;
+ a.sa_restorer = 0;
+ a.sa_mask.clear();
+ }
+ }
+};
+
+/* ================================================================== */
+/* signal_struct — per-thread-group shared state */
+/* ================================================================== */
+
+struct TaskControlBlock;
+struct tty_struct;
+
+/*
+ * Linux's signal_struct is the per-thread-group container holding:
+ * - Process group / session identity (pgid, sid)
+ * - Controlling terminal (tty)
+ * - Misc state shared across CLONE_THREAD siblings
+ *
+ * Refcount tracks the number of tasks in the thread group; when the last
+ * task in the group exits, the signal_struct is freed. Detachment from
+ * the pgrp hash also happens at that point.
+ *
+ * `pgrp_link` makes this signal_struct a member of one pgrp bucket; the
+ * pgrp subsystem walks the bucket to enumerate all thread groups whose
+ * pgid hashes there.
+ */
+struct signal_struct {
+ Spinlock lock;
+ uint32 refcount; /* # tasks sharing this (≥1) */
+ int32 pgid; /* process group id */
+ int32 sid; /* session id */
+ tty_struct* tty; /* controlling tty (or nullptr) */
+ int32 tty_old_pgrp; /* SIGHUP target on TIOCNOTTY */
+ TaskControlBlock* leader; /* thread-group leader (pid==tgid) */
+ list_node pgrp_link; /* on pgrp hash bucket */
+
+ void init(int32 leader_pid, TaskControlBlock* tg_leader) {
+ lock.init("signal");
+ refcount = 1;
+ pgid = leader_pid;
+ sid = leader_pid;
+ tty = nullptr;
+ tty_old_pgrp = 0;
+ leader = tg_leader;
+ pgrp_link.init();
+ }
+};
+
+/* ================================================================== */
+/* Default signal dispositions */
+/* ================================================================== */
+
+enum SigDefaultAction {
+ SIG_ACT_TERM, /* Terminate */
+ SIG_ACT_IGN, /* Ignore */
+ SIG_ACT_CORE, /* Terminate + core dump */
+ SIG_ACT_STOP, /* Stop process */
+ SIG_ACT_CONT, /* Continue if stopped */
+};
+
+/* Returns the default action for a standard signal */
+inline SigDefaultAction sig_default_action(int sig)
+{
+ switch (sig) {
+ case SIGCHLD:
+ case SIGURG:
+ case SIGWINCH:
+ return SIG_ACT_IGN;
+
+ case SIGCONT:
+ return SIG_ACT_CONT;
+
+ case SIGSTOP:
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ return SIG_ACT_STOP;
+
+ case SIGQUIT:
+ case SIGILL:
+ case SIGTRAP:
+ case SIGABRT:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGSEGV:
+ case SIGXCPU:
+ case SIGXFSZ:
+ case SIGSYS:
+ return SIG_ACT_CORE;
+
+ default:
+ return SIG_ACT_TERM;
+ }
+}
+
+/* SIG_KERNEL_ONLY: cannot be caught or ignored */
+inline bool sig_kernel_only(int sig)
+{
+ return sig == SIGKILL || sig == SIGSTOP;
+}
+
+/* ================================================================== */
+/* SigprocMask how values (Linux UAPI) */
+/* ================================================================== */
+
+inline constexpr int SIG_BLOCK = 0;
+inline constexpr int SIG_UNBLOCK = 1;
+inline constexpr int SIG_SETMASK = 2;
diff --git a/na/include/lume/smp.h b/na/include/lume/smp.h
new file mode 100644
index 0000000..a5292b6
--- /dev/null
+++ b/na/include/lume/smp.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * smp.h — Machine-Independent SMP primitives
+ *
+ * Provides cross-CPU communication (IPI) and TLB coordination.
+ * The MI interface calls through to arch-specific backends
+ * (e.g. SBI on RISC-V, APIC on x86).
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §SMP
+ */
+
+#include <lume/types.h>
+#include <lume/atomic.h>
+#include <lume/config.h>
+
+/*
+ * IPI reasons — each bit in the per-CPU pending mask
+ * corresponds to one reason. Multiple reasons can be
+ * pending simultaneously.
+ */
+enum IpiReason : uint32 {
+ IPI_RESCHEDULE = (1U << 0), /* Target should call schedule() */
+ IPI_TLB_SHOOTDOWN = (1U << 1), /* Target should flush TLB */
+ IPI_CALL_FUNCTION = (1U << 2), /* Target should run a function */
+ IPI_HALT = (1U << 3), /* Target should halt */
+};
+
+/*
+ * Per-CPU IPI pending state.
+ * Written by sender (atomically), consumed by receiver in IRQ context.
+ */
+struct IpiData {
+ lume::atomic<uint32> pending; /* Bitmask of IpiReason */
+
+ /* TLB shootdown payload */
+ uint64 tlb_start;
+ uint64 tlb_end;
+};
+
+/* Per-CPU IPI data array (BSS zero-initialized) */
+extern IpiData g_ipi_data[kMaxCpus];
+
+/*
+ * smp_send_ipi — Send an IPI to a set of CPUs
+ *
+ * @cpu_mask: bitmask of target CPU IDs (bit N = CPU N)
+ * @reason: reason bits to set in the target's pending mask
+ *
+ * Safe to call from any context (interrupt or process).
+ */
+void smp_send_ipi(uint64 cpu_mask, uint32 reason);
+
+/*
+ * smp_send_reschedule — Convenience: send reschedule IPI to one CPU
+ */
+inline void smp_send_reschedule(uint8 cpu_id)
+{
+ smp_send_ipi(1ULL << cpu_id, IPI_RESCHEDULE);
+}
+
+/*
+ * smp_init — Initialize SMP subsystem (called from BSP after trap_init)
+ */
+void smp_init();
+
+/*
+ * smp_handle_ipi — Called from trap handler on receiving S-mode
+ * software interrupt. Processes all pending IPI reasons.
+ */
+void smp_handle_ipi();
diff --git a/na/include/lume/stdarg.h b/na/include/lume/stdarg.h
new file mode 100644
index 0000000..c8dc7f4
--- /dev/null
+++ b/na/include/lume/stdarg.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef _LINUX_STDARG_H
+#define _LINUX_STDARG_H
+
+typedef __builtin_va_list va_list;
+#define va_start(v, l) __builtin_va_start(v, l)
+#define va_end(v) __builtin_va_end(v)
+#define va_arg(v, T) __builtin_va_arg(v, T)
+#define va_copy(d, s) __builtin_va_copy(d, s)
+
+#endif
diff --git a/na/include/lume/stringify.h b/na/include/lume/stringify.h
new file mode 100644
index 0000000..0e84cbe
--- /dev/null
+++ b/na/include/lume/stringify.h
@@ -0,0 +1,14 @@
+#ifndef __LINUX_STRINGIFY_H
+#define __LINUX_STRINGIFY_H
+
+/* Indirect stringification. Doing two levels allows the parameter to be a
+ * macro itself. For example, compile with -DFOO=bar, __stringify(FOO)
+ * converts to "bar".
+ */
+
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+#define FILE_LINE __FILE__ ":" __stringify(__LINE__)
+
+#endif /* !__LINUX_STRINGIFY_H */
diff --git a/na/include/lume/syscall_nr.h b/na/include/lume/syscall_nr.h
index 1b677d4..29e3904 100644
--- a/na/include/lume/syscall_nr.h
+++ b/na/include/lume/syscall_nr.h
@@ -47,11 +47,28 @@ inline constexpr int SYS_writev = 66;
inline constexpr int SYS_ppoll = 73;
inline constexpr int SYS_fstatat = 79;
inline constexpr int SYS_fstat = 80;
+inline constexpr int SYS_getdents64 = 61;
+
+inline constexpr int SYS_fcntl = 25;
+inline constexpr int SYS_linkat = 37;
+inline constexpr int SYS_mount = 40;
+inline constexpr int SYS_umount2 = 39;
inline constexpr int SYS_kill = 129;
+inline constexpr int SYS_tkill = 130;
+inline constexpr int SYS_tgkill = 131;
+inline constexpr int SYS_sigaltstack = 132;
inline constexpr int SYS_rt_sigaction = 134;
inline constexpr int SYS_rt_sigprocmask = 135;
inline constexpr int SYS_rt_sigreturn = 139;
+inline constexpr int SYS_reboot = 142;
+inline constexpr int SYS_setpgid = 154;
+inline constexpr int SYS_getpgid = 155;
+inline constexpr int SYS_getsid = 156;
+inline constexpr int SYS_setsid = 157;
+inline constexpr int SYS_prctl = 167;
+inline constexpr int SYS_geteuid = 175;
+inline constexpr int SYS_getegid = 177;
inline constexpr int SYS_futex = 98;
inline constexpr int SYS_nanosleep = 101;
@@ -59,3 +76,7 @@ inline constexpr int SYS_clock_gettime = 113;
inline constexpr int SYS_epoll_create1 = 20;
inline constexpr int SYS_epoll_ctl = 21;
inline constexpr int SYS_epoll_pwait = 22;
+
+inline constexpr int SYS_rt_sigtimedwait = 137;
+inline constexpr int SYS_socket = 198;
+inline constexpr int SYS_sendto = 206;
diff --git a/na/include/lume/task.h b/na/include/lume/task.h
index c87938c..e959147 100644
--- a/na/include/lume/task.h
+++ b/na/include/lume/task.h
@@ -17,7 +17,9 @@
#include <lume/list.h>
#include <arch/context.h>
#include <arch/trapframe.h>
+#include <arch/fpstate.h>
#include <lume/waitqueue.h>
+#include <lume/signal_types.h>
/* Forward declarations */
struct VmSpace;
@@ -33,6 +35,7 @@ enum class TaskState : uint8 {
Sleeping = 2, // Blocked on a WaitQueue
Zombie = 3, // Exited, waiting for parent wait()
Dead = 4, // Fully reclaimed, TCB can be freed
+ Stopped = 5, // Job control: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU
};
/* ========================================================================
@@ -43,13 +46,15 @@ inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user m
inline constexpr uint8 TF_IDLE = 0x02; // Idle thread
inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1)
-inline constexpr int kExitCodeSignalFlag = 0x100;
-
/* ========================================================================
* Kernel Stack
* ======================================================================== */
inline constexpr uint64 kKernelStackSize = CONFIG_KERNEL_STACK_SIZE;
+inline constexpr int kKernelStackOrder = (kKernelStackSize <= 4096) ? 0
+ : (kKernelStackSize <= 8192) ? 1
+ : (kKernelStackSize <= 16384) ? 2
+ : (kKernelStackSize <= 32768) ? 3 : 4;
inline constexpr uint64 kStackCanary = 0xDEAD'BEEF'CAFE'BABEULL;
/* ========================================================================
@@ -66,6 +71,7 @@ struct TaskControlBlock {
int32 time_slice; // Remaining time slice (ticks)
int32 pid; // Unique task ID (Linux: tid)
int32 tgid; // Thread group ID (Linux: pid)
+ int32 preempt_count; // >0: preemption disabled (holding locks etc.)
list_node run_link; // RunQueue list node
list_node all_link; // Global task list node
@@ -73,6 +79,7 @@ struct TaskControlBlock {
/* ===== Context ===== */
arch::Context context; // Callee-saved registers (scheduler switch)
arch::TrapFrame* trapframe; // Trap frame (user-mode entry/exit)
+ arch::FpState fpstate; // FPU register state (f0-f31 + fcsr)
/* ===== Kernel Stack ===== */
uint64 kstack_pa; // Physical address
@@ -81,6 +88,11 @@ struct TaskControlBlock {
/* ===== Address Space ===== */
VmSpace* mm; // nullptr = kernel thread
+ /* ===== Per-task lock =====
+ * Protects: parent, children, sibling, exit_code, state transitions.
+ * Lock ordering: task->lock -> g_task_list_lock (never reverse). */
+ Spinlock lock;
+
/* ===== Task Hierarchy ===== */
TaskControlBlock* parent;
list_node children; // Children list head
@@ -89,11 +101,34 @@ struct TaskControlBlock {
/* ===== Exit ===== */
int32 exit_code;
+ int32 exit_signal; // Linux: signal sent to parent on exit.
+ // SIGCHLD = notify parent, become zombie
+ // -1 = auto-reap (kthreads)
+ uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: write 0 here on exit
+
+ /* ===== Job control reporting =====
+ * stopped_code: 0 means "no pending stop notification"; non-zero is the
+ * Linux wstatus value to hand back from wait4(WUNTRACED). Cleared
+ * after the parent consumes it via wait4 (or task termination).
+ * notify_continued: set when SIGCONT resumes us; consumed by
+ * wait4(WCONTINUED). */
+ int32 stopped_code;
+ bool notify_continued;
/* ===== Scheduler class metadata ===== */
const SchedClass* sched_class;
void* sched_entity;
+ /* ===== VFS ===== */
+ struct files_struct* files; // Per-task fd table (shared via CLONE_FILES)
+ struct fs_struct* fs; // cwd/root (shared via CLONE_FS)
+
+ /* ===== Signals ===== */
+ struct sigpending pending; // Per-task pending signal queue
+ sigset_t blocked; // Blocked signal mask
+ struct sighand_struct* sighand; // Signal handler table (shared via CLONE_SIGHAND)
+ struct signal_struct* signal; // Thread-group identity: pgid/sid/ctty (shared via CLONE_THREAD)
+
/* ===== Debug ===== */
const char* name;
};
diff --git a/na/include/lume/timer.h b/na/include/lume/timer.h
index e7de2f1..9bdddd5 100644
--- a/na/include/lume/timer.h
+++ b/na/include/lume/timer.h
@@ -9,9 +9,24 @@
* MI Timer Subsystem
*/
+#include <lume/list.h>
+
+struct TimerList {
+ list_node entry;
+ uint64 expires;
+ void (*function)(TimerList*);
+ void* data;
+
+ void init(void (*cb)(TimerList*)) {
+ entry.init();
+ function = cb;
+ data = nullptr;
+ expires = 0;
+ }
+};
+
extern uint64 g_jiffies;
-// BSP global initialization
void timer_init();
// AP per-CPU initialization
@@ -22,3 +37,7 @@ void timer_tick();
// Get configured timebase frequency
uint64 timer_get_freq();
+
+void add_timer(TimerList* timer);
+int del_timer(TimerList* timer);
+int mod_timer(TimerList* timer, uint64 expires);
diff --git a/na/include/lume/tlb.h b/na/include/lume/tlb.h
new file mode 100644
index 0000000..b4d51ea
--- /dev/null
+++ b/na/include/lume/tlb.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * tlb.h — Machine-Independent TLB management interface
+ *
+ * All TLB invalidation in the kernel MUST go through these functions.
+ * Direct use of arch::mmu::flush_tlb_*() is prohibited outside of
+ * this file's implementation.
+ *
+ * Current implementation: local CPU sfence.vma only (single-core).
+ * When SMP IPI infrastructure is available, tlb_shootdown_range()
+ * and tlb_shootdown_all() will broadcast invalidation to remote
+ * CPUs via IPI — no caller changes required.
+ *
+ * Reference: docs/specs/linux_abi_mm_plan.md §4
+ */
+
+#include <lume/types.h>
+
+struct VmSpace;
+
+/*
+ * Invalidate TLB entries for a VA range [start, end) on ALL CPUs
+ * that may have cached translations for the given address space.
+ *
+ * @mm: address space (used to determine which CPUs to IPI;
+ * nullptr means kernel address space = all CPUs).
+ * @start: first VA to invalidate (page-aligned).
+ * @end: one-past-last VA to invalidate (page-aligned).
+ *
+ * If (end - start) exceeds a threshold, falls back to full flush.
+ */
+void tlb_shootdown_range(VmSpace* mm, uint64 start, uint64 end);
+
+/*
+ * Invalidate a single page's TLB entry on ALL CPUs.
+ * Convenience wrapper around tlb_shootdown_range().
+ */
+void tlb_shootdown_page(VmSpace* mm, uint64 va);
+
+/*
+ * Full TLB flush on ALL CPUs.
+ * Used after page table switch, large-scale munmap, etc.
+ */
+void tlb_shootdown_all();
diff --git a/na/include/lume/tty.h b/na/include/lume/tty.h
new file mode 100644
index 0000000..34cf7bb
--- /dev/null
+++ b/na/include/lume/tty.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * tty.h — TTY subsystem core structures
+ *
+ * tty_struct is the central object representing an open terminal.
+ * It connects a hardware driver (tty_driver) with a line discipline
+ * (tty_ldisc) to implement the full terminal abstraction.
+ *
+ * Architecture:
+ * user read/write → tty_core → tty_ldisc (n_tty) → tty_driver (uart/pty)
+ *
+ * Reference: Linux include/linux/tty.h, include/linux/tty_ldisc.h
+ */
+
+#include <lume/types.h>
+#include <lume/tty_ioctl.h>
+#include <lume/waitqueue.h>
+#include <kernel/sync/spinlock.h>
+
+struct tty_struct;
+struct file;
+struct file_operations;
+struct inode;
+
+/* ================================================================== */
+/* tty_driver — hardware/pseudo-terminal driver interface */
+/* ================================================================== */
+
+struct tty_driver_ops {
+ int (*open)(tty_struct* tty);
+ void (*close)(tty_struct* tty);
+ int (*write)(tty_struct* tty, const char* buf, int count);
+ int (*write_room)(tty_struct* tty);
+ void (*put_char)(tty_struct* tty, char ch);
+ void (*flush_chars)(tty_struct* tty);
+ int (*chars_in_buffer)(tty_struct* tty);
+ void (*flush_buffer)(tty_struct* tty);
+ int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg);
+ void (*throttle)(tty_struct* tty);
+ void (*unthrottle)(tty_struct* tty);
+ void (*hangup)(tty_struct* tty);
+};
+
+struct tty_driver {
+ const char* name; /* "ttyS", "ttySIF", "pts" */
+ int major;
+ int minor_start;
+ int num; /* number of devices */
+ const tty_driver_ops* ops;
+};
+
+/* ================================================================== */
+/* tty_ldisc — line discipline interface */
+/* ================================================================== */
+
+struct tty_ldisc_ops {
+ const char* name;
+ int num; /* N_TTY = 0 */
+
+ int (*open)(tty_struct* tty);
+ void (*close)(tty_struct* tty);
+ int (*read)(tty_struct* tty, file* fp, char* buf, uint64 count);
+ int (*write)(tty_struct* tty, file* fp, const char* buf, uint64 count);
+ void (*receive_buf)(tty_struct* tty, const char* buf, int count);
+ int (*ioctl)(tty_struct* tty, unsigned int cmd, uint64 arg);
+ void (*flush_buffer)(tty_struct* tty);
+ int (*chars_in_buffer)(tty_struct* tty);
+ void (*set_termios)(tty_struct* tty, const termios* old);
+ void (*hangup)(tty_struct* tty);
+};
+
+struct tty_ldisc {
+ const tty_ldisc_ops* ops;
+ void* data; /* ldisc private data (n_tty_data*) */
+};
+
+/* ================================================================== */
+/* tty_struct — central TTY object */
+/* ================================================================== */
+
+inline constexpr int TTY_BUF_SIZE = 4096;
+
+/* TTY flags */
+inline constexpr uint32 TTY_THROTTLED = (1 << 0);
+inline constexpr uint32 TTY_IO_ERROR = (1 << 1);
+inline constexpr uint32 TTY_CLOSING = (1 << 2);
+inline constexpr uint32 TTY_HUPPING = (1 << 3);
+
+struct tty_struct {
+ int index; /* device index */
+ const tty_driver* driver; /* hardware driver */
+ tty_ldisc ldisc; /* current line discipline */
+ tty_struct* link; /* PTY peer (master ↔ slave) */
+
+ termios termios_data; /* terminal settings */
+ winsize ws; /* window size */
+
+ int32 pgrp; /* foreground process group */
+ int32 session; /* controlling session */
+
+ /* Output buffer (tty_core → driver) */
+ char write_buf[TTY_BUF_SIZE];
+ int write_cnt;
+
+ /* Synchronization */
+ WaitQueue read_wait;
+ WaitQueue write_wait;
+ Spinlock lock;
+
+ uint32 flags;
+ int open_count;
+
+ /* Private driver data */
+ void* driver_data;
+};
+
+/* ================================================================== */
+/* TTY core API */
+/* ================================================================== */
+
+/* Initialize the TTY subsystem (creates /dev/console TTY) */
+void tty_init();
+
+/* Allocate and initialize a new tty_struct */
+tty_struct* tty_alloc(const tty_driver* driver, int index);
+
+/* Open/close a TTY (called from chardev open/close) */
+int tty_open(tty_struct* tty);
+void tty_close(tty_struct* tty);
+
+/* Core I/O (called from file_operations) */
+int64 tty_read(tty_struct* tty, file* fp, char* buf, uint64 count);
+int64 tty_write(tty_struct* tty, file* fp, const char* buf, uint64 count);
+long tty_ioctl(tty_struct* tty, file* fp, unsigned int cmd, uint64 arg);
+
+/* Called by hardware driver when bytes arrive */
+void tty_receive_buf(tty_struct* tty, const char* buf, int count);
+
+/* Signal helpers */
+void tty_signal_pgrp(tty_struct* tty, int sig);
+
+/* file_operations for TTY devices */
+extern file_operations tty_fops;
+
+/* Global console TTY */
+extern tty_struct* g_console_tty;
+
+/* ================================================================== */
+/* N_TTY line discipline */
+/* ================================================================== */
+
+extern const tty_ldisc_ops n_tty_ops;
+
+/* ================================================================== */
+/* UART TTY driver */
+/* ================================================================== */
+
+extern const tty_driver uart_tty_driver;
+
+/* Initialize UART as a TTY driver (registers RX IRQ) */
+void uart_tty_init(tty_struct* tty);
diff --git a/na/include/lume/tty_ioctl.h b/na/include/lume/tty_ioctl.h
new file mode 100644
index 0000000..87ba59b
--- /dev/null
+++ b/na/include/lume/tty_ioctl.h
@@ -0,0 +1,202 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * tty_ioctl.h — Terminal I/O control constants and structures
+ *
+ * Linux-compatible termios, winsize, and TIOC* ioctl definitions.
+ * Values match the RISC-V/generic Linux ABI.
+ *
+ * Reference: include/uapi/asm-generic/termbits.h,
+ * include/uapi/asm-generic/ioctls.h
+ */
+
+#include <lume/types.h>
+
+/* ================================================================== */
+/* termios c_cc indices */
+/* ================================================================== */
+
+inline constexpr int VINTR = 0;
+inline constexpr int VQUIT = 1;
+inline constexpr int VERASE = 2;
+inline constexpr int VKILL = 3;
+inline constexpr int VEOF = 4;
+inline constexpr int VTIME = 5;
+inline constexpr int VMIN = 6;
+inline constexpr int VSWTC = 7;
+inline constexpr int VSTART = 8;
+inline constexpr int VSTOP = 9;
+inline constexpr int VSUSP = 10;
+inline constexpr int VEOL = 11;
+inline constexpr int VREPRINT = 12;
+inline constexpr int VDISCARD = 13;
+inline constexpr int VWERASE = 14;
+inline constexpr int VLNEXT = 15;
+inline constexpr int VEOL2 = 16;
+inline constexpr int NCCS = 19;
+
+/* ================================================================== */
+/* c_iflag — input mode flags */
+/* ================================================================== */
+
+inline constexpr uint32 IGNBRK = 0000001;
+inline constexpr uint32 BRKINT = 0000002;
+inline constexpr uint32 IGNPAR = 0000004;
+inline constexpr uint32 PARMRK = 0000010;
+inline constexpr uint32 INPCK = 0000020;
+inline constexpr uint32 ISTRIP = 0000040;
+inline constexpr uint32 INLCR = 0000100;
+inline constexpr uint32 IGNCR = 0000200;
+inline constexpr uint32 ICRNL = 0000400;
+inline constexpr uint32 IUCLC = 0001000;
+inline constexpr uint32 IXON = 0002000;
+inline constexpr uint32 IXANY = 0004000;
+inline constexpr uint32 IXOFF = 0010000;
+inline constexpr uint32 IMAXBEL = 0020000;
+inline constexpr uint32 IUTF8 = 0040000;
+
+/* ================================================================== */
+/* c_oflag — output mode flags */
+/* ================================================================== */
+
+inline constexpr uint32 OPOST = 0000001;
+inline constexpr uint32 OLCUC = 0000002;
+inline constexpr uint32 ONLCR = 0000004;
+inline constexpr uint32 OCRNL = 0000010;
+inline constexpr uint32 ONOCR = 0000020;
+inline constexpr uint32 ONLRET = 0000040;
+inline constexpr uint32 OFILL = 0000100;
+inline constexpr uint32 OFDEL = 0000200;
+
+/* ================================================================== */
+/* c_cflag — control mode flags */
+/* ================================================================== */
+
+inline constexpr uint32 CSIZE = 0000060;
+inline constexpr uint32 CS5 = 0000000;
+inline constexpr uint32 CS6 = 0000020;
+inline constexpr uint32 CS7 = 0000040;
+inline constexpr uint32 CS8 = 0000060;
+inline constexpr uint32 CSTOPB = 0000100;
+inline constexpr uint32 CREAD = 0000200;
+inline constexpr uint32 PARENB = 0000400;
+inline constexpr uint32 PARODD = 0001000;
+inline constexpr uint32 HUPCL = 0002000;
+inline constexpr uint32 CLOCAL = 0004000;
+
+/* Baud rates (stored in c_cflag masked with CBAUD) */
+inline constexpr uint32 CBAUD = 0010017;
+inline constexpr uint32 B0 = 0000000;
+inline constexpr uint32 B9600 = 0000015;
+inline constexpr uint32 B19200 = 0000016;
+inline constexpr uint32 B38400 = 0000017;
+inline constexpr uint32 B115200 = 0010002;
+
+/* ================================================================== */
+/* c_lflag — local mode flags */
+/* ================================================================== */
+
+inline constexpr uint32 ISIG = 0000001;
+inline constexpr uint32 ICANON = 0000002;
+inline constexpr uint32 XCASE = 0000004;
+inline constexpr uint32 LECHO = 0000010; /* Named LECHO to avoid macro conflict */
+inline constexpr uint32 ECHOE = 0000020;
+inline constexpr uint32 ECHOK = 0000040;
+inline constexpr uint32 ECHONL = 0000100;
+inline constexpr uint32 NOFLSH = 0000200;
+inline constexpr uint32 TOSTOP = 0000400;
+inline constexpr uint32 ECHOCTL = 0001000;
+inline constexpr uint32 ECHOPRT = 0002000;
+inline constexpr uint32 ECHOKE = 0004000;
+inline constexpr uint32 IEXTEN = 0100000;
+
+/* Linux uses ECHO = 0x0008, we use LECHO to avoid reserved-name issues.
+ * Provide the standard name as well for ioctl compatibility. */
+inline constexpr uint32 ECHO = LECHO;
+
+/* ================================================================== */
+/* termios structure */
+/* ================================================================== */
+
+struct termios {
+ uint32 c_iflag; /* input mode flags */
+ uint32 c_oflag; /* output mode flags */
+ uint32 c_cflag; /* control mode flags */
+ uint32 c_lflag; /* local mode flags */
+ uint8 c_line; /* line discipline */
+ uint8 c_cc[NCCS]; /* control characters */
+};
+
+/* ================================================================== */
+/* winsize structure */
+/* ================================================================== */
+
+struct winsize {
+ uint16 ws_row;
+ uint16 ws_col;
+ uint16 ws_xpixel;
+ uint16 ws_ypixel;
+};
+
+/* ================================================================== */
+/* ioctl command numbers (Linux asm-generic/ioctls.h) */
+/* ================================================================== */
+
+inline constexpr uint32 TCGETS = 0x5401;
+inline constexpr uint32 TCSETS = 0x5402;
+inline constexpr uint32 TCSETSW = 0x5403;
+inline constexpr uint32 TCSETSF = 0x5404;
+inline constexpr uint32 TCGETA = 0x5405;
+inline constexpr uint32 TCSETA = 0x5406;
+inline constexpr uint32 TCSETAW = 0x5407;
+inline constexpr uint32 TCSETAF = 0x5408;
+inline constexpr uint32 TCSBRK = 0x5409;
+inline constexpr uint32 TCXONC = 0x540A;
+inline constexpr uint32 TCFLSH = 0x540B;
+inline constexpr uint32 TIOCEXCL = 0x540C;
+inline constexpr uint32 TIOCNXCL = 0x540D;
+inline constexpr uint32 TIOCSCTTY = 0x540E;
+inline constexpr uint32 TIOCGPGRP = 0x540F;
+inline constexpr uint32 TIOCSPGRP = 0x5410;
+inline constexpr uint32 TIOCOUTQ = 0x5411;
+inline constexpr uint32 TIOCSTI = 0x5412;
+inline constexpr uint32 TIOCGWINSZ = 0x5413;
+inline constexpr uint32 TIOCSWINSZ = 0x5414;
+inline constexpr uint32 TIOCMGET = 0x5415;
+inline constexpr uint32 TIOCMSET = 0x5418;
+inline constexpr uint32 FIONREAD = 0x541B;
+inline constexpr uint32 TIOCNOTTY = 0x5422;
+inline constexpr uint32 TIOCGPTLCK = 0x80045430;
+inline constexpr uint32 TIOCSPTLCK = 0x40045431;
+
+/* Default termios for a new TTY */
+inline void tty_default_termios(termios* t)
+{
+ t->c_iflag = ICRNL | IXON | IUTF8;
+ t->c_oflag = OPOST | ONLCR;
+ t->c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;
+ t->c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
+ t->c_line = 0; /* N_TTY */
+
+ for (int i = 0; i < NCCS; i++)
+ t->c_cc[i] = 0;
+
+ t->c_cc[VINTR] = 003; /* Ctrl+C */
+ t->c_cc[VQUIT] = 034; /* Ctrl+\ */
+ t->c_cc[VERASE] = 0177; /* DEL */
+ t->c_cc[VKILL] = 025; /* Ctrl+U */
+ t->c_cc[VEOF] = 004; /* Ctrl+D */
+ t->c_cc[VSTART] = 021; /* Ctrl+Q */
+ t->c_cc[VSTOP] = 023; /* Ctrl+S */
+ t->c_cc[VSUSP] = 032; /* Ctrl+Z */
+ t->c_cc[VREPRINT] = 022; /* Ctrl+R */
+ t->c_cc[VWERASE] = 027; /* Ctrl+W */
+ t->c_cc[VLNEXT] = 026; /* Ctrl+V */
+ t->c_cc[VEOL] = 0;
+ t->c_cc[VEOL2] = 0;
+ t->c_cc[VMIN] = 1;
+ t->c_cc[VTIME] = 0;
+}
diff --git a/na/include/lume/types.h b/na/include/lume/types.h
index 1d5b896..2342359 100644
--- a/na/include/lume/types.h
+++ b/na/include/lume/types.h
@@ -1,35 +1,78 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (C) 2026 Richard Qin */
-#pragma once
+#ifndef _LUME_TYPES_H
+#define _LUME_TYPES_H
/*
* LumeOS Base Type Definitions
*
- * All kernel code should use these types instead of
- * stdint.h (which may not exist in freestanding mode).
+ * Three naming layers coexist so that source files imported from
+ * upstream projects (Linux kernel, libfdt) compile unchanged:
*
- * Kernel-wide configuration constants are in <lume/config.h>.
+ * LumeOS native uint8 / int8 / ... / uint64 / int64
+ * Linux kernel short names u8 / s8 / ... / u64 / s64
+ * POSIX / C99 (with _t) uint8_t / ... / uint64_t / intptr_t
+ *
+ * All three resolve to the same underlying compiler builtin, so they
+ * are binary-compatible.
+ *
+ * Declared with C `typedef` (not C++ `using`) so this header may be
+ * included from .c sources — required for libfdt shims and the
+ * arch/riscv/kernel/pi/ subsystem, which are pure C imports from
+ * Linux. C++ accepts `typedef` syntax identically.
*/
-using uint8 = unsigned char;
-using uint16 = unsigned short;
-using uint32 = unsigned int;
-using uint64 = unsigned long long;
+/* ===== LumeOS native names ===== */
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef unsigned int uint32;
+typedef unsigned long long uint64;
-using int8 = signed char;
-using int16 = signed short;
-using int32 = signed int;
-using int64 = signed long long;
+typedef signed char int8;
+typedef signed short int16;
+typedef signed int int32;
+typedef signed long long int64;
-typedef __SIZE_TYPE__ size_t;
-typedef __PTRDIFF_TYPE__ ptrdiff_t;
-using ssize_t = int64;
-using uintptr = uint64;
+typedef __SIZE_TYPE__ size_t;
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef int64 ssize_t;
+typedef uint64 uintptr;
-#define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER)
+/* ===== Linux kernel short names ===== */
+typedef uint8 u8; typedef int8 s8;
+typedef uint16 u16; typedef int16 s16;
+typedef uint32 u32; typedef int32 s32;
+typedef uint64 u64; typedef int64 s64;
-/* Null pointer constant */
-#ifndef nullptr
-/* C++11 and later have nullptr built-in */
+/* ===== POSIX / C99 aliases =====
+ * Guarded so a later #include <stdint.h> from libc-shim during musl
+ * userspace builds is a no-op rather than a redefinition error. */
+#ifndef _STDINT_H
+# define _STDINT_H
+typedef uint8 uint8_t;
+typedef uint16 uint16_t;
+typedef uint32 uint32_t;
+typedef uint64 uint64_t;
+typedef int8 int8_t;
+typedef int16 int16_t;
+typedef int32 int32_t;
+typedef int64 int64_t;
+typedef uintptr uintptr_t;
+typedef int64 intptr_t;
#endif
+
+/* ===== Linux domain typedefs needed by RISC-V boot path ===== */
+typedef uint64 phys_addr_t;
+
+/* ===== libfdt on-wire types (big-endian). Real byte-swap helpers
+ * live in <lume/libfdt_env.h>. ===== */
+typedef uint16 fdt16_t;
+typedef uint32 fdt32_t;
+typedef uint64 fdt64_t;
+
+#ifndef offsetof
+# define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER)
+#endif
+
+#endif /* _LUME_TYPES_H */
diff --git a/na/include/lume/uuid.h b/na/include/lume/uuid.h
new file mode 100644
index 0000000..fcbd4a3
--- /dev/null
+++ b/na/include/lume/uuid.h
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * UUID/GUID definition
+ *
+ * Copyright (C) 2010, 2016 Intel Corp.
+ * Huang Ying <ying.huang@intel.com>
+ */
+
+#pragma once
+
+#include <lume/types.h>
+#include <libc-shim/string.h>
+
+constexpr uint32 UUID_SIZE = 16;
+constexpr uint32 UUID_STRING_LEN = 36;
+
+static inline int uuid_hex_value(char c)
+{
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ }
+ if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ return -1;
+}
+
+static inline bool uuid_parse_raw(const char* uuid, uint8 out[UUID_SIZE])
+{
+ if (!uuid) {
+ return false;
+ }
+ if (strlen(uuid) != UUID_STRING_LEN) {
+ return false;
+ }
+ if (uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || uuid[23] != '-') {
+ return false;
+ }
+
+ int out_index = 0;
+ for (int i = 0; i < static_cast<int>(UUID_STRING_LEN); ) {
+ if (uuid[i] == '-') {
+ ++i;
+ continue;
+ }
+
+ int hi = uuid_hex_value(uuid[i]);
+ int lo = uuid_hex_value(uuid[i + 1]);
+ if (hi < 0 || lo < 0) {
+ return false;
+ }
+
+ out[out_index++] = static_cast<uint8>((hi << 4) | lo);
+ i += 2;
+ }
+
+ return out_index == static_cast<int>(UUID_SIZE);
+}
+
+struct uuid_base {
+ uint8 b[UUID_SIZE];
+
+ bool is_null() const
+ {
+ for (uint32 i = 0; i < UUID_SIZE; ++i) {
+ if (b[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool operator==(const uuid_base& other) const
+ {
+ return memcmp(b, other.b, UUID_SIZE) == 0;
+ }
+
+ bool operator!=(const uuid_base& other) const
+ {
+ return !(*this == other);
+ }
+};
+
+struct uuid_t : public uuid_base {
+ explicit uuid_t(const uint8 in_b[UUID_SIZE])
+ {
+ memcpy(b, in_b, UUID_SIZE);
+ }
+
+ explicit constexpr uuid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1,
+ uint8 d2, uint8 d3, uint8 d4, uint8 d5,
+ uint8 d6, uint8 d7)
+ : uuid_base{{
+ static_cast<uint8>((a >> 24) & 0xff),
+ static_cast<uint8>((a >> 16) & 0xff),
+ static_cast<uint8>((a >> 8) & 0xff),
+ static_cast<uint8>(a & 0xff),
+ static_cast<uint8>((b >> 8) & 0xff),
+ static_cast<uint8>(b & 0xff),
+ static_cast<uint8>((c >> 8) & 0xff),
+ static_cast<uint8>(c & 0xff),
+ d0, d1, d2, d3, d4, d5, d6, d7
+ }}
+ {
+ }
+
+ constexpr uuid_t() : uuid_base{{0}} {}
+
+ static bool parse(const char* uuid, uuid_t& out)
+ {
+ uint8 tmp[UUID_SIZE];
+ if (!uuid_parse_raw(uuid, tmp)) {
+ return false;
+ }
+
+ memcpy(out.b, tmp, UUID_SIZE);
+ return true;
+ }
+
+ static bool is_valid(const char* uuid)
+ {
+ uint8 tmp[UUID_SIZE];
+ return uuid_parse_raw(uuid, tmp);
+ }
+
+ static constexpr uuid_t null()
+ {
+ return uuid_t();
+ }
+
+ void import_bytes(const uint8* src)
+ {
+ memcpy(b, src, UUID_SIZE);
+ }
+
+ void export_bytes(uint8* dst) const
+ {
+ memcpy(dst, b, UUID_SIZE);
+ }
+};
+
+struct guid_t : public uuid_base {
+ explicit guid_t(const uint8 in_b[UUID_SIZE])
+ {
+ memcpy(b, in_b, UUID_SIZE);
+ }
+
+ explicit constexpr guid_t(uint32 a, uint16 b, uint16 c, uint8 d0, uint8 d1,
+ uint8 d2, uint8 d3, uint8 d4, uint8 d5,
+ uint8 d6, uint8 d7)
+ : uuid_base{{
+ static_cast<uint8>(a & 0xff),
+ static_cast<uint8>((a >> 8) & 0xff),
+ static_cast<uint8>((a >> 16) & 0xff),
+ static_cast<uint8>((a >> 24) & 0xff),
+ static_cast<uint8>(b & 0xff),
+ static_cast<uint8>((b >> 8) & 0xff),
+ static_cast<uint8>(c & 0xff),
+ static_cast<uint8>((c >> 8) & 0xff),
+ d0, d1, d2, d3, d4, d5, d6, d7
+ }}
+ {
+ }
+
+ constexpr guid_t() : uuid_base{{0}} {}
+ static bool parse(const char* uuid, guid_t& out)
+ {
+ uint8 tmp[UUID_SIZE];
+ if (!uuid_parse_raw(uuid, tmp)) {
+ return false;
+ }
+
+ static constexpr uint8 guid_index[UUID_SIZE] = {
+ 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15,
+ };
+
+ for (int i = 0; i < static_cast<int>(UUID_SIZE); ++i) {
+ out.b[guid_index[i]] = tmp[i];
+ }
+ return true;
+ }
+
+ static bool is_valid(const char* uuid)
+ {
+ uint8 tmp[UUID_SIZE];
+ return uuid_parse_raw(uuid, tmp);
+ }
+
+ static constexpr guid_t null()
+ {
+ return guid_t();
+ }
+
+ void import_bytes(const uint8* src)
+ {
+ memcpy(b, src, UUID_SIZE);
+ }
+
+ void export_bytes(uint8* dst) const
+ {
+ memcpy(dst, b, UUID_SIZE);
+ }
+};
+
diff --git a/na/include/lume/vmm.h b/na/include/lume/vmm.h
index 85988e4..9cbebf1 100644
--- a/na/include/lume/vmm.h
+++ b/na/include/lume/vmm.h
@@ -13,6 +13,7 @@
#include <lume/types.h>
#include <lume/rbtree.h>
+#include <lume/atomic.h>
#include <kernel/sync/spinlock.h>
/* ------------------------------------------------------------------ */
@@ -61,14 +62,25 @@ struct VmSpace {
uint64 root_pa; // SV39 root page table physical address
struct rb_root vma_tree; // Red-black tree of VmAreaStructs
Spinlock lock; // Protects vma_tree mutations
+ lume::atomic<int32> refcount; // Reference count (for CLONE_VM sharing)
+ lume::atomic<uint64> cpu_mask; // Bitmask of CPUs with this mm in satp
uint64 brk_start; // Start of heap (page-aligned)
uint64 brk_end; // Current heap end (page-aligned)
+ uint64 mmap_base; // Base for mmap (Linux: TASK_UNMAPPED_BASE = TASK_SIZE/3)
- // Create a brand new VmSpace (allocates root pagetable)
+ // Create a brand new VmSpace (allocates root pagetable, refcount=1)
static VmSpace* create();
- // Destroy VmSpace, unmapping all VMAs and freeing pagetables
+ // Increment reference count (for CLONE_VM)
+ void incref();
+
+ // Decrement reference count; destroys VmSpace when it reaches 0.
+ // Call this instead of destroy() directly.
+ void put();
+
+ // Destroy VmSpace, unmapping all VMAs and freeing pagetables.
+ // Internal — called by put() when refcount drops to 0.
void destroy();
};
diff --git a/na/include/lume/wait.h b/na/include/lume/wait.h
index 02012ab..865c1f2 100644
--- a/na/include/lume/wait.h
+++ b/na/include/lume/wait.h
@@ -11,12 +11,21 @@
/* waitpid options */
inline constexpr int WNOHANG = 1;
inline constexpr int WUNTRACED = 2;
+inline constexpr int WCONTINUED = 8;
+inline constexpr int WSTOPPED = WUNTRACED; /* POSIX alias */
+inline constexpr int WEXITED = 4; /* POSIX */
+inline constexpr int WNOWAIT = 0x01000000; /* POSIX: peek, don't reap */
/* Exit status encoding (Linux-compatible) */
-static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; }
-static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; }
-static inline bool WIFSIGNALED(int s){ return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; }
-static inline int WTERMSIG(int s) { return s & 0x7F; }
+static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; }
+static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; }
+static inline bool WIFSIGNALED(int s) { return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; }
+static inline int WTERMSIG(int s) { return s & 0x7F; }
+static inline bool WIFSTOPPED(int s) { return (s & 0xFF) == 0x7F; }
+static inline int WSTOPSIG(int s) { return (s >> 8) & 0xFF; }
+static inline bool WIFCONTINUED(int s){ return s == 0xFFFF; }
/* Encode exit code into wstatus (normal exit) */
-static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; }
+static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; }
+static inline int W_STOPCODE(int sig) { return ((sig & 0xFF) << 8) | 0x7F; }
+static inline int W_CONTINUED() { return 0xFFFF; }
diff --git a/na/include/lume/waitqueue.h b/na/include/lume/waitqueue.h
index dd5edfe..3720cb7 100644
--- a/na/include/lume/waitqueue.h
+++ b/na/include/lume/waitqueue.h
@@ -6,11 +6,24 @@
/*
* WaitQueue — blocking synchronization primitive
*
- * A thread calls wq.sleep() to block itself until another thread
- * (or interrupt handler) calls wq.wake_one() or wq.wake_all().
+ * Supports two usage patterns:
*
- * Implementation: intrusive doubly-linked list of sleeping tasks,
- * protected by an internal spinlock.
+ * 1. Simple (legacy): wq.sleep() — combines prepare/schedule/finish.
+ * Suitable for simple cases where no external condition is checked.
+ *
+ * 2. Linux-style two-phase (recommended for correctness):
+ *
+ * wq.prepare_to_wait(curr, TaskState::Sleeping);
+ * if (!condition) {
+ * schedule();
+ * }
+ * wq.finish_wait(curr);
+ *
+ * This pattern prevents the "lost wakeup" race: the task is already
+ * on the wait list (and marked Sleeping) BEFORE checking the condition.
+ * If a wake_one/wake_all fires between the check and schedule(), the
+ * task's state will already be set back to Runnable, and schedule()
+ * will see state == Runnable and simply re-run the task.
*
* Reference: docs/specs/scheduler.md, docs/specs/task.md §5
*/
@@ -20,6 +33,7 @@
#include <kernel/sync/spinlock.h>
struct TaskControlBlock;
+enum class TaskState : uint8;
class WaitQueue {
public:
@@ -28,11 +42,26 @@ public:
lock_.init(name);
}
- /* Block the current task on this queue.
+ /*
+ * Phase 1: Add the task to the wait queue and set its state.
+ * The task is NOT yet descheduled — the caller must check the
+ * condition and call schedule() if the condition is still false.
+ *
+ * Must be called with interrupts disabled or preemption disabled.
+ */
+ void prepare_to_wait(TaskControlBlock* task, TaskState new_state);
+
+ /*
+ * Phase 3: Remove the task from the wait queue (if still on it)
+ * and ensure its state is Runnable. Called after schedule() returns
+ * (i.e. after the task has been woken up, or if the condition was
+ * true and schedule() was skipped).
+ */
+ void finish_wait(TaskControlBlock* task);
+
+ /* Legacy convenience: combines prepare_to_wait + schedule + finish_wait.
* If external_lock is provided, it is released AFTER the task is
- * added to the wait queue but BEFORE schedule() is called,
- * to avoid lost wakeups.
- * Returns after wake_one/wake_all unblocks this task. */
+ * added to the wait queue but BEFORE schedule() is called. */
void sleep(Spinlock* external_lock = nullptr);
/* Wake the first (oldest) sleeping task. Returns true if a task was woken. */