Attempt 2 #2
21
apps/libc/crt0.S
Normal file
21
apps/libc/crt0.S
Normal file
@@ -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
|
||||
147
apps/libc/syscalls.h
Normal file
147
apps/libc/syscalls.h
Normal file
@@ -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 */
|
||||
180
apps/sh/sh.c
Normal file
180
apps/sh/sh.c
Normal file
@@ -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 <path> */
|
||||
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 <path> - 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;
|
||||
}
|
||||
@@ -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)"
|
||||
|
||||
@@ -20,6 +20,7 @@ add_executable(kernel
|
||||
vfs.c
|
||||
initrd_fs.c
|
||||
env.c
|
||||
keyboard.c
|
||||
interrupts.S
|
||||
kernel.c
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "pic.h"
|
||||
#include "process.h"
|
||||
#include "syscall.h"
|
||||
#include "keyboard.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
188
src/keyboard.c
Normal file
188
src/keyboard.c
Normal file
@@ -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 <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 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);
|
||||
}
|
||||
56
src/keyboard.h
Normal file
56
src/keyboard.h
Normal file
@@ -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 <stdint.h>
|
||||
#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 */
|
||||
128
src/syscall.c
128
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 <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user