diff options
Diffstat (limited to 'na/include')
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. */ |
