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
This commit is contained in:
AI
2026-02-23 13:36:34 +00:00
parent 993cf05712
commit e3d011da2f
9 changed files with 146 additions and 22 deletions

View File

@@ -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.

View File

@@ -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;

36
apps/ls/ls.c Normal file
View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;
}