diff --git a/apps/libc/crt0.S b/apps/libc/crt0.S new file mode 100644 index 0000000..53d01e3 --- /dev/null +++ b/apps/libc/crt0.S @@ -0,0 +1,21 @@ +/** + * @file crt0.S + * @brief C runtime startup for user-mode applications. + * + * Calls the C main() function and then exits with the return value. + */ +.section .text +.global _start +.extern main + +_start: + /* Call main() */ + call main + + /* Exit with main's return value (in EAX) */ + movl %eax, %ebx /* exit code = return value */ + movl $0, %eax /* SYS_EXIT = 0 */ + int $0x80 + + /* Should never reach here */ +1: jmp 1b diff --git a/apps/libc/syscalls.h b/apps/libc/syscalls.h new file mode 100644 index 0000000..2a89d99 --- /dev/null +++ b/apps/libc/syscalls.h @@ -0,0 +1,147 @@ +/** + * @file syscalls.h + * @brief User-space system call wrappers for ClaudeOS. + * + * Provides inline functions that invoke INT 0x80 with the appropriate + * system call numbers and arguments. + */ + +#ifndef USERSPACE_SYSCALLS_H +#define USERSPACE_SYSCALLS_H + +typedef unsigned int uint32_t; +typedef int int32_t; + +/* System call numbers (must match kernel's syscall.h) */ +#define SYS_EXIT 0 +#define SYS_WRITE 1 +#define SYS_READ 2 +#define SYS_FORK 3 +#define SYS_GETPID 4 +#define SYS_YIELD 5 +#define SYS_WAITPID 6 +#define SYS_EXEC 7 +#define SYS_GETENV 8 +#define SYS_SETENV 9 + +static inline int32_t syscall0(int num) { + int32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(num)); + return ret; +} + +static inline int32_t syscall1(int num, uint32_t arg1) { + int32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) : "a"(num), "b"(arg1)); + return ret; +} + +static inline int32_t syscall2(int num, uint32_t arg1, uint32_t arg2) { + int32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) + : "a"(num), "b"(arg1), "c"(arg2)); + return ret; +} + +static inline int32_t syscall3(int num, uint32_t arg1, uint32_t arg2, uint32_t arg3) { + int32_t ret; + __asm__ volatile("int $0x80" : "=a"(ret) + : "a"(num), "b"(arg1), "c"(arg2), "d"(arg3)); + return ret; +} + +static inline void exit(int code) { + syscall1(SYS_EXIT, (uint32_t)code); + __builtin_unreachable(); +} + +static inline int32_t write(int fd, const void *buf, uint32_t len) { + return syscall3(SYS_WRITE, (uint32_t)fd, (uint32_t)buf, len); +} + +static inline int32_t read(int fd, void *buf, uint32_t len) { + return syscall3(SYS_READ, (uint32_t)fd, (uint32_t)buf, len); +} + +static inline int32_t fork(void) { + return syscall0(SYS_FORK); +} + +static inline int32_t getpid(void) { + return syscall0(SYS_GETPID); +} + +static inline void yield(void) { + syscall0(SYS_YIELD); +} + +static inline int32_t waitpid(int32_t pid) { + return syscall1(SYS_WAITPID, (uint32_t)pid); +} + +static inline int32_t exec(const char *path) { + return syscall1(SYS_EXEC, (uint32_t)path); +} + +static inline int32_t getenv(const char *key, char *buf, uint32_t bufsize) { + return syscall3(SYS_GETENV, (uint32_t)key, (uint32_t)buf, bufsize); +} + +static inline int32_t setenv(const char *key, const char *value) { + return syscall2(SYS_SETENV, (uint32_t)key, (uint32_t)value); +} + +/* Basic string operations for user-space */ +static inline uint32_t strlen(const char *s) { + uint32_t len = 0; + while (s[len]) len++; + return len; +} + +static inline int strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +static inline int strncmp(const char *a, const char *b, uint32_t n) { + while (n && *a && *a == *b) { a++; b++; n--; } + return n ? ((unsigned char)*a - (unsigned char)*b) : 0; +} + +static inline char *strcpy(char *dst, const char *src) { + char *d = dst; + while ((*d++ = *src++)); + return dst; +} + +static inline char *strncpy(char *dst, const char *src, uint32_t n) { + char *d = dst; + while (n && (*d++ = *src++)) n--; + while (n--) *d++ = '\0'; + return dst; +} + +static inline void *memset(void *s, int c, uint32_t n) { + unsigned char *p = (unsigned char *)s; + while (n--) *p++ = (unsigned char)c; + return s; +} + +static inline void *memcpy(void *dst, const void *src, uint32_t n) { + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + while (n--) *d++ = *s++; + return dst; +} + +/** Print a string to stdout. */ +static inline void puts(const char *s) { + write(1, s, strlen(s)); +} + +/** Print a single character to stdout. */ +static inline void putchar(char c) { + write(1, &c, 1); +} + +#endif /* USERSPACE_SYSCALLS_H */ diff --git a/apps/sh/sh.c b/apps/sh/sh.c new file mode 100644 index 0000000..fd66e37 --- /dev/null +++ b/apps/sh/sh.c @@ -0,0 +1,180 @@ +/** + * @file sh.c + * @brief ClaudeOS shell. + * + * A simple interactive shell that reads commands from stdin, + * supports built-in commands (cd, exit, help, env), and + * executes external programs via fork+exec. + */ + +#include "syscalls.h" + +/** Maximum command line length. */ +#define CMD_MAX 256 + +/** Read a line from stdin with echo and basic line editing. + * Returns length of the line (excluding newline). */ +static int readline(char *buf, int maxlen) { + int pos = 0; + while (pos < maxlen - 1) { + char c; + int n = read(0, &c, 1); + if (n <= 0) { + /* No data yet: yield CPU and retry */ + yield(); + continue; + } + + if (c == '\n' || c == '\r') { + putchar('\n'); + break; + } else if (c == '\b' || c == 127) { + /* Backspace */ + if (pos > 0) { + pos--; + puts("\b \b"); /* Move back, overwrite with space, move back */ + } + } else if (c >= 32) { + /* Printable character */ + buf[pos++] = c; + putchar(c); + } + } + buf[pos] = '\0'; + return pos; +} + +/** Skip leading whitespace, return pointer to first non-space. */ +static char *skip_spaces(char *s) { + while (*s == ' ' || *s == '\t') s++; + return s; +} + +/** Find the next space or end of string. */ +static char *find_space(char *s) { + while (*s && *s != ' ' && *s != '\t') s++; + return s; +} + +/** Built-in: cd */ +static void builtin_cd(char *arg) { + if (!arg || !*arg) { + arg = "/"; + } + + /* Update CWD environment variable */ + setenv("CWD", arg); + /* Verify it was set */ + char cwd[128]; + if (getenv("CWD", cwd, sizeof(cwd)) >= 0) { + puts("cd: "); + puts(cwd); + putchar('\n'); + } +} + +/** Built-in: env - print all known env vars. */ +static void builtin_env(void) { + char buf[128]; + if (getenv("CWD", buf, sizeof(buf)) >= 0) { + puts("CWD="); + puts(buf); + putchar('\n'); + } + if (getenv("PATH", buf, sizeof(buf)) >= 0) { + puts("PATH="); + puts(buf); + putchar('\n'); + } + if (getenv("TEST", buf, sizeof(buf)) >= 0) { + puts("TEST="); + puts(buf); + putchar('\n'); + } +} + +/** Built-in: help */ +static void builtin_help(void) { + puts("ClaudeOS Shell\n"); + puts("Built-in commands:\n"); + puts(" cd - change working directory\n"); + puts(" env - show environment variables\n"); + puts(" help - show this message\n"); + puts(" exit - exit the shell\n"); + puts("External commands are loaded from initrd.\n"); +} + +/** Execute an external command via fork+exec. */ +static void run_command(const char *cmd) { + int32_t pid = fork(); + if (pid < 0) { + puts("sh: fork failed\n"); + return; + } + + if (pid == 0) { + /* Child: exec the command */ + int32_t ret = exec(cmd); + if (ret < 0) { + puts("sh: "); + puts(cmd); + puts(": not found\n"); + exit(127); + } + /* exec doesn't return on success */ + exit(1); + } + + /* Parent: wait for child */ + waitpid(pid); +} + +int main(void) { + puts("ClaudeOS Shell v0.1\n"); + puts("Type 'help' for available commands.\n\n"); + + char cmd[CMD_MAX]; + + for (;;) { + /* Print prompt with CWD */ + char cwd[128]; + if (getenv("CWD", cwd, sizeof(cwd)) < 0) { + strcpy(cwd, "/"); + } + puts(cwd); + puts("$ "); + + /* Read command */ + int len = readline(cmd, CMD_MAX); + if (len == 0) continue; + + /* Parse command and arguments */ + char *line = skip_spaces(cmd); + if (*line == '\0') continue; + + char *arg_start = find_space(line); + char *arg = (char *)0; + if (*arg_start) { + *arg_start = '\0'; + arg = skip_spaces(arg_start + 1); + if (*arg == '\0') arg = (char *)0; + } + + /* Check built-in commands */ + if (strcmp(line, "exit") == 0) { + puts("Goodbye!\n"); + exit(0); + } else if (strcmp(line, "cd") == 0) { + builtin_cd(arg); + } else if (strcmp(line, "env") == 0) { + builtin_env(); + } else if (strcmp(line, "help") == 0) { + builtin_help(); + } else { + /* External command */ + run_command(line); + } + } + + return 0; +} diff --git a/scripts/build_apps.sh b/scripts/build_apps.sh index 6a6ebee..1746189 100755 --- a/scripts/build_apps.sh +++ b/scripts/build_apps.sh @@ -11,14 +11,24 @@ LINKER_SCRIPT="$APPS_DIR/user.ld" CC="${CC:-clang}" OBJCOPY="${OBJCOPY:-objcopy}" -CFLAGS="-ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -O2 -Wall" +CFLAGS="-ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -O2 -Wall -I$APPS_DIR/libc" LDFLAGS="-m32 -nostdlib -no-pie -Wl,--no-dynamic-linker" mkdir -p "$OUTPUT_DIR" +# Build crt0 if it exists +CRT0_OBJ="" +if [ -f "$APPS_DIR/libc/crt0.S" ]; then + CRT0_OBJ="$OUTPUT_DIR/_crt0.o" + $CC $CFLAGS -c "$APPS_DIR/libc/crt0.S" -o "$CRT0_OBJ" +fi + for app_dir in "$APPS_DIR"/*/; do [ -d "$app_dir" ] || continue app_name=$(basename "$app_dir") + + # Skip the libc directory (shared library, not an app) + [ "$app_name" = "libc" ] && continue echo "Building app: $app_name" @@ -36,13 +46,22 @@ for app_dir in "$APPS_DIR"/*/; do continue fi - # Link into ELF + # Link into ELF (include crt0 if app has .c files and doesn't have its own _start) elf="$OUTPUT_DIR/$app_name.elf" - $CC $LDFLAGS -T "$LINKER_SCRIPT" $OBJ_FILES -o "$elf" + HAS_C_FILES="" + for src in "$app_dir"*.c; do + [ -f "$src" ] && HAS_C_FILES="yes" + done - # Convert to flat binary (strip non-code sections) + if [ -n "$HAS_C_FILES" ] && [ -n "$CRT0_OBJ" ]; then + $CC $LDFLAGS -T "$LINKER_SCRIPT" "$CRT0_OBJ" $OBJ_FILES -o "$elf" + else + $CC $LDFLAGS -T "$LINKER_SCRIPT" $OBJ_FILES -o "$elf" + fi + + # Convert to flat binary (include .bss for zero-initialized data) bin="$OUTPUT_DIR/$app_name" - $OBJCOPY -O binary --only-section=.text --only-section=.rodata --only-section=.data "$elf" "$bin" + $OBJCOPY -O binary --only-section=.text --only-section=.rodata --only-section=.data --only-section=.bss "$elf" "$bin" size=$(wc -c < "$bin") echo " Built: $bin ($size bytes)" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0da459d..8eef8e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ add_executable(kernel vfs.c initrd_fs.c env.c + keyboard.c interrupts.S kernel.c ) diff --git a/src/isr.c b/src/isr.c index fac1f69..6e94d99 100644 --- a/src/isr.c +++ b/src/isr.c @@ -2,6 +2,7 @@ #include "pic.h" #include "process.h" #include "syscall.h" +#include "keyboard.h" #include /* Forward declaration for kernel panic or similar */ @@ -61,8 +62,8 @@ void isr_handler(registers_t *regs) /* Timer tick - invoke scheduler */ schedule_tick(regs); } else if (regs->int_no == 33) { - /* Keyboard */ - offset_print("Keyboard IRQ!\n"); + /* Keyboard IRQ */ + keyboard_irq(regs); } return; } diff --git a/src/kernel.c b/src/kernel.c index 45f831c..94334bf 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -16,6 +16,7 @@ #include "cpio.h" #include "vfs.h" #include "initrd_fs.h" +#include "keyboard.h" void offset_print(const char *str) { @@ -52,6 +53,8 @@ void kernel_main(uint32_t magic, uint32_t addr) { offset_print("IDT initialized\n"); init_pic(); + /* Unmask timer IRQ (IRQ0) explicitly */ + pic_clear_mask(0); offset_print("PIC initialized\n"); init_pmm(addr); @@ -130,6 +133,9 @@ void kernel_main(uint32_t magic, uint32_t addr) { init_syscalls(); offset_print("Syscalls initialized\n"); + keyboard_init(); + offset_print("Keyboard initialized\n"); + init_process(); offset_print("Process subsystem initialized\n"); diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..27c0636 --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,188 @@ +/** + * @file keyboard.c + * @brief PS/2 keyboard driver implementation. + * + * Reads scancodes from I/O port 0x60 on IRQ1, translates scancode set 1 + * to ASCII using a simple lookup table, stores characters in a ring buffer, + * and wakes any process blocked waiting for keyboard input. + */ + +#include "keyboard.h" +#include "port_io.h" +#include "pic.h" +#include "process.h" +#include +#include + +/* Debug print helpers defined in kernel.c */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/** PS/2 keyboard data port. */ +#define KB_DATA_PORT 0x60 + +/** Ring buffer for keyboard input. */ +static char kb_buffer[KB_BUFFER_SIZE]; +static volatile uint32_t kb_head = 0; +static volatile uint32_t kb_tail = 0; + +/** Process waiting for keyboard input (PID, or 0 if none). */ +static volatile uint32_t kb_waiting_pid = 0; + +/** Shift key state. */ +static int shift_pressed = 0; + +/** + * Scancode set 1 to ASCII lookup table (unshifted). + * Index = scancode, value = ASCII character (0 = unmapped). + */ +static const char scancode_ascii[128] = { + 0, 27, '1', '2', '3', '4', '5', '6', /* 0x00 - 0x07 */ + '7', '8', '9', '0', '-', '=', '\b', '\t', /* 0x08 - 0x0F */ + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', /* 0x10 - 0x17 */ + 'o', 'p', '[', ']', '\n', 0, 'a', 's', /* 0x18 - 0x1F */ + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 0x20 - 0x27 */ + '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', /* 0x28 - 0x2F */ + 'b', 'n', 'm', ',', '.', '/', 0, '*', /* 0x30 - 0x37 */ + 0, ' ', 0, 0, 0, 0, 0, 0, /* 0x38 - 0x3F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x47 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 - 0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x57 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 - 0x5F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x67 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 - 0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x77 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 - 0x7F */ +}; + +/** + * Scancode set 1 to ASCII lookup table (shifted). + */ +static const char scancode_ascii_shift[128] = { + 0, 27, '!', '@', '#', '$', '%', '^', /* 0x00 - 0x07 */ + '&', '*', '(', ')', '_', '+', '\b', '\t', /* 0x08 - 0x0F */ + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', /* 0x10 - 0x17 */ + 'O', 'P', '{', '}', '\n', 0, 'A', 'S', /* 0x18 - 0x1F */ + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', /* 0x20 - 0x27 */ + '"', '~', 0, '|', 'Z', 'X', 'C', 'V', /* 0x28 - 0x2F */ + 'B', 'N', 'M', '<', '>', '?', 0, '*', /* 0x30 - 0x37 */ + 0, ' ', 0, 0, 0, 0, 0, 0, /* 0x38 - 0x3F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x47 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x48 - 0x4F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x57 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x58 - 0x5F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x67 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x68 - 0x6F */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x77 */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x78 - 0x7F */ +}; + +/** Left/right shift scancodes. */ +#define SC_LSHIFT_PRESS 0x2A +#define SC_LSHIFT_RELEASE 0xAA +#define SC_RSHIFT_PRESS 0x36 +#define SC_RSHIFT_RELEASE 0xB6 + +/** + * Put a character into the ring buffer. + */ +static void kb_buffer_put(char c) { + uint32_t next = (kb_head + 1) % KB_BUFFER_SIZE; + if (next == kb_tail) { + return; /* Buffer full, drop character */ + } + kb_buffer[kb_head] = c; + kb_head = next; +} + +void keyboard_init(void) { + kb_head = 0; + kb_tail = 0; + kb_waiting_pid = 0; + shift_pressed = 0; + + /* Flush any pending data from the keyboard controller */ + while (inb(0x64) & 0x01) { + inb(KB_DATA_PORT); + } + + /* Unmask IRQ1 (keyboard) in the PIC */ + pic_clear_mask(1); + + offset_print(" KEYBOARD: initialized\n"); +} + +void keyboard_irq(registers_t *regs) { + (void)regs; + uint8_t scancode = inb(KB_DATA_PORT); + + /* Handle shift keys */ + if (scancode == SC_LSHIFT_PRESS || scancode == SC_RSHIFT_PRESS) { + shift_pressed = 1; + return; + } + if (scancode == SC_LSHIFT_RELEASE || scancode == SC_RSHIFT_RELEASE) { + shift_pressed = 0; + return; + } + + /* Ignore key releases (bit 7 set) */ + if (scancode & 0x80) { + return; + } + + /* Translate scancode to ASCII */ + char c; + if (shift_pressed) { + c = scancode_ascii_shift[scancode]; + } else { + c = scancode_ascii[scancode]; + } + + if (c == 0) { + return; /* Unmapped key */ + } + + /* Put character in buffer */ + kb_buffer_put(c); + + /* Wake any process waiting for keyboard input */ + if (kb_waiting_pid != 0) { + process_t *waiter = process_get(kb_waiting_pid); + if (waiter && waiter->state == PROCESS_BLOCKED) { + waiter->state = PROCESS_READY; + } + kb_waiting_pid = 0; + } +} + +uint32_t keyboard_read(char *buf, uint32_t count) { + uint32_t n = 0; + while (n < count && kb_tail != kb_head) { + buf[n++] = kb_buffer[kb_tail]; + kb_tail = (kb_tail + 1) % KB_BUFFER_SIZE; + } + return n; +} + +int keyboard_has_data(void) { + return kb_head != kb_tail; +} + +void keyboard_block_for_input(registers_t *regs) { + process_t *cur = process_current(); + if (!cur) return; + + cur->state = PROCESS_BLOCKED; + cur->saved_regs = *regs; + + /* Rewind EIP by 2 bytes so that when the process is unblocked and + * scheduled, the CPU re-executes the INT 0x80 instruction. At that + * point keyboard_has_data() will return true and the read succeeds. */ + cur->saved_regs.eip -= 2; + + kb_waiting_pid = cur->pid; + + /* Schedule next process */ + schedule_tick(regs); +} diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..7e8546c --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,56 @@ +/** + * @file keyboard.h + * @brief PS/2 keyboard driver. + * + * Handles IRQ1 keyboard interrupts, translates scancodes to ASCII, + * and provides a ring buffer for user-space reading via SYS_READ. + */ + +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include +#include "isr.h" + +/** Keyboard input buffer size. */ +#define KB_BUFFER_SIZE 256 + +/** + * Initialize the keyboard driver. + */ +void keyboard_init(void); + +/** + * Handle a keyboard IRQ (called from isr_handler for IRQ1). + * + * @param regs Register state (may be used to wake blocked processes). + */ +void keyboard_irq(registers_t *regs); + +/** + * Read characters from the keyboard buffer. + * Non-blocking: returns whatever is available, 0 if empty. + * + * @param buf Destination buffer. + * @param count Maximum bytes to read. + * @return Number of bytes read. + */ +uint32_t keyboard_read(char *buf, uint32_t count); + +/** + * Check if there is data available in the keyboard buffer. + * + * @return Non-zero if data is available. + */ +int keyboard_has_data(void); + +/** + * Block the given process until keyboard data is available. + * Sets the process to BLOCKED state and records it as waiting for keyboard. + * When data arrives, the process will be unblocked. + * + * @param regs Current interrupt frame (for saving process state). + */ +void keyboard_block_for_input(registers_t *regs); + +#endif /* KEYBOARD_H */ diff --git a/src/syscall.c b/src/syscall.c index 4c799ad..637cda3 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -12,7 +12,12 @@ #include "env.h" #include "port_io.h" #include "vga.h" +#include "keyboard.h" +#include "cpio.h" +#include "paging.h" +#include "pmm.h" #include +#include /** Magic return value indicating the syscall blocked and switched processes. * syscall_handler must NOT overwrite regs->eax in this case. */ @@ -60,11 +65,24 @@ static int32_t sys_write(registers_t *regs) { /** * Handle SYS_READ: read bytes from a file descriptor. - * Stub for now. + * fd=0 (stdin) reads from the keyboard buffer (non-blocking). + * Returns 0 if no data available; caller should yield and retry. */ static int32_t sys_read(registers_t *regs) { - (void)regs; - return -1; /* Not implemented */ + int fd = (int)regs->ebx; + char *buf = (char *)regs->ecx; + uint32_t len = regs->edx; + + if (fd == 0) { + /* stdin: non-blocking read from keyboard */ + if (keyboard_has_data()) { + uint32_t n = keyboard_read(buf, len); + return (int32_t)n; + } + return 0; /* No data available */ + } + + return -1; /* Invalid fd */ } /** @@ -85,11 +103,11 @@ static int32_t sys_getpid(registers_t *regs) { /** * Handle SYS_YIELD: voluntarily yield the CPU. + * Calls schedule_tick directly to potentially switch to another process. */ static int32_t sys_yield(registers_t *regs) { - (void)regs; - schedule(); - return 0; + schedule_tick(regs); + return SYSCALL_SWITCHED; } /** @@ -133,11 +151,103 @@ static int32_t sys_waitpid(registers_t *regs) { } /** - * Handle SYS_EXEC: placeholder. + * Handle SYS_EXEC: replace the current process image with a new program. + * EBX = path to binary (C string), e.g. "hello-world". + * Loads the binary from the initrd and replaces the current process's + * code and stack. Does not return on success. */ static int32_t sys_exec(registers_t *regs) { - (void)regs; - return -1; /* Not implemented yet */ + const char *path = (const char *)regs->ebx; + if (!path) return -1; + + process_t *cur = process_current(); + if (!cur) return -1; + + /* Look up the binary in the initrd */ + cpio_entry_t entry; + if (cpio_find(path, &entry) != 0) { + return -1; /* Not found */ + } + + uint32_t *pd = (uint32_t *)cur->page_directory; + + /* Unmap and free old user code pages (0x08048000 region). + * We don't know exactly how many pages were mapped, so scan a + * reasonable range. */ + for (uint32_t vaddr = USER_CODE_START; + vaddr < USER_CODE_START + 0x100000; /* up to 1 MiB of code */ + vaddr += 4096) { + uint32_t pd_idx = vaddr >> 22; + uint32_t pt_idx = (vaddr >> 12) & 0x3FF; + if (!(pd[pd_idx] & 0x001)) break; /* No page table */ + uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000); + if (!(pt[pt_idx] & 0x001)) break; /* No page */ + phys_addr_t old_phys = pt[pt_idx] & 0xFFFFF000; + pt[pt_idx] = 0; + pmm_free_page(old_phys); + } + + /* Map new code pages */ + uint32_t code_pages = (entry.datasize + 4095) / 4096; + for (uint32_t i = 0; i < code_pages; i++) { + phys_addr_t phys = pmm_alloc_page(PMM_ZONE_NORMAL); + if (phys == 0) return -1; + + uint32_t vaddr = USER_CODE_START + i * 4096; + paging_map_page_in(pd, vaddr, phys, + PAGE_PRESENT | PAGE_WRITE | PAGE_USER); + + uint32_t offset = i * 4096; + uint32_t bytes = entry.datasize - offset; + if (bytes > 4096) bytes = 4096; + memcpy((void *)phys, (const uint8_t *)entry.data + offset, bytes); + if (bytes < 4096) { + memset((void *)(phys + bytes), 0, 4096 - bytes); + } + } + + /* Zero the user stack pages (reuse existing stack mappings) */ + uint32_t stack_base = USER_STACK_TOP - USER_STACK_PAGES * 4096; + for (uint32_t i = 0; i < USER_STACK_PAGES; i++) { + uint32_t vaddr = stack_base + i * 4096; + uint32_t pd_idx = vaddr >> 22; + uint32_t pt_idx = (vaddr >> 12) & 0x3FF; + if ((pd[pd_idx] & 0x001)) { + uint32_t *pt = (uint32_t *)(pd[pd_idx] & 0xFFFFF000); + if ((pt[pt_idx] & 0x001)) { + phys_addr_t phys = pt[pt_idx] & 0xFFFFF000; + memset((void *)phys, 0, 4096); + } + } + } + + /* Flush TLB */ + paging_switch_directory(cur->page_directory); + + /* Update process name */ + uint32_t nlen = strlen(path); + if (nlen > 31) nlen = 31; + memcpy(cur->name, path, nlen); + cur->name[nlen] = '\0'; + + /* Set up registers for the new program */ + regs->eip = USER_CODE_START; + regs->useresp = USER_STACK_TOP; + regs->esp = USER_STACK_TOP; + regs->eax = 0; + regs->ebx = 0; + regs->ecx = 0; + regs->edx = 0; + regs->esi = 0; + regs->edi = 0; + regs->ebp = 0; + regs->cs = 0x1B; + regs->ds = 0x23; + regs->ss = 0x23; + regs->eflags = 0x202; /* IF=1 */ + + /* Return SYSCALL_SWITCHED so syscall_handler doesn't overwrite regs */ + return SYSCALL_SWITCHED; } /**