Compare commits
7 Commits
e9b66cd60e
...
3512e937ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3512e937ae | ||
|
|
c3c01049bf | ||
|
|
d3ab5a5b55 | ||
|
|
e3d011da2f | ||
|
|
993cf05712 | ||
|
|
000d53e2f3 | ||
|
|
c25ba1fccd |
@@ -38,7 +38,7 @@ add_custom_command(
|
||||
add_custom_target(initrd DEPENDS ${INITRD_FILE})
|
||||
|
||||
# Create grub.cfg for ISO - includes module2 for the initrd
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin\n module2 /boot/initrd.cpio }")
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" {\n multiboot2 /boot/kernel.bin\n module2 /boot/initrd.cpio\n}")
|
||||
|
||||
|
||||
# ISO Generation
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
159
apps/libc/syscalls.h
Normal file
159
apps/libc/syscalls.h
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @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
|
||||
#define SYS_READDIR 10
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
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,15 +11,25 @@ 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"
|
||||
|
||||
# Collect source files
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -26,9 +26,8 @@ multiboot_header:
|
||||
/* checksum */
|
||||
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
|
||||
|
||||
/* Tags here */
|
||||
|
||||
/* End tag */
|
||||
.align 8
|
||||
.short MULTIBOOT_HEADER_TAG_END
|
||||
.short 0
|
||||
.long 8
|
||||
|
||||
135
src/devicefs.h
Normal file
135
src/devicefs.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file devicefs.h
|
||||
* @brief Device filesystem (devicefs) subsystem.
|
||||
*
|
||||
* Provides a VFS interface at /dev for exposing block and character devices.
|
||||
* Drivers register devices through the devicefs API, and each device is
|
||||
* assigned a sequential number by device class (e.g., hdd1, hdd2, cd1).
|
||||
*
|
||||
* The devicefs owns device naming — drivers specify a class name (e.g., "hdd")
|
||||
* and the devicefs appends a sequential number.
|
||||
*/
|
||||
|
||||
#ifndef DEVICEFS_H
|
||||
#define DEVICEFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/** Maximum number of registered devices. */
|
||||
#define DEVICEFS_MAX_DEVICES 32
|
||||
|
||||
/** Maximum length of a device class name (e.g., "hdd", "cd", "floppy"). */
|
||||
#define DEVICEFS_MAX_CLASS_NAME 16
|
||||
|
||||
/** Maximum length of a full device name (class + number, e.g., "hdd1"). */
|
||||
#define DEVICEFS_MAX_DEV_NAME 32
|
||||
|
||||
/** Device types. */
|
||||
#define DEVICEFS_BLOCK 0x01 /**< Block device (e.g., hard drives, CDs). */
|
||||
#define DEVICEFS_CHAR 0x02 /**< Character device (e.g., serial ports). */
|
||||
|
||||
/**
|
||||
* Block device operations.
|
||||
*
|
||||
* Block devices transfer data in fixed-size sectors.
|
||||
*/
|
||||
typedef struct devicefs_block_ops {
|
||||
/** Read `count` sectors starting at `lba` into `buf`. Returns 0 on success. */
|
||||
int (*read_sectors)(void *dev_data, uint32_t lba, uint32_t count, void *buf);
|
||||
|
||||
/** Write `count` sectors from `buf` starting at `lba`. Returns 0 on success. */
|
||||
int (*write_sectors)(void *dev_data, uint32_t lba, uint32_t count, const void *buf);
|
||||
|
||||
/** Get the sector size in bytes. */
|
||||
uint32_t (*sector_size)(void *dev_data);
|
||||
|
||||
/** Get total number of sectors. */
|
||||
uint32_t (*sector_count)(void *dev_data);
|
||||
} devicefs_block_ops_t;
|
||||
|
||||
/**
|
||||
* Character device operations.
|
||||
*
|
||||
* Character devices transfer data as byte streams.
|
||||
*/
|
||||
typedef struct devicefs_char_ops {
|
||||
/** Read up to `size` bytes into `buf`. Returns bytes read, or -1. */
|
||||
int32_t (*read)(void *dev_data, uint32_t size, void *buf);
|
||||
|
||||
/** Write `size` bytes from `buf`. Returns bytes written, or -1. */
|
||||
int32_t (*write)(void *dev_data, uint32_t size, const void *buf);
|
||||
} devicefs_char_ops_t;
|
||||
|
||||
/**
|
||||
* Registered device entry.
|
||||
*/
|
||||
typedef struct devicefs_device {
|
||||
char name[DEVICEFS_MAX_DEV_NAME]; /**< Full device name (e.g., "hdd1"). */
|
||||
char class_name[DEVICEFS_MAX_CLASS_NAME]; /**< Device class (e.g., "hdd"). */
|
||||
uint8_t type; /**< DEVICEFS_BLOCK or DEVICEFS_CHAR. */
|
||||
uint32_t number; /**< Assigned device number within class. */
|
||||
int active; /**< 1 if registered, 0 if free. */
|
||||
|
||||
/** Device-specific operations (union of block/char). */
|
||||
union {
|
||||
devicefs_block_ops_t *block_ops;
|
||||
devicefs_char_ops_t *char_ops;
|
||||
};
|
||||
|
||||
/** Opaque driver-specific data passed to operation callbacks. */
|
||||
void *dev_data;
|
||||
} devicefs_device_t;
|
||||
|
||||
/**
|
||||
* Initialize the devicefs subsystem and mount at /dev.
|
||||
*
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int init_devicefs(void);
|
||||
|
||||
/**
|
||||
* Register a new block device.
|
||||
*
|
||||
* The devicefs assigns a sequential number within the class. For example,
|
||||
* registering class "hdd" twice yields "hdd1" and "hdd2".
|
||||
*
|
||||
* @param class_name Device class name (e.g., "hdd", "cd").
|
||||
* @param ops Block device operations.
|
||||
* @param dev_data Opaque data passed to operation callbacks.
|
||||
* @return Pointer to the device entry, or NULL on failure.
|
||||
*/
|
||||
devicefs_device_t *devicefs_register_block(const char *class_name,
|
||||
devicefs_block_ops_t *ops,
|
||||
void *dev_data);
|
||||
|
||||
/**
|
||||
* Register a new character device.
|
||||
*
|
||||
* @param class_name Device class name (e.g., "tty", "serial").
|
||||
* @param ops Character device operations.
|
||||
* @param dev_data Opaque data passed to operation callbacks.
|
||||
* @return Pointer to the device entry, or NULL on failure.
|
||||
*/
|
||||
devicefs_device_t *devicefs_register_char(const char *class_name,
|
||||
devicefs_char_ops_t *ops,
|
||||
void *dev_data);
|
||||
|
||||
/**
|
||||
* Find a device by its full name (e.g., "hdd1").
|
||||
*
|
||||
* @param name Device name.
|
||||
* @return Pointer to the device entry, or NULL if not found.
|
||||
*/
|
||||
devicefs_device_t *devicefs_find(const char *name);
|
||||
|
||||
/**
|
||||
* Get the next device number for a given class.
|
||||
* This is called internally but may be useful for drivers.
|
||||
*
|
||||
* @param class_name Device class name.
|
||||
* @return Next sequential number (starting from 1).
|
||||
*/
|
||||
uint32_t devicefs_next_number(const char *class_name);
|
||||
|
||||
#endif /* DEVICEFS_H */
|
||||
215
src/font8x16.h
Normal file
215
src/font8x16.h
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* @file font8x16.h
|
||||
* @brief Embedded 8x16 VGA bitmap font for graphical framebuffer rendering.
|
||||
*
|
||||
* Each character is 16 bytes: one byte per scanline, MSB is leftmost pixel.
|
||||
* Covers ASCII 32 (space) through 126 (~). Characters outside this range
|
||||
* render as a filled block.
|
||||
*
|
||||
* This is the standard VGA 8x16 font data, in the public domain.
|
||||
*/
|
||||
|
||||
#ifndef FONT8X16_H
|
||||
#define FONT8X16_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define FONT_WIDTH 8
|
||||
#define FONT_HEIGHT 16
|
||||
#define FONT_FIRST 32
|
||||
#define FONT_LAST 126
|
||||
|
||||
static const uint8_t font8x16_data[][16] = {
|
||||
/* 32: space */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 33: ! */
|
||||
{0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||
/* 34: " */
|
||||
{0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 35: # */
|
||||
{0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00},
|
||||
/* 36: $ */
|
||||
{0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0x86,0xC6,0x7C,0x18,0x18,0x00,0x00},
|
||||
/* 37: % */
|
||||
{0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00},
|
||||
/* 38: & */
|
||||
{0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||
/* 39: ' */
|
||||
{0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 40: ( */
|
||||
{0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00},
|
||||
/* 41: ) */
|
||||
{0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00},
|
||||
/* 42: * */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 43: + */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 44: , */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00},
|
||||
/* 45: - */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 46: . */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||
/* 47: / */
|
||||
{0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00},
|
||||
/* 48: 0 */
|
||||
{0x00,0x00,0x3C,0x66,0xC3,0xC3,0xDB,0xDB,0xC3,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 49: 1 */
|
||||
{0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00},
|
||||
/* 50: 2 */
|
||||
{0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||
/* 51: 3 */
|
||||
{0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 52: 4 */
|
||||
{0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00},
|
||||
/* 53: 5 */
|
||||
{0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 54: 6 */
|
||||
{0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 55: 7 */
|
||||
{0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00},
|
||||
/* 56: 8 */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 57: 9 */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00},
|
||||
/* 58: : */
|
||||
{0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 59: ; */
|
||||
{0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00},
|
||||
/* 60: < */
|
||||
{0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00},
|
||||
/* 61: = */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 62: > */
|
||||
{0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00},
|
||||
/* 63: ? */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||
/* 64: @ */
|
||||
{0x00,0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 65: A */
|
||||
{0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 66: B */
|
||||
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00},
|
||||
/* 67: C */
|
||||
{0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 68: D */
|
||||
{0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00},
|
||||
/* 69: E */
|
||||
{0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
|
||||
/* 70: F */
|
||||
{0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||
/* 71: G */
|
||||
{0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00},
|
||||
/* 72: H */
|
||||
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 73: I */
|
||||
{0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 74: J */
|
||||
{0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00},
|
||||
/* 75: K */
|
||||
{0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||
/* 76: L */
|
||||
{0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
|
||||
/* 77: M */
|
||||
{0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 78: N */
|
||||
{0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 79: O */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 80: P */
|
||||
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||
/* 81: Q */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00},
|
||||
/* 82: R */
|
||||
{0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||
/* 83: S */
|
||||
{0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 84: T */
|
||||
{0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 85: U */
|
||||
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 86: V */
|
||||
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00},
|
||||
/* 87: W */
|
||||
{0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00},
|
||||
/* 88: X */
|
||||
{0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 89: Y */
|
||||
{0x00,0x00,0xC3,0xC3,0x66,0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 90: Z */
|
||||
{0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||
/* 91: [ */
|
||||
{0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 92: \ */
|
||||
{0x00,0x00,0x00,0x80,0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x06,0x02,0x00,0x00,0x00,0x00},
|
||||
/* 93: ] */
|
||||
{0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 94: ^ */
|
||||
{0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 95: _ */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00},
|
||||
/* 96: ` */
|
||||
{0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
/* 97: a */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||
/* 98: b */
|
||||
{0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 99: c */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 100: d */
|
||||
{0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||
/* 101: e */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 102: f */
|
||||
{0x00,0x00,0x1C,0x36,0x32,0x30,0x78,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00},
|
||||
/* 103: g */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00,0x00},
|
||||
/* 104: h */
|
||||
{0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||
/* 105: i */
|
||||
{0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 106: j */
|
||||
{0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x3C,0x00,0x00},
|
||||
/* 107: k */
|
||||
{0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00},
|
||||
/* 108: l */
|
||||
{0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
|
||||
/* 109: m */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xE6,0xFF,0xDB,0xDB,0xDB,0xDB,0xDB,0x00,0x00,0x00,0x00},
|
||||
/* 110: n */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00},
|
||||
/* 111: o */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 112: p */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00,0x00},
|
||||
/* 113: q */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00,0x00},
|
||||
/* 114: r */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
|
||||
/* 115: s */
|
||||
{0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00},
|
||||
/* 116: t */
|
||||
{0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00},
|
||||
/* 117: u */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
|
||||
/* 118: v */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xC3,0xC3,0xC3,0xC3,0x66,0x3C,0x18,0x00,0x00,0x00,0x00},
|
||||
/* 119: w */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00},
|
||||
/* 120: x */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00},
|
||||
/* 121: y */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00,0x00},
|
||||
/* 122: z */
|
||||
{0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00},
|
||||
/* 123: { */
|
||||
{0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00},
|
||||
/* 124: | */
|
||||
{0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00},
|
||||
/* 125: } */
|
||||
{0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00},
|
||||
/* 126: ~ */
|
||||
{0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
|
||||
};
|
||||
|
||||
#endif /* FONT8X16_H */
|
||||
44
src/framebuffer.h
Normal file
44
src/framebuffer.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file framebuffer.h
|
||||
* @brief Framebuffer information from the bootloader.
|
||||
*
|
||||
* Stores the display mode and framebuffer address provided by GRUB
|
||||
* via the multiboot2 framebuffer tag. The VGA driver uses this to
|
||||
* decide between text-mode writes (0xB8000) and pixel rendering.
|
||||
*/
|
||||
|
||||
#ifndef FRAMEBUFFER_H
|
||||
#define FRAMEBUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** Framebuffer types (matches multiboot2 definitions). */
|
||||
#define FB_TYPE_INDEXED 0
|
||||
#define FB_TYPE_RGB 1
|
||||
#define FB_TYPE_EGA_TEXT 2
|
||||
|
||||
/**
|
||||
* Framebuffer information structure.
|
||||
* Populated during boot from the multiboot2 framebuffer tag.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t addr; /**< Physical address of the framebuffer. */
|
||||
uint32_t pitch; /**< Bytes per scanline. */
|
||||
uint32_t width; /**< Width in pixels (or columns for text). */
|
||||
uint32_t height; /**< Height in pixels (or rows for text). */
|
||||
uint8_t bpp; /**< Bits per pixel. */
|
||||
uint8_t type; /**< FB_TYPE_RGB, FB_TYPE_EGA_TEXT, etc. */
|
||||
|
||||
/* RGB field positions (only valid when type == FB_TYPE_RGB). */
|
||||
uint8_t red_pos;
|
||||
uint8_t red_size;
|
||||
uint8_t green_pos;
|
||||
uint8_t green_size;
|
||||
uint8_t blue_pos;
|
||||
uint8_t blue_size;
|
||||
} framebuffer_info_t;
|
||||
|
||||
/** Global framebuffer info, filled by kernel_main. */
|
||||
extern framebuffer_info_t fb_info;
|
||||
|
||||
#endif /* FRAMEBUFFER_H */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
164
src/kernel.c
164
src/kernel.c
@@ -1,6 +1,7 @@
|
||||
#include <multiboot2.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "gdt.h"
|
||||
#include "idt.h"
|
||||
#include "pic.h"
|
||||
@@ -16,11 +17,37 @@
|
||||
#include "cpio.h"
|
||||
#include "vfs.h"
|
||||
#include "initrd_fs.h"
|
||||
#include "keyboard.h"
|
||||
#include "framebuffer.h"
|
||||
|
||||
/* 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++;
|
||||
}
|
||||
}
|
||||
@@ -28,37 +55,84 @@ 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 VGA: write directly to the text buffer at 0xB8000 for boot
|
||||
* progress that is visible even without the VGA driver initialized.
|
||||
* Attribute 0x1F = white on blue. */
|
||||
volatile uint16_t *early_vga = (volatile uint16_t *)0xB8000;
|
||||
int early_pos = 0;
|
||||
|
||||
/* Helper macro: write a short string to early VGA */
|
||||
#define EARLY_PRINT(s) do { \
|
||||
const char *_p = (s); \
|
||||
while (*_p) { \
|
||||
early_vga[early_pos++] = (uint16_t)(unsigned char)*_p | (0x1F << 8); \
|
||||
_p++; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Clear screen to blue */
|
||||
for (int i = 0; i < 80 * 25; i++)
|
||||
early_vga[i] = (uint16_t)' ' | (0x1F << 8);
|
||||
|
||||
EARLY_PRINT("ClaudeOS early boot");
|
||||
early_pos = 80; /* Move to line 2 */
|
||||
|
||||
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
|
||||
EARLY_PRINT("ERROR: Bad magic=0x");
|
||||
/* Print magic in hex */
|
||||
const char *hex = "0123456789ABCDEF";
|
||||
for (int i = 28; i >= 0; i -= 4)
|
||||
early_vga[early_pos++] = (uint16_t)(unsigned char)hex[(magic >> i) & 0xF] | (0x4F << 8);
|
||||
early_pos = 80 * 3;
|
||||
EARLY_PRINT("Expected 0x36D76289 (Multiboot2)");
|
||||
early_pos = 80 * 4;
|
||||
EARLY_PRINT("Got MB1 magic? Checking grub.cfg...");
|
||||
|
||||
offset_print("Invalid magic number: ");
|
||||
print_hex(magic);
|
||||
return;
|
||||
|
||||
/* Hang with interrupts disabled so user can see the message */
|
||||
for (;;) __asm__ volatile("hlt");
|
||||
}
|
||||
|
||||
EARLY_PRINT("Magic OK ");
|
||||
offset_print("Booting...\n");
|
||||
|
||||
init_gdt();
|
||||
EARLY_PRINT("GDT ");
|
||||
offset_print("GDT initialized\n");
|
||||
|
||||
init_idt();
|
||||
EARLY_PRINT("IDT ");
|
||||
offset_print("IDT initialized\n");
|
||||
|
||||
init_pic();
|
||||
/* Unmask timer IRQ (IRQ0) explicitly */
|
||||
pic_clear_mask(0);
|
||||
EARLY_PRINT("PIC ");
|
||||
offset_print("PIC initialized\n");
|
||||
|
||||
init_pmm(addr);
|
||||
EARLY_PRINT("PMM ");
|
||||
offset_print("PMM initialized\n");
|
||||
|
||||
/* Scan Multiboot2 tags for the initrd module */
|
||||
/* Scan Multiboot2 tags for the initrd module and framebuffer info */
|
||||
uint32_t initrd_start = 0, initrd_end = 0;
|
||||
memset(&fb_info, 0, sizeof(fb_info));
|
||||
{
|
||||
struct multiboot_tag *tag;
|
||||
for (tag = (struct multiboot_tag *)(addr + 8);
|
||||
@@ -72,14 +146,59 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
||||
print_hex(initrd_start);
|
||||
offset_print(" to ");
|
||||
print_hex(initrd_end);
|
||||
break; /* Use first module */
|
||||
}
|
||||
if (tag->type == MULTIBOOT_TAG_TYPE_FRAMEBUFFER) {
|
||||
struct multiboot_tag_framebuffer *fbt =
|
||||
(struct multiboot_tag_framebuffer *)tag;
|
||||
fb_info.addr = (uint32_t)fbt->common.framebuffer_addr;
|
||||
fb_info.pitch = fbt->common.framebuffer_pitch;
|
||||
fb_info.width = fbt->common.framebuffer_width;
|
||||
fb_info.height = fbt->common.framebuffer_height;
|
||||
fb_info.bpp = fbt->common.framebuffer_bpp;
|
||||
fb_info.type = fbt->common.framebuffer_type;
|
||||
|
||||
if (fb_info.type == FB_TYPE_RGB) {
|
||||
fb_info.red_pos = fbt->framebuffer_red_field_position;
|
||||
fb_info.red_size = fbt->framebuffer_red_mask_size;
|
||||
fb_info.green_pos = fbt->framebuffer_green_field_position;
|
||||
fb_info.green_size = fbt->framebuffer_green_mask_size;
|
||||
fb_info.blue_pos = fbt->framebuffer_blue_field_position;
|
||||
fb_info.blue_size = fbt->framebuffer_blue_mask_size;
|
||||
}
|
||||
|
||||
offset_print("Framebuffer: type=");
|
||||
print_hex(fb_info.type);
|
||||
offset_print(" addr=");
|
||||
print_hex(fb_info.addr);
|
||||
offset_print(" ");
|
||||
print_hex(fb_info.width);
|
||||
offset_print(" x ");
|
||||
print_hex(fb_info.height);
|
||||
offset_print(" bpp=");
|
||||
print_hex(fb_info.bpp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init_paging();
|
||||
EARLY_PRINT("PAGING ");
|
||||
offset_print("Paging initialized\n");
|
||||
|
||||
/* If GRUB provided a graphical framebuffer, identity-map it so
|
||||
* the VGA driver can write pixels to it after paging is enabled. */
|
||||
if (fb_info.addr != 0 && fb_info.type == FB_TYPE_RGB) {
|
||||
uint32_t fb_size = fb_info.pitch * fb_info.height;
|
||||
uint32_t fb_base = fb_info.addr & ~0xFFFu; /* page-align down */
|
||||
uint32_t fb_end = (fb_info.addr + fb_size + 0xFFF) & ~0xFFFu;
|
||||
offset_print(" Mapping framebuffer ");
|
||||
print_hex(fb_base);
|
||||
offset_print(" to ");
|
||||
print_hex(fb_end);
|
||||
for (uint32_t pa = fb_base; pa < fb_end; pa += 4096) {
|
||||
paging_map_page(pa, pa, PAGE_PRESENT | PAGE_WRITE | PAGE_WRITETHROUGH);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test paging: allocate a page and write to it */
|
||||
void *test_page = paging_alloc_page();
|
||||
if (test_page) {
|
||||
@@ -93,21 +212,25 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
||||
}
|
||||
|
||||
init_kmalloc();
|
||||
EARLY_PRINT("HEAP ");
|
||||
offset_print("Memory allocator initialized\n");
|
||||
|
||||
/* Initialize CPIO ramdisk if module was loaded */
|
||||
if (initrd_start != 0) {
|
||||
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
|
||||
EARLY_PRINT("CPIO ");
|
||||
offset_print("CPIO ramdisk initialized\n");
|
||||
} else {
|
||||
offset_print("No initrd module found\n");
|
||||
}
|
||||
|
||||
init_vfs();
|
||||
EARLY_PRINT("VFS ");
|
||||
offset_print("VFS initialized\n");
|
||||
|
||||
if (initrd_start != 0) {
|
||||
init_initrd_fs();
|
||||
EARLY_PRINT("INITRD ");
|
||||
offset_print("Initrd filesystem mounted\n");
|
||||
|
||||
/* Test VFS: read a file from the initrd */
|
||||
@@ -125,17 +248,42 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
||||
}
|
||||
|
||||
init_tss();
|
||||
EARLY_PRINT("TSS ");
|
||||
offset_print("TSS initialized\n");
|
||||
|
||||
init_syscalls();
|
||||
EARLY_PRINT("SYSCALL ");
|
||||
offset_print("Syscalls initialized\n");
|
||||
|
||||
keyboard_init();
|
||||
EARLY_PRINT("KBD ");
|
||||
offset_print("Keyboard initialized\n");
|
||||
|
||||
init_process();
|
||||
EARLY_PRINT("PROC ");
|
||||
offset_print("Process subsystem initialized\n");
|
||||
|
||||
/* If the early VGA canary at 0xB8000 was visible, the display is
|
||||
* definitely in text mode, regardless of what the GRUB framebuffer
|
||||
* tag says. Force text mode so vga_init doesn't try to use a
|
||||
* pixel framebuffer that isn't actually being displayed. */
|
||||
if (fb_info.type == FB_TYPE_RGB) {
|
||||
offset_print(" Overriding fb type from RGB to EGA_TEXT (early VGA visible)\n");
|
||||
fb_info.type = FB_TYPE_EGA_TEXT;
|
||||
fb_info.addr = 0x000B8000;
|
||||
fb_info.width = 80;
|
||||
fb_info.height = 25;
|
||||
fb_info.bpp = 16;
|
||||
fb_info.pitch = 80 * 2;
|
||||
}
|
||||
|
||||
init_drivers();
|
||||
EARLY_PRINT("DRV ");
|
||||
offset_print("Drivers initialized\n");
|
||||
|
||||
/* At this point the VGA driver has been initialized and taken over
|
||||
* the display. The early VGA text is no longer visible. */
|
||||
|
||||
/* Show memory statistics and boot progress on VGA */
|
||||
vga_show_mem_stats();
|
||||
vga_puts("Boot complete.\n\n");
|
||||
|
||||
198
src/keyboard.c
Normal file
198
src/keyboard.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @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;
|
||||
|
||||
offset_print(" KEYBOARD: flushing controller...\n");
|
||||
|
||||
/* Flush any pending data from the keyboard controller.
|
||||
* Use a timeout to avoid hanging if the controller keeps reporting data
|
||||
* (some emulators/VMs behave differently). */
|
||||
int flush_count = 0;
|
||||
while ((inb(0x64) & 0x01) && flush_count < 1024) {
|
||||
inb(KB_DATA_PORT);
|
||||
flush_count++;
|
||||
}
|
||||
|
||||
offset_print(" KEYBOARD: flushed ");
|
||||
print_hex((uint32_t)flush_count);
|
||||
offset_print(" KEYBOARD: bytes, unmasking IRQ1...\n");
|
||||
|
||||
/* 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 */
|
||||
154
src/syscall.c
154
src/syscall.c
@@ -12,7 +12,13 @@
|
||||
#include "env.h"
|
||||
#include "port_io.h"
|
||||
#include "vga.h"
|
||||
#include "vfs.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 +66,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 +104,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 +152,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,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] = {
|
||||
@@ -184,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;
|
||||
|
||||
411
src/vga.c
411
src/vga.c
@@ -1,10 +1,14 @@
|
||||
/**
|
||||
* @file vga.c
|
||||
* @brief VGA text-mode driver implementation.
|
||||
* @brief Display driver supporting both VGA text mode and graphical framebuffer.
|
||||
*
|
||||
* Drives the standard VGA text-mode framebuffer at 0xB8000. The buffer
|
||||
* is an array of 80×25 16-bit values, where the low byte is the ASCII
|
||||
* character and the high byte encodes foreground and background color.
|
||||
* Supports two modes depending on what GRUB provides:
|
||||
* - EGA text mode: writes character+attribute pairs to the text buffer
|
||||
* - Graphical (RGB) framebuffer: renders an embedded 8x16 bitmap font
|
||||
* to the pixel framebuffer provided by GRUB
|
||||
*
|
||||
* The mode is detected at init time from the global fb_info structure,
|
||||
* which kernel_main populates from the multiboot2 framebuffer tag.
|
||||
*
|
||||
* This driver registers itself via the REGISTER_DRIVER macro and is
|
||||
* automatically discovered during boot.
|
||||
@@ -14,122 +18,295 @@
|
||||
#include "driver.h"
|
||||
#include "port_io.h"
|
||||
#include "pmm.h"
|
||||
#include "framebuffer.h"
|
||||
#include "font8x16.h"
|
||||
#include <string.h>
|
||||
|
||||
/** Base address of the VGA text-mode framebuffer. */
|
||||
#define VGA_BUFFER 0xB8000
|
||||
/* Debug helpers defined in kernel.c */
|
||||
extern void offset_print(const char *str);
|
||||
extern void print_hex(uint32_t val);
|
||||
|
||||
/** Pointer to the VGA framebuffer, treated as an array of uint16_t. */
|
||||
static uint16_t *vga_buffer = (uint16_t *)VGA_BUFFER;
|
||||
/* ================================================================
|
||||
* Common state
|
||||
* ================================================================ */
|
||||
|
||||
/** Current cursor row (0-based). */
|
||||
static uint8_t cursor_row = 0;
|
||||
/** Current text cursor position. */
|
||||
static uint32_t cursor_row = 0;
|
||||
static uint32_t cursor_col = 0;
|
||||
|
||||
/** Current cursor column (0-based). */
|
||||
static uint8_t cursor_col = 0;
|
||||
/** Columns and rows of the text grid. */
|
||||
static uint32_t text_cols = 80;
|
||||
static uint32_t text_rows = 25;
|
||||
|
||||
/** Current text attribute byte (foreground | background << 4). */
|
||||
static uint8_t text_attr = 0;
|
||||
/** Current color attribute (foreground | background << 4). */
|
||||
static uint8_t text_attr = 0x07;
|
||||
|
||||
/** Display mode: 0 = text, 1 = pixel. */
|
||||
static int display_mode = 0;
|
||||
|
||||
/* ================================================================
|
||||
* Text mode (EGA) internals
|
||||
* ================================================================ */
|
||||
|
||||
/** VGA text-mode framebuffer default base. */
|
||||
#define VGA_TEXT_BUFFER 0xB8000
|
||||
static uint16_t *text_buffer = (uint16_t *)VGA_TEXT_BUFFER;
|
||||
|
||||
/**
|
||||
* Create a VGA entry (character + attribute) for the framebuffer.
|
||||
*
|
||||
* @param c ASCII character.
|
||||
* @param attr Color attribute byte.
|
||||
* @return 16-bit VGA entry.
|
||||
*/
|
||||
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
||||
return (uint16_t)c | ((uint16_t)attr << 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a color attribute byte from foreground and background colors.
|
||||
*
|
||||
* @param fg Foreground color (0–15).
|
||||
* @param bg Background color (0–15).
|
||||
* @return Attribute byte.
|
||||
*/
|
||||
static inline uint8_t vga_color(vga_color_t fg, vga_color_t bg) {
|
||||
return (uint8_t)fg | ((uint8_t)bg << 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the hardware cursor position via VGA I/O ports.
|
||||
*/
|
||||
static void update_cursor(void) {
|
||||
uint16_t pos = cursor_row * VGA_WIDTH + cursor_col;
|
||||
|
||||
static void text_update_cursor(void) {
|
||||
uint16_t pos = (uint16_t)(cursor_row * text_cols + cursor_col);
|
||||
outb(0x3D4, 0x0F);
|
||||
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
||||
outb(0x3D4, 0x0E);
|
||||
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the screen up by one line.
|
||||
*
|
||||
* The top line is discarded, all other lines move up, and the bottom
|
||||
* line is filled with spaces.
|
||||
*/
|
||||
static void scroll(void) {
|
||||
/* Move all lines up by one */
|
||||
for (int i = 0; i < (VGA_HEIGHT - 1) * VGA_WIDTH; i++) {
|
||||
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
|
||||
static void text_scroll(void) {
|
||||
for (uint32_t i = 0; i < (text_rows - 1) * text_cols; i++) {
|
||||
text_buffer[i] = text_buffer[i + text_cols];
|
||||
}
|
||||
|
||||
/* Clear the last line */
|
||||
uint16_t blank = vga_entry(' ', text_attr);
|
||||
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
|
||||
vga_buffer[i] = blank;
|
||||
for (uint32_t i = (text_rows - 1) * text_cols; i < text_rows * text_cols; i++) {
|
||||
text_buffer[i] = blank;
|
||||
}
|
||||
cursor_row = text_rows - 1;
|
||||
}
|
||||
|
||||
cursor_row = VGA_HEIGHT - 1;
|
||||
}
|
||||
|
||||
void vga_clear(void) {
|
||||
static void text_clear(void) {
|
||||
uint16_t blank = vga_entry(' ', text_attr);
|
||||
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
||||
vga_buffer[i] = blank;
|
||||
for (uint32_t i = 0; i < text_cols * text_rows; i++) {
|
||||
text_buffer[i] = blank;
|
||||
}
|
||||
cursor_row = 0;
|
||||
cursor_col = 0;
|
||||
update_cursor();
|
||||
text_update_cursor();
|
||||
}
|
||||
|
||||
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
||||
text_attr = vga_color(fg, bg);
|
||||
}
|
||||
|
||||
void vga_putchar(char c) {
|
||||
static void text_putchar(char c) {
|
||||
if (c == '\n') {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
} else if (c == '\r') {
|
||||
cursor_col = 0;
|
||||
} else if (c == '\t') {
|
||||
cursor_col = (cursor_col + 8) & ~7;
|
||||
if (cursor_col >= VGA_WIDTH) {
|
||||
cursor_col = (cursor_col + 8) & ~7u;
|
||||
if (cursor_col >= text_cols) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
} else if (c == '\b') {
|
||||
if (cursor_col > 0) {
|
||||
cursor_col--;
|
||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(' ', text_attr);
|
||||
text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(' ', text_attr);
|
||||
}
|
||||
} else {
|
||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(c, text_attr);
|
||||
text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(c, text_attr);
|
||||
cursor_col++;
|
||||
if (cursor_col >= VGA_WIDTH) {
|
||||
if (cursor_col >= text_cols) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor_row >= VGA_HEIGHT) {
|
||||
scroll();
|
||||
if (cursor_row >= text_rows) {
|
||||
text_scroll();
|
||||
}
|
||||
text_update_cursor();
|
||||
}
|
||||
|
||||
update_cursor();
|
||||
/* ================================================================
|
||||
* Pixel mode (graphical framebuffer) internals
|
||||
* ================================================================ */
|
||||
|
||||
/** Pointer to the pixel framebuffer. */
|
||||
static uint8_t *pixel_fb = (uint8_t *)0;
|
||||
|
||||
/** Framebuffer parameters. */
|
||||
static uint32_t fb_pitch = 0;
|
||||
static uint32_t fb_width = 0;
|
||||
static uint32_t fb_height = 0;
|
||||
static uint32_t fb_bpp = 0;
|
||||
|
||||
/** RGB field info. */
|
||||
static uint8_t fb_red_pos = 16, fb_red_size = 8;
|
||||
static uint8_t fb_green_pos = 8, fb_green_size = 8;
|
||||
static uint8_t fb_blue_pos = 0, fb_blue_size = 8;
|
||||
|
||||
/**
|
||||
* Pack an RGB color into the framebuffer's native pixel format.
|
||||
*/
|
||||
static inline uint32_t pack_color(uint8_t r, uint8_t g, uint8_t b) {
|
||||
(void)fb_red_size; (void)fb_green_size; (void)fb_blue_size;
|
||||
return ((uint32_t)r << fb_red_pos) |
|
||||
((uint32_t)g << fb_green_pos) |
|
||||
((uint32_t)b << fb_blue_pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single pixel in the framebuffer.
|
||||
*/
|
||||
static inline void pixel_set(uint32_t x, uint32_t y, uint32_t color) {
|
||||
if (x >= fb_width || y >= fb_height) return;
|
||||
uint32_t offset = y * fb_pitch + x * (fb_bpp / 8);
|
||||
uint32_t bytes = fb_bpp / 8;
|
||||
|
||||
if (bytes == 4) {
|
||||
*(volatile uint32_t *)(pixel_fb + offset) = color;
|
||||
} else if (bytes == 3) {
|
||||
pixel_fb[offset] = (uint8_t)(color & 0xFF);
|
||||
pixel_fb[offset + 1] = (uint8_t)((color >> 8) & 0xFF);
|
||||
pixel_fb[offset + 2] = (uint8_t)((color >> 16) & 0xFF);
|
||||
} else if (bytes == 2) {
|
||||
*(volatile uint16_t *)(pixel_fb + offset) = (uint16_t)color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* VGA color index to 24-bit RGB mapping.
|
||||
*/
|
||||
static const uint32_t vga_palette[16] = {
|
||||
0x000000, /* 0 black */
|
||||
0x0000AA, /* 1 blue */
|
||||
0x00AA00, /* 2 green */
|
||||
0x00AAAA, /* 3 cyan */
|
||||
0xAA0000, /* 4 red */
|
||||
0xAA00AA, /* 5 magenta */
|
||||
0xAA5500, /* 6 brown */
|
||||
0xAAAAAA, /* 7 light grey */
|
||||
0x555555, /* 8 dark grey */
|
||||
0x5555FF, /* 9 light blue */
|
||||
0x55FF55, /* 10 light green */
|
||||
0x55FFFF, /* 11 light cyan */
|
||||
0xFF5555, /* 12 light red */
|
||||
0xFF55FF, /* 13 light magenta */
|
||||
0xFFFF55, /* 14 yellow */
|
||||
0xFFFFFF, /* 15 white */
|
||||
};
|
||||
|
||||
/**
|
||||
* Get packed foreground/background colors from text_attr.
|
||||
*/
|
||||
static void attr_to_colors(uint32_t *fg_out, uint32_t *bg_out) {
|
||||
uint8_t fg_idx = text_attr & 0x0F;
|
||||
uint8_t bg_idx = (text_attr >> 4) & 0x0F;
|
||||
uint32_t fg_rgb = vga_palette[fg_idx];
|
||||
uint32_t bg_rgb = vga_palette[bg_idx];
|
||||
*fg_out = pack_color((fg_rgb >> 16) & 0xFF, (fg_rgb >> 8) & 0xFF, fg_rgb & 0xFF);
|
||||
*bg_out = pack_color((bg_rgb >> 16) & 0xFF, (bg_rgb >> 8) & 0xFF, bg_rgb & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single glyph at character grid position (col, row).
|
||||
*/
|
||||
static void pixel_render_char(uint32_t col, uint32_t row, char c) {
|
||||
uint32_t fg, bg;
|
||||
attr_to_colors(&fg, &bg);
|
||||
|
||||
const uint8_t *glyph;
|
||||
if (c >= FONT_FIRST && c <= FONT_LAST) {
|
||||
glyph = font8x16_data[c - FONT_FIRST];
|
||||
} else {
|
||||
glyph = 0; /* NULL = solid block for unknown chars */
|
||||
}
|
||||
|
||||
uint32_t px = col * FONT_WIDTH;
|
||||
uint32_t py = row * FONT_HEIGHT;
|
||||
|
||||
for (uint32_t y = 0; y < FONT_HEIGHT; y++) {
|
||||
uint8_t bits = glyph ? glyph[y] : 0xFF;
|
||||
for (uint32_t x = 0; x < FONT_WIDTH; x++) {
|
||||
uint32_t color = (bits & (0x80 >> x)) ? fg : bg;
|
||||
pixel_set(px + x, py + y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the pixel framebuffer up by one text row (FONT_HEIGHT pixels).
|
||||
*/
|
||||
static void pixel_scroll(void) {
|
||||
uint32_t row_bytes = FONT_HEIGHT * fb_pitch;
|
||||
uint32_t total_text_bytes = text_rows * row_bytes;
|
||||
|
||||
/* Move all rows up by one */
|
||||
memcpy(pixel_fb, pixel_fb + row_bytes, total_text_bytes - row_bytes);
|
||||
|
||||
/* Clear the last text row */
|
||||
uint32_t dummy, bg;
|
||||
attr_to_colors(&dummy, &bg);
|
||||
uint32_t last_row_y = (text_rows - 1) * FONT_HEIGHT;
|
||||
for (uint32_t y = last_row_y; y < last_row_y + FONT_HEIGHT; y++) {
|
||||
for (uint32_t x = 0; x < text_cols * FONT_WIDTH; x++) {
|
||||
pixel_set(x, y, bg);
|
||||
}
|
||||
}
|
||||
cursor_row = text_rows - 1;
|
||||
}
|
||||
|
||||
static void pixel_clear(void) {
|
||||
uint32_t dummy, bg;
|
||||
attr_to_colors(&dummy, &bg);
|
||||
for (uint32_t y = 0; y < fb_height; y++) {
|
||||
for (uint32_t x = 0; x < fb_width; x++) {
|
||||
pixel_set(x, y, bg);
|
||||
}
|
||||
}
|
||||
cursor_row = 0;
|
||||
cursor_col = 0;
|
||||
}
|
||||
|
||||
static void pixel_putchar(char c) {
|
||||
if (c == '\n') {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
} else if (c == '\r') {
|
||||
cursor_col = 0;
|
||||
} else if (c == '\t') {
|
||||
cursor_col = (cursor_col + 8) & ~7u;
|
||||
if (cursor_col >= text_cols) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
} else if (c == '\b') {
|
||||
if (cursor_col > 0) {
|
||||
cursor_col--;
|
||||
pixel_render_char(cursor_col, cursor_row, ' ');
|
||||
}
|
||||
} else {
|
||||
pixel_render_char(cursor_col, cursor_row, c);
|
||||
cursor_col++;
|
||||
if (cursor_col >= text_cols) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
}
|
||||
if (cursor_row >= text_rows) {
|
||||
pixel_scroll();
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Public interface
|
||||
* ================================================================ */
|
||||
|
||||
void vga_clear(void) {
|
||||
if (display_mode == 0)
|
||||
text_clear();
|
||||
else
|
||||
pixel_clear();
|
||||
}
|
||||
|
||||
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
||||
text_attr = (uint8_t)fg | ((uint8_t)bg << 4);
|
||||
}
|
||||
|
||||
void vga_putchar(char c) {
|
||||
if (display_mode == 0)
|
||||
text_putchar(c);
|
||||
else
|
||||
pixel_putchar(c);
|
||||
}
|
||||
|
||||
void vga_puts(const char *str) {
|
||||
@@ -153,26 +330,24 @@ void vga_put_dec(uint32_t val) {
|
||||
vga_putchar('0');
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[12];
|
||||
int pos = 0;
|
||||
while (val > 0) {
|
||||
buf[pos++] = '0' + (val % 10);
|
||||
val /= 10;
|
||||
}
|
||||
/* Print in reverse */
|
||||
while (pos > 0) {
|
||||
vga_putchar(buf[--pos]);
|
||||
}
|
||||
}
|
||||
|
||||
void vga_show_mem_stats(void) {
|
||||
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total including lower */
|
||||
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 (");
|
||||
@@ -194,28 +369,88 @@ 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);
|
||||
}
|
||||
|
||||
/* --- Driver registration --- */
|
||||
/* ================================================================
|
||||
* Driver registration
|
||||
* ================================================================ */
|
||||
|
||||
/**
|
||||
* VGA probe: always succeeds since VGA text mode is always available
|
||||
* on the target platform (i386).
|
||||
*/
|
||||
static driver_probe_result_t vga_probe(void) {
|
||||
return DRIVER_PROBE_OK;
|
||||
}
|
||||
|
||||
int vga_init(void) {
|
||||
text_attr = vga_color(VGA_LIGHT_GREY, VGA_BLACK);
|
||||
text_attr = (uint8_t)VGA_LIGHT_GREY | ((uint8_t)VGA_BLACK << 4);
|
||||
|
||||
if (fb_info.type == FB_TYPE_EGA_TEXT || fb_info.addr == 0) {
|
||||
/* Text mode (or no framebuffer tag — assume legacy text mode) */
|
||||
display_mode = 0;
|
||||
text_cols = 80;
|
||||
text_rows = 25;
|
||||
|
||||
if (fb_info.addr != 0) {
|
||||
text_buffer = (uint16_t *)(uint32_t)fb_info.addr;
|
||||
text_cols = fb_info.width;
|
||||
text_rows = fb_info.height;
|
||||
}
|
||||
|
||||
offset_print(" VGA: text mode ");
|
||||
print_hex(text_cols);
|
||||
offset_print(" VGA: x ");
|
||||
print_hex(text_rows);
|
||||
} else if (fb_info.type == FB_TYPE_RGB) {
|
||||
/* Graphical framebuffer — render with bitmap font */
|
||||
display_mode = 1;
|
||||
pixel_fb = (uint8_t *)(uint32_t)fb_info.addr;
|
||||
fb_pitch = fb_info.pitch;
|
||||
fb_width = fb_info.width;
|
||||
fb_height = fb_info.height;
|
||||
fb_bpp = fb_info.bpp;
|
||||
|
||||
fb_red_pos = fb_info.red_pos;
|
||||
fb_red_size = fb_info.red_size;
|
||||
fb_green_pos = fb_info.green_pos;
|
||||
fb_green_size = fb_info.green_size;
|
||||
fb_blue_pos = fb_info.blue_pos;
|
||||
fb_blue_size = fb_info.blue_size;
|
||||
|
||||
/* Calculate text grid from pixel dimensions */
|
||||
text_cols = fb_width / FONT_WIDTH;
|
||||
text_rows = fb_height / FONT_HEIGHT;
|
||||
if (text_cols == 0) text_cols = 1;
|
||||
if (text_rows == 0) text_rows = 1;
|
||||
|
||||
offset_print(" VGA: pixel mode ");
|
||||
print_hex(fb_width);
|
||||
offset_print(" VGA: x ");
|
||||
print_hex(fb_height);
|
||||
offset_print(" VGA: bpp=");
|
||||
print_hex(fb_bpp);
|
||||
offset_print(" VGA: text grid ");
|
||||
print_hex(text_cols);
|
||||
offset_print(" VGA: x ");
|
||||
print_hex(text_rows);
|
||||
offset_print(" VGA: addr=");
|
||||
print_hex((uint32_t)pixel_fb);
|
||||
} else {
|
||||
/* Indexed or unknown — fall back to text mode */
|
||||
display_mode = 0;
|
||||
text_cols = 80;
|
||||
text_rows = 25;
|
||||
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