From e3d011da2fac0b8a67eaa646f90396bc51e2a61c Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 13:36:34 +0000 Subject: [PATCH] Fix UTM display: remove required framebuffer tag, add serial output, ls app Display fixes for UTM/Mac black screen issue: - Remove MULTIBOOT_HEADER_TAG_FRAMEBUFFER from multiboot2 header (was required, requesting text mode depth=0 which confused GRUB on some platforms) - Add COM1 serial port (0x3F8) output alongside debugcon for UTM serial capture - Change VGA background from black to dark blue for diagnostics - Add early canary write to 0xB8000 ('COS' in magenta) before subsystem init - print_hex now outputs to both debugcon and COM1 New ls command and SYS_READDIR syscall: - SYS_READDIR (10): reads directory entries via VFS - VFS root listing: vfs_readdir handles '/' by iterating mount table - apps/ls: lists CWD contents, appends '/' for directories - apps/libc/syscalls.h: readdir() wrapper --- README.md | 2 +- apps/libc/syscalls.h | 12 ++++++++++++ apps/ls/ls.c | 36 ++++++++++++++++++++++++++++++++++ src/boot.S | 9 --------- src/kernel.c | 46 +++++++++++++++++++++++++++++++++++++++----- src/syscall.c | 26 +++++++++++++++++++++++++ src/syscall.h | 3 ++- src/vfs.c | 19 ++++++++++++++++++ src/vga.c | 15 +++++++++------ 9 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 apps/ls/ls.c diff --git a/README.md b/README.md index d3f11d4..740f6e7 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Once a task is completed, it should be checked off. - [x] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work. - [x] Implement the fork system call. - [x] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app. -- [ ] Create a basic shell program `sh`. This shell must be able to start the hello-world app. It must include `cd` as a built-in to change the current working directory. +- [x] Create a basic shell program `sh`. This shell must be able to start the hello-world app. It must include `cd` as a built-in to change the current working directory. - [ ] Create an `ls` app. It should list the contents of the current working directory, via the environment variable. - [ ] Create a devicefs vfs driver. The devicefs subsystem should allow drivers to create new block devices devices. - [ ] Create an IDE driver. It should enumerate all IDE devices, and expose them to the vfs driver as `hddN` or `cdN`, where N is a number that starts at 1 and increases for each new device. The `N` value should be calucated by the devicefs subsystem, not the IDE driver. diff --git a/apps/libc/syscalls.h b/apps/libc/syscalls.h index 2a89d99..c78d740 100644 --- a/apps/libc/syscalls.h +++ b/apps/libc/syscalls.h @@ -23,6 +23,7 @@ typedef int int32_t; #define SYS_EXEC 7 #define SYS_GETENV 8 #define SYS_SETENV 9 +#define SYS_READDIR 10 static inline int32_t syscall0(int num) { int32_t ret; @@ -91,6 +92,17 @@ static inline int32_t setenv(const char *key, const char *value) { return syscall2(SYS_SETENV, (uint32_t)key, (uint32_t)value); } +/** + * Read a directory entry. + * @param path Directory path. + * @param idx Entry index (0-based). + * @param name Buffer for entry name (128 bytes min). + * @return Entry type (1=file, 2=dir) on success, -1 at end. + */ +static inline int32_t readdir(const char *path, uint32_t idx, char *name) { + return syscall3(SYS_READDIR, (uint32_t)path, idx, (uint32_t)name); +} + /* Basic string operations for user-space */ static inline uint32_t strlen(const char *s) { uint32_t len = 0; diff --git a/apps/ls/ls.c b/apps/ls/ls.c new file mode 100644 index 0000000..a19c5f1 --- /dev/null +++ b/apps/ls/ls.c @@ -0,0 +1,36 @@ +/** + * @file ls.c + * @brief List directory contents. + * + * Lists entries in the current working directory (from the CWD + * environment variable). Uses the SYS_READDIR syscall to enumerate + * directory entries. + */ + +#include "syscalls.h" + +int main(void) { + /* Get the current working directory */ + char cwd[128]; + if (getenv("CWD", cwd, sizeof(cwd)) < 0) { + /* Default to root if CWD not set */ + cwd[0] = '/'; + cwd[1] = '\0'; + } + + char name[128]; + uint32_t idx = 0; + int32_t type; + + while ((type = readdir(cwd, idx, name)) >= 0) { + puts(name); + if (type == 2) { + /* Directory: append / indicator */ + putchar('/'); + } + putchar('\n'); + idx++; + } + + return 0; +} diff --git a/src/boot.S b/src/boot.S index 82f73c1..37e5e44 100644 --- a/src/boot.S +++ b/src/boot.S @@ -26,15 +26,6 @@ multiboot_header: /* checksum */ .long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header)) - /* Framebuffer tag: request 80x25 EGA text mode */ - .align 8 - .short MULTIBOOT_HEADER_TAG_FRAMEBUFFER - .short 0 /* flags: required (not optional) */ - .long 20 /* size of this tag */ - .long 80 /* width */ - .long 25 /* height */ - .long 0 /* depth: 0 = text mode */ - /* End tag */ .align 8 .short MULTIBOOT_HEADER_TAG_END diff --git a/src/kernel.c b/src/kernel.c index bab47a4..18ca0bb 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -23,10 +23,31 @@ /* Global framebuffer info, parsed from multiboot2 tags. */ framebuffer_info_t fb_info; +/** + * Initialize COM1 serial port for debug output. + * Baud rate: 115200, 8N1. + */ +static void serial_init(void) { + outb(0x3F8 + 1, 0x00); /* Disable interrupts */ + outb(0x3F8 + 3, 0x80); /* Enable DLAB */ + outb(0x3F8 + 0, 0x01); /* Divisor low: 115200 baud */ + outb(0x3F8 + 1, 0x00); /* Divisor high */ + outb(0x3F8 + 3, 0x03); /* 8 bits, no parity, 1 stop */ + outb(0x3F8 + 2, 0xC7); /* Enable FIFO */ + outb(0x3F8 + 4, 0x03); /* RTS/DSR set */ +} + +static void serial_putc(char c) { + /* Wait for transmit buffer empty */ + while (!(inb(0x3F8 + 5) & 0x20)); + outb(0x3F8, c); +} + void offset_print(const char *str) { while (*str) { - outb(0xE9, *str); + outb(0xE9, *str); /* debugcon */ + serial_putc(*str); /* COM1 serial */ str++; } } @@ -34,15 +55,30 @@ void offset_print(const char *str) void print_hex(uint32_t val) { const char *hex = "0123456789ABCDEF"; - outb(0xE9, '0'); - outb(0xE9, 'x'); + outb(0xE9, '0'); serial_putc('0'); + outb(0xE9, 'x'); serial_putc('x'); for (int i = 28; i >= 0; i -= 4) { - outb(0xE9, hex[(val >> i) & 0xF]); + char c = hex[(val >> i) & 0xF]; + outb(0xE9, c); + serial_putc(c); } - outb(0xE9, '\n'); + outb(0xE9, '\n'); serial_putc('\n'); } void kernel_main(uint32_t magic, uint32_t addr) { + /* Initialize serial port first so all debug output goes to COM1 too */ + serial_init(); + + /* Early canary: write directly to VGA text buffer at 0xB8000. + * If the display is in text mode, this will show a bright magenta 'C' + * in the top-left corner before any subsystem is initialized. */ + { + volatile uint16_t *vga = (volatile uint16_t *)0xB8000; + vga[0] = (uint16_t)'C' | (0x5F << 8); /* magenta on magenta = visible block */ + vga[1] = (uint16_t)'O' | (0x5F << 8); + vga[2] = (uint16_t)'S' | (0x5F << 8); + } + if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) { offset_print("Invalid magic number: "); print_hex(magic); diff --git a/src/syscall.c b/src/syscall.c index 637cda3..b199fc3 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -12,6 +12,7 @@ #include "env.h" #include "port_io.h" #include "vga.h" +#include "vfs.h" #include "keyboard.h" #include "cpio.h" #include "paging.h" @@ -281,6 +282,30 @@ static int32_t sys_setenv(registers_t *regs) { return env_set(&cur->env, key, value); } +/** + * Handle SYS_READDIR: read a directory entry. + * EBX = path, ECX = index, EDX = name buffer (128 bytes min). + * Returns entry type (VFS_FILE=1, VFS_DIRECTORY=2, ...) on success, -1 at end. + */ +static int32_t sys_readdir(registers_t *regs) { + const char *path = (const char *)regs->ebx; + uint32_t idx = regs->ecx; + char *name_buf = (char *)regs->edx; + + vfs_dirent_t entry; + if (vfs_readdir(path, idx, &entry) != 0) { + return -1; + } + + /* Copy entry name to user buffer */ + uint32_t len = strlen(entry.name); + if (len >= 128) len = 127; + memcpy(name_buf, entry.name, len); + name_buf[len] = '\0'; + + return (int32_t)entry.type; +} + /** System call dispatch table. */ typedef int32_t (*syscall_fn)(registers_t *); static syscall_fn syscall_table[NUM_SYSCALLS] = { @@ -294,6 +319,7 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = { [SYS_EXEC] = sys_exec, [SYS_GETENV] = sys_getenv, [SYS_SETENV] = sys_setenv, + [SYS_READDIR] = sys_readdir, }; void syscall_handler(registers_t *regs) { diff --git a/src/syscall.h b/src/syscall.h index 02e4df3..1474cbe 100644 --- a/src/syscall.h +++ b/src/syscall.h @@ -24,9 +24,10 @@ #define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */ #define SYS_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */ #define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */ +#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */ /** Total number of system calls. */ -#define NUM_SYSCALLS 10 +#define NUM_SYSCALLS 11 /** * Initialize the system call handler. diff --git a/src/vfs.c b/src/vfs.c index 8a60918..201f20d 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -279,6 +279,25 @@ int32_t vfs_seek(int fd, int32_t offset, int whence) { } int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out) { + /* Special case: root directory lists mount points */ + if (path[0] == '/' && path[1] == '\0') { + uint32_t count = 0; + for (int i = 0; i < VFS_MAX_MOUNTS; i++) { + if (!mounts[i].active) continue; + if (count == idx) { + memset(out, 0, sizeof(vfs_dirent_t)); + /* Extract top-level name from mount path (skip leading /) */ + const char *name = mounts[i].path + 1; + strncpy(out->name, name, VFS_MAX_NAME - 1); + out->type = VFS_DIRECTORY; + out->inode = (uint32_t)i; + return 0; + } + count++; + } + return -1; /* No more entries */ + } + vfs_node_t node; if (resolve_path(path, &node) != 0) { return -1; diff --git a/src/vga.c b/src/vga.c index fc87e8e..c83a0a6 100644 --- a/src/vga.c +++ b/src/vga.c @@ -344,10 +344,10 @@ void vga_put_dec(uint32_t val) { void vga_show_mem_stats(void) { uint32_t mem_kb = pmm_get_memory_size() + 1024; - vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK); + vga_set_color(VGA_LIGHT_CYAN, VGA_BLUE); vga_puts("=== ClaudeOS Memory Statistics ===\n"); - vga_set_color(VGA_WHITE, VGA_BLACK); + vga_set_color(VGA_WHITE, VGA_BLUE); vga_puts(" Total RAM: "); vga_put_dec(mem_kb); vga_puts(" KiB ("); @@ -369,9 +369,9 @@ void vga_show_mem_stats(void) { vga_put_dec(kernel_size / 1024); vga_puts(" KiB\n"); - vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK); + vga_set_color(VGA_LIGHT_CYAN, VGA_BLUE); vga_puts("==================================\n"); - vga_set_color(VGA_LIGHT_GREY, VGA_BLACK); + vga_set_color(VGA_LIGHT_GREY, VGA_BLUE); } /* ================================================================ @@ -443,11 +443,14 @@ int vga_init(void) { offset_print(" VGA: unknown fb type, assuming text mode\n"); } + /* Use dark blue background so user can distinguish "rendering works + * but text invisible" from "framebuffer not working at all". */ + vga_set_color(VGA_LIGHT_GREY, VGA_BLUE); vga_clear(); - vga_set_color(VGA_LIGHT_GREEN, VGA_BLACK); + vga_set_color(VGA_LIGHT_GREEN, VGA_BLUE); vga_puts("ClaudeOS v0.1 booting...\n\n"); - vga_set_color(VGA_LIGHT_GREY, VGA_BLACK); + vga_set_color(VGA_LIGHT_GREY, VGA_BLUE); return 0; }