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:
@@ -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.
|
||||
|
||||
@@ -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
36
apps/ls/ls.c
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
46
src/kernel.c
46
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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
19
src/vfs.c
19
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;
|
||||
|
||||
15
src/vga.c
15
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user