feat: shell (sh) with keyboard driver, SYS_READ, SYS_EXEC
- PS/2 keyboard driver: IRQ1 handler, scancode set 1 translation, ring buffer, shift key support - SYS_READ (fd=0): non-blocking keyboard read for stdin - SYS_YIELD: calls schedule_tick directly (no nested INT 0x80) - SYS_EXEC: loads binary from CPIO initrd, replaces process image - User-space libc: crt0.S (C runtime startup), syscalls.h (inline syscall wrappers, basic string functions) - Shell app (sh): readline with echo/backspace, builtins (cd, env, help, exit), fork+exec for external commands - Updated build_apps.sh: C app support with crt0 linking, libc include path, .bss section in objcopy Tested: shell boots, keyboard input works, hello-world runs via fork+exec, env shows CWD, exit cleanly terminates.
This commit is contained in:
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}"
|
CC="${CC:-clang}"
|
||||||
OBJCOPY="${OBJCOPY:-objcopy}"
|
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"
|
LDFLAGS="-m32 -nostdlib -no-pie -Wl,--no-dynamic-linker"
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
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
|
for app_dir in "$APPS_DIR"/*/; do
|
||||||
[ -d "$app_dir" ] || continue
|
[ -d "$app_dir" ] || continue
|
||||||
app_name=$(basename "$app_dir")
|
app_name=$(basename "$app_dir")
|
||||||
|
|
||||||
|
# Skip the libc directory (shared library, not an app)
|
||||||
|
[ "$app_name" = "libc" ] && continue
|
||||||
|
|
||||||
echo "Building app: $app_name"
|
echo "Building app: $app_name"
|
||||||
|
|
||||||
@@ -36,13 +46,22 @@ for app_dir in "$APPS_DIR"/*/; do
|
|||||||
continue
|
continue
|
||||||
fi
|
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"
|
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"
|
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")
|
size=$(wc -c < "$bin")
|
||||||
echo " Built: $bin ($size bytes)"
|
echo " Built: $bin ($size bytes)"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ add_executable(kernel
|
|||||||
vfs.c
|
vfs.c
|
||||||
initrd_fs.c
|
initrd_fs.c
|
||||||
env.c
|
env.c
|
||||||
|
keyboard.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
#include "syscall.h"
|
#include "syscall.h"
|
||||||
|
#include "keyboard.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* Forward declaration for kernel panic or similar */
|
/* Forward declaration for kernel panic or similar */
|
||||||
@@ -61,8 +62,8 @@ void isr_handler(registers_t *regs)
|
|||||||
/* Timer tick - invoke scheduler */
|
/* Timer tick - invoke scheduler */
|
||||||
schedule_tick(regs);
|
schedule_tick(regs);
|
||||||
} else if (regs->int_no == 33) {
|
} else if (regs->int_no == 33) {
|
||||||
/* Keyboard */
|
/* Keyboard IRQ */
|
||||||
offset_print("Keyboard IRQ!\n");
|
keyboard_irq(regs);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "cpio.h"
|
#include "cpio.h"
|
||||||
#include "vfs.h"
|
#include "vfs.h"
|
||||||
#include "initrd_fs.h"
|
#include "initrd_fs.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
|
||||||
void offset_print(const char *str)
|
void offset_print(const char *str)
|
||||||
{
|
{
|
||||||
@@ -52,6 +53,8 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
offset_print("IDT initialized\n");
|
offset_print("IDT initialized\n");
|
||||||
|
|
||||||
init_pic();
|
init_pic();
|
||||||
|
/* Unmask timer IRQ (IRQ0) explicitly */
|
||||||
|
pic_clear_mask(0);
|
||||||
offset_print("PIC initialized\n");
|
offset_print("PIC initialized\n");
|
||||||
|
|
||||||
init_pmm(addr);
|
init_pmm(addr);
|
||||||
@@ -130,6 +133,9 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
init_syscalls();
|
init_syscalls();
|
||||||
offset_print("Syscalls initialized\n");
|
offset_print("Syscalls initialized\n");
|
||||||
|
|
||||||
|
keyboard_init();
|
||||||
|
offset_print("Keyboard initialized\n");
|
||||||
|
|
||||||
init_process();
|
init_process();
|
||||||
offset_print("Process subsystem initialized\n");
|
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 "env.h"
|
||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "vga.h"
|
#include "vga.h"
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "cpio.h"
|
||||||
|
#include "paging.h"
|
||||||
|
#include "pmm.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/** Magic return value indicating the syscall blocked and switched processes.
|
/** Magic return value indicating the syscall blocked and switched processes.
|
||||||
* syscall_handler must NOT overwrite regs->eax in this case. */
|
* 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.
|
* 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) {
|
static int32_t sys_read(registers_t *regs) {
|
||||||
(void)regs;
|
int fd = (int)regs->ebx;
|
||||||
return -1; /* Not implemented */
|
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.
|
* 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) {
|
static int32_t sys_yield(registers_t *regs) {
|
||||||
(void)regs;
|
schedule_tick(regs);
|
||||||
schedule();
|
return SYSCALL_SWITCHED;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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) {
|
static int32_t sys_exec(registers_t *regs) {
|
||||||
(void)regs;
|
const char *path = (const char *)regs->ebx;
|
||||||
return -1; /* Not implemented yet */
|
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