From e90b72ec3082588b296956730bd2930cf8d5d671 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 10 Jul 2026 11:31:46 +0800 Subject: refactor: promote new architecture to repository root Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior. --- lume-legacy/src/boot/entry.S | 47 ++ lume-legacy/src/drivers/plic.cc | 77 +++ lume-legacy/src/drivers/uart.cc | 204 ++++++ lume-legacy/src/drivers/virtio_dist.cc | 382 +++++++++++ lume-legacy/src/fs/fat32.cc | 806 +++++++++++++++++++++++ lume-legacy/src/fs/file.cc | 358 ++++++++++ lume-legacy/src/fs/fs.cc | 268 ++++++++ lume-legacy/src/kernel/bio.cc | 139 ++++ lume-legacy/src/kernel/config.cc | 13 + lume-legacy/src/kernel/cxx.cc | 78 +++ lume-legacy/src/kernel/fdt.cc | 273 ++++++++ lume-legacy/src/kernel/initcode.S | 11 + lume-legacy/src/kernel/main.cc | 79 +++ lume-legacy/src/kernel/mm/pmm.cc | 305 +++++++++ lume-legacy/src/kernel/mm/slab.cc | 259 ++++++++ lume-legacy/src/kernel/mm/vm.cc | 459 +++++++++++++ lume-legacy/src/kernel/proc/exec.cc | 260 ++++++++ lume-legacy/src/kernel/proc/proc.cc | 654 ++++++++++++++++++ lume-legacy/src/kernel/proc/swtch.S | 37 ++ lume-legacy/src/kernel/sbi.cc | 52 ++ lume-legacy/src/kernel/spinlock.cc | 199 ++++++ lume-legacy/src/kernel/syscall.cc | 698 ++++++++++++++++++++ lume-legacy/src/kernel/timer.cc | 66 ++ lume-legacy/src/kernel/trap/handler.cc | 217 ++++++ lume-legacy/src/kernel/trap/trampoline.S | 101 +++ lume-legacy/src/kernel/trap/trap.S | 90 +++ lume-legacy/src/lib/string.cc | 88 +++ lume-legacy/src/ulib/assert.c | 12 + lume-legacy/src/ulib/cxx.cc | 35 + lume-legacy/src/ulib/iostream.cpp | 12 + lume-legacy/src/ulib/stdio.c | 1056 ++++++++++++++++++++++++++++++ lume-legacy/src/ulib/stdlib.c | 543 +++++++++++++++ lume-legacy/src/ulib/string.c | 335 ++++++++++ lume-legacy/src/ulib/syscalls.c | 153 +++++ lume-legacy/src/user/cat.cc | 52 ++ lume-legacy/src/user/cp.cc | 52 ++ lume-legacy/src/user/echo.cc | 26 + lume-legacy/src/user/entry.S | 12 + lume-legacy/src/user/grep.cc | 72 ++ lume-legacy/src/user/init.cc | 64 ++ lume-legacy/src/user/kill.cc | 27 + lume-legacy/src/user/ln.cc | 22 + lume-legacy/src/user/ls.cc | 116 ++++ lume-legacy/src/user/mkdir.cc | 25 + lume-legacy/src/user/mv.cc | 30 + lume-legacy/src/user/rm.cc | 28 + lume-legacy/src/user/sh.cc | 423 ++++++++++++ lume-legacy/src/user/touch.cc | 31 + lume-legacy/src/user/user.ld | 36 + lume-legacy/src/user/usys.S | 38 ++ lume-legacy/src/user/wc.cc | 60 ++ 51 files changed, 9480 insertions(+) create mode 100644 lume-legacy/src/boot/entry.S create mode 100644 lume-legacy/src/drivers/plic.cc create mode 100644 lume-legacy/src/drivers/uart.cc create mode 100644 lume-legacy/src/drivers/virtio_dist.cc create mode 100644 lume-legacy/src/fs/fat32.cc create mode 100644 lume-legacy/src/fs/file.cc create mode 100644 lume-legacy/src/fs/fs.cc create mode 100644 lume-legacy/src/kernel/bio.cc create mode 100644 lume-legacy/src/kernel/config.cc create mode 100644 lume-legacy/src/kernel/cxx.cc create mode 100644 lume-legacy/src/kernel/fdt.cc create mode 100644 lume-legacy/src/kernel/initcode.S create mode 100644 lume-legacy/src/kernel/main.cc create mode 100644 lume-legacy/src/kernel/mm/pmm.cc create mode 100644 lume-legacy/src/kernel/mm/slab.cc create mode 100644 lume-legacy/src/kernel/mm/vm.cc create mode 100644 lume-legacy/src/kernel/proc/exec.cc create mode 100644 lume-legacy/src/kernel/proc/proc.cc create mode 100644 lume-legacy/src/kernel/proc/swtch.S create mode 100644 lume-legacy/src/kernel/sbi.cc create mode 100644 lume-legacy/src/kernel/spinlock.cc create mode 100644 lume-legacy/src/kernel/syscall.cc create mode 100644 lume-legacy/src/kernel/timer.cc create mode 100644 lume-legacy/src/kernel/trap/handler.cc create mode 100644 lume-legacy/src/kernel/trap/trampoline.S create mode 100644 lume-legacy/src/kernel/trap/trap.S create mode 100644 lume-legacy/src/lib/string.cc create mode 100644 lume-legacy/src/ulib/assert.c create mode 100644 lume-legacy/src/ulib/cxx.cc create mode 100644 lume-legacy/src/ulib/iostream.cpp create mode 100644 lume-legacy/src/ulib/stdio.c create mode 100644 lume-legacy/src/ulib/stdlib.c create mode 100644 lume-legacy/src/ulib/string.c create mode 100644 lume-legacy/src/ulib/syscalls.c create mode 100644 lume-legacy/src/user/cat.cc create mode 100644 lume-legacy/src/user/cp.cc create mode 100644 lume-legacy/src/user/echo.cc create mode 100644 lume-legacy/src/user/entry.S create mode 100644 lume-legacy/src/user/grep.cc create mode 100644 lume-legacy/src/user/init.cc create mode 100644 lume-legacy/src/user/kill.cc create mode 100644 lume-legacy/src/user/ln.cc create mode 100644 lume-legacy/src/user/ls.cc create mode 100644 lume-legacy/src/user/mkdir.cc create mode 100644 lume-legacy/src/user/mv.cc create mode 100644 lume-legacy/src/user/rm.cc create mode 100644 lume-legacy/src/user/sh.cc create mode 100644 lume-legacy/src/user/touch.cc create mode 100644 lume-legacy/src/user/user.ld create mode 100644 lume-legacy/src/user/usys.S create mode 100644 lume-legacy/src/user/wc.cc (limited to 'lume-legacy/src') diff --git a/lume-legacy/src/boot/entry.S b/lume-legacy/src/boot/entry.S new file mode 100644 index 0000000..8910ddc --- /dev/null +++ b/lume-legacy/src/boot/entry.S @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text.entry +.global _start + +_start: + mv tp, a0 + + la t0, stack_base + li t1, 4096 + addi t2, a0, 1 + mul t1, t1, t2 + add sp, t0, t1 + + bnez a0, .L_jump_main + + la t0, sbss + la t1, ebss +.L_clear_bss: + bge t0, t1, .L_save_dtb + sd zero, 0(t0) + addi t0, t0, 8 + j .L_clear_bss + +.L_save_dtb: + la t0, g_dtb_addr + sd a1, 0(t0) + +.L_jump_main: + call kernel_main + +.L_park: + wfi + j .L_park + +.section .bss +.align 4 +.global stack_top +stack_base: + .space 4096 * 8 +stack_top: + +.align 8 +.global g_dtb_addr +g_dtb_addr: + .space 8 \ No newline at end of file diff --git a/lume-legacy/src/drivers/plic.cc b/lume-legacy/src/drivers/plic.cc new file mode 100644 index 0000000..a975c44 --- /dev/null +++ b/lume-legacy/src/drivers/plic.cc @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "drivers/plic.h" +#include "drivers/uart.h" +#include "kernel/fdt.h" + +// Helper Macros — all relative to PLIC base from FDT +#define PLIC_BASE (g_devices.plic.base_addr) +#define PLIC_PRIORITY (PLIC_BASE + 0x0) +#define PLIC_PENDING (PLIC_BASE + 0x1000) + +#define PLIC_SENABLE_ADDR(hart) (PLIC_BASE + 0x2080 + (hart) * 0x100) +#define PLIC_STHRESHOLD_ADDR(hart) (PLIC_BASE + 0x201000 + (hart) * 0x2000) +#define PLIC_SCLAIM_ADDR(hart) (PLIC_BASE + 0x201004 + (hart) * 0x2000) + +// Helper for Volatile Access +static inline volatile uint32 *REG32(uint64 addr) +{ + return reinterpret_cast(addr); +} + +namespace PLIC +{ + void init() + { + // Set priority for UART IRQ + if (g_devices.uart.irq > 0) + *REG32(PLIC_PRIORITY + g_devices.uart.irq * 4) = 1; + + // Set priority for all VirtIO IRQs + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (g_devices.virtio[i].irq > 0) + *REG32(PLIC_PRIORITY + g_devices.virtio[i].irq * 4) = 1; + } + } + + void inithart() + { + int hart = static_cast(r_tp()); + + // Build enable bitmask from discovered devices + uint32 enable = 0; + + if (g_devices.uart.irq > 0 && g_devices.uart.irq < 32) + enable |= (1 << g_devices.uart.irq); + + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (g_devices.virtio[i].irq > 0 && g_devices.virtio[i].irq < 32) + enable |= (1 << g_devices.virtio[i].irq); + } + + *REG32(PLIC_SENABLE_ADDR(hart)) = enable; + + // Set Priority Threshold to 0 (Allow all) + *REG32(PLIC_STHRESHOLD_ADDR(hart)) = 0; + + Drivers::uart_puts("[PLIC] Hart Init Done.\n"); + } + + int claim() + { + int hart = static_cast(r_tp()); + return *REG32(PLIC_SCLAIM_ADDR(hart)); + } + + void complete(int irq) + { + int hart = static_cast(r_tp()); + *REG32(PLIC_SCLAIM_ADDR(hart)) = static_cast(irq); + } +} \ No newline at end of file diff --git a/lume-legacy/src/drivers/uart.cc b/lume-legacy/src/drivers/uart.cc new file mode 100644 index 0000000..397a402 --- /dev/null +++ b/lume-legacy/src/drivers/uart.cc @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "drivers/uart.h" +#include "kernel/riscv.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +#include "kernel/mm.h" +#include "lib/string.h" + +namespace Drivers +{ + struct + { + Spinlock lock; + char buf[128]; // Line buffer + uint32 r; // Read pointer (position taken by the user) + uint32 w; // Write pointer (position confirmed by Enter) + uint32 e; // Editing cursor (current typing position) + } cons; + + // Circular buffer structure + struct + { + Spinlock lock; + char buf[UART_RX_BUF_SIZE]; + uint32 r; // read index + uint32 w; // write index + } uart_rx; + + Spinlock uart_tx_lock; + + void uart_init() + { + uart_rx.lock.init("uart_rx"); + cons.lock.init("console"); + uart_tx_lock.init("uart_tx"); + uart_rx.r = 0; + uart_rx.w = 0; + + // Disable interrupts and configure + *(volatile uint8 *)(uart_base() + IER) = 0x00; + + // Enter baud rate setting mode (LCR_DLAB) = 0x80; + *(volatile uint8 *)(uart_base() + LCR) = 0x80; + *(volatile uint8 *)(uart_base() + 0) = 0x03; // DLL: 38.4K baud + *(volatile uint8 *)(uart_base() + 1) = 0x00; // DLM + + // Exit baud rate setting, set 8-bit word length, no parity + *(volatile uint8 *)(uart_base() + LCR) = 0x03; + + // Reset and enable FIFO + *(volatile uint8 *)(uart_base() + FCR) = 0x07; + + // Enable Receive Data Available Interrupt + *(volatile uint8 *)(uart_base() + IER) = 0x01; + } + + void uart_putc(char c) + { + uart_tx_lock.acquire(); + while ((*(volatile uint8 *)(uart_base() + LSR) & LSR_TX_IDLE) == 0) + ; + *(volatile uint8 *)(uart_base() + THR) = c; + uart_tx_lock.release(); + } + + void uart_puts(const char *s) + { + while (*s) + uart_putc(*s++); + } + + void uart_put_int(int val) + { + char buf[16]; + int i = 0; + if (val == 0) + { + uart_puts("0"); + return; + } + if (val < 0) + { + uart_puts("-"); + val = -val; + } + while (val > 0) + { + buf[i++] = (val % 10) + '0'; + val /= 10; + } + while (i > 0) + { + uart_putc(buf[--i]); + } + } + + void print_hex(uint64 val) + { + char hex[] = "0123456789ABCDEF"; + uart_puts("0x"); + for (int i = 60; i >= 0; i -= 4) + { + uart_putc(hex[(val >> i) & 0xF]); + } + } + + void uart_intr() + { + uart_rx.lock.acquire(); + + while (1) + { + if ((*(volatile uint8 *)(uart_base() + LSR) & LSR_RX_READY) == 0) + break; + + char c = *(volatile uint8 *)(uart_base() + RHR); + + uart_rx.buf[uart_rx.w % UART_RX_BUF_SIZE] = c; + uart_rx.w++; + + ProcManager::wakeup(&uart_rx); + } + + uart_rx.lock.release(); + } + + // Modern console read: Line buffering + Editing Keys + Echo + int console_read(uint64 target, int len) + { + struct Proc *p = myproc(); + int target_idx = 0; + + cons.lock.acquire(); + + while (target_idx < len) + { + while (cons.r < cons.w) + { + char c = cons.buf[cons.r++ % sizeof(cons.buf)]; + cons.lock.release(); + + if (VM::copyout(p->pagetable, target + target_idx, &c, 1) < 0) + return -1; + + target_idx++; + if (c == '\n') + return target_idx; + if (target_idx == len) + return target_idx; + + cons.lock.acquire(); + } + + uart_rx.lock.acquire(); + while (uart_rx.r == uart_rx.w) + { + if (p->xstate) + { + uart_rx.lock.release(); + cons.lock.release(); + return -1; + } + cons.lock.release(); + ProcManager::sleep(&uart_rx, &uart_rx.lock); + cons.lock.acquire(); + } + + char c = uart_rx.buf[uart_rx.r++ % UART_RX_BUF_SIZE]; + uart_rx.lock.release(); + + if (c == 127 || c == '\b') + { + if (cons.e != cons.w) + { + cons.e--; + uart_putc('\b'); + uart_putc(' '); + uart_putc('\b'); + } + } + else + { + if (c == '\r') + c = '\n'; + + uart_putc(c); + + cons.buf[cons.e++ % sizeof(cons.buf)] = c; + + if (c == '\n' || cons.e == cons.w + sizeof(cons.buf)) + { + cons.w = cons.e; + ProcManager::wakeup(&cons); + } + } + } + + cons.lock.release(); + return target_idx; + } +} \ No newline at end of file diff --git a/lume-legacy/src/drivers/virtio_dist.cc b/lume-legacy/src/drivers/virtio_dist.cc new file mode 100644 index 0000000..8292874 --- /dev/null +++ b/lume-legacy/src/drivers/virtio_dist.cc @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "drivers/virtio.h" +#include "kernel/fdt.h" +#include "kernel/riscv.h" +#include "kernel/pmm.h" +#include "kernel/spinlock.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/proc.h" +#include "kernel/buf.h" + +static uint64 virtio_base = 0x10001000; + +// VirtIO Block Device Config Structure +struct virtio_blk_config +{ + uint64 capacity; + uint32 size_max; + uint32 seg_max; + struct virtio_blk_geometry + { + uint16 cylinders; + uint8 heads; + uint8 sectors; + } geometry; + uint32 blk_size; + uint8 physical_block_exp; + uint8 alignment_offset; + uint16 min_io_size; + uint32 opt_io_size; +} __attribute__((packed)); + +static inline volatile uint32 *REG(uint32 offset) +{ + return reinterpret_cast(virtio_base + offset); +} + +namespace VirtIO +{ + struct Disk + { + struct VRingDesc *desc; + struct VRingAvail *avail; + struct VRingUsed *used; + + int free_head; + int num_free; + int free_chain[NUM_DESCS]; + + uint16 used_idx; + + struct TraceInfo + { + int busy; + int status; + } info[NUM_DESCS]; + + struct BlockRequest reqs[NUM_DESCS]; + uint8 req_status[NUM_DESCS]; + + Spinlock lock; + } disk; + + void init() + { + disk.lock.init("virtio_disk"); + + // 1. Probe VirtIO devices discovered by FDT + bool found = false; + for (int i = 0; i < g_devices.virtio_count; i++) + { + uint64 addr = g_devices.virtio[i].base_addr; + if (addr == 0) + continue; + + volatile uint32 *magic = (volatile uint32 *)(addr + VIRTIO_MMIO_MAGIC_VALUE); + volatile uint32 *dev_id = (volatile uint32 *)(addr + VIRTIO_MMIO_DEVICE_ID); + + if (*magic == 0x74726976 && *dev_id == 2) // ID 2 = Block Device + { + virtio_base = addr; + found = true; + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Found Block Device @ "); + Drivers::print_hex(addr); + Drivers::uart_puts(ANSI_RESET "\n"); + break; + } + } + + if (!found) + { + Drivers::uart_puts(ANSI_RED "[VirtIO] Fatal - No Block Device found.\n" ANSI_RESET); + return; + } + + // 2. Check Device Version (We want Modern v2) + if (*REG(VIRTIO_MMIO_VERSION) != 2) + { + Drivers::uart_puts(ANSI_YELLOW "[VirtIO] Warning: Legacy device detected (expected v2).\n" ANSI_RESET); + } + + // 3. Reset Device + *REG(VIRTIO_MMIO_STATUS) = 0; + + // 4. Set ACKNOWLEDGE & DRIVER status + uint32 status = *REG(VIRTIO_MMIO_STATUS); + status |= VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER; + *REG(VIRTIO_MMIO_STATUS) = status; + + // 5. Feature Negotiation + *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 0; + uint32 device_features_lo = *REG(VIRTIO_MMIO_DEVICE_FEATURES); + (void)device_features_lo; + + *REG(VIRTIO_MMIO_DEVICE_FEATURES_SEL) = 1; + uint32 device_features_hi = *REG(VIRTIO_MMIO_DEVICE_FEATURES); + (void)device_features_hi; + + *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 0; + *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 0; + + *REG(VIRTIO_MMIO_DRIVER_FEATURES_SEL) = 1; + // VIRTIO_F_VERSION_1 is usually bit 32. + // If not defined in header, we hardcode logic here: (1ULL << 0) for high bits + *REG(VIRTIO_MMIO_DRIVER_FEATURES) = 1; + + status |= VIRTIO_CONFIG_S_FEATURES_OK; + *REG(VIRTIO_MMIO_STATUS) = status; + + if (!(*REG(VIRTIO_MMIO_STATUS) & VIRTIO_CONFIG_S_FEATURES_OK)) + { + Drivers::uart_puts("VirtIO: Feature negotiation failed.\n"); + return; + } + + // 6. Queue Configuration + *REG(VIRTIO_MMIO_QUEUE_SEL) = 0; + if (*REG(VIRTIO_MMIO_QUEUE_READY)) + { + Drivers::uart_puts("VirtIO: Queue 0 not ready.\n"); + } + + uint32 max_queue = *REG(VIRTIO_MMIO_QUEUE_NUM_MAX); + if (max_queue < NUM_DESCS) + { + Drivers::uart_puts("VirtIO: Queue size too small.\n"); + return; + } + + *REG(VIRTIO_MMIO_QUEUE_NUM) = NUM_DESCS; + + // Allocate Memory for VirtQueues + void *ptr = PMM::alloc_pages(1); // 4KB is enough for small rings + memset(ptr, 0, PGSIZE); + + uint64 base_addr = (uint64)ptr; + disk.desc = reinterpret_cast(base_addr); + disk.avail = reinterpret_cast(base_addr + NUM_DESCS * 16); + disk.used = reinterpret_cast(base_addr + PGSIZE / 2); + + *REG(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint32)base_addr; + *REG(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint32)(base_addr >> 32); + + uint64 avail_addr = (uint64)disk.avail; + *REG(VIRTIO_MMIO_QUEUE_DRIVER_LOW) = (uint32)avail_addr; + *REG(VIRTIO_MMIO_QUEUE_DRIVER_HIGH) = (uint32)(avail_addr >> 32); + + uint64 used_addr = (uint64)disk.used; + *REG(VIRTIO_MMIO_QUEUE_DEVICE_LOW) = (uint32)used_addr; + *REG(VIRTIO_MMIO_QUEUE_DEVICE_HIGH) = (uint32)(used_addr >> 32); + + *REG(VIRTIO_MMIO_QUEUE_READY) = 1; + + // 7. Initialize Internal Structures + disk.free_head = 0; + disk.num_free = NUM_DESCS; + disk.used_idx = 0; + for (int i = 0; i < NUM_DESCS; i++) + { + disk.free_chain[i] = i + 1; + disk.info[i].busy = 0; + } + + // 8. Finalize Setup + status |= VIRTIO_CONFIG_S_DRIVER_OK; + *REG(VIRTIO_MMIO_STATUS) = status; + + // === [Feature] Read Capacity === + volatile struct virtio_blk_config *config = (volatile struct virtio_blk_config *)(virtio_base + VIRTIO_MMIO_CONFIG); + uint64 capacity_sectors = config->capacity; + uint64 size_mb = (capacity_sectors * 512) / (1024 * 1024); + + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Initialized. Capacity: "); + Drivers::uart_put_int(size_mb); + Drivers::uart_puts(" MB\n" ANSI_RESET); + } + + static int alloc_desc() + { + if (disk.num_free == 0) + return -1; + int id = disk.free_head; + disk.free_head = disk.free_chain[id]; + disk.num_free--; + return id; + } + + static void free_desc(int id) + { + disk.free_chain[id] = disk.free_head; + disk.free_head = id; + disk.num_free++; + ProcManager::wakeup(&disk.free_head); + } + + static void free_chain_soft(int head_idx) + { + int curr = head_idx; + while (1) + { + int flag = disk.desc[curr].flags; + int next = disk.desc[curr].next; + free_desc(curr); + if (flag & VRING_DESC_F_NEXT) + curr = next; + else + break; + } + } + + void rw(int write, uint64 blockno, char *buf) + { + uint64 sector = blockno * (1024 / 512); // Assume 1KB blocks + + disk.lock.acquire(); + + // Wait for enough descriptors + while (disk.num_free < 3) + { + ProcManager::sleep(&disk.free_head, &disk.lock); + } + + int idx[3]; + idx[0] = alloc_desc(); + idx[1] = alloc_desc(); + idx[2] = alloc_desc(); + + // 1. Request Header + struct BlockRequest *req = &disk.reqs[idx[0]]; + req->type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN; + req->reserved = 0; + req->sector = sector; + + disk.desc[idx[0]].addr = (uint64)req; + disk.desc[idx[0]].len = sizeof(struct BlockRequest); + disk.desc[idx[0]].flags = VRING_DESC_F_NEXT; + disk.desc[idx[0]].next = idx[1]; + + // 2. Data Buffer + disk.desc[idx[1]].addr = (uint64)buf; + disk.desc[idx[1]].len = 1024; // Fixed 1KB for this test + disk.desc[idx[1]].flags = VRING_DESC_F_NEXT; + if (!write) + disk.desc[idx[1]].flags |= VRING_DESC_F_WRITE; + disk.desc[idx[1]].next = idx[2]; + + // 3. Status Byte + disk.desc[idx[2]].addr = (uint64)&disk.req_status[idx[0]]; + disk.desc[idx[2]].len = 1; + disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; + disk.desc[idx[2]].next = 0; + + disk.info[idx[0]].busy = 1; + + // Submit to Avail Ring + disk.avail->ring[disk.avail->idx % NUM_DESCS] = idx[0]; + __sync_synchronize(); + disk.avail->idx++; + __sync_synchronize(); + + // Notify Device + *REG(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; + + // Wait for Completion + while (disk.info[idx[0]].busy) + { + ProcManager::sleep(&disk.info[idx[0]], &disk.lock); + } + + free_chain_soft(idx[0]); + disk.lock.release(); + } + + void intr() + { + disk.lock.acquire(); + + uint32 status = *REG(VIRTIO_MMIO_INTERRUPT_STATUS); + *REG(VIRTIO_MMIO_INTERRUPT_ACK) = status & 0x3; + + volatile struct VRingUsed *used_ring = (volatile struct VRingUsed *)disk.used; + + while (disk.used_idx != used_ring->idx) + { + int id = used_ring->ring[disk.used_idx % NUM_DESCS].id; + + if (disk.info[id].busy) + { + disk.info[id].busy = 0; + ProcManager::wakeup(&disk.info[id]); + } + disk.used_idx++; + } + + disk.lock.release(); + } + + void test_rw() + { + Drivers::uart_puts(ANSI_BLUE "[VirtIO] Disk Test Start...\n" ANSI_RESET); + + char *buf = (char *)PMM::alloc_page(); + if (!buf) + return; + + const char *msg = "Lume OS Disk Driver Test Pattern"; + strcpy(buf, msg); + + rw(1, 0, buf); // Write + memset(buf, 0, PGSIZE); + rw(0, 0, buf); // Read + + if (strcmp(buf, msg) == 0) + { + Drivers::uart_puts(ANSI_GREEN "[VirtIO] Test PASSED (R/W Verified).\n" ANSI_RESET); + } + else + { + Drivers::uart_puts(ANSI_RED "[VirtIO] Test FAILED.\n" ANSI_RESET); + } + + PMM::free_page(buf); + } + + void test_bio() + { + Drivers::uart_puts(ANSI_BLUE "[Bio] Starting Buffer Cache Test...\n" ANSI_RESET); + + struct Buf *b = BufferCache::bread(0, 100); + + Drivers::uart_puts("[Bio] Bread block 100 (1st time).\n"); + + b->data[0] = 'L'; + b->data[1] = 'u'; + b->data[2] = 'm'; + b->data[3] = 'e'; + b->data[4] = '\0'; + + BufferCache::bwrite(b); + Drivers::uart_puts("[Bio] Bwrite block 100.\n"); + + BufferCache::brelse(b); + Drivers::uart_puts("[Bio] Brelease block 100.\n"); + + struct Buf *b2 = BufferCache::bread(0, 100); + Drivers::uart_puts("[Bio] Bread block 100 (2nd time).\n"); + + if (b2->data[0] == 'L' && b2->data[1] == 'u' && b2->data[3] == 'e') + { + Drivers::uart_puts(ANSI_GREEN "[Bio] Data verification PASSED.\n" ANSI_RESET); + } + else + { + Drivers::uart_puts(ANSI_RED "[Bio] Data verification FAILED.\n" ANSI_RESET); + } + + BufferCache::brelse(b2); + } +} \ No newline at end of file diff --git a/lume-legacy/src/fs/fat32.cc b/lume-legacy/src/fs/fat32.cc new file mode 100644 index 0000000..e4089d4 --- /dev/null +++ b/lume-legacy/src/fs/fat32.cc @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/fat32.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/mm.h" +#include "kernel/proc.h" + +FAT32FileSystem fat32_fs; + +// Helper function +static void generate_short_name(char *dst, const char *src) +{ + memset(dst, ' ', 11); + + int i = 0; + for (; *src && *src != '.' && i < 8; src++) + { + char c = *src; + if (c >= 'a' && c <= 'z') + c -= 32; + dst[i++] = c; + } + + while (*src && *src != '.') + src++; + if (*src == '.') + src++; + + i = 8; + for (; *src && i < 11; src++) + { + char c = *src; + if (c >= 'a' && c <= 'z') + c -= 32; + dst[i++] = c; + } +} + +// FAT32 FileSystem Implementation + +void FAT32FileSystem::init() +{ + struct Buf *b = BufferCache::bread(0, 0); + uint8 *data = (uint8 *)b->data; + struct FAT32_BPB *bpb = (struct FAT32_BPB *)data; + + uint32 partition_lba = 0; + + bool is_valid_bpb = (bpb->bytesPerSector == 512) && (bpb->rootClus != 0); + + if (!is_valid_bpb) + { + Drivers::uart_puts("[FAT32] Sector 0 is not a valid BPB. Checking MBR...\n"); + + if (data[510] != 0x55 || data[511] != 0xAA) + { + Drivers::uart_puts("[FAT32] Panic: Invalid Disk Signature!\n"); + while (1) + ; + } + + uint32 lba = *(uint32 *)(data + 446 + 8); + + if (lba > 0) + { + Drivers::uart_puts("[FAT32] MBR Partition 1 found @ Sector "); + Drivers::uart_put_int(lba); + Drivers::uart_puts("\n"); + partition_lba = lba; + } + else + { + Drivers::uart_puts("[FAT32] Panic: No partition found.\n"); + while (1) + ; + } + + BufferCache::brelse(b); + + b = BufferCache::bread(0, partition_lba / 2); + bpb = (struct FAT32_BPB *)b->data; + } + + if (bpb->bytesPerSector != 512) + { + Drivers::uart_puts("[FAT32] Panic: Invalid FAT32 BPB in partition.\n"); + while (1) + ; + } + + uint32 fat_sz = bpb->FATSz32; + this->fat_sz = fat_sz; + uint32 reserved_sec = bpb->reservedSecCnt; + uint8 num_fats = bpb->numFATs; + + sectors_per_cluster = bpb->secPerClus; + bytes_per_cluster = sectors_per_cluster * 512; + root_cluster = bpb->rootClus; + + fat_start_sector = partition_lba + reserved_sec; + data_start_sector = partition_lba + reserved_sec + (fat_sz * num_fats); + + Drivers::uart_puts(ANSI_GREEN); + Drivers::uart_puts("[FAT32] Init Success: RootClus="); + Drivers::uart_put_int(root_cluster); + Drivers::uart_puts(", DataStart="); + Drivers::uart_put_int(data_start_sector); + Drivers::uart_puts(ANSI_RESET); + Drivers::uart_puts("\n"); + + BufferCache::brelse(b); +} + +Inode *FAT32FileSystem::root() +{ + FAT32Inode *ip = new FAT32Inode(); + + ip->fs = this; + ip->dev = 0; + ip->inum = root_cluster; + ip->ref_cnt = 1; + ip->valid = 1; + ip->type = T_DIR; + ip->size = 0; + ip->nlink = 1; + + ip->first_clus = root_cluster; + ip->parent_clus = 0; + + return ip; +} + +uint32 FAT32FileSystem::cluster_to_sector(uint32 cluster) +{ + return data_start_sector + (cluster - 2) * sectors_per_cluster; +} + +uint32 FAT32FileSystem::next_cluster(uint32 cluster) +{ + uint32 fat_offset = cluster * 4; + uint32 fat_sector = fat_start_sector + (fat_offset / 512); + uint32 ent_offset = fat_offset % 512; + + // block = sector / 2 + struct Buf *b = BufferCache::bread(0, fat_sector / 2); + + uint32 block_offset = (fat_sector % 2) * 512 + ent_offset; + uint32 next = *(uint32 *)(b->data + block_offset); + + BufferCache::brelse(b); + + return next & 0x0FFFFFFF; +} + +// FAT32 Inode Implementation + +static bool match_name(const char *disk_name, const char *target) +{ + char temp[13]; + int i, j; + + for (i = 0; i < 8 && disk_name[i] != ' '; i++) + temp[i] = disk_name[i]; + if (disk_name[8] != ' ') + { + temp[i++] = '.'; + for (j = 0; j < 3 && disk_name[8 + j] != ' '; j++) + temp[i++] = disk_name[8 + j]; + } + temp[i] = 0; + return strcasecmp(temp, target) == 0; +} + +int FAT32Inode::read(char *dst, uint32 off, uint32 n, int isUser) +{ + if (first_clus == 0) + return 0; + + // [FIX] 只有非目录文件才检查 size 边界 + // 目录文件 size 可能为 0,应允许读取直到簇链结束 + if (type != T_DIR) + { + if (off >= size) + { + return 0; + } + if (off + n > size) + { + n = size - off; + } + } + + uint32 tot = 0; + uint32 clus = first_clus; + uint32 clus_sz = fs->bytes_per_cluster; + + uint32 skip = off / clus_sz; + uint32 current_off = off % clus_sz; + + while (skip > 0) + { + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + return 0; + skip--; + } + + while (tot < n) + { + uint32 sec_in_clus = current_off / 512; + + if (sec_in_clus >= fs->sectors_per_cluster) + { + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + break; + current_off = 0; + continue; + } + + uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; + uint32 off_in_sec = current_off % 512; + + uint32 m = 512 - off_in_sec; + if (n - tot < m) + m = n - tot; + + struct Buf *b = BufferCache::bread(dev, sec / 2); + uint32 block_offset = (sec % 2) * 512 + off_in_sec; + + if (isUser) + { + if (VM::copyout(myproc()->pagetable, (uint64)dst, (char *)(b->data + block_offset), m) < 0) + { + BufferCache::brelse(b); + return -1; + } + } + else + { + memmove(dst, b->data + block_offset, m); + } + BufferCache::brelse(b); + + tot += m; + dst += m; + current_off += m; + } + return tot; +} + +Inode *FAT32Inode::lookup(const char *name) +{ + if (type != T_DIR) + return nullptr; + + uint32 clus = first_clus; + struct FAT32_DirEnt de; + + while (clus < 0x0FFFFFF8) + { + uint32 sec = fs->cluster_to_sector(clus); + + for (uint32 i = 0; i < fs->sectors_per_cluster; i++) + { + uint32 current_sec = sec + i; + struct Buf *b = BufferCache::bread(dev, current_sec / 2); + uint32 block_base = (current_sec % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + memmove(&de, b->data + block_base + off, 32); + + if (de.name[0] == 0x00) + { + BufferCache::brelse(b); + return nullptr; + } + if (de.name[0] == 0xE5) + continue; + if (de.attr & ATTR_LONG_NAME) + continue; + + if (match_name(de.name, name)) + { + FAT32Inode *ip = new FAT32Inode(); + ip->fs = fs; + ip->dev = dev; + ip->type = (de.attr & ATTR_DIRECTORY) ? T_DIR : T_FILE; + ip->size = de.fileSize; + ip->first_clus = (de.fstClusHI << 16) | de.fstClusLO; + ip->inum = ip->first_clus; + ip->nlink = 1; + ip->ref_cnt = 1; + ip->valid = 1; + ip->ent_sec = current_sec; // Log Sector + ip->ent_off = off; // Log Setoff + + BufferCache::brelse(b); + return ip; + } + } + BufferCache::brelse(b); + } + clus = fs->next_cluster(clus); + } + return nullptr; +} + +// Placeholder +uint32 FAT32FileSystem::alloc_cluster() +{ + uint32 fat_entries_per_sector = 512 / 4; + for (uint32 i = 0; i < fat_sz; i++) + { + uint32 current_fat_sec = fat_start_sector + i; + struct Buf *b = BufferCache::bread(0, current_fat_sec / 2); + uint32 *table = (uint32 *)(b->data + (current_fat_sec % 2) * 512); + for (uint32 j = 0; j < fat_entries_per_sector; j++) + { + if (i == 0 && j < 2) + continue; + + if ((table[j] & 0x0FFFFFFF) == 0) + { + // Find the free cluster and mark it as EOF (0x0FFFFFFF) + table[j] = 0x0FFFFFFF; + BufferCache::bwrite(b); // Write back to disk + BufferCache::brelse(b); + + uint32 cluster_idx = i * fat_entries_per_sector + j; + + // Clear the data content of the new cluster + uint32 data_sec = cluster_to_sector(cluster_idx); + for (uint32 k = 0; k < sectors_per_cluster; k++) + { + struct Buf *db = BufferCache::bread(0, (data_sec + k) / 2); + memset(db->data + ((data_sec + k) % 2) * 512, 0, 512); + BufferCache::bwrite(db); + BufferCache::brelse(db); + } + + return cluster_idx; + } + } + BufferCache::brelse(b); + } + Drivers::uart_puts("[FAT32] Out of space!\n"); + return 0; +} + +void FAT32FileSystem::free_cluster_chain(uint32 cluster) +{ + while (cluster >= 2 && cluster < 0x0FFFFFF8) + { + uint32 next = next_cluster(cluster); + + // Calculate the current cluster's position in the FAT table + uint32 fat_offset = cluster * 4; + uint32 fat_sector = fat_start_sector + (fat_offset / 512); + uint32 ent_offset = fat_offset % 512; + + // Read FAT sector + struct Buf *b = BufferCache::bread(0, fat_sector / 2); + + // Marked as idle (0x00000000) + uint32 *entry = (uint32 *)(b->data + (fat_sector % 2) * 512 + ent_offset); + *entry = 0; + + BufferCache::bwrite(b); + BufferCache::brelse(b); + + cluster = next; + } +} + +void FAT32Inode::update() +{ + struct Buf *b = BufferCache::bread(dev, ent_sec / 2); + uint32 offset = (ent_sec % 2) * 512 + ent_off; + + struct FAT32_DirEnt *de = (struct FAT32_DirEnt *)(b->data + offset); + + // Update file size and starting cluster + de->fileSize = size; + de->fstClusHI = (uint16)((first_clus >> 16) & 0xFFFF); + de->fstClusLO = (uint16)(first_clus & 0xFFFF); + + BufferCache::bwrite(b); + BufferCache::brelse(b); +} + +int FAT32Inode::write(const char *src, uint32 off, uint32 n, int isUser) +{ + if (off > size) + { + return -1; + } + + uint32 tot = 0; + uint32 clus = first_clus; + uint32 clus_sz = fs->bytes_per_cluster; + uint32 prev_clus = 0; + + if (clus == 0) + { + clus = fs->alloc_cluster(); + if (clus == 0) + return -1; + first_clus = clus; + // Update the metadata immediately to prevent data loss in case of a power outage. + update(); + } + + uint32 skip = off / clus_sz; + uint32 current_off = off % clus_sz; + + while (skip > 0) + { + prev_clus = clus; + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + { + return -1; + } + skip--; + } + while (tot < n) + { + if (current_off == clus_sz) + { + uint32 next = fs->next_cluster(clus); + if (next >= 0x0FFFFFF8) + { + uint32 new_clus = fs->alloc_cluster(); + if (new_clus == 0) + break; + + // Point the current cluster to the new cluster (update the FAT table) + uint32 fat_offset = clus * 4; + uint32 fat_sec = fs->fat_start_sector + (fat_offset / 512); + struct Buf *fat_b = BufferCache::bread(dev, fat_sec / 2); + uint32 *entry = (uint32 *)(fat_b->data + (fat_sec % 2) * 512 + (fat_offset % 512)); + *entry = new_clus; // Link + BufferCache::bwrite(fat_b); + BufferCache::brelse(fat_b); + + clus = new_clus; + } + else + { + clus = next; + } + current_off = 0; + } + + // Calculate the physical sector to write + uint32 sec_in_clus = current_off / 512; + uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus; + uint32 off_in_sec = current_off % 512; + + // Calculate how many bytes can be written this time + uint32 m = 512 - off_in_sec; + if (n - tot < m) + m = n - tot; + + struct Buf *b = BufferCache::bread(dev, sec / 2); + uint32 block_offset = (sec % 2) * 512 + off_in_sec; + + if (isUser) + { + if (VM::copyin(myproc()->pagetable, (char *)(b->data + block_offset), (uint64)src, m) < 0) + { + BufferCache::brelse(b); + return -1; + } + } + else + { + memmove(b->data + block_offset, src, m); + } + + BufferCache::bwrite(b); + BufferCache::brelse(b); + + tot += m; + src += m; + current_off += m; + } + if (off + n > size) + { + size = off + n; + update(); + } + + return tot; +} + +Inode *FAT32Inode::create(const char *name, short type, short major, short minor) +{ + Inode *exist = lookup(name); + if (exist) + { + return exist; + } + + struct FAT32_DirEnt de; + memset(&de, 0, sizeof(de)); + generate_short_name(de.name, name); + de.attr = (type == T_DIR) ? ATTR_DIRECTORY : ATTR_ARCHIVE; + + uint32 new_dir_clus = 0; + if (type == T_DIR) + { + new_dir_clus = fs->alloc_cluster(); + if (new_dir_clus == 0) + return nullptr; + + de.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); + de.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); + } + else + { + de.fileSize = 0; + } + + uint32 clus = first_clus; + bool written = false; + + while (true) + { + uint32 sec = fs->cluster_to_sector(clus); + + for (uint32 i = 0; i < fs->sectors_per_cluster; i++) + { + struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); + uint8 *data = (uint8 *)b->data + ((sec + i) % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + if (data[off] == 0x00 || data[off] == 0xE5) + { + memmove(data + off, &de, 32); + BufferCache::bwrite(b); + BufferCache::brelse(b); + written = true; + break; + } + } + + if (written) + break; + BufferCache::brelse(b); + } + + if (written) + break; + + uint32 next = fs->next_cluster(clus); + if (next >= 0x0FFFFFF8) + { + uint32 new_parent_clus = fs->alloc_cluster(); + if (new_parent_clus == 0) + return nullptr; + + uint32 fat_off = clus * 4; + uint32 fat_sec = fs->fat_start_sector + (fat_off / 512); + struct Buf *fb = BufferCache::bread(dev, fat_sec / 2); + *(uint32 *)(fb->data + (fat_sec % 2) * 512 + (fat_off % 512)) = new_parent_clus; + BufferCache::bwrite(fb); + BufferCache::brelse(fb); + + uint32 s = fs->cluster_to_sector(new_parent_clus); + for (uint32 k = 0; k < fs->sectors_per_cluster; k++) + { + struct Buf *zb = BufferCache::bread(dev, (s + k) / 2); + uint8 *zdata = (uint8 *)zb->data + ((s + k) % 2) * 512; + memset(zdata, 0, 512); + + if (k == 0) + { + memmove(zdata, &de, 32); + } + + BufferCache::bwrite(zb); + BufferCache::brelse(zb); + } + + written = true; + break; + } + clus = next; + } + + if (written && type == T_DIR) + { + struct FAT32_DirEnt dot; + memset(&dot, 0, 32); + memset(dot.name, ' ', 11); + dot.name[0] = '.'; + dot.attr = ATTR_DIRECTORY; + dot.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF); + dot.fstClusLO = (uint16)(new_dir_clus & 0xFFFF); + + struct FAT32_DirEnt dotdot; + memset(&dotdot, 0, 32); + memset(dotdot.name, ' ', 11); + dotdot.name[0] = '.'; + dotdot.name[1] = '.'; + dotdot.attr = ATTR_DIRECTORY; + + uint32 parent_clus_num = this->first_clus; + if (parent_clus_num == fs->root_cluster) + { + parent_clus_num = 0; + } + dotdot.fstClusHI = (uint16)((parent_clus_num >> 16) & 0xFFFF); + dotdot.fstClusLO = (uint16)(parent_clus_num & 0xFFFF); + + uint32 s = fs->cluster_to_sector(new_dir_clus); + + struct Buf *zb = BufferCache::bread(dev, s / 2); + uint8 *zdata = (uint8 *)zb->data + (s % 2) * 512; + + memmove(zdata, &dot, 32); // Offset 0 + memmove(zdata + 32, &dotdot, 32); // Offset 32 + + BufferCache::bwrite(zb); + BufferCache::brelse(zb); + } + + if (written) + { + return lookup(name); + } + + return nullptr; +} + +void FAT32Inode::truncate() +{ + if (first_clus == 0) + { + if (size != 0) + { + size = 0; + update(); + } + return; + } + + fs->free_cluster_chain(first_clus); + + size = 0; + first_clus = 0; + + update(); +} + +int FAT32Inode::unlink(const char *name) +{ + if (type != T_DIR) + return -1; + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + return -1; + + uint32_t clus = first_clus; + bool found = false; + + // [FIX] Deadlock prevention: Do not hold buffer while looking up inode + uint32_t target_block = 0; + uint32_t target_offset = 0; + struct FAT32_DirEnt de; + + while (true) + { + uint32_t sec = fs->cluster_to_sector(clus); + for (uint32 i = 0; i < fs->sectors_per_cluster; ++i) + { + struct Buf *b = BufferCache::bread(dev, (sec + i) / 2); + uint8_t *data = (uint8_t *)b->data + ((sec + i) % 2) * 512; + + for (int off = 0; off < 512; off += 32) + { + memmove(&de, data + off, 32); + if (de.name[0] == 0x00) // end of dir + { + BufferCache::brelse(b); + return -1; // Not found + } + if (de.name[0] == 0xE5) + continue; // have been unlink + if (de.attr & ATTR_LONG_NAME) + continue; + + if (match_name(de.name, name)) + { + target_block = (sec + i) / 2; + target_offset = ((sec + i) % 2) * 512 + off; + found = true; + break; + } + } + + BufferCache::brelse(b); // Always release buffer + if (found) + break; + } + + if (found) + break; + + clus = fs->next_cluster(clus); + if (clus >= 0x0FFFFFF8) + return -1; + } + + if (found) + { + Inode *target_ip = lookup(name); + if (target_ip) + { + VFS::ilock(target_ip); + if (target_ip->type == T_DIR) + { + VFS::iunlockput(target_ip); + return -1; + } + + target_ip->nlink--; + target_ip->update(); + VFS::iunlockput(target_ip); + } + + // Re-read buffer to mark deletion + struct Buf *target_buf = BufferCache::bread(dev, target_block); + uint8_t *ptr = (uint8_t *)target_buf->data + target_offset; + *ptr = 0xE5; + + BufferCache::bwrite(target_buf); + BufferCache::brelse(target_buf); + return 0; + } + + return -1; +} + +// Test Function +void fat32_test() +{ + static bool inited = false; + if (!inited) + { + fat32_fs.init(); + inited = true; + } + + Drivers::uart_puts("\n[FAT32] Starting Test via Syscall...\n"); + + Inode *root = fat32_fs.root(); + if (root) + { + Drivers::uart_puts("[FAT32] Root Inode retrieved. Inum: "); + Drivers::uart_put_int(root->inum); + Drivers::uart_puts("\n"); + + Drivers::uart_puts("[FAT32] Looking for TEST.TXT...\n"); + Inode *node = root->lookup("test.txt"); + + if (node) + { + Drivers::uart_puts("[FAT32] Found TEST.TXT! Size: "); + Drivers::uart_put_int(node->size); + Drivers::uart_puts("\n"); + + char buf[128]; + int n = node->read(buf, 0, 127, 0); + if (n > 0) + { + buf[n] = 0; + Drivers::uart_puts("[FAT32] Content:\n>> "); + Drivers::uart_puts(buf); + Drivers::uart_puts("\n"); + } + else + { + Drivers::uart_puts("[FAT32] Read returned 0.\n"); + } + VFS::iput(node); + } + else + { + Drivers::uart_puts("[FAT32] TEST.TXT not found.\n"); + } + VFS::iput(root); + } + else + { + Drivers::uart_puts("[FAT32] Failed to get root inode.\n"); + } + + Drivers::uart_puts("[FAT32] Test Finished.\n"); +} \ No newline at end of file diff --git a/lume-legacy/src/fs/file.cc b/lume-legacy/src/fs/file.cc new file mode 100644 index 0000000..d814332 --- /dev/null +++ b/lume-legacy/src/fs/file.cc @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/file.h" +#include "kernel/spinlock.h" +#include "kernel/mm.h" +#include "drivers/uart.h" +#include "kernel/proc.h" +#include "kernel/slab.h" + +#define NFILE 100 +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +extern void *operator new(unsigned long, void *p); + +int Pipe::create_pair(file** f0, file** f1) +{ + void *mem = Slab::kmalloc(sizeof(Pipe)); + if (!mem) + return -1; + Pipe *pi = new (mem) Pipe(); + + *f0 = FileTable::alloc(); + *f1 = FileTable::alloc(); + + if (*f0 == nullptr || *f1 == nullptr) + { + if (*f0) + FileTable::close(*f0); + if (*f1) + FileTable::close(*f1); + pi->~Pipe(); + Slab::kfree(pi); + return -1; + } + + (*f0)->type = FD_PIPE; + (*f0)->readable = 1; + (*f0)->writable = 0; + (*f0)->pipe = pi; + + (*f1)->type = FD_PIPE; + (*f1)->readable = 0; + (*f1)->writable = 1; + (*f1)->pipe = pi; + + return 0; +} + +void Pipe::close(bool is_writer) +{ + lock.acquire(); + if (is_writer) + { + write_open = false; + ProcManager::wakeup(&nread); // Wake up the process that is currently reading + } + else + { + read_open = false; + ProcManager::wakeup(&nwrite); // Wake up the process that is writing + } + + bool empty = !read_open && !write_open; + lock.release(); + + if (empty) + { + this->~Pipe(); + Slab::kfree(this); + } +} + +int Pipe::write(uint64_t addr, int n) +{ + int i = 0; + struct Proc *p = myproc(); + + lock.acquire(); + while (i < n) + { + if (!read_open || p->xstate) // The read end is closed or the process is abnormal + { + lock.release(); + return -1; + } + + if (nwrite == nread + SIZE) // buffer is full + { + ProcManager::wakeup(&nread); + ProcManager::sleep(&nwrite, &lock); + } + else + { + char ch; + if (VM::copyin(p->pagetable, &ch, addr + i, 1) < 0) + break; + data[nwrite++ % SIZE] = ch; + i++; + } + } + ProcManager::wakeup(&nread); + lock.release(); + return i; +} + +int Pipe::read(uint64_t addr, int n) +{ + int i = 0; + struct Proc *p = myproc(); + + lock.acquire(); + while (nread == nwrite && write_open) // The buffer is empty and the write end is not closed + { + if (p->xstate) + { + lock.release(); + return -1; + } + ProcManager::sleep(&nread, &lock); + } + + for (i = 0; i < n; ++i) + { + if (nread == nwrite) + break; + char ch = data[nread++ % SIZE]; + if (VM::copyout(p->pagetable, addr + i, &ch, 1) < 0) + break; + } + ProcManager::wakeup(&nwrite); + lock.release(); + return i; +} + +namespace FileTable +{ + struct + { + Spinlock lock; + struct file files[NFILE]; + } ftable; + + void init() + { + ftable.lock.init("file_table"); + } + + struct file *alloc() + { + struct file *f; + ftable.lock.acquire(); + for (f = ftable.files; f < ftable.files + NFILE; f++) + { + if (f->ref == 0) + { + f->ref = 1; + ftable.lock.release(); + return f; + } + } + ftable.lock.release(); + return nullptr; + } + + struct file *dup(struct file *f) + { + ftable.lock.acquire(); + if (f->ref < 1) + { + Drivers::uart_puts("panic: file dup\n"); + while (1) + ; + } + f->ref++; + ftable.lock.release(); + return f; + } + + void close(struct file *f) + { + struct file ff; + + ftable.lock.acquire(); + if(f->ref < 1) + { + Drivers::uart_puts("panic: file close\n"); + while (1) + ; + } + f->ref--; + if (f->ref > 0) + { + ftable.lock.release(); + return; + } + + ff = *f; + f->ref = 0; + f->type = FD_NONE; + ftable.lock.release(); + if (ff.type == FD_INODE) + { + // Release the corresponding Inode + VFS::iput(ff.ip); + } + else if (ff.type == FD_PIPE) + { + if (ff.pipe) + ff.pipe->close(ff.writable); + } + } + + int read(struct file *f, uint64 addr, int n) + { + if (f->readable == 0) + return -1; + if (f->type == FD_INODE) + { + VFS::ilock(f->ip); + + if (f->ip->type == T_DEVICE) + { + if (f->ip->major == 1) + { + VFS::iunlock(f->ip); + return Drivers::console_read(addr, n); + } + } + + int r = f->ip->read((char*)addr, f->off, n, 1); + if(r > 0) + { + f->off += r; + } + VFS::iunlock(f->ip); + return r; + } + else if (f->type == FD_PIPE) + { + if (f->pipe) + return f->pipe->read(addr, n); + } + return -1; + } + + int write(struct file *f, uint64 addr, int n) + { + if (f->writable == 0) + return -1; + if (f->type == FD_INODE) + { + VFS::ilock(f->ip); + + if (f->ip->type == T_DEVICE) + { + if (f->ip->major == 1) // Console + { + VFS::iunlock(f->ip); + struct Proc *proc = myproc(); + constexpr int MAX_WRITE_BUF = 128; + char buf[MAX_WRITE_BUF]; + int i = 0; + while (i < n) + { + int len = n - i; + if (len > MAX_WRITE_BUF) + len = MAX_WRITE_BUF; + + if (VM::copyin(proc->pagetable, buf, addr + i, static_cast(len)) < 0) + { + return -1; + } + + for (int j = 0; j < len; j++) + { + Drivers::uart_putc(buf[j]); + } + i += len; + } + return n; + } + } + + int r = f->ip->write((char *)addr, f->off, n, 1); + if (r > 0) + { + f->off += r; + } + VFS::iunlock(f->ip); + return r; + } + else if (f->type == FD_PIPE) + { + if (f->pipe) + return f->pipe->write(addr, n); + } + + return -1; + } + + int stat(struct file *f, uint64 addr) + { + if (f->type == FD_INODE) + { + struct kstat st; + VFS::ilock(f->ip); + f->ip->getattr(&st); + VFS::iunlock(f->ip); + + struct Proc *p = myproc(); + if (VM::copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) + return -1; + return 0; + } + return -1; + } + + int lseek(struct file *f, int offset, int whence) + { + if (f->type != FD_INODE) + return -1; + + VFS::ilock(f->ip); + + int new_off = f->off; + int size = f->ip->size; + + switch (whence) + { + case SEEK_SET: + new_off = offset; + break; + case SEEK_CUR: + new_off += offset; + break; + case SEEK_END: + new_off = size + offset; + break; + default: + VFS::iunlock(f->ip); + return -1; + } + + if (new_off < 0) + { + VFS::iunlock(f->ip); + return -1; + } + + f->off = new_off; + VFS::iunlock(f->ip); + return new_off; + } +} // namespace FileTable diff --git a/lume-legacy/src/fs/fs.cc b/lume-legacy/src/fs/fs.cc new file mode 100644 index 0000000..fc8f555 --- /dev/null +++ b/lume-legacy/src/fs/fs.cc @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "fs/fs.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +// #include "fs/fat32.h" // Decoupled +#include "lib/string.h" + +static Inode *global_root_inode = nullptr; + +static const char *skipelem(const char *path, char *name) +{ + const char *s; + int len; + + while (*path == '/') + path++; + if (*path == 0) + return nullptr; + + s = path; + while (*path != '/' && *path != 0) + path++; + + len = path - s; + if (len >= 128) + len = 127; + + memmove(name, s, len); + name[len] = 0; + + while (*path == '/') + path++; + return path; +} + +void Inode::getattr(struct kstat *st) +{ + st->dev = dev; + st->ino = inum; + st->type = type; + st->nlink = nlink; + st->size = size; + + st->uid = 0; + st->gid = 0; + st->mode = 0; +} + +namespace VFS +{ + // File System Registry + static FileSystem *filesystems[8]; + static int fs_count = 0; + + void register_fs(FileSystem *fs) + { + if (fs_count < 8) + { + filesystems[fs_count++] = fs; + } + } + + void init() + { + Drivers::uart_puts("[VFS] Inode subsystem initialized.\n"); + } + + void iput(Inode *ip) + { + if (ip == nullptr) + return; + + ip->lock.acquire(); + if(ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: iput ref\n"); + while (1) + ; + } + ip->ref_cnt--; + int ref = ip->ref_cnt; + ip->lock.release(); + + if (ref == 0) + { + delete ip; + } + } + + Inode* idup(Inode *ip) + { + if (ip == nullptr) + return nullptr; + + ip->lock.acquire(); + ip->ref_cnt++; + ip->lock.release(); + return ip; + } + + void ilock(Inode *ip) + { + if (ip == 0 || ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: ilock\n"); + while (1) + ; + } + + ip->lock.acquire(); + + } + + void iunlock(Inode *ip) + { + if (ip == 0 || !ip->lock.holding() || ip->ref_cnt < 1) + { + Drivers::uart_puts("panic: iunlock\n"); + while (1) + ; + } + ip->lock.release(); + } + + void iunlockput(Inode *ip) + { + if (!ip) + return; + iunlock(ip); + iput(ip); + } + + void mount_root(Inode *root) + { + if (global_root_inode) + { + Drivers::uart_puts("VFS: Root already mounted!\n"); + return; + } + global_root_inode = root; + Drivers::uart_puts("[VFS] Root filesystem mounted.\n"); + } + + Inode *get_root() + { + if (global_root_inode == nullptr) + { + // Try to mount the first registered FS + if (fs_count > 0) + { + Drivers::uart_puts("[VFS] Lazy init: Mounting root FS...\n"); + filesystems[0]->init(); + mount_root(filesystems[0]->root()); + } + else + { + Drivers::uart_puts("[VFS] No filesystem registered!\n"); + } + } + if (!global_root_inode) + return nullptr; + return idup(global_root_inode); + } + + Inode *namei(const char *path) + { + char name[128]; + Inode *ip, *next; + struct Proc *p = myproc(); + + // 1. 确定起点 + if (*path == '/') + { + ip = get_root(); + } + else + { + if (p->cwd == nullptr) + { + ip = get_root(); + } + else + { + ip = idup(p->cwd); + } + } + + if (ip == nullptr) + return nullptr; + + while ((path = skipelem(path, name)) != nullptr) + { + if (strcmp(name, ".") == 0) + { + continue; + } + + if (strcmp(name, "..") == 0) + { + Inode *root = get_root(); + bool is_root = (ip->inum == root->inum); + iput(root); + + if (is_root) + { + continue; + } + } + + ilock(ip); + + if (ip->type != T_DIR) + { + iunlockput(ip); + return nullptr; + } + + next = ip->lookup(name); + iunlockput(ip); + + ip = next; + if (ip == nullptr) + return nullptr; + } + + return ip; + } + + Inode *nameiparent(const char* path, char* name) + { + Inode* ip; + Inode* next; + + if (*path == '/') + ip = get_root(); + else + ip = idup(myproc()->cwd ? myproc()->cwd : get_root()); + + while ((path = skipelem(path, name)) != nullptr) + { + if (*path == 0) + { + return ip; + } + + ilock(ip); + if (ip->type != T_DIR) + { + iunlockput(ip); + return nullptr; + } + + next = ip->lookup(name); + iunlockput(ip); + + ip = next; + if (!ip) + return nullptr; + } + iput(ip); + return nullptr; + } +} // namespace VFS diff --git a/lume-legacy/src/kernel/bio.cc b/lume-legacy/src/kernel/bio.cc new file mode 100644 index 0000000..5f1aae9 --- /dev/null +++ b/lume-legacy/src/kernel/bio.cc @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/buf.h" +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "drivers/virtio.h" +#include "drivers/uart.h" + +namespace BufferCache +{ + struct + { + Spinlock lock; + struct Buf buf[NBUF]; + struct Buf head; + } bcache; + + void init() + { + bcache.lock.init("bcache"); + // Initialize LRU doubly circular linked list + bcache.head.prev = &bcache.head; + bcache.head.next = &bcache.head; + for (struct Buf *b = bcache.buf; b < bcache.buf + NBUF; ++b) + { + b->lock.init("buffer"); + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + Drivers::uart_puts("[Bio] Buffer cache initialized.\n"); + } + + static struct Buf* bget(uint32 dev, uint32 blockno) + { + struct Buf* b; + bcache.lock.acquire(); + + // Attempt to hit the cache + for (b = bcache.head.next; b != &bcache.head; b = b->next) + { + if (b->dev == dev && b->blockno == blockno) + { + b->refcnt++; + bcache.lock.release(); + b->lock.acquire(); // Acquire Sleep Lock + return b; + } + } + + // Cache miss, evicting LRU + for (b = bcache.head.prev; b != &bcache.head; b = b->prev) + { + if (b->refcnt == 0) + { + b->dev = dev; + b->blockno = blockno; + b->valid = 0; + b->refcnt = 1; + bcache.lock.release(); + b->lock.acquire(); + return b; + } + } + + Drivers::uart_puts("panic: bget: no buffers\n"); + while (1) + ; + } + + struct Buf* bread(uint32 dev, uint32 blockno) + { + struct Buf *b = bget(dev, blockno); + + if (!b->valid) + { + VirtIO::rw(0, b->blockno, (char *)b->data); + b->valid = 1; + } + + return b; + } + + void bwrite(struct Buf *b) + { + if (!b->lock.holding()) + { + Drivers::uart_puts("panic: bwrite\n"); + while (1) + ; + } + VirtIO::rw(1, b->blockno, (char *)b->data); + } + + void brelse(struct Buf *b) + { + if (!b->lock.holding()) + { + Drivers::uart_puts("panic: brelse\n"); + while (1) + ; + } + b->lock.release(); + + bcache.lock.acquire(); + b->refcnt--; + + if (b->refcnt == 0) + { + b->next->prev = b->prev; + b->prev->next = b->next; + + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + + bcache.lock.release(); + } + + void bpin(struct Buf *b) + { + bcache.lock.acquire(); + b->refcnt++; + bcache.lock.release(); + } + + void bunpin(struct Buf* b) + { + bcache.lock.acquire(); + b->refcnt --; + bcache.lock.release(); + } + +} // namespace BufferCache diff --git a/lume-legacy/src/kernel/config.cc b/lume-legacy/src/kernel/config.cc new file mode 100644 index 0000000..36858a1 --- /dev/null +++ b/lume-legacy/src/kernel/config.cc @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" + +uint64 g_uart_base = 0; +// uint64 g_clint_base = 0; +uint64 g_plic_base = 0; +uint64 g_ram_start = 0; +uint64 g_ram_size = 0; + +extern "C" uint64 g_dtb_addr; \ No newline at end of file diff --git a/lume-legacy/src/kernel/cxx.cc b/lume-legacy/src/kernel/cxx.cc new file mode 100644 index 0000000..00f2799 --- /dev/null +++ b/lume-legacy/src/kernel/cxx.cc @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/pmm.h" +#include "drivers/uart.h" + +/* + * C++ Kernel Runtime Support + * + * * Strategy: Map directly to PMM::alloc_page(). + * Pros: Simple, works immediately for VFS. + * Cons: Allocates 4KB for every object (wasteful for small objects), + * but acceptable for current stage. + */ + +void *operator new(unsigned long size) +{ + // Safety check: PMM allocates 4096 bytes max per call + if (size > 4096) + { + Drivers::uart_puts("panic: new allocation too big (max 4096)\n"); + while (1) + ; + } + + void *p = PMM::alloc_page(); + if (!p) + { + Drivers::uart_puts("panic: new out of memory\n"); + while (1) + ; + } + // PMM::alloc_page returns zeroed memory usually, + // but standard 'new' doesn't guarantee it. + // The constructor will handle initialization. + return p; +} + +void *operator new[](unsigned long size) +{ + return operator new(size); +} + +void operator delete(void *p) +{ + if (p) + PMM::free_page(p); +} + +void operator delete[](void *p) +{ + if (p) + PMM::free_page(p); +} + +// C++14 Sized Deallocation (Required by modern compilers) +// The compiler optimizes 'delete' calls to pass the size of the object +void operator delete(void *p, unsigned long size) +{ + (void)size; // We ignore size because PMM always frees a full page + if (p) + PMM::free_page(p); +} + +void operator delete[](void *p, unsigned long size) +{ + (void)size; + if (p) + PMM::free_page(p); +} + +void *operator new(unsigned long size, void *ptr) +{ + (void)size; + return ptr; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/fdt.cc b/lume-legacy/src/kernel/fdt.cc new file mode 100644 index 0000000..ae09c84 --- /dev/null +++ b/lume-legacy/src/kernel/fdt.cc @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * LumeOS - FDT Parser + * + * Parses the Flattened Device Tree to discover hardware. + * Populates g_devices for use by all drivers. + */ +#include "common/types.h" +#include "kernel/fdt.h" +#include "lib/string.h" + +// Global device registry +DeviceRegistry g_devices; + +// Keep g_dtb_addr for PMM to protect DTB pages +// g_dtb_addr is defined in entry.S, g_uart_base in config.cc +extern "C" uint64 g_dtb_addr; +extern uint64 g_uart_base; + +static inline uint32 bswap(uint32 x) +{ + return ((x & 0xFF) << 24) | ((x & 0xFF00) << 8) | + ((x & 0xFF0000) >> 8) | ((x >> 24) & 0xFF); +} + +struct fdt_header +{ + uint32 magic; + uint32 totalsize; + uint32 off_dt_struct; + uint32 off_dt_strings; + uint32 off_mem_rsvmap; + uint32 version; + uint32 last_comp_version; + uint32 boot_cpuid_phys; + uint32 size_dt_strings; + uint32 size_dt_struct; +}; + +#define FDT_MAGIC 0xd00dfeed +#define FDT_BEGIN_NODE 1 +#define FDT_END_NODE 2 +#define FDT_PROP 3 +#define FDT_NOP 4 +#define FDT_END 9 + +// Check if 'str' starts with 'prefix' +static bool starts_with(const char *str, const char *prefix) +{ + while (*prefix) + { + if (*str != *prefix) + return false; + str++; + prefix++; + } + return true; +} + +// Check if 'haystack' contains 'needle' as a substring within 'len' bytes +// (compatible strings may have multiple null-separated entries) +static bool compat_match(const uint8 *data, uint32 len, const char *needle) +{ + uint32 nlen = strlen(needle); + for (uint32 i = 0; i + nlen <= len; i++) + { + if (memcmp(data + i, needle, nlen) == 0) + return true; + } + return false; +} + +// Add a MMIO region to the registry (avoids duplicates) +static void add_mmio_region(uint64 base, uint64 size) +{ + if (base == 0 || size == 0) + return; + // Only track I/O regions below RAM + if (base >= 0x80000000ULL) + return; + if (g_devices.mmio_count >= DeviceRegistry::MAX_MMIO) + return; + + // Check for overlap/duplicate + for (int i = 0; i < g_devices.mmio_count; i++) + { + if (g_devices.mmio_regions[i].base == base) + return; + } + + g_devices.mmio_regions[g_devices.mmio_count].base = base; + g_devices.mmio_regions[g_devices.mmio_count].size = size; + g_devices.mmio_count++; +} + +void fdt_parse(uint64 dtb) +{ + // Clear registry + memset(&g_devices, 0, sizeof(g_devices)); + + if (dtb == 0) + return; + + g_dtb_addr = dtb; + + struct fdt_header *header = (struct fdt_header *)dtb; + if (bswap(header->magic) != FDT_MAGIC) + return; + + uint8 *struct_ptr = (uint8 *)(dtb + bswap(header->off_dt_struct)); + char *strings_ptr = (char *)(dtb + bswap(header->off_dt_strings)); + + // Parsing state + char current_node_name[64]; + current_node_name[0] = '\0'; + + // Per-node state + uint64 current_reg_addr = 0; + uint64 current_reg_size = 0; + uint32 current_irq = 0; + const uint8 *current_compat = nullptr; + uint32 current_compat_len = 0; + bool has_reg = false; + bool has_compat = false; + // bool has_irq = false; // Unused + + // Depth tracking (for #address-cells / #size-cells) + // Default for root: 2 address-cells, 2 size-cells + int depth = 0; + + while (1) + { + uint32 token = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + + if (token == FDT_END) + break; + if (token == FDT_NOP) + continue; + + if (token == FDT_BEGIN_NODE) + { + // Save node name + // char *name = (char *)struct_ptr; + int i = 0; + while (*struct_ptr != 0 && i < 63) + { + current_node_name[i++] = *struct_ptr; + struct_ptr++; + } + current_node_name[i] = '\0'; + while (*struct_ptr != 0) + struct_ptr++; + struct_ptr++; + struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); + + // Reset per-node state + current_reg_addr = 0; + current_reg_size = 0; + current_irq = 0; + current_compat = nullptr; + current_compat_len = 0; + has_reg = false; + has_compat = false; + // has_irq = false; + + depth++; + } + else if (token == FDT_END_NODE) + { + // Process the node we just finished + if (has_reg && has_compat) + { + // UART: ns16550a + if (compat_match(current_compat, current_compat_len, "ns16550")) + { + g_devices.uart.base_addr = current_reg_addr; + g_devices.uart.size = current_reg_size ? current_reg_size : 0x100; + g_devices.uart.irq = current_irq; + add_mmio_region(current_reg_addr, g_devices.uart.size); + } + // PLIC + else if (compat_match(current_compat, current_compat_len, "plic")) + { + g_devices.plic.base_addr = current_reg_addr; + g_devices.plic.size = current_reg_size ? current_reg_size : 0x4000000; + g_devices.plic.irq = 0; + add_mmio_region(current_reg_addr, g_devices.plic.size); + } + // CLINT + else if (compat_match(current_compat, current_compat_len, "clint")) + { + g_devices.clint.base_addr = current_reg_addr; + g_devices.clint.size = current_reg_size ? current_reg_size : 0x10000; + g_devices.clint.irq = 0; + add_mmio_region(current_reg_addr, g_devices.clint.size); + } + // VirtIO + else if (compat_match(current_compat, current_compat_len, "virtio")) + { + if (g_devices.virtio_count < DeviceRegistry::MAX_VIRTIO) + { + int idx = g_devices.virtio_count++; + g_devices.virtio[idx].base_addr = current_reg_addr; + g_devices.virtio[idx].size = current_reg_size ? current_reg_size : 0x1000; + g_devices.virtio[idx].irq = current_irq; + add_mmio_region(current_reg_addr, g_devices.virtio[idx].size); + } + } + } + + // Memory node: name starts with "memory" + if (has_reg && starts_with(current_node_name, "memory")) + { + g_devices.mem_base = current_reg_addr; + g_devices.mem_size = current_reg_size; + } + + depth--; + } + else if (token == FDT_PROP) + { + uint32 len = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + uint32 nameoff = bswap(*(uint32 *)struct_ptr); + struct_ptr += 4; + + char *name = strings_ptr + nameoff; + uint8 *data = struct_ptr; + + struct_ptr += len; + struct_ptr = (uint8 *)(((uint64)struct_ptr + 3) & ~3); + + if (strcmp(name, "reg") == 0 && len >= 8) + { + // Assume 2 address-cells, 2 size-cells (standard for QEMU virt) + uint32 hi = bswap(*(uint32 *)data); + uint32 lo = bswap(*(uint32 *)(data + 4)); + current_reg_addr = ((uint64)hi << 32) | lo; + + if (len >= 16) + { + uint32 shi = bswap(*(uint32 *)(data + 8)); + uint32 slo = bswap(*(uint32 *)(data + 12)); + current_reg_size = ((uint64)shi << 32) | slo; + } + has_reg = true; + } + else if (strcmp(name, "compatible") == 0) + { + current_compat = data; + current_compat_len = len; + has_compat = true; + } + else if (strcmp(name, "interrupts") == 0 && len >= 4) + { + current_irq = bswap(*(uint32 *)data); + // has_irq = true; + } + else if (strcmp(name, "interrupts-extended") == 0 && len >= 8) + { + // interrupts-extended = <&phandle irq_num> + // IRQ number is typically the second cell + current_irq = bswap(*(uint32 *)(data + 4)); + // has_irq = true; + } + } + } + + // Legacy compat + g_uart_base = g_devices.uart.base_addr; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/initcode.S b/lume-legacy/src/kernel/initcode.S new file mode 100644 index 0000000..22e55ca --- /dev/null +++ b/lume-legacy/src/kernel/initcode.S @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .rodata +.global initcode_start +.global initcode_end +.align 4 + +initcode_start: + .incbin "build/user/initcode" +initcode_end: diff --git a/lume-legacy/src/kernel/main.cc b/lume-legacy/src/kernel/main.cc new file mode 100644 index 0000000..6a19915 --- /dev/null +++ b/lume-legacy/src/kernel/main.cc @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "drivers/plic.h" +#include "drivers/virtio.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/slab.h" +#include "kernel/proc.h" +#include "kernel/trap.h" +#include "kernel/timer.h" +#include "kernel/cpu.h" +#include "kernel/buf.h" +#include "fs/fs.h" +#include "fs/file.h" +#include "fs/fat32.h" +#include "lib/string.h" + +#include "kernel/fdt.h" // fdt_parse + +extern uint64 g_dtb_addr; + +// Placement new support (referenced from cxx.cc) +void *operator new(unsigned long size, void *ptr); + +volatile static int started = 0; + +extern "C" void kernel_main(uint64 hartid, uint64 dtb) +{ + if (hartid == 0) + { + fdt_parse(dtb); + + Drivers::uart_init(); + Drivers::uart_puts("\n[Lume OS] Booting...\n"); + + Drivers::uart_puts("[Boot] PMP configured.\n"); + + PMM::init(); // Physical Memory Management + VM::kvminit(); // Kernel Pagetable + VM::kvminithart(); // Enable MMU + Slab::init(); + Trap::init(); // Trap Management + Trap::inithart(); + PLIC::init(); + PLIC::inithart(); + VirtIO::init(); + BufferCache::init(); + VFS::init(); + // Initialize global FAT32 object manually (global constructors not supported yet) + new (&fat32_fs) FAT32FileSystem(); + VFS::register_fs(&fat32_fs); + FileTable::init(); + Timer::init(); + ProcManager::init(); // Process Management + ProcManager::user_init(); // Initialize first user process + + __atomic_store_n(&started, 1, __ATOMIC_RELEASE); + + Drivers::uart_puts("[Boot] System initialized. Entering scheduler...\n"); + } + else + { + while (__atomic_load_n(&started, __ATOMIC_ACQUIRE) == 0) + ; + + VM::kvminithart(); + Trap::inithart(); + PLIC::inithart(); + Timer::init(); + Drivers::uart_puts("[Boot] Hart started.\n"); + } + + ProcManager::scheduler(); // Enable Scheduler +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/mm/pmm.cc b/lume-legacy/src/kernel/mm/pmm.cc new file mode 100644 index 0000000..d3d99ba --- /dev/null +++ b/lume-legacy/src/kernel/mm/pmm.cc @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/fdt.h" +// #include "kernel/spinlock.h" +#include "drivers/uart.h" +#include "lib/string.h" + +extern "C" char kernel_end[]; +extern uint64 g_dtb_addr; + +// Default memory size 128MB, will be overwritten by FDT +uint64 g_phys_size = 128 * 1024 * 1024; + +namespace PMM +{ + // Global metadata array pointer + struct Page *mem_map = nullptr; + // Total number of physical memory pages + uint64 total_pages = 0; + // Free list array (free_area[k] stores the head of the free block list of order=k) + struct Page *free_area[MAX_ORDER]; + // Protective lock + Spinlock pmm_lock; + + struct Page *pa_to_page(uint64 pa) + { + if (pa < KERNBASE || pa >= PHYSTOP) + return nullptr; + uint64 pfn = (pa - KERNBASE) >> PGSHIFT; + if (pfn >= total_pages) + return nullptr; + return &mem_map[pfn]; + } + + uint64 page_to_pa(struct Page *page) + { + uint64 pfn = page - mem_map; + return KERNBASE + (pfn << PGSHIFT); + } + + void list_add(struct Page *page, int order) + { + page->order = order; + page->flags = 0; // Mark as available + page->next = free_area[order]; + page->prev = nullptr; + + if (free_area[order]) + { + free_area[order]->prev = page; + } + free_area[order] = page; + } + + void list_del(struct Page* page, int order) + { + if (page->prev) + { + page->prev->next = page->next; + } + else + { + free_area[order] = page->next; + } + if (page->next) + { + page->next->prev = page->prev; + } + page->next = nullptr; + page->prev = nullptr; + } + + void init() + { + // Use FDT memory size if available + if (g_devices.mem_size > 0) + { + g_phys_size = g_devices.mem_size; + } + + pmm_lock.init("pmm"); + + uint64 kern_end_pa = PGROUNDUP((uint64)kernel_end); + total_pages = (PHYSTOP - KERNBASE) / PGSIZE; + + Drivers::uart_puts(ANSI_GREEN "[PMM] Total RAM size: "); + Drivers::uart_put_int(g_phys_size / (1024 * 1024)); + Drivers::uart_puts(" MB\n" ANSI_RESET); + // Place the mem_map array + // mem_map is located immediately after kernel_end + mem_map = (struct Page*)kern_end_pa; + + uint64 mem_map_size = total_pages * sizeof(struct Page); + uint64 free_mem_start = PGROUNDUP(kern_end_pa + mem_map_size); + + Drivers::uart_puts(ANSI_GREEN "[PMM] MemMap placed at: " ); + Drivers::print_hex((uint64)mem_map); + Drivers::uart_puts("\n" ANSI_RESET); + + Drivers::uart_puts(ANSI_GREEN "[PMM] Free memory starts at: "); + Drivers::print_hex(free_mem_start); + Drivers::uart_puts("\n" ANSI_RESET); + + if (free_mem_start >= PHYSTOP) + { + Drivers::uart_puts(ANSI_RED "[PMM] Error - Kernel too large, no memory left!\n" ANSI_RESET); + while (1) + ; + } + + // Initialize all page states + for (uint64 i = 0; i < total_pages; i++) + { + mem_map[i].flags = PG_reserved; + mem_map[i].order = 0; + mem_map[i].refcnt = 0; + mem_map[i].next = nullptr; + mem_map[i].prev = nullptr; + } + + for (int i = 0; i < MAX_ORDER; i++) + { + free_area[i] = nullptr; + } + + // Release actual free memory to the allocator + // Range: [free_mem_start, PHYSTOP) + // Note: need to avoid the page where the DTB is located + uint64 dtb_pa_start = PGROUNDDOWN(g_dtb_addr); + uint64 dtb_pa_end = PGROUNDUP(g_dtb_addr + 0x10000); + + uint64 p = free_mem_start; + while (p < PHYSTOP) + { + if (p >= dtb_pa_start && p < dtb_pa_end) + { + p += PGSIZE; + continue; + } + + // Mark the page as Used, then call free_pages to release it + // This allows free_pages to handle the merging logic automatically + struct Page *page = pa_to_page(p); + if (page) + { + page->flags = PG_used; + free_pages((void *)p, 0); // Release page by page, let Buddy merge automatically + } + p += PGSIZE; + + + } + + } + + void *alloc_pages(int order) + { + if (order < 0 || order >= MAX_ORDER) + return nullptr; + + pmm_lock.acquire(); + + int current_order = order; + while (current_order < MAX_ORDER) + { + if (free_area[current_order]) + { + // Find free block + struct Page *page = free_area[current_order]; + list_del(page, current_order); + + // If the block is too large, split it. + while (current_order > order) + { + current_order--; + // page is the left part (Buddy A) + // buddy is the right part (Buddy B) + // Pointer addition: Page* + n is equivalent to address offset n * sizeof(Page) + // Since mem_map is linearly mapped, mem_map[i + (1<flags = PG_used; + page->order = order; // Record the orders that have been assigned + page->refcnt = 1; + + pmm_lock.release(); + return (void *)page_to_pa(page); + } + current_order++; + } + pmm_lock.release(); + Drivers::uart_puts("PMM: OOM alloc_pages\n"); + return nullptr; + } + + void free_pages(void *pa, int order) + { + if (!pa) + return; + struct Page *page = pa_to_page((uint64)pa); + if (!page) + return; + pmm_lock.acquire(); + + if (!(page->flags & PG_used)) + { + Drivers::uart_puts("PMM: Double free warning\n"); + pmm_lock.release(); + return; + } + + // Mark as basic status + page->flags = 0; + page->refcnt = 0; + + // try merge + uint64 page_idx = page - mem_map; + + while (order < MAX_ORDER - 1) + { + // calculate Buddy index: index ^ (1 << order) + uint64 buddy_idx = page_idx ^ (1 << order); + struct Page *buddy = &mem_map[buddy_idx]; + + // Check if the Buddy can be merged: + // 1. Must be within the memory range + // 2. Must be free (flags == 0) + // 3. Must be at the current order (to prevent merging already split small blocks) + if (buddy_idx >= total_pages || (buddy->flags & PG_used) || (buddy->flags & PG_reserved) || buddy->order != order) + { + break; + } + + // Can be merged! + // Remove Buddy from the free list + list_del(buddy, order); + + // Update the current block to the merged block + // The starting address of the merged block is the smaller one between index and buddy_index + if (buddy_idx < page_idx) + { + page = buddy; + page_idx = buddy_idx; + } + + order++; + } + + // Insert into the final order linked list + list_add(page, order); + + pmm_lock.release(); + } + + void *alloc_page() + { + void *p = alloc_pages(0); + if (p) + { + memset(p, 0, PGSIZE); + } + return p; + } + + void free_page(void *pa) + { + free_pages(pa, 0); + } + + void print_free_lists() + { + // Debug print the number of free blocks at each level + Drivers::uart_puts("PMM Stats: [ "); + for (int i = 0; i < MAX_ORDER; i++) + { + int count = 0; + struct Page *p = free_area[i]; + while (p) + { + count++; + p = p->next; + } + if (count > 0) + { + Drivers::uart_puts("Order"); + // Drivers::uart_put_int(i); + Drivers::uart_puts(":"); + // Drivers::uart_put_int(count); + Drivers::uart_puts(" "); + } + } + Drivers::uart_puts("]\n"); + } +} // namespace PMM diff --git a/lume-legacy/src/kernel/mm/slab.cc b/lume-legacy/src/kernel/mm/slab.cc new file mode 100644 index 0000000..3bb6b1b --- /dev/null +++ b/lume-legacy/src/kernel/mm/slab.cc @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/slab.h" +#include "kernel/pmm.h" +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "lib/string.h" + +#define PG_slab 0x04 + +// Supported object size (must be a multiple of 8 and <= 2048) +const uint64 kmalloc_sizes[] = { + 16, 32, 64, 128, 256, 512, 1024, 2048}; +const int NUM_CACHES = 8; + +struct SlabHeader +{ + struct SlabHeader *next; // Next slab in the linked list + uint16 free_idx; // Index of the head of the free list (0xFFFF means none) + uint16 used_count; // Number of allocated objects + // Followed immediately by object data... + // The free list of objects is embedded in the first 2 bytes of the object itself (stores the index of the next object) + uint32 padding; +}; + +struct KmemCache +{ + Spinlock lock; + uint64 obj_size; + uint64 obj_count_per_slab; + struct SlabHeader *slab_list; // Slab list with free objects + const char *name; +}; + +struct KmemCache caches[NUM_CACHES]; + +// Virtual address -> struct Page* +static struct Page *virt_to_page(void *ptr) +{ + uint64 pa = (uint64)ptr; + return PMM::pa_to_page(pa); +} + +// Slab Implementation + +namespace Slab +{ + + void init() + { + for (int i = 0; i < NUM_CACHES; i++) + { + caches[i].lock.init("slab_lock"); + caches[i].obj_size = kmalloc_sizes[i]; + caches[i].name = "kmalloc"; + + // Calculate how many objects can fit on a page + // Excluding the overhead of the SlabHeader + uint64 available = PGSIZE - sizeof(struct SlabHeader); + caches[i].obj_count_per_slab = available / caches[i].obj_size; + caches[i].slab_list = nullptr; + } + Drivers::uart_puts("[Slab] Initialized kmalloc caches.\n"); + } + + // Allocate a new slab page for the cache + // Must hold cache->lock + static struct SlabHeader *grow_cache(struct KmemCache *c) + { + void *p = PMM::alloc_page(); + if (!p) + return nullptr; + + // Mark this page as a Slab page + struct Page *page = virt_to_page(p); + if (page) + { + page->flags |= PG_slab; + int cache_idx = c - caches; + page->order = (uint8)cache_idx; + } + + struct SlabHeader *slab = (struct SlabHeader *)p; + slab->next = nullptr; + slab->used_count = 0; + slab->free_idx = 0; + slab->padding = 0; + + // Initialize the free list of objects + // The starting position of each object stores the index of the next object + // Starting position of the object data area + char *base = (char *)(slab + 1); + + for (uint i = 0; i < c->obj_count_per_slab - 1; i++) + { + // The content of the i-th object is i+1 + uint16 *ptr = (uint16 *)(base + i * c->obj_size); + *ptr = i + 1; + } + // The last object points to 0xFFFF + uint16 *ptr = (uint16 *)(base + (c->obj_count_per_slab - 1) * c->obj_size); + *ptr = 0xFFFF; + + return slab; + } + + void *kmalloc(uint64 size) + { + if (size == 0) + return nullptr; + + // Large objects go directly through PMM + if (size > 2048) + { + // calculate order + int order = 0; + uint64 req = size; + uint64 pages = (size + PGSIZE - 1) / PGSIZE; + (void)req; + + // calculate order: 2^order >= pages + while ((1UL << order) < pages) + { + order++; + } + if (order > MAX_ORDER) + return nullptr; + + void *p = PMM::alloc_pages(order); + // Here, page->flags defaults to PG_used, not PG_slab + // kfree needs to be able to recognize it + return p; + } + + int idx = 0; + while (idx < NUM_CACHES && kmalloc_sizes[idx] < size) + { + idx++; + } + + struct KmemCache *c = &caches[idx]; + c->lock.acquire(); + + if (!c->slab_list) + { + c->slab_list = grow_cache(c); + if (!c->slab_list) + { + c->lock.release(); + return nullptr; + } + } + + struct SlabHeader *slab = c->slab_list; + + int obj_idx = slab->free_idx; + char *base = (char *)(slab + 1); + void *ptr = base + obj_idx * c->obj_size; + + uint16 *link = (uint16 *)ptr; + slab->free_idx = *link; + slab->used_count++; + + if (slab->free_idx == 0xFFFF) + { + if (slab->next) + { + c->slab_list = slab->next; + slab->next = nullptr; + } + else + { + c->slab_list = nullptr; + } + } + + c->lock.release(); + + memset(ptr, 0, size); + return ptr; + } + + void kfree(void *ptr) + { + if (!ptr) + return; + + struct Page *page = virt_to_page(ptr); + if (!page) + return; + + if (!page || !(page->flags & PG_slab)) + { + PMM::free_pages(ptr, page->order); + return; + } + + int cache_idx = page->order; + if (cache_idx >= NUM_CACHES) + { + Drivers::uart_puts("Slab: Corrupted page metadata\n"); + return; + } + + struct KmemCache *c = &caches[cache_idx]; + c->lock.acquire(); + + struct SlabHeader *slab = (struct SlabHeader *)PGROUNDDOWN((uint64)ptr); + + if ((uint64)ptr < (uint64)(slab + 1) || (uint64)ptr >= (uint64)slab + PGSIZE) + { + Drivers::uart_puts("Slab: kfree invalid pointer\n"); + c->lock.release(); + return; + } + + char *base = (char *)(slab + 1); + int obj_idx = ((uint64)ptr - (uint64)base) / c->obj_size; + + uint16 *link = (uint16 *)ptr; + *link = slab->free_idx; + slab->free_idx = (uint16)obj_idx; + + slab->used_count--; + + if (slab->used_count == 0) + { + struct SlabHeader **pp = &c->slab_list; + // bool found = false; + while (*pp) + { + if (*pp == slab) + { + *pp = slab->next; + // found = true; + break; + } + pp = &(*pp)->next; + } + + page->flags &= ~PG_slab; + PMM::free_page(slab); + } + else + { + + if (slab->used_count == c->obj_count_per_slab - 1) + { + slab->next = c->slab_list; + c->slab_list = slab; + } + } + + c->lock.release(); + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/mm/vm.cc b/lume-legacy/src/kernel/mm/vm.cc new file mode 100644 index 0000000..be2f2f2 --- /dev/null +++ b/lume-legacy/src/kernel/mm/vm.cc @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/mm.h" +#include "kernel/pmm.h" +#include "kernel/riscv.h" +#include "common/types.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/fdt.h" + +extern "C" +{ + extern char kernel_end[]; + extern char trampoline[]; +} + +namespace VM +{ + + uint64 *kernel_pagetable; + + static void page_ref_inc(uint64 pa) + { + struct Page *p = PMM::pa_to_page(pa); + if (p) + { + __atomic_add_fetch(&p->refcnt, 1, __ATOMIC_RELAXED); + } + } + + static bool page_ref_dec(uint64 pa) + { + struct Page *p = PMM::pa_to_page(pa); + if (p) + { + if (__atomic_sub_fetch(&p->refcnt, 1, __ATOMIC_RELEASE) == 0) + { + return true; + } + } + return false; + } + + static uint64 *walk(uint64 *pagetable, uint64 va, int alloc) + { + if (va >= MAXVA) + return nullptr; + + for (int level = 2; level > 0; level--) + { + uint64 *pte = &pagetable[PX(level, va)]; + if (*pte & PTE_V) + { + pagetable = (uint64 *)PTE2PA(*pte); + } + else + { + if (!alloc || (pagetable = (uint64 *)PMM::alloc_page()) == nullptr) + return nullptr; + + memset(pagetable, 0, PGSIZE); + *pte = PA2PTE(pagetable) | PTE_V; + } + } + return &pagetable[PX(0, va)]; + } + + int mappages(uint64 *pagetable, uint64 va, uint64 size, uint64 pa, int perm) + { + uint64 a, last; + uint64 *pte; + + if (size == 0) + return -1; + + a = PGROUNDDOWN(va); + last = PGROUNDDOWN(va + size - 1); + + for (;;) + { + if ((pte = walk(pagetable, a, 1)) == nullptr) + return -1; + + if (*pte & PTE_V) + { + return -1; + } + + *pte = PA2PTE(pa) | perm | PTE_V; + + if (a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; + } + + uint64 *uvmcreate() + { + uint64 *pagetable = (uint64 *)PMM::alloc_page(); + if (pagetable) + { + memset(pagetable, 0, PGSIZE); + + if (mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0) + { + PMM::free_page(pagetable); + return nullptr; + } + } + return pagetable; + } + + void uvminit(uint64 *pagetable, uchar *src, uint64 sz) + { + char *mem; + + if (sz >= PGSIZE) + { + Drivers::uart_puts("uvminit: initcode too big!\n"); + return; + } + + mem = (char *)PMM::alloc_page(); + memset(mem, 0, PGSIZE); + + mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W | PTE_R | PTE_X | PTE_U); + memmove(mem, src, sz); + } + + uint64 uvmalloc(uint64 *pagetable, uint64 oldsz, uint64 newsz, int xperm) + { + char *mem; + uint64 a; + + if (newsz < oldsz) + return oldsz; + + oldsz = PGROUNDUP(oldsz); + for (a = oldsz; a < newsz; a += PGSIZE) + { + mem = (char *)PMM::alloc_page(); + if (mem == nullptr) + { + uvmdealloc(pagetable, a, oldsz); + return 0; + } + memset(mem, 0, PGSIZE); + if (mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_U | xperm) != 0) + { + PMM::free_page(mem); + uvmdealloc(pagetable, a, oldsz); + return 0; + } + } + return newsz; + } + + uint64 uvmdealloc(uint64 *pagetable, uint64 oldsz, uint64 newsz) + { + if (newsz >= oldsz) + return oldsz; + + if (PGROUNDUP(newsz) < PGROUNDUP(oldsz)) + { + int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE; + uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1); + } + + return newsz; + } + + void uvmunmap(uint64 *pagetable, uint64 va, uint64 npages, int do_free) + { + uint64 a; + uint64 *pte; + + if ((va % PGSIZE) != 0) + return; + + for (a = va; a < va + npages * PGSIZE; a += PGSIZE) + { + if ((pte = walk(pagetable, a, 0)) == nullptr) + continue; + + if ((*pte & PTE_V) == 0) + continue; + + if ((PTE_FLAGS(*pte) & (PTE_R | PTE_W | PTE_X)) == 0) + { + continue; + } + + if (do_free) + { + uint64 pa = PTE2PA(*pte); + if (page_ref_dec(pa)) + { + PMM::free_page((void *)pa); + } + } + *pte = 0; + } + } + + static void freewalk(uint64 *pagetable) + { + for (int i = 0; i < 512; i++) + { + uint64 pte = pagetable[i]; + if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) + { + uint64 *child = (uint64 *)PTE2PA(pte); + freewalk(child); + pagetable[i] = 0; + } + } + PMM::free_page(pagetable); + } + + void uvmfree(uint64 *pagetable, uint64 sz) + { + if (sz > 0) + uvmunmap(pagetable, 0, PGROUNDUP(sz) / PGSIZE, 1); + freewalk(pagetable); + } + + int uvmcopy(uint64 *oldpt, uint64 *newpt, uint64 sz) + { + uint64 *pte; + uint64 pa, i; + uint flags; + + for (i = 0; i < sz; i += PGSIZE) + { + if ((pte = walk(oldpt, i, 0)) == nullptr) + continue; + if ((*pte & PTE_V) == 0) + continue; + + pa = PTE2PA(*pte); + flags = PTE_FLAGS(*pte); + + if (flags & PTE_W) + { + flags &= ~PTE_W; + flags |= PTE_COW; + *pte = PA2PTE(pa) | flags; + } + + if (mappages(newpt, i, PGSIZE, pa, flags) != 0) + { + return -1; + } + + page_ref_inc(pa); + } + + sfence_vma(); + return 0; + } + + int handle_cow_fault(uint64 *pagetable, uint64 va); + + int copyout(uint64 *pagetable, uint64 dstva, char *src, uint64 len) + { + uint64 n, va0, pa0; + uint64 *pte; + + while (len > 0) + { + va0 = PGROUNDDOWN(dstva); + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + // Note: Maybe a bug + if (*pte & PTE_COW) + { + if (handle_cow_fault(pagetable, va0) < 0) + return -1; + // Re-walk because handle_cow_fault replaced the page mapping + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + } + + pa0 = PTE2PA(*pte); + n = PGSIZE - (dstva - va0); + if (n > len) + n = len; + + memmove((void *)(pa0 + (dstva - va0)), src, n); + + len -= n; + src += n; + dstva = va0 + PGSIZE; + } + return 0; + } + + int copyin(uint64 *pagetable, char *dst, uint64 srcva, uint64 len) + { + uint64 n, va0, pa0; + uint64 *pte; + + while (len > 0) + { + va0 = PGROUNDDOWN(srcva); + pte = walk(pagetable, va0, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + pa0 = PTE2PA(*pte); + n = PGSIZE - (srcva - va0); + if (n > len) + n = len; + + memmove(dst, (void *)(pa0 + (srcva - va0)), n); + + len -= n; + dst += n; + srcva = va0 + PGSIZE; + } + return 0; + } + + int copyinstr(uint64 *pagetable, char *dst, uint64 srcva, uint64 max) + { + uint64 n, va0, pa0; + uint64 *pte; + int got_null = 0; + uint64 len = 0; + + while (len < max) + { + va0 = PGROUNDDOWN(srcva); + pte = walk(pagetable, va0, 0); + + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + pa0 = PTE2PA(*pte); + + n = PGSIZE - (srcva - va0); + if (n > max - len) + n = max - len; + + char *p = (char *)(pa0 + (srcva - va0)); + while (n > 0) + { + if (*p == '\0') + { + *dst = '\0'; + got_null = 1; + break; + } + *dst = *p; + n--; + srcva++; + dst++; + p++; + len++; + } + if (got_null) + break; + } + + if (got_null) + return 0; + + return -1; + } + + int handle_cow_fault(uint64 *pagetable, uint64 va) + { + if (va >= MAXVA) + return -1; + + uint64 *pte = walk(pagetable, va, 0); + if (pte == nullptr || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + + if ((*pte & PTE_COW) == 0) + return -1; + + uint64 pa = PTE2PA(*pte); + uint64 flags = PTE_FLAGS(*pte); + + char *mem = (char *)PMM::alloc_page(); + if (mem == nullptr) + return -1; + + memmove(mem, (char *)pa, PGSIZE); + + uint64 new_flags = (flags | PTE_W) & ~PTE_COW; + *pte = PA2PTE((uint64)mem) | new_flags | PTE_V; + + if (page_ref_dec(pa)) + { + PMM::free_page((void *)pa); + } + + sfence_vma(); + return 0; + } + + static void kvmmap(uint64 va, uint64 pa, uint64 sz, int perm) + { + if (mappages(kernel_pagetable, va, sz, pa, perm) != 0) + { + Drivers::uart_puts("kvmmap: failed\n"); + while (1) + ; + } + } + + void kvminit() + { + kernel_pagetable = uvmcreate(); + + // 1. Map all device MMIO regions discovered by FDT + for (int i = 0; i < g_devices.mmio_count; i++) + { + uint64 base = g_devices.mmio_regions[i].base; + uint64 size = g_devices.mmio_regions[i].size; + kvmmap(base, base, size, PTE_R | PTE_W); + } + + // 2. Map Kernel Memory (PHYSTOP is now dynamic based on FDT) + kvmmap(KERNBASE, KERNBASE, PHYSTOP - KERNBASE, PTE_R | PTE_W | PTE_X); + } + + void kvminithart() + { + w_satp(MAKE_SATP(kernel_pagetable)); + sfence_vma(); + Drivers::uart_puts("[Boot] MMU ENABLED! Using virtual addresses.\n"); + } + + uint64 walkaddr(uint64 *pagetable, uint64 va) + { + uint64 *pte; + uint64 pa; + + if (va >= MAXVA) + return 0; + + pte = walk(pagetable, va, 0); + if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return 0; + + pa = PTE2PA(*pte); + return pa; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/exec.cc b/lume-legacy/src/kernel/proc/exec.cc new file mode 100644 index 0000000..8d8c467 --- /dev/null +++ b/lume-legacy/src/kernel/proc/exec.cc @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/riscv.h" +#include "kernel/proc.h" +#include "kernel/mm.h" +#include "kernel/elf.h" +#include "fs/fs.h" +#include "lib/string.h" +#include "drivers/uart.h" +#include "kernel/trap.h" + +extern char trampoline[]; + +// RAII: Page Table Resource Management +class VmGuard +{ +public: + uint64 *pagetable = nullptr; + uint64 sz = 0; + + VmGuard() : pagetable(VM::uvmcreate()) {} + + ~VmGuard() + { + if (pagetable) + { + VM::uvmfree(pagetable, sz); + } + } + + // Copying is prohibited + VmGuard(const VmGuard &) = delete; + VmGuard &operator=(const VmGuard &) = delete; + + // Submit resources: Ownership transfer, destructor no longer releases + uint64 *commit() + { + uint64 *pt = pagetable; + pagetable = nullptr; + return pt; + } +}; + +// RAII: Inode Lock Management +class InodeGuard +{ +public: + Inode *ip; + InodeGuard(Inode *i) : ip(i) + { + if (ip) + VFS::ilock(ip); + } + ~InodeGuard() + { + if (ip) + { + VFS::iunlockput(ip); // Automatically unlock and release references + } + } + // Copying is prohibited + InodeGuard(const InodeGuard &) = delete; + InodeGuard &operator=(const InodeGuard &) = delete; +}; + +// laod segment +static int loadseg(uint64 *pagetable, uint64 va, Inode *ip, uint64 offset, uint64 sz) +{ + for (uint64 i = 0; i < sz; i += PGSIZE) + { + uint64 pa = VM::walkaddr(pagetable, va + i); + if (pa == 0) + { + Drivers::uart_puts("[Exec Debug] loadseg: walkaddr failed\n"); + return -1; + } + + uint64 n = PGSIZE - (va + i) % PGSIZE; + if (i + n > sz) + n = sz - i; + + // Read physical address (isUser=0) + if (ip->read((char *)(pa + (va + i) % PGSIZE), offset + i, n, 0) != (int)n) + { + Drivers::uart_puts("[Exec Debug] loadseg: read failed\n"); + return -1; + } + } + return 0; +} + +namespace Exec +{ + int exec(char *path, char **argv) + { + // 1. Get Inode + Inode *raw_ip = VFS::namei(path); + if (!raw_ip) + { + Drivers::uart_puts("[Exec Debug] namei failed for path: "); + Drivers::uart_puts(path); + Drivers::uart_puts("\n"); + return -1; + } + InodeGuard iguard(raw_ip); + + // Get ELF head + struct elfhdr elf; + if (raw_ip->read((char *)&elf, 0, sizeof(elf), 0) != sizeof(elf)) + { + Drivers::uart_puts("[Exec Debug] read elf header failed\n"); + return -1; + } + if (elf.magic != ELF_MAGIC) + { + Drivers::uart_puts("[Exec Debug] invalid elf magic\n"); + return -1; + } + + // Create new pagetable + VmGuard vm_guard; + if (!vm_guard.pagetable) + { + Drivers::uart_puts("[Exec Debug] uvmcreate failed\n"); + return -1; + } + + // Load Program Segment + struct proghdr ph; + for (int i = 0; i < elf.phnum; i++) + { + uint64 off = elf.phoff + i * sizeof(ph); + if (raw_ip->read((char *)&ph, off, sizeof(ph), 0) != sizeof(ph)) + { + Drivers::uart_puts("[Exec Debug] read proghdr failed\n"); + return -1; + } + + if (ph.type != ELF_PROG_LOAD) + continue; + if (ph.memsz < ph.filesz) + return -1; + if (ph.vaddr + ph.memsz < ph.vaddr) + return -1; + + uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, vm_guard.sz, ph.vaddr + ph.memsz, PTE_W | PTE_X | PTE_R | PTE_U); + if (sz1 == 0) + { + Drivers::uart_puts("[Exec Debug] uvmalloc failed (segment)\n"); + return -1; + } + vm_guard.sz = sz1; + + if (loadseg(vm_guard.pagetable, ph.vaddr, raw_ip, ph.off, ph.filesz) < 0) + return -1; + } + + // Prepare user stack + uint64 sz = PGROUNDUP(vm_guard.sz); + + const int stack_data_pages = 16; + const int stack_total_pages = stack_data_pages + 1; + + uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, sz, sz + stack_total_pages * PGSIZE, PTE_W | PTE_R | PTE_U); + if (sz1 == 0) + { + Drivers::uart_puts("[Exec Debug] uvmalloc failed (stack)\n"); + return -1; + } + vm_guard.sz = sz1; + + VM::uvmunmap(vm_guard.pagetable, sz, 1, 0); + + uint64 sp = sz1; + uint64 stackbase = sp - stack_data_pages * PGSIZE; + uint64 ustack[32]; + uint64 argc = 0; + + // Push parameter string + for (; argv[argc]; argc++) + { + if (argc >= 31) + return -1; + sp -= strlen(argv[argc]) + 1; + sp -= sp % 16; + if (sp < stackbase) + { + Drivers::uart_puts("[Exec Debug] stack overflow (args)\n"); + return -1; + } + + if (VM::copyout(vm_guard.pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0) + { + Drivers::uart_puts("[Exec Debug] copyout arg string failed\n"); + return -1; + } + ustack[argc] = sp; + } + ustack[argc] = 0; + + // Push into parameter array + sp -= (argc + 1) * sizeof(uint64); + sp -= sp % 16; + if (sp < stackbase) + { + Drivers::uart_puts("[Exec Debug] stack overflow (argv array)\n"); + return -1; + } + if (VM::copyout(vm_guard.pagetable, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) < 0) + { + Drivers::uart_puts("[Exec Debug] copyout argv array failed\n"); + return -1; + } + + // Commit Changes + struct Proc *p = myproc(); + if (VM::mappages(vm_guard.pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) + { + Drivers::uart_puts("[Exec Debug] mappages TRAPFRAME failed\n"); + return -1; + } + + uint64 oldsz = p->sz; + uint64 *oldpagetable = p->pagetable; + + p->tf->a0 = argc; + p->tf->a1 = sp; + p->tf->sp = sp; + p->tf->epc = elf.entry; + + char *s = path, *last = path; + while (*s) + { + if (*s == '/') + last = s + 1; + s++; + } + + size_t len = 0; + while (last[len] && len < sizeof(p->name) - 1) + { + p->name[len] = last[len]; + len++; + } + p->name[len] = '\0'; + + // change pagetable + p->pagetable = vm_guard.commit(); + p->sz = vm_guard.sz; + + // release old pagetable + VM::uvmfree(oldpagetable, oldsz); + asm volatile("fence.i"); + + return 0; // iguard unlocks and releases IP upon destruction + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/proc.cc b/lume-legacy/src/kernel/proc/proc.cc new file mode 100644 index 0000000..3923fe8 --- /dev/null +++ b/lume-legacy/src/kernel/proc/proc.cc @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/proc.h" +#include "kernel/proc_def.h" +#include "kernel/runqueue.h" +#include "kernel/cpu.h" +#include "kernel/spinlock.h" +#include "kernel/pmm.h" +#include "kernel/mm.h" +#include "kernel/trap.h" +#include "kernel/riscv.h" +#include "kernel/slab.h" +#include "drivers/uart.h" +#include "fs/fs.h" +#include "fs/file.h" +#include "lib/string.h" + +// #define NPROC 64 +#define NCPU 8 + +extern "C" void swtch(struct Context *old_ctx, struct Context *new_ctx); +extern char uservec[], trampoline[]; +extern "C" void usertrap(); +extern "C" void userret(uint64, uint64); +extern char initcode_start[], initcode_end[]; + +// struct Proc procs[NPROC]; +struct Proc *proc_list_head = nullptr; +struct Proc *initproc = nullptr; +struct Spinlock proc_mem_lock; +struct cpu cpus[NCPU]; +struct RunQueue runqueues[NCPU]; + + +static int nextpid = 1; + + +struct cpu *mycpu() +{ + int id = r_tp(); + return &cpus[id]; +} + + +void RunQueue::init(const char *name) +{ + lock.init(name); + count = 0; + for (int i = 0; i < N_PRIO; i++) + { + heads[i] = nullptr; + tails[i] = nullptr; + } +} + +void RunQueue::enqueue(struct Proc *p) +{ + if (p->priority < 0 || p->priority >= N_PRIO) + p->priority = PRIO_MIN; + p->next = nullptr; + if (heads[p->priority] == nullptr) + { + heads[p->priority] = p; + tails[p->priority] = p; + } + else + { + tails[p->priority]->next = p; + tails[p->priority] = p; + } + count++; +} + +struct Proc *RunQueue::dequeue() +{ + for (int i = 0; i < N_PRIO; i++) + { + if (heads[i]) + { + struct Proc *p = heads[i]; + heads[i] = p->next; + if (!heads[i]) + tails[i] = nullptr; + p->next = nullptr; + count--; + return p; + } + } + return nullptr; +} + +struct Proc *RunQueue::steal() +{ + for (int i = N_PRIO - 1; i >= 0; i--) + { + if (heads[i]) + { + struct Proc *p = heads[i]; + heads[i] = p->next; + if (!heads[i]) + tails[i] = nullptr; + p->next = nullptr; + count--; + return p; + } + } + return nullptr; +} + +// Trap Return Functions + +void usertrapret() +{ + struct Proc *p = myproc(); + + intr_off(); + + // Set stvec to point to uservec in the trampoline + // When an interrupt occurs in user mode, the CPU will jump here + uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline); + w_stvec(trampoline_uservec); + + // Fill in the Trapframe, used when uservec saves the kernel state + p->tf->kernel_satp = r_satp(); + p->tf->kernel_sp = (uint64)p->kstack + PGSIZE; + p->tf->kernel_trap = (uint64)usertrap; + p->tf->kernel_hartid = r_tp(); + + // Set SSTATUS + // SPP = 0 (return to User Mode) + // SPIE = 1 (enable interrupts) + unsigned long x = r_sstatus(); + x &= ~SSTATUS_SPP; + x |= SSTATUS_SPIE; + w_sstatus(x); + + // Set SEPC to the user program counter + w_sepc(p->tf->epc); + + // Prepare user page table SATP + uint64 satp = MAKE_SATP(p->pagetable); + + // Calculate the virtual address of userret in the trampoline + uint64 fn = TRAMPOLINE + ((uint64)userret - (uint64)trampoline); + + // Jump to userret, with parameters being the TRAPFRAME address and the user page table SATP + ((void (*)(uint64, uint64))fn)(TRAPFRAME, satp); +} + + +void forkret() +{ + + struct cpu *c = mycpu(); + c->runqueue->lock.release(); + + // Debug + // Drivers::uart_puts("[Proc] forkret: jumping to user.\n"); + + // Enter user mode + usertrapret(); +} + +// ProcManager Implementation + +static Proc *allocproc() +{ + struct Proc *p; + proc_mem_lock.acquire(); + + p = (struct Proc *)Slab::kmalloc(sizeof(struct Proc)); + + if (!p) + { + proc_mem_lock.release(); + return 0; + } + + memset(p, 0, sizeof(struct Proc)); + + p->all_next = proc_list_head; + p->all_prev = nullptr; + if (proc_list_head) + proc_list_head->all_prev = p; + proc_list_head = p; + + + p->state = USED; + p->pid = nextpid++; + p->priority = 0; + p->killed = 0; + p->cpu = mycpu(); + p->cwd = nullptr; + for (int i = 0; i < NOFILE; i++) + p->ofile[i] = nullptr; + + if ((p->tf = (struct Trapframe *)PMM::alloc_page()) == 0) + { + p->state = UNUSED; + if (p->all_next) + p->all_next->all_prev = p->all_prev; + if (p->all_prev) + p->all_prev->all_next = p->all_next; + if (p == proc_list_head) + proc_list_head = p->all_next; + Slab::kfree(p); + proc_mem_lock.release(); + return 0; + } + + if ((p->kstack = PMM::alloc_page()) == 0) + { + PMM::free_page(p->tf); + p->state = UNUSED; + if (p->all_next) + p->all_next->all_prev = p->all_prev; + if (p->all_prev) + p->all_prev->all_next = p->all_next; + if (p == proc_list_head) + proc_list_head = p->all_next; + Slab::kfree(p); + + proc_mem_lock.release(); + return 0; + } + memset(p->kstack, 0, PGSIZE); + + memset(&p->context, 0, sizeof(p->context)); + + // Set the context so that the next time this process is scheduled, it starts executing directly from forkret + p->context.ra = (uint64)forkret; + p->context.sp = (uint64)p->kstack + PGSIZE; + return p; +} + +static void freeproc(struct Proc *p) +{ + if (p->tf) + PMM::free_page(p->tf); + p->tf = 0; + if (p->kstack) + PMM::free_page(p->kstack); + p->kstack = 0; + if (p->pagetable) + VM::uvmfree(p->pagetable, p->sz); + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i]) + { + FileTable::close(p->ofile[i]); + p->ofile[i] = nullptr; + } + } + if (p->cwd) + { + VFS::iput(p->cwd); + p->cwd = nullptr; + } + + if (p->all_prev) + p->all_prev->all_next = p->all_next; + else + proc_list_head = p->all_next; + + if (p->all_next) + p->all_next->all_prev = p->all_prev; + + Slab::kfree(p); +} + +int fork() +{ + struct Proc *np; + struct Proc *p = myproc(); + + if ((np = allocproc()) == 0) + return -1; + + np->pagetable = VM::uvmcreate(); + np->sz = p->sz; + if (VM::uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) + { + freeproc(np); + proc_mem_lock.release(); + return -1; + } + + + if (VM::mappages(np->pagetable, TRAPFRAME, PGSIZE, (uint64)np->tf, PTE_R | PTE_W) < 0) + { + freeproc(np); + proc_mem_lock.release(); + return -1; + } + + *(np->tf) = *(p->tf); + np->tf->a0 = 0; + strcpy(np->name, p->name); + np->parent = p; + np->priority = p->priority; + + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i]) + { + np->ofile[i] = FileTable::dup(p->ofile[i]); + } + } + + if (p->cwd) + { + np->cwd = VFS::idup(p->cwd); + } + + proc_mem_lock.release(); + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + np->state = RUNNABLE; + c->runqueue->enqueue(np); + c->runqueue->lock.release(); + + return np->pid; +} + +namespace ProcManager +{ + + void init() + { + proc_mem_lock.init("proc_mem"); + + proc_list_head = nullptr; + + for (int i = 0; i < NCPU; i++) + { + cpus[i].runqueue = &runqueues[i]; + cpus[i].runqueue->init("runqueue"); + } + } + + void scheduler() + { + struct Proc *p; + struct cpu *c = mycpu(); + c->proc = 0; + Drivers::uart_puts("[Proc] Scheduler started (Per-CPU).\n"); + + for (;;) + { + intr_on(); + + // Get from the local queue + c->runqueue->lock.acquire(); + p = c->runqueue->dequeue(); + c->runqueue->lock.release(); + + // Work Stealing + if (!p) + { + for (int i = 0; i < NCPU; i++) + { + if ((uint64)i == r_tp()) + continue; + + struct cpu *victim = &cpus[i]; + victim->runqueue->lock.acquire(); + if (victim->runqueue->count > 0) + p = victim->runqueue->steal(); + victim->runqueue->lock.release(); + if (p) + break; + } + } + + if (p) + { + p->state = RUNNING; + c->proc = p; + + c->runqueue->lock.acquire(); + swtch(&c->context, &p->context); + c->runqueue->lock.release(); + + c->proc = 0; + } + } + } + + void yield() + { + struct Proc *p = myproc(); + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + if (p && p->state == RUNNING) + { + p->state = RUNNABLE; + if (p->priority < PRIO_MIN) + p->priority++; + c->runqueue->enqueue(p); + swtch(&p->context, &c->context); + } + c->runqueue->lock.release(); + } + + void user_init() + { + struct Proc *p = allocproc(); + if (!p) + return; + + initproc = p; + + p->pagetable = VM::uvmcreate(); + + uint64 initcode_sz = initcode_end - initcode_start; + Drivers::uart_puts("[Proc] initcode size: "); + Drivers::uart_put_int(initcode_sz); + Drivers::uart_puts(" bytes\n"); + + uint64 code_pages = PGROUNDUP(initcode_sz) / PGSIZE; + + for (uint64 i = 0; i < code_pages; i++) + { + void *mem = PMM::alloc_page(); + if (!mem) + { + Drivers::uart_puts("user_init: out of memory for code\n"); + return; + } + memset(mem, 0, PGSIZE); + + uint64 src_offset = i * PGSIZE; + uint64 copy_len = PGSIZE; + if (src_offset + copy_len > initcode_sz) + { + copy_len = initcode_sz - src_offset; + } + memmove(mem, (void *)(initcode_start + src_offset), copy_len); + + if (VM::mappages(p->pagetable, i * PGSIZE, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_X | PTE_U) < 0) + { + Drivers::uart_puts("user_init: mappages code failed\n"); + return; + } + } + + uint64 stack_va = (code_pages + 1) * PGSIZE; + + void *stack = PMM::alloc_page(); + if (!stack) + return; + memset(stack, 0, PGSIZE); + + if (VM::mappages(p->pagetable, stack_va, PGSIZE, (uint64)stack, PTE_R | PTE_W | PTE_U) < 0) + return; + + if (VM::mappages(p->pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) + return; + + p->tf->epc = 0; + p->tf->sp = stack_va + PGSIZE; + p->sz = stack_va + PGSIZE; + strcpy(p->name, "initcode"); + + p->cwd = nullptr; + + proc_mem_lock.release(); + + struct cpu *c = &cpus[0]; + c->runqueue->lock.acquire(); + p->state = RUNNABLE; + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + + Drivers::uart_puts("[Proc] user_init success. PID: 1\n"); + asm volatile("fence.i"); + } + + void sleep(void *chan, Spinlock *lk) + { + struct Proc *p = myproc(); + struct cpu *c = mycpu(); + + if (lk && lk != &proc_mem_lock) + lk->release(); + + c->runqueue->lock.acquire(); + p->chan = chan; + p->state = SLEEPING; + swtch(&p->context, &c->context); + p->chan = 0; + c->runqueue->lock.release(); + + if (lk && lk != &proc_mem_lock) + lk->acquire(); + } + + void wakeup(void *chan) + { + for (struct Proc *p = proc_list_head; p != nullptr; p = p->all_next) + { + if (p->state == SLEEPING && p->chan == chan) + { + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + p->state = RUNNABLE; + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + } + } + } + + void exit(int status) + { + struct Proc *p = myproc(); + if (p == initproc) + while (1) + ; + + wakeup(p->parent); + + proc_mem_lock.acquire(); + + for (struct Proc *pp = proc_list_head; pp != nullptr; pp = pp->all_next) + { + if (pp->parent == p) + { + pp->parent = initproc; + wakeup(initproc); + } + } + + proc_mem_lock.release(); + + p->xstate = status; + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + p->state = ZOMBIE; + swtch(&p->context, &c->context); + while (1) + ; + } + + int wait(uint64 addr) + { + + struct Proc *np; + int havekids, pid; + struct Proc *p = myproc(); + for (;;) + { + havekids = 0; + proc_mem_lock.acquire(); + + for (np = proc_list_head; np != nullptr; np = np->all_next) + { + if (np->parent == p) + { + havekids = 1; + if (np->state == ZOMBIE) + { + + struct cpu *child_cpu = np->cpu; + if (child_cpu) + { + proc_mem_lock.release(); + child_cpu->runqueue->lock.acquire(); + child_cpu->runqueue->lock.release(); + proc_mem_lock.acquire(); + } + + pid = np->pid; + if (addr != 0 && VM::copyout(p->pagetable, addr, (char *)&np->xstate, sizeof(np->xstate)) < 0) + { + proc_mem_lock.release(); + return -1; + } + freeproc(np); + proc_mem_lock.release(); + return pid; + } + } + } + proc_mem_lock.release(); + + if (!havekids || p->state == ZOMBIE) + return -1; + sleep(p, &proc_mem_lock); + } + } + + int kill(int pid) + { + Proc* p; + proc_mem_lock.acquire(); + for (p = proc_list_head; p != nullptr; p = p->all_next) + { + if (p->pid == pid) + { + p->killed = 1; + + if (p->state == SLEEPING) + { + p->state = RUNNABLE; + + struct cpu *c = mycpu(); + c->runqueue->lock.acquire(); + c->runqueue->enqueue(p); + c->runqueue->lock.release(); + } + + proc_mem_lock.release(); + return 0; + } + } + proc_mem_lock.release(); + return -1; + } + + int growproc(int n) + { + uint64 sz; + struct Proc *p = myproc(); + + sz = p->sz; + + if (n > 0) + { + // Call VM::uvmalloc to allocate memory + // Parameters: page table, old size, new size, permissions (PTE_W) + if ((sz = VM::uvmalloc(p->pagetable, sz, sz + n, PTE_W | PTE_R | PTE_U)) == 0) + { + return -1; + } + } + else if (n < 0) + { + sz = VM::uvmdealloc(p->pagetable, sz, sz + n); + } + + p->sz = sz; + return 0; + } + + void create_kernel_thread(void (*func)(), const char *name) + { + (void)func; + (void)name; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/proc/swtch.S b/lume-legacy/src/kernel/proc/swtch.S new file mode 100644 index 0000000..d55c03e --- /dev/null +++ b/lume-legacy/src/kernel/proc/swtch.S @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.global swtch + +swtch: + sd ra, 0(a0) + sd sp, 8(a0) + sd s0, 16(a0) + sd s1, 24(a0) + sd s2, 32(a0) + sd s3, 40(a0) + sd s4, 48(a0) + sd s5, 56(a0) + sd s6, 64(a0) + sd s7, 72(a0) + sd s8, 80(a0) + sd s9, 88(a0) + sd s10, 96(a0) + sd s11, 104(a0) + + ld ra, 0(a1) + ld sp, 8(a1) + ld s0, 16(a1) + ld s1, 24(a1) + ld s2, 32(a1) + ld s3, 40(a1) + ld s4, 48(a1) + ld s5, 56(a1) + ld s6, 64(a1) + ld s7, 72(a1) + ld s8, 80(a1) + ld s9, 88(a1) + ld s10, 96(a1) + ld s11, 104(a1) + + ret diff --git a/lume-legacy/src/kernel/sbi.cc b/lume-legacy/src/kernel/sbi.cc new file mode 100644 index 0000000..5ae7edb --- /dev/null +++ b/lume-legacy/src/kernel/sbi.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/sbi.h" + +namespace SBI +{ + struct sbiret + { + long error; + long value; + }; + + struct sbiret sbi_call(int ext, int fid, uint64 arg0, uint64 arg1, uint64 arg2) + { + struct sbiret ret; + register uint64 a0 asm("a0") = arg0; + register uint64 a1 asm("a1") = arg1; + register uint64 a2 asm("a2") = arg2; + register uint64 a6 asm("a6") = fid; + register uint64 a7 asm("a7") = ext; + + asm volatile( + "ecall" + : "=r"(a0), "=r"(a1) + : "r"(a0), "r"(a1), "r"(a2), "r"(a6), "r"(a7) + : "memory"); + ret.error = a0; + ret.value = a1; + return ret; + } + + void set_timer(uint64 stime_value) + { + sbi_call(0x54494D45, 0, stime_value, 0, 0); + } + + void sbi_shutdown() + { + sbi_call(0x53525354, 0, 0, 0, 0); + sbi_call(0x08, 0, 0, 0, 0); + while (1) + ; + } + + void sbi_reboot() + { + sbi_call(SBI_EID_SRST, SBI_FID_SRST_RESET, SBI_SRST_TYPE_COLD_REBOOT, SBI_SRST_REASON_NONE, 0); + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/spinlock.cc b/lume-legacy/src/kernel/spinlock.cc new file mode 100644 index 0000000..aadce21 --- /dev/null +++ b/lume-legacy/src/kernel/spinlock.cc @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/spinlock.h" +#include "kernel/riscv.h" +#include "kernel/cpu.h" +#include "kernel/proc_def.h" +#include "drivers/uart.h" + +extern struct cpu cpus[]; + +namespace ProcManager +{ + void sleep(void *chan, Spinlock *lk); + void wakeup(void *chan); +} + +// Interrupt Depth Management +void push_off(void); +void pop_off(void); + +// // Get the pointer of the current CPU +// // Depends on r_tp() in riscv.h to read the tp register +// struct cpu *mycpu() +// { +// int id = r_tp(); +// return &cpus[id]; +// } + +// Get the current process +// Must be called with interrupts disabled, or read atomically in a safe manner +struct Proc *myproc() +{ + push_off(); + struct cpu *c = mycpu(); + struct Proc *p = c->proc; + pop_off(); + return p; +} + +// Spinlock Implementation + +void Spinlock::init(const char *lock_name) +{ + name = lock_name; + locked = 0; + cpu = nullptr; +} + +void Spinlock::acquire() +{ + push_off(); // Disable interrupts: prevent deadlocks (interrupt handler requests a lock that is already held) + + if (holding()) + { + Drivers::uart_puts("panic: recursive acquire "); + Drivers::uart_puts(name); + Drivers::uart_puts("\n"); + while (1) + ; + } + + // Atomic Exchange + // __ATOMIC_ACQUIRE: Ensures that reads and writes after the acquire are not reordered before the acquire + while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE)) + { + // Busy-wait (Spinning) + // Optimization: Read-only, no writes, reducing cache coherence traffic + while (__atomic_load_n(&locked, __ATOMIC_RELAXED) == 1) + { + asm volatile("nop"); // RISC-V indicates that the CPU is in a spin state + } + } + + // Record holder + cpu = mycpu(); +} + +void Spinlock::release() +{ + if (!holding()) + { + Drivers::uart_puts("panic: release not holding\n"); + while (1) + ; + } + + cpu = nullptr; + + // Atomic Store + // __ATOMIC_RELEASE: Ensures all reads and writes before the Release are completed + __atomic_store_n(&locked, 0, __ATOMIC_RELEASE); + + pop_off(); +} + +// Check if the current CPU holds the lock +bool Spinlock::holding() +{ + // Interrupt checks must be disabled, otherwise a process switch may occur causing CPU fluctuations. + int r; + push_off(); + r = (locked && cpu == mycpu()); + pop_off(); + return r; +} + + +// Mutex Implementation (Sleep Locks) + +void Mutex::init(const char *lock_name) +{ + name = lock_name; + locked = 0; + pid = 0; + lk.init("mutex_internal"); +} + +void Mutex::acquire() +{ + lk.acquire(); // Acquire the internal lock to protect the locked field + + while (locked) + { + // Lock is held, preparing to sleep + // Atomicity contract of sleep(): + // 1. Mark the current process as SLEEPING + // 2. Release lk (so others can operate on locked) + // 3. Switch CPU (context switch) + // 4. After being woken up, reacquire the lk lock and return + ProcManager::sleep(this, &lk); + } + + locked = 1; + pid = myproc()->pid; + + lk.release(); +} + +void Mutex::release() +{ + lk.acquire(); + + locked = 0; + pid = 0; + + // Wake up all processes sleeping on this lock (chan = this) + ProcManager::wakeup(this); + + lk.release(); +} + +bool Mutex::holding() +{ + lk.acquire(); // Must use a lock when reading to prevent race conditions. + bool r = locked && (pid == myproc()->pid); + lk.release(); + return r; +} + +// Interrupt Depth Management + +void push_off(void) +{ + int old = intr_get(); + intr_off(); + + struct cpu *c = mycpu(); + + if (c->noff == 0) + c->intena = old; + + c->noff += 1; +} + +void pop_off(void) +{ + struct cpu *c = mycpu(); + + if (intr_get()) + { + Drivers::uart_puts("panic: pop_off - interruptible\n"); + while (1) + ; + } + if (c->noff < 1) + { + Drivers::uart_puts("panic: pop_off count\n"); + while (1) + ; + } + + c->noff -= 1; + + // An interrupt is only truly enabled when the nesting level returns to zero and the outermost layer allows interrupts. + if (c->noff == 0 && c->intena) + intr_on(); +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/syscall.cc b/lume-legacy/src/kernel/syscall.cc new file mode 100644 index 0000000..0979c24 --- /dev/null +++ b/lume-legacy/src/kernel/syscall.cc @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + */ +#include "kernel/syscall.h" +#include "common/types.h" +#include "common/fcntl.h" +#include "kernel/proc.h" +#include "kernel/riscv.h" +#include "drivers/uart.h" +#include "drivers/virtio.h" +#include "kernel/timer.h" +#include "kernel/mm.h" +#include "kernel/trap.h" +#include "kernel/slab.h" +#include "kernel/sbi.h" +#include "fs/fat32.h" +#include "fs/file.h" +#include "lib/string.h" +#include "lib/errno.h" + +extern "C++" int fork(); +namespace Exec +{ + int exec(char *path, char **argv); +} + +namespace ProcManager +{ + void exit(int status); + int wait(uint64_t addr); +} + +static uint64_t argraw(int n) +{ + Proc *p = myproc(); + switch (n) + { + case 0: + return p->tf->a0; + case 1: + return p->tf->a1; + case 2: + return p->tf->a2; + case 3: + return p->tf->a3; + case 4: + return p->tf->a4; + case 5: + return p->tf->a5; + default: + return static_cast(-1); + } +} + +static int argint(int n, int *ip) +{ + *ip = static_cast(argraw(n)); + return 0; +} + +static int argstr(int n, char *buf, int max) +{ + uint64 addr = argraw(n); + return VM::copyinstr(myproc()->pagetable, buf, addr, max); +} + +struct KernelArgv +{ + static const int MAX_ARGS = 32; + static const int MAX_ARG_LEN = 128; + char *args[MAX_ARGS]; + + KernelArgv() { memset(args, 0, sizeof(args)); } + ~KernelArgv() + { + for (int i = 0; i < MAX_ARGS; i++) + if (args[i]) + { + Slab::kfree(args[i]); + args[i] = nullptr; + } + } + KernelArgv(const KernelArgv &) = delete; + KernelArgv &operator=(const KernelArgv &) = delete; +}; + +static int fdalloc(struct file *f) +{ + struct Proc *p = myproc(); + for (int i = 0; i < NOFILE; i++) + { + if (p->ofile[i] == 0) + { + p->ofile[i] = f; + return i; + } + } + return -1; +} + +// sys_openat(int dfd, const char *filename, int flags, mode_t mode) +static uint64 sys_openat() +{ + char path[128]; + int dfd, omode, mode_unused; + int fd; + struct file *f; + Inode *ip; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &omode) < 0 || argint(3, &mode_unused) < 0) + return -EINVAL; + + if (omode & O_CREATE) + { + ip = VFS::namei(path); + if (ip == nullptr) + { + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp) + { + VFS::ilock(dp); + ip = dp->create(name, T_FILE, 0, 0); + VFS::iunlockput(dp); + } + } + } + else + { + ip = VFS::namei(path); + } + + if (ip == nullptr) + return -ENOENT; + + VFS::ilock(ip); + if ((omode & O_TRUNC) && ip->type == T_FILE) + { + ip->truncate(); + } + VFS::iunlock(ip); + + f = FileTable::alloc(); + if (f == nullptr) + { + VFS::iput(ip); + return -ENFILE; + } + + fd = fdalloc(f); + if (fd < 0) + { + f->ref = 0; + f->type = FD_NONE; + VFS::iput(ip); + return -EMFILE; + } + + f->type = FD_INODE; + f->ip = ip; + f->off = 0; + f->readable = !(omode & O_WRONLY); + f->writable = (omode & O_WRONLY) || (omode & O_RDWR); + + return fd; +} + +// sys_mkdirat(int dfd, const char *pathname, mode_t mode) +static uint64 sys_mkdirat() +{ + char path[128]; + int dfd, mode; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0) + return -EINVAL; + + Inode *ip = VFS::namei(path); + if (ip != nullptr) + { + VFS::iput(ip); + return -EEXIST; + } + + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp == nullptr) + return -ENOENT; + + VFS::ilock(dp); + ip = dp->create(name, T_DIR, 0, 0); + VFS::iunlockput(dp); + + if (ip == nullptr) + return -EIO; + + VFS::iput(ip); + return 0; +} + +static uint64 sys_chdir() +{ + char path[128]; + if (argstr(0, path, 128) < 0) + return -EFAULT; + + Inode *ip = VFS::namei(path); + if (ip == nullptr) + return -ENOENT; + + VFS::ilock(ip); + if (ip->type != T_DIR) + { + VFS::iunlockput(ip); + return -ENOTDIR; + } + VFS::iunlock(ip); + + struct Proc *p = myproc(); + if (p->cwd) + VFS::iput(p->cwd); + p->cwd = ip; + return 0; +} + +static uint64 sys_dup() +{ + int oldfd; + if (argint(0, &oldfd) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (oldfd < 0 || oldfd >= NOFILE || p->ofile[oldfd] == nullptr) + return -EBADF; + + struct file *f = FileTable::dup(p->ofile[oldfd]); + int newfd = fdalloc(f); + if (newfd < 0) + { + FileTable::close(f); + return -EMFILE; + } + return newfd; +} + +static uint64 sys_fstat() +{ + int fd; + uint64 stat_addr; + + if (argint(0, &fd) < 0 || argint(1, (int *)&stat_addr) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == nullptr) + return -EBADF; + + if (FileTable::stat(p->ofile[fd], stat_addr) < 0) + return -EIO; + + return 0; +} + +// sys_mknodat(int dfd, const char *filename, mode_t mode, dev_t dev) +static uint64 sys_mknodat() +{ + char path[128]; + int dfd, mode, dev; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &mode) < 0 || argint(3, &dev) < 0) + return -EINVAL; + + Inode *ip = VFS::namei(path); + if (ip != nullptr) + { + VFS::iput(ip); + return -EEXIST; + } + + char name[128]; + Inode *dp = VFS::nameiparent(path, name); + if (dp == nullptr) + return -ENOENT; + + // Decode dev_t (Assuming simplified minor/major for now) + int major = (dev >> 20) & 0xFFF; // Just a guess for Linux packing, or pass raw + int minor = dev & 0xFFFFF; + + // For Phase 1 compatibility with init.cc: + // init.cc passes raw major/minor. Shim will pack them. + // We just unpack naively or assume Shim passed us correct structure. + + VFS::ilock(dp); + ip = dp->create(name, T_DEVICE, major, minor); + VFS::iunlockput(dp); + + if (ip == nullptr) + return -EIO; + + VFS::iput(ip); + return 0; +} + +static uint64 sys_close() +{ + int fd; + if (argint(0, &fd) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) + return -EBADF; + + FileTable::close(p->ofile[fd]); + p->ofile[fd] = 0; + return 0; +} + +static uint64 sys_pipe2() +{ + uint64 fdarray; + int flags; + struct file *rf, *wf; + int fd0, fd1; + struct Proc *p = myproc(); + + if (argint(0, (int *)&fdarray) < 0 || argint(1, &flags) < 0) + return -EFAULT; + + if (Pipe::create_pair(&rf, &wf) < 0) + return -ENFILE; + + if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) + { + if (fd0 >= 0) + p->ofile[fd0] = nullptr; + FileTable::close(rf); + FileTable::close(wf); + return -EMFILE; + } + + if (VM::copyout(p->pagetable, fdarray, (char *)&fd0, sizeof(int)) < 0 || + VM::copyout(p->pagetable, fdarray + sizeof(int), (char *)&fd1, sizeof(int)) < 0) + { + p->ofile[fd0] = nullptr; + p->ofile[fd1] = nullptr; + FileTable::close(rf); + FileTable::close(wf); + return -EFAULT; + } + return 0; +} + +static uint64 sys_unlinkat() +{ + char path[128], name[128]; + int dfd, flag; + + if (argint(0, &dfd) < 0 || argstr(1, path, 128) < 0 || argint(2, &flag) < 0) + return -EINVAL; + + Inode *dp = VFS::nameiparent(path, name); + if (!dp) + return -ENOENT; + + VFS::ilock(dp); + int ret = dp->unlink(name); + VFS::iunlockput(dp); + + return (ret < 0) ? -EIO : 0; +} + +// sys_linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) +static uint64 sys_linkat() +{ + // FAT32 does not support hard links. + // Return Error to allow compilation/running to proceed (ln will fail gracefully). + return -EPERM; +} + +static uint64 sys_execve() +{ + char path[128]; + uint64 uargv_ptr; + + if (argstr(0, path, sizeof(path)) < 0) + return -EFAULT; + + uargv_ptr = argraw(1); + // uenvp_ptr = argraw(2); // Ignored + + KernelArgv kargv; + + for (int i = 0; i < KernelArgv::MAX_ARGS; ++i) + { + uint64 uarg_str_ptr; + if (VM::copyin(myproc()->pagetable, (char *)&uarg_str_ptr, uargv_ptr + i * sizeof(uint64), sizeof(uint64)) < 0) + { + return -EFAULT; + } + + if (uarg_str_ptr == 0) + { + kargv.args[i] = nullptr; + break; + } + + kargv.args[i] = (char *)Slab::kmalloc(KernelArgv::MAX_ARG_LEN); + if (kargv.args[i] == nullptr) + { + return -ENOMEM; + } + + if (VM::copyinstr(myproc()->pagetable, kargv.args[i], uarg_str_ptr, KernelArgv::MAX_ARG_LEN) < 0) + { + return -EFAULT; + } + } + return Exec::exec(path, kargv.args); +} + +static uint64 sys_exit() +{ + int n; + if (argint(0, &n) < 0) + return -EINVAL; + ProcManager::exit(n); + return 0; +} + +static uint64 sys_clone() +{ + return static_cast(fork()); +} + +static uint64 sys_wait4() +{ + uint64 status_addr; + + // argraw(0) is pid (ignored) + status_addr = argraw(1); + // argraw(2) is options (ignored) + // argraw(3) is rusage (ignored) + + if (status_addr == static_cast(-1)) + return -EINVAL; + + return static_cast(ProcManager::wait(status_addr)); +} + +static uint64 sys_getpid() +{ + return static_cast(myproc()->pid); +} + +static uint64 sys_putc() +{ + char c = static_cast(argraw(0)); + Drivers::uart_putc(c); + return 0; +} + +static uint64 sys_write() +{ + struct file *f; + int n; + uint64 p; + int fd; + + if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) + return -EINVAL; + + p = argraw(1); + + struct Proc *proc = myproc(); + + if (fd == 1 && proc->ofile[fd] == nullptr) + { + constexpr int MAX_WRITE_BUF = 128; + char buf[MAX_WRITE_BUF]; + int i = 0; + + while (i < n) + { + int len = n - i; + if (len > MAX_WRITE_BUF) + len = MAX_WRITE_BUF; + + if (VM::copyin(proc->pagetable, buf, p + i, static_cast(len)) < 0) + return -EFAULT; + + for (int j = 0; j < len; j++) + { + Drivers::uart_putc(buf[j]); + } + i += len; + } + return static_cast(n); + } + + if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) + return -EBADF; + + return FileTable::write(f, p, n); +} + +static uint64_t sys_read() +{ + struct file *f; + int n; + uint64 p; + int fd; + + if (argint(0, &fd) < 0 || argint(1, (int *)&p) < 0 || argint(2, &n) < 0) + return -EINVAL; + + p = argraw(1); + + struct Proc *proc = myproc(); + if (fd == 0 && proc->ofile[fd] == nullptr) + return Drivers::console_read(p, n); + + if (fd < 0 || fd >= NOFILE || (f = proc->ofile[fd]) == 0) + return -EBADF; + + return FileTable::read(f, p, n); +} + +static uint64 sys_brk() +{ + int n; + if (argint(0, &n) < 0) + return -EINVAL; + + uint64 addr = myproc()->sz; + if (ProcManager::growproc(n) < 0) + return -ENOMEM; + + return addr; +} + +static uint64 sys_kill() +{ + int pid; + if (argint(0, &pid) < 0) + return -EINVAL; + return ProcManager::kill(pid); +} + +static uint64 sys_sleep() +{ + int n; + uint64 ticks0; + + if (argint(0, &n) < 0) + return -EINVAL; + + Spinlock *lk = Timer::get_lock(); + lk->acquire(); + ticks0 = Timer::get_ticks(); + while (Timer::get_ticks() - ticks0 < (uint64)n) + { + if (myproc()->killed) + { + lk->release(); + return -EINTR; + } + ProcManager::sleep(Timer::get_tick_chan(), lk); + } + lk->release(); + return 0; +} + +static uint64 sys_disk_test() +{ + // VirtIO::test_rw(); + // VirtIO::test_bio(); + fat32_test(); + return 0; +} + +static uint64 sys_lseek() +{ + int fd, offset, whence; + if (argint(0, &fd) < 0 || argint(1, &offset) < 0 || argint(2, &whence) < 0) + return -EINVAL; + + struct Proc *p = myproc(); + if (fd < 0 || fd >= NOFILE || p->ofile[fd] == 0) + return -EBADF; + + return FileTable::lseek(p->ofile[fd], offset, whence); +} + +static uint64 sys_reboot() +{ + SBI::sbi_shutdown(); + return 0; +} + +void syscall() +{ + Proc *p = myproc(); + int num = p->tf->a7; + uint64 ret = static_cast(-ENOSYS); + + switch (num) + { + case SYS_mknodat: + ret = sys_mknodat(); + break; + case SYS_mkdirat: + ret = sys_mkdirat(); + break; + case SYS_unlinkat: + ret = sys_unlinkat(); + break; + case SYS_linkat: + ret = sys_linkat(); + break; + case SYS_chdir: + ret = sys_chdir(); + break; + case SYS_openat: + ret = sys_openat(); + break; + case SYS_dup: + ret = sys_dup(); + break; + case SYS_close: + ret = sys_close(); + break; + case SYS_pipe2: + ret = sys_pipe2(); + break; + case SYS_lseek: + ret = sys_lseek(); + break; + case SYS_read: + ret = sys_read(); + break; + case SYS_write: + ret = sys_write(); + break; + case SYS_fstat: + ret = sys_fstat(); + break; + case SYS_exit: + ret = sys_exit(); + break; + case SYS_exit_group: + ret = sys_exit(); + break; + case SYS_kill: + ret = sys_kill(); + break; + case SYS_getpid: + ret = sys_getpid(); + break; + case SYS_brk: + ret = sys_brk(); + break; + case SYS_clone: + ret = sys_clone(); + break; + case SYS_execve: + ret = sys_execve(); + if (ret == 0) + return; + break; + case SYS_wait4: + ret = sys_wait4(); + break; + + // Custom / Debug + case SYS_reboot: + ret = sys_reboot(); + break; + case SYS_putc: + ret = sys_putc(); + break; + case SYS_disk_test: + ret = sys_disk_test(); + break; + case SYS_nanosleep: + ret = sys_sleep(); + break; + + default: + Drivers::uart_puts("Unknown Syscall ID: "); + Drivers::print_hex(num); + Drivers::uart_puts("\n"); + ret = -ENOSYS; + break; + } + + p->tf->a0 = ret; +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/timer.cc b/lume-legacy/src/kernel/timer.cc new file mode 100644 index 0000000..fc02f27 --- /dev/null +++ b/lume-legacy/src/kernel/timer.cc @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "common/types.h" +#include "kernel/config.h" +#include "kernel/spinlock.h" +#include "kernel/proc.h" +#include "kernel/sbi.h" + +#define INTERVAL 1000000 + +namespace Timer +{ + uint64 ticks; + Spinlock tickslock; + + uint64 get_time() + { + uint64 time; + asm volatile("rdtime %0" : "=r"(time)); + return time; + } + + void set_next_trigger() + { + SBI::set_timer(get_time() + INTERVAL); + } + + void init() + { + set_next_trigger(); + + uint64 sie; + asm volatile("csrr %0, sie" : "=r"(sie)); + sie |= (1L << 5); + asm volatile("csrw sie, %0" : : "r"(sie)); + } + + void tick() + { + tickslock.acquire(); + ticks++; + ProcManager::wakeup(&ticks); + tickslock.release(); + set_next_trigger(); + } + + uint64 get_ticks() + { + tickslock.acquire(); + uint64 t = ticks; + tickslock.release(); + return t; + } + + Spinlock *get_lock() + { + return &tickslock; + } + + void *get_tick_chan() + { + return &ticks; + } +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/handler.cc b/lume-legacy/src/kernel/trap/handler.cc new file mode 100644 index 0000000..b7bd5f0 --- /dev/null +++ b/lume-legacy/src/kernel/trap/handler.cc @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "kernel/trap.h" +#include "kernel/riscv.h" +#include "kernel/proc.h" +#include "drivers/uart.h" +#include "kernel/syscall.h" +#include "kernel/mm.h" +#include "kernel/timer.h" +#include "drivers/plic.h" +#include "drivers/virtio.h" +#include "kernel/fdt.h" +#include "lib/string.h" + + +extern "C" void kernelvec(); + +// Handle Function + +void panic(const char *s) +{ + Drivers::uart_puts("\n\n!!! KERNEL PANIC !!!\n"); + Drivers::uart_puts(s); + Drivers::uart_puts("\nSystem Halted.\n"); + while (1) + ; +} + +// Trap Implementation + +namespace Trap +{ + void init() + { + Drivers::uart_puts("[Trap] Global init done.\n"); + } + + // Each CPU core needs to call this function when starting to set its own stvec + void inithart() + { + w_stvec((uint64)kernelvec); + + // Ensure that the S-Mode interrupt enable bit (SIE) is cleared during initialization + // Interrupt enabling will be done by the scheduler when the first process runs + w_sstatus(r_sstatus() & ~SSTATUS_SIE); + + // Enable the external interrupt, clock interrupt, and software interrupt bits in S-Mode (in SIE register) + uint64 sie = r_sie(); + sie |= SIE_SEIE | SIE_STIE | SIE_SSIE; + w_sie(sie); + + Drivers::uart_puts("[Trap] Hart initialized (stvec set).\n"); + } + + // Device Interrupt Check + int devintr() + { + uint64 scause = r_scause(); + + bool is_interrupt = (scause & 0x8000000000000000L) != 0; + uint64 code = scause & 0xff; + + if (is_interrupt && code == 9) + { + // Supervisor External Interrupt (IRQ 9) + int irq = PLIC::claim(); + + if (irq == (int)g_devices.uart.irq) + { + Drivers::uart_intr(); + } + else + { + // Check if it's a VirtIO IRQ + bool handled = false; + for (int i = 0; i < g_devices.virtio_count; i++) + { + if (irq == (int)g_devices.virtio[i].irq) + { + VirtIO::intr(); + handled = true; + break; + } + } + } + + if (irq > 0) + { + PLIC::complete(irq); + } + + return 1; + } + else if (is_interrupt && code == 5) + { + // Supervisor Timer Interrupt (IRQ 5) + // Timer::set_next_trigger(); + Timer::tick(); + // w_sip(r_sip() & ~2); + return 2; + } + + return 0; + } +} + +// Exception Handling Entry + +extern "C" void kerneltrap(struct Trapframe *tf) +{ + uint64 sepc = r_sepc(); + uint64 sstatus = r_sstatus(); + uint64 scause = r_scause(); + + if ((sstatus & SSTATUS_SPP) == 0) + panic("kerneltrap: not from supervisor mode"); + + if (intr_get() != 0) + panic("kerneltrap: interrupts enabled"); + + if (Trap::devintr() == 0) + { + Drivers::uart_puts("\n--- KERNEL EXCEPTION ---\n"); + Drivers::uart_puts("scause: "); + Drivers::print_hex(scause); + Drivers::uart_puts("\n"); + Drivers::uart_puts("sepc: "); + Drivers::print_hex(sepc); + Drivers::uart_puts("\n"); + Drivers::uart_puts("stval: "); + Drivers::print_hex(r_stval()); + Drivers::uart_puts("\n"); + panic("kerneltrap exception"); + } + + w_sepc(sepc); + w_sstatus(sstatus); +} + +extern "C" void usertrap() +{ + // Drivers::uart_puts("[Debug] Entered usertrap!\n"); + int which_dev = 0; + + if ((r_sstatus() & SSTATUS_SPP) != 0) + panic("usertrap: not from user mode"); + + w_stvec((uint64)kernelvec); + + struct Proc *p = myproc(); + if (!p) + panic("usertrap: no process"); + + p->tf->epc = r_sepc(); + + uint64 scause = r_scause(); + uint64 stval = r_stval(); + + if (scause == 8) // Syscall + { + if (p->state == ZOMBIE) + ProcManager::exit(-1); + p->tf->epc += 4; + intr_on(); + syscall(); + intr_off(); + } + else if (scause == 15) // Store Page Fault + { + if (VM::handle_cow_fault(p->pagetable, PGROUNDDOWN(stval)) != 0) + { + uint64 pa = VM::walkaddr(p->pagetable, stval); + + if (pa == 0) + { + Drivers::uart_puts("usertrap: Segmentation Fault (unmapped) at "); + } + else + { + Drivers::uart_puts("usertrap: Protection Fault (or OOM) at "); + } + + Drivers::print_hex(stval); + Drivers::uart_puts("\n"); + p->state = ZOMBIE; + ProcManager::exit(-1); + } + } + else if ((which_dev = Trap::devintr()) != 0) + { + if (which_dev == 2) + { + ProcManager::yield(); + } + } + else + { + Drivers::uart_puts("\nusertrap(): unexpected scause "); + Drivers::print_hex(scause); + Drivers::uart_puts(" pid="); + Drivers::uart_put_int(p->pid); + Drivers::uart_puts(" sepc="); + Drivers::print_hex(p->tf->epc); + Drivers::uart_puts(" stval="); + Drivers::print_hex(stval); + Drivers::uart_puts("\n"); + p->state = ZOMBIE; + ProcManager::exit(-1); + } + + if (p->state == ZOMBIE) + ProcManager::exit(-1); + + usertrapret(); +} \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/trampoline.S b/lume-legacy/src/kernel/trap/trampoline.S new file mode 100644 index 0000000..57d6f25 --- /dev/null +++ b/lume-legacy/src/kernel/trap/trampoline.S @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.globl trampoline +.globl uservec +.globl userret + +.align 12 +trampoline: +.align 4 +uservec: + + csrrw a0, sscratch, a0 + + sd ra, 40(a0) + sd sp, 48(a0) + sd gp, 56(a0) + sd tp, 64(a0) + sd t0, 72(a0) + sd t1, 80(a0) + sd t2, 88(a0) + sd s0, 96(a0) + sd s1, 104(a0) + sd a1, 120(a0) + sd a2, 128(a0) + sd a3, 136(a0) + sd a4, 144(a0) + sd a5, 152(a0) + sd a6, 160(a0) + sd a7, 168(a0) + sd s2, 176(a0) + sd s3, 184(a0) + sd s4, 192(a0) + sd s5, 200(a0) + sd s6, 208(a0) + sd s7, 216(a0) + sd s8, 224(a0) + sd s9, 232(a0) + sd s10, 240(a0) + sd s11, 248(a0) + sd t3, 256(a0) + sd t4, 264(a0) + sd t5, 272(a0) + sd t6, 280(a0) + + csrr t0, sscratch + sd t0, 112(a0) + + ld sp, 8(a0) + ld tp, 32(a0) + ld t0, 16(a0) + ld t1, 0(a0) + + csrw satp, t1 + sfence.vma zero, zero + + jr t0 + +.align 4 +userret: + + csrw satp, a1 + sfence.vma zero, zero + + csrw sscratch, a0 + + ld ra, 40(a0) + ld sp, 48(a0) + ld gp, 56(a0) + ld tp, 64(a0) + ld t0, 72(a0) + ld t1, 80(a0) + ld t2, 88(a0) + ld s0, 96(a0) + ld s1, 104(a0) + ld a1, 120(a0) + ld a2, 128(a0) + ld a3, 136(a0) + ld a4, 144(a0) + ld a5, 152(a0) + ld a6, 160(a0) + ld a7, 168(a0) + ld s2, 176(a0) + ld s3, 184(a0) + ld s4, 192(a0) + ld s5, 200(a0) + ld s6, 208(a0) + ld s7, 216(a0) + ld s8, 224(a0) + ld s9, 232(a0) + ld s10, 240(a0) + ld s11, 248(a0) + ld t3, 256(a0) + ld t4, 264(a0) + ld t5, 272(a0) + ld t6, 280(a0) + + ld a0, 112(a0) + + sret \ No newline at end of file diff --git a/lume-legacy/src/kernel/trap/trap.S b/lume-legacy/src/kernel/trap/trap.S new file mode 100644 index 0000000..8bce53d --- /dev/null +++ b/lume-legacy/src/kernel/trap/trap.S @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.global kernelvec +.global uservec +.global userret +.align 12 + +kernelvec: + addi sp, sp, -288 + + sd ra, 40(sp) + sd sp, 48(sp) + sd gp, 56(sp) + sd tp, 64(sp) + sd t0, 72(sp) + sd t1, 80(sp) + sd t2, 88(sp) + sd s0, 96(sp) + sd s1, 104(sp) + sd a0, 112(sp) + sd a1, 120(sp) + sd a2, 128(sp) + sd a3, 136(sp) + sd a4, 144(sp) + sd a5, 152(sp) + sd a6, 160(sp) + sd a7, 168(sp) + sd s2, 176(sp) + sd s3, 184(sp) + sd s4, 192(sp) + sd s5, 200(sp) + sd s6, 208(sp) + sd s7, 216(sp) + sd s8, 224(sp) + sd s9, 232(sp) + sd s10, 240(sp) + sd s11, 248(sp) + sd t3, 256(sp) + sd t4, 264(sp) + sd t5, 272(sp) + sd t6, 280(sp) + + csrr t0, sepc + csrr t1, sstatus + sd t0, 24(sp) # Trapframe.epc + sd t1, 32(sp) # Trapframe.kernel_hartid + + mv a0, sp + call kerneltrap + + ld t0, 24(sp) + ld t1, 32(sp) + csrw sepc, t0 + csrw sstatus, t1 + + ld ra, 40(sp) + ld gp, 56(sp) + ld tp, 64(sp) + ld t0, 72(sp) + ld t1, 80(sp) + ld t2, 88(sp) + ld s0, 96(sp) + ld s1, 104(sp) + ld a0, 112(sp) + ld a1, 120(sp) + ld a2, 128(sp) + ld a3, 136(sp) + ld a4, 144(sp) + ld a5, 152(sp) + ld a6, 160(sp) + ld a7, 168(sp) + ld s2, 176(sp) + ld s3, 184(sp) + ld s4, 192(sp) + ld s5, 200(sp) + ld s6, 208(sp) + ld s7, 216(sp) + ld s8, 224(sp) + ld s9, 232(sp) + ld s10, 240(sp) + ld s11, 248(sp) + ld t3, 256(sp) + ld t4, 264(sp) + ld t5, 272(sp) + ld t6, 280(sp) + + addi sp, sp, 288 + sret \ No newline at end of file diff --git a/lume-legacy/src/lib/string.cc b/lume-legacy/src/lib/string.cc new file mode 100644 index 0000000..e8eccb3 --- /dev/null +++ b/lume-legacy/src/lib/string.cc @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "lib/string.h" + +void *memset(void *dst, int c, uint n) +{ + char *cdst = (char *)dst; + for (uint i = 0; i < n; i++) + cdst[i] = c; + return dst; +} + +int memcmp(const void *v1, const void *v2, uint n) +{ + const uchar *s1 = (const uchar *)v1; + const uchar *s2 = (const uchar *)v2; + while (n-- > 0) + { + if (*s1 != *s2) + return *s1 - *s2; + s1++, s2++; + } + return 0; +} + +void *memmove(void *dst, const void *src, uint n) +{ + const char *s = (const char *)src; + char *d = (char *)dst; + if (s < d && s + n > d) + { + s += n; + d += n; + while (n-- > 0) + *--d = *--s; + } + else + { + while (n-- > 0) + *d++ = *s++; + } + return dst; +} + +void *memcpy(void *dst, const void *src, uint n) +{ + return memmove(dst, src, n); +} + +void strcpy(char *dst, const char *src) +{ + while (*src) + *dst++ = *src++; + *dst = 0; +} + +int strlen(const char *s) +{ + int n = 0; + while (s[n]) + n++; + return n; +} + +int strcmp(const char *s1, const char *s2) +{ + while (*s1 && (*s1 == *s2)) + { + s1++; + s2++; + } + return *(const unsigned char *)s1 - *(const unsigned char *)s2; +} + +int strcasecmp(const char *s1, const char *s2) +{ + while (*s1) + { + if(*s1 == *s2 || *s1 + 32 == *s2 || *s1 == *s2 + 32){ + s1++; + s2++; + } + else break; + } + return *(const unsigned char *)s1 - *(const unsigned char *)s2; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/assert.c b/lume-legacy/src/ulib/assert.c new file mode 100644 index 0000000..c60643a --- /dev/null +++ b/lume-legacy/src/ulib/assert.c @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "stdio.h" +#include "stdlib.h" + +void __assert_fail(const char *expr, const char *file, int line, const char *func) +{ + fprintf(stderr, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); + abort(); +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/cxx.cc b/lume-legacy/src/ulib/cxx.cc new file mode 100644 index 0000000..d8f510d --- /dev/null +++ b/lume-legacy/src/ulib/cxx.cc @@ -0,0 +1,35 @@ +// 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/lume-legacy/src/ulib/iostream.cpp b/lume-legacy/src/ulib/iostream.cpp new file mode 100644 index 0000000..4d721c1 --- /dev/null +++ b/lume-legacy/src/ulib/iostream.cpp @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/iostream" + +namespace std +{ + istream cin(stdin); + ostream cout(stdout); + ostream cerr(stderr); +} // namespace std diff --git a/lume-legacy/src/ulib/stdio.c b/lume-legacy/src/ulib/stdio.c new file mode 100644 index 0000000..e81f69a --- /dev/null +++ b/lume-legacy/src/ulib/stdio.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/string.h" +#include "ulib/stdlib.h" + +/* Include STB Sprintf Implementation */ +#define STB_SPRINTF_IMPLEMENTATION +#include "ulib/stb_sprintf.h" + +const int STDIN_FD = 0; +const int STDOUT_FD = 1; +const int STDERR_FD = 2; + + + +extern int open(const char *path, int flags); +extern int close(int fd); +extern int read(int fd, void *buf, uint64 count); +extern int write(int fd, const void *buf, uint64 count); +extern int lseek(int fd, int offset, int whence); + +static int isspace(int c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +/* Core Formatting Implementation */ + +static int _ul_to_str_invert(char *buf, uint64 val, int base, int uppercase) +{ + int i = 0; + const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; + if (val == 0) + { + buf[i++] = '0'; + return i; + } + while (val != 0) + { + buf[i++] = digits[val % base]; + val /= base; + } + return i; +} + +typedef struct +{ + char *buf; + uint64 limit; + uint64 written; + FILE *stream; +} _OutContext; + +static void _out_char(_OutContext *ctx, char c) +{ + if (ctx->stream) + { + fputc(c, ctx->stream); + ctx->written++; + } + else + { + if (ctx->limit > 0 && ctx->written < ctx->limit - 1) + { + ctx->buf[ctx->written] = c; + } + ctx->written++; + } +} + +static int _vprintf_core(_OutContext *ctx, const char *format, va_list ap) +{ + const char *p = format; + char num_buf[32]; + + while (*p) + { + if (*p != '%') + { + _out_char(ctx, *p++); + continue; + } + p++; + + int flag_left = 0, flag_zero = 0, flag_plus = 0, flag_space = 0, flag_hash = 0; + while (1) + { + if (*p == '-') + flag_left = 1; + else if (*p == '0') + flag_zero = 1; + else if (*p == '+') + flag_plus = 1; + else if (*p == ' ') + flag_space = 1; + else if (*p == '#') + flag_hash = 1; + else + break; + p++; + } + + int width = 0; + if (*p == '*') + { + width = va_arg(ap, int); + if (width < 0) + { + flag_left = 1; + width = -width; + } + p++; + } + else + { + while (*p >= '0' && *p <= '9') + { + width = width * 10 + (*p - '0'); + p++; + } + } + + int precision = -1; + if (*p == '.') + { + p++; + if (*p == '*') + { + precision = va_arg(ap, int); + p++; + } + else + { + precision = 0; + while (*p >= '0' && *p <= '9') + { + precision = precision * 10 + (*p - '0'); + p++; + } + } + } + + int len_mod = 0; + if (*p == 'l') + { + len_mod = 1; + p++; + if (*p == 'l') + { + len_mod = 2; + p++; + } + } + else if (*p == 'z') + { + len_mod = 2; + p++; + } + + char spec = *p++; + if (!spec) + break; + + if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x' || spec == 'X' || spec == 'p') + { + uint64 val; + int is_signed = (spec == 'd' || spec == 'i'); + int base = (spec == 'x' || spec == 'X' || spec == 'p') ? 16 : 10; + + if (spec == 'p') + val = (uint64)va_arg(ap, void *); + else if (len_mod == 2) + val = is_signed ? (uint64)va_arg(ap, int64) : va_arg(ap, uint64); + else if (len_mod == 1) + val = is_signed ? (uint64)va_arg(ap, long) : (uint64)va_arg(ap, unsigned long); + else + val = is_signed ? (uint64)((int64)va_arg(ap, int)) : (uint64)va_arg(ap, uint32); + + char sign_char = 0; + if (is_signed) + { + int64 sval = (int64)val; + if (sval < 0) + { + sign_char = '-'; + val = (uint64)(-sval); + } + else if (flag_plus) + sign_char = '+'; + else if (flag_space) + sign_char = ' '; + } + + int digit_len = _ul_to_str_invert(num_buf, val, base, (spec == 'X')); + if (precision >= 0) + flag_zero = 0; + int prec_zeros = (precision > digit_len) ? precision - digit_len : 0; + if (precision == 0 && val == 0) + digit_len = 0; + + int prefix_len = sign_char ? 1 : 0; + char *prefix_str = NULL; + if (spec == 'p' || (flag_hash && val != 0 && (spec == 'x' || spec == 'X'))) + { + prefix_str = (spec == 'X') ? "0X" : "0x"; + prefix_len += 2; + } + + int padding = (width > prefix_len + prec_zeros + digit_len) ? width - (prefix_len + prec_zeros + digit_len) : 0; + + if (!flag_left && !flag_zero) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + if (sign_char) + _out_char(ctx, sign_char); + if (prefix_str) + { + _out_char(ctx, prefix_str[0]); + _out_char(ctx, prefix_str[1]); + } + int total_zeros = prec_zeros + ((flag_zero && !flag_left) ? padding : 0); + for (int i = 0; i < total_zeros; i++) + _out_char(ctx, '0'); + for (int i = digit_len - 1; i >= 0; i--) + _out_char(ctx, num_buf[i]); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') + { + char fmt_tmp[32]; + int f_idx = 0; + fmt_tmp[f_idx++] = '%'; + if (flag_left) + fmt_tmp[f_idx++] = '-'; + if (flag_zero) + fmt_tmp[f_idx++] = '0'; + if (flag_plus) + fmt_tmp[f_idx++] = '+'; + if (flag_space) + fmt_tmp[f_idx++] = ' '; + if (flag_hash) + fmt_tmp[f_idx++] = '#'; + if (width > 0) + f_idx += sprintf(&fmt_tmp[f_idx], "%d", width); + if (precision >= 0) + f_idx += sprintf(&fmt_tmp[f_idx], ".%d", precision); + fmt_tmp[f_idx++] = spec; + fmt_tmp[f_idx] = '\0'; + double f_val = va_arg(ap, double); + char f_buf[128]; + stbsp_snprintf(f_buf, sizeof(f_buf), fmt_tmp, f_val); + for (int i = 0; f_buf[i] != '\0'; i++) + _out_char(ctx, f_buf[i]); + } + else if (spec == 's') + { + const char *s = va_arg(ap, char *); + if (!s) + s = "(null)"; + int len = 0; + const char *tmp = s; + while (*tmp && (precision < 0 || len < precision)) + { + tmp++; + len++; + } + int padding = (width > len) ? width - len : 0; + if (!flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + for (int i = 0; i < len; i++) + _out_char(ctx, s[i]); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else if (spec == 'c') + { + char c = (char)va_arg(ap, int); + int padding = (width > 1) ? width - 1 : 0; + if (!flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + _out_char(ctx, c); + if (flag_left) + for (int i = 0; i < padding; i++) + _out_char(ctx, ' '); + } + else + { + _out_char(ctx, '%'); + if (spec != '%') + _out_char(ctx, spec); + } + } + return (int)ctx->written; +} + +int vsnprintf(char *str, uint64 size, const char *format, va_list ap) +{ + _OutContext ctx; + ctx.buf = str; + ctx.limit = size; + ctx.written = 0; + ctx.stream = NULL; + _vprintf_core(&ctx, format, ap); + if (size > 0) + { + if (ctx.written < size) + str[ctx.written] = '\0'; + else + str[size - 1] = '\0'; + } + return (int)ctx.written; +} +int snprintf(char *str, uint64 size, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int ret = vsnprintf(str, size, format, ap); + va_end(ap); + return ret; +} +int sprintf(char *str, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int ret = vsnprintf(str, (uint64)-1, format, ap); + va_end(ap); + return ret; +} +int printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _OutContext ctx; + ctx.buf = NULL; + ctx.limit = 0; + ctx.written = 0; + ctx.stream = stdout; + _vprintf_core(&ctx, format, ap); + va_end(ap); + return (int)ctx.written; +} + +/* Core Function Implementation */ + +typedef struct +{ + const char *buf; + int fd; + uint64 pos; + int ungotten; +} _InContext; + +static int _in_char(_InContext *ctx) +{ + if (ctx->ungotten >= 0) + { + int c = ctx->ungotten; + ctx->ungotten = -1; + return c; + } + if (ctx->fd == -1) + { + if (ctx->buf[ctx->pos] == '\0') + return -1; + return (unsigned char)ctx->buf[ctx->pos++]; + } + char c; + if (read(ctx->fd, &c, 1) == 1) + return (unsigned char)c; + return -1; +} + +static void _unget_char(_InContext *ctx, int c) +{ + if (c != -1) + ctx->ungotten = c; +} +static void _skip_space(_InContext *ctx) +{ + int c; + while ((c = _in_char(ctx)) != -1) + { + if (!isspace(c)) + { + _unget_char(ctx, c); + break; + } + } +} + +static double _simple_strtod(const char *str, char **endptr) +{ + double val = 0.0; + int sign = 1; + if (*str == '-') + { + sign = -1; + str++; + } + else if (*str == '+') + str++; + while (*str >= '0' && *str <= '9') + { + val = val * 10.0 + (*str - '0'); + str++; + } + if (*str == '.') + { + str++; + double div = 10.0; + while (*str >= '0' && *str <= '9') + { + val += (*str - '0') / div; + div *= 10.0; + str++; + } + } + if (*str == 'e' || *str == 'E') + { + str++; + int esign = 1; + if (*str == '-') + { + esign = -1; + str++; + } + else if (*str == '+') + str++; + int eval = 0; + while (*str >= '0' && *str <= '9') + { + eval = eval * 10 + (*str - '0'); + str++; + } + double p = 1.0; + while (eval--) + p *= 10.0; + if (esign == 1) + val *= p; + else + val /= p; + } + if (endptr) + *endptr = (char *)str; + return sign * val; +} + +static int _vscanf_core(_InContext *ctx, const char *format, va_list ap) +{ + int assigned = 0; + const char *p = format; + while (*p) + { + if (isspace(*p)) + { + _skip_space(ctx); + while (isspace(*p)) + p++; + continue; + } + if (*p != '%') + { + _skip_space(ctx); + int c = _in_char(ctx); + if (c != *p) + { + _unget_char(ctx, c); + return assigned; + } + p++; + continue; + } + p++; + int suppress = 0; + if (*p == '*') + { + suppress = 1; + p++; + } + int width = -1; + while (*p >= '0' && *p <= '9') + { + if (width == -1) + width = 0; + width = width * 10 + (*p - '0'); + p++; + } + int len_mod = 0; + if (*p == 'l') + { + len_mod = 1; + p++; + if (*p == 'l') + { + len_mod = 2; + p++; + } + } + else if (*p == 'h') + { + p++; + if (*p == 'h') + p++; + } + char spec = *p++; + if (!spec) + break; + + if (spec == 'd' || spec == 'i' || spec == 'u' || spec == 'x') + { + _skip_space(ctx); + char num_buf[64]; + int i = 0, c = _in_char(ctx); + if (c == '-' || c == '+') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + int base = (spec == 'x') ? 16 : (spec == 'i' ? 0 : 10); + while (c != -1 && i < 63) + { + int digit = (c >= '0' && c <= '9'); + int hex = (base == 16 || base == 0) && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + int x = (c == 'x' || c == 'X'); + if (base == 0 && i == 1 && num_buf[0] == '0' && x) + base = 16; + if (width > 0 && i >= width) + break; + if (digit || hex || x) + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + else + break; + } + num_buf[i] = '\0'; + _unget_char(ctx, c); + if (i == 0) + return assigned; + if (!suppress) + { + int64 val = strtoll(num_buf, NULL, base); + if (len_mod == 2) + *va_arg(ap, int64 *) = val; + else if (len_mod == 1) + *va_arg(ap, long *) = (long)val; + else + *va_arg(ap, int *) = (int)val; + assigned++; + } + } + else if (spec == 'f' || spec == 'F' || spec == 'e' || spec == 'E' || spec == 'g' || spec == 'G') + { + _skip_space(ctx); + char num_buf[64]; + int i = 0, c = _in_char(ctx); + if (c == '-' || c == '+') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + while (c != -1 && i < 63) + { + if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') + { + num_buf[i++] = (char)c; + c = _in_char(ctx); + } + else + break; + } + num_buf[i] = '\0'; + _unget_char(ctx, c); + if (i == 0) + return assigned; + if (!suppress) + { + double val = _simple_strtod(num_buf, NULL); + if (len_mod == 1) + *va_arg(ap, double *) = val; + else + *va_arg(ap, float *) = (float)val; + assigned++; + } + } + else if (spec == 's') + { + _skip_space(ctx); + char *buf = suppress ? NULL : va_arg(ap, char *); + int i = 0, c = _in_char(ctx); + while (c != -1 && !isspace(c)) + { + if (width > 0 && i >= width) + break; + if (!suppress) + buf[i] = (char)c; + i++; + c = _in_char(ctx); + } + if (!suppress) + buf[i] = '\0'; + _unget_char(ctx, c); + if (i > 0 && !suppress) + assigned++; + } + else if (spec == 'c') + { + int count = (width > 0) ? width : 1; + char *buf = suppress ? NULL : va_arg(ap, char *); + for (int i = 0; i < count; i++) + { + int c = _in_char(ctx); + if (c == -1) + return assigned; + if (!suppress) + buf[i] = (char)c; + } + if (!suppress) + assigned++; + } + } + return assigned; +} + +int sscanf(const char *str, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = str; + ctx.fd = -1; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} +int scanf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = NULL; + ctx.fd = STDIN_FD; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} + +/* FILE Stream Implementation */ + +#define O_RDONLY 00 +#define O_WRONLY 01 +#define O_RDWR 02 +#define O_CREAT 0100 +#define O_TRUNC 01000 +#define O_APPEND 02000 + + +static FILE _stdin = {.fd = 0, .buf_mode = 1}; +static FILE _stdout = {.fd = 1, .buf_mode = 1}; +static FILE _stderr = {.fd = 2, .buf_mode = 2}; + +FILE *stdin = &_stdin; +FILE *stdout = &_stdout; +FILE *stderr = &_stderr; + +FILE *fopen(const char *filename, const char *mode) +{ + int flags = 0; + if (strcmp(mode, "r") == 0) + flags = O_RDONLY; + else if (strcmp(mode, "w") == 0) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else if (strcmp(mode, "a") == 0) + flags = O_WRONLY | O_CREAT | O_APPEND; + else if (strcmp(mode, "r+") == 0) + flags = O_RDWR; + else + return NULL; + + int64 fd = open(filename, flags); + if (fd < 0) + return NULL; + + FILE *f = (FILE *)malloc(sizeof(FILE)); + if (!f) + { + close(fd); + return NULL; + } + + memset(f, 0, sizeof(FILE)); + f->fd = (int)fd; + f->flags = flags; + + if (fd == 1) + f->buf_mode = 1; + else if (fd == 2) + f->buf_mode = 2; + else + f->buf_mode = 0; + return f; +} + +int fclose(FILE *stream) +{ + if (!stream) + return -1; + fflush(stream); + if (stream != stdin && stream != stdout && stream != stderr) + { + close(stream->fd); + free(stream); + } + return 0; +} + +int fflush(FILE *stream) +{ + if (!stream) + return EOF; + + if (stream->buf_write_pos > 0) + { + int64 ret = write(stream->fd, stream->buffer, stream->buf_write_pos); + if (ret < stream->buf_write_pos) + { + stream->error = 1; + stream->buf_write_pos = 0; + return EOF; + } + stream->buf_write_pos = 0; + } + return 0; +} + +int fprintf(FILE *stream, const char *format, ...) +{ + if (!stream) + return -1; + va_list ap; + va_start(ap, format); + _OutContext ctx; + ctx.buf = NULL; + ctx.limit = 0; + ctx.written = 0; + ctx.stream = stream; + _vprintf_core(&ctx, format, ap); + va_end(ap); + return (int)ctx.written; +} + +int fscanf(FILE *stream, const char *format, ...) +{ + if (!stream) + return -1; + va_list ap; + va_start(ap, format); + _InContext ctx; + ctx.buf = NULL; + ctx.fd = stream->fd; + ctx.pos = 0; + ctx.ungotten = -1; + int ret = _vscanf_core(&ctx, format, ap); + va_end(ap); + return ret; +} + +int fputc(int c, FILE *stream) +{ + if (!stream) + return EOF; + if (stream->buf_mode == 2) + { + char ch = (char)c; + if (write(stream->fd, &ch, 1) != 1) + { + stream->error = 1; + return EOF; + } + return c; + } + + stream->buffer[stream->buf_write_pos++] = (char)c; + + if (stream->buf_write_pos >= BUFSIZ) + { + fflush(stream); + } + else if (stream->buf_mode == 1 && c == '\n') + { + fflush(stream); + } + + return c; +} + +int fputs(const char *s, FILE *stream) +{ + if (!stream) + return -1; + while (*s) + { + if (fputc(*s++, stream) == EOF) + return EOF; + } + return 0; +} + +int fgetc(FILE *stream) +{ + if (!stream) + return EOF; + + if (stream->buf_write_pos > 0) + { + fflush(stream); + } + + if (stream->buf_read_pos < stream->buf_read_cnt) + { + return (unsigned char)stream->buffer[stream->buf_read_pos++]; + } + + int64 ret = read(stream->fd, stream->buffer, BUFSIZ); + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return EOF; + } + + stream->buf_read_cnt = (int)ret; + stream->buf_read_pos = 0; + return (unsigned char)stream->buffer[stream->buf_read_pos++]; +} + +char *fgets(char *str, int n, FILE *stream) +{ + if (!stream || n <= 0) + return NULL; + int i = 0, c; + while (i < n - 1) + { + c = fgetc(stream); + if (c == -1) + { + if (i == 0) + return NULL; + break; + } + str[i++] = (char)c; + if (c == '\n') + break; + } + str[i] = '\0'; + return str; +} + +uint64 fread(void *ptr, uint64 size, uint64 nmemb, FILE *stream) +{ + if (!stream || size == 0 || nmemb == 0) + return 0; + + uint64 total_bytes = size * nmemb; + uint64 bytes_read = 0; + char *dst = (char *)ptr; + + if (stream->buf_write_pos > 0) + fflush(stream); + + while (bytes_read < total_bytes) + { + if (stream->buf_read_pos < stream->buf_read_cnt) + { + uint64 available = stream->buf_read_cnt - stream->buf_read_pos; + uint64 remaining = total_bytes - bytes_read; + uint64 to_copy = (available < remaining) ? available : remaining; + + memcpy(dst + bytes_read, stream->buffer + stream->buf_read_pos, to_copy); + + stream->buf_read_pos += to_copy; + bytes_read += to_copy; + } + else + { + uint64 remaining = total_bytes - bytes_read; + + if (remaining >= BUFSIZ) + { + int64 ret = read(stream->fd, dst + bytes_read, remaining); + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return bytes_read / size; + } + bytes_read += ret; + } + else + { + stream->buf_read_pos = 0; + stream->buf_read_cnt = 0; + int64 ret = read(stream->fd, stream->buffer, BUFSIZ); + + if (ret <= 0) + { + if (ret == 0) + stream->eof = 1; + else + stream->error = 1; + return bytes_read / size; + } + stream->buf_read_cnt = (int)ret; + } + } + } + return bytes_read / size; +} + +uint64 fwrite(const void *ptr, uint64 size, uint64 nmemb, FILE *stream) +{ + if (!stream || !ptr || size == 0 || nmemb == 0) + return 0; + uint64 total_bytes = size * nmemb; + const char *src = (const char *)ptr; + uint64 bytes_written = 0; + + if (stream->buf_mode == 2) + { + while (bytes_written < total_bytes) + { + int64 ret = write(stream->fd, src + bytes_written, total_bytes - bytes_written); + if (ret <= 0) + { + stream->error = 1; + break; + } + bytes_written += ret; + } + return bytes_written / size; + } + + while (bytes_written < total_bytes) + { + uint64 space_left = BUFSIZ - stream->buf_write_pos; + uint64 remaining = total_bytes - bytes_written; + + if (remaining <= space_left) + { + memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, remaining); + stream->buf_write_pos += remaining; + bytes_written += remaining; + if (stream->buf_write_pos == BUFSIZ) + fflush(stream); + } + else + { + memcpy(stream->buffer + stream->buf_write_pos, src + bytes_written, space_left); + stream->buf_write_pos += space_left; + bytes_written += space_left; + fflush(stream); + } + } + + return bytes_written / size; +} + +int fseek(FILE *stream, long offset, int whence) +{ + if (!stream) + return -1; + + if (stream->buf_write_pos > 0) + { + fflush(stream); + } + stream->buf_read_pos = 0; + stream->buf_read_cnt = 0; + stream->eof = 0; + + if (lseek(stream->fd, offset, whence) < 0) + { + stream->error = 1; + return -1; + } + return 0; +} +long ftell(FILE *stream) +{ + if (!stream) + return -1L; + int64 ret = lseek(stream->fd, 0, SEEK_CUR); + if (ret < 0) + { + stream->error = 1; + return -1L; + } + return (long)ret; +} +void rewind(FILE *stream) +{ + if (stream) + { + fseek(stream, 0, SEEK_SET); + stream->error = 0; + } +} + +int feof(FILE *stream) +{ + return (stream) ? stream->eof : 1; +} +int ferror(FILE *stream) +{ + return (stream) ? stream->error : 1; +} +void clearerr(FILE *stream) +{ + if (stream) + { + stream->error = 0; + stream->eof = 0; + } +} + +int ungetc(int c, FILE *stream) +{ + if (!stream || c == EOF) + return EOF; + + if (stream->buf_read_pos > 0) + { + stream->buf_read_pos--; + stream->buffer[stream->buf_read_pos] = (char)c; + stream->eof = 0; + return c; + } + return EOF; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/stdlib.c b/lume-legacy/src/ulib/stdlib.c new file mode 100644 index 0000000..8b22b6f --- /dev/null +++ b/lume-legacy/src/ulib/stdlib.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "ulib/ctypes.h" + +extern int sys_exit(int status); +extern void *sbrk(long increment); + +#undef errno +int errno = 0; + +#define ATEXIT_MAX 32 +static void (*__atexit_funcs[ATEXIT_MAX])(void); +static int __atexit_func_count = 0; + +int atexit(void (*function)(void)) +{ + if (__atexit_func_count >= ATEXIT_MAX) + return -1; + __atexit_funcs[__atexit_func_count++] = function; + return 0; +} + +int exit(int status) +{ + while (__atexit_func_count > 0) + { + (__atexit_funcs[--__atexit_func_count])(); + } + sys_exit(status); + + while (1) + ; + return 0; +} + +void abort(void) +{ + exit(127); +} + +char *getenv(const char *name) +{ + /* None */ + return NULL; +} + +int system(const char *command) +{ + /* None */ + return -1; +} + +/* Memory Management */ +#define ALIGNMENT 16 +#define ALIGN(size) (((size) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1)) + +typedef struct header +{ + size_t size; + struct header *next; +} header_t; + +static header_t base; +static header_t *freep = NULL; + + +#define LOCK() +#define UNLOCK() + +void free(void *ptr) +{ + header_t *bp, *p; + + if (!ptr) + return; + + + bp = (header_t *)ptr - 1; + + LOCK(); + + + for (p = freep; !(bp > p && bp < p->next); p = p->next) + { + if (p >= p->next && (bp > p || bp < p->next)) + break; + } + + if ((char *)bp + bp->size == (char *)p->next) + { + bp->size += p->next->size; + bp->next = p->next->next; + } + else + { + bp->next = p->next; + } + + if ((char *)p + p->size == (char *)bp) + { + p->size += bp->size; + p->next = bp->next; + } + else + { + p->next = bp; + } + + freep = p; + UNLOCK(); +} + +static header_t *morecore(size_t nu) +{ + char *cp; + header_t *up; + + if (nu < 4096) + nu = 4096; + + nu = ALIGN(nu); + + cp = (char *)sbrk(nu); + if (cp == (void *)-1) + return NULL; + + up = (header_t *)cp; + up->size = nu; + + free((void *)(up + 1)); + return freep; +} + +void *malloc(size_t size) +{ + header_t *p, *prevp; + + if (size == 0) + return NULL; + + size = ALIGN(size + sizeof(header_t)); + + LOCK(); + + if ((prevp = freep) == NULL) + { + base.next = freep = prevp = &base; + base.size = 0; + } + + for (p = prevp->next;; prevp = p, p = p->next) + { + if (p->size >= size) + { + if (p->size == size) + { + prevp->next = p->next; + } + else + { + p->size -= size; + + p = (header_t *)((char *)p + p->size); + + p->size = size; + } + freep = prevp; + UNLOCK(); + return (void *)(p + 1); + } + + if (p == freep) + { + if ((p = morecore(size)) == NULL) + { + UNLOCK(); + errno = ENOMEM; + return NULL; + } + } + } +} + +void *calloc(size_t nmemb, size_t size) +{ + size_t total; + if (__builtin_mul_overflow(nmemb, size, &total)) + { + errno = ENOMEM; + return NULL; + } + void *p = malloc(total); + if (p) + memset(p, 0, total); + return p; +} + +void *realloc(void *ptr, size_t size) +{ + if (!ptr) + return malloc(size); + if (size == 0) + { + free(ptr); + return NULL; + } + + header_t *bp = (header_t *)ptr - 1; + size_t new_size = ALIGN(size + sizeof(header_t)); + + if (bp->size >= new_size) + return ptr; + + void *new_ptr = malloc(size); + if (!new_ptr) + return NULL; + + memcpy(new_ptr, ptr, bp->size - sizeof(header_t)); + free(ptr); + return new_ptr; +} + +/* String Conversion*/ + +unsigned long long strtoull(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + unsigned long long cutoff; + int c; + int neg = 0, any, cutlim; + + if (base < 0 || base == 1 || base > 36) + { + if (endptr) + *endptr = (char *)nptr; + errno = 0; /* EINVAL */ + return 0; + } + + do + { + c = *s++; + } while (isspace(c)); + + if (c == '-') + { + neg = 1; + c = *s++; + } + else if (c == '+') + { + c = *s++; + } + + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) + { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = (c == '0' ? 8 : 10); + + cutoff = ULLONG_MAX / (unsigned long long)base; + cutlim = ULLONG_MAX % (unsigned long long)base; + + for (acc = 0, any = 0;; c = *s++) + { + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + + if (c >= base) + break; + + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else + { + any = 1; + acc *= base; + acc += c; + } + } + + if (any < 0) + { + acc = ULLONG_MAX; + errno = ERANGE; + } + else if (neg) + acc = -acc; + + if (endptr != 0) + *endptr = (char *)(any ? s - 1 : nptr); + + return acc; +} + +long long strtoll(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + int c, neg = 0; + + do + { + c = *s++; + } while (isspace(c)); + if (c == '-') + neg = 1; + + acc = strtoull(nptr, endptr, base); + + if (errno == ERANGE && acc == ULLONG_MAX) + return neg ? LLONG_MIN : LLONG_MAX; + + if (!neg && acc > (unsigned long long)LLONG_MAX) + { + errno = ERANGE; + return LLONG_MAX; + } + if (neg && acc > (unsigned long long)LLONG_MAX + 1) + { + errno = ERANGE; + return LLONG_MIN; + } + + return neg ? -(long long)acc : (long long)acc; +} + +unsigned long strtoul(const char *nptr, char **endptr, int base) +{ + unsigned long long ret = strtoull(nptr, endptr, base); + if (ret > ULONG_MAX) + { + errno = ERANGE; + return ULONG_MAX; + } + return (unsigned long)ret; +} + +long strtol(const char *nptr, char **endptr, int base) +{ + long long ret = strtoll(nptr, endptr, base); + if (ret > LONG_MAX) + { + errno = ERANGE; + return LONG_MAX; + } + if (ret < LONG_MIN) + { + errno = ERANGE; + return LONG_MIN; + } + return (long)ret; +} + +int atoi(const char *nptr) { return (int)strtol(nptr, NULL, 10); } +long atol(const char *nptr) { return strtol(nptr, NULL, 10); } +long long atoll(const char *nptr) { return strtoll(nptr, NULL, 10); } + +double strtod(const char *nptr, char **endptr) +{ + double val = 0.0; + int sign = 1; + const char *s = nptr; + + while (isspace(*s)) + s++; + if (*s == '-') + { + sign = -1; + s++; + } + else if (*s == '+') + s++; + + while (isdigit(*s)) + { + val = val * 10.0 + (*s - '0'); + s++; + } + if (*s == '.') + { + double factor = 1.0; + s++; + while (isdigit(*s)) + { + factor *= 0.1; + val += (*s - '0') * factor; + s++; + } + } + + if (endptr) + *endptr = (char *)s; + return val * sign; +} + +double atof(const char *nptr) { return strtod(nptr, NULL); } +float strtof(const char *nptr, char **endptr) { return (float)strtod(nptr, endptr); } + +/* Sort & Search */ + +static void swap_bytes(char *a, char *b, size_t size) +{ + char temp; + while (size--) + { + temp = *a; + *a++ = *b; + *b++ = temp; + } +} + +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) +{ + if (nmemb < 2 || size == 0) + return; + + char *base_ptr = (char *)base; + char *pivot = base_ptr + (nmemb - 1) * size; + char *i = base_ptr; + char *j = base_ptr; + + /* Partition */ + for (; j < pivot; j += size) + { + if (compar(j, pivot) < 0) + { + if (i != j) + swap_bytes(i, j, size); + i += size; + } + } + swap_bytes(i, pivot, size); + + /* Recurse */ + size_t left_count = (i - base_ptr) / size; + qsort(base, left_count, size, compar); + qsort(i + size, nmemb - left_count - 1, size, compar); +} + +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) +{ + size_t l = 0, u = nmemb; + while (l < u) + { + size_t idx = (l + u) / 2; + const void *p = (void *)((const char *)base + (idx * size)); + int cmp = compar(key, p); + if (cmp < 0) + u = idx; + else if (cmp > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} + +/* Math & Mics */ + +int abs(int j) { return (j < 0) ? -j : j; } +long labs(long j) { return (j < 0) ? -j : j; } +long long llabs(long long j) { return (j < 0) ? -j : j; } + +div_t div(int numer, int denom) +{ + div_t res = {numer / denom, numer % denom}; + return res; +} +ldiv_t ldiv(long numer, long denom) +{ + ldiv_t res = {numer / denom, numer % denom}; + return res; +} +lldiv_t lldiv(long long numer, long long denom) +{ + lldiv_t res = {numer / denom, numer % denom}; + return res; +} + +static unsigned long next = 1; +int rand(void) +{ + next = next * 1103515245 + 12345; + return (unsigned int)(next / 65536) % 32768; +} +void srand(unsigned int seed) { next = seed; } + +/* Stub */ +int mblen(const char *s, size_t n) { return (s && *s && n) ? 1 : 0; } +int mbtowc(wchar_t *pwc, const char *s, size_t n) +{ + if (!s) + return 0; + if (n == 0) + return -1; + if (pwc) + *pwc = (wchar_t)*s; + return (*s != 0); +} +int wctomb(char *s, wchar_t wchar) +{ + if (!s) + return 0; + *s = (char)wchar; + return 1; +} +size_t mbstowcs(wchar_t *dest, const char *src, size_t n) +{ + size_t i = 0; + while (src[i] && i < n) + { + dest[i] = (wchar_t)src[i]; + i++; + } + if (i < n) + dest[i] = 0; + return i; +} +size_t wcstombs(char *dest, const wchar_t *src, size_t n) +{ + size_t i = 0; + while (src[i] && i < n) + { + dest[i] = (char)src[i]; + i++; + } + if (i < n) + dest[i] = 0; + return i; +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/string.c b/lume-legacy/src/ulib/string.c new file mode 100644 index 0000000..c3dbe9a --- /dev/null +++ b/lume-legacy/src/ulib/string.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/string.h" +#include "ulib/stdlib.h" + + +/* Memory Manipulation Functions */ + +void *memset(void *dst, int c, size_t n) +{ + char *cdst = (char *)dst; + for (size_t i = 0; i < n; i++) + cdst[i] = (char)c; + return dst; +} + +void *memmove(void *vdst, const void *vsrc, size_t n) +{ + char *dst = (char *)vdst; + const char *src = (const char *)vsrc; + if (dst == src) + return vdst; + + if (src > dst) + { + while (n--) + *dst++ = *src++; + } + else + { + dst += n; + src += n; + while (n-- > 0) + *--dst = *--src; + } + return vdst; +} + +void *memcpy(void *vdst, const void *vsrc, size_t n) +{ + return memmove(vdst, vsrc, n); +} + +int memcmp(const void *s1, const void *s2, size_t n) +{ + const unsigned char *p1 = (const unsigned char *)s1; + const unsigned char *p2 = (const unsigned char *)s2; + while (n--) + { + if (*p1 != *p2) + return *p1 - *p2; + p1++; + p2++; + } + return 0; +} + +void *memchr(const void *s, int c, size_t n) +{ + const unsigned char *p = (const unsigned char *)s; + while (n--) + { + if (*p == (unsigned char)c) + return (void *)p; + p++; + } + return NULL; +} + +/* String Manipulation Functions */ + +size_t strlen(const char *s) +{ + const char *p = s; + while (*p) + p++; + return (size_t)(p - s); +} + +char *strcpy(char *dst, const char *src) +{ + char *os = dst; + while ((*dst++ = *src++) != 0) + ; + return os; +} + +char *strncpy(char *dst, const char *src, size_t n) +{ + char *os = dst; + size_t i; + + for (i = 0; i < n && src[i] != '\0'; i++) + dst[i] = src[i]; + + for (; i < n; i++) + dst[i] = '\0'; + + return os; +} + +char *strcat(char *dst, const char *src) +{ + char *os = dst; + while (*dst) + dst++; + while ((*dst++ = *src++) != 0) + ; + return os; +} + +char *strncat(char *dst, const char *src, size_t n) +{ + char *os = dst; + size_t dest_len = strlen(dst); + size_t i; + + dst += dest_len; + + for (i = 0; i < n && src[i] != '\0'; i++) + dst[i] = src[i]; + + dst[i] = '\0'; + + return os; +} + +int strcmp(const char *p, const char *q) +{ + while (*p && *p == *q) + { + p++; + q++; + } + return (int)((unsigned char)*p - (unsigned char)*q); +} + +int strncmp(const char *s1, const char *s2, size_t n) +{ + if (n == 0) + return 0; + + while (n-- > 0 && *s1 && *s1 == *s2) + { + if (n == 0 || *s1 == '\0') + break; + s1++; + s2++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +char *strchr(const char *s, int c) +{ + while (*s != (char)c) + { + if (!*s++) + return NULL; + } + return (char *)s; +} + +char *strrchr(const char *s, int c) +{ + const char *last = NULL; + do + { + if (*s == (char)c) + last = s; + } while (*s++); + + return (char *)last; +} + + +char *strstr(const char *haystack, const char *needle) +{ + size_t n_len = strlen(needle); + if (n_len == 0) + return (char *)haystack; + + for (; *haystack; haystack++) + { + if (*haystack != *needle) + continue; + + if (strncmp(haystack, needle, n_len) == 0) + return (char *)haystack; + } + return NULL; +} + +char *strdup(const char *s) +{ + size_t len = strlen(s) + 1; + char *new_str = (char *)malloc(len); + if (new_str == NULL) + return NULL; + + return (char *)memcpy(new_str, s, len); +} + +size_t strlcpy(char *dst, const char *src, size_t size) +{ + size_t src_len = strlen(src); + + if (size > 0) + { + size_t copy_len = (src_len >= size) ? size - 1 : src_len; + memcpy(dst, src, copy_len); + dst[copy_len] = '\0'; + } + + return src_len; +} + +size_t strlcat(char *dst, const char *src, size_t size) +{ + size_t dst_len = 0; + size_t src_len = strlen(src); + + while (dst_len < size && dst[dst_len] != '\0') + dst_len++; + + if (dst_len == size) + return size + src_len; + + size_t space_left = size - dst_len; + size_t copy_len = (src_len >= space_left) ? space_left - 1 : src_len; + + memcpy(dst + dst_len, src, copy_len); + dst[dst_len + copy_len] = '\0'; + + return dst_len + src_len; +} + + +size_t strspn(const char *s, const char *accept) +{ + const char *p = s; + const char *a; + + while (*p) + { + for (a = accept; *a; a++) + { + if (*p == *a) + break; + } + if (*a == '\0') + return (size_t)(p - s); + p++; + } + return (size_t)(p - s); +} + +size_t strcspn(const char *s, const char *reject) +{ + const char *p = s; + const char *r; + + while (*p) + { + for (r = reject; *r; r++) + { + if (*p == *r) + return (size_t)(p - s); + } + p++; + } + return (size_t)(p - s); +} + +char *strpbrk(const char *s, const char *accept) +{ + const char *a; + while (*s) + { + for (a = accept; *a; a++) + { + if (*s == *a) + return (char *)s; + } + s++; + } + return NULL; +} + +char *strtok(char *str, const char *delim) +{ + static char *olds; + char *token; + + if (str == NULL) + str = olds; + + str += strspn(str, delim); + if (*str == '\0') + { + olds = str; + return NULL; + } + + token = str; + str = strpbrk(token, delim); + + if (str == NULL) + { + olds = strchr(token, '\0'); + } + else + { + *str = '\0'; + olds = str + 1; + } + + return token; +} + +char *strerror(int errnum) +{ + + switch (errnum) + { + case 0: + return "Success"; + // case EINVAL: return "Invalid argument"; + case ENOMEM: return "Out of memory"; + + default: + return "Unknown error"; + } +} \ No newline at end of file diff --git a/lume-legacy/src/ulib/syscalls.c b/lume-legacy/src/ulib/syscalls.c new file mode 100644 index 0000000..676bf18 --- /dev/null +++ b/lume-legacy/src/ulib/syscalls.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard Qin + * Libc Syscall Shims + */ + +#include "common/types.h" +#include "user/user.h" + +// Define external raw syscalls from usys.S +extern int sys_openat(int dfd, const char *filename, int flags, int mode); +extern int sys_mknodat(int dfd, const char *filename, int mode, int dev); +extern int sys_mkdirat(int dfd, const char *path, int mode); +extern int sys_unlinkat(int dfd, const char *path, int flags); +extern int sys_linkat(int olddfd, const char *oldpath, int newdfd, const char *newpath, int flags); +extern int sys_chdir(const char *path); +extern int sys_dup(int oldfd); +extern int sys_fstat(int fd, void *statbuf); +extern int sys_read(int fd, void *buf, int count); +extern int sys_write(int fd, const void *buf, int count); +extern int sys_close(int fd); +extern int sys_pipe2(int pipefd[2], int flags); +extern int sys_clone(unsigned long flags, void *stack, int *ptid, void *tls, int *ctid); +extern int sys_execve(const char *path, char *const argv[], char *const envp[]); +extern int sys_wait4(int pid, int *status, int options, void *rusage); +extern int sys_exit(int status); +extern int sys_getpid(); +extern int sys_brk(int n); +extern int sys_kill(int pid, int sig); +extern int sys_nanosleep(void *req, void *rem); +extern int sys_reboot(int magic1, int magic2, int cmd, void *arg); +extern int sys_lseek(int fd, int offset, int whence); +extern void sys_putc(char c); +extern int sys_disk_test(); + +// --- POSIX Implementation --- + +int open(const char *path, int flags) +{ + return sys_openat(AT_FDCWD, path, flags, 0); +} + +int mknod(const char *path, short major, short minor) +{ + // Pack major/minor into simplified dev_t (major << 20 | minor) + int dev = (major << 20) | (minor & 0xFFFFF); + return sys_mknodat(AT_FDCWD, path, 0, dev); +} + +int mkdir(const char *path) +{ + return sys_mkdirat(AT_FDCWD, path, 0); +} + +int unlink(const char *path) +{ + return sys_unlinkat(AT_FDCWD, path, 0); +} + +int link(const char *oldpath, const char *newpath) +{ + return sys_linkat(AT_FDCWD, oldpath, AT_FDCWD, newpath, 0); +} + +int chdir(const char *path) +{ + return sys_chdir(path); +} + +int fork() +{ + return sys_clone(0, 0, 0, 0, 0); +} + +int exec(char *path, char **argv) +{ + return sys_execve(path, argv, 0); +} + +int wait(uint64 status_addr) +{ + return sys_wait4(-1, (int *)status_addr, 0, 0); +} + +int pipe(int p[]) +{ + return sys_pipe2(p, 0); +} + +int read(int fd, void *buf, int n) +{ + return sys_read(fd, buf, n); +} + +int write(int fd, const void *buf, int n) +{ + return sys_write(fd, buf, n); +} + +int close(int fd) +{ + return sys_close(fd); +} + +int fstat(int fd, void *st) +{ + return sys_fstat(fd, st); +} + +int dup(int fd) +{ + return sys_dup(fd); +} + +int getpid() +{ + return sys_getpid(); +} + +char *sbrk(int n) +{ + return (char *)(long)sys_brk(n); +} + +int kill(int pid) +{ + return sys_kill(pid, 0); +} + +int sleep(int ticks) +{ + return sys_nanosleep((void *)(long)ticks, 0); +} + +int shutdown() +{ + return sys_reboot(0, 0, 0, 0); +} + +void putc(char c) +{ + sys_putc(c); +} + +int disk_test() +{ + return sys_disk_test(); +} + +int lseek(int fd, int offset, int whence) +{ + return sys_lseek(fd, offset, whence); +} \ No newline at end of file diff --git a/lume-legacy/src/user/cat.cc b/lume-legacy/src/user/cat.cc new file mode 100644 index 0000000..b518d77 --- /dev/null +++ b/lume-legacy/src/user/cat.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +void cat(int fd) +{ + int n; + while ((n = read(fd, buf, sizeof(buf))) > 0) + { + if (write(1, buf, n) != n) + { + printf("cat: write error\n"); + exit(1); + } + } + if (n < 0) + { + printf("cat: read error\n"); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + int fd, i; + + if (argc <= 1) + { + cat(0); + exit(0); + } + + for (i = 1; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("cat: cannot open %s\n", argv[i]); + exit(1); + } + cat(fd); + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/cp.cc b/lume-legacy/src/user/cp.cc new file mode 100644 index 0000000..ca35e82 --- /dev/null +++ b/lume-legacy/src/user/cp.cc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +int main(int argc, char *argv[]) +{ + int fd_src, fd_dst, n; + + if (argc != 3) + { + printf("Usage: cp src dst\n"); + exit(1); + } + + if ((fd_src = open(argv[1], O_RDONLY)) < 0) + { + printf("cp: cannot open source %s\n", argv[1]); + exit(1); + } + + if ((fd_dst = open(argv[2], O_WRONLY | O_CREATE | O_TRUNC)) < 0) + { + printf("cp: cannot create destination %s\n", argv[2]); + close(fd_src); + exit(1); + } + + while ((n = read(fd_src, buf, sizeof(buf))) > 0) + { + if (write(fd_dst, buf, n) != n) + { + printf("cp: write error\n"); + exit(1); + } + } + + if (n < 0) + { + printf("cp: read error\n"); + } + + close(fd_src); + close(fd_dst); + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/echo.cc b/lume-legacy/src/user/echo.cc new file mode 100644 index 0000000..cc9dc2a --- /dev/null +++ b/lume-legacy/src/user/echo.cc @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "user/user.h" +#include "ulib/stdio.h" +#include "ulib/string.h" + +int main(int argc, char* argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + write(1, argv[i], strlen(argv[i])); + if (i + 1 < argc) + { + write(1, " ", 1); + } + else + { + write(1, "\n", 1); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/entry.S b/lume-legacy/src/user/entry.S new file mode 100644 index 0000000..41e0964 --- /dev/null +++ b/lume-legacy/src/user/entry.S @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +.section .text +.global _start + +_start: + call main + + call exit + +1: j 1b \ No newline at end of file diff --git a/lume-legacy/src/user/grep.cc b/lume-legacy/src/user/grep.cc new file mode 100644 index 0000000..1e1c42d --- /dev/null +++ b/lume-legacy/src/user/grep.cc @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[1024]; + +void grep(char *pattern, int fd) +{ + int n, m; + char *p, *q; + + m = 0; + while ((n = read(fd, buf + m, sizeof(buf) - m - 1)) > 0) + { + m += n; + buf[m] = '\0'; + p = buf; + while ((q = strchr(p, '\n')) != 0) + { + *q = 0; + if (strstr(p, pattern) != 0) + { + printf("%s\n", p); + } + p = q + 1; + } + if (p == buf) + m = 0; + if (m > 0) + { + m -= p - buf; + memmove(buf, p, m); + } + } +} + +int main(int argc, char *argv[]) +{ + int fd, i; + char *pattern; + + if (argc < 2) + { + printf("Usage: grep pattern [file...]\n"); + exit(1); + } + pattern = argv[1]; + + if (argc == 2) + { + grep(pattern, 0); // stdin + exit(0); + } + + for (i = 2; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("grep: cannot open %s\n", argv[i]); + exit(1); + } + grep(pattern, fd); + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/init.cc b/lume-legacy/src/user/init.cc new file mode 100644 index 0000000..376e697 --- /dev/null +++ b/lume-legacy/src/user/init.cc @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "drivers/uart_color.h" + +int main() +{ + char *argv[] = {(char *)"sh", 0}; + + printf(ANSI_BLUE "\n[Init] Starting Lume OS Shell..." ANSI_RESET); + printf("\n"); + + while (1) + { + // printf("fork start\n"); + int pid = fork(); + // printf("fork suceess, pid=%i\n", pid); + + if (pid < 0) + { + printf("[Init] fork failed!\n"); + exit(1); + } + + if (pid == 0) + { + + char sh_path[] = "/sh"; + exec(sh_path, argv); + + printf("[Init] exec sh failed\n"); + return 0; + exit(1); + } + else + { + // break; + int status; + int wpid; + while (1) + { + wpid = wait((uint64)&status); + + if (wpid == pid) + { + printf("Shell Exit"); + break; + } + if (wpid < 0) + { + printf("[Init] wait error or no child\n"); + + break; + } + } + } + } + return 0; +} \ No newline at end of file diff --git a/lume-legacy/src/user/kill.cc b/lume-legacy/src/user/kill.cc new file mode 100644 index 0000000..d38cdd6 --- /dev/null +++ b/lume-legacy/src/user/kill.cc @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2) + { + printf("Usage: kill pid...\n"); + exit(1); + } + for (i = 1; i < argc; i++) + { + int pid = atoi(argv[i]); + if (kill(pid) < 0) + { + printf("kill: failed to kill %d\n", pid); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/ln.cc b/lume-legacy/src/user/ln.cc new file mode 100644 index 0000000..a21ddf9 --- /dev/null +++ b/lume-legacy/src/user/ln.cc @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + printf("Usage: ln old new\n"); + exit(1); + } + if (link(argv[1], argv[2]) < 0) + { + printf("link %s %s failed\n", argv[1], argv[2]); + exit(1); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/ls.cc b/lume-legacy/src/user/ls.cc new file mode 100644 index 0000000..c44c799 --- /dev/null +++ b/lume-legacy/src/user/ls.cc @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + + +struct FAT32_DirEnt +{ + char name[11]; + uint8 attr; + uint8 lcase; + uint8 ctime_cs; + uint16 ctime; + uint16 cdate; + uint16 adate; + uint16 fstClusHI; + uint16 wtime; + uint16 wdate; + uint16 fstClusLO; + uint32 fileSize; +} __attribute__((packed)); + +#define ATTR_DIRECTORY 0x10 +#define ATTR_LONG_NAME 0x0F + +void fmt_name(char *dst, const char *src) +{ + int i; + char *d = dst; + + for (i = 0; i < 8; i++) + { + if (src[i] == ' ') + break; + *d++ = src[i]; + } + + if (src[8] != ' ') + { + *d++ = '.'; + for (i = 8; i < 11; i++) + { + if (src[i] == ' ') + break; + *d++ = src[i]; + } + } + *d = 0; + + for (d = dst; *d; d++) + { + if (*d >= 'A' && *d <= 'Z') + *d += 32; + } +} + +void ls(const char *path) +{ + int fd; + struct FAT32_DirEnt de; + char name_buf[16]; + + if ((fd = open(path, O_RDONLY)) < 0) + { + printf("ls: cannot open %s\n", path); + return; + } + + while (read(fd, &de, sizeof(de)) == sizeof(de)) + { + if (de.name[0] == 0) + { + break; + } + + if ((unsigned char)de.name[0] == 0xE5) + { + continue; + } + + if ((de.attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) + { + continue; + } + + fmt_name(name_buf, de.name); + + printf("%s %d\t%s\n", + (de.attr & ATTR_DIRECTORY) ? "" : " ", + de.fileSize, + name_buf); + } + + close(fd); +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + ls("."); + } + else + { + for (int i = 1; i < argc; i++) + { + ls(argv[i]); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/mkdir.cc b/lume-legacy/src/user/mkdir.cc new file mode 100644 index 0000000..6da8b49 --- /dev/null +++ b/lume-legacy/src/user/mkdir.cc @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "user/user.h" + +int main(int argc, char* argv[]) +{ + if (argc < 2) + { + printf("Usage: mkdir ...\n"); + exit(1); + } + + for (int i = 1; i < argc; i++) + { + if (mkdir(argv[i]) < 0) + { + printf("mkdir: cannot create directory '%s'\n", argv[i]); + } + } + + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/mv.cc b/lume-legacy/src/user/mv.cc new file mode 100644 index 0000000..a196145 --- /dev/null +++ b/lume-legacy/src/user/mv.cc @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + printf("Usage: mv old new\n"); + exit(1); + } + + if (link(argv[1], argv[2]) < 0) + { + printf("mv: failed to link %s to %s\n", argv[1], argv[2]); + exit(1); + } + + if (unlink(argv[1]) < 0) + { + printf("mv: failed to unlink %s\n", argv[1]); + exit(1); + } + + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/rm.cc b/lume-legacy/src/user/rm.cc new file mode 100644 index 0000000..4777ade --- /dev/null +++ b/lume-legacy/src/user/rm.cc @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" + +int main(int argc, char *argv[]) +{ + int i; + + if (argc < 2) + { + printf("Usage: rm files...\n"); + exit(1); + } + + for (i = 1; i < argc; i++) + { + if (unlink(argv[i]) < 0) + { + printf("rm: %s failed to delete\n", argv[i]); + exit(1); + } + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/sh.cc b/lume-legacy/src/user/sh.cc new file mode 100644 index 0000000..47aad9e --- /dev/null +++ b/lume-legacy/src/user/sh.cc @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" +#include "drivers/uart_color.h" + +constexpr int MAX_ARGS = 10; +const char *WHITESPACE = " \t\r\n\v"; +const char *SYMBOLS = "<|>&;()"; + +void panic(const char *s) +{ + printf("shell panic: %s\n", s); + exit(1); +} + +int fork_or_panic() +{ + int pid = fork(); + if (pid == -1) + panic("fork"); + return pid; +} + +struct Command +{ + virtual ~Command() = default; + + virtual void run() = 0; +}; + +struct ExecCommand : public Command +{ + char *argv[MAX_ARGS]; + + ExecCommand() + { + memset(argv, 0, sizeof(argv)); + } + + void run() override + { + if (argv[0] == nullptr) + exit(1); + + exec(argv[0], argv); + + printf("exec %s failed\n", argv[0]); + exit(1); + } +}; + +struct RedirCommand : public Command +{ + Command *sub_cmd; + char *file; + char *efile; // file name end pointer (used by parser) + int mode; + int fd; + + RedirCommand(Command *c, char *f, char *ef, int m, int fd_in) + : sub_cmd(c), file(f), efile(ef), mode(m), fd(fd_in) {} + + ~RedirCommand() { delete sub_cmd; } + + void run() override + { + close(fd); + + int newfd = open(file, mode); + + if (newfd < 0) + { + printf("open %s failed\n", file); + exit(1); + } + + if (newfd != fd) + { + + if (dup(newfd) != fd) + { + printf("Redirection failed: could not move fd %d to %d\n", newfd, fd); + exit(1); + } + close(newfd); + } + + sub_cmd->run(); + } +}; + +struct PipeCommand : public Command +{ + Command *left; + Command *right; + + PipeCommand(Command *l, Command *r) : left(l), right(r) {} + ~PipeCommand() + { + delete left; + delete right; + } + + void run() override + { + int p[2]; + if (pipe(p) < 0) + panic("pipe"); + + if (fork_or_panic() == 0) + { + // Left child: write to pipe + close(1); // close stdout + dup(p[1]); // dup write-end to stdout + close(p[0]); + close(p[1]); + left->run(); + } + + if (fork_or_panic() == 0) + { + // Right child: read from pipe + close(0); // close stdin + dup(p[0]); // dup read-end to stdin + close(p[0]); + close(p[1]); + right->run(); + } + + close(p[0]); + close(p[1]); + wait(0); + wait(0); + exit(0); + } +}; + +struct ListCommand : public Command +{ + Command *left; + Command *right; + + ListCommand(Command *l, Command *r) : left(l), right(r) {} + ~ListCommand() + { + delete left; + delete right; + } + + void run() override + { + if (fork_or_panic() == 0) + { + left->run(); + } + wait(0); + right->run(); + } +}; + +struct BackCommand : public Command +{ + Command *sub_cmd; + + BackCommand(Command *c) : sub_cmd(c) {} + ~BackCommand() { delete sub_cmd; } + + void run() override + { + if (fork_or_panic() == 0) + { + sub_cmd->run(); + } + // Parent doesn't wait + exit(0); + } +}; + +class Parser +{ +public: + static Command *parse(char *s) + { + char *es = s + strlen(s); + Command *cmd = parse_line(&s, es); + peek(&s, es, ""); + if (s != es) + { + printf("leftovers: %s\n", s); + // panic("syntax"); // strict syntax check + } + terminate_strings(cmd); + return cmd; + } + +private: + // Helper: NUL-terminate strings found during parsing + static void terminate_strings(Command *cmd) + { + if (!cmd) + return; + } + + static char get_token(char **ps, char *es, char **q, char **eq) + { + char *s; + int ret; + + s = *ps; + while (s < es && strchr(WHITESPACE, *s)) + s++; + if (q) + *q = s; + ret = *s; + switch (*s) + { + case 0: + break; + case '|': + case '(': + case ')': + case ';': + case '&': + case '<': + s++; + break; + case '>': + s++; + if (*s == '>') + { + ret = '+'; + s++; + } + break; + default: + ret = 'a'; + while (s < es && !strchr(WHITESPACE, *s) && !strchr(SYMBOLS, *s)) + s++; + break; + } + if (eq) + *eq = s; + + while (s < es && strchr(WHITESPACE, *s)) + s++; + *ps = s; + return ret; + } + + static int peek(char **ps, char *es, const char *toks) + { + char *s = *ps; + while (s < es && strchr(WHITESPACE, *s)) + s++; + *ps = s; + return *s && strchr(toks, *s); + } + + // Recursive Descent Parsers + + static Command *parse_line(char **ps, char *es) + { + Command *cmd = parse_pipe(ps, es); + while (peek(ps, es, "&")) + { + get_token(ps, es, 0, 0); + cmd = new BackCommand(cmd); + } + if (peek(ps, es, ";")) + { + get_token(ps, es, 0, 0); + cmd = new ListCommand(cmd, parse_line(ps, es)); + } + return cmd; + } + + static Command *parse_pipe(char **ps, char *es) + { + Command *cmd = parse_exec(ps, es); + if (peek(ps, es, "|")) + { + get_token(ps, es, 0, 0); + cmd = new PipeCommand(cmd, parse_pipe(ps, es)); + } + return cmd; + } + + static Command *parse_exec(char **ps, char *es) + { + char *q, *eq; + int tok, argc; + ExecCommand *cmd = new ExecCommand(); + Command *ret = cmd; + + argc = 0; + ret = parse_redirs(ret, ps, es); + while (!peek(ps, es, "|)&;")) + { + if ((tok = get_token(ps, es, &q, &eq)) == 0) + break; + if (tok != 'a') + panic("syntax"); + + cmd->argv[argc] = q; + *eq = 0; // Temp + + argc++; + if (argc >= MAX_ARGS) + panic("too many args"); + ret = parse_redirs(ret, ps, es); + } + cmd->argv[argc] = 0; + return ret; + } + + static Command *parse_redirs(Command *cmd, char **ps, char *es) + { + int tok; + char *q, *eq; + + while (peek(ps, es, "<>")) + { + tok = get_token(ps, es, 0, 0); + if (get_token(ps, es, &q, &eq) != 'a') + panic("missing file for redirection"); + + *eq = 0; // Null terminate filename immediately + + switch (tok) + { + case '<': + cmd = new RedirCommand(cmd, q, eq, O_RDONLY, 0); + break; + case '>': + cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE | O_TRUNC, 1); + break; + case '+': // >> + cmd = new RedirCommand(cmd, q, eq, O_WRONLY | O_CREATE, 1); // Append not fully supported in simple OS yet? + break; + } + } + return cmd; + } +}; + +int getcmd(char *buf, int nbuf) +{ + write(1, "$ ", 2); + memset(buf, 0, nbuf); + + int n = read(0, buf, nbuf - 1); + + if (n < 0) + return -1; + + buf[n] = 0; + return 0; +} + +int main() +{ + static char buf[100]; + + + printf(ANSI_GREEN "\n[Lume Shell] Welcome! \n" ANSI_RESET); + printf(ANSI_YELLOW "[Lume Shell] Start your journey of exploration!" ANSI_RESET); + printf("\n"); + + while (getcmd(buf, sizeof(buf)) >= 0) + { + int len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + { + buf[len - 1] = 0; + } + + if (buf[0] == 'c' && buf[1] == 'd' && (buf[2] == ' ' || buf[2] == '\n' || buf[2] == 0)) + { + if (buf[strlen(buf) - 1] == '\n') + buf[strlen(buf) - 1] = 0; + + char *path = buf + 3; + if (buf[2] == '\n' || buf[2] == 0) + { + printf("cd: argument missing\n"); + continue; + } + + if (chdir(path) < 0) + { + printf("cannot cd %s\n", path); + } + continue; + } + if (strcmp(buf, "shutdown") == 0 || strcmp(buf, "poweroff") == 0) + { + printf(ANSI_BLUE "System shutting down..." ANSI_RESET); + printf("\n"); + shutdown(); + + continue; + } + + if (fork_or_panic() == 0) + { + Command *cmd = Parser::parse(buf); + if (cmd) + { + cmd->run(); + delete cmd; + } + exit(0); + } + wait(0); + } + return 0; +} \ No newline at end of file diff --git a/lume-legacy/src/user/touch.cc b/lume-legacy/src/user/touch.cc new file mode 100644 index 0000000..2f9f9f6 --- /dev/null +++ b/lume-legacy/src/user/touch.cc @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "user/user.h" +#include "common/fcntl.h" + +int main(int argc, char *argv[]) +{ + int i, fd; + + if (argc < 2) + { + printf("Usage: touch files...\n"); + exit(1); + } + + for (i = 1; i < argc; i++) + { + fd = open(argv[i], O_CREATE | O_RDWR); + if (fd < 0) + { + printf("touch: cannot touch %s\n", argv[i]); + exit(1); + } + close(fd); + } + exit(0); +} \ No newline at end of file diff --git a/lume-legacy/src/user/user.ld b/lume-legacy/src/user/user.ld new file mode 100644 index 0000000..f0a4b27 --- /dev/null +++ b/lume-legacy/src/user/user.ld @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +OUTPUT_ARCH( "riscv" ) +ENTRY( _start ) + +SECTIONS +{ + . = 0x0; + + .text : { + *(.text.entry) + *(.text .text.*) + } + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) + . = ALIGN(16); + *(.rodata .rodata.*) + } + + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) + . = ALIGN(16); + *(.data .data.*) + } + + .bss : { + . = ALIGN(16); + *(.sbss .sbss.*) + . = ALIGN(16); + *(.bss .bss.*) + } +} \ No newline at end of file diff --git a/lume-legacy/src/user/usys.S b/lume-legacy/src/user/usys.S new file mode 100644 index 0000000..1e373d3 --- /dev/null +++ b/lume-legacy/src/user/usys.S @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2026 Richard Qin + +#include "kernel/syscall.h" + +#define SYSCALL_NAME(name, sysname) \ + .global name; \ + name: \ + li a7, SYS_ ## sysname; \ + ecall; \ + ret + +SYSCALL_NAME(sys_openat, openat) +SYSCALL_NAME(sys_mknodat, mknodat) +SYSCALL_NAME(sys_mkdirat, mkdirat) +SYSCALL_NAME(sys_unlinkat, unlinkat) +SYSCALL_NAME(sys_linkat, linkat) +SYSCALL_NAME(sys_chdir, chdir) +SYSCALL_NAME(sys_dup, dup) +SYSCALL_NAME(sys_fstat, fstat) +SYSCALL_NAME(sys_read, read) +SYSCALL_NAME(sys_write, write) +SYSCALL_NAME(sys_close, close) +SYSCALL_NAME(sys_pipe2, pipe2) +SYSCALL_NAME(sys_clone, clone) +SYSCALL_NAME(sys_execve, execve) +SYSCALL_NAME(sys_wait4, wait4) +SYSCALL_NAME(sys_exit, exit) +SYSCALL_NAME(sys_getpid, getpid) +SYSCALL_NAME(sys_brk, brk) +SYSCALL_NAME(sys_kill, kill) +SYSCALL_NAME(sys_nanosleep, nanosleep) +SYSCALL_NAME(sys_reboot, reboot) +SYSCALL_NAME(sys_lseek, lseek) + +# === Custom / Debug Syscalls === +SYSCALL_NAME(sys_putc, putc) +SYSCALL_NAME(sys_disk_test, disk_test) \ No newline at end of file diff --git a/lume-legacy/src/user/wc.cc b/lume-legacy/src/user/wc.cc new file mode 100644 index 0000000..fbe9735 --- /dev/null +++ b/lume-legacy/src/user/wc.cc @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2026 Richard QIn + */ +#include "ulib/stdio.h" +#include "ulib/stdlib.h" +#include "ulib/string.h" +#include "user/user.h" +#include "common/fcntl.h" + +char buf[512]; + +void wc(int fd, char *name) +{ + int i, n; + int l, w, c, inword; + + l = w = c = 0; + inword = 0; + while ((n = read(fd, buf, sizeof(buf))) > 0) + { + for (i = 0; i < n; i++) + { + c++; + if (buf[i] == '\n') + l++; + if (strchr(" \r\t\n\v", buf[i])) + inword = 0; + else if (!inword) + { + w++; + inword = 1; + } + } + } + printf("%d %d %d %s\n", l, w, c, name); +} + +int main(int argc, char *argv[]) +{ + int fd, i; + + if (argc <= 1) + { + wc(0, ""); + exit(0); + } + + for (i = 1; i < argc; i++) + { + if ((fd = open(argv[i], O_RDONLY)) < 0) + { + printf("wc: cannot open %s\n", argv[i]); + exit(1); + } + wc(fd, argv[i]); + close(fd); + } + exit(0); +} \ No newline at end of file -- cgit v1.2.3