diff options
Diffstat (limited to 'include')
136 files changed, 7417 insertions, 4635 deletions
diff --git a/include/common/fcntl.h b/include/common/fcntl.h deleted file mode 100644 index 6059538..0000000 --- a/include/common/fcntl.h +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -#define O_RDONLY 0x000 -#define O_WRONLY 0x001 -#define O_RDWR 0x002 -#define O_CREATE 0x200 -#define O_TRUNC 0x400 - -#define NOFILE 16
\ No newline at end of file diff --git a/include/common/types.h b/include/common/types.h deleted file mode 100644 index aa2cb08..0000000 --- a/include/common/types.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef unsigned long long uint64_t; - -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef signed long long int64_t; - -typedef uint64_t size_t; -typedef uint64_t uintptr_t; - -typedef uint64_t uint64; -typedef uint32_t uint32; -typedef uint16_t uint16; -typedef uint8_t uint8; -typedef unsigned int uint; -typedef unsigned short ushort; -typedef unsigned char uchar; - -typedef int64_t int64; -typedef int32_t int32; -typedef int16_t int16; -typedef int8_t int8;
\ No newline at end of file diff --git a/include/drivers/plic.h b/include/drivers/plic.h deleted file mode 100644 index cdcd0ce..0000000 --- a/include/drivers/plic.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace PLIC -{ - // Global initialization (executed only once by hart 0) - void init(); - - // Per-core initialization (executed when each CPU starts) - void inithart(); - - // Claim interrupt: Get the currently occurring interrupt number (IRQ) - int claim(); - - // End of interrupt: notify the PLIC that this IRQ has been handled - void complete(int irq); -} // namespace PILC diff --git a/include/drivers/uart.h b/include/drivers/uart.h deleted file mode 100644 index a56624c..0000000 --- a/include/drivers/uart.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "drivers/uart_color.h" -#include "kernel/fdt.h" - -// UART register offsets (NS16550A) -#define RHR 0 // Receive Holding Register (read mode) -#define THR 0 // Transmit Holding Register (write mode) -#define IER 1 // Interrupt Enable Register -#define FCR 2 // FIFO Control Register -#define ISR 2 // Interrupt Status Register -#define LCR 3 // Line Control Register -#define LSR 5 // Line Status Register - -#define LSR_RX_READY 0x01 // input is waiting to be read from RHR -#define LSR_TX_IDLE 0x20 // THR can accept another character to send - -#define UART_RX_BUF_SIZE 128 - -// UART base address from FDT (use inline function to avoid macro issues) -static inline uint64 uart_base() { return g_devices.uart.base_addr; } - -namespace Drivers -{ - void uart_init(); - void uart_intr(); - int console_read(uint64 target, int len); - - void uart_putc(char c); - void uart_puts(const char *s); - void uart_put_int(int value); - void print_hex(uint64 value); -}
\ No newline at end of file diff --git a/include/drivers/uart_color.h b/include/drivers/uart_color.h deleted file mode 100644 index 7ecc28b..0000000 --- a/include/drivers/uart_color.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -// ANSI Escape Codes for Modern Terminal Colors -#define ANSI_RESET "\033[0m" -#define ANSI_RED "\033[31m" -#define ANSI_GREEN "\033[32m" -#define ANSI_YELLOW "\033[33m" -#define ANSI_BLUE "\033[34m" -#define ANSI_CLEAR "\033[2J\033[1;1H"
\ No newline at end of file diff --git a/include/drivers/virtio.h b/include/drivers/virtio.h deleted file mode 100644 index 9fda8cf..0000000 --- a/include/drivers/virtio.h +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -// VirtIO MMIO Base Address -#define VIRTIO0 0x10001000 - -// MMIO Registers (Modern / Version 2) -#define VIRTIO_MMIO_MAGIC_VALUE 0x000 -#define VIRTIO_MMIO_VERSION 0x004 -#define VIRTIO_MMIO_DEVICE_ID 0x008 -#define VIRTIO_MMIO_VENDOR_ID 0x00c -#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 -#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 -#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 -#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 -#define VIRTIO_MMIO_QUEUE_SEL 0x030 -#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 -#define VIRTIO_MMIO_QUEUE_NUM 0x038 -#define VIRTIO_MMIO_QUEUE_READY 0x044 -#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 -#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 -#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 -#define VIRTIO_MMIO_STATUS 0x070 - -// 64-bit Queue Address Registers (Modern) -#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 -#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 -#define VIRTIO_MMIO_QUEUE_DRIVER_LOW 0x090 -#define VIRTIO_MMIO_QUEUE_DRIVER_HIGH 0x094 -#define VIRTIO_MMIO_QUEUE_DEVICE_LOW 0x0a0 -#define VIRTIO_MMIO_QUEUE_DEVICE_HIGH 0x0a4 - -// Status Bits -#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 -#define VIRTIO_CONFIG_S_DRIVER 2 -#define VIRTIO_CONFIG_S_DRIVER_OK 4 -#define VIRTIO_CONFIG_S_FEATURES_OK 8 - -// Feature Bits -#define VIRTIO_BLK_F_RO 5 -#define VIRTIO_BLK_F_SCSI 7 -#define VIRTIO_BLK_F_CONFIG_WCE 11 -#define VIRTIO_BLK_F_MQ 12 -#define VIRTIO_F_ANY_LAYOUT 27 -#define VIRTIO_RING_F_INDIRECT_DESC 28 -#define VIRTIO_RING_F_EVENT_IDX 29 -#define VIRTIO_F_VERSION_1 32 - -// Descriptor Flags -#define VRING_DESC_F_NEXT 1 -#define VRING_DESC_F_WRITE 2 - -#define NUM_DESCS 8 - -#define VIRTIO_BLK_T_IN 0 -#define VIRTIO_BLK_T_OUT 1 - -// Device Configuration Layout (Offset 0x100) -#define VIRTIO_MMIO_CONFIG 0x100 - -namespace VirtIO -{ - struct VRingDesc - { - uint64 addr; - uint32 len; - uint16 flags; - uint16 next; - }; - - struct VRingAvail - { - uint16 flags; - uint16 idx; - uint16 ring[NUM_DESCS]; - }; - - struct VRingUsedElem - { - uint32 id; - uint32 len; - }; - - struct VRingUsed - { - uint16 flags; - uint16 idx; - struct VRingUsedElem ring[NUM_DESCS]; - }; - - struct __attribute__((packed)) BlockRequest - { - uint32 type; // 0=Read, 1=Write - uint32 reserved; - uint64 sector; - }; - - void init(); - void intr(); - void rw(int write, uint64 blockno, char *buf); - void test_rw(); - void test_bio(); -}
\ No newline at end of file diff --git a/include/fs/fat32.h b/include/fs/fat32.h deleted file mode 100644 index 63d62ae..0000000 --- a/include/fs/fat32.h +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "fs/fs.h" -#include "kernel/buf.h" - -// Disk Layout Structures (On-Disk) - -// FAT32 Boot Sector (BPB) -struct FAT32_BPB -{ - uint8 jmpBoot[3]; - char oemName[8]; - uint16 bytesPerSector; - uint8 secPerClus; - uint16 reservedSecCnt; - uint8 numFATs; - uint16 rootEntCnt; - uint16 totSec16; - uint8 media; - uint16 FATSz16; - uint16 secPerTrk; - uint16 numHeads; - uint32 hiddSec; - uint32 totSec32; - uint32 FATSz32; - uint16 extFlags; - uint16 FSVer; - uint32 rootClus; - uint16 FSInfo; - uint16 bkBootSec; - uint8 reserved[12]; - uint8 drvNum; - uint8 reserved1; - uint8 bootSig; - uint32 volID; - char volLab[11]; - char filSysType[8]; -} __attribute__((packed)); - -// FAT32 Directory Entry (32 bytes) -struct FAT32_DirEnt -{ - char name[11]; // 8.3 filename - uint8 attr; // File attributes - uint8 ntRes; - uint8 crtTimeTenth; - uint16 crtTime; - uint16 crtDate; - uint16 lstAccDate; - uint16 fstClusHI; // High 16 bits of first cluster - uint16 wrtTime; - uint16 wrtDate; - uint16 fstClusLO; // Low 16 bits of first cluster - uint32 fileSize; -} __attribute__((packed)); - -#define ATTR_READ_ONLY 0x01 -#define ATTR_HIDDEN 0x02 -#define ATTR_SYSTEM 0x04 -#define ATTR_VOLUME_ID 0x08 -#define ATTR_DIRECTORY 0x10 -#define ATTR_ARCHIVE 0x20 -#define ATTR_LONG_NAME 0x0F - -// In-Memory Classes - -class FAT32FileSystem; - -class FAT32Inode : public Inode -{ -public: - FAT32FileSystem *fs; - - // FAT32 Private Data - uint32 first_clus; // The starting cluster number of the file - uint32 parent_clus; // Starting cluster number of the parent directory - uint32 dir_offset; // The byte offset of this directory entry in the parent directory - uint32 ent_sec; // Sector number of the directory entry - uint32 ent_off; // Offset of the directory entry within the sector - - FAT32Inode() : fs(nullptr), first_clus(0) {} - // Implement VFS Interface - void update() override; - int read(char *dst, uint32 off, uint32 n, int isUser) override; - int write(const char *src, uint32 off, uint32 n, int isUser) override; - Inode *lookup(const char *name) override; - Inode *create(const char *name, short type, short major, short minor) override; - void truncate() override; - int unlink(const char *name) override; -}; - -class FAT32FileSystem : public FileSystem -{ -public: - uint32 fat_start_sector; - uint32 data_start_sector; - uint32 root_cluster; - uint32 sectors_per_cluster; - uint32 bytes_per_cluster; - uint32 fat_sz; // Size of the FAT table (number of sectors) - - void init() override; - Inode* root() override; - - // Helper functions - uint32 next_cluster(uint32 cluster); // Check the FAT table for the next cluster - uint32 alloc_cluster(); // Assign a new cluster - uint32 cluster_to_sector(uint32 cluster); - - // directory entry parsing - void read_dir_ent(struct FAT32_DirEnt *de, char *filename); - // Release the entire cluster chain starting from the cluster - void free_cluster_chain(uint32 cluster); -}; - -extern FAT32FileSystem fat32_fs; - -void fat32_test();
\ No newline at end of file diff --git a/include/fs/file.h b/include/fs/file.h deleted file mode 100644 index 9aff1af..0000000 --- a/include/fs/file.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "fs/fs.h" - -enum FileType -{ - FD_NONE, - FD_INODE, - FD_DEVICE, - FD_PIPE -}; - -class Pipe; - -struct file -{ - enum FileType type; - int ref; - char readable; - char writable; - - Pipe *pipe; // pipeline pointer - Inode *ip; // VFS Inode - uint32 off; // Current Offset -}; - -class Pipe -{ -public: - static constexpr uint32_t SIZE = 512; - - Pipe() : nread(0), nwrite(0), read_open(true), write_open(true) - { - lock.init("pipe"); - } - - // forbid copy - Pipe(const Pipe &) = delete; - Pipe &operator=(const Pipe &) = delete; - - ~Pipe() - { - // - } - - // creates a pair of connected file objects - static int create_pair(struct file **f0, struct file **f1); - - int read(uint64_t addr, int n); - int write(uint64_t addr, int n); - void close(bool is_writer); - -private: - Spinlock lock; - char data[SIZE]; - uint32_t nread; // read - uint32_t nwrite; // write - bool read_open; - bool write_open; -}; - -namespace FileTable -{ - void init(); - struct file *alloc(); // Allocate a free file handle - struct file *dup(struct file *f); // Duplicate handle (sys_dup) - void close(struct file *f); // Close handle - - int stat(struct file *f, uint64 addr); - int read(struct file *f, uint64 addr, int n); - int write(struct file *f, uint64 addr, int n); - int lseek(struct file *f, int offset, int whence); -} // namespace FileTable diff --git a/include/fs/fs.h b/include/fs/fs.h deleted file mode 100644 index b511a84..0000000 --- a/include/fs/fs.h +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "fs/stat.h" - -class FileSystem; - -// VFS Inode (Abstract Base Class) - -class Inode -{ -private: - ; -public: - // VFS Common Fields - uint32 dev; // Device number (VirtIO Block ID) - uint32 inum; // Inode number - int ref_cnt; // Memory reference count (number of struct file or processes referencing it) - int valid; // Whether the data is valid (whether metadata has been read from disk) - - Mutex lock; // Sleep lock: protects internal Inode data and atomicity of read/write operations - - // Metadata Cache (for speeding up getattr) - short type; // T_DIR, T_FILE, T_DEVICE - short major; // Major device number (only for T_DEVICE) - short minor; // Minor device number (only for T_DEVICE) - short nlink; // Number of hard links - uint32 size; // File size - - Inode() : ref_cnt(0), valid(0) - { - lock.init("inode"); - } - virtual ~Inode() {} - - /* Core Operation Interface (Subclass Must Implement) */ - // Update metadata - // Sync size, type, links, etc. from memory back to disk - virtual void update() = 0; - // Read content - // Read n bytes from the file at offset into dst - // Returns the actual number of bytes read, returns -1 on error - virtual int read(char *dst, uint32 off, uint32 n, int isUser) = 0; - // 3. Write content - // Write n bytes from src to the file at the given offset - // Need to handle file growth - virtual int write(const char *src, uint32 off, uint32 n, int isUser) = 0; - // Directory Lookup (only valid when type == T_DIR) - // Search for a file named 'name' in the current directory and return its Inode pointer (locked) - // Return nullptr if not found - virtual Inode *lookup(const char *name) = 0; - // Create file/directory (only effective when type == T_DIR) - // Create a new entry in the current directory - virtual Inode *create(const char *name, short type, short major, short minor) = 0; - // Get file status (populate the stat structure) - // Default implementation, subclasses can override to provide more detailed information - virtual void getattr(struct kstat *st); - // Truncate file (set size to 0, release all data blocks) - virtual void truncate() = 0; - // Delete the file named 'name' from the directory (Unlink) - virtual int unlink(const char *name) = 0; -}; - -// File System Abstraction (SuperBlock) -class FileSystem -{ -private: - ; -public: - virtual void init() = 0; - virtual Inode* root() = 0; - virtual ~FileSystem() {}; -}; - -// Inode Cache Interface (Global Function) -namespace VFS -{ - void init(); - - // Decrease the reference count, and if it reaches 0, free the memory - void iput(Inode *ip); - // Increase reference count - Inode *idup(Inode *ip); - // lock and load data - void ilock(Inode *ip); - // unlock - void iunlock(Inode *ip); - void iunlockput(Inode *ip); - // Mount the root filesystem - void mount_root(Inode *root); - // Get system root node - Inode *get_root(); - // Path Analysis - Inode *namei(const char *path); - // Find the parent directory inode of the path, and copy the last part of the path (the file name) into the name buffer - Inode *nameiparent(const char *path, char *name); - // Register a file system - void register_fs(FileSystem *fs); - -} // namespace VFS diff --git a/include/fs/stat.h b/include/fs/stat.h deleted file mode 100644 index e60ae42..0000000 --- a/include/fs/stat.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -#define T_DIR 1 // Directory -#define T_FILE 2 // File -#define T_DEVICE 3 // Device - -struct kstat -{ - int dev; // File System's disk device - uint32 ino; // Inode number - short type; //Type of file - short nlink; // Number of links to file - uint64 size; // Size of file in bytes - - uint32 mode; // Protection: rwx - uint32 uid; // User ID - uint32 gid; // Group ID - uint32 atime; // Access time - uint32 mtime; // Modified time - uint32 ctime; // Change time -}; diff --git a/include/kernel/buf.h b/include/kernel/buf.h deleted file mode 100644 index d0d849c..0000000 --- a/include/kernel/buf.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" - -#define BSIZE 1024 -#define NBUF 30 - -struct Buf -{ - int valid; // Whether the data has been read from the disk (1=valid, 0=invalid) - int disk; // Whether it has been held by the driver (dirty bit/write-back flag is usually extended here) - uint32 dev; // Device Number - uint32 blockno; // Block number - uint32 refcnt; // Reference Counting - struct Buf *prev; // LRU linked list - struct Buf *next; - uchar data[BSIZE]; - - Mutex lock; // Sleep Lock -}; - -namespace BufferCache -{ - void init(); - struct Buf *bread(uint32 dev, uint32 blockno); - void bwrite(struct Buf *b); - void brelse(struct Buf *b); - void bpin(struct Buf *b); - void bunpin(struct Buf *b); -} // namespace BufferCache diff --git a/include/kernel/config.h b/include/kernel/config.h deleted file mode 100644 index 49df2ff..0000000 --- a/include/kernel/config.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "kernel/fdt.h" - -// Legacy compat — these are still referenced in some places -extern uint64 g_uart_base; -extern uint64 g_dtb_addr;
\ No newline at end of file diff --git a/include/kernel/cpu.h b/include/kernel/cpu.h deleted file mode 100644 index 62bdc17..0000000 --- a/include/kernel/cpu.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/proc_def.h" - -struct RunQueue; - -struct cpu -{ - struct Proc *proc; - struct Context context; - int noff; - int intena; - - struct RunQueue *runqueue; -}; - - -extern struct cpu cpus[]; -struct cpu *mycpu();
\ No newline at end of file diff --git a/include/kernel/elf.h b/include/kernel/elf.h deleted file mode 100644 index 30193d2..0000000 --- a/include/kernel/elf.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -#define ELF_MAGIC 0x464C457F // "\x7FELF" in little endian - -// ELF File Header -struct elfhdr -{ - uint32 magic; // Must equal ELF_MAGIC - uint8 elf[12]; - uint16 type; - uint16 machine; - uint32 version; - uint64 entry; // Program Entry Point (Virtual Address) - uint64 phoff; // File offset of the Program Header Table - uint64 shoff; - uint32 flags; - uint16 ehsize; - uint16 phentsize; - uint16 phnum; // Number of Program Headers - uint16 shentsize; - uint16 shnum; - uint16 shstrndx; -}; - -// Program Header -// Describes how to load segments from a file into memory -struct proghdr -{ - uint32 type; - uint32 flags; - uint64 off; // Offset of the section in the file - uint64 vaddr; // Virtual address of the segment in memory - uint64 paddr; - uint64 filesz; // Size of the section in the file - uint64 memsz; // The size of the segment in memory (memsz >= filesz) - uint64 align; -}; - -// Values for Proghdr type -#define ELF_PROG_LOAD 1 - -// Flag bits for Proghdr flags -#define ELF_PROG_FLAG_EXEC 1 -#define ELF_PROG_FLAG_WRITE 2 -#define ELF_PROG_FLAG_READ 4
\ No newline at end of file diff --git a/include/kernel/exec.h b/include/kernel/exec.h deleted file mode 100644 index 304e03f..0000000 --- a/include/kernel/exec.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once - -namespace Exec -{ - int exec(char *path, char **argv); -}
\ No newline at end of file diff --git a/include/kernel/fdt.h b/include/kernel/fdt.h deleted file mode 100644 index 89cbae6..0000000 --- a/include/kernel/fdt.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * LumeOS - FDT Device Discovery - */ - -#pragma once -#include "common/types.h" - -// Single device descriptor (populated from FDT) -struct FdtDevice { - uint64 base_addr; // MMIO base address - uint64 size; // MMIO region size - uint32 irq; // Interrupt number (0 = none) -}; - -// Device registry — populated by fdt_parse(), consumed by drivers -struct DeviceRegistry { - // Memory - uint64 mem_base; - uint64 mem_size; - - // Core platform devices - FdtDevice uart; - FdtDevice plic; - FdtDevice clint; - - // VirtIO devices (multiple possible) - static constexpr int MAX_VIRTIO = 8; - FdtDevice virtio[MAX_VIRTIO]; - int virtio_count; - - // MMIO ranges for kvminit() to map - static constexpr int MAX_MMIO = 16; - struct MmioRegion { - uint64 base; - uint64 size; - } mmio_regions[MAX_MMIO]; - int mmio_count; -}; - -extern DeviceRegistry g_devices; - -// Parse FDT and populate g_devices -void fdt_parse(uint64 dtb); diff --git a/include/kernel/mm.h b/include/kernel/mm.h deleted file mode 100644 index e3de09a..0000000 --- a/include/kernel/mm.h +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace VM -{ - extern uint64 *kernel_pagetable; - - void kvminit(); - void kvminithart(); - - uint64 *uvmcreate(); - void uvminit(uint64 *pagetable, uchar *src, uint64 sz); - uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm); - uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz); - void uvmfree(uint64 *pagetable, uint64 sz); - int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz); - void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free); - - int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len); - int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len); - int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max); - - int handle_cow_fault(uint64 *pagetable, uint64 va); - int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm); - - uint64 walkaddr(uint64 *pagetable, uint64 va); -}
\ No newline at end of file diff --git a/include/kernel/pmm.h b/include/kernel/pmm.h deleted file mode 100644 index 261786d..0000000 --- a/include/kernel/pmm.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "kernel/fdt.h" // For g_devices - -// Physical Memory Layout Definition -#define KERNBASE 0x80000000 - -// Dynamic memory size from FDT -// Default to 128MB if FDT not present/parsed -extern uint64 g_phys_size; - -#define PHYSIZE (g_phys_size) -#define PHYSTOP (KERNBASE + PHYSIZE) - -// Buddy System Max Order -// Order 10 => 2^10 pages = 1024 * 4KB = 4MB contiguous block -// Order 11 => 2^11 pages = 8MB -#define MAX_ORDER 11 - -// Page flag -#define PG_used 0x01 // This page is occupied -#define PG_reserved 0x02 // This page is reserved (kernel code/data, DTB, MemMap) - -// Physical Page Metadata Structure -struct Page -{ - uint8 flags; // Page Status (PG_used, PG_reserved) - uint8 order; // If it is the first page of a free block, record the order of the current block - uint16 refcnt; // Reference Counting - struct Page *next; // Free list pointer (points to the next free block of the same size) - struct Page *prev; // Doubly linked list (easy to remove) -}; - -namespace PMM -{ - // Initialize physical memory management - void init(); - - // Core Allocation Interface (Buddy System) - // order: 0 ~ MAX_ORDER-1 - void *alloc_pages(int order); - void free_pages(void *pa, int order); - - // Compatible with old interface (allocate single page with order=0 and automatically reset to zero) - void *alloc_page(); - void free_page(void *pa); - - // Helper function - struct Page *pa_to_page(uint64 pa); - uint64 page_to_pa(struct Page *page); - - // Debug information - void print_free_lists(); -}
\ No newline at end of file diff --git a/include/kernel/proc.h b/include/kernel/proc.h deleted file mode 100644 index e11dc47..0000000 --- a/include/kernel/proc.h +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/proc_def.h" -#include "kernel/spinlock.h" - -namespace ProcManager -{ - void init(); - void scheduler(); - void yield(); - - void create_kernel_thread(void (*func)(), const char *name); - - void user_init(); - void exit(int status); - int wait(uint64 addr); - - void sleep(void *chan, Spinlock *lk); - void wakeup(void *chan); - - int growproc(int n); - - int kill(int pid); -} - -void forkret(); -struct Proc *myproc(); -void usertrapret(); -int fork();
\ No newline at end of file diff --git a/include/kernel/proc_def.h b/include/kernel/proc_def.h deleted file mode 100644 index 50f27d7..0000000 --- a/include/kernel/proc_def.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "common/fcntl.h" - -struct cpu; -struct Trapframe; -class Inode; -struct file; - -enum ProcState -{ - UNUSED, - USED, - SLEEPING, - RUNNABLE, - RUNNING, - ZOMBIE, -}; - -#define PRIO_MAX 0 -#define PRIO_MIN 3 -#define N_PRIO 4 - -// Context structure -struct Context -{ - uint64 ra; - uint64 sp; - uint64 s0; - uint64 s1; - uint64 s2; - uint64 s3; - uint64 s4; - uint64 s5; - uint64 s6; - uint64 s7; - uint64 s8; - uint64 s9; - uint64 s10; - uint64 s11; -}; - -struct Proc -{ - struct Context context; - struct Trapframe *tf; - uint64 *pagetable; - enum ProcState state; - int pid; - void *kstack; - char name[16]; - uint64 sz; - struct Proc *parent; - void *chan; - int xstate; - int killed; - - Inode *cwd; - struct file *ofile[NOFILE]; - - struct Proc *next; - struct Proc *prev; - - struct Proc *all_next; - struct Proc *all_prev; - - int priority; - int ticks; - struct cpu *cpu; -};
\ No newline at end of file diff --git a/include/kernel/riscv.h b/include/kernel/riscv.h deleted file mode 100644 index 0c586a9..0000000 --- a/include/kernel/riscv.h +++ /dev/null @@ -1,404 +0,0 @@ -#ifndef __ASSEMBLER__ - -// which hart (core) is this? -static inline uint64 -r_mhartid() -{ - uint64 x; - asm volatile("csrr %0, mhartid" : "=r" (x) ); - return x; -} - -// Machine Status Register, mstatus - -#define MSTATUS_MPP_MASK (3L << 11) // previous mode. -#define MSTATUS_MPP_M (3L << 11) -#define MSTATUS_MPP_S (1L << 11) -#define MSTATUS_MPP_U (0L << 11) - -static inline uint64 -r_mstatus() -{ - uint64 x; - asm volatile("csrr %0, mstatus" : "=r" (x) ); - return x; -} - -static inline void -w_mstatus(uint64 x) -{ - asm volatile("csrw mstatus, %0" : : "r" (x)); -} - -// machine exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void -w_mepc(uint64 x) -{ - asm volatile("csrw mepc, %0" : : "r" (x)); -} - -// Supervisor Status Register, sstatus - -#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User -#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable -#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable -#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable -#define SSTATUS_UIE (1L << 0) // User Interrupt Enable - -static inline uint64 -r_sstatus() -{ - uint64 x; - asm volatile("csrr %0, sstatus" : "=r" (x) ); - return x; -} - -static inline void -w_sstatus(uint64 x) -{ - asm volatile("csrw sstatus, %0" : : "r" (x)); -} - -// Supervisor Interrupt Pending -static inline uint64 -r_sip() -{ - uint64 x; - asm volatile("csrr %0, sip" : "=r" (x) ); - return x; -} - -static inline void -w_sip(uint64 x) -{ - asm volatile("csrw sip, %0" : : "r" (x)); -} - -// Supervisor Interrupt Enable -#define SIE_SEIE (1L << 9) // external -#define SIE_STIE (1L << 5) // timer -#define SIE_SSIE (1L << 1) // software -static inline uint64 -r_sie() -{ - uint64 x; - asm volatile("csrr %0, sie" : "=r" (x) ); - return x; -} - -static inline void -w_sie(uint64 x) -{ - asm volatile("csrw sie, %0" : : "r" (x)); -} - -// Machine-mode Interrupt Enable -#define MIE_STIE (1L << 5) // supervisor timer -static inline uint64 -r_mie() -{ - uint64 x; - asm volatile("csrr %0, mie" : "=r" (x) ); - return x; -} - -static inline void -w_mie(uint64 x) -{ - asm volatile("csrw mie, %0" : : "r" (x)); -} - -// supervisor exception program counter, holds the -// instruction address to which a return from -// exception will go. -static inline void -w_sepc(uint64 x) -{ - asm volatile("csrw sepc, %0" : : "r" (x)); -} - -static inline uint64 -r_sepc() -{ - uint64 x; - asm volatile("csrr %0, sepc" : "=r" (x) ); - return x; -} - -// Machine Exception Delegation -static inline uint64 -r_medeleg() -{ - uint64 x; - asm volatile("csrr %0, medeleg" : "=r" (x) ); - return x; -} - -static inline void -w_medeleg(uint64 x) -{ - asm volatile("csrw medeleg, %0" : : "r" (x)); -} - -// Machine Interrupt Delegation -static inline uint64 -r_mideleg() -{ - uint64 x; - asm volatile("csrr %0, mideleg" : "=r" (x) ); - return x; -} - -static inline void -w_mideleg(uint64 x) -{ - asm volatile("csrw mideleg, %0" : : "r" (x)); -} - -// Supervisor Trap-Vector Base Address -// low two bits are mode. -static inline void -w_stvec(uint64 x) -{ - asm volatile("csrw stvec, %0" : : "r" (x)); -} - -static inline uint64 -r_stvec() -{ - uint64 x; - asm volatile("csrr %0, stvec" : "=r" (x) ); - return x; -} - -// Supervisor Timer Comparison Register -static inline uint64 -r_stimecmp() -{ - uint64 x; - // asm volatile("csrr %0, stimecmp" : "=r" (x) ); - asm volatile("csrr %0, 0x14d" : "=r" (x) ); - return x; -} - -static inline void -w_stimecmp(uint64 x) -{ - // asm volatile("csrw stimecmp, %0" : : "r" (x)); - asm volatile("csrw 0x14d, %0" : : "r" (x)); -} - -// Machine Environment Configuration Register -static inline uint64 -r_menvcfg() -{ - uint64 x; - // asm volatile("csrr %0, menvcfg" : "=r" (x) ); - asm volatile("csrr %0, 0x30a" : "=r" (x) ); - return x; -} - -static inline void -w_menvcfg(uint64 x) -{ - //asm volatile("csrw menvcfg, %0" : : "r" (x)); - asm volatile("csrw 0x30a, %0" : : "r" (x)); -} - -// Physical Memory Protection -static inline void -w_pmpcfg0(uint64 x) -{ - asm volatile("csrw pmpcfg0, %0" : : "r" (x)); -} - -static inline void -w_pmpaddr0(uint64 x) -{ - asm volatile("csrw pmpaddr0, %0" : : "r" (x)); -} - -// use riscv's sv39 page table scheme. -#define SATP_SV39 (8L << 60) - -#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) - -// supervisor address translation and protection; -// holds the address of the page table. -static inline void -w_satp(uint64 x) -{ - asm volatile("csrw satp, %0" : : "r" (x)); -} - -static inline uint64 -r_satp() -{ - uint64 x; - asm volatile("csrr %0, satp" : "=r" (x) ); - return x; -} - -// Supervisor Trap Cause -static inline uint64 -r_scause() -{ - uint64 x; - asm volatile("csrr %0, scause" : "=r" (x) ); - return x; -} - -// Supervisor Trap Value -static inline uint64 -r_stval() -{ - uint64 x; - asm volatile("csrr %0, stval" : "=r" (x) ); - return x; -} - -// Machine-mode Counter-Enable -static inline void -w_mcounteren(uint64 x) -{ - asm volatile("csrw mcounteren, %0" : : "r" (x)); -} - -static inline uint64 -r_mcounteren() -{ - uint64 x; - asm volatile("csrr %0, mcounteren" : "=r" (x) ); - return x; -} - -// machine-mode cycle counter -static inline uint64 -r_time() -{ - uint64 x; - asm volatile("csrr %0, time" : "=r" (x) ); - return x; -} - -// enable device interrupts -static inline void -intr_on() -{ - w_sstatus(r_sstatus() | SSTATUS_SIE); -} - -// disable device interrupts -static inline void -intr_off() -{ - w_sstatus(r_sstatus() & ~SSTATUS_SIE); -} - -// are device interrupts enabled? -static inline int -intr_get() -{ - uint64 x = r_sstatus(); - return (x & SSTATUS_SIE) != 0; -} - -static inline uint64 -r_sp() -{ - uint64 x; - asm volatile("mv %0, sp" : "=r" (x) ); - return x; -} - -static inline uint64 -r_fp() -{ - uint64 x; - asm volatile("mv %0, s0" : "=r" (x) ); - return x; -} - -// read and write tp, the thread pointer, which xv6 uses to hold -// this core's hartid (core number), the index into cpus[]. -static inline uint64 -r_tp() -{ - uint64 x; - asm volatile("mv %0, tp" : "=r" (x) ); - return x; -} - -static inline void -w_tp(uint64 x) -{ - asm volatile("mv tp, %0" : : "r" (x)); -} - -static inline uint64 -r_ra() -{ - uint64 x; - asm volatile("mv %0, ra" : "=r" (x) ); - return x; -} - -// flush the TLB. -static inline void -sfence_vma() -{ - // the zero, zero means flush all TLB entries. - asm volatile("sfence.vma zero, zero"); -} - -typedef uint64 pte_t; -typedef uint64 *pagetable_t; // 512 PTEs - -#endif // __ASSEMBLER__ - -#define PGSIZE 4096 // bytes per page -#define PGSHIFT 12 // bits of offset within a page - -#ifdef LAB_PGTBL -#define SUPERPGSIZE (2 * (1 << 20)) // bytes per page -#define SUPERPGROUNDUP(sz) (((sz)+SUPERPGSIZE-1) & ~(SUPERPGSIZE-1)) -#define SUPERPGROUNDDOWN(sz) (SUPERPGROUNDUP(sz)-SUPERPGSIZE) -#endif - -#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1)) -#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) - -#define PTE_V (1L << 0) // valid -#define PTE_R (1L << 1) -#define PTE_W (1L << 2) -#define PTE_X (1L << 3) -#define PTE_U (1L << 4) // user can access - -// COW Flag (PTE bit 8) -#define PTE_COW (1L << 8) - -#if defined(LAB_MMAP) || defined(LAB_PGTBL) || defined(LAB_COW) -#define PTE_LEAF(pte) (((pte) & PTE_R) | ((pte) & PTE_W) | ((pte) & PTE_X)) -#endif - -// shift a physical address to the right place for a PTE. -#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) - -#define PTE2PA(pte) (((pte) >> 10) << 12) - -#define PTE_FLAGS(pte) ((pte) & 0x3FF) - -// extract the three 9-bit page table indices from a virtual address. -#define PXMASK 0x1FF // 9 bits -#define PXSHIFT(level) (PGSHIFT+(9*(level))) -#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) - -// one beyond the highest possible virtual address. -// MAXVA is actually one bit less than the max allowed by -// Sv39, to avoid having to sign-extend virtual addresses -// that have the high bit set. -#define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) -#define TRAMPOLINE (MAXVA - PGSIZE) -#define TRAPFRAME (TRAMPOLINE - PGSIZE)
\ No newline at end of file diff --git a/include/kernel/runqueue.h b/include/kernel/runqueue.h deleted file mode 100644 index 14704ce..0000000 --- a/include/kernel/runqueue.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "kernel/spinlock.h" -#include "kernel/proc_def.h" - -struct RunQueue -{ - Spinlock lock; - struct Proc *heads[N_PRIO]; - struct Proc *tails[N_PRIO]; - uint64 count; - - void init(const char *name); - void enqueue(struct Proc *p); - struct Proc *dequeue(); - struct Proc *steal(); -};
\ No newline at end of file diff --git a/include/kernel/sbi.h b/include/kernel/sbi.h deleted file mode 100644 index a6be5b3..0000000 --- a/include/kernel/sbi.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -// SBI Extension IDs -#define SBI_EID_SRST 0x53525354 - -// SBI Function IDs for SRST -#define SBI_FID_SRST_RESET 0 - -// Reset Types -#define SBI_SRST_TYPE_SHUTDOWN 0 -#define SBI_SRST_TYPE_COLD_REBOOT 1 -#define SBI_SRST_TYPE_WARM_REBOOT 2 - -// Reset Reasons -#define SBI_SRST_REASON_NONE 0 -#define SBI_SRST_REASON_SYSTEM_FAILURE 1 - -namespace SBI -{ - void set_timer(uint64 stime_value); - void sbi_shutdown(); - void sbi_reboot(); -} // namespace SBI diff --git a/include/kernel/slab.h b/include/kernel/slab.h deleted file mode 100644 index bb7b699..0000000 --- a/include/kernel/slab.h +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -namespace Slab -{ - void init(); - - void *kmalloc(uint64 size); - void kfree(void *ptr); -}
\ No newline at end of file diff --git a/include/kernel/spinlock.h b/include/kernel/spinlock.h deleted file mode 100644 index 1511199..0000000 --- a/include/kernel/spinlock.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -struct cpu; - -// Spinlock -// Features: Non-interruptible, busy-wait loop, extremely short hold time. -// Scenario: Protecting core data such as scheduler, interrupt handling, PMM, etc. -class Spinlock -{ -public: - volatile uint32 locked; // Whether it is locked (0: unlocked, 1: locked) - const char *name; // Lock Name (for debugging) - struct cpu *cpu; // The CPU holding the lock - - // Constructor - Spinlock() : locked(0), name("uninit"), cpu(nullptr) {} - - // Initialize lock name - void init(const char *name); - - // Acquire lock - void acquire(); - - // Release lock - void release(); - - // Check if the current CPU holds the lock - bool holding(); -}; - -// Mutex -// Features: Allows interruption, allows sleeping (yield CPU), can hold for a long time. -// Scenarios: File system IO, device driver waiting, long-term buffering operations. -class Mutex -{ -public: - uint32 locked; // Whether it is locked - const char *name; // Lock Name - int pid; // PID of the process holding the lock (for debugging) - - Spinlock lk; // Internal spin lock, protects the state of the Mutex itself - - void init(const char *name); - void acquire(); - void release(); - bool holding(); -};
\ No newline at end of file diff --git a/include/kernel/sync.h b/include/kernel/sync.h deleted file mode 100644 index 7f7c208..0000000 --- a/include/kernel/sync.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" -#include "kernel/spinlock.h" -#include "kernel/proc_def.h" - - -struct WaitQueue -{ - Spinlock lock; - struct Proc *head; - struct Proc *tail; - - void init(const char *name); - void sleep(); - void wakeup(); - void wakeup_all(); -}; - -struct Semaphore -{ - int value; - WaitQueue wq; - Spinlock lock; - - void init(int val, const char *name); - void wait(); - void post(); -}; - -struct CondVar -{ - WaitQueue wq; - - void init(const char *name); - void wait(Spinlock *mutex_lock); - void signal(); - void broadcast(); -};
\ No newline at end of file diff --git a/include/kernel/syscall.h b/include/kernel/syscall.h deleted file mode 100644 index 3594c08..0000000 --- a/include/kernel/syscall.h +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - * Lume OS - Linux Compatibility Layer - * - * Strictly aligned with Linux RISC-V 64 ABI (asm-generic) - * Reference: linux/usr/include/asm-generic/unistd.h - */ - -#pragma once - -// File System -#define SYS_getcwd 17 -#define SYS_dup 23 -#define SYS_dup3 24 -#define SYS_fcntl 25 -#define SYS_ioctl 29 -#define SYS_mknodat 33 -#define SYS_mkdirat 34 -#define SYS_unlinkat 35 -#define SYS_linkat 37 -#define SYS_umount2 39 -#define SYS_mount 40 -#define SYS_statfs 43 -#define SYS_ftruncate 46 -#define SYS_faccessat 48 -#define SYS_chdir 49 -#define SYS_fchmod 52 -#define SYS_fchmodat 53 -#define SYS_fchownat 54 -#define SYS_openat 56 -#define SYS_close 57 -#define SYS_vhangup 58 -#define SYS_pipe2 59 -#define SYS_getdents64 61 -#define SYS_lseek 62 -#define SYS_read 63 -#define SYS_write 64 -#define SYS_readv 65 -#define SYS_writev 66 -#define SYS_pread64 67 -#define SYS_pwrite64 68 -#define SYS_pselect6 72 -#define SYS_ppoll 73 -#define SYS_readlinkat 78 -#define SYS_fstatat 79 // newfstatat -#define SYS_fstat 80 - -// Process Management -#define SYS_exit 93 -#define SYS_exit_group 94 -#define SYS_set_tid_address 96 -#define SYS_nanosleep 101 -#define SYS_kill 129 -#define SYS_rt_sigaction 134 -#define SYS_times 153 -#define SYS_uname 160 -#define SYS_gettimeofday 169 -#define SYS_getpid 172 -#define SYS_getppid 173 -#define SYS_getuid 174 -#define SYS_geteuid 175 -#define SYS_getgid 176 -#define SYS_getegid 177 -#define SYS_sysinfo 179 -#define SYS_brk 214 -#define SYS_munmap 215 -#define SYS_mremap 216 -#define SYS_clone 220 -#define SYS_execve 221 -#define SYS_mmap 222 -#define SYS_wait4 260 -#define SYS_prlimit64 261 - -// System -#define SYS_reboot 142 // Used for shutdown - -// === Custom / Debug (Non-Standard) === -// Placed above 1000 to avoid conflicts with Linux ABI -#define SYS_putc 1000 -#define SYS_disk_test 1001 - -#ifndef __ASSEMBLER__ -void syscall(); -#endif
\ No newline at end of file diff --git a/include/kernel/timer.h b/include/kernel/timer.h deleted file mode 100644 index 8b97bbe..0000000 --- a/include/kernel/timer.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#include "common/types.h" - -namespace Timer{ - uint64 get_time(); - void set_next_trigger(); - void init(); - - void tick(); - uint64 get_ticks(); - Spinlock *get_lock(); - void *get_tick_chan(); -}
\ No newline at end of file diff --git a/include/kernel/trap.h b/include/kernel/trap.h deleted file mode 100644 index ddc1d12..0000000 --- a/include/kernel/trap.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - -struct Trapframe -{ - /* 0 */ uint64 kernel_satp; - /* 8 */ uint64 kernel_sp; - /* 16 */ uint64 kernel_trap; - /* 24 */ uint64 epc; - /* 32 */ uint64 kernel_hartid; - - /* 40 */ uint64 ra; // x1 - /* 48 */ uint64 sp; // x2 - /* 56 */ uint64 gp; // x3 - /* 64 */ uint64 tp; // x4 - /* 72 */ uint64 t0; // x5 - /* 80 */ uint64 t1; // x6 - /* 88 */ uint64 t2; // x7 - /* 96 */ uint64 s0; // x8 (Frame Pointer) - /* 104 */ uint64 s1; // x9 - /* 112 */ uint64 a0; // x10 - /* 120 */ uint64 a1; // x11 - /* 128 */ uint64 a2; // x12 - /* 136 */ uint64 a3; // x13 - /* 144 */ uint64 a4; // x14 - /* 152 */ uint64 a5; // x15 - /* 160 */ uint64 a6; // x16 - /* 168 */ uint64 a7; // x17 - /* 176 */ uint64 s2; // x18 - /* 184 */ uint64 s3; // x19 - /* 192 */ uint64 s4; // x20 - /* 200 */ uint64 s5; // x21 - /* 208 */ uint64 s6; // x22 - /* 216 */ uint64 s7; // x23 - /* 224 */ uint64 s8; // x24 - /* 232 */ uint64 s9; // x25 - /* 240 */ uint64 s10; // x26 - /* 248 */ uint64 s11; // x27 - /* 256 */ uint64 t3; // x28 - /* 264 */ uint64 t4; // x29 - /* 272 */ uint64 t5; // x30 - /* 280 */ uint64 t6; // x31 -}; - -namespace Trap -{ - void init(); - void inithart(); - int devintr(); -}
\ No newline at end of file diff --git a/include/lib/errno.h b/include/lib/errno.h deleted file mode 100644 index f0c1436..0000000 --- a/include/lib/errno.h +++ /dev/null @@ -1,168 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Lume OS - Linux Compatibility Layer - * - * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * The definitions are copied from the Linux kernel's UAPI headers - * (include/uapi/asm-generic/errno-base.h and include/uapi/asm-generic/errno.h) - * to ensure ABI compatibility. Original copyrights apply. - */ - -#ifndef _ERRNO_H -#define _ERRNO_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* 1-34: asm-generic/errno-base.h */ -#define EPERM 1 /* Operation not permitted */ -#define ENOENT 2 /* No such file or directory */ -#define ESRCH 3 /* No such process */ -#define EINTR 4 /* Interrupted system call */ -#define EIO 5 /* I/O error */ -#define ENXIO 6 /* No such device or address */ -#define E2BIG 7 /* Argument list too long */ -#define ENOEXEC 8 /* Exec format error */ -#define EBADF 9 /* Bad file number */ -#define ECHILD 10 /* No child processes */ -#define EAGAIN 11 /* Try again */ -#define ENOMEM 12 /* Out of memory */ -#define EACCES 13 /* Permission denied */ -#define EFAULT 14 /* Bad address */ -#define ENOTBLK 15 /* Block device required */ -#define EBUSY 16 /* Device or resource busy */ -#define EEXIST 17 /* File exists */ -#define EXDEV 18 /* Cross-device link */ -#define ENODEV 19 /* No such device */ -#define ENOTDIR 20 /* Not a directory */ -#define EISDIR 21 /* Is a directory */ -#define EINVAL 22 /* Invalid argument */ -#define ENFILE 23 /* File table overflow */ -#define EMFILE 24 /* Too many open files */ -#define ENOTTY 25 /* Not a typewriter */ -#define ETXTBSY 26 /* Text file busy */ -#define EFBIG 27 /* File too large */ -#define ENOSPC 28 /* No space left on device */ -#define ESPIPE 29 /* Illegal seek */ -#define EROFS 30 /* Read-only file system */ -#define EMLINK 31 /* Too many links */ -#define EPIPE 32 /* Broken pipe */ -#define EDOM 33 /* Math argument out of domain of func */ -#define ERANGE 34 /* Math result not representable */ - -/* * CRITICAL FIX: Missing definitions (35-37) from asm-generic - * Without these, your file system and locking code will fail compliance. - */ -#define EDEADLK 35 /* Resource deadlock would occur */ -#define ENAMETOOLONG 36 /* File name too long */ -#define ENOLCK 37 /* No record locks available */ - -#define ENOSYS 38 /* Invalid system call number */ -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ - -#define EDEADLOCK EDEADLK - -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Can not access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ -#define EOWNERDEAD 130 /* Owner died */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ -#define ERFKILL 132 /* Operation not possible due to RF-kill */ -#define EHWPOISON 133 /* Memory page has hardware error */ - -extern __thread int errno; - - -#ifdef __cplusplus -} -#endif - -#endif
\ No newline at end of file diff --git a/include/lib/string.h b/include/lib/string.h deleted file mode 100644 index 6a74942..0000000 --- a/include/lib/string.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#pragma once -#include "common/types.h" - - -void *memset(void *dst, int c, uint n); -int memcmp(const void *v1, const void *v2, uint n); -void *memmove(void *dst, const void *src, uint n); -void *memcpy(void *dst, const void *src, uint n); - - -void strcpy(char *dst, const char *src); -int strlen(const char *s); -int strcmp(const char *s1, const char *s2); -int strcasecmp(const char *s1, const char *s2);
\ No newline at end of file diff --git a/include/libc-shim/bits/alltypes.h b/include/libc-shim/bits/alltypes.h new file mode 100644 index 0000000..cf21a5e --- /dev/null +++ b/include/libc-shim/bits/alltypes.h @@ -0,0 +1,15 @@ +/* Shim: provide types musl string functions need */ +#ifndef _BITS_ALLTYPES_H +#define _BITS_ALLTYPES_H + +#ifndef __cplusplus +typedef unsigned long size_t; +#endif + +typedef long ssize_t; +typedef unsigned long uintptr_t; + +/* locale_t stub — unused in kernel, but declared by some headers */ +typedef void *locale_t; + +#endif diff --git a/include/libc-shim/endian.h b/include/libc-shim/endian.h new file mode 100644 index 0000000..8346386 --- /dev/null +++ b/include/libc-shim/endian.h @@ -0,0 +1,9 @@ +/* Shim: endianness for musl string functions */ +#ifndef _ENDIAN_H +#define _ENDIAN_H + +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN /* RISC-V is LE */ + +#endif diff --git a/include/libc-shim/features.h b/include/libc-shim/features.h new file mode 100644 index 0000000..a3466c8 --- /dev/null +++ b/include/libc-shim/features.h @@ -0,0 +1,12 @@ +/* Shim: musl expects this header */ +#ifndef _FEATURES_H +#define _FEATURES_H + +/* weak_alias: musl uses this for symbol aliasing (e.g. __memrchr -> memrchr) */ +#define weak_alias(old, new) \ + extern __typeof(old) new __attribute__((__weak__, __alias__(#old))) + +/* hidden attribute used by some musl sources */ +#define hidden __attribute__((__visibility__("hidden"))) + +#endif diff --git a/include/libc-shim/limits.h b/include/libc-shim/limits.h new file mode 100644 index 0000000..a1d546e --- /dev/null +++ b/include/libc-shim/limits.h @@ -0,0 +1,17 @@ +/* Shim: limits for musl string functions (memmem uses SIZE_MAX) */ +#ifndef _LIMITS_H +#define _LIMITS_H + +#define CHAR_BIT 8 +#define UCHAR_MAX 255 +#define SCHAR_MAX 127 +#define SCHAR_MIN (-128) +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) +#define UINT_MAX 4294967295U +#define LONG_MAX 9223372036854775807L +#define LONG_MIN (-LONG_MAX - 1L) +#define ULONG_MAX 18446744073709551615UL +#define SIZE_MAX ULONG_MAX + +#endif diff --git a/include/libc-shim/stdint.h b/include/libc-shim/stdint.h new file mode 100644 index 0000000..650bdcf --- /dev/null +++ b/include/libc-shim/stdint.h @@ -0,0 +1,21 @@ +/* Shim: stdint types for musl string functions */ +#ifndef _STDINT_H +#define _STDINT_H + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long uint64_t; +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef signed long int64_t; +typedef unsigned long uintptr_t; +typedef long intptr_t; + +#define UINT8_MAX 0xFF +#define UINT16_MAX 0xFFFF +#define UINT32_MAX 0xFFFFFFFFU +#define UINT64_MAX 0xFFFFFFFFFFFFFFFFUL + +#endif diff --git a/include/libc-shim/string.h b/include/libc-shim/string.h new file mode 100644 index 0000000..8a86304 --- /dev/null +++ b/include/libc-shim/string.h @@ -0,0 +1,110 @@ +#ifndef _STRING_H +#define _STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <features.h> + +#if __cplusplus >= 201103L +#define NULL nullptr +#elif defined(__cplusplus) +#define NULL 0L +#else +#define NULL ((void*)0) +#endif + +#define __NEED_size_t +#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ + || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +#define __NEED_locale_t +#endif + +#include <bits/alltypes.h> + +void *memcpy (void *__restrict, const void *__restrict, size_t); +void *memmove (void *, const void *, size_t); +void *memset (void *, int, size_t); +int memcmp (const void *, const void *, size_t); +void *memchr (const void *, int, size_t); + +char *strcpy (char *__restrict, const char *__restrict); +char *strncpy (char *__restrict, const char *__restrict, size_t); + +char *strcat (char *__restrict, const char *__restrict); +char *strncat (char *__restrict, const char *__restrict, size_t); + +int strcmp (const char *, const char *); +int strncmp (const char *, const char *, size_t); + +int strcoll (const char *, const char *); +size_t strxfrm (char *__restrict, const char *__restrict, size_t); + +char *strchr (const char *, int); +char *strrchr (const char *, int); + +size_t strcspn (const char *, const char *); +size_t strspn (const char *, const char *); +char *strpbrk (const char *, const char *); +char *strstr (const char *, const char *); +char *strtok (char *__restrict, const char *__restrict); + +size_t strlen (const char *); + +char *strerror (int); + +#if defined(_BSD_SOURCE) || defined(_GNU_SOURCE) +#include <strings.h> +#endif + +#if defined(_POSIX_SOURCE) || defined(_POSIX_C_SOURCE) \ + || defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +char *strtok_r (char *__restrict, const char *__restrict, char **__restrict); +int strerror_r (int, char *, size_t); +char *stpcpy(char *__restrict, const char *__restrict); +char *stpncpy(char *__restrict, const char *__restrict, size_t); +size_t strnlen (const char *, size_t); +char *strdup (const char *); +char *strndup (const char *, size_t); +char *strsignal(int); +char *strerror_l (int, locale_t); +int strcoll_l (const char *, const char *, locale_t); +size_t strxfrm_l (char *__restrict, const char *__restrict, size_t, locale_t); +void *memmem(const void *, size_t, const void *, size_t); +#endif + +#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) \ + || defined(_BSD_SOURCE) +void *memccpy (void *__restrict, const void *__restrict, int, size_t); +#endif + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +char *strsep(char **, const char *); +size_t strlcat (char *, const char *, size_t); +size_t strlcpy (char *, const char *, size_t); +void explicit_bzero (void *, size_t); +#endif + +#ifdef _GNU_SOURCE +#define strdupa(x) strcpy(alloca(strlen(x)+1),x) +int strverscmp (const char *, const char *); +char *strchrnul(const char *, int); +char *strcasestr(const char *, const char *); +void *memrchr(const void *, int, size_t); +void *mempcpy(void *, const void *, size_t); +#endif + +/* musl internal symbols used across translation units */ +char *__strchrnul(const char *, int); +void *__memrchr(const void *, int, size_t); +char *__stpcpy(char *__restrict, const char *__restrict); +char *__stpncpy(char *__restrict, const char *__restrict, size_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/libc-shim/strings.h b/include/libc-shim/strings.h new file mode 100644 index 0000000..b7a5ea0 --- /dev/null +++ b/include/libc-shim/strings.h @@ -0,0 +1,40 @@ +#ifndef _STRINGS_H +#define _STRINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <features.h> + +#define __NEED_size_t +#define __NEED_locale_t +#include <bits/alltypes.h> + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) || defined(_POSIX_SOURCE) \ + || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE+0 < 200809L) \ + || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE+0 < 700) +int bcmp (const void *, const void *, size_t); +void bcopy (const void *, void *, size_t); +void bzero (void *, size_t); +char *index (const char *, int); +char *rindex (const char *, int); +#endif + +#if defined(_XOPEN_SOURCE) || defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +int ffs (int); +int ffsl (long); +int ffsll (long long); +#endif + +int strcasecmp (const char *, const char *); +int strncasecmp (const char *, const char *, size_t); + +int strcasecmp_l (const char *, const char *, locale_t); +int strncasecmp_l (const char *, const char *, size_t, locale_t); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/lume/addr.h b/include/lume/addr.h new file mode 100644 index 0000000..572b6fe --- /dev/null +++ b/include/lume/addr.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Machine-Independent Address Conversion Helpers + * + * All PA↔VA conversions in MI code (mm/, kernel/, etc.) must go through + * these functions. The actual offset is provided by <arch/config.h>. + * + * Reference: docs/specs/00_conventions.md §1.1 (MI/MD separation) + */ + +#include <lume/types.h> +#include <lume/config.h> +#include <lume/addr_types.h> +#include <arch/config.h> + +/* Convert physical address to kernel virtual address (direct map). */ +inline VirtAddr pa_to_va(PhysAddr pa) +{ + return VirtAddr{pa.raw + arch::g_direct_map_base}; +} + +/* Convert kernel virtual address to physical address (direct map). */ +inline PhysAddr va_to_pa(VirtAddr va) +{ + return PhysAddr{va.raw - arch::g_direct_map_base}; +} + +/* + * Early-boot conversions pinned to the bootstrap direct-map base. + * + * Use these only before vmm_init() activates the final kernel page table. + * They are required when KASLR has already randomized g_direct_map_base, + * but the CPU is still running on the bootstrap mapping built in entry.S. + */ +inline VirtAddr boot_pa_to_va(PhysAddr pa) +{ + return VirtAddr{pa.raw + arch::kDirectMapBaseDefault}; +} + +inline PhysAddr boot_va_to_pa(VirtAddr va) +{ + return PhysAddr{va.raw - arch::kDirectMapBaseDefault}; +} + +/* Align up to page boundary */ +inline uint64 page_align_up(uint64 addr) +{ + return (addr + kPageSize - 1) & ~(kPageSize - 1); +} + +/* Align down to page boundary */ +inline uint64 page_align_down(uint64 addr) +{ + return addr & ~(kPageSize - 1); +} + +inline PhysAddr page_align_up(PhysAddr addr) +{ + return PhysAddr{page_align_up(addr.raw)}; +} + +inline PhysAddr page_align_down(PhysAddr addr) +{ + return PhysAddr{page_align_down(addr.raw)}; +} + +inline VirtAddr page_align_up(VirtAddr addr) +{ + return VirtAddr{page_align_up(addr.raw)}; +} + +inline VirtAddr page_align_down(VirtAddr addr) +{ + return VirtAddr{page_align_down(addr.raw)}; +} + +/* Convert physical address to page frame number (PFN) */ +inline Pfn pa_to_pfn(PhysAddr pa) +{ + return Pfn{pa.raw / kPageSize}; +} + +/* Convert page frame number (PFN) to physical address */ +inline PhysAddr pfn_to_pa(Pfn pfn) +{ + return PhysAddr{pfn.raw * kPageSize}; +} diff --git a/include/lume/addr_types.h b/include/lume/addr_types.h new file mode 100644 index 0000000..91a0779 --- /dev/null +++ b/include/lume/addr_types.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include <lume/types.h> + +struct PhysAddr { + uint64 raw; +}; + +struct VirtAddr { + uint64 raw; +}; + +struct Pfn { + uint64 raw; +}; + +inline constexpr PhysAddr phys_addr(uint64 raw) { return PhysAddr{raw}; } +inline constexpr VirtAddr virt_addr(uint64 raw) { return VirtAddr{raw}; } +inline constexpr Pfn pfn(uint64 raw) { return Pfn{raw}; } + +inline constexpr bool operator==(PhysAddr a, PhysAddr b) { return a.raw == b.raw; } +inline constexpr bool operator!=(PhysAddr a, PhysAddr b) { return a.raw != b.raw; } +inline constexpr bool operator==(VirtAddr a, VirtAddr b) { return a.raw == b.raw; } +inline constexpr bool operator!=(VirtAddr a, VirtAddr b) { return a.raw != b.raw; } +inline constexpr bool operator==(Pfn a, Pfn b) { return a.raw == b.raw; } +inline constexpr bool operator!=(Pfn a, Pfn b) { return a.raw != b.raw; } diff --git a/include/lume/args.h b/include/lume/args.h new file mode 100644 index 0000000..0562dc5 --- /dev/null +++ b/include/lume/args.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_ARGS_H +#define _LINUX_ARGS_H + +/* + * How do these macros work? + * + * In __COUNT_ARGS() _0 to _15 are just placeholders from the start + * in order to make sure _n is positioned over the correct number + * from 15 to 0 (depending on X, which is a variadic argument list). + * They serve no purpose other than occupying a position. Since each + * macro parameter must have a distinct identifier, those identifiers + * are as good as any. + * + * In COUNT_ARGS() we use actual integers, so __COUNT_ARGS() returns + * that as _n. + */ + +/* This counts to 15. Any more, it will return 16th argument. */ +#define __COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _n, X...) _n +#define COUNT_ARGS(X...) __COUNT_ARGS(, ##X, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) + +/* Concatenate two parameters, but allow them to be expanded beforehand. */ +#define __CONCAT(a, b) a ## b +#define CONCATENATE(a, b) __CONCAT(a, b) + +#endif /* _LINUX_ARGS_H */ diff --git a/include/lume/atomic.h b/include/lume/atomic.h new file mode 100644 index 0000000..6e31129 --- /dev/null +++ b/include/lume/atomic.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * lume::atomic<T> — Thin wrapper over __atomic builtins. + * + * All multi-core shared data that does NOT use a spinlock MUST use + * this wrapper instead of raw __atomic_* builtins. + * + * Reference: docs/specs/00_conventions.md §5.3 + */ + +#include <lume/types.h> + +namespace lume { + +template<typename T> +class atomic { +public: + atomic() = default; + explicit atomic(T val) : val_(val) {} + + /* Prevent copies — an atomic value has identity */ + atomic(const atomic&) = delete; + atomic& operator=(const atomic&) = delete; + + /* Load with specified memory ordering */ + T load(int order = __ATOMIC_SEQ_CST) const { + return __atomic_load_n(&val_, order); + } + + /* Store with specified memory ordering */ + void store(T desired, int order = __ATOMIC_SEQ_CST) { + __atomic_store_n(&val_, desired, order); + } + + /* Atomic exchange, returns old value */ + T exchange(T desired, int order = __ATOMIC_SEQ_CST) { + return __atomic_exchange_n(&val_, desired, order); + } + + /* Fetch-and-add, returns old value */ + T fetch_add(T delta, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_add(&val_, delta, order); + } + + /* Fetch-and-subtract, returns old value */ + T fetch_sub(T delta, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_sub(&val_, delta, order); + } + + /* Fetch-and-or, returns old value */ + T fetch_or(T mask, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_or(&val_, mask, order); + } + + /* Fetch-and-and, returns old value */ + T fetch_and(T mask, int order = __ATOMIC_ACQ_REL) { + return __atomic_fetch_and(&val_, mask, order); + } + + /* Compare-and-swap. Returns true on success. */ + bool compare_exchange(T& expected, T desired, + int success_order = __ATOMIC_ACQ_REL, + int failure_order = __ATOMIC_ACQUIRE) { + return __atomic_compare_exchange_n(&val_, &expected, desired, + false, success_order, failure_order); + } + +private: + T val_ = 0; +}; + +} // namespace lume diff --git a/include/lume/bitops.h b/include/lume/bitops.h new file mode 100644 index 0000000..4227ca3 --- /dev/null +++ b/include/lume/bitops.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Bit Operations — portable kernel bit manipulation utilities + * + * Provides ctz, clz, ffs, fls and related operations. + * All implementations are manual (no __builtin_ctz) to avoid + * generating libgcc __ctzdi2/__clzdi2 calls in freestanding. + */ + +#include <lume/types.h> + +namespace lume { + +/* ------------------------------------------------------------------ */ +/* Count Trailing Zeros (ctz) */ +/* Returns the number of trailing 0-bits in val. */ +/* Precondition: val != 0 (undefined if val == 0). */ +/* ------------------------------------------------------------------ */ + +static inline int ctz32(uint32 val) +{ + int n = 0; + if (!(val & 0x0000FFFFU)) { n += 16; val >>= 16; } + if (!(val & 0x000000FFU)) { n += 8; val >>= 8; } + if (!(val & 0x0000000FU)) { n += 4; val >>= 4; } + if (!(val & 0x00000003U)) { n += 2; val >>= 2; } + if (!(val & 0x00000001U)) { n += 1; } + return n; +} + +static inline int ctz64(uint64 val) +{ + if (static_cast<uint32>(val) != 0) { + return ctz32(static_cast<uint32>(val)); + } + return 32 + ctz32(static_cast<uint32>(val >> 32)); +} + +/* ------------------------------------------------------------------ */ +/* Count Leading Zeros (clz) */ +/* Returns the number of leading 0-bits in val. */ +/* Precondition: val != 0 (undefined if val == 0). */ +/* ------------------------------------------------------------------ */ + +static inline int clz32(uint32 val) +{ + int n = 0; + if (!(val & 0xFFFF0000U)) { n += 16; val <<= 16; } + if (!(val & 0xFF000000U)) { n += 8; val <<= 8; } + if (!(val & 0xF0000000U)) { n += 4; val <<= 4; } + if (!(val & 0xC0000000U)) { n += 2; val <<= 2; } + if (!(val & 0x80000000U)) { n += 1; } + return n; +} + +static inline int clz64(uint64 val) +{ + if (static_cast<uint32>(val >> 32) != 0) { + return clz32(static_cast<uint32>(val >> 32)); + } + return 32 + clz32(static_cast<uint32>(val)); +} + +/* ------------------------------------------------------------------ */ +/* Find First Set (ffs) — 1-indexed, returns 0 if val == 0 */ +/* ------------------------------------------------------------------ */ + +static inline int ffs32(uint32 val) +{ + return val ? ctz32(val) + 1 : 0; +} + +static inline int ffs64(uint64 val) +{ + return val ? ctz64(val) + 1 : 0; +} + +/* ------------------------------------------------------------------ */ +/* Find Last Set (fls) — 1-indexed, returns 0 if val == 0 */ +/* Returns the position of the highest set bit. */ +/* ------------------------------------------------------------------ */ + +static inline int fls32(uint32 val) +{ + return val ? 32 - clz32(val) : 0; +} + +static inline int fls64(uint64 val) +{ + return val ? 64 - clz64(val) : 0; +} + +/* ------------------------------------------------------------------ */ +/* Order (log2) — returns floor(log2(val)), val must be > 0 */ +/* ------------------------------------------------------------------ */ + +static inline int ilog2_32(uint32 val) +{ + return fls32(val) - 1; +} + +static inline int ilog2_64(uint64 val) +{ + return fls64(val) - 1; +} + +/* ------------------------------------------------------------------ */ +/* Power-of-two test */ +/* ------------------------------------------------------------------ */ + +static inline bool is_power_of_2(uint64 val) +{ + return val != 0 && (val & (val - 1)) == 0; +} + +} /* namespace lume */ diff --git a/include/lume/chardev.h b/include/lume/chardev.h new file mode 100644 index 0000000..be81ab9 --- /dev/null +++ b/include/lume/chardev.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * chardev.h — Character device abstraction + * + * Minimal Linux-style cdev registry: each character device has a + * major/minor number and a file_operations table. + */ + +#include <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/include/lume/clone.h b/include/lume/clone.h new file mode 100644 index 0000000..156bae4 --- /dev/null +++ b/include/lume/clone.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Linux-compatible clone(2) flags. + * + * Only the flags relevant to current and near-future use are defined. + * Reference: include/uapi/linux/sched.h + */ + +#include <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/include/lume/compiler.h b/include/lume/compiler.h new file mode 100644 index 0000000..243f0de --- /dev/null +++ b/include/lume/compiler.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_COMPILER_H +#define _LUME_COMPILER_H + +/* + * Minimal compiler primitives used across LumeOS. + * + * Provides only what current code actually references: + * - READ_ONCE / WRITE_ONCE memory access opacity for concurrent code + * (rbtree.h, rbtree_augmented.h) + * - likely / unlikely branch hints for hot/cold paths + * + * Implementation mirrors Linux's semantics — same surface, same effect — + * without pulling in Linux's compiler_types.h cascade (KASAN/KCSAN/KMSAN + * sanitizer attribute machinery, BTF type tags, hardened-usercopy hooks). + * LumeOS doesn't build with those sanitizers, so the simpler form is + * sufficient and keeps the dependency graph flat. + */ + +/* Branch prediction hints */ +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +/* Compile-time barrier — prevent the compiler from reordering memory + * accesses across this point. Not a CPU fence; for CPU ordering use + * the architecture-specific fence intrinsics. */ +#define barrier() __asm__ __volatile__("" ::: "memory") + +/* + * __always_inline — force the compiler to actually inline a function, + * overriding -fno-inline / -Os / size heuristics. Used by rbtree helper + * templates whose performance depends on the call being inlined into + * the iterating loop. + */ +#define __always_inline inline __attribute__((__always_inline__)) + +/* + * READ_ONCE / WRITE_ONCE — opaque single-access memory operations. + * + * The volatile cast prevents the compiler from: + * - merging adjacent reads/writes to the same location, + * - splitting a load into multiple narrower loads, + * - hoisting reads out of a loop (or sinking writes past one). + * + * Used in lock-free / cross-CPU shared data paths (rbtree color writes, + * future RCU pointers, atomic flag polls). Does NOT imply any CPU-side + * memory ordering — pair with smp_mb()/acquire/release for that. + */ +#define READ_ONCE(x) (*(const volatile __typeof__(x) *)&(x)) +#define WRITE_ONCE(x, val) (*(volatile __typeof__(x) *)&(x) = (val)) + +#endif /* _LUME_COMPILER_H */ diff --git a/include/lume/config.h b/include/lume/config.h new file mode 100644 index 0000000..bde717a --- /dev/null +++ b/include/lume/config.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * LumeOS Global Compile-Time Configuration + * + * All kernel-wide tuning constants live here. + * Architecture-specific constants live in <arch/config.h>. + * + * This header is safe to include from both C++ and assembly files. + * Assembly-visible constants use #define; C++ code gets typed constexpr. + * + * Values are injected by the build system via -DCONFIG_xxx flags + * (see scripts/Kconfig.mk). The #ifndef guards below provide + * safe defaults so the header is self-contained for IDE analysis. + * + * Reference: docs/specs/00_conventions.md §1.3 + */ + +/* ================================================================ + * Defaults — only used when CONFIG_xxx is NOT supplied by -D flag. + * In a normal build, scripts/Kconfig.mk always provides them. + * ================================================================ */ + +#ifndef CONFIG_NR_CPUS +#define CONFIG_NR_CPUS 8 +#endif + +#ifndef CONFIG_PAGE_SIZE +#define CONFIG_PAGE_SIZE 4096 +#endif + +#ifndef CONFIG_MAX_ORDER +#define CONFIG_MAX_ORDER 11 +#endif + +#ifndef CONFIG_KERNEL_STACK_SIZE +#define CONFIG_KERNEL_STACK_SIZE 16384 +#endif + +#ifndef CONFIG_HZ +#define CONFIG_HZ 100 +#endif + +#ifndef CONFIG_NR_PRIORITIES +#define CONFIG_NR_PRIORITIES 32 +#endif + +#ifndef CONFIG_DEFAULT_PRIORITY +#define CONFIG_DEFAULT_PRIORITY (CONFIG_NR_PRIORITIES / 2) +#endif + +#ifndef CONFIG_IDLE_PRIORITY +#define CONFIG_IDLE_PRIORITY (CONFIG_NR_PRIORITIES - 1) +#endif + +#ifndef CONFIG_DEFAULT_TIMESLICE +#define CONFIG_DEFAULT_TIMESLICE (CONFIG_HZ / 10) +#endif + +#ifndef CONFIG_KASLR_POLICY_ID +#define CONFIG_KASLR_POLICY_ID 0 /* Default: basic KASLR policy */ +#endif + +#ifndef CONFIG_KASLR_LOG_LEVEL +#define CONFIG_KASLR_LOG_LEVEL 1 /* 0=off, 1=minimal, 2=debug */ +#endif + +/* ================================================================ + * Legacy compatibility aliases (shared with assembly) + * ================================================================ */ +#define LUME_MAX_CPUS CONFIG_NR_CPUS +#define LUME_PAGE_SIZE CONFIG_PAGE_SIZE + +#ifndef __ASSEMBLER__ + +#include <lume/types.h> + +/* Maximum number of CPUs supported by the kernel. + * Compile-time limit for static arrays (boot stacks, per-cpu data). + * Actual CPU count is detected at runtime via FDT. */ +inline constexpr int kMaxCpus = CONFIG_NR_CPUS; + +/* Virtual memory page size (4KB, matching SV39 base page). */ +inline constexpr uint64 kPageSize = CONFIG_PAGE_SIZE; + +/* Page shift: log2(kPageSize). Used for PFN ↔ PA conversions. */ +inline constexpr int kPageShift = 12; // 4096 = 1 << 12 + +/* KASLR policy ID and logging level (build-time configurable) */ +inline constexpr uint32 kKaslrPolicyId = CONFIG_KASLR_POLICY_ID; +inline constexpr int kKaslrLogLevel = CONFIG_KASLR_LOG_LEVEL; + +#include <arch/config.h> + +using arch::kPhysStart; + +#endif /* __ASSEMBLER__ */
\ No newline at end of file diff --git a/include/lume/console.h b/include/lume/console.h new file mode 100644 index 0000000..af187a6 --- /dev/null +++ b/include/lume/console.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * console.h — Kernel console abstraction + * + * Two-phase design following Linux: + * + * Phase 1 (early boot): + * early_putc() → direct UART MMIO write. Used before MMU and + * before the console registry is ready. kprintf() and panic + * use this path. + * + * Phase 2 (runtime): + * struct console registered via register_console(). printk/klog + * emit through the console driver chain. Multiple consoles can + * be registered (e.g., UART + framebuffer). + * + * Reference: Linux include/linux/console.h + */ + +#include <lume/types.h> +#include <lume/list.h> + +/* ================================================================== */ +/* Early console (Phase 1 — direct UART access) */ +/* ================================================================== */ + +extern "C" void early_putc(char c); +extern "C" void early_puts(const char* s); +extern "C" void console_set_mmu_enabled(); + +/* Early UART discovery via raw FDT parsing. */ +void console_early_init(uint64 fdt_paddr); + +/* Full console initialization. Maps MMIO. */ +void console_init(); + +/* + * Notify console that the runtime kernel page table is active. + * After this point early_putc() must use runtime direct-map base + * instead of the bootstrap fixed base. + */ +void console_use_runtime_mapping(); + +/* ================================================================== */ +/* struct console (Phase 2 — registered console drivers) */ +/* ================================================================== */ + +inline constexpr int CON_PRINTBUFFER = (1 << 0); /* Replay klog on register */ +inline constexpr int CON_ENABLED = (1 << 1); +inline constexpr int CON_BOOT = (1 << 2); /* Early boot console */ + +struct console { + const char* name; + void (*write)(console* con, const char* s, unsigned int count); + int (*setup)(console* con, char* options); + int flags; + int index; + void* data; /* driver private data */ + list_node list; +}; + +/* Register / unregister a console driver */ +void register_console(console* con); +void unregister_console(console* con); + +/* + * Write to all registered consoles. + * Falls back to early_putc() if no console is registered. + */ +void console_write_all(const char* s, int count); + +/* Check if any console is registered */ +bool console_drivers_registered(); diff --git a/include/lume/container_of.h b/include/lume/container_of.h new file mode 100644 index 0000000..a6889eb --- /dev/null +++ b/include/lume/container_of.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * container_of.h — Recover containing struct from member pointer + * + * Equivalent to Linux include/linux/container_of.h. + */ + +#include <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/include/lume/dcache.h b/include/lume/dcache.h new file mode 100644 index 0000000..a31402e --- /dev/null +++ b/include/lume/dcache.h @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * dcache.h — Directory entry cache + * + * Modeled after Linux include/linux/dcache.h. + * Reference: docs/vfs/03_dentry.md + */ + +#include <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/include/lume/device.h b/include/lume/device.h new file mode 100644 index 0000000..46e22e4 --- /dev/null +++ b/include/lume/device.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Driver model: device, device_driver, bus_type. + * + * Mirrors Linux include/linux/device.h. Every hardware unit embeds a + * `device` and binds to a `device_driver` via a `bus_type` that knows + * how to match the two. Buses we will have: + * - platform: FDT-described memory-mapped devices (this commit) + * - virtio: virtio-mmio devices (P5 of storage_stack.md) + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include <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/include/lume/device_node.h b/include/lume/device_node.h new file mode 100644 index 0000000..e35f22d --- /dev/null +++ b/include/lume/device_node.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * DeviceNode — FDT object tree node + * + * Each DeviceNode represents a single node in the unflattened device tree. + * Nodes form a parent-children hierarchy via intrusive linked lists. + * After unflattening, the tree is globally read-only and requires no locks. + * + * Created when Slab is available. + * + * Reference: docs/specs/fdt.md §3.2 + */ + +#include <lume/types.h> +#include <lume/list.h> +#include <lume/fdt_prop.h> + +class DeviceNode { +public: + // Construction / destruction (used by FdtManager::unflatten) + DeviceNode(DeviceNode *parent, const char *name); + ~DeviceNode(); + + // --- Property access --- + + // Look up a property by name. Returns nullptr if not found. + const FdtProperty *get_prop(const char *name) const; + + // Convenience helpers that decode the property value. + // Return default_val when the property does not exist. + uint32 get_prop_u32(const char *name, uint32 default_val = 0) const; + uint64 get_prop_u64(const char *name, uint64 default_val = 0) const; + const char *get_prop_string(const char *name) const; + + // --- Child traversal --- + + // Find a direct child by name. Returns nullptr if not found. + DeviceNode *find_child(const char *name); + + // Invoke callback for every direct child. + void for_each_child(void (*callback)(DeviceNode *)); + + // --- Internal (used by FdtManager to build the tree) --- + void add_child(DeviceNode *child); + void add_prop(FdtProperty *prop); + + // --- Accessors for tree traversal --- + const char *get_name() const { return name_; } + list_node *children_list_head() { return children_list_.next; } + list_node *children_list_sentinel() { return &children_list_; } + + // sibling_link_ offset needed by container_of in fdt.cc + list_node sibling_link_; // Link in parent's children_list_ + +private: + const char *name_; // Node name (e.g. "memory@80000000") + DeviceNode *parent_; // Parent node (nullptr for root) + list_node children_list_; // Head of the children linked list + list_node props_list_; // Head of the property linked list +}; diff --git a/include/lume/driver.h b/include/lume/driver.h new file mode 100644 index 0000000..a8bd787 --- /dev/null +++ b/include/lume/driver.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * driver.h — boot-time driver probing entry point. + * + * The actual driver-model primitives (kobject, device, bus_type, + * platform_device, platform_driver) live in their own headers: + * <lume/kobject.h>, <lume/device.h>, <lume/platform_device.h>. + * + * This header just exposes the single boot entry point that + * kernel_main() calls after the slab allocator + FDT unflatten are + * ready. The implementation lives in drivers/base/platform.cc. + * + * Reference: docs/specs/storage_stack.md §4 + */ + +/* Register the platform bus, register every linker-set platform_driver, + * walk the FDT, and instantiate a platform_device for each compatible + * match. Each device is bound to its driver via the platform bus + * match/probe path. Returns after all probes have run. */ +void driver_probe_all(); diff --git a/include/lume/elf.h b/include/lume/elf.h new file mode 100644 index 0000000..772ad97 --- /dev/null +++ b/include/lume/elf.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * ELF64 Type Definitions — Minimal set for loading executables + * AND for the kernel's own .rela.dyn relocation walk at boot. + * + * Follows the ELF specification (Tool Interface Standard, TIS). + * Only includes what's needed for static ELF64 executable loading. + * + * Reference: https://refspecs.linuxfoundation.org/elf/elf.pdf + */ + +#include <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/include/lume/errno.h b/include/lume/errno.h new file mode 100644 index 0000000..f3bf09f --- /dev/null +++ b/include/lume/errno.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_ERRNO_H +#define _LUME_ERRNO_H + +/* + * Error codes — numeric values aligned with Linux's asm-generic/errno-base.h, + * asm-generic/errno.h, and linux/errno.h (kernel-internal codes 512+). + * + * Userspace-visible codes (1..133) match POSIX / Linux exactly so a LumeOS + * binary returning -EINVAL to a user program receives the same number Linux + * would. Kernel-internal codes (>= 512) follow Linux's convention of being + * out of POSIX range; they are translated to a user-visible code (typically + * EINTR) before crossing into user mode. + * + * Defined as plain #define so this header is usable from C, C++, and + * assembly (the macros expand to integer literals, no types). + */ + +/* ===== Base errors (asm-generic/errno-base.h equivalent) ===== */ +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain */ +#define ERANGE 34 /* Math result not representable */ + +/* ===== Extended errors (asm-generic/errno.h equivalent) ===== */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ +#define EDEADLOCK EDEADLK +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Cannot access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Linking in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define ENOTSUP EOPNOTSUPP +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died (for robust mutexes) */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + +/* ===== Kernel-internal errors (linux/errno.h equivalent, >= 512) ===== + * These should never be seen by user programs. signal/syscall layer + * translates them to a user-visible code (typically EINTR) before + * returning to userspace. + */ +#define ERESTARTSYS 512 +#define ERESTARTNOINTR 513 +#define ERESTARTNOHAND 514 /* restart if no signal handler */ +#define ENOIOCTLCMD 515 /* No ioctl command */ +#define ERESTART_RESTARTBLOCK 516 /* restart by calling sys_restart_syscall */ +#define EPROBE_DEFER 517 /* Driver requests probe retry */ +#define EOPENSTALE 518 /* open() found a stale dentry */ +#define ENOPARAM 519 /* Parameter not supported */ + +/* NFSv3 reserved range (521..531) */ +#define EBADHANDLE 521 /* Illegal NFS file handle */ +#define ENOTSYNC 522 /* Update synchronization mismatch */ +#define EBADCOOKIE 523 /* Cookie is stale */ +#define ENOTSUPP 524 /* Operation is not supported */ +#define ETOOSMALL 525 /* Buffer or request is too small */ +#define ESERVERFAULT 526 /* An untranslatable error occurred */ +#define EBADTYPE 527 /* Type not supported by server */ +#define EJUKEBOX 528 /* Request initiated, will not complete before timeout */ +#define EIOCBQUEUED 529 /* iocb queued, will get completion event */ +#define ERECALLCONFLICT 530 /* conflict with recalled state */ +#define ENOGRACE 531 /* NFS file lock reclaim refused */ + +#endif /* _LUME_ERRNO_H */ diff --git a/include/lume/fdt.h b/include/lume/fdt.h new file mode 100644 index 0000000..626cc48 --- /dev/null +++ b/include/lume/fdt.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FdtManager — Flattened Device Tree parser + * + * Two-phase design: + * Phase 1 (Early Scan): Static methods that walk the raw FDT blob + * without any memory allocation. Used before PMM is available. + * Phase 2 (Unflatten): Builds a persistent C++ DeviceNode object + * tree once Slab is ready. The tree is then globally read-only. + * + * Concurrency: all init is done by BSP in the single-core window. + * After unflattening the tree is immutable — no locks required. + * + * Reference: docs/specs/fdt.md + */ + +#include <lume/device_node.h> +#include <lume/types.h> + +class FdtManager { +public: + // Construct with the physical address of the FDT blob. + // Called via Placement New in fdt_init(). + FdtManager(uint64 fdt_paddr); + + // --- Phase 1: Early scan (static, allocation-free) --- + + // Extract physical memory base and size from the /memory node. + // Panics if the FDT is invalid or /memory is missing. + static void early_scan_mem(uint64 fdt_paddr, uint64 *base, uint64 *size); + + // Extract the UART base address and size by matching compatible = "ns16550a". + // Falls back to platform default on failure. + static void early_scan_uart(uint64 fdt_paddr, uint64 *uart_addr, + uint64 *uart_size); + + static void early_scan_kaslr(uint64 fdt_paddr, uint64 *seed); + + // --- Phase 2: Object tree construction (requires Slab) --- + + // Walk the FDT blob and build the DeviceNode tree. + void unflatten(); + + // --- Query interface (read-only after unflatten) --- + + // Find the first node whose "compatible" property contains the string. + // Returns nullptr if no match. + DeviceNode *find_compatible(const char *compat_string); + + // Look up a node by its full path (e.g. "/soc/uart@10000000"). + // Returns nullptr if not found. + DeviceNode *get_node_by_path(const char *path); + + // Return the stored FDT physical address. + uint64 get_paddr() const { return fdt_paddr_; } + +private: + uint64 fdt_paddr_; // Physical address of the FDT blob + bool is_unflattened_ = false; // True after unflatten() completes + DeviceNode *root_ = nullptr; // Root of the unflattened tree +}; + +/* Global FDT manager singleton (Placement New, see global_init.md §3.1) */ +extern FdtManager *g_fdt; + +// --- Public free-function API --- + +// Save FDT address and validate magic. Called early by BSP. +void fdt_init(uint64 fdt_paddr); + +// Build the DeviceNode object tree. Requires Slab. +void fdt_unflatten(); + +// Driver probing helpers (delegate to g_fdt). +DeviceNode *fdt_find_compatible(const char *compat_string); +DeviceNode *fdt_get_node_by_path(const char *path); + +// Early scan wrappers (delegate to FdtManager static methods). +void fdt_early_get_mem_info(uint64 *base, uint64 *size); +void fdt_early_get_uart_info(uint64 *uart_addr, uint64 *uart_size); +void fdt_early_get_kaslr_seed(uint64 *seed); diff --git a/include/lume/fdt_prop.h b/include/lume/fdt_prop.h new file mode 100644 index 0000000..f8a263c --- /dev/null +++ b/include/lume/fdt_prop.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * FDT Property + * + * Represents a single property within a DeviceNode (e.g. "reg", + * "compatible"). The value pointer points directly into the + * original FDT blob — no copies are made. + * + * Reference: docs/specs/fdt.md §3.1 + */ + +#include <lume/types.h> +#include <lume/list.h> + +struct FdtProperty { + const char *name; // Property name (points into FDT strings block) + const void *value; // Raw property data (points into FDT struct block) + int len; // Length of value in bytes + list_node link; // Intrusive link into DeviceNode's property list +}; diff --git a/include/lume/fdtable.h b/include/lume/fdtable.h new file mode 100644 index 0000000..6159519 --- /dev/null +++ b/include/lume/fdtable.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fdtable.h — Per-task file descriptor table + * + * Modeled after Linux include/linux/fdtable.h. + * Reference: docs/vfs/05_fdtable.md + */ + +#include <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/include/lume/features.h b/include/lume/features.h new file mode 100644 index 0000000..85cfb72 --- /dev/null +++ b/include/lume/features.h @@ -0,0 +1,40 @@ +#ifndef _FEATURES_H +#define _FEATURES_H + +#if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE 1 +#endif + +#if defined(_DEFAULT_SOURCE) && !defined(_BSD_SOURCE) +#define _BSD_SOURCE 1 +#endif + +#if !defined(_POSIX_SOURCE) && !defined(_POSIX_C_SOURCE) \ + && !defined(_XOPEN_SOURCE) && !defined(_GNU_SOURCE) \ + && !defined(_BSD_SOURCE) && !defined(__STRICT_ANSI__) +#define _BSD_SOURCE 1 +#define _XOPEN_SOURCE 700 +#endif + +#if __STDC_VERSION__ >= 199901L +#define __restrict restrict +#elif !defined(__GNUC__) +#define __restrict +#endif + +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +#define __inline inline +#elif !defined(__GNUC__) +#define __inline +#endif + +#if __STDC_VERSION__ >= 201112L +#elif defined(__GNUC__) +#define _Noreturn __attribute__((__noreturn__)) +#else +#define _Noreturn +#endif + +#define __REDIR(x,y) __typeof__(x) x __asm__(#y) + +#endif diff --git a/include/lume/file.h b/include/lume/file.h new file mode 100644 index 0000000..349059b --- /dev/null +++ b/include/lume/file.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * file.h — Open file description + file_operations + path + vfsmount + * + * Modeled after Linux include/linux/fs.h struct file / file_operations. + * Reference: docs/vfs/04_file.md + */ + +#include <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/include/lume/fs.h b/include/lume/fs.h new file mode 100644 index 0000000..8dac552 --- /dev/null +++ b/include/lume/fs.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs.h — Core VFS data structures + * + * Defines super_block, inode, address_space, and their operations. + * Modeled after Linux include/linux/fs.h + include/linux/fs/super_types.h. + * + * Reference: docs/vfs/01_super_block.md, docs/vfs/02_inode.md + */ + +#include <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/include/lume/fs_struct.h b/include/lume/fs_struct.h new file mode 100644 index 0000000..ba643b5 --- /dev/null +++ b/include/lume/fs_struct.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs_struct.h — Per-task filesystem context (cwd, root) + * + * Modeled after Linux include/linux/fs_struct.h. + * Reference: docs/vfs/05_fdtable.md + */ + +#include <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/include/lume/fs_types.h b/include/lume/fs_types.h new file mode 100644 index 0000000..767f37c --- /dev/null +++ b/include/lume/fs_types.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * fs_types.h — File mode constants, open flags, seek constants + * + * All values match Linux asm-generic so musl/busybox binaries work. + * Reference: docs/vfs/02_inode.md, docs/vfs/04_file.md + */ + +#include <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/include/lume/init.h b/include/lume/init.h new file mode 100644 index 0000000..541ab96 --- /dev/null +++ b/include/lume/init.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_INIT_H +#define _LUME_INIT_H + +/* + * Boot-time section markers. + * + * Mirrors the subset of Linux include/linux/init.h that LumeOS uses: + * + * __init mark a function as boot-only — emitted into .init.text, + * which the kernel can later free once setup_arch / + * start_kernel have finished (LumeOS doesn't yet reclaim + * this memory, but the segment lives so future versions + * can without source changes). + * __initdata same for data, emitted into .init.data. + * asmlinkage ABI marker for C functions called from assembly. On + * RISC-V the default C ABI matches the assembly calling + * convention, so this is empty (matches Linux RISC-V). + * + * No attempt is made to mirror Linux's COMMAND_LINE_SIZE / saved_command_line + * machinery — that belongs in a future <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/include/lume/irq.h b/include/lume/irq.h new file mode 100644 index 0000000..f5f34e4 --- /dev/null +++ b/include/lume/irq.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * IRQ Manager — Dynamic Interrupt Dispatch + * + * Drivers register ISR callbacks at init time via register_handler(). + * The MI trap layer calls dispatch() when an external interrupt arrives. + * dispatch() runs in interrupt context — handlers must be short and + * non-blocking. + * + * Lock contract: + * - lock_ protects register/unregister (cold path). + * - dispatch() is lock-free: handler pointers are published with RELEASE + * semantics and read with ACQUIRE in dispatch(). + * - Handlers must not acquire VmSpace::lock or sleep. + * + * Reference: docs/specs/trap.md §3.3 + */ + +#include <lume/atomic.h> +#include <lume/types.h> + +/* IRQ handler callback signature */ +using IrqHandler = void (*)(uint32 irq, void *arg); + +/* Abstract Interrupt Controller Interface */ +struct IrqChip { + const char* name; + uint32 (*claim)(); + void (*complete)(uint32 irq); + void (*init_ap)(); + void (*enable)(uint32 irq); + void (*disable)(uint32 irq); + void (*set_priority)(uint32 irq, uint32 priority); +}; + +class IrqManager { +public: + void init(); + + /* Register an ISR for the given IRQ number. + * Returns 0 on success, -EINVAL if irq out of range, -EBUSY if already + * registered. */ + int register_handler(uint32 irq, IrqHandler handler, void *arg); + + /* Unregister the ISR for the given IRQ number. */ + void unregister_handler(uint32 irq); + + /* Dispatch an interrupt to the registered handler. + * Called from MI trap layer in interrupt context. Lock-free. */ + void dispatch(uint32 irq); + + /* Set the active interrupt controller chip */ + void set_chip(const IrqChip* chip); + const IrqChip* get_chip() const { return chip_; } + + /* Architecture-independent claim and complete for the active IRQ chip */ + uint32 claim() const; + void complete(uint32 irq) const; + void init_ap() const; + void enable(uint32 irq) const; + void disable(uint32 irq) const; + void set_priority(uint32 irq, uint32 priority) const; + + /* Check if a handler is registered for the given IRQ. */ + bool has_handler(uint32 irq) const; + + static constexpr uint32 kMaxIrqs = 64; + +private: + struct IrqEntry { + lume::atomic<uint64> handler_ptr; // Stored as uint64 for atomic access + void *arg = nullptr; + }; + + const IrqChip* chip_ = nullptr; + IrqEntry entries_[kMaxIrqs]; +}; + +/* Global IRQ manager singleton */ +extern IrqManager g_irq_manager; diff --git a/include/lume/kbuild.h b/include/lume/kbuild.h new file mode 100644 index 0000000..4c3e12a --- /dev/null +++ b/include/lume/kbuild.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_KBUILD_H +#define _LUME_KBUILD_H + +/* + * Helpers for arch/<arch>/kernel/asm-offsets.c — 1:1 with Linux + * include/linux/kbuild.h. + * + * Trick: each DEFINE/OFFSET emits an inline-asm ".ascii" directive + * containing a literal "->" prefix. When the file is compiled with + * + * $(CC) -S asm-offsets.c -o asm-offsets.s + * + * the resulting assembly contains lines like + * + * ->KERNEL_MAP_VIRT_ADDR $0 offsetof(struct kernel_mapping, virt_addr) + * + * scripts/extract-asm-offsets.sed then rewrites those into proper + * `#define KERNEL_MAP_VIRT_ADDR 0` lines for inclusion from head.S. + * + * The benefit over hand-written #defines is that struct layout changes + * are caught at build time: rearrange a field and the asm offset + * changes automatically. + */ + +#define DEFINE(sym, val) \ + asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val)) + +#define BLANK() \ + asm volatile("\n.ascii \"->\"" : : ) + +#define OFFSET(sym, str, mem) \ + DEFINE(sym, offsetof(struct str, mem)) + +#define COMMENT(x) \ + asm volatile("\n.ascii \"->#" x "\"") + +#endif /* _LUME_KBUILD_H */ diff --git a/include/lume/kernel_map.h b/include/lume/kernel_map.h new file mode 100644 index 0000000..7eafe88 --- /dev/null +++ b/include/lume/kernel_map.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_KERNEL_MAP_H +#define _LUME_KERNEL_MAP_H + +/* + * struct kernel_mapping — where the kernel image lives in physical and + * virtual memory. Field names and order match Linux's + * arch/riscv/include/asm/page.h struct kernel_mapping so that: + * + * 1. arch/riscv/kernel/head.S can do `la a1, kernel_map` then + * `REG_L a1, KERNEL_MAP_VIRT_ADDR(a1)` and read the virtual + * address. The KERNEL_MAP_* asm constants come from + * <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/include/lume/klog.h b/include/lume/klog.h new file mode 100644 index 0000000..69ab269 --- /dev/null +++ b/include/lume/klog.h @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Kernel Logging Subsystem + * + * All kernel output goes through klog(), which formats messages into + * a fixed-size ring buffer and optionally emits them to the console. + * + * Architecture: + * vsnprintf_() ← pure formatting (mpaland/printf) + * ↓ + * KlogBuffer (ring) ← all messages stored here + * ↓ + * early_putc() (sync) ← current: synchronous UART output + * klogd thread (async) ← future: background flush thread + * /proc/kmsg ← future: userspace dmesg interface + * + * Reference: docs/specs/klog.md + */ + +#include <lume/types.h> + +/* ======================================================================== + * Log Levels (matches Linux kern_levels.h ordering) + * ======================================================================== */ + +enum KlogLevel : uint8 { + KLOG_EMERG = 0, /* System is unusable */ + KLOG_ALERT = 1, /* Action must be taken immediately */ + KLOG_CRIT = 2, /* Critical conditions */ + KLOG_ERR = 3, /* Error conditions */ + KLOG_WARN = 4, /* Warning conditions */ + KLOG_NOTICE = 5, /* Normal but significant condition */ + KLOG_INFO = 6, /* Informational */ + KLOG_DEBUG = 7, /* Debug-level messages */ +}; + +/* Log subsystems (bitmask-friendly) */ +enum KlogSubsys : uint8 { + KLOG_SUBSYS_GENERIC = 0, + KLOG_SUBSYS_SCHED = 1, + KLOG_SUBSYS_TRAP = 2, + KLOG_SUBSYS_IRQ = 3, + KLOG_SUBSYS_TIMER = 4, + KLOG_SUBSYS_VMM = 5, + KLOG_SUBSYS_PMM = 6, + KLOG_SUBSYS_SLAB = 7, + KLOG_SUBSYS_FS = 8, + KLOG_SUBSYS_DRIVER = 9, + KLOG_SUBSYS_TEST = 10, + KLOG_SUBSYS_SYSCALL = 11, /* per-syscall entry trace */ + KLOG_SUBSYS_EXEC = 12, /* ELF load / exec internals */ + KLOG_SUBSYS_SIGNAL = 13, /* signal delivery trace */ + KLOG_SUBSYS_TTY = 14, /* tty / job-control trace */ + KLOG_SUBSYS_MAX = 15, +}; + +/* Default: show everything up to INFO. DEBUG is compile-gated. */ +#ifndef KLOG_DEFAULT_LEVEL +#define KLOG_DEFAULT_LEVEL KLOG_INFO +#endif + +/* Ring buffer size: 2^KLOG_BUF_SHIFT bytes (default 64KB) */ +#ifndef KLOG_BUF_SHIFT +#define KLOG_BUF_SHIFT 16 +#endif + +#define KLOG_BUF_SIZE (1U << KLOG_BUF_SHIFT) + +/* Maximum length of a single log message (stack-allocated) */ +#define KLOG_LINE_MAX 256 + +struct KlogStats { + uint32 head; + uint32 tail; + uint32 console_tail; + uint32 dropped; + uint32 seq; +}; + +struct KlogRateLimit { + uint64 last_ts; + uint32 interval; + uint32 burst; + uint32 printed; + uint32 suppressed; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ======================================================================== + * Initialization + * ======================================================================== */ + +/* Initialize the ring buffer and console backend. Call once from BSP. */ +void klog_init(void); + +/* ======================================================================== + * Core API + * ======================================================================== */ + +/* Write a leveled log message. Formats into ring buffer, then + * synchronously flushes to console if level <= current threshold. */ +__attribute__((format(printf, 2, 3))) +void klog(KlogLevel level, const char* fmt, ...); + +__attribute__((format(printf, 3, 4))) +void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...); + +/* Linux-style printk interface */ +__attribute__((format(printf, 1, 2))) +int printk(const char* fmt, ...); +int vprintk(const char* fmt, __builtin_va_list va); + +/* Set runtime log level filter. Messages above this level are + * still stored in the ring buffer but not printed to console. */ +void klog_set_level(KlogLevel level); + +void klog_set_subsys_mask(uint64 mask); +void klog_enable_subsys(KlogSubsys subsys); +void klog_disable_subsys(KlogSubsys subsys); + +/* ======================================================================== + * Ring Buffer Read (for future dmesg / /proc/kmsg) + * ======================================================================== */ + +/* Read up to 'size' bytes from the ring buffer into 'buf'. + * Returns number of bytes read. Non-destructive peek. */ +int klog_read(char* buf, int size); + +/* Read and consume entries (advances tail). */ +int klog_read_consume(char* buf, int size); + +/* Flush pending entries to console (advances console tail). */ +void klog_flush(void); + +bool klog_ratelimit(KlogRateLimit* rl, uint64 now); + +void klog_stats(KlogStats* out); + +/* ======================================================================== + * Convenience Macros + * ======================================================================== */ + +#define pr_emerg(fmt, ...) klog_ex(KLOG_EMERG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_alert(fmt, ...) klog_ex(KLOG_ALERT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_crit(fmt, ...) klog_ex(KLOG_CRIT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_err(fmt, ...) klog_ex(KLOG_ERR, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_warn(fmt, ...) klog_ex(KLOG_WARN, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_notice(fmt, ...) klog_ex(KLOG_NOTICE, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#define pr_info(fmt, ...) klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) + +#define pr_emerg_s(subsys, fmt, ...) klog_ex(KLOG_EMERG, subsys, fmt, ##__VA_ARGS__) +#define pr_alert_s(subsys, fmt, ...) klog_ex(KLOG_ALERT, subsys, fmt, ##__VA_ARGS__) +#define pr_crit_s(subsys, fmt, ...) klog_ex(KLOG_CRIT, subsys, fmt, ##__VA_ARGS__) +#define pr_err_s(subsys, fmt, ...) klog_ex(KLOG_ERR, subsys, fmt, ##__VA_ARGS__) +#define pr_warn_s(subsys, fmt, ...) klog_ex(KLOG_WARN, subsys, fmt, ##__VA_ARGS__) +#define pr_notice_s(subsys, fmt, ...) klog_ex(KLOG_NOTICE, subsys, fmt, ##__VA_ARGS__) +#define pr_info_s(subsys, fmt, ...) klog_ex(KLOG_INFO, subsys, fmt, ##__VA_ARGS__) + +#ifdef KLOG_ENABLE_DEBUG +#define pr_debug(fmt, ...) klog_ex(KLOG_DEBUG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__) +#else +#define pr_debug(fmt, ...) do {} while (0) +#endif + +/* pr_debug_s: subsystem-tagged debug. Always compiled in (subsys is the gate); + * level KLOG_DEBUG ensures messages stay in the ring buffer but are filtered + * out of the console by default (console_level = KLOG_INFO). Toggle a subsys + * with klog_enable_subsys()/klog_disable_subsys() at runtime — or, to make + * the messages also reach the console for debugging, raise console_level + * via klog_set_level(KLOG_DEBUG). */ +#define pr_debug_s(subsys, fmt, ...) \ + klog_ex(KLOG_DEBUG, subsys, fmt, ##__VA_ARGS__) + +/* Linux kernel loglevel prefixes */ +#define KERN_SOH "\001" +#define KERN_EMERG KERN_SOH "0" +#define KERN_ALERT KERN_SOH "1" +#define KERN_CRIT KERN_SOH "2" +#define KERN_ERR KERN_SOH "3" +#define KERN_WARNING KERN_SOH "4" +#define KERN_NOTICE KERN_SOH "5" +#define KERN_INFO KERN_SOH "6" +#define KERN_DEBUG KERN_SOH "7" + +/* Boot stage log (thin wrapper over printk) */ +#define BOOT_STAGE(n, desc) \ + printk(KERN_INFO "STAGE%u %s\n", (unsigned)(n), (desc)) + +extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/kobject.h b/include/lume/kobject.h new file mode 100644 index 0000000..8581d6e --- /dev/null +++ b/include/lume/kobject.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * kobject — generic refcounted object with parent/child relationship. + * + * Mirrors Linux include/linux/kobject.h. Embedded into device, gendisk, + * request_queue, block_device, etc. Each kobject has a kobj_type that + * provides the release callback called when the last reference drops. + * + * sysfs integration is not yet implemented — the sd/sysfs_ops fields are + * reserved for future use. + * + * Reference: docs/specs/storage_stack.md §4.2 + */ + +#include <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/include/lume/kprintf.h b/include/lume/kprintf.h new file mode 100644 index 0000000..b158602 --- /dev/null +++ b/include/lume/kprintf.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Kernel Formatted Output — kprintf + * + * Provides printf-style formatted output for the kernel. + * Built on top of stb_sprintf for full format string support + * without any libc dependency. + * + * Output is synchronous and directed to the console (UART). + * Safe to call from any context (interrupt or process). + * + * Usage: + * kprintf("Hello %s, addr=0x%lx\n", name, addr); + */ + +#include <stdarg.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Formatted print to console (like printf). */ +__attribute__((format(printf, 1, 2))) +int kprintf(const char* fmt, ...); + +/* Formatted print with va_list. */ +int kvprintf(const char* fmt, va_list va); + +/* Formatted snprintf into buffer. */ +__attribute__((format(printf, 3, 4))) +int ksnprintf(char* buf, int count, const char* fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/kref.h b/include/lume/kref.h new file mode 100644 index 0000000..13d53f3 --- /dev/null +++ b/include/lume/kref.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * kref — atomic reference counting. + * + * Mirrors Linux include/linux/kref.h. Use kref_init() to set count=1, + * kref_get() / kref_put() to add/drop refs. kref_put() invokes the + * release callback when the final ref is dropped and returns 1 in that + * case. + * + * Reference: docs/specs/storage_stack.md §4.1 + */ + +#include <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/include/lume/libfdt.h b/include/lume/libfdt.h new file mode 100644 index 0000000..e3496e9 --- /dev/null +++ b/include/lume/libfdt.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INCLUDE_LIBFDT_H_ +#define _INCLUDE_LIBFDT_H_ + +#include <lume/libfdt_env.h> +#include "../../scripts/dtc/libfdt/libfdt.h" + +#endif /* _INCLUDE_LIBFDT_H_ */ diff --git a/include/lume/libfdt_env.h b/include/lume/libfdt_env.h new file mode 100644 index 0000000..a95fb1d --- /dev/null +++ b/include/lume/libfdt_env.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef LIBFDT_ENV_H +#define LIBFDT_ENV_H + +/* + * Kernel-side adapter for the standalone libfdt sources in + * scripts/dtc/libfdt/. libfdt's *.c files start with + * #include "libfdt_env.h" + * and expect this header to provide: + * - byte-swap helpers (cpu_to_fdt16/32/64, fdt16/32/64_to_cpu) + * - fdt16/32/64_t typedefs (just plain uint types — endianness is + * handled in the helpers above) + * - string / mem helpers (memcpy, memmove, memcmp, strlen, strcmp, + * strchr, strnlen, ...) + * - INT_MAX, UINT32_MAX (libfdt bounds-checks props with these) + * - size_t / uintptr_t (everywhere) + * + * LumeOS-native: no Linux <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/include/lume/limits.h b/include/lume/limits.h new file mode 100644 index 0000000..a275a71 --- /dev/null +++ b/include/lume/limits.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_LIMITS_H +#define _LUME_LIMITS_H + +/* + * Numeric limit constants — minimal subset needed by LumeOS kernel code. + * + * Values match the standard <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/include/lume/list.h b/include/lume/list.h new file mode 100644 index 0000000..ed1202a --- /dev/null +++ b/include/lume/list.h @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include <lume/types.h> +#include <lume/container_of.h> + +/** + * @brief The node to be embedded in a struct to make it linkable. + * + * An object can be on multiple lists simultaneously by embedding multiple + * list_node members with different names. + */ +struct list_node { + list_node* prev; + list_node* next; + + /** + * @brief Initializes the node to point to itself, forming an empty list head. + */ + void init() { + prev = this; + next = this; + } + + /** + * @brief Checks if the list this node is the head of is empty. + * @return True if the list is empty, false otherwise. + */ + bool is_empty() const { + return next == this; + } +}; + +/* + * Type-safe container_of: given a pointer to a list_node member, + * recover a pointer to the enclosing object. + * Uses pointer-to-member arithmetic — no __builtin_offsetof needed. + */ +template <typename T, list_node T::*Member> +static inline T *list_entry(list_node *node) +{ + const size_t off = (size_t)(uintptr) & (((T *)nullptr)->*Member); + return reinterpret_cast<T *>(reinterpret_cast<uintptr>(node) - off); +} + +/** + * @brief A type-safe, intrusive, doubly-linked list. + * + * This class manages objects of type T that have a `list_node` member. + * It has zero runtime overhead compared to a manual C implementation and + * provides compile-time type safety. + * + * @tparam T The type of the object in the list. + * @tparam NodeMember A pointer-to-member specifying which `list_node` in T to use. + */ +template <typename T, list_node T::*NodeMember> +class List { +private: + static list_node* get_node(T* obj) { + return &(obj->*NodeMember); + } + + // NOTE: `((T*)nullptr)->*NodeMember` is technically undefined behavior in + // standard C++. However, it's a common idiom in low-level programming + // (similar to `offsetof`) that compilers typically handle correctly, + // especially with flags like `-fno-delete-null-pointer-checks`. + // The alternative (using `offsetof`) would sacrifice the compile-time + // type safety of the pointer-to-member template parameter. + static T* get_obj(list_node* node) { + constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); + return reinterpret_cast<T*>(reinterpret_cast<uintptr>(node) - offset); + } + + static const T* get_obj(const list_node* node) { + constexpr size_t offset = (size_t)(uintptr) & (((T*)nullptr)->*NodeMember); + return reinterpret_cast<const T*>(reinterpret_cast<uintptr>(node) - offset); + } + +public: + class iterator; + class const_iterator; + + class iterator { + friend class List<T, NodeMember>; + friend class const_iterator; + + public: + iterator(list_node* node) : node_(node) {} + + T& operator*() const { return *get_obj(node_); } + T* operator->() const { return get_obj(node_); } + + iterator& operator++() { + node_ = node_->next; + return *this; + } + iterator operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; + } + + iterator& operator--() { + node_ = node_->prev; + return *this; + } + iterator operator--(int) { + iterator tmp = *this; + --(*this); + return tmp; + } + + bool operator==(const iterator& other) const { return node_ == other.node_; } + bool operator!=(const iterator& other) const { return !(*this == other); } + + private: + list_node* node_; + }; + + class const_iterator { + public: + const_iterator(const list_node* node) : node_(node) {} + const_iterator(const iterator& other) : node_(other.node_) {} + + const T& operator*() const { return *get_obj(node_); } + const T* operator->() const { return get_obj(node_); } + + const_iterator& operator++() { + node_ = node_->next; + return *this; + } + const_iterator operator++(int) { + const_iterator tmp = *this; + ++(*this); + return tmp; + } + + const_iterator& operator--() { + node_ = node_->prev; + return *this; + } + const_iterator operator--(int) { + const_iterator tmp = *this; + --(*this); + return tmp; + } + + bool operator==(const const_iterator& other) const { return node_ == other.node_; } + bool operator!=(const const_iterator& other) const { return !(*this == other); } + + private: + const list_node* node_; + }; + + List() { head_.init(); } + ~List() = default; + + // This container is intrusive and does not own the objects. + // Copying or moving it is a logical error. + List(const List&) = delete; + List& operator=(const List&) = delete; + + bool empty() const { return head_.is_empty(); } + + void push_front(T* obj) { + list_node* node = get_node(obj); + node->next = head_.next; + node->prev = &head_; + head_.next->prev = node; + head_.next = node; + } + + void push_back(T* obj) { + list_node* node = get_node(obj); + node->prev = head_.prev; + node->next = &head_; + head_.prev->next = node; + head_.prev = node; + } + + /** + * @brief Splices all elements from another list to the front of this list. + * + * Moves all elements from `other` to the beginning of `this` list in O(1) + * time. The `other` list becomes empty after the operation. + * + * @param other The list to splice from. + */ + void splice_front(List& other) { + if (other.empty()) { + return; + } + list_node* this_first = head_.next; + list_node* other_first = other.head_.next; + list_node* other_last = other.head_.prev; + + // Link other's last to this's original first + other_last->next = this_first; + this_first->prev = other_last; + + // Link this's head to other's first + head_.next = other_first; + other_first->prev = &head_; + + // Reset the other list + other.head_.init(); + } + + /** + * @brief Splices all elements from another list to the back of this list. + * + * Moves all elements from `other` to the end of `this` list in O(1) + * time. The `other` list becomes empty after the operation. + * + * @param other The list to splice from. + */ + void splice_tail(List& other) { + if (other.empty()) { + return; + } + list_node* this_last = head_.prev; + list_node* other_first = other.head_.next; + list_node* other_last = other.head_.prev; + + // Link this's last to other's first + this_last->next = other_first; + other_first->prev = this_last; + + // Link other's last to this's head + other_last->next = &head_; + head_.prev = other_last; + + // Reset the other list + other.head_.init(); + } + + T* pop_front() { + if (empty()) { + return nullptr; + } + list_node* node = head_.next; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + return get_obj(node); + } + + T* pop_back() { + if (empty()) { + return nullptr; + } + list_node* node = head_.prev; + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + return get_obj(node); + } + + void remove(T* obj) { + list_node* node = get_node(obj); + node->prev->next = node->next; + node->next->prev = node->prev; + node->init(); + } + + T* front() { + return empty() ? nullptr : get_obj(head_.next); + } + + const T* front() const { + return empty() ? nullptr : get_obj(head_.next); + } + + T* back() { + return empty() ? nullptr : get_obj(head_.prev); + } + + const T* back() const { + return empty() ? nullptr : get_obj(head_.prev); + } + + iterator begin() { return iterator(head_.next); } + iterator end() { return iterator(&head_); } + const_iterator begin() const { return const_iterator(head_.next); } + const_iterator end() const { return const_iterator(&head_); } + const_iterator cbegin() const { return const_iterator(head_.next); } + const_iterator cend() const { return const_iterator(&head_); } + +private: + list_node head_; +}; + +/* ======================================================================== + * Linux-compatible C-style list macros (for VFS subsystem) + * ======================================================================== */ + +using list_head = list_node; + +inline void list_init(list_node* head) { + head->prev = head; + head->next = head; +} + +inline bool list_empty(const list_node* head) { + return head->next == head; +} + +inline void __list_add(list_node* node, list_node* p, list_node* n) { + n->prev = node; + node->next = n; + node->prev = p; + p->next = node; +} + +inline void list_add(list_node* node, list_node* head) { + __list_add(node, head, head->next); +} + +inline void list_add_tail(list_node* node, list_node* head) { + __list_add(node, head->prev, head); +} + +inline void list_del(list_node* node) { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = nullptr; + node->prev = nullptr; +} + +inline void list_del_init(list_node* node) { + node->prev->next = node->next; + node->next->prev = node->prev; + list_init(node); +} + +#define list_entry(ptr, type, member) container_of(ptr, type, member) + +#define list_for_each(pos, head) \ + for ((pos) = (head)->next; (pos) != (head); (pos) = (pos)->next) + +#define list_for_each_safe(pos, tmp, head) \ + for ((pos) = (head)->next, (tmp) = (pos)->next; \ + (pos) != (head); \ + (pos) = (tmp), (tmp) = (pos)->next) diff --git a/include/lume/lumeos.lds.h b/include/lume/lumeos.lds.h new file mode 100644 index 0000000..58d2f72 --- /dev/null +++ b/include/lume/lumeos.lds.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Common Linker Script Macros (Machine-Independent) + * + * Shared section definitions that every architecture must emit. + * Architecture-specific lumeos.lds.S files #include this header + * and use these macros to avoid duplicating common boilerplate. + * + * Modeled after Linux include/asm-generic/vmlinux.lds.h + * Reference: docs/specs/linker_and_entry.md + */ + +#ifndef _LUME_LUMEOS_LDS_H +#define _LUME_LUMEOS_LDS_H + +/* ===== Text Section: executable code ===== + * + * .init.text is merged into the main .text segment so PC-relative `la` + * from regular code can call __init helpers without a thunk. Linux + * keeps a separate .init.text only to reclaim the pages after boot — + * LumeOS doesn't yet reclaim, so the merge is harmless and avoids an + * extra PT_LOAD entry. */ +#define TEXT_SECTION \ + .text : AT(ADDR(.text) - PAGE_OFFSET) { \ + _stext = .; \ + *(.text .text.*) \ + *(.init.text .init.text.*) \ + *(.fixup) \ + _etext = .; \ + } = 0 + +/* ===== Read-Only Data + C++ init_array + initramfs ===== + * + * Layout invariants: + * - extable / init_array / driver_array / initramfs all live INSIDE + * .rodata between _srodata and _erodata so the VMM has one R-only + * mapping to manage. + * - The relocation table (.lume.reloc) is NOT in here — it lives in + * its own padded output section (see RELOC_SECTION) so that its + * two-pass-build size variance can't shift downstream offsets. + * (Putting it in .rodata caused a heisenbug where pre-link offsets + * used by gen_reloc.py didn't match post-link offsets after the + * reloc.o blob was inserted.) + */ +#define RODATA_SECTION \ + .rodata : AT(ADDR(.rodata) - PAGE_OFFSET) { \ + _srodata = .; \ + *(.rodata .rodata.*) \ + *(.srodata .srodata.* .srodata.cst*) \ + . = ALIGN(8); \ + __extable_start = .; \ + KEEP(*(.extable)) \ + __extable_end = .; \ + INIT_ARRAY \ + DRIVER_ARRAY \ + /* Embedded initramfs CPIO archive */ \ + . = ALIGN(8); \ + __initramfs_start = .; \ + KEEP(*(.init.ramfs)) \ + __initramfs_end = .; \ + . = ALIGN(4096); \ + _erodata = .; \ + } + +/* ===== .rel.dyn / .rela.dyn — Linker-emitted RELA relocations ===== + * + * Mirrors arch/riscv/kernel/vmlinux.lds.S §101-109: the .rela.dyn + * section sits BETWEEN init data and .rodata so it lands in a + * read-only PT_LOAD segment alongside .text/.rodata. The companion + * dynamic-linker bookkeeping (.dynamic, .dynsym, .dynstr, .hash, + * .gnu.hash) lives in the RW segment AFTER .data — Linux does the + * same under #ifdef CONFIG_RELOCATABLE (§139-147). */ +#define RELA_DYN_SECTION \ + .rel.dyn : AT(ADDR(.rel.dyn) - PAGE_OFFSET) { \ + *(.rel.dyn*) \ + } \ + .rela.dyn : AT(ADDR(.rela.dyn) - PAGE_OFFSET) ALIGN(8) {\ + __rela_dyn_start = .; \ + *(.rela .rela*) \ + __rela_dyn_end = .; \ + } + +/* ===== GOT — kept; everything else dynamic-linker is discarded ===== + * + * Strict mirror of arch/riscv/kernel/vmlinux.lds.S in Linux: the only + * dynamic-linker artefact the kernel keeps is `.got` (it's referenced + * by every -fPIE compiled global access). `.plt .dynamic .dynsym + * .dynstr .hash .gnu.hash .interp .dynbss` are all sent to /DISCARD/ + * via the DISCARDS macro below — the kernel never invokes a dynamic + * linker (head.S walks .rela.dyn directly). + * + * Keeping .plt was the cause of two distinct bugs: + * 1. PLT trampoline target for an undefined symbol (selftest_reloc + * after we removed test_reloc.cc) was unresolved → jumps to a + * slot that re-jumps to itself → instruction page fault. + * 2. .plt carries X permission; living in the same PT_LOAD as .data + * forces the load segment to RWX, which `ld` warns about. + * + * Sit in the same PT_LOAD as .data because .got is RW. */ +#define GOT_SECTION \ + .got : AT(ADDR(.got) - PAGE_OFFSET) { *(.got*) } + +/* ===== Embedded initramfs (CPIO archive) ===== */ +#define INITRAMFS_SECTION \ + .init.ramfs : AT(ADDR(.init.ramfs) - PAGE_OFFSET) { \ + . = ALIGN(8); \ + __initramfs_start = .; \ + KEEP(*(.init.ramfs)) \ + __initramfs_end = .; \ + } + +/* ===== C++ Global Constructor Table ===== */ +#define INIT_ARRAY \ + . = ALIGN(8); \ + __init_array_start = .; \ + KEEP(*(.init_array .init_array.*)) \ + __init_array_end = .; + +/* ===== Driver Registration Table ===== */ +#define DRIVER_ARRAY \ + . = ALIGN(8); \ + PROVIDE(__drivers_start = .); \ + KEEP(*(.drivers)) \ + PROVIDE(__drivers_end = .); + +/* ===== Initialized Read-Write Data ===== + * + * .init.data is merged into .data for the same reason TEXT_SECTION + * absorbs .init.text — LumeOS doesn't yet reclaim __init pages, so + * splitting them gains nothing while costing a PT_LOAD entry. */ +#define DATA_SECTION \ + .data : AT(ADDR(.data) - PAGE_OFFSET) { \ + _sdata = .; \ + *(.data .data.*) \ + *(.init.data .init.data.*) \ + ARCH_DATA_EXTRA \ + _edata = .; \ + } + +/* ===== BSS: Uninitialized Data ===== */ +#define BSS_SECTION \ + .bss : AT(ADDR(.bss) - PAGE_OFFSET) { \ + _sbss = .; \ + *(.bss .bss.*) \ + *(.sbss .sbss.*) \ + ARCH_BSS_EXTRA \ + _ebss = .; \ + } + +/* ===== Discard Unwanted Sections ===== + * + * Mirrors arch/riscv/kernel/vmlinux.lds.S in Linux: + * /DISCARD/ : { + * *(.dynbss) + * *(.dynstr*) + * *(.dynamic*) + * *(.plt*) + * *(.interp*) + * *(.eh_frame) + * *(.note.GNU-stack) + * } + * + * Why each is safe to discard: + * .plt — -Bsymbolic resolves all internal refs at link time; + * any remaining PLT entry is an undefined symbol that + * the kernel would have crashed on anyway. + * .dynamic etc. — kernel doesn't invoke a dynamic linker; head.S + * walks __rela_dyn_start..__rela_dyn_end manually. + * .interp — no PT_INTERP at runtime. + * .eh_frame* — no C++ exceptions in kernel (-fno-exceptions). + * .note.* — boot-time identification metadata, unused. */ +#define DISCARDS \ + /DISCARD/ : { \ + *(.dynbss) \ + *(.dynstr*) \ + *(.dynamic*) \ + *(.dynsym*) \ + *(.hash*) *(.gnu.hash*) \ + *(.plt*) \ + *(.interp*) \ + *(.eh_frame .eh_frame_hdr) \ + *(.note .note.*) \ + *(.comment) \ + } + +/* ===== Kernel Image Boundary ===== */ +#define KERNEL_END \ + . = ALIGN(4096); \ + _kernel_end = .; + +#endif /* _LUME_LUMEOS_LDS_H */ diff --git a/include/lume/memblock.h b/include/lume/memblock.h new file mode 100644 index 0000000..cb9a76b --- /dev/null +++ b/include/lume/memblock.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * memblock — Boot-time physical memory allocator + * + * A simple region-based allocator active from early boot until + * the buddy system (PMM) takes over. Inspired by Linux's memblock. + * + * Lifetime: + * 1. memblock_init() — discovers RAM via FDT, reserves kernel image + * 2. vmm_init() — uses memblock_alloc for page table pages & page_map + * 3. pmm_init() — absorbs remaining free regions into buddy + * 4. memblock_retire() — marks memblock inactive; all alloc via PMM + * + * This eliminates the need for post-init pointer relocation when + * rebinding page_map to a VMEMMAP virtual address. + */ + +#include <lume/types.h> + +inline constexpr int kMemblockMaxRegions = 128; + +/* Initialize memblock: scan FDT for RAM, reserve kernel image + firmware. */ +void memblock_init(uint64 fdt_paddr); + +/* Mark a physical range as reserved (will not be handed to buddy). */ +void memblock_reserve(uint64 base, uint64 size); + +/* Allocate `size` bytes aligned to `align` from top of free memory. + * Returns PA, or 0 on failure. Used for page_map. */ +uint64 memblock_alloc_top(uint64 size, uint64 align); + +/* Allocate `size` bytes aligned to `align` from bottom of free memory. + * Returns PA, or 0 on failure. Used for page table pages. */ +uint64 memblock_alloc(uint64 size, uint64 align); + +/* Query memory layout (same info as FDT). */ +uint64 memblock_get_mem_base(); +uint64 memblock_get_mem_size(); + +/* Is memblock still the active allocator? */ +bool memblock_is_active(); + +/* Mark memblock as retired (PMM takes over). */ +void memblock_retire(); + +/* Iterate all free (unreserved) regions in ascending order. + * Used by pmm_init() to feed free pages into the buddy system. */ +void memblock_for_each_free(void (*cb)(uint64 base, uint64 size, void* ctx), + void* ctx); diff --git a/include/lume/mm_layout.h b/include/lume/mm_layout.h new file mode 100644 index 0000000..7744de0 --- /dev/null +++ b/include/lume/mm_layout.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * mm_layout.h — User/Kernel address space boundary definitions + * + * Single source of truth for the user/kernel VA split. + * All modules (VMM, uaccess, page fault, tests) MUST include this + * header instead of defining their own constants. + * + * Layout (SV39, RISC-V): + * [0x0000_0000_0000, kUserVaLimit) — User space + * [kUserVaLimit, 0xFFFF_FFFF_FFFF) — Kernel space (direct map, vmemmap, etc.) + * + * Reference: docs/specs/linux_abi_mm_plan.md §5 + */ + +#include <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/include/lume/mman.h b/include/lume/mman.h new file mode 100644 index 0000000..d952419 --- /dev/null +++ b/include/lume/mman.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * POSIX Memory Management Constants + * Reference: docs/specs/syscall.md §9 + */ + +/* mmap / mprotect protection flags */ +inline constexpr int PROT_NONE = 0x0; +inline constexpr int PROT_READ = 0x1; +inline constexpr int PROT_WRITE = 0x2; +inline constexpr int PROT_EXEC = 0x4; + +/* mmap flags */ +inline constexpr int MAP_SHARED = 0x01; +inline constexpr int MAP_PRIVATE = 0x02; +inline constexpr int MAP_FIXED = 0x10; +inline constexpr int MAP_ANONYMOUS = 0x20; diff --git a/include/lume/mount.h b/include/lume/mount.h new file mode 100644 index 0000000..13f8e62 --- /dev/null +++ b/include/lume/mount.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * mount.h — Internal mount structure + */ + +#include <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/include/lume/mutex.h b/include/lume/mutex.h new file mode 100644 index 0000000..9719aa6 --- /dev/null +++ b/include/lume/mutex.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Mutex — Linux-style sleeping mutex (no priority inheritance). + * + * Semantics mirror Linux kernel/locking/mutex.c: + * - Single owner (nullptr when free). + * - FIFO wait list — first waiter is the next owner. + * - Recursive acquire by the same task is FORBIDDEN (panic). + * - Owner MUST be the one to unlock. + * - lock() may sleep; MUST NOT be called from IRQ context, with + * preemption disabled, or while holding a spinlock. + * - try_lock() never sleeps; may be called from any context. + * + * Priority inheritance lives in a separate primitive (RtMutex, future) + * just like Linux splits struct mutex / struct rt_mutex. + * + * Reference: docs/specs/mutex.md + */ + +#include <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/include/lume/namei.h b/include/lume/namei.h new file mode 100644 index 0000000..1764693 --- /dev/null +++ b/include/lume/namei.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * namei.h — Pathname lookup (path resolution) + */ + +#include <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/include/lume/new.h b/include/lume/new.h new file mode 100644 index 0000000..cdcacb8 --- /dev/null +++ b/include/lume/new.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Placement New for Freestanding Environments + * + * In -ffreestanding -nostdlib mode, <new> may not be available. + * LumeOS provides its own placement new/delete operators. + * + * Usage: g_obj = new (g_obj_buf) MyClass(args...); + * + * Reference: docs/specs/global_init.md §3.1 + */ + +inline void* operator new(decltype(sizeof(0)), void* ptr) noexcept { return ptr; } +inline void operator delete(void*, void*) noexcept {} diff --git a/include/lume/page.h b/include/lume/page.h new file mode 100644 index 0000000..61bff95 --- /dev/null +++ b/include/lume/page.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Page — Physical page descriptor + * + * Each Page describes one 4KB physical page. A flat array of Page + * structs (page_map) covers all physical memory discovered via FDT. + * + * Memory layout is compressed using a union: only one of free/slab + * metadata is valid at a time, determined by `type`. + * + * Fields: + * refcount — lifecycle reference count (atomic). Page cannot be + * freed while refcount > 0. + * mapcount — PTE mapping count (atomic). Number of page table + * entries pointing to this page. Used by COW and + * future reclaim. + * flags — page state bits (PG_*). Also serves as a bit-lock + * via PG_LOCKED for short critical sections. + * type — discriminant for the union (Free/Slab/Anon/File). + * mapping — pointer to reverse-mapping context. For anonymous + * pages, points to AnonVma (low bit set). For file + * pages, points to FileMapping (low bit clear). + * index — page offset within the mapping object. + * + * Reference: docs/specs/pmm.md §3.1 + */ + +#include <lume/types.h> +#include <lume/list.h> +#include <lume/atomic.h> + +/* Forward declarations */ +class KmemCache; + +/* ================================================================== */ +/* Page type discriminant */ +/* ================================================================== */ + +enum class PageType : uint8 { + Free, // In Buddy free list + PcpCached, // In Per-CPU cache (invisible to buddy coalescing) + Allocated, // Handed out but not yet typed (PMM default) + Slab, // Backing a Slab cache + Anon, // Anonymous user page + File, // File-backed page (page cache) +}; + +/* ================================================================== */ +/* Page flags (stored as atomic uint32) */ +/* ================================================================== */ + +inline constexpr uint32 kPgLocked = (1U << 0); // Bit-lock for short critical sections +inline constexpr uint32 kPgReferenced = (1U << 1); // Accessed since last check +inline constexpr uint32 kPgDirty = (1U << 2); // Modified, needs writeback +inline constexpr uint32 kPgWriteback = (1U << 3); // IO writeback in progress +inline constexpr uint32 kPgLru = (1U << 4); // On an LRU list +inline constexpr uint32 kPgMapped = (1U << 5); // Has at least one PTE mapping +inline constexpr uint32 kPgSwapcache = (1U << 6); // In swap cache (future) +inline constexpr uint32 kPgCow = (1U << 7); // Involved in COW sharing + +/* ================================================================== */ +/* Page descriptor */ +/* ================================================================== */ + +struct Page { + /* --- Counts (atomic, lock-free) --- */ + lume::atomic<uint32> refcount; // Lifecycle ref: 0 = reclaimable + lume::atomic<uint32> mapcount; // PTE mapping count: 0 = no user mapping + + /* --- State --- */ + lume::atomic<uint32> flags; // PG_* bits (also serves as bit-lock) + PageType type; // Discriminant for union + uint8 order; // Buddy order (0–10, i.e. 4KB–4MB) + + /* --- Reverse mapping --- */ + void* mapping; // AnonVma* (low bit set) or FileMapping* + uint64 index; // Page offset within mapping object + + /* --- LRU (future) --- */ + list_node lru; // LRU list linkage (for reclaim) + + /* --- Subsystem-specific metadata --- */ + union { + /* Valid when type == Free */ + struct { + list_node free_link; // Link into Buddy or PCP free list + } free; + + /* Valid when type == Slab */ + struct { + KmemCache* cache; // Owning slab cache + uint32 obj_count; // Number of allocated objects on this page + void* freelist; // Head of in-page free object linked list + } slab; + + /* type == Anon / File: no extra union metadata (rmap via mapping) */ + }; +}; + +/* ================================================================== */ +/* Page bit-lock (lightweight, no sleep, used for rmap critical */ +/* sections and short metadata updates) */ +/* ================================================================== */ + +inline bool page_trylock(Page* page) +{ + uint32 old = page->flags.fetch_or(kPgLocked); + return (old & kPgLocked) == 0; +} + +inline void page_lock(Page* page) +{ + while (!page_trylock(page)) { + /* spin — acceptable for short critical sections only */ + } +} + +inline void page_unlock(Page* page) +{ + page->flags.fetch_and(~kPgLocked); +} + +/* ================================================================== */ +/* Mapping pointer helpers (Linux convention: low bit = anon) */ +/* ================================================================== */ + +inline bool page_is_anon(const Page* page) +{ + return (reinterpret_cast<uint64>(page->mapping) & 1ULL) != 0; +} + +inline void* page_mapping_raw(const Page* page) +{ + return reinterpret_cast<void*>( + reinterpret_cast<uint64>(page->mapping) & ~1ULL); +} + +inline void page_set_anon_mapping(Page* page, void* anon_vma) +{ + page->mapping = reinterpret_cast<void*>( + reinterpret_cast<uint64>(anon_vma) | 1ULL); +} + +inline void page_set_file_mapping(Page* page, void* file_mapping) +{ + page->mapping = file_mapping; +} diff --git a/include/lume/page_cache.h b/include/lume/page_cache.h new file mode 100644 index 0000000..abdc535 --- /dev/null +++ b/include/lume/page_cache.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * page_cache.h — Page Cache interface + * + * Provides a stable abstraction for caching physical pages indexed by + * (mapping, index) pairs. All file-backed fault paths should go + * through this interface so that upgrading the backing implementation + * (e.g. LRU eviction, writeback, radix tree) does not require + * changing any caller. + * + * Current implementation: simple open-addressing hash table. + * Future: radix tree + LRU lists + writeback integration. + * + * Terminology: + * mapping — opaque pointer identifying the backing object + * (FileMapping*, or any future cache-able object). + * index — page-granularity offset within the mapping. + * + * Reference: docs/specs/linux_abi_mm_plan.md §2.9, §3 + */ + +#include <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/include/lume/pgrp.h b/include/lume/pgrp.h new file mode 100644 index 0000000..127f3f8 --- /dev/null +++ b/include/lume/pgrp.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Process Group / Session infrastructure. + * + * Linux models pgrp/session via struct pid + PIDTYPE_PGID/PIDTYPE_SID hlists. + * We use a simpler design that supports the same operations cheaply: + * + * - Each thread group owns one signal_struct (refcount-shared with + * CLONE_THREAD siblings). + * - signal_struct carries pgid/sid and is the linked-list node on a + * fixed-size pgrp hash table. + * - kill(pid<=0), tty_signal_pgrp, orphan checks all walk one bucket. + * + * Concurrency: g_pgrp_lock protects every bucket and the pgrp_link of + * every signal_struct on a bucket. signal_struct::lock is a finer-grain + * lock for fields that change without re-hashing (e.g. tty pointer). + * + * Reference: Linux include/linux/pid.h, kernel/pid.c + */ + +#include <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/include/lume/pgtable_types.h b/include/lume/pgtable_types.h new file mode 100644 index 0000000..e309a82 --- /dev/null +++ b/include/lume/pgtable_types.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Page Table Entry Types — strongly typed wrappers. + * + * Layout and macro names mirror Linux's arch/riscv/include/asm/page.h + * and pgtable-64.h so that arch/riscv/mm/init.c can be copied from + * Linux without source edits. The wrapped struct prevents accidental + * mixing of a pte_t with a pgd_t or a raw physical address, which the + * previous bare-uint64 design in pmap.cc could not catch. + * + * Underlying width on RV64 LP64 is `unsigned long` = 64 bits, matching + * Linux exactly — assembly references (e.g. REG_S/REG_L on a pte_t*) + * see the same byte layout. + * + * SV39 uses 3 levels (pgd -> pmd -> pte); SV48 uses 4 (pgd -> pud -> + * pmd -> pte). p4d_t/pud_t are emitted unconditionally so that the + * higher-level abstractions in asm-generic/pgtable-nopud.h and friends + * (which Linux uses to collapse unused levels) work without #ifdef. + */ + +#include <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/include/lume/pid.h b/include/lume/pid.h new file mode 100644 index 0000000..8503750 --- /dev/null +++ b/include/lume/pid.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * PID Allocator — Bitmap-based O(1) block search + * Reference: docs/specs/task.md §3.2 + */ + +#include <lume/types.h> +#include <kernel/sync/spinlock.h> + +inline constexpr int32 kMaxPid = 32768; + +class PidAllocator { +public: + void init(); + int32 alloc(); + void free(int32 pid); + +private: + static constexpr int32 kBitmapWords = kMaxPid / 64; + Spinlock lock_; + uint64 bitmap_[kBitmapWords]; + int32 next_hint_ = 2; +}; + +extern PidAllocator g_pid_alloc; diff --git a/include/lume/pipe.h b/include/lume/pipe.h new file mode 100644 index 0000000..c905c9a --- /dev/null +++ b/include/lume/pipe.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * pipe.h — Pipe data structure and API + * + * A pipe is a unidirectional byte stream backed by a fixed-size + * circular buffer. Two file structs (reader + writer) share a + * single pipe_inode_info. + */ + +#include <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/include/lume/platform_device.h b/include/lume/platform_device.h new file mode 100644 index 0000000..ac3e527 --- /dev/null +++ b/include/lume/platform_device.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * platform_device / platform_driver — FDT-described memory-mapped devices. + * + * Mirrors Linux include/linux/platform_device.h. The platform bus is + * the catch-all bus for devices that don't live on a real bus like PCI + * or USB — every device discovered via the FDT lands here. + * + * Reference: docs/specs/storage_stack.md §4.3 + */ + +#include <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/include/lume/pmm.h b/include/lume/pmm.h new file mode 100644 index 0000000..3e4b819 --- /dev/null +++ b/include/lume/pmm.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Physical Memory Manager (PMM) — Buddy System + Per-CPU Cache + * + * Manages all physical pages discovered via FDT. Built on a + * power-of-two buddy allocator for multi-page requests, with a + * Per-CPU single-page cache (PCP) to reduce lock contention on the + * hot single-page alloc/free path. + * + * Concurrency model: + * - PCP operations run with interrupts disabled (same-CPU safety). + * - Global buddy operations are protected by a single Spinlock. + * - Reference counts use __atomic builtins (lock-free). + * + * Reference: docs/specs/pmm.md + */ + +#include <lume/types.h> +#include <lume/page.h> +#include <lume/addr_types.h> +#include <lume/config.h> + +/* Buddy system constants */ +inline constexpr int kMaxOrder = CONFIG_MAX_ORDER; // Orders 0..(N-1) + +/* Per-CPU cache dynamic parameters (calculated in pmm_init) */ +extern uint32 g_pcp_high_watermark; +extern uint32 g_pcp_batch_size; + +/* ================================================================== */ +/* Public free-function API */ +/* ================================================================== */ + +/* + * Initialize the PMM. Called by BSP after vmm_init() + memblock. + * page_map is already mapped at VMEMMAP VA by vmm_init. + * Populates buddy lists from memblock's free regions, then retires memblock. + * Panics on failure. + */ +void pmm_init(); + +/* --- Single-page fast path (uses PCP) --- */ + +// Allocate one 4KB page. Returns nullptr on OOM. +Page* pmm_alloc_page(); + +// Free one 4KB page. +void pmm_free_page(Page* page); + +/* --- Multi-page slow path (direct buddy) --- */ + +// Allocate 2^order contiguous pages. Returns nullptr on OOM. +Page* pmm_alloc_pages(uint8 order); + +// Free 2^order contiguous pages. +void pmm_free_pages(Page* page, uint8 order); + +/* --- Reference counting (atomic, lock-free) --- */ + +void page_incref(Page* page); +void page_decref(Page* page); + +/* --- Map counting (atomic, lock-free) --- */ + +inline void page_inc_mapcount(Page* page) { + page->mapcount.fetch_add(1); +} + +inline uint32 page_dec_mapcount(Page* page) { + return page->mapcount.fetch_sub(1) - 1; // Returns new value +} + +inline uint32 page_mapcount(const Page* page) { + return page->mapcount.load(__ATOMIC_RELAXED); +} + +/* --- Address conversion --- */ + +// Convert a Page* to its physical address. +PhysAddr page_to_pa(const Page* page); + +// Convert a physical address to its Page*. +Page* pa_to_page(PhysAddr pa); + +// Convert a Page* to a kernel virtual address. +VirtAddr page_to_va(const Page* page); + +/* --- PFN (Page Frame Number) abstraction --- */ +/* + * PFN is the fundamental unit for PA↔Page conversion. + * PA↔PFN converters already exist in addr.h (pa_to_pfn / pfn_to_pa). + * When sparse vmemmap lands, only pfn_to_page/page_to_pfn + * need to change — no callers are affected. + */ + +// Convert PFN to Page*. PFN = pa / PAGE_SIZE. +Page* pfn_to_page(uint64 pfn); + +// Convert Page* to PFN. +uint64 page_to_pfn(const Page* page); + +// Query PMM memory range (for VMM kernel mapping) +PhysAddr pmm_get_mem_base(); +uint64 pmm_get_mem_size(); diff --git a/include/lume/poll.h b/include/lume/poll.h new file mode 100644 index 0000000..7d5bca2 --- /dev/null +++ b/include/lume/poll.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * poll.h — POSIX poll() / ppoll() infrastructure. + * + * Linux pattern (include/linux/poll.h): + * - file_operations->poll(file, poll_table) returns a bitmask of + * POLL* events that are currently true on this file. + * - As a side effect, the poll callback calls poll_wait(file, wq, pt) + * to register the caller on every wait queue that might fire when + * state changes. sys_ppoll() then sleeps on those queues until + * one of them fires or a signal/timeout arrives. + * + * Our minimal implementation supports a SINGLE wait queue per poll call + * (sufficient for nfds==1, which is the BusyBox interactive shell case). + * Multi-fd polls work but fall back to busy re-checking; a proper + * per-fd waiter-list upgrade can come later. + */ + +#include <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/include/lume/ptrace.h b/include/lume/ptrace.h new file mode 100644 index 0000000..687d103 --- /dev/null +++ b/include/lume/ptrace.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * PtRegs — Linux-compatible user register context + * + * This structure MUST match the Linux RISC-V pt_regs layout exactly. + * It is the canonical representation for: + * - Signal frame construction (ucontext / sigcontext) + * - ptrace(GETREGS/SETREGS) + * - /proc/<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/include/lume/random.h b/include/lume/random.h new file mode 100644 index 0000000..740b060 --- /dev/null +++ b/include/lume/random.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include <lume/types.h> + +enum class RandomSeedSource : uint8 { + Unknown = 0, + FdtProvided, + HwRandom, + CycleFallback, +}; + +/* + * MI: Machine-Independent Random Number Generator API + * + * Provides early PRNG and eventually a full CSPRNG. + */ + +/* + * Initialize early PRNG with a seed (e.g., from FDT). + * If seed is 0, attempts to use hardware TRNG or falls back to timer jitter. + */ +void random_early_init(uint64 seed); + +/* Consume random bits */ +uint64 get_random_u64(); +uint32 get_random_u32(); +void get_random_bytes(void* buf, size_t len); + +/* Inject entropy (for future interrupt/device mixing) */ +void add_device_randomness(const void* buf, size_t len); + +/* Early-seed metadata (for boot diagnostics/KASLR logs). */ +RandomSeedSource random_early_seed_source(); +uint64 random_early_seed_value(); +const char* random_seed_source_name(RandomSeedSource source); diff --git a/include/lume/rbtree.h b/include/lume/rbtree.h new file mode 100644 index 0000000..80b0c6c --- /dev/null +++ b/include/lume/rbtree.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Trees — public API + * Adapted from Linux kernel include/linux/rbtree.h + * + * (C) 1999 Andrea Arcangeli <andrea@suse.de> + */ + +#pragma once + +#include <lume/compiler.h> +#include <lume/rbtree_types.h> +#include <lume/container_of.h> + +/* ------------------------------------------------------------------ */ +/* Core macros */ +/* ------------------------------------------------------------------ */ + +#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3UL)) + +#define rb_entry(ptr, type, member) container_of(ptr, type, member) + +#define RB_EMPTY_ROOT(root) (READ_ONCE((root)->rb_node) == nullptr) + +#define RB_EMPTY_NODE(node) \ + ((node)->__rb_parent_color == (unsigned long)(node)) +#define RB_CLEAR_NODE(node) \ + ((node)->__rb_parent_color = (unsigned long)(node)) + +/* ------------------------------------------------------------------ */ +/* Core API (implemented in lib/rbtree.cc) */ +/* ------------------------------------------------------------------ */ + +#ifdef __cplusplus +extern "C" { +#endif + +void rb_insert_color(struct rb_node *, struct rb_root *); +void rb_erase(struct rb_node *, struct rb_root *); + +struct rb_node *rb_next(const struct rb_node *); +struct rb_node *rb_prev(const struct rb_node *); +struct rb_node *rb_first(const struct rb_root *); +struct rb_node *rb_last(const struct rb_root *); + +struct rb_node *rb_first_postorder(const struct rb_root *); +struct rb_node *rb_next_postorder(const struct rb_node *); + +void rb_replace_node(struct rb_node *victim, struct rb_node *_new, + struct rb_root *root); + +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------ */ +/* Inline helpers */ +/* ------------------------------------------------------------------ */ + +static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, + struct rb_node **rb_link) +{ + node->__rb_parent_color = (unsigned long)parent; + node->rb_left = node->rb_right = nullptr; + *rb_link = node; +} + +#define rb_entry_safe(ptr, type, member) \ + ({ __typeof__(ptr) ____ptr = (ptr); \ + ____ptr ? rb_entry(____ptr, type, member) : nullptr; \ + }) + +/* ------------------------------------------------------------------ */ +/* Cached (leftmost) tree helpers */ +/* ------------------------------------------------------------------ */ + +#define rb_first_cached(root) (root)->rb_leftmost + +static inline void rb_insert_color_cached(struct rb_node *node, + struct rb_root_cached *root, + bool leftmost) +{ + if (leftmost) + root->rb_leftmost = node; + rb_insert_color(node, &root->rb_root); +} + +static inline void rb_erase_cached(struct rb_node *node, + struct rb_root_cached *root) +{ + if (root->rb_leftmost == node) + root->rb_leftmost = rb_next(node); + rb_erase(node, &root->rb_root); +} + +static inline void rb_replace_node_cached(struct rb_node *victim, + struct rb_node *_new, + struct rb_root_cached *root) +{ + if (root->rb_leftmost == victim) + root->rb_leftmost = _new; + rb_replace_node(victim, _new, &root->rb_root); +} + +static inline void rb_erase_init(struct rb_node *n, struct rb_root *root) +{ + rb_erase(n, root); + RB_CLEAR_NODE(n); +} + +/* ------------------------------------------------------------------ */ +/* Generic insert / find helpers (inline, using function pointers) */ +/* ------------------------------------------------------------------ */ + +static inline void +rb_add(struct rb_node *node, struct rb_root *tree, + bool (*less)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_node; + struct rb_node *parent = nullptr; + + while (*link) { + parent = *link; + if (less(node, parent)) + link = &parent->rb_left; + else + link = &parent->rb_right; + } + + rb_link_node(node, parent, link); + rb_insert_color(node, tree); +} + +static inline void +rb_add_cached(struct rb_node *node, struct rb_root_cached *tree, + bool (*less)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_root.rb_node; + struct rb_node *parent = nullptr; + bool leftmost = true; + + while (*link) { + parent = *link; + if (less(node, parent)) { + link = &parent->rb_left; + } else { + link = &parent->rb_right; + leftmost = false; + } + } + + rb_link_node(node, parent, link); + rb_insert_color_cached(node, tree, leftmost); +} + +static inline struct rb_node * +rb_find(const void *key, const struct rb_root *tree, + int (*cmp)(const void *key, const struct rb_node *)) +{ + struct rb_node *node = tree->rb_node; + + while (node) { + int c = cmp(key, node); + if (c < 0) + node = node->rb_left; + else if (c > 0) + node = node->rb_right; + else + return node; + } + return nullptr; +} + +static inline struct rb_node * +rb_find_add(struct rb_node *node, struct rb_root *tree, + int (*cmp)(struct rb_node *, const struct rb_node *)) +{ + struct rb_node **link = &tree->rb_node; + struct rb_node *parent = nullptr; + int c; + + while (*link) { + parent = *link; + c = cmp(node, parent); + if (c < 0) + link = &parent->rb_left; + else if (c > 0) + link = &parent->rb_right; + else + return parent; + } + + rb_link_node(node, parent, link); + rb_insert_color(node, tree); + return nullptr; +} + +/* ------------------------------------------------------------------ */ +/* Iteration macros */ +/* ------------------------------------------------------------------ */ + +#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ + for (pos = rb_entry_safe(rb_first_postorder(root), __typeof__(*pos), field); \ + pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ + __typeof__(*pos), field); 1; }); \ + pos = n) + diff --git a/include/lume/rbtree_augmented.h b/include/lume/rbtree_augmented.h new file mode 100644 index 0000000..d4559c4 --- /dev/null +++ b/include/lume/rbtree_augmented.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Trees — augmented operations & internal helpers + * Adapted from Linux kernel include/linux/rbtree_augmented.h + * + * (C) 1999 Andrea Arcangeli <andrea@suse.de> + * (C) 2002 David Woodhouse <dwmw2@infradead.org> + * (C) 2012 Michel Lespinasse <walken@google.com> + */ + +#pragma once + +#include <lume/rbtree.h> + +/* ------------------------------------------------------------------ */ +/* Augmented callback structure */ +/* ------------------------------------------------------------------ */ + +struct rb_augment_callbacks { + void (*propagate)(struct rb_node *node, struct rb_node *stop); + void (*copy)(struct rb_node *old, struct rb_node *_new); + void (*rotate)(struct rb_node *old, struct rb_node *_new); +}; + +extern "C" void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); + +extern "C" void __rb_erase_color(struct rb_node *parent, struct rb_root *root, + void (*augment_rotate)(struct rb_node *old, struct rb_node *_new)); + +static inline void +rb_insert_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + __rb_insert_augmented(node, root, augment->rotate); +} + +static inline void +rb_insert_augmented_cached(struct rb_node *node, + struct rb_root_cached *root, bool newleft, + const struct rb_augment_callbacks *augment) +{ + if (newleft) + root->rb_leftmost = node; + rb_insert_augmented(node, &root->rb_root, augment); +} + +/* ------------------------------------------------------------------ */ +/* Color / parent encoding helpers */ +/* ------------------------------------------------------------------ */ + +#define RB_RED 0 +#define RB_BLACK 1 + +#define __rb_parent(pc) ((struct rb_node *)(pc & ~3UL)) + +#define __rb_color(pc) ((pc) & 1) +#define __rb_is_black(pc) __rb_color(pc) +#define __rb_is_red(pc) (!__rb_color(pc)) +#define rb_color(rb) __rb_color((rb)->__rb_parent_color) +#define rb_is_red(rb) __rb_is_red((rb)->__rb_parent_color) +#define rb_is_black(rb) __rb_is_black((rb)->__rb_parent_color) + +static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) +{ + rb->__rb_parent_color = rb_color(rb) + (unsigned long)p; +} + +static inline void rb_set_parent_color(struct rb_node *rb, + struct rb_node *p, int color) +{ + rb->__rb_parent_color = (unsigned long)p + color; +} + +static inline void +__rb_change_child(struct rb_node *old, struct rb_node *_new, + struct rb_node *parent, struct rb_root *root) +{ + if (parent) { + if (parent->rb_left == old) + WRITE_ONCE(parent->rb_left, _new); + else + WRITE_ONCE(parent->rb_right, _new); + } else + WRITE_ONCE(root->rb_node, _new); +} + +/* ------------------------------------------------------------------ */ +/* Augmented erase (inline) */ +/* ------------------------------------------------------------------ */ + +static __always_inline struct rb_node * +__rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *child = node->rb_right; + struct rb_node *tmp = node->rb_left; + struct rb_node *parent, *rebalance; + unsigned long pc; + + if (!tmp) { + /* + * Case 1: node to erase has no more than 1 child (easy!) + */ + pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, child, parent, root); + if (child) { + child->__rb_parent_color = pc; + rebalance = nullptr; + } else + rebalance = __rb_is_black(pc) ? parent : nullptr; + tmp = parent; + } else if (!child) { + /* Still case 1, but this time the child is node->rb_left */ + tmp->__rb_parent_color = pc = node->__rb_parent_color; + parent = __rb_parent(pc); + __rb_change_child(node, tmp, parent, root); + rebalance = nullptr; + tmp = parent; + } else { + struct rb_node *successor = child, *child2; + + tmp = child->rb_left; + if (!tmp) { + /* + * Case 2: node's successor is its right child + */ + parent = successor; + child2 = successor->rb_right; + + augment->copy(node, successor); + } else { + /* + * Case 3: node's successor is leftmost under + * node's right child subtree + */ + do { + parent = successor; + successor = tmp; + tmp = tmp->rb_left; + } while (tmp); + child2 = successor->rb_right; + WRITE_ONCE(parent->rb_left, child2); + WRITE_ONCE(successor->rb_right, child); + rb_set_parent(child, successor); + + augment->copy(node, successor); + augment->propagate(parent, successor); + } + + tmp = node->rb_left; + WRITE_ONCE(successor->rb_left, tmp); + rb_set_parent(tmp, successor); + + pc = node->__rb_parent_color; + tmp = __rb_parent(pc); + __rb_change_child(node, successor, tmp, root); + + if (child2) { + rb_set_parent_color(child2, parent, RB_BLACK); + rebalance = nullptr; + } else { + rebalance = rb_is_black(successor) ? parent : nullptr; + } + successor->__rb_parent_color = pc; + tmp = successor; + } + + augment->propagate(tmp, nullptr); + return rebalance; +} + +static __always_inline void +rb_erase_augmented(struct rb_node *node, struct rb_root *root, + const struct rb_augment_callbacks *augment) +{ + struct rb_node *rebalance = __rb_erase_augmented(node, root, augment); + if (rebalance) + __rb_erase_color(rebalance, root, augment->rotate); +} + +static __always_inline void +rb_erase_augmented_cached(struct rb_node *node, struct rb_root_cached *root, + const struct rb_augment_callbacks *augment) +{ + if (root->rb_leftmost == node) + root->rb_leftmost = rb_next(node); + rb_erase_augmented(node, &root->rb_root, augment); +} + +/* ------------------------------------------------------------------ */ +/* Augmented callback declaration macros */ +/* ------------------------------------------------------------------ */ + +#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ +static inline void \ +RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ +{ \ + while (rb != stop) { \ + RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ + if (RBCOMPUTE(node, true)) \ + break; \ + rb = rb_parent(&node->RBFIELD); \ + } \ +} \ +static inline void \ +RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ + RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ + new_->RBAUGMENTED = old_->RBAUGMENTED; \ +} \ +static void \ +RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ +{ \ + RBSTRUCT *old_ = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ + RBSTRUCT *new_ = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ + new_->RBAUGMENTED = old_->RBAUGMENTED; \ + RBCOMPUTE(old_, false); \ +} \ +RBSTATIC const struct rb_augment_callbacks RBNAME = { \ + RBNAME ## _propagate, \ + RBNAME ## _copy, \ + RBNAME ## _rotate \ +}; + +#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ + RBTYPE, RBAUGMENTED, RBCOMPUTE) \ +static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit_early) \ +{ \ + RBSTRUCT *child; \ + RBTYPE max = RBCOMPUTE(node); \ + if (node->RBFIELD.rb_left) { \ + child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD); \ + if (child->RBAUGMENTED > max) \ + max = child->RBAUGMENTED; \ + } \ + if (node->RBFIELD.rb_right) { \ + child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD); \ + if (child->RBAUGMENTED > max) \ + max = child->RBAUGMENTED; \ + } \ + if (exit_early && node->RBAUGMENTED == max) \ + return true; \ + node->RBAUGMENTED = max; \ + return false; \ +} \ +RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max) + diff --git a/include/lume/rbtree_types.h b/include/lume/rbtree_types.h new file mode 100644 index 0000000..688ade4 --- /dev/null +++ b/include/lume/rbtree_types.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Red-Black Tree type definitions + * Adapted from Linux kernel include/linux/rbtree_types.h + */ + +#pragma once + +#include <lume/types.h> + +struct rb_node { + unsigned long __rb_parent_color; + struct rb_node *rb_right; + struct rb_node *rb_left; +} __attribute__((aligned(sizeof(long)))); + +struct rb_root { + struct rb_node *rb_node; +}; + +struct rb_root_cached { + struct rb_root rb_root; + struct rb_node *rb_leftmost; +}; + +#define RB_ROOT (struct rb_root) { nullptr, } +#define RB_ROOT_CACHED (struct rb_root_cached) { {nullptr, }, nullptr } + diff --git a/include/lume/sched.h b/include/lume/sched.h new file mode 100644 index 0000000..ab26bac --- /dev/null +++ b/include/lume/sched.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Scheduler Public Interface + * + * Linux-style sched_class core with per-CPU runqueues. + * Phase 2: O(1) bitmap class is the default scheduling class. + * + * Reference: docs/specs/scheduler.md + */ + +#include <lume/types.h> +#include <lume/config.h> + +struct TaskControlBlock; + +/* ======================================================================== + * Scheduler Constants — driven by CONFIG_xxx (see scripts/Kconfig.mk) + * ======================================================================== */ + +inline constexpr int kNumPriorities = CONFIG_NR_PRIORITIES; +inline constexpr int kDefaultPriority = CONFIG_DEFAULT_PRIORITY; +inline constexpr int kDefaultTimeSlice = CONFIG_DEFAULT_TIMESLICE; +inline constexpr int kIdlePriority = CONFIG_IDLE_PRIORITY; + +/* ======================================================================== + * Initialization + * ======================================================================== */ + +/* BSP: initialize all RunQueues, create BSP idle thread and boot TCB. */ +void sched_init(); + +/* AP: create per-CPU idle thread and boot TCB. */ +void sched_init_ap(); + +/* ======================================================================== + * Core Scheduling + * ======================================================================== */ + +/* Main scheduling entry point. Picks the next task and context-switches. + * Called voluntarily (yield, sleep, exit) or from trap return path. */ +void schedule(); + +/* Timer tick handler. Delegates to current task's sched_class. + * Called from MI trap layer in interrupt context. */ +void sched_tick(); + +/* Check if rescheduling is needed (called before returning to user mode). */ +void sched_check_preempt(); + +/* Voluntarily yield the current time slice. */ +void sched_yield(); + +/* ======================================================================== + * Task Queue Management (used by task.cc, waitqueue.cc) + * ======================================================================== */ + +/* Make a task runnable: add it to its CPU's RunQueue. */ +void sched_enqueue(TaskControlBlock* task); + +/* Mark a task as sleeping: remove it from the RunQueue. */ +void sched_dequeue(TaskControlBlock* task); + +/* ======================================================================== + * Time Slice Policy + * ======================================================================== */ + +/* Convert priority level to time slice length. + * Higher priority (lower number) → longer time slice. */ +int32 priority_to_timeslice(uint8 priority); + +/* ======================================================================== + * Task Migration & Load Balancing + * ======================================================================== */ + +/* + * sched_migrate_task — Move a task from its current CPU to target_cpu. + * + * The task must NOT be the currently running task. It is dequeued from + * its old runqueue and enqueued onto the target. Both runqueue locks + * are acquired in cpu_id order to prevent deadlock. + * + * Returns 0 on success, -EINVAL if task is current or target is invalid. + */ +int sched_migrate_task(TaskControlBlock* task, uint8 target_cpu); + +/* + * sched_balance — Periodic load balancer. + * + * Called from sched_tick(). Checks if any CPU has significantly more + * runnable tasks and pulls work to the local runqueue. This is a + * simplified version of Linux's load_balance(). + */ +void sched_balance(); + +/* + * sched_nr_running — Get the number of runnable tasks on a CPU. + */ +uint32 sched_nr_running(uint32 cpu_id); diff --git a/include/lume/sched_class.h b/include/lume/sched_class.h new file mode 100644 index 0000000..08686c1 --- /dev/null +++ b/include/lume/sched_class.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Scheduler Class Interface (Linux-style) + * + * Each scheduling algorithm implements a sched_class and is chained + * in priority order. The scheduler core walks the chain to select + * the next runnable task. + */ + +#include <lume/types.h> + +struct RunQueue; +struct TaskControlBlock; + +struct SchedClass { + const char* name; + const SchedClass* next; // Linked list order: higher priority first + + void (*init_rq)(RunQueue* rq); + + void (*enqueue_task)(RunQueue* rq, TaskControlBlock* task); + void (*dequeue_task)(RunQueue* rq, TaskControlBlock* task); + TaskControlBlock* (*pick_next_task)(RunQueue* rq); + void (*put_prev_task)(RunQueue* rq, TaskControlBlock* task); + + void (*task_tick)(RunQueue* rq, TaskControlBlock* curr); + void (*yield_task)(RunQueue* rq, TaskControlBlock* curr); +}; + +extern const SchedClass g_sched_class_o1; +extern const SchedClass g_sched_class_idle; diff --git a/include/lume/selftest.h b/include/lume/selftest.h new file mode 100644 index 0000000..7040986 --- /dev/null +++ b/include/lume/selftest.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Boot Self-Test Framework + * + * Minimal assertion-based test harness that runs during kernel_main() + * after all subsystems are initialized. Each test is a void function + * that calls ST_ASSERT / ST_ASSERT_EQ macros. On failure, prints + * the failing expression and panics. + * + * Usage: + * void selftest_pmm(); // defined in test/test_pmm.cc + * void selftest_slab(); // defined in test/test_slab.cc + * + * All self-tests run only on BSP in single-core mode. + */ + +#include <lume/types.h> +#include <lume/klog.h> +#include <lume/kprintf.h> + +#define ST_ASSERT(cond) \ + do { \ + if (!(cond)) { \ + kprintf("[FAIL] %s:%d: assertion failed: %s\n", \ + __FILE__, __LINE__, #cond); \ + kernel_panic("selftest failed", #cond); \ + } \ + } while (0) + +#define ST_ASSERT_EQ(a, b) \ + do { \ + auto _a = (a); auto _b = (b); \ + if (_a != _b) { \ + kprintf("[FAIL] %s:%d: %s != %s (0x%llx != 0x%llx)\n", \ + __FILE__, __LINE__, #a, #b, \ + (uint64)(uintptr)(_a), (uint64)(uintptr)(_b)); \ + kernel_panic("selftest failed", #a " != " #b); \ + } \ + } while (0) + +#define ST_ASSERT_NE(a, b) \ + do { \ + auto _a = (a); auto _b = (b); \ + if (_a == _b) { \ + kprintf("[FAIL] %s:%d: %s == %s (both 0x%llx)\n", \ + __FILE__, __LINE__, #a, #b, \ + (uint64)(uintptr)(_a)); \ + kernel_panic("selftest failed", #a " == " #b); \ + } \ + } while (0) + +/* Announce a test suite beginning/end */ +inline void st_begin(const char* name) +{ + kprintf("[test] %s ... ", name); +} + +inline void st_pass() +{ + kprintf("PASS\n"); +} diff --git a/include/lume/setup.h b/include/lume/setup.h new file mode 100644 index 0000000..f8e6cb6 --- /dev/null +++ b/include/lume/setup.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_SETUP_H +#define _LUME_SETUP_H + +/* + * Boot-time interfaces called between assembly and C. + * + * Mirrors the subset of Linux include/linux/setup.h relevant to LumeOS's + * Linux-aligned RISC-V boot flow: + * + * setup_vm Build early_pg_dir / trampoline_pg_dir before + * the MMU is enabled. Filled kernel_map for + * all subsequent code to consult. + * relocate_enable_mmu Switch SATP, jump to higher-half, apply + * .rela.dyn via relocate_kernel. + * relocate_kernel Walk .rela.dyn, patch R_RISCV_RELATIVE + * slots with addend + kaslr_offset. + * + * kernel_main (LumeOS's equivalent of Linux's start_kernel) is declared + * in kernel/main.cc and re-declared in arch-side glue — kept local to + * that file to avoid creating an artificial public API for the lone + * call site in head.S. + */ + +#include <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/include/lume/signal.h b/include/lume/signal.h new file mode 100644 index 0000000..05b7542 --- /dev/null +++ b/include/lume/signal.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Signal Infrastructure — Core signal delivery API + * + * Provides the interface between trap handling, signal delivery, + * and user-space signal handler execution. + * + * Reference: Linux include/linux/sched/signal.h + */ + +#include <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/include/lume/signal_nr.h b/include/lume/signal_nr.h new file mode 100644 index 0000000..8e595d7 --- /dev/null +++ b/include/lume/signal_nr.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Linux-compatible signal numbers and related ABI constants. + * + * This mirrors the common Linux UAPI numbering used by asm-generic/signal.h. + * Keep the values stable so user-space binaries see the same numbers. + */ + +inline constexpr int _NSIG = 64; +inline constexpr int _NSIG_BPW = sizeof(unsigned long) * 8; +inline constexpr int _NSIG_WORDS = _NSIG / _NSIG_BPW; + +inline constexpr int SIGHUP = 1; +inline constexpr int SIGINT = 2; +inline constexpr int SIGQUIT = 3; +inline constexpr int SIGILL = 4; +inline constexpr int SIGTRAP = 5; +inline constexpr int SIGABRT = 6; +inline constexpr int SIGIOT = SIGABRT; +inline constexpr int SIGBUS = 7; +inline constexpr int SIGFPE = 8; +inline constexpr int SIGKILL = 9; +inline constexpr int SIGUSR1 = 10; +inline constexpr int SIGSEGV = 11; +inline constexpr int SIGUSR2 = 12; +inline constexpr int SIGPIPE = 13; +inline constexpr int SIGALRM = 14; +inline constexpr int SIGTERM = 15; +inline constexpr int SIGSTKFLT = 16; +inline constexpr int SIGCHLD = 17; +inline constexpr int SIGCONT = 18; +inline constexpr int SIGSTOP = 19; +inline constexpr int SIGTSTP = 20; +inline constexpr int SIGTTIN = 21; +inline constexpr int SIGTTOU = 22; +inline constexpr int SIGURG = 23; +inline constexpr int SIGXCPU = 24; +inline constexpr int SIGXFSZ = 25; +inline constexpr int SIGVTALRM = 26; +inline constexpr int SIGPROF = 27; +inline constexpr int SIGWINCH = 28; +inline constexpr int SIGIO = 29; +inline constexpr int SIGPOLL = SIGIO; +inline constexpr int SIGPWR = 30; +inline constexpr int SIGSYS = 31; +inline constexpr int SIGUNUSED = 31; + +/* Real-time signals start here in Linux UAPI. */ +inline constexpr int SIGRTMIN = 32; +inline constexpr int SIGRTMAX = _NSIG; diff --git a/include/lume/signal_types.h b/include/lume/signal_types.h new file mode 100644 index 0000000..f66231b --- /dev/null +++ b/include/lume/signal_types.h @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Signal Types — Linux-compatible structures for signal delivery + * + * Follows the RISC-V LP64 ABI layout so that user-space programs + * compiled with musl / glibc see the correct struct sizes. + * + * Reference: Linux include/uapi/asm-generic/signal.h + * Linux include/uapi/asm-generic/siginfo.h + */ + +#include <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/include/lume/slab.h b/include/lume/slab.h new file mode 100644 index 0000000..4ebade1 --- /dev/null +++ b/include/lume/slab.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * SLUB public API + * + * This header exposes the allocator entry points to the rest of the kernel. + * Implementation details stay in mm/slab.h and mm/slab.cc. + */ + +#include <lume/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize global slab caches. */ +void slab_init(); + +/* Allocate/free kernel memory. */ +void* kmalloc(uint32 size); +void kfree(void* ptr); + +#ifdef __cplusplus +} +#endif diff --git a/include/lume/smp.h b/include/lume/smp.h new file mode 100644 index 0000000..a5292b6 --- /dev/null +++ b/include/lume/smp.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * smp.h — Machine-Independent SMP primitives + * + * Provides cross-CPU communication (IPI) and TLB coordination. + * The MI interface calls through to arch-specific backends + * (e.g. SBI on RISC-V, APIC on x86). + * + * Reference: docs/specs/linux_abi_mm_plan.md §SMP + */ + +#include <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/include/lume/stdarg.h b/include/lume/stdarg.h new file mode 100644 index 0000000..c8dc7f4 --- /dev/null +++ b/include/lume/stdarg.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef _LINUX_STDARG_H +#define _LINUX_STDARG_H + +typedef __builtin_va_list va_list; +#define va_start(v, l) __builtin_va_start(v, l) +#define va_end(v) __builtin_va_end(v) +#define va_arg(v, T) __builtin_va_arg(v, T) +#define va_copy(d, s) __builtin_va_copy(d, s) + +#endif diff --git a/include/lume/stringify.h b/include/lume/stringify.h new file mode 100644 index 0000000..0e84cbe --- /dev/null +++ b/include/lume/stringify.h @@ -0,0 +1,14 @@ +#ifndef __LINUX_STRINGIFY_H +#define __LINUX_STRINGIFY_H + +/* Indirect stringification. Doing two levels allows the parameter to be a + * macro itself. For example, compile with -DFOO=bar, __stringify(FOO) + * converts to "bar". + */ + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#define FILE_LINE __FILE__ ":" __stringify(__LINE__) + +#endif /* !__LINUX_STRINGIFY_H */ diff --git a/include/lume/syscall.h b/include/lume/syscall.h new file mode 100644 index 0000000..8185096 --- /dev/null +++ b/include/lume/syscall.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * System Call Interface — Machine-Independent Layer + * + * Defines the unified syscall handler signature and the MI dispatch function. + * MD layer (arch/riscv/syscall/syscall_md.cc) extracts registers into args[6] + * and calls syscall_dispatch(). + * + * Reference: docs/specs/syscall.md §4 + */ + +#include <lume/types.h> + +/* Unified syscall handler signature. + * All arguments and return value are register-width (uint64). + * Individual sys_* functions cast internally. */ +using SyscallHandler = int64 (*)(uint64, uint64, uint64, + uint64, uint64, uint64); + +/* MI-layer syscall dispatcher. + * Called by MD layer after extracting syscall number and arguments. + * Returns the syscall result (negative errno on error). */ +int64 syscall_dispatch(uint64 nr, uint64 args[6]); + +/* Register all implemented syscall handlers into the jump table. + * Called from kernel_main() after sched_init(). */ +void syscall_init(); + +/* MD-layer entry point — called from trap handler on TrapCause::Syscall. + * Extracts registers from TrapFrame, calls syscall_dispatch, writes result. */ +namespace arch { +struct TrapFrame; +} +void syscall_dispatch_md(arch::TrapFrame* tf); diff --git a/include/lume/syscall_nr.h b/include/lume/syscall_nr.h new file mode 100644 index 0000000..29e3904 --- /dev/null +++ b/include/lume/syscall_nr.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * System Call Numbers — RISC-V LP64 (asm-generic/unistd.h compatible) + * + * Numbering strictly follows Linux asm-generic so that musl libc / Busybox + * binaries can be run without modification. + * + * Reference: docs/specs/syscall.md §4.3 + */ + +inline constexpr int SYS_exit = 93; +inline constexpr int SYS_exit_group = 94; +inline constexpr int SYS_set_tid_address = 96; +inline constexpr int SYS_sched_yield = 124; +inline constexpr int SYS_uname = 160; +inline constexpr int SYS_getpid = 172; +inline constexpr int SYS_getppid = 173; +inline constexpr int SYS_getuid = 174; +inline constexpr int SYS_getgid = 176; +inline constexpr int SYS_gettid = 178; +inline constexpr int SYS_brk = 214; +inline constexpr int SYS_munmap = 215; +inline constexpr int SYS_clone = 220; +inline constexpr int SYS_execve = 221; +inline constexpr int SYS_mmap = 222; +inline constexpr int SYS_mprotect = 226; +inline constexpr int SYS_wait4 = 260; + +inline constexpr int SYS_getcwd = 17; +inline constexpr int SYS_dup = 23; +inline constexpr int SYS_dup3 = 24; +inline constexpr int SYS_ioctl = 29; +inline constexpr int SYS_mkdirat = 34; +inline constexpr int SYS_unlinkat = 35; +inline constexpr int SYS_chdir = 49; +inline constexpr int SYS_openat = 56; +inline constexpr int SYS_close = 57; +inline constexpr int SYS_pipe2 = 59; +inline constexpr int SYS_lseek = 62; +inline constexpr int SYS_read = 63; +inline constexpr int SYS_write = 64; +inline constexpr int SYS_writev = 66; +inline constexpr int SYS_ppoll = 73; +inline constexpr int SYS_fstatat = 79; +inline constexpr int SYS_fstat = 80; +inline constexpr int SYS_getdents64 = 61; + +inline constexpr int SYS_fcntl = 25; +inline constexpr int SYS_linkat = 37; +inline constexpr int SYS_mount = 40; +inline constexpr int SYS_umount2 = 39; + +inline constexpr int SYS_kill = 129; +inline constexpr int SYS_tkill = 130; +inline constexpr int SYS_tgkill = 131; +inline constexpr int SYS_sigaltstack = 132; +inline constexpr int SYS_rt_sigaction = 134; +inline constexpr int SYS_rt_sigprocmask = 135; +inline constexpr int SYS_rt_sigreturn = 139; +inline constexpr int SYS_reboot = 142; +inline constexpr int SYS_setpgid = 154; +inline constexpr int SYS_getpgid = 155; +inline constexpr int SYS_getsid = 156; +inline constexpr int SYS_setsid = 157; +inline constexpr int SYS_prctl = 167; +inline constexpr int SYS_geteuid = 175; +inline constexpr int SYS_getegid = 177; + +inline constexpr int SYS_futex = 98; +inline constexpr int SYS_nanosleep = 101; +inline constexpr int SYS_clock_gettime = 113; +inline constexpr int SYS_epoll_create1 = 20; +inline constexpr int SYS_epoll_ctl = 21; +inline constexpr int SYS_epoll_pwait = 22; + +inline constexpr int SYS_rt_sigtimedwait = 137; +inline constexpr int SYS_socket = 198; +inline constexpr int SYS_sendto = 206; diff --git a/include/lume/task.h b/include/lume/task.h new file mode 100644 index 0000000..e959147 --- /dev/null +++ b/include/lume/task.h @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Task Control Block & Task State + * + * Central representation of every execution context in LumeOS, + * whether kernel thread or (future) user process. + * + * Reference: docs/specs/task.md §3.1, docs/specs/scheduler.md §3.1 + */ + +#include <lume/types.h> +#include <lume/config.h> +#include <lume/list.h> +#include <arch/context.h> +#include <arch/trapframe.h> +#include <arch/fpstate.h> +#include <lume/waitqueue.h> +#include <lume/signal_types.h> + +/* Forward declarations */ +struct VmSpace; +struct SchedClass; + +/* ======================================================================== + * Task State Machine + * ======================================================================== */ + +enum class TaskState : uint8 { + Runnable = 0, // In a Per-CPU ready queue + Running = 1, // Currently executing on a CPU + Sleeping = 2, // Blocked on a WaitQueue + Zombie = 3, // Exited, waiting for parent wait() + Dead = 4, // Fully reclaimed, TCB can be freed + Stopped = 5, // Job control: SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU +}; + +/* ======================================================================== + * Task Flags + * ======================================================================== */ + +inline constexpr uint8 TF_KTHREAD = 0x01; // Kernel thread (never enters user mode) +inline constexpr uint8 TF_IDLE = 0x02; // Idle thread +inline constexpr uint8 TF_INIT = 0x04; // init process (PID 1) + +/* ======================================================================== + * Kernel Stack + * ======================================================================== */ + +inline constexpr uint64 kKernelStackSize = CONFIG_KERNEL_STACK_SIZE; +inline constexpr int kKernelStackOrder = (kKernelStackSize <= 4096) ? 0 + : (kKernelStackSize <= 8192) ? 1 + : (kKernelStackSize <= 16384) ? 2 + : (kKernelStackSize <= 32768) ? 3 : 4; +inline constexpr uint64 kStackCanary = 0xDEAD'BEEF'CAFE'BABEULL; + +/* ======================================================================== + * Task Control Block + * ======================================================================== */ + +struct TaskControlBlock { + /* ===== Scheduler hot fields (cache-line optimized) ===== */ + TaskState state; // Current state + uint8 priority; // Static priority [0, 31] + uint8 cpu_id; // Pinned CPU (Phase 2: no migration) + uint8 flags; // TF_KTHREAD | TF_IDLE | TF_INIT + + int32 time_slice; // Remaining time slice (ticks) + int32 pid; // Unique task ID (Linux: tid) + int32 tgid; // Thread group ID (Linux: pid) + int32 preempt_count; // >0: preemption disabled (holding locks etc.) + + list_node run_link; // RunQueue list node + list_node all_link; // Global task list node + + /* ===== Context ===== */ + arch::Context context; // Callee-saved registers (scheduler switch) + arch::TrapFrame* trapframe; // Trap frame (user-mode entry/exit) + arch::FpState fpstate; // FPU register state (f0-f31 + fcsr) + + /* ===== Kernel Stack ===== */ + uint64 kstack_pa; // Physical address + uint64 kstack_va; // Virtual address (stack bottom) + + /* ===== Address Space ===== */ + VmSpace* mm; // nullptr = kernel thread + + /* ===== Per-task lock ===== + * Protects: parent, children, sibling, exit_code, state transitions. + * Lock ordering: task->lock -> g_task_list_lock (never reverse). */ + Spinlock lock; + + /* ===== Task Hierarchy ===== */ + TaskControlBlock* parent; + list_node children; // Children list head + list_node sibling; // Sibling list node + WaitQueue wait_chld; // Parent waits on this for children to exit + + /* ===== Exit ===== */ + int32 exit_code; + int32 exit_signal; // Linux: signal sent to parent on exit. + // SIGCHLD = notify parent, become zombie + // -1 = auto-reap (kthreads) + uint64 clear_child_tid; // CLONE_CHILD_CLEARTID: write 0 here on exit + + /* ===== Job control reporting ===== + * stopped_code: 0 means "no pending stop notification"; non-zero is the + * Linux wstatus value to hand back from wait4(WUNTRACED). Cleared + * after the parent consumes it via wait4 (or task termination). + * notify_continued: set when SIGCONT resumes us; consumed by + * wait4(WCONTINUED). */ + int32 stopped_code; + bool notify_continued; + + /* ===== Scheduler class metadata ===== */ + const SchedClass* sched_class; + void* sched_entity; + + /* ===== VFS ===== */ + struct files_struct* files; // Per-task fd table (shared via CLONE_FILES) + struct fs_struct* fs; // cwd/root (shared via CLONE_FS) + + /* ===== Signals ===== */ + struct sigpending pending; // Per-task pending signal queue + sigset_t blocked; // Blocked signal mask + struct sighand_struct* sighand; // Signal handler table (shared via CLONE_SIGHAND) + struct signal_struct* signal; // Thread-group identity: pgid/sid/ctty (shared via CLONE_THREAD) + + /* ===== Debug ===== */ + const char* name; +}; + +/* ======================================================================== + * current_task() — fast access to the running task on this CPU + * + * Must be called with interrupts disabled or while holding a spinlock. + * ======================================================================== */ +TaskControlBlock* current_task(); + +/* Terminate the current task with a signal. */ +void task_kill_current(int sig); diff --git a/include/lume/timer.h b/include/lume/timer.h new file mode 100644 index 0000000..9bdddd5 --- /dev/null +++ b/include/lume/timer.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +#include <lume/types.h> + +/* + * MI Timer Subsystem + */ + +#include <lume/list.h> + +struct TimerList { + list_node entry; + uint64 expires; + void (*function)(TimerList*); + void* data; + + void init(void (*cb)(TimerList*)) { + entry.init(); + function = cb; + data = nullptr; + expires = 0; + } +}; + +extern uint64 g_jiffies; + +void timer_init(); + +// AP per-CPU initialization +void timer_init_ap(); + +// Handle timer interrupt tick (called from TrapHandler) +void timer_tick(); + +// Get configured timebase frequency +uint64 timer_get_freq(); + +void add_timer(TimerList* timer); +int del_timer(TimerList* timer); +int mod_timer(TimerList* timer, uint64 expires); diff --git a/include/lume/tlb.h b/include/lume/tlb.h new file mode 100644 index 0000000..b4d51ea --- /dev/null +++ b/include/lume/tlb.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tlb.h — Machine-Independent TLB management interface + * + * All TLB invalidation in the kernel MUST go through these functions. + * Direct use of arch::mmu::flush_tlb_*() is prohibited outside of + * this file's implementation. + * + * Current implementation: local CPU sfence.vma only (single-core). + * When SMP IPI infrastructure is available, tlb_shootdown_range() + * and tlb_shootdown_all() will broadcast invalidation to remote + * CPUs via IPI — no caller changes required. + * + * Reference: docs/specs/linux_abi_mm_plan.md §4 + */ + +#include <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/include/lume/trap.h b/include/lume/trap.h new file mode 100644 index 0000000..61a5054 --- /dev/null +++ b/include/lume/trap.h @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * Trap Subsystem — Machine-Independent Interface + * + * Defines TrapCause enum and MI-layer declarations. + * MD layer (arch/riscv/trap/trap_md.cc) translates scause → TrapCause. + * MI layer (kernel/trap.cc) dispatches based on TrapCause alone. + * + * Reference: docs/specs/trap.md §3.2 + */ + +#include <lume/types.h> + +/* MI-layer trap cause classification. + * MD layer is responsible for translating architecture-specific cause codes + * (e.g., RISC-V scause) into these semantic values. MI layer never reads CSRs. */ +enum class TrapCause : uint32 { + /* --- Exceptions (Synchronous) --- */ + PageFaultLoad = 0, // Load page fault + PageFaultStore = 1, // Store/AMO page fault + PageFaultExec = 2, // Instruction page fault + Syscall = 3, // User-mode ecall + IllegalInst = 4, // Illegal instruction + Breakpoint = 5, // Breakpoint (ebreak) + AccessFault = 6, // Bus access fault (not page fault) + AlignFault = 7, // Address misalignment + + /* --- Interrupts (Asynchronous) --- */ + IrqTimer = 16, // S-mode timer interrupt + IrqSoftware = 17, // S-mode software interrupt (IPI) + IrqExternal = 18, // S-mode external interrupt (PLIC) + + /* --- Unknown --- */ + Unknown = 255, +}; + +/* High-level trap class for routing and stats. */ +enum class TrapClass : uint8 { + Exception = 0, + Interrupt = 1, + Syscall = 2, + Unknown = 3, +}; + +/* Origin privilege of the trap. */ +enum class TrapOrigin : uint8 { + FromUser = 0, + FromKernel = 1, +}; + +/* Unified trap context passed from MD to MI. */ +struct TrapInfo { + TrapClass cls; + TrapCause cause; + TrapOrigin origin; + uint64 val; // stval (faulting address or instruction) + uint64 epc; // sepc at trap entry (may be adjusted by MD) + uint64 scause; // raw scause for diagnostics +}; + +/* Trap statistics for debugging/testing. */ +inline constexpr uint32 kTrapCauseCount = 12; + +struct TrapStats { + uint64 total; + uint64 by_cause[kTrapCauseCount]; + TrapCause last_cause; + TrapClass last_class; + TrapOrigin last_origin; + uint64 last_val; + uint64 last_epc; + uint64 last_scause; +}; + +namespace arch { +struct TrapFrame; // Forward declaration (defined in arch/trapframe.h) +} + +/* + * trap_init() — BSP initialization + * Sets stvec to kernel_trap_entry (handles traps while in S-mode). + * Initializes the global IrqManager. + * Called from kernel_main() after VMM and before PLIC. + */ +void trap_init(); + +/* + * trap_init_ap() — AP per-CPU initialization + * Sets this CPU's stvec. + */ +void trap_init_ap(); + +/* + * trap_handle() — MI-layer semantic dispatcher + * Called by MD layer after scause → TrapCause translation. + * Routes to page fault handler, syscall dispatcher, IRQ manager, etc. + * + * tf: pointer to the current TrapFrame + * info: translated trap context from MD layer + */ +void trap_handle(arch::TrapFrame* tf, const TrapInfo& info); + +/* Trap statistics helpers (MI layer). */ +void trap_stats_get(TrapStats* out); +void trap_stats_reset(); diff --git a/include/lume/tty.h b/include/lume/tty.h new file mode 100644 index 0000000..34cf7bb --- /dev/null +++ b/include/lume/tty.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tty.h — TTY subsystem core structures + * + * tty_struct is the central object representing an open terminal. + * It connects a hardware driver (tty_driver) with a line discipline + * (tty_ldisc) to implement the full terminal abstraction. + * + * Architecture: + * user read/write → tty_core → tty_ldisc (n_tty) → tty_driver (uart/pty) + * + * Reference: Linux include/linux/tty.h, include/linux/tty_ldisc.h + */ + +#include <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/include/lume/tty_ioctl.h b/include/lume/tty_ioctl.h new file mode 100644 index 0000000..87ba59b --- /dev/null +++ b/include/lume/tty_ioctl.h @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * tty_ioctl.h — Terminal I/O control constants and structures + * + * Linux-compatible termios, winsize, and TIOC* ioctl definitions. + * Values match the RISC-V/generic Linux ABI. + * + * Reference: include/uapi/asm-generic/termbits.h, + * include/uapi/asm-generic/ioctls.h + */ + +#include <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/include/lume/types.h b/include/lume/types.h new file mode 100644 index 0000000..2342359 --- /dev/null +++ b/include/lume/types.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#ifndef _LUME_TYPES_H +#define _LUME_TYPES_H + +/* + * LumeOS Base Type Definitions + * + * Three naming layers coexist so that source files imported from + * upstream projects (Linux kernel, libfdt) compile unchanged: + * + * LumeOS native uint8 / int8 / ... / uint64 / int64 + * Linux kernel short names u8 / s8 / ... / u64 / s64 + * POSIX / C99 (with _t) uint8_t / ... / uint64_t / intptr_t + * + * All three resolve to the same underlying compiler builtin, so they + * are binary-compatible. + * + * Declared with C `typedef` (not C++ `using`) so this header may be + * included from .c sources — required for libfdt shims and the + * arch/riscv/kernel/pi/ subsystem, which are pure C imports from + * Linux. C++ accepts `typedef` syntax identically. + */ + +/* ===== LumeOS native names ===== */ +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef signed long long int64; + +typedef __SIZE_TYPE__ size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef int64 ssize_t; +typedef uint64 uintptr; + +/* ===== Linux kernel short names ===== */ +typedef uint8 u8; typedef int8 s8; +typedef uint16 u16; typedef int16 s16; +typedef uint32 u32; typedef int32 s32; +typedef uint64 u64; typedef int64 s64; + +/* ===== POSIX / C99 aliases ===== + * Guarded so a later #include <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/include/lume/uaccess.h b/include/lume/uaccess.h new file mode 100644 index 0000000..5a9ed71 --- /dev/null +++ b/include/lume/uaccess.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * User-space memory access — safe copy routines. + * + * Phase 2: Software page-table validation. + * Phase 3+: Exception Table (zero-cost happy path). + * + * Reference: docs/specs/syscall.md §8 + */ + +#include <lume/types.h> + +/* Copy len bytes from user address to kernel buffer. + * Returns 0 on success, -EFAULT if user address is invalid. */ +int copy_from_user(void* kernel_dst, uint64 user_src, uint64 len); + +/* Copy len bytes from kernel buffer to user address. + * Returns 0 on success, -EFAULT if user address is invalid. */ +int copy_to_user(uint64 user_dst, const void* kernel_src, uint64 len); + +/* Copy NUL-terminated string from user space. + * Returns string length (excluding NUL) or -EFAULT. */ +int64 strncpy_from_user(char* kernel_dst, uint64 user_src, uint64 max_len); diff --git a/include/lume/uuid.h b/include/lume/uuid.h new file mode 100644 index 0000000..fcbd4a3 --- /dev/null +++ b/include/lume/uuid.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * UUID/GUID definition + * + * Copyright (C) 2010, 2016 Intel Corp. + * Huang Ying <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/include/lume/vmm.h b/include/lume/vmm.h new file mode 100644 index 0000000..9cbebf1 --- /dev/null +++ b/include/lume/vmm.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * vmm.h — Virtual Memory Manager + * + * Managing kernel and user virtual address spaces. + * + * Reference: docs/specs/vmm.md + */ + +#include <lume/types.h> +#include <lume/rbtree.h> +#include <lume/atomic.h> +#include <kernel/sync/spinlock.h> + +/* ------------------------------------------------------------------ */ +/* MI-layer permission flags (architecture-independent) */ +/* */ +/* These are used by VMA and vmm_map_user / vmm_handle_page_fault. */ +/* The pmap MD layer translates them to hardware PTE bits. */ +/* ------------------------------------------------------------------ */ +inline constexpr uint64 VM_READ = (1ULL << 0); +inline constexpr uint64 VM_WRITE = (1ULL << 1); +inline constexpr uint64 VM_EXEC = (1ULL << 2); +inline constexpr uint64 VM_USER = (1ULL << 3); + +enum class VmFaultCause : uint8 { + Read = 0, + Write = 1, + Exec = 2, +}; + +/* Forward declarations */ +struct VmSpace; +struct VmAreaStruct; + +/* + * Virtual Memory Area (VMA) + * Represents a contiguous range [start, end) of uniform permissions. + */ +struct VmAreaStruct { + uint64 start; // Start VA (page aligned) + uint64 end; // End VA (exclusive, page aligned) + uint64 perm; // VM_READ | VM_WRITE | VM_EXEC | VM_USER + struct rb_node rb; // Linkage in VmSpace::vma_tree + VmSpace* mm; // Back pointer to the parent address space +}; + +/* + * Address Space (VmSpace) + * Represents a user process's complete virtual memory mapping. + * + * Lock contract: + * Holds: lock (internal spinlock) + * May acquire: PMM global lock (via pmm_alloc_page inside page fault) + * Callers must NOT hold: PMM lock (lock ordering: VmSpace -> PMM) + */ +struct VmSpace { + uint64 root_pa; // SV39 root page table physical address + struct rb_root vma_tree; // Red-black tree of VmAreaStructs + Spinlock lock; // Protects vma_tree mutations + lume::atomic<int32> refcount; // Reference count (for CLONE_VM sharing) + lume::atomic<uint64> cpu_mask; // Bitmask of CPUs with this mm in satp + + uint64 brk_start; // Start of heap (page-aligned) + uint64 brk_end; // Current heap end (page-aligned) + uint64 mmap_base; // Base for mmap (Linux: TASK_UNMAPPED_BASE = TASK_SIZE/3) + + // Create a brand new VmSpace (allocates root pagetable, refcount=1) + static VmSpace* create(); + + // Increment reference count (for CLONE_VM) + void incref(); + + // Decrement reference count; destroys VmSpace when it reaches 0. + // Call this instead of destroy() directly. + void put(); + + // Destroy VmSpace, unmapping all VMAs and freeing pagetables. + // Internal — called by put() when refcount drops to 0. + void destroy(); +}; + +/* Initialize kernel page table (Phase 1) */ +void vmm_init(); + +/* Switch AP to the kernel page table built by BSP (Phase 1) */ +void vmm_init_ap(); + +/* Map MMIO device region to the active kernel page table */ +void vmm_map_kernel_mmio(uint64 pa, uint64 size); + +/* Query page_map allocation (set by vmm_init, read by pmm_init). + * Returns the PA, byte size, and page count of the vmemmap region. */ +uint64 vmm_get_page_map_pa(); +uint64 vmm_get_page_map_size(); +uint64 vmm_get_num_pages(); + +/* User space memory management (VMA & Demand Paging) */ +int vmm_map_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); +int vmm_unmap_user(VmSpace* space, uint64 va, uint64 len); +int vmm_protect_user(VmSpace* space, uint64 va, uint64 len, uint64 perm); + +/* VMA helpers for mmap/mprotect implementations */ +uint64 mm_find_free_va(VmSpace* space, uint64 length, uint64 align); +bool vma_range_covered(VmSpace* space, uint64 start, uint64 end); + +/* Handle user page fault (lookup VMA -> allocate page -> map) */ +int vmm_handle_page_fault(VmSpace* space, uint64 fault_addr, VmFaultCause cause); + +/* Clone an address space for fork() */ +VmSpace* vmm_clone(VmSpace* parent_space); diff --git a/include/lume/wait.h b/include/lume/wait.h new file mode 100644 index 0000000..865c1f2 --- /dev/null +++ b/include/lume/wait.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * POSIX waitpid / wait4 Constants + * Reference: docs/specs/syscall.md §9 + */ + +/* waitpid options */ +inline constexpr int WNOHANG = 1; +inline constexpr int WUNTRACED = 2; +inline constexpr int WCONTINUED = 8; +inline constexpr int WSTOPPED = WUNTRACED; /* POSIX alias */ +inline constexpr int WEXITED = 4; /* POSIX */ +inline constexpr int WNOWAIT = 0x01000000; /* POSIX: peek, don't reap */ + +/* Exit status encoding (Linux-compatible) */ +static inline int WEXITSTATUS(int s) { return (s >> 8) & 0xFF; } +static inline bool WIFEXITED(int s) { return (s & 0x7F) == 0; } +static inline bool WIFSIGNALED(int s) { return (s & 0x7F) != 0 && (s & 0x7F) != 0x7F; } +static inline int WTERMSIG(int s) { return s & 0x7F; } +static inline bool WIFSTOPPED(int s) { return (s & 0xFF) == 0x7F; } +static inline int WSTOPSIG(int s) { return (s >> 8) & 0xFF; } +static inline bool WIFCONTINUED(int s){ return s == 0xFFFF; } + +/* Encode exit code into wstatus (normal exit) */ +static inline int W_EXITCODE(int code) { return (code & 0xFF) << 8; } +static inline int W_STOPCODE(int sig) { return ((sig & 0xFF) << 8) | 0x7F; } +static inline int W_CONTINUED() { return 0xFFFF; } diff --git a/include/lume/waitqueue.h b/include/lume/waitqueue.h new file mode 100644 index 0000000..3720cb7 --- /dev/null +++ b/include/lume/waitqueue.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +#pragma once + +/* + * WaitQueue — blocking synchronization primitive + * + * Supports two usage patterns: + * + * 1. Simple (legacy): wq.sleep() — combines prepare/schedule/finish. + * Suitable for simple cases where no external condition is checked. + * + * 2. Linux-style two-phase (recommended for correctness): + * + * wq.prepare_to_wait(curr, TaskState::Sleeping); + * if (!condition) { + * schedule(); + * } + * wq.finish_wait(curr); + * + * This pattern prevents the "lost wakeup" race: the task is already + * on the wait list (and marked Sleeping) BEFORE checking the condition. + * If a wake_one/wake_all fires between the check and schedule(), the + * task's state will already be set back to Runnable, and schedule() + * will see state == Runnable and simply re-run the task. + * + * Reference: docs/specs/scheduler.md, docs/specs/task.md §5 + */ + +#include <lume/types.h> +#include <lume/list.h> +#include <kernel/sync/spinlock.h> + +struct TaskControlBlock; +enum class TaskState : uint8; + +class WaitQueue { +public: + void init(const char* name = "wq") { + head_.init(); + lock_.init(name); + } + + /* + * Phase 1: Add the task to the wait queue and set its state. + * The task is NOT yet descheduled — the caller must check the + * condition and call schedule() if the condition is still false. + * + * Must be called with interrupts disabled or preemption disabled. + */ + void prepare_to_wait(TaskControlBlock* task, TaskState new_state); + + /* + * Phase 3: Remove the task from the wait queue (if still on it) + * and ensure its state is Runnable. Called after schedule() returns + * (i.e. after the task has been woken up, or if the condition was + * true and schedule() was skipped). + */ + void finish_wait(TaskControlBlock* task); + + /* Legacy convenience: combines prepare_to_wait + schedule + finish_wait. + * If external_lock is provided, it is released AFTER the task is + * added to the wait queue but BEFORE schedule() is called. */ + void sleep(Spinlock* external_lock = nullptr); + + /* Wake the first (oldest) sleeping task. Returns true if a task was woken. */ + bool wake_one(); + + /* Wake all sleeping tasks. Returns the number of tasks woken. */ + int wake_all(); + + /* Check if any tasks are waiting. */ + bool is_empty() const { return head_.is_empty(); } + +private: + list_node head_; + Spinlock lock_; +}; diff --git a/include/ulib/assert.h b/include/ulib/assert.h deleted file mode 100644 index 014be9b..0000000 --- a/include/ulib/assert.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _ASSERT_H -#define _ASSERT_H - -#ifdef NDEBUG -#define assert(ignore) ((void)0) -#else -#ifdef __cplusplus -extern "C" -{ -#endif - void __assert_fail(const char *expr, const char *file, int line, const char *func); -#ifdef __cplusplus -} -#endif - -#define assert(expr) \ - ((expr) ? (void)0 : __assert_fail(#expr, __FILE__, __LINE__, __func__)) -#endif - -#endif
\ No newline at end of file diff --git a/include/ulib/cstdlib b/include/ulib/cstdlib deleted file mode 100644 index 8b07338..0000000 --- a/include/ulib/cstdlib +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CPP_CSTDLIB -#define _CPP_CSTDLIB - -#include "stdlib.h" - -namespace std -{ - using :: malloc; - using :: free; - using :: calloc; - using :: realloc; - - using :: exit; - using :: abort; - using :: atexit; - using :: getenv; - using :: system; - - using :: atoi; - using :: atol; - using :: atoll; - using :: atof; - - using :: strtol; - using :: strtoul; - using :: strtoll; - using :: strtoull; - - using :: strtod; - using :: strtof; - - using :: bsearch; - using :: qsort; - - using :: abs; - using :: labs; - using :: llabs; - using :: div; - using :: ldiv; - using :: lldiv; - - using :: rand; - using :: srand; - - using :: mblen; - using :: mbtowc; - using :: wctomb; - using :: mbstowcs; - using :: wcstombs; -} - -#endif
\ No newline at end of file diff --git a/include/ulib/cstring b/include/ulib/cstring deleted file mode 100644 index d65dc8d..0000000 --- a/include/ulib/cstring +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CPP_CSTRING -#define _CPP_CSTRING - -#include "string.h" - -namespace std -{ - using::memchr; - using::memcmp; - using::memcpy; - using::memmove; - using::memset; - using::strcat; - using::strchr; - using::strcmp; - using::strcpy; - using::strcspn; - using::strdup; - using::strerror; - using::strlcat; - using::strlcpy; - using::strlen; - using::strncat; - using::strncmp; - using::strpbrk; - using::strrchr; - using::strspn; - using::strstr; - using::strtok; -} // namespace std - -#endif
\ No newline at end of file diff --git a/include/ulib/ctypes.h b/include/ulib/ctypes.h deleted file mode 100644 index 1ec204e..0000000 --- a/include/ulib/ctypes.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _CTYPE_H -#define _CTYPE_H - -#ifdef __cplusplus -extern "C" -{ -#endif - - static inline int isdigit(int c) { return c >= '0' && c <= '9'; } - static inline int islower(int c) { return c >= 'a' && c <= 'z'; } - static inline int isupper(int c) { return c >= 'A' && c <= 'Z'; } - static inline int isalpha(int c) { return islower(c) || isupper(c); } - static inline int isalnum(int c) { return isalpha(c) || isdigit(c); } - static inline int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; } - static inline int isprint(int c) { return c >= 32 && c <= 126; } - static inline int iscntrl(int c) { return c < 32 || c == 127; } - static inline int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } - - static inline int tolower(int c) { return isupper(c) ? c + 32 : c; } - static inline int toupper(int c) { return islower(c) ? c - 32 : c; } - -#ifdef __cplusplus -} -#endif - -#endif
\ No newline at end of file diff --git a/include/ulib/cxx.cc b/include/ulib/cxx.cc deleted file mode 100644 index f454fea..0000000 --- a/include/ulib/cxx.cc +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#include "ulib/stdlib.h" - - -void *operator new(unsigned long n) -{ - return malloc(n); -} - -void *operator new[](unsigned long n) -{ - return malloc(n); -} - -void operator delete(void *p) -{ - free(p); -} - -void operator delete[](void *p) -{ - free(p); -} - -void operator delete(void *p, unsigned long) -{ - free(p); -} - -void operator delete[](void *p, unsigned long) -{ - free(p); -}
\ No newline at end of file diff --git a/include/ulib/errno.h b/include/ulib/errno.h deleted file mode 100644 index dd51598..0000000 --- a/include/ulib/errno.h +++ /dev/null @@ -1,170 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Lume OS - Linux Compatibility Layer - * - * This file aligns strictly with Linux RISC-V 64 ABI (asm-generic). - * The definitions are copied from the Linux kernel's UAPI headers - * (include/uapi/asm-generic/errno-base.h and include/uapi/asm-generic/errno.h) - * to ensure ABI compatibility. Original copyrights apply. - */ - -#ifndef _ERRNO_H -#define _ERRNO_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define EPERM 1 /* Operation not permitted */ -#define ENOENT 2 /* No such file or directory */ -#define ESRCH 3 /* No such process */ -#define EINTR 4 /* Interrupted system call */ -#define EIO 5 /* I/O error */ -#define ENXIO 6 /* No such device or address */ -#define E2BIG 7 /* Argument list too long */ -#define ENOEXEC 8 /* Exec format error */ -#define EBADF 9 /* Bad file number */ -#define ECHILD 10 /* No child processes */ -#define EAGAIN 11 /* Try again */ -#define ENOMEM 12 /* Out of memory */ -#define EACCES 13 /* Permission denied */ -#define EFAULT 14 /* Bad address */ -#define ENOTBLK 15 /* Block device required */ -#define EBUSY 16 /* Device or resource busy */ -#define EEXIST 17 /* File exists */ -#define EXDEV 18 /* Cross-device link */ -#define ENODEV 19 /* No such device */ -#define ENOTDIR 20 /* Not a directory */ -#define EISDIR 21 /* Is a directory */ -#define EINVAL 22 /* Invalid argument */ -#define ENFILE 23 /* File table overflow */ -#define EMFILE 24 /* Too many open files */ -#define ENOTTY 25 /* Not a typewriter */ -#define ETXTBSY 26 /* Text file busy */ -#define EFBIG 27 /* File too large */ -#define ENOSPC 28 /* No space left on device */ -#define ESPIPE 29 /* Illegal seek */ -#define EROFS 30 /* Read-only file system */ -#define EMLINK 31 /* Too many links */ -#define EPIPE 32 /* Broken pipe */ -#define EDOM 33 /* Math argument out of domain of func */ -#define ERANGE 34 /* Math result not representable */ -/* - * This error code is special: arch syscall entry code will return - * -ENOSYS if users try to call a syscall that doesn't exist. To keep - * failures of syscalls that really do exist distinguishable from - * failures due to attempts to use a nonexistent syscall, syscall - * implementations should refrain from returning -ENOSYS. - */ -#define ENOSYS 38 /* Invalid system call number */ - -#define ENOTEMPTY 39 /* Directory not empty */ -#define ELOOP 40 /* Too many symbolic links encountered */ -#define EWOULDBLOCK EAGAIN /* Operation would block */ -#define ENOMSG 42 /* No message of desired type */ -#define EIDRM 43 /* Identifier removed */ -#define ECHRNG 44 /* Channel number out of range */ -#define EL2NSYNC 45 /* Level 2 not synchronized */ -#define EL3HLT 46 /* Level 3 halted */ -#define EL3RST 47 /* Level 3 reset */ -#define ELNRNG 48 /* Link number out of range */ -#define EUNATCH 49 /* Protocol driver not attached */ -#define ENOCSI 50 /* No CSI structure available */ -#define EL2HLT 51 /* Level 2 halted */ -#define EBADE 52 /* Invalid exchange */ -#define EBADR 53 /* Invalid request descriptor */ -#define EXFULL 54 /* Exchange full */ -#define ENOANO 55 /* No anode */ -#define EBADRQC 56 /* Invalid request code */ -#define EBADSLT 57 /* Invalid slot */ - -#define EDEADLOCK EDEADLK - -#define EBFONT 59 /* Bad font file format */ -#define ENOSTR 60 /* Device not a stream */ -#define ENODATA 61 /* No data available */ -#define ETIME 62 /* Timer expired */ -#define ENOSR 63 /* Out of streams resources */ -#define ENONET 64 /* Machine is not on the network */ -#define ENOPKG 65 /* Package not installed */ -#define EREMOTE 66 /* Object is remote */ -#define ENOLINK 67 /* Link has been severed */ -#define EADV 68 /* Advertise error */ -#define ESRMNT 69 /* Srmount error */ -#define ECOMM 70 /* Communication error on send */ -#define EPROTO 71 /* Protocol error */ -#define EMULTIHOP 72 /* Multihop attempted */ -#define EDOTDOT 73 /* RFS specific error */ -#define EBADMSG 74 /* Not a data message */ -#define EOVERFLOW 75 /* Value too large for defined data type */ -#define ENOTUNIQ 76 /* Name not unique on network */ -#define EBADFD 77 /* File descriptor in bad state */ -#define EREMCHG 78 /* Remote address changed */ -#define ELIBACC 79 /* Can not access a needed shared library */ -#define ELIBBAD 80 /* Accessing a corrupted shared library */ -#define ELIBSCN 81 /* .lib section in a.out corrupted */ -#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ -#define ELIBEXEC 83 /* Cannot exec a shared library directly */ -#define EILSEQ 84 /* Illegal byte sequence */ -#define ERESTART 85 /* Interrupted system call should be restarted */ -#define ESTRPIPE 86 /* Streams pipe error */ -#define EUSERS 87 /* Too many users */ -#define ENOTSOCK 88 /* Socket operation on non-socket */ -#define EDESTADDRREQ 89 /* Destination address required */ -#define EMSGSIZE 90 /* Message too long */ -#define EPROTOTYPE 91 /* Protocol wrong type for socket */ -#define ENOPROTOOPT 92 /* Protocol not available */ -#define EPROTONOSUPPORT 93 /* Protocol not supported */ -#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ -#define EPFNOSUPPORT 96 /* Protocol family not supported */ -#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ -#define EADDRINUSE 98 /* Address already in use */ -#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ -#define ENETDOWN 100 /* Network is down */ -#define ENETUNREACH 101 /* Network is unreachable */ -#define ENETRESET 102 /* Network dropped connection because of reset */ -#define ECONNABORTED 103 /* Software caused connection abort */ -#define ECONNRESET 104 /* Connection reset by peer */ -#define ENOBUFS 105 /* No buffer space available */ -#define EISCONN 106 /* Transport endpoint is already connected */ -#define ENOTCONN 107 /* Transport endpoint is not connected */ -#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ -#define ETOOMANYREFS 109 /* Too many references: cannot splice */ -#define ETIMEDOUT 110 /* Connection timed out */ -#define ECONNREFUSED 111 /* Connection refused */ -#define EHOSTDOWN 112 /* Host is down */ -#define EHOSTUNREACH 113 /* No route to host */ -#define EALREADY 114 /* Operation already in progress */ -#define EINPROGRESS 115 /* Operation now in progress */ -#define ESTALE 116 /* Stale file handle */ -#define EUCLEAN 117 /* Structure needs cleaning */ -#define ENOTNAM 118 /* Not a XENIX named type file */ -#define ENAVAIL 119 /* No XENIX semaphores available */ -#define EISNAM 120 /* Is a named type file */ -#define EREMOTEIO 121 /* Remote I/O error */ -#define EDQUOT 122 /* Quota exceeded */ - -#define ENOMEDIUM 123 /* No medium found */ -#define EMEDIUMTYPE 124 /* Wrong medium type */ -#define ECANCELED 125 /* Operation Canceled */ -#define ENOKEY 126 /* Required key not available */ -#define EKEYEXPIRED 127 /* Key has expired */ -#define EKEYREVOKED 128 /* Key has been revoked */ -#define EKEYREJECTED 129 /* Key was rejected by service */ - -/* for robust mutexes */ -#define EOWNERDEAD 130 /* Owner died */ -#define ENOTRECOVERABLE 131 /* State not recoverable */ - -#define ERFKILL 132 /* Operation not possible due to RF-kill */ - -#define EHWPOISON 133 /* Memory page has hardware error */ - -extern int errno; -#ifdef __cplusplus -} -#endif - -#endif
\ No newline at end of file diff --git a/include/ulib/iostream b/include/ulib/iostream deleted file mode 100644 index b05f603..0000000 --- a/include/ulib/iostream +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _IOSTREAM -#define _IOSTREAM - -#include "stdio.h" - -namespace std -{ - class ostream - { - private: - FILE* _stream; - public: - ostream(FILE* f) : _stream(f) {} - - ostream& operator<<(const char* str) - { - fprintf(_stream, "%s", str); - return *this; - } - - ostream& operator<<(int n) - { - fprintf(_stream, "%d", n); - return *this; - } - - ostream &operator<<(long n) - { - fprintf(_stream, "%ld", n); - return *this; - } - - ostream &operator<<(long long n) - { - fprintf(_stream, "%lld", n); - return *this; - } - - ostream &operator<<(double f) - { - fprintf(_stream, "%f", f); - return *this; - } - - ostream &operator<<(char c) - { - fputc(c, _stream); - return *this; - } - - ostream &operator<<(void *p) - { - fprintf(_stream, "%p", p); - return *this; - } - - ostream &operator<<(ostream &(*func)(ostream &)) - { - return func(*this); - } - - ostream &flush() - { - fflush(_stream); - return *this; - } - }; - - class istream{ - private: - FILE* _stream; - public: - istream(FILE *f) : _stream(f) {} - - istream& operator>>(int n) - { - fscanf(_stream, "%d", n); - return *this; - } - - istream &operator>>(double &f) - { - fscanf(_stream, "%lf", &f); - return *this; - } - - istream &operator>>(char *str) - { - fscanf(_stream, "%s", str); - return *this; - } - - istream &operator>>(char &c) - { - char tmp[2]; - fscanf(_stream, " %c", tmp); - c = tmp[0]; - return *this; - } - }; - - inline ostream &endl(ostream &os) - { - os << '\n'; - os.flush(); - return os; - } -} - -#endif
\ No newline at end of file diff --git a/include/ulib/limits.h b/include/ulib/limits.h deleted file mode 100644 index b317e6a..0000000 --- a/include/ulib/limits.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#pragma once - -#define CHAR_BIT 8 -#define SCHAR_MIN (-128) -#define SCHAR_MAX 127 -#define UCHAR_MAX 255 - -#define SHRT_MIN (-32768) -#define SHRT_MAX 32767 -#define USHRT_MAX 65535 - -#define INT_MIN (-2147483648) -#define INT_MAX 2147483647 -#define UINT_MAX 4294967295U - -#define LONG_MAX 9223372036854775807L -#define LONG_MIN (-LONG_MAX - 1L) -#define ULONG_MAX 18446744073709551615UL - -#define LLONG_MAX 9223372036854775807LL -#define LLONG_MIN (-LLONG_MAX - 1LL) -#define ULLONG_MAX 18446744073709551615ULL
\ No newline at end of file diff --git a/include/ulib/stb_sprintf.h b/include/ulib/stb_sprintf.h deleted file mode 100644 index 00dc123..0000000 --- a/include/ulib/stb_sprintf.h +++ /dev/null @@ -1,1916 +0,0 @@ -// stb_sprintf - v1.10 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// github:aganm (attribute format) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -==== -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -=============== -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -============ -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -======= -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) - #if defined(__has_feature) && defined(__has_attribute) - #if __has_feature(address_sanitizer) - #if __has_attribute(__no_sanitize__) - #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) - #elif __has_attribute(__no_sanitize_address__) - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #elif __has_attribute(__no_address_safety_analysis__) - #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) - #endif - #endif - #endif -#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#if defined(__has_attribute) - #if __has_attribute(format) - #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) - #endif -#endif - -#ifndef STBSP__ATTRIBUTE_FORMAT -#define STBSP__ATTRIBUTE_FORMAT(fmt,va) -#endif - -#ifdef _MSC_VER -#define STBSP__NOTUSED(v) (void)(v) -#else -#define STBSP__NOTUSED(v) (void)sizeof(v) -#endif - -#include <stdarg.h> // for va_arg(), va_list() -#include <stddef.h> // size_t, ptrdiff_t - -#ifndef stbsp__uintptr -#if defined(__riscv) && (__riscv_xlen == 64) -#define stbsp__uintptr unsigned long long -#elif defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr unsigned long long -#else -#define stbsp__uintptr unsigned int -#endif -#endif - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); - -#endif // STB_SPRINTF_H_INCLUDE - -#ifdef STB_SPRINTF_IMPLEMENTATION - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) -{ - char const * sn = s; - - // get up to 4-byte alignment - for (;;) { - if (((stbsp__uintptr)sn & 3) == 0) - break; - - if (!limit || *sn == 0) - return (stbsp__uint32)(sn - s); - - ++sn; - --limit; - } - - // scan over 4 bytes at a time to find terminating 0 - // this will intentionally scan up to 3 bytes past the end of buffers, - // but becase it works 4B aligned, it will never cross page boundaries - // (hence the STBSP__ASAN markup; the over-read here is intentional - // and harmless) - while (limit >= 4) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - // bit hack to find if there's a 0 byte in there - if ((v - 0x01010101) & (~v) & 0x80808080UL) - break; - - sn += 4; - limit -= 4; - } - - // handle the last few characters to find actual size - while (limit && *sn) { - ++sn; - --limit; - } - - return (stbsp__uint32)(sn - s); -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff - #define stbsp__chk_cb_bufL(bytes) \ - { \ - int len = (int)(bf - buf); \ - if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ - tlen += len; \ - if (0 == (bf = buf = callback(buf, user, len))) \ - goto done; \ - } \ - } - #define stbsp__chk_cb_buf(bytes) \ - { \ - if (callback) { \ - stbsp__chk_cb_bufL(bytes); \ - } \ - } - #define stbsp__flush_cb() \ - { \ - stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ - } // flush if there is even one byte in the buffer - #define stbsp__cb_buf_clamp(cl, v) \ - cl = v; \ - if (callback) { \ - int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ - if (cl > lg) \ - cl = lg; \ - } - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; - #ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else - #endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { - #define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length, limited to desired precision - // always limit to ~0u chars since our counts are 32b - l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - cs = 0; - STBSP__NOTUSED(dp); - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); -// add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = 0; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } -endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - -done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ - { \ - int cn; \ - for (cn = 0; cn < 8; cn++) \ - ((char *)&dest)[cn] = ((char *)&src)[cn]; \ - } - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ - { \ - double ahi = 0, alo, bhi = 0, blo; \ - stbsp__int64 bt; \ - oh = xh * yh; \ - STBSP__COPYFP(bt, xh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(ahi, bt); \ - alo = xh - ahi; \ - STBSP__COPYFP(bt, yh); \ - bt &= ((~(stbsp__uint64)0) << 27); \ - STBSP__COPYFP(bhi, bt); \ - blo = yh - bhi; \ - ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ - } - -#define stbsp__ddtoS64(ob, xh, xl) \ - { \ - double ahi = 0, alo, vh, t; \ - ob = (stbsp__int64)xh; \ - vh = (double)ob; \ - ahi = (xh - vh); \ - t = (ahi - xh); \ - alo = (xh - (ahi - t)) - (vh + t); \ - ob += (stbsp__int64)(ahi + alo + xl); \ - } - -#define stbsp__ddrenorm(oh, ol) \ - { \ - double s; \ - s = oh + ol; \ - ol = ol - (s - oh); \ - oh = s; \ - } - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/include/ulib/stdio.h b/include/ulib/stdio.h deleted file mode 100644 index a6df21d..0000000 --- a/include/ulib/stdio.h +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -/* Standard I/O Library for Lume OS */ - -#ifndef _STDIO_H -#define _STDIO_H - -#include <stdarg.h> -#include "ulib/types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/*Type Definition (Adjusted According to Architecture)*/ -#ifndef NULL -#define NULL ((void *)0) -#endif - -/*Macro definition*/ -#define EOF (-1) - -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 - -#define BUFSIZ 1024 - - -/* FILE structure */ -typedef struct -{ - int fd; - int flags; - int error; - int eof; - - char buffer[BUFSIZ]; - - int buf_write_pos; - - int buf_read_pos; - int buf_read_cnt; - - int buf_mode; -} FILE; - -/* standard stream object */ -extern FILE *stdin; -extern FILE *stdout; -extern FILE *stderr; - -/* Function Declaration */ - -/* File Operations */ -FILE *fopen(const char *filename, const char *mode); -int fclose(FILE *stream); -int fflush(FILE *stream); // Not implement - -/* Formatted input and output */ -int printf(const char *format, ...); -int scanf(const char *format, ...); -int sprintf(char *str, const char *format, ...); -int snprintf(char *str, uint64 size, const char *format, ...); -int vsnprintf(char *str, uint64 size, const char *format, va_list ap); -int sscanf(const char *str, const char *format, ...); - -int fprintf(FILE *stream, const char *format, ...); -int fscanf(FILE *stream, const char *format, ...); - -/* Character/String Read and Write */ -int fputc(int c, FILE *stream); -int fputs(const char *s, FILE *stream); -int fgetc(FILE *stream); -char *fgets(char *str, int n, FILE *stream); -int ungetc(int c, FILE *stream); - -/* Common Macros/Wrapper Functions */ -#define getchar() fgetc(stdin) -#define putchar(c) fputc((c), stdout) - -/* Binary read and write */ -uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream); -uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream); - -/* File location */ -int fseek(FILE *stream, long offset, int whence); -long ftell(FILE *stream); -void rewind(FILE *stream); - -/* Status Inquiry */ -int feof(FILE *stream); -int ferror(FILE *stream); -void clearerr(FILE *stream); - -#ifdef __cplusplus -} -#endif -#endif /* _STDIO_H */
\ No newline at end of file diff --git a/include/ulib/stdlib.h b/include/ulib/stdlib.h deleted file mode 100644 index 6d8f75d..0000000 --- a/include/ulib/stdlib.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ - -#ifndef _STDLIB_H -#define _STDLIB_H - -#include "ulib/types.h" -#include "ulib/limits.h" -#include "ulib/errno.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -#ifndef NULL -#define NULL ((void *)0) -#endif - -#define ERANGE 34 /* Math result not representable */ -#define ENOMEM 12 /* Out of memory */ - -#define EXIT_SUCCESS 0 -#define EXIT_FAILURE 1 -#define RAND_MAX 32767 -#define MB_CUR_MAX 1 - -typedef struct -{ - int quot; - int rem; -} div_t; -typedef struct -{ - long quot; - long rem; -} ldiv_t; -typedef struct -{ - long long quot; - long long rem; -} lldiv_t; - -typedef int (*__compar_fn_t)(const void *, const void *); - -/* Memory Management */ -void *malloc(size_t size); -void free(void *ptr); -void *calloc(size_t nmemb, size_t size); -void *realloc(void *ptr, size_t size); - -/* Process Control */ -int exit(int status); -void abort(void); -int atexit(void (*function)(void)); -char *getenv(const char *name); -int system(const char *command); - -/* String Conversion */ -int atoi(const char *nptr); -long atol(const char *nptr); -long long atoll(const char *nptr); -double atof(const char *nptr); - -long strtol(const char *nptr, char **endptr, int base); -unsigned long strtoul(const char *nptr, char **endptr, int base); -long long strtoll(const char *nptr, char **endptr, int base); -unsigned long long strtoull(const char *nptr, char **endptr, int base); - -double strtod(const char *nptr, char **endptr); -float strtof(const char *nptr, char **endptr); - -/* Search & Sort*/ -void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); -void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); - -/* Math & Mics */ -int abs(int j); -long labs(long j); -long long llabs(long long j); -div_t div(int numer, int denom); -ldiv_t ldiv(long numer, long denom); -lldiv_t lldiv(long long numer, long long denom); - -int rand(void); -void srand(unsigned int seed); - -int mblen(const char *s, size_t n); -int mbtowc(wchar_t *pwc, const char *s, size_t n); -int wctomb(char *s, wchar_t wchar); -size_t mbstowcs(wchar_t *dest, const char *src, size_t n); -size_t wcstombs(char *dest, const wchar_t *src, size_t n); - -#ifdef __cplusplus -} -#endif - -#endif /* _STDLIB_H */
\ No newline at end of file diff --git a/include/ulib/string.h b/include/ulib/string.h deleted file mode 100644 index c7519ea..0000000 --- a/include/ulib/string.h +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#ifndef _STRING_H -#define _STRING_H - -#include "ulib/types.h" -#include "ulib/errno.h" - -#ifndef _SIZE_T -#define _SIZE_T -typedef unsigned long long size_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - - /* Memory Manipulation*/ - void *memcpy(void *dest, const void *src, size_t n); - - void *memmove(void *dest, const void *src, size_t n); - - void *memset(void *dest, int c, size_t n); - - int memcmp(const void *s1, const void *s2, size_t n); - - void *memchr(const void *s, int c, size_t n); - - /* String Manipulation */ - - size_t strlen(const char *s); - - char *strcpy(char *dest, const char *src); - - char *strncpy(char *dest, const char *src, size_t n); - - char *strcat(char *dest, const char *src); - - char *strncat(char *dest, const char *src, size_t n); - - int strcmp(const char *s1, const char *s2); - - int strncmp(const char *s1, const char *s2, size_t n); - - char *strchr(const char *s, int c); - - char *strrchr(const char *s, int c); - - char *strstr(const char *haystack, const char *needle); - - char *strdup(const char *s); - - size_t strlcpy(char *dest, const char *src, size_t size); - - size_t strlcat(char *dest, const char *src, size_t size); - - size_t strspn(const char *s, const char *accept); - - size_t strcspn(const char *s, const char *reject); - - char *strpbrk(const char *s, const char *accept); - - char *strtok(char *str, const char *delim); - - char *strerror(int errnum); - -#ifdef __cplusplus -} -#endif - -#endif /* _STRING_H */
\ No newline at end of file diff --git a/include/ulib/types.h b/include/ulib/types.h deleted file mode 100644 index 69ee233..0000000 --- a/include/ulib/types.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#ifndef _ULIB_TYPES_H -#define _ULIB_TYPES_H - -#include "common/types.h" - -#ifndef _ULIB_ALIASES -#define _ULIB_ALIASES -typedef unsigned int uint; -typedef unsigned short ushort; -typedef unsigned char uchar; -#endif - -#ifndef NULL -#define NULL ((void *)0) -#endif - -#ifndef __cplusplus -typedef int wchar_t; -#endif - -#endif
\ No newline at end of file diff --git a/include/user/user.h b/include/user/user.h deleted file mode 100644 index 7af8bdd..0000000 --- a/include/user/user.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard Qin - */ -#pragma once - -typedef unsigned long long uint64; - -#define AT_FDCWD -100 - -#ifdef __cplusplus -extern "C" -{ -#endif - - // Raw Linux System Calls - int sys_openat(int dfd, const char *filename, int flags, int mode); - int sys_mknodat(int dfd, const char *filename, int mode, int dev); - int sys_mkdirat(int dfd, const char *path, int mode); - int sys_unlinkat(int dfd, const char *path, int flags); - int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); - int sys_chdir(const char *path); - int sys_dup(int oldfd); - int sys_fstat(int fd, void *statbuf); - int sys_read(int fd, void *buf, int count); - int sys_write(int fd, const void *buf, int count); - int sys_close(int fd); - int sys_pipe2(int pipefd[2], int flags); - int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); - int sys_execve(const char *path, char *const argv[], char *const envp[]); - int sys_wait4(int pid, int *status, int options, void *rusage); - int sys_exit(int status); - int sys_getpid(); - int sys_brk(int n); - int sys_kill(int pid, int sig); - int sys_nanosleep(void *req, void *rem); - int sys_reboot(int magic1, int magic2, int cmd, void *arg); - int sys_lseek(int fd, int offset, int whence); - - // Custom - void sys_putc(char c); - int sys_disk_test(); - - // POSIX Wrappers - int fork(); - int exit(int status); - int wait(uint64 status_addr); - int pipe(int p[]); - int write(int fd, const void *buf, int n); - int read(int fd, void *buf, int n); - int close(int fd); - int kill(int pid); - int exec(char *, char **); - int open(const char *, int); - int mknod(const char *, short, short); - int unlink(const char *); - int link(const char *, const char *); - int fstat(int fd, void *st); - int mkdir(const char *); - int chdir(const char *); - int dup(int fd); - int getpid(); - char *sbrk(int n); - int sleep(int ticks); - int uptime(); - int shutdown(); - int lseek(int fd, int offset, int whence); - - // Debug - void putc(char c); - int disk_test(); - -#ifdef __cplusplus -} -#endif
\ No newline at end of file |
