Compare commits
11 Commits
e9b66cd60e
...
attempt-2a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1bf69ce0d | ||
|
|
d064e67a8f | ||
|
|
c07ec030a7 | ||
|
|
c12d49dea0 | ||
|
|
3512e937ae | ||
|
|
c3c01049bf | ||
|
|
d3ab5a5b55 | ||
|
|
e3d011da2f | ||
|
|
993cf05712 | ||
|
|
000d53e2f3 | ||
|
|
c25ba1fccd |
@@ -38,7 +38,7 @@ add_custom_command(
|
|||||||
add_custom_target(initrd DEPENDS ${INITRD_FILE})
|
add_custom_target(initrd DEPENDS ${INITRD_FILE})
|
||||||
|
|
||||||
# Create grub.cfg for ISO - includes module2 for the initrd
|
# 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
|
# ISO Generation
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -56,12 +56,12 @@ 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] 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 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.
|
- [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.
|
- [x] 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.
|
- [x] 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.
|
- [x] 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.
|
||||||
- [ ] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
|
- [x] Create an MBR driver. It should be able to automatically detect when new hard drives are detected, and automatically scan them for MBR partitions. Each MBR partition found should be listed as `hddNmbrY`, where Y is a number determined by the devicefs subsystem.
|
||||||
- [ ] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
|
- [x] Create a `sysfs` vfs driver. It should allow drivers to expose text/config files to the VFS. Each driver can request a namespace in the sysfs. E.g.: the IDE driver could request `ide`. During this registration, the drive must provide a struct containing function callbacks. The callbacks must contain the function `list`, `read`, and `write`. These are executed when the user lists a directory, reads a file, or writes a file. It is expected that the contents of these files are extremely small and can simply be stored on the stack. It should be very easy for a driver to expose new information.
|
||||||
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device.
|
- [ ] Create a FAT32 driver. It should allow reading and writing to and from a block device.
|
||||||
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
|
- [ ] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount.
|
||||||
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
|
- [ ] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`.
|
||||||
|
|||||||
41
apps/cat/cat.c
Normal file
41
apps/cat/cat.c
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @file cat.c
|
||||||
|
* @brief Display file contents.
|
||||||
|
*
|
||||||
|
* Reads a file specified as the first argument (from shell)
|
||||||
|
* and prints its contents to stdout. If no argument is given,
|
||||||
|
* prints usage.
|
||||||
|
*
|
||||||
|
* Usage: cat <filepath>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
/* Get the file path from the ARGS environment variable.
|
||||||
|
* The shell sets ARGS to the arguments after the command name. */
|
||||||
|
char path[128];
|
||||||
|
if (getenv("ARG1", path, sizeof(path)) < 0) {
|
||||||
|
puts("Usage: cat <file>\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open the file */
|
||||||
|
int32_t fd = open(path, 0);
|
||||||
|
if (fd < 0) {
|
||||||
|
puts("cat: ");
|
||||||
|
puts(path);
|
||||||
|
puts(": open failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read and print in chunks */
|
||||||
|
char buf[256];
|
||||||
|
int32_t n;
|
||||||
|
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
||||||
|
write(1, buf, (uint32_t)n);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
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
|
||||||
180
apps/libc/syscalls.h
Normal file
180
apps/libc/syscalls.h
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
#define SYS_OPEN 11
|
||||||
|
#define SYS_CLOSE 12
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file by path.
|
||||||
|
* @param path File path.
|
||||||
|
* @param flags Open flags (currently unused, pass 0).
|
||||||
|
* @return File descriptor (>= 3) on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
static inline int32_t open(const char *path, uint32_t flags) {
|
||||||
|
return syscall2(SYS_OPEN, (uint32_t)path, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a file descriptor.
|
||||||
|
* @param fd File descriptor.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
static inline int32_t close(int32_t fd) {
|
||||||
|
return syscall1(SYS_CLOSE, (uint32_t)fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
38
apps/ls/ls.c
Normal file
38
apps/ls/ls.c
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @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) {
|
||||||
|
/* Check for an explicit path argument first */
|
||||||
|
char cwd[128];
|
||||||
|
if (getenv("ARG1", cwd, sizeof(cwd)) < 0 || cwd[0] == '\0') {
|
||||||
|
/* No argument; use the current working directory */
|
||||||
|
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
187
apps/sh/sh.c
Normal file
187
apps/sh/sh.c
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* @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, const char *arg) {
|
||||||
|
int32_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
puts("sh: fork failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
/* Child: set ARG1 if there's an argument */
|
||||||
|
if (arg && *arg) {
|
||||||
|
setenv("ARG1", arg);
|
||||||
|
} else {
|
||||||
|
setenv("ARG1", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)"
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ add_executable(kernel
|
|||||||
cpio.c
|
cpio.c
|
||||||
vfs.c
|
vfs.c
|
||||||
initrd_fs.c
|
initrd_fs.c
|
||||||
|
devicefs.c
|
||||||
|
sysfs.c
|
||||||
|
ide.c
|
||||||
|
mbr.c
|
||||||
env.c
|
env.c
|
||||||
|
keyboard.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ multiboot_header:
|
|||||||
/* checksum */
|
/* checksum */
|
||||||
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
|
.long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
|
||||||
|
|
||||||
/* Tags here */
|
|
||||||
|
|
||||||
/* End tag */
|
/* End tag */
|
||||||
|
.align 8
|
||||||
.short MULTIBOOT_HEADER_TAG_END
|
.short MULTIBOOT_HEADER_TAG_END
|
||||||
.short 0
|
.short 0
|
||||||
.long 8
|
.long 8
|
||||||
|
|||||||
350
src/devicefs.c
Normal file
350
src/devicefs.c
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
/**
|
||||||
|
* @file devicefs.c
|
||||||
|
* @brief Device filesystem (devicefs) implementation.
|
||||||
|
*
|
||||||
|
* Provides a VFS driver mounted at /dev that exposes block and character
|
||||||
|
* devices. Kernel drivers register devices via devicefs_register_block()
|
||||||
|
* or devicefs_register_char(), and the devicefs assigns sequential numbers
|
||||||
|
* per device class (e.g., hdd1, hdd2, cd1).
|
||||||
|
*
|
||||||
|
* The VFS interface supports:
|
||||||
|
* - readdir: lists all registered devices
|
||||||
|
* - finddir: looks up a device by name
|
||||||
|
* - read/write: delegates to the device's block or char operations
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "devicefs.h"
|
||||||
|
#include "vfs.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);
|
||||||
|
|
||||||
|
/** Device table. */
|
||||||
|
static devicefs_device_t devices[DEVICEFS_MAX_DEVICES];
|
||||||
|
|
||||||
|
/** Number of active devices. */
|
||||||
|
static uint32_t device_count = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a free slot in the device table.
|
||||||
|
* @return Index of free slot, or -1 if full.
|
||||||
|
*/
|
||||||
|
static int find_free_slot(void) {
|
||||||
|
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
|
||||||
|
if (!devices[i].active) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* VFS operations for /dev
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a device file.
|
||||||
|
*
|
||||||
|
* For block devices, translates byte offset/size to sector reads.
|
||||||
|
* For character devices, delegates directly to the char read op.
|
||||||
|
*/
|
||||||
|
static int32_t devfs_read(vfs_node_t *node, uint32_t offset,
|
||||||
|
uint32_t size, void *buf) {
|
||||||
|
if (!node || !node->fs_data) return -1;
|
||||||
|
|
||||||
|
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
|
||||||
|
|
||||||
|
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
|
||||||
|
uint32_t sec_size = 512;
|
||||||
|
if (dev->block_ops->sector_size) {
|
||||||
|
sec_size = dev->block_ops->sector_size(dev->dev_data);
|
||||||
|
}
|
||||||
|
if (sec_size == 0) return -1;
|
||||||
|
|
||||||
|
uint32_t start_lba = offset / sec_size;
|
||||||
|
uint32_t end_byte = offset + size;
|
||||||
|
uint32_t end_lba = (end_byte + sec_size - 1) / sec_size;
|
||||||
|
uint32_t num_sectors = end_lba - start_lba;
|
||||||
|
|
||||||
|
/* For simplicity, require aligned reads for now */
|
||||||
|
if (offset % sec_size != 0 || size % sec_size != 0) {
|
||||||
|
/* Unaligned read: read full sectors, copy partial */
|
||||||
|
/* TODO: implement unaligned block reads with temp buffer */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->block_ops->read_sectors) {
|
||||||
|
int ret = dev->block_ops->read_sectors(dev->dev_data,
|
||||||
|
start_lba,
|
||||||
|
num_sectors,
|
||||||
|
buf);
|
||||||
|
return (ret == 0) ? (int32_t)size : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
|
||||||
|
if (dev->char_ops->read) {
|
||||||
|
return dev->char_ops->read(dev->dev_data, size, buf);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to a device file.
|
||||||
|
*
|
||||||
|
* For block devices, translates byte offset/size to sector writes.
|
||||||
|
* For character devices, delegates directly to the char write op.
|
||||||
|
*/
|
||||||
|
static int32_t devfs_write(vfs_node_t *node, uint32_t offset,
|
||||||
|
uint32_t size, const void *buf) {
|
||||||
|
if (!node || !node->fs_data) return -1;
|
||||||
|
|
||||||
|
devicefs_device_t *dev = (devicefs_device_t *)node->fs_data;
|
||||||
|
|
||||||
|
if (dev->type == DEVICEFS_BLOCK && dev->block_ops) {
|
||||||
|
uint32_t sec_size = 512;
|
||||||
|
if (dev->block_ops->sector_size) {
|
||||||
|
sec_size = dev->block_ops->sector_size(dev->dev_data);
|
||||||
|
}
|
||||||
|
if (sec_size == 0) return -1;
|
||||||
|
|
||||||
|
if (offset % sec_size != 0 || size % sec_size != 0) {
|
||||||
|
return -1; /* Require aligned writes */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t start_lba = offset / sec_size;
|
||||||
|
uint32_t num_sectors = size / sec_size;
|
||||||
|
|
||||||
|
if (dev->block_ops->write_sectors) {
|
||||||
|
int ret = dev->block_ops->write_sectors(dev->dev_data,
|
||||||
|
start_lba,
|
||||||
|
num_sectors,
|
||||||
|
buf);
|
||||||
|
return (ret == 0) ? (int32_t)size : -1;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev->type == DEVICEFS_CHAR && dev->char_ops) {
|
||||||
|
if (dev->char_ops->write) {
|
||||||
|
return dev->char_ops->write(dev->dev_data, size, buf);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a directory entry from /dev.
|
||||||
|
* Lists all registered devices.
|
||||||
|
*/
|
||||||
|
static int devfs_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) {
|
||||||
|
(void)dir;
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
|
||||||
|
if (!devices[i].active) continue;
|
||||||
|
if (count == idx) {
|
||||||
|
memset(out, 0, sizeof(vfs_dirent_t));
|
||||||
|
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
|
||||||
|
out->inode = (uint32_t)i;
|
||||||
|
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
|
||||||
|
VFS_BLOCKDEV : VFS_CHARDEV;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return -1; /* No more entries */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a device by name within /dev.
|
||||||
|
*/
|
||||||
|
static int devfs_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) {
|
||||||
|
(void)dir;
|
||||||
|
|
||||||
|
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
|
||||||
|
if (!devices[i].active) continue;
|
||||||
|
if (strcmp(devices[i].name, name) == 0) {
|
||||||
|
memset(out, 0, sizeof(vfs_node_t));
|
||||||
|
strncpy(out->name, devices[i].name, VFS_MAX_NAME - 1);
|
||||||
|
out->type = (devices[i].type == DEVICEFS_BLOCK) ?
|
||||||
|
VFS_BLOCKDEV : VFS_CHARDEV;
|
||||||
|
out->inode = (uint32_t)i;
|
||||||
|
out->fs_data = &devices[i];
|
||||||
|
|
||||||
|
/* For block devices, compute size from sector count */
|
||||||
|
if (devices[i].type == DEVICEFS_BLOCK && devices[i].block_ops) {
|
||||||
|
uint32_t sec_size = 512;
|
||||||
|
uint32_t sec_count = 0;
|
||||||
|
if (devices[i].block_ops->sector_size) {
|
||||||
|
sec_size = devices[i].block_ops->sector_size(devices[i].dev_data);
|
||||||
|
}
|
||||||
|
if (devices[i].block_ops->sector_count) {
|
||||||
|
sec_count = devices[i].block_ops->sector_count(devices[i].dev_data);
|
||||||
|
}
|
||||||
|
out->size = sec_count * sec_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Filesystem operations for /dev. */
|
||||||
|
static vfs_fs_ops_t devfs_ops = {
|
||||||
|
.open = NULL,
|
||||||
|
.close = NULL,
|
||||||
|
.read = devfs_read,
|
||||||
|
.write = devfs_write,
|
||||||
|
.readdir = devfs_readdir,
|
||||||
|
.finddir = devfs_finddir,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public API
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
uint32_t devicefs_next_number(const char *class_name) {
|
||||||
|
uint32_t max_num = 0;
|
||||||
|
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
|
||||||
|
if (!devices[i].active) continue;
|
||||||
|
if (strcmp(devices[i].class_name, class_name) == 0) {
|
||||||
|
if (devices[i].number >= max_num) {
|
||||||
|
max_num = devices[i].number + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Numbers start at 1 */
|
||||||
|
return (max_num == 0) ? 1 : max_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a uint32 as a decimal string into buf.
|
||||||
|
* Returns pointer to the start of the number within buf.
|
||||||
|
*/
|
||||||
|
static char *uint_to_str(uint32_t val, char *buf, int buf_size) {
|
||||||
|
buf[buf_size - 1] = '\0';
|
||||||
|
int pos = buf_size - 2;
|
||||||
|
if (val == 0) {
|
||||||
|
buf[pos] = '0';
|
||||||
|
return &buf[pos];
|
||||||
|
}
|
||||||
|
while (val > 0 && pos >= 0) {
|
||||||
|
buf[pos--] = (char)('0' + (val % 10));
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
return &buf[pos + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *devicefs_register_block(const char *class_name,
|
||||||
|
devicefs_block_ops_t *ops,
|
||||||
|
void *dev_data) {
|
||||||
|
int slot = find_free_slot();
|
||||||
|
if (slot < 0) {
|
||||||
|
offset_print(" DEVICEFS: no free device slots\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *dev = &devices[slot];
|
||||||
|
memset(dev, 0, sizeof(devicefs_device_t));
|
||||||
|
|
||||||
|
/* Copy class name */
|
||||||
|
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
|
||||||
|
|
||||||
|
/* Assign sequential number */
|
||||||
|
dev->number = devicefs_next_number(class_name);
|
||||||
|
|
||||||
|
/* Build full device name: class_name + number */
|
||||||
|
char num_buf[12];
|
||||||
|
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
|
||||||
|
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
|
||||||
|
/* Append number string */
|
||||||
|
uint32_t nlen = strlen(dev->name);
|
||||||
|
uint32_t slen = strlen(num_str);
|
||||||
|
if (nlen + slen < DEVICEFS_MAX_DEV_NAME) {
|
||||||
|
memcpy(dev->name + nlen, num_str, slen + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->type = DEVICEFS_BLOCK;
|
||||||
|
dev->block_ops = ops;
|
||||||
|
dev->dev_data = dev_data;
|
||||||
|
dev->active = 1;
|
||||||
|
device_count++;
|
||||||
|
|
||||||
|
offset_print(" DEVICEFS: registered block device /dev/");
|
||||||
|
offset_print(dev->name);
|
||||||
|
offset_print("\n");
|
||||||
|
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *devicefs_register_char(const char *class_name,
|
||||||
|
devicefs_char_ops_t *ops,
|
||||||
|
void *dev_data) {
|
||||||
|
int slot = find_free_slot();
|
||||||
|
if (slot < 0) {
|
||||||
|
offset_print(" DEVICEFS: no free device slots\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *dev = &devices[slot];
|
||||||
|
memset(dev, 0, sizeof(devicefs_device_t));
|
||||||
|
|
||||||
|
strncpy(dev->class_name, class_name, DEVICEFS_MAX_CLASS_NAME - 1);
|
||||||
|
dev->number = devicefs_next_number(class_name);
|
||||||
|
|
||||||
|
char num_buf[12];
|
||||||
|
char *num_str = uint_to_str(dev->number, num_buf, sizeof(num_buf));
|
||||||
|
strncpy(dev->name, class_name, DEVICEFS_MAX_DEV_NAME - 12);
|
||||||
|
/* Append number string */
|
||||||
|
uint32_t nlen2 = strlen(dev->name);
|
||||||
|
uint32_t slen2 = strlen(num_str);
|
||||||
|
if (nlen2 + slen2 < DEVICEFS_MAX_DEV_NAME) {
|
||||||
|
memcpy(dev->name + nlen2, num_str, slen2 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->type = DEVICEFS_CHAR;
|
||||||
|
dev->char_ops = ops;
|
||||||
|
dev->dev_data = dev_data;
|
||||||
|
dev->active = 1;
|
||||||
|
device_count++;
|
||||||
|
|
||||||
|
offset_print(" DEVICEFS: registered char device /dev/");
|
||||||
|
offset_print(dev->name);
|
||||||
|
offset_print("\n");
|
||||||
|
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *devicefs_find(const char *name) {
|
||||||
|
for (int i = 0; i < DEVICEFS_MAX_DEVICES; i++) {
|
||||||
|
if (!devices[i].active) continue;
|
||||||
|
if (strcmp(devices[i].name, name) == 0) {
|
||||||
|
return &devices[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int init_devicefs(void) {
|
||||||
|
memset(devices, 0, sizeof(devices));
|
||||||
|
device_count = 0;
|
||||||
|
|
||||||
|
int ret = vfs_mount("/dev", &devfs_ops, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
offset_print(" DEVICEFS: failed to mount at /dev\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" DEVICEFS: mounted at /dev\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
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 */
|
||||||
616
src/ide.c
Normal file
616
src/ide.c
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
/**
|
||||||
|
* @file ide.c
|
||||||
|
* @brief IDE/ATA disk driver implementation.
|
||||||
|
*
|
||||||
|
* Probes the primary and secondary IDE channels for ATA hard drives and
|
||||||
|
* ATAPI CD/DVD drives using PIO-mode IDENTIFY commands. Detected devices
|
||||||
|
* are registered with the devicefs subsystem as block devices:
|
||||||
|
* - ATA drives → "hdd" class (hdd1, hdd2, ...)
|
||||||
|
* - ATAPI drives → "cd" class (cd1, cd2, ...)
|
||||||
|
*
|
||||||
|
* Supports PIO-mode sector reads and writes using 28-bit LBA addressing,
|
||||||
|
* which covers drives up to 128 GiB.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ide.h"
|
||||||
|
#include "port_io.h"
|
||||||
|
#include "devicefs.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
#include "driver.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);
|
||||||
|
|
||||||
|
/** All detected IDE devices. */
|
||||||
|
static ide_device_t ide_devices[IDE_MAX_DEVICES];
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Low-level IDE I/O helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the BSY flag to clear on the given channel.
|
||||||
|
* Returns the final status byte, or 0xFF on timeout.
|
||||||
|
*
|
||||||
|
* @param io_base Channel I/O base port.
|
||||||
|
* @return Status byte.
|
||||||
|
*/
|
||||||
|
static uint8_t ide_wait(uint16_t io_base) {
|
||||||
|
uint8_t status;
|
||||||
|
int timeout = 500000;
|
||||||
|
do {
|
||||||
|
status = inb(io_base + IDE_REG_STATUS);
|
||||||
|
if (status == 0xFF) return 0xFF; /* Floating bus */
|
||||||
|
if (--timeout == 0) return 0xFF;
|
||||||
|
} while (status & IDE_STATUS_BSY);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for BSY to clear and DRQ to set (data ready).
|
||||||
|
* Returns 0 on success, -1 on timeout or error.
|
||||||
|
*
|
||||||
|
* @param io_base Channel I/O base port.
|
||||||
|
* @return 0 on success, -1 on error/timeout.
|
||||||
|
*/
|
||||||
|
static int ide_wait_drq(uint16_t io_base) {
|
||||||
|
uint8_t status;
|
||||||
|
int timeout = 500000;
|
||||||
|
do {
|
||||||
|
status = inb(io_base + IDE_REG_STATUS);
|
||||||
|
if (status == 0xFF) return -1;
|
||||||
|
if (status & (IDE_STATUS_ERR | IDE_STATUS_DF)) return -1;
|
||||||
|
if (--timeout == 0) return -1;
|
||||||
|
} while ((status & (IDE_STATUS_BSY | IDE_STATUS_DRQ)) != IDE_STATUS_DRQ);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read 256 16-bit words (512 bytes) from the data register.
|
||||||
|
*
|
||||||
|
* @param io_base Channel I/O base port.
|
||||||
|
* @param buf Destination buffer (must be at least 512 bytes).
|
||||||
|
*/
|
||||||
|
static void ide_read_buffer(uint16_t io_base, uint16_t *buf) {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
buf[i] = inw(io_base + IDE_REG_DATA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write 256 16-bit words (512 bytes) to the data register.
|
||||||
|
*
|
||||||
|
* @param io_base Channel I/O base port.
|
||||||
|
* @param buf Source buffer (must be at least 512 bytes).
|
||||||
|
*/
|
||||||
|
static void ide_write_buffer(uint16_t io_base, const uint16_t *buf) {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
outw(io_base + IDE_REG_DATA, buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a software reset on an IDE channel.
|
||||||
|
*
|
||||||
|
* @param ctrl_base Channel control port.
|
||||||
|
*/
|
||||||
|
static void ide_soft_reset(uint16_t ctrl_base) {
|
||||||
|
outb(ctrl_base, 0x04); /* Set SRST bit */
|
||||||
|
/* Wait ~5 µs (several I/O reads) */
|
||||||
|
for (int i = 0; i < 4; i++) inb(ctrl_base);
|
||||||
|
outb(ctrl_base, 0x00); /* Clear SRST bit */
|
||||||
|
/* Wait for BSY to clear */
|
||||||
|
for (int i = 0; i < 4; i++) inb(ctrl_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a drive on a channel (master=0, slave=1).
|
||||||
|
*
|
||||||
|
* @param io_base Channel I/O base port.
|
||||||
|
* @param drive 0 for master, 1 for slave.
|
||||||
|
*/
|
||||||
|
static void ide_select_drive(uint16_t io_base, uint8_t drive) {
|
||||||
|
outb(io_base + IDE_REG_DRIVE_HEAD, 0xA0 | (drive << 4));
|
||||||
|
/* Wait ~400 ns by reading status 4 times */
|
||||||
|
for (int i = 0; i < 4; i++) inb(io_base + IDE_REG_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* IDENTIFY command
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send IDENTIFY (or IDENTIFY PACKET) to a drive and read the result.
|
||||||
|
*
|
||||||
|
* @param dev Pointer to the device descriptor to fill.
|
||||||
|
* @return 0 on success, -1 if no device or error.
|
||||||
|
*/
|
||||||
|
static int ide_identify(ide_device_t *dev) {
|
||||||
|
uint16_t io = dev->io_base;
|
||||||
|
|
||||||
|
/* Select the drive */
|
||||||
|
ide_select_drive(io, dev->drive);
|
||||||
|
|
||||||
|
/* Check for floating bus (no device) — read status, if 0xFF, no drive */
|
||||||
|
uint8_t check = inb(io + IDE_REG_STATUS);
|
||||||
|
if (check == 0xFF) return -1;
|
||||||
|
|
||||||
|
/* Clear sector count and LBA registers */
|
||||||
|
outb(io + IDE_REG_SECCOUNT, 0);
|
||||||
|
outb(io + IDE_REG_LBA_LO, 0);
|
||||||
|
outb(io + IDE_REG_LBA_MID, 0);
|
||||||
|
outb(io + IDE_REG_LBA_HI, 0);
|
||||||
|
|
||||||
|
/* Send IDENTIFY command */
|
||||||
|
outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY);
|
||||||
|
|
||||||
|
/* Read status — if 0 or 0xFF, no device */
|
||||||
|
uint8_t status = inb(io + IDE_REG_STATUS);
|
||||||
|
if (status == 0 || status == 0xFF) return -1;
|
||||||
|
|
||||||
|
/* Wait for BSY to clear */
|
||||||
|
status = ide_wait(io);
|
||||||
|
if (status == 0xFF) return -1;
|
||||||
|
|
||||||
|
/* Check if this is an ATAPI device (LBA_MID/HI will be non-zero) */
|
||||||
|
uint8_t lba_mid = inb(io + IDE_REG_LBA_MID);
|
||||||
|
uint8_t lba_hi = inb(io + IDE_REG_LBA_HI);
|
||||||
|
|
||||||
|
if (lba_mid == 0x14 && lba_hi == 0xEB) {
|
||||||
|
/* ATAPI device — re-identify with IDENTIFY PACKET DEVICE */
|
||||||
|
dev->type = IDE_TYPE_ATAPI;
|
||||||
|
outb(io + IDE_REG_COMMAND, IDE_CMD_IDENTIFY_PKT);
|
||||||
|
status = ide_wait(io);
|
||||||
|
if (status == 0xFF) return -1;
|
||||||
|
} else if (lba_mid == 0 && lba_hi == 0) {
|
||||||
|
dev->type = IDE_TYPE_ATA;
|
||||||
|
} else {
|
||||||
|
/* Unknown device type */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for DRQ */
|
||||||
|
if (ide_wait_drq(io) != 0) return -1;
|
||||||
|
|
||||||
|
/* Read 256 words of identification data */
|
||||||
|
uint16_t identify_buf[256];
|
||||||
|
ide_read_buffer(io, identify_buf);
|
||||||
|
|
||||||
|
/* Parse model string (words 27-46, each word is big-endian) */
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
dev->model[i * 2] = (char)(identify_buf[27 + i] >> 8);
|
||||||
|
dev->model[i * 2 + 1] = (char)(identify_buf[27 + i] & 0xFF);
|
||||||
|
}
|
||||||
|
dev->model[40] = '\0';
|
||||||
|
/* Trim trailing spaces */
|
||||||
|
for (int i = 39; i >= 0; i--) {
|
||||||
|
if (dev->model[i] == ' ') dev->model[i] = '\0';
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse sector count (28-bit LBA: words 60-61) */
|
||||||
|
if (dev->type == IDE_TYPE_ATA) {
|
||||||
|
dev->sector_count = (uint32_t)identify_buf[60] |
|
||||||
|
((uint32_t)identify_buf[61] << 16);
|
||||||
|
dev->sector_size = 512;
|
||||||
|
} else {
|
||||||
|
/* ATAPI: sector count from READ CAPACITY, default to 0 */
|
||||||
|
dev->sector_count = 0;
|
||||||
|
dev->sector_size = 2048; /* Standard CD sector size */
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->present = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Block device operations (for devicefs)
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read sectors from an ATA drive using PIO.
|
||||||
|
*/
|
||||||
|
static int ide_block_read(void *dev_data, uint32_t lba,
|
||||||
|
uint32_t count, void *buf) {
|
||||||
|
ide_device_t *dev = (ide_device_t *)dev_data;
|
||||||
|
if (!dev || dev->type != IDE_TYPE_ATA) return -1;
|
||||||
|
|
||||||
|
uint16_t io = dev->io_base;
|
||||||
|
uint8_t *dest = (uint8_t *)buf;
|
||||||
|
|
||||||
|
for (uint32_t s = 0; s < count; s++) {
|
||||||
|
uint32_t cur_lba = lba + s;
|
||||||
|
|
||||||
|
/* Select drive with LBA mode and top 4 LBA bits */
|
||||||
|
outb(io + IDE_REG_DRIVE_HEAD,
|
||||||
|
0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F));
|
||||||
|
|
||||||
|
/* Set sector count = 1 and LBA */
|
||||||
|
outb(io + IDE_REG_SECCOUNT, 1);
|
||||||
|
outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF);
|
||||||
|
outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF);
|
||||||
|
outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF);
|
||||||
|
|
||||||
|
/* Send READ SECTORS command */
|
||||||
|
outb(io + IDE_REG_COMMAND, IDE_CMD_READ_PIO);
|
||||||
|
|
||||||
|
/* Wait for data */
|
||||||
|
if (ide_wait_drq(io) != 0) return -1;
|
||||||
|
|
||||||
|
/* Read 256 words (512 bytes) */
|
||||||
|
ide_read_buffer(io, (uint16_t *)(dest + s * 512));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write sectors to an ATA drive using PIO.
|
||||||
|
*/
|
||||||
|
static int ide_block_write(void *dev_data, uint32_t lba,
|
||||||
|
uint32_t count, const void *buf) {
|
||||||
|
ide_device_t *dev = (ide_device_t *)dev_data;
|
||||||
|
if (!dev || dev->type != IDE_TYPE_ATA) return -1;
|
||||||
|
|
||||||
|
uint16_t io = dev->io_base;
|
||||||
|
const uint8_t *src = (const uint8_t *)buf;
|
||||||
|
|
||||||
|
for (uint32_t s = 0; s < count; s++) {
|
||||||
|
uint32_t cur_lba = lba + s;
|
||||||
|
|
||||||
|
outb(io + IDE_REG_DRIVE_HEAD,
|
||||||
|
0xE0 | (dev->drive << 4) | ((cur_lba >> 24) & 0x0F));
|
||||||
|
|
||||||
|
outb(io + IDE_REG_SECCOUNT, 1);
|
||||||
|
outb(io + IDE_REG_LBA_LO, cur_lba & 0xFF);
|
||||||
|
outb(io + IDE_REG_LBA_MID, (cur_lba >> 8) & 0xFF);
|
||||||
|
outb(io + IDE_REG_LBA_HI, (cur_lba >> 16) & 0xFF);
|
||||||
|
|
||||||
|
outb(io + IDE_REG_COMMAND, IDE_CMD_WRITE_PIO);
|
||||||
|
|
||||||
|
if (ide_wait_drq(io) != 0) return -1;
|
||||||
|
|
||||||
|
ide_write_buffer(io, (const uint16_t *)(src + s * 512));
|
||||||
|
|
||||||
|
/* Flush cache: wait for BSY to clear after write */
|
||||||
|
ide_wait(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return sector size for a device.
|
||||||
|
*/
|
||||||
|
static uint32_t ide_block_sector_size(void *dev_data) {
|
||||||
|
ide_device_t *dev = (ide_device_t *)dev_data;
|
||||||
|
return dev ? dev->sector_size : 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return total sector count for a device.
|
||||||
|
*/
|
||||||
|
static uint32_t ide_block_sector_count(void *dev_data) {
|
||||||
|
ide_device_t *dev = (ide_device_t *)dev_data;
|
||||||
|
return dev ? dev->sector_count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Block operations for ATA/ATAPI devices. */
|
||||||
|
static devicefs_block_ops_t ide_block_ops = {
|
||||||
|
.read_sectors = ide_block_read,
|
||||||
|
.write_sectors = ide_block_write,
|
||||||
|
.sector_size = ide_block_sector_size,
|
||||||
|
.sector_count = ide_block_sector_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Sysfs interface for /sys/ide
|
||||||
|
* ================================================================
|
||||||
|
*
|
||||||
|
* Exposes per-device info:
|
||||||
|
* /sys/ide/ → lists device names (hdd1, cd1, ...)
|
||||||
|
* /sys/ide/hdd1/ → lists attributes (model, type, channel, drive,
|
||||||
|
* sectors, sector_size)
|
||||||
|
* /sys/ide/hdd1/model → "QEMU HARDDISK\n"
|
||||||
|
* /sys/ide/hdd1/sectors → "0x00003800\n"
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Integer to hex string helper for sysfs output. */
|
||||||
|
static int ide_sysfs_hex(uint32_t val, char *buf, uint32_t buf_size) {
|
||||||
|
static const char hex[] = "0123456789ABCDEF";
|
||||||
|
if (buf_size < 12) return -1; /* "0x" + 8 hex + \n + \0 */
|
||||||
|
buf[0] = '0'; buf[1] = 'x';
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
buf[2 + (7 - i)] = hex[(val >> (i * 4)) & 0xF];
|
||||||
|
}
|
||||||
|
buf[10] = '\n'; buf[11] = '\0';
|
||||||
|
return 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split a path string into first component and remainder.
|
||||||
|
* "hdd1/model" → first="hdd1", rest="model"
|
||||||
|
*/
|
||||||
|
static void ide_split_path(const char *path, char *first, uint32_t fsize,
|
||||||
|
const char **rest) {
|
||||||
|
while (*path == '/') path++;
|
||||||
|
const char *s = path;
|
||||||
|
while (*s && *s != '/') s++;
|
||||||
|
uint32_t len = (uint32_t)(s - path);
|
||||||
|
if (len >= fsize) len = fsize - 1;
|
||||||
|
memcpy(first, path, len);
|
||||||
|
first[len] = '\0';
|
||||||
|
if (*s == '/') s++;
|
||||||
|
*rest = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attribute names exposed for each device. */
|
||||||
|
static const char *ide_sysfs_attrs[] = {
|
||||||
|
"model", "type", "channel", "drive", "sectors", "sector_size"
|
||||||
|
};
|
||||||
|
#define IDE_SYSFS_NUM_ATTRS 6
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an IDE device by its devicefs name (hdd1, cd1, etc.).
|
||||||
|
* Returns the device pointer, or NULL.
|
||||||
|
*/
|
||||||
|
static ide_device_t *ide_find_by_name(const char *name) {
|
||||||
|
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
|
||||||
|
if (!ide_devices[i].present) continue;
|
||||||
|
|
||||||
|
/* Reconstruct the devicefs name for this device */
|
||||||
|
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA) ? "hdd" : "cd";
|
||||||
|
uint32_t cls_len = strlen(cls);
|
||||||
|
if (strncmp(name, cls, cls_len) != 0) continue;
|
||||||
|
|
||||||
|
/* Parse the number suffix */
|
||||||
|
const char *num_str = name + cls_len;
|
||||||
|
uint32_t num = 0;
|
||||||
|
while (*num_str >= '0' && *num_str <= '9') {
|
||||||
|
num = num * 10 + (*num_str - '0');
|
||||||
|
num_str++;
|
||||||
|
}
|
||||||
|
if (*num_str != '\0' || num == 0) continue;
|
||||||
|
|
||||||
|
/* Count how many devices of this class precede idx i */
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
if (!ide_devices[j].present) continue;
|
||||||
|
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA) ? "hdd" : "cd";
|
||||||
|
if (strcmp(cls, jcls) == 0) count++;
|
||||||
|
}
|
||||||
|
if (count == num) return &ide_devices[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs list callback.
|
||||||
|
* path="" → list device names (hdd1, cd1, ...)
|
||||||
|
* path="hdd1" → list attributes
|
||||||
|
*/
|
||||||
|
static int ide_sysfs_list(void *ctx, const char *path, uint32_t idx,
|
||||||
|
sysfs_entry_t *out) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (path[0] == '\0') {
|
||||||
|
/* List all present devices */
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (int i = 0; i < IDE_MAX_DEVICES; i++) {
|
||||||
|
if (!ide_devices[i].present) continue;
|
||||||
|
if (count == idx) {
|
||||||
|
memset(out, 0, sizeof(sysfs_entry_t));
|
||||||
|
/* Build device name */
|
||||||
|
const char *cls = (ide_devices[i].type == IDE_TYPE_ATA)
|
||||||
|
? "hdd" : "cd";
|
||||||
|
/* Count devices of this class up to and including i */
|
||||||
|
uint32_t cls_count = 0;
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
if (!ide_devices[j].present) continue;
|
||||||
|
const char *jcls = (ide_devices[j].type == IDE_TYPE_ATA)
|
||||||
|
? "hdd" : "cd";
|
||||||
|
if (strcmp(cls, jcls) == 0) cls_count++;
|
||||||
|
}
|
||||||
|
uint32_t clen = strlen(cls);
|
||||||
|
memcpy(out->name, cls, clen);
|
||||||
|
/* Append number as ASCII (max 1 digit for 4 devs) */
|
||||||
|
out->name[clen] = '0' + (char)cls_count;
|
||||||
|
out->name[clen + 1] = '\0';
|
||||||
|
out->is_dir = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List attributes for a specific device */
|
||||||
|
ide_device_t *dev = ide_find_by_name(path);
|
||||||
|
if (!dev) return -1;
|
||||||
|
|
||||||
|
if (idx >= IDE_SYSFS_NUM_ATTRS) return -1;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(sysfs_entry_t));
|
||||||
|
strncpy(out->name, ide_sysfs_attrs[idx], SYSFS_MAX_NAME - 1);
|
||||||
|
out->is_dir = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs read callback.
|
||||||
|
* path="hdd1/model" → device model string
|
||||||
|
* path="hdd1/sectors" → hex sector count
|
||||||
|
*/
|
||||||
|
static int ide_sysfs_read(void *ctx, const char *path, char *buf,
|
||||||
|
uint32_t buf_size) {
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
/* Split path into device name and attribute name */
|
||||||
|
char dev_name[SYSFS_MAX_NAME];
|
||||||
|
const char *attr;
|
||||||
|
ide_split_path(path, dev_name, sizeof(dev_name), &attr);
|
||||||
|
|
||||||
|
ide_device_t *dev = ide_find_by_name(dev_name);
|
||||||
|
if (!dev) return -1;
|
||||||
|
if (attr[0] == '\0') return -1; /* no attribute specified */
|
||||||
|
|
||||||
|
if (strcmp(attr, "model") == 0) {
|
||||||
|
uint32_t mlen = strlen(dev->model);
|
||||||
|
if (mlen + 2 > buf_size) return -1;
|
||||||
|
memcpy(buf, dev->model, mlen);
|
||||||
|
buf[mlen] = '\n';
|
||||||
|
buf[mlen + 1] = '\0';
|
||||||
|
return (int)(mlen + 1);
|
||||||
|
}
|
||||||
|
if (strcmp(attr, "type") == 0) {
|
||||||
|
const char *t = (dev->type == IDE_TYPE_ATA) ? "ATA\n" :
|
||||||
|
(dev->type == IDE_TYPE_ATAPI) ? "ATAPI\n" : "unknown\n";
|
||||||
|
uint32_t tlen = strlen(t);
|
||||||
|
if (tlen + 1 > buf_size) return -1;
|
||||||
|
memcpy(buf, t, tlen + 1);
|
||||||
|
return (int)tlen;
|
||||||
|
}
|
||||||
|
if (strcmp(attr, "channel") == 0) {
|
||||||
|
const char *c = (dev->channel == 0) ? "primary\n" : "secondary\n";
|
||||||
|
uint32_t clen = strlen(c);
|
||||||
|
if (clen + 1 > buf_size) return -1;
|
||||||
|
memcpy(buf, c, clen + 1);
|
||||||
|
return (int)clen;
|
||||||
|
}
|
||||||
|
if (strcmp(attr, "drive") == 0) {
|
||||||
|
const char *d = (dev->drive == 0) ? "master\n" : "slave\n";
|
||||||
|
uint32_t dlen = strlen(d);
|
||||||
|
if (dlen + 1 > buf_size) return -1;
|
||||||
|
memcpy(buf, d, dlen + 1);
|
||||||
|
return (int)dlen;
|
||||||
|
}
|
||||||
|
if (strcmp(attr, "sectors") == 0) {
|
||||||
|
return ide_sysfs_hex(dev->sector_count, buf, buf_size);
|
||||||
|
}
|
||||||
|
if (strcmp(attr, "sector_size") == 0) {
|
||||||
|
return ide_sysfs_hex(dev->sector_size, buf, buf_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs write callback — IDE is read-only for now.
|
||||||
|
*/
|
||||||
|
static int ide_sysfs_write(void *ctx, const char *path, const char *buf,
|
||||||
|
uint32_t size) {
|
||||||
|
(void)ctx; (void)path; (void)buf; (void)size;
|
||||||
|
return -1; /* Read-only */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sysfs operations for the IDE namespace. */
|
||||||
|
static sysfs_ops_t ide_sysfs_ops = {
|
||||||
|
.list = ide_sysfs_list,
|
||||||
|
.read = ide_sysfs_read,
|
||||||
|
.write = ide_sysfs_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Driver probe and init
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe: always return OK since IDE ports are standard.
|
||||||
|
*/
|
||||||
|
static driver_probe_result_t ide_probe(void) {
|
||||||
|
return DRIVER_PROBE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the IDE driver: scan channels and register devices.
|
||||||
|
*/
|
||||||
|
static int ide_driver_init(void) {
|
||||||
|
memset(ide_devices, 0, sizeof(ide_devices));
|
||||||
|
|
||||||
|
/* Channel definitions: primary (0x1F0, 0x3F6), secondary (0x170, 0x376) */
|
||||||
|
static const uint16_t io_bases[2] = { IDE_PRIMARY_IO, IDE_SECONDARY_IO };
|
||||||
|
static const uint16_t ctrl_bases[2] = { IDE_PRIMARY_CTRL, IDE_SECONDARY_CTRL };
|
||||||
|
|
||||||
|
int found = 0;
|
||||||
|
|
||||||
|
for (int ch = 0; ch < 2; ch++) {
|
||||||
|
/* Check if channel exists by reading status — 0xFF = floating bus */
|
||||||
|
uint8_t ch_status = inb(io_bases[ch] + IDE_REG_STATUS);
|
||||||
|
if (ch_status == 0xFF) {
|
||||||
|
offset_print(" IDE: ");
|
||||||
|
offset_print(ch == 0 ? "primary" : "secondary");
|
||||||
|
offset_print(" channel not present\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Software reset the channel */
|
||||||
|
ide_soft_reset(ctrl_bases[ch]);
|
||||||
|
|
||||||
|
for (int drv = 0; drv < 2; drv++) {
|
||||||
|
int idx = ch * 2 + drv;
|
||||||
|
ide_devices[idx].channel = (uint8_t)ch;
|
||||||
|
ide_devices[idx].drive = (uint8_t)drv;
|
||||||
|
ide_devices[idx].io_base = io_bases[ch];
|
||||||
|
ide_devices[idx].ctrl_base = ctrl_bases[ch];
|
||||||
|
ide_devices[idx].present = 0;
|
||||||
|
|
||||||
|
if (ide_identify(&ide_devices[idx]) == 0) {
|
||||||
|
found++;
|
||||||
|
|
||||||
|
const char *class_name = (ide_devices[idx].type == IDE_TYPE_ATA)
|
||||||
|
? "hdd" : "cd";
|
||||||
|
const char *type_str = (ide_devices[idx].type == IDE_TYPE_ATA)
|
||||||
|
? "ATA" : "ATAPI";
|
||||||
|
|
||||||
|
offset_print(" IDE: ");
|
||||||
|
offset_print(type_str);
|
||||||
|
offset_print(" device on ");
|
||||||
|
offset_print(ch == 0 ? "primary" : "secondary");
|
||||||
|
offset_print(drv == 0 ? " master" : " slave");
|
||||||
|
offset_print(": ");
|
||||||
|
offset_print(ide_devices[idx].model);
|
||||||
|
offset_print(" (");
|
||||||
|
print_hex(ide_devices[idx].sector_count);
|
||||||
|
offset_print(" sectors)\n");
|
||||||
|
|
||||||
|
/* Register with devicefs */
|
||||||
|
devicefs_register_block(class_name, &ide_block_ops,
|
||||||
|
&ide_devices[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found == 0) {
|
||||||
|
offset_print(" IDE: no devices found\n");
|
||||||
|
} else {
|
||||||
|
offset_print(" IDE: ");
|
||||||
|
print_hex((uint32_t)found);
|
||||||
|
offset_print(" device(s) found\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register sysfs namespace for IDE information */
|
||||||
|
sysfs_register("ide", &ide_sysfs_ops, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ide_init(void) {
|
||||||
|
return ide_driver_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ide_device_t *ide_get_device(int index) {
|
||||||
|
if (index < 0 || index >= IDE_MAX_DEVICES) return NULL;
|
||||||
|
if (!ide_devices[index].present) return NULL;
|
||||||
|
return &ide_devices[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IDE driver descriptor. */
|
||||||
|
static const driver_t ide_driver = {
|
||||||
|
.name = "ide",
|
||||||
|
.probe = ide_probe,
|
||||||
|
.init = ide_driver_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
REGISTER_DRIVER(ide_driver);
|
||||||
88
src/ide.h
Normal file
88
src/ide.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* @file ide.h
|
||||||
|
* @brief IDE/ATA disk driver.
|
||||||
|
*
|
||||||
|
* Enumerates IDE devices on the primary and secondary channels, identifies
|
||||||
|
* ATA hard drives and ATAPI CD-ROMs, and registers them with the devicefs
|
||||||
|
* subsystem as block devices (hddN / cdN).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IDE_H
|
||||||
|
#define IDE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Maximum number of IDE devices (2 channels × 2 drives). */
|
||||||
|
#define IDE_MAX_DEVICES 4
|
||||||
|
|
||||||
|
/** IDE channel I/O port bases. */
|
||||||
|
#define IDE_PRIMARY_IO 0x1F0
|
||||||
|
#define IDE_PRIMARY_CTRL 0x3F6
|
||||||
|
#define IDE_SECONDARY_IO 0x170
|
||||||
|
#define IDE_SECONDARY_CTRL 0x376
|
||||||
|
|
||||||
|
/** IDE register offsets from I/O base. */
|
||||||
|
#define IDE_REG_DATA 0x00
|
||||||
|
#define IDE_REG_ERROR 0x01
|
||||||
|
#define IDE_REG_FEATURES 0x01
|
||||||
|
#define IDE_REG_SECCOUNT 0x02
|
||||||
|
#define IDE_REG_LBA_LO 0x03
|
||||||
|
#define IDE_REG_LBA_MID 0x04
|
||||||
|
#define IDE_REG_LBA_HI 0x05
|
||||||
|
#define IDE_REG_DRIVE_HEAD 0x06
|
||||||
|
#define IDE_REG_STATUS 0x07
|
||||||
|
#define IDE_REG_COMMAND 0x07
|
||||||
|
|
||||||
|
/** IDE status register bits. */
|
||||||
|
#define IDE_STATUS_ERR 0x01 /**< Error occurred. */
|
||||||
|
#define IDE_STATUS_DRQ 0x08 /**< Data request ready. */
|
||||||
|
#define IDE_STATUS_SRV 0x10 /**< Overlapped mode service request. */
|
||||||
|
#define IDE_STATUS_DF 0x20 /**< Drive fault. */
|
||||||
|
#define IDE_STATUS_DRDY 0x40 /**< Drive ready. */
|
||||||
|
#define IDE_STATUS_BSY 0x80 /**< Drive busy. */
|
||||||
|
|
||||||
|
/** IDE commands. */
|
||||||
|
#define IDE_CMD_IDENTIFY 0xEC /**< ATA IDENTIFY DEVICE. */
|
||||||
|
#define IDE_CMD_IDENTIFY_PKT 0xA1 /**< ATAPI IDENTIFY PACKET DEVICE. */
|
||||||
|
#define IDE_CMD_READ_PIO 0x20 /**< Read sectors (PIO, 28-bit LBA). */
|
||||||
|
#define IDE_CMD_WRITE_PIO 0x30 /**< Write sectors (PIO, 28-bit LBA). */
|
||||||
|
|
||||||
|
/** IDE device types. */
|
||||||
|
#define IDE_TYPE_NONE 0 /**< No device present. */
|
||||||
|
#define IDE_TYPE_ATA 1 /**< ATA hard disk. */
|
||||||
|
#define IDE_TYPE_ATAPI 2 /**< ATAPI CD/DVD drive. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IDE device descriptor.
|
||||||
|
*/
|
||||||
|
typedef struct ide_device {
|
||||||
|
uint8_t present; /**< 1 if device is present. */
|
||||||
|
uint8_t type; /**< IDE_TYPE_ATA or IDE_TYPE_ATAPI. */
|
||||||
|
uint8_t channel; /**< 0 = primary, 1 = secondary. */
|
||||||
|
uint8_t drive; /**< 0 = master, 1 = slave. */
|
||||||
|
uint16_t io_base; /**< I/O base port for this channel. */
|
||||||
|
uint16_t ctrl_base; /**< Control port for this channel. */
|
||||||
|
uint32_t sector_count; /**< Total sectors (28-bit LBA max). */
|
||||||
|
uint32_t sector_size; /**< Sector size in bytes (usually 512). */
|
||||||
|
char model[41]; /**< Model string from IDENTIFY. */
|
||||||
|
} ide_device_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the IDE driver.
|
||||||
|
*
|
||||||
|
* Scans primary and secondary channels for ATA/ATAPI devices,
|
||||||
|
* reads their IDENTIFY data, and registers them with devicefs.
|
||||||
|
*
|
||||||
|
* @return Number of devices found.
|
||||||
|
*/
|
||||||
|
int ide_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an IDE device by index (0–3).
|
||||||
|
*
|
||||||
|
* @param index Device index.
|
||||||
|
* @return Pointer to the device descriptor, or NULL if invalid/not present.
|
||||||
|
*/
|
||||||
|
ide_device_t *ide_get_device(int index);
|
||||||
|
|
||||||
|
#endif /* IDE_H */
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
179
src/kernel.c
179
src/kernel.c
@@ -1,6 +1,7 @@
|
|||||||
#include <multiboot2.h>
|
#include <multiboot2.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
#include "gdt.h"
|
#include "gdt.h"
|
||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
@@ -16,11 +17,40 @@
|
|||||||
#include "cpio.h"
|
#include "cpio.h"
|
||||||
#include "vfs.h"
|
#include "vfs.h"
|
||||||
#include "initrd_fs.h"
|
#include "initrd_fs.h"
|
||||||
|
#include "devicefs.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
#include "mbr.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)
|
void offset_print(const char *str)
|
||||||
{
|
{
|
||||||
while (*str) {
|
while (*str) {
|
||||||
outb(0xE9, *str);
|
outb(0xE9, *str); /* debugcon */
|
||||||
|
serial_putc(*str); /* COM1 serial */
|
||||||
str++;
|
str++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,37 +58,84 @@ void offset_print(const char *str)
|
|||||||
void print_hex(uint32_t val)
|
void print_hex(uint32_t val)
|
||||||
{
|
{
|
||||||
const char *hex = "0123456789ABCDEF";
|
const char *hex = "0123456789ABCDEF";
|
||||||
outb(0xE9, '0');
|
outb(0xE9, '0'); serial_putc('0');
|
||||||
outb(0xE9, 'x');
|
outb(0xE9, 'x'); serial_putc('x');
|
||||||
for (int i = 28; i >= 0; i -= 4) {
|
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) {
|
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) {
|
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: ");
|
offset_print("Invalid magic number: ");
|
||||||
print_hex(magic);
|
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");
|
offset_print("Booting...\n");
|
||||||
|
|
||||||
init_gdt();
|
init_gdt();
|
||||||
|
EARLY_PRINT("GDT ");
|
||||||
offset_print("GDT initialized\n");
|
offset_print("GDT initialized\n");
|
||||||
|
|
||||||
init_idt();
|
init_idt();
|
||||||
|
EARLY_PRINT("IDT ");
|
||||||
offset_print("IDT initialized\n");
|
offset_print("IDT initialized\n");
|
||||||
|
|
||||||
init_pic();
|
init_pic();
|
||||||
|
/* Unmask timer IRQ (IRQ0) explicitly */
|
||||||
|
pic_clear_mask(0);
|
||||||
|
EARLY_PRINT("PIC ");
|
||||||
offset_print("PIC initialized\n");
|
offset_print("PIC initialized\n");
|
||||||
|
|
||||||
init_pmm(addr);
|
init_pmm(addr);
|
||||||
|
EARLY_PRINT("PMM ");
|
||||||
offset_print("PMM initialized\n");
|
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;
|
uint32_t initrd_start = 0, initrd_end = 0;
|
||||||
|
memset(&fb_info, 0, sizeof(fb_info));
|
||||||
{
|
{
|
||||||
struct multiboot_tag *tag;
|
struct multiboot_tag *tag;
|
||||||
for (tag = (struct multiboot_tag *)(addr + 8);
|
for (tag = (struct multiboot_tag *)(addr + 8);
|
||||||
@@ -72,14 +149,59 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
print_hex(initrd_start);
|
print_hex(initrd_start);
|
||||||
offset_print(" to ");
|
offset_print(" to ");
|
||||||
print_hex(initrd_end);
|
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();
|
init_paging();
|
||||||
|
EARLY_PRINT("PAGING ");
|
||||||
offset_print("Paging initialized\n");
|
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 */
|
/* Test paging: allocate a page and write to it */
|
||||||
void *test_page = paging_alloc_page();
|
void *test_page = paging_alloc_page();
|
||||||
if (test_page) {
|
if (test_page) {
|
||||||
@@ -93,21 +215,25 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init_kmalloc();
|
init_kmalloc();
|
||||||
|
EARLY_PRINT("HEAP ");
|
||||||
offset_print("Memory allocator initialized\n");
|
offset_print("Memory allocator initialized\n");
|
||||||
|
|
||||||
/* Initialize CPIO ramdisk if module was loaded */
|
/* Initialize CPIO ramdisk if module was loaded */
|
||||||
if (initrd_start != 0) {
|
if (initrd_start != 0) {
|
||||||
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
|
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
|
||||||
|
EARLY_PRINT("CPIO ");
|
||||||
offset_print("CPIO ramdisk initialized\n");
|
offset_print("CPIO ramdisk initialized\n");
|
||||||
} else {
|
} else {
|
||||||
offset_print("No initrd module found\n");
|
offset_print("No initrd module found\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
init_vfs();
|
init_vfs();
|
||||||
|
EARLY_PRINT("VFS ");
|
||||||
offset_print("VFS initialized\n");
|
offset_print("VFS initialized\n");
|
||||||
|
|
||||||
if (initrd_start != 0) {
|
if (initrd_start != 0) {
|
||||||
init_initrd_fs();
|
init_initrd_fs();
|
||||||
|
EARLY_PRINT("INITRD ");
|
||||||
offset_print("Initrd filesystem mounted\n");
|
offset_print("Initrd filesystem mounted\n");
|
||||||
|
|
||||||
/* Test VFS: read a file from the initrd */
|
/* Test VFS: read a file from the initrd */
|
||||||
@@ -124,18 +250,55 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_devicefs();
|
||||||
|
EARLY_PRINT("DEV ");
|
||||||
|
offset_print("Devicefs initialized\n");
|
||||||
|
|
||||||
|
init_sysfs();
|
||||||
|
EARLY_PRINT("SYS ");
|
||||||
|
offset_print("Sysfs initialized\n");
|
||||||
|
|
||||||
init_tss();
|
init_tss();
|
||||||
|
EARLY_PRINT("TSS ");
|
||||||
offset_print("TSS initialized\n");
|
offset_print("TSS initialized\n");
|
||||||
|
|
||||||
init_syscalls();
|
init_syscalls();
|
||||||
|
EARLY_PRINT("SYSCALL ");
|
||||||
offset_print("Syscalls initialized\n");
|
offset_print("Syscalls initialized\n");
|
||||||
|
|
||||||
|
keyboard_init();
|
||||||
|
EARLY_PRINT("KBD ");
|
||||||
|
offset_print("Keyboard initialized\n");
|
||||||
|
|
||||||
init_process();
|
init_process();
|
||||||
|
EARLY_PRINT("PROC ");
|
||||||
offset_print("Process subsystem initialized\n");
|
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();
|
init_drivers();
|
||||||
|
EARLY_PRINT("DRV ");
|
||||||
offset_print("Drivers initialized\n");
|
offset_print("Drivers initialized\n");
|
||||||
|
|
||||||
|
/* Scan for MBR partitions on detected hard drives */
|
||||||
|
init_mbr();
|
||||||
|
offset_print("MBR scan complete\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 */
|
/* Show memory statistics and boot progress on VGA */
|
||||||
vga_show_mem_stats();
|
vga_show_mem_stats();
|
||||||
vga_puts("Boot complete.\n\n");
|
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 */
|
||||||
270
src/mbr.c
Normal file
270
src/mbr.c
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/**
|
||||||
|
* @file mbr.c
|
||||||
|
* @brief MBR partition table driver implementation.
|
||||||
|
*
|
||||||
|
* Reads sector 0 from hard drives registered with the devicefs, parses
|
||||||
|
* the MBR partition table, and registers each partition as a sub-device.
|
||||||
|
* Partition devices are named using the parent device name + "mbr" class
|
||||||
|
* prefix, with the number assigned by the devicefs (e.g., hdd1mbr1).
|
||||||
|
*
|
||||||
|
* Each partition sub-device translates LBA offsets relative to its own
|
||||||
|
* start into absolute LBA addresses on the parent device.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mbr.h"
|
||||||
|
#include "devicefs.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);
|
||||||
|
|
||||||
|
/** Maximum number of MBR partition sub-devices. */
|
||||||
|
#define MBR_MAX_SUBS 16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-partition context for sub-device read/write operations.
|
||||||
|
*/
|
||||||
|
typedef struct mbr_sub_device {
|
||||||
|
devicefs_device_t *parent; /**< Parent block device. */
|
||||||
|
uint32_t lba_start; /**< Partition start LBA on parent. */
|
||||||
|
uint32_t sector_count; /**< Partition size in sectors. */
|
||||||
|
uint8_t type; /**< MBR partition type code. */
|
||||||
|
int active; /**< 1 if in use. */
|
||||||
|
} mbr_sub_device_t;
|
||||||
|
|
||||||
|
/** Pool of sub-device contexts. */
|
||||||
|
static mbr_sub_device_t sub_devices[MBR_MAX_SUBS];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a sub-device context.
|
||||||
|
* @return Pointer to a free sub-device, or NULL if exhausted.
|
||||||
|
*/
|
||||||
|
static mbr_sub_device_t *alloc_sub(void) {
|
||||||
|
for (int i = 0; i < MBR_MAX_SUBS; i++) {
|
||||||
|
if (!sub_devices[i].active) {
|
||||||
|
sub_devices[i].active = 1;
|
||||||
|
return &sub_devices[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Block device ops for MBR partition sub-devices
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read sectors from a partition (translates LBA to parent device).
|
||||||
|
*/
|
||||||
|
static int mbr_sub_read(void *dev_data, uint32_t lba,
|
||||||
|
uint32_t count, void *buf) {
|
||||||
|
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
|
||||||
|
if (!sub || !sub->parent) return -1;
|
||||||
|
|
||||||
|
/* Bounds check */
|
||||||
|
if (lba + count > sub->sector_count) return -1;
|
||||||
|
|
||||||
|
/* Translate to absolute LBA on parent device */
|
||||||
|
uint32_t abs_lba = sub->lba_start + lba;
|
||||||
|
|
||||||
|
/* Read from parent device */
|
||||||
|
if (!sub->parent->block_ops || !sub->parent->block_ops->read_sectors) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sub->parent->block_ops->read_sectors(sub->parent->dev_data,
|
||||||
|
abs_lba, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write sectors to a partition (translates LBA to parent device).
|
||||||
|
*/
|
||||||
|
static int mbr_sub_write(void *dev_data, uint32_t lba,
|
||||||
|
uint32_t count, const void *buf) {
|
||||||
|
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
|
||||||
|
if (!sub || !sub->parent) return -1;
|
||||||
|
|
||||||
|
if (lba + count > sub->sector_count) return -1;
|
||||||
|
|
||||||
|
uint32_t abs_lba = sub->lba_start + lba;
|
||||||
|
|
||||||
|
if (!sub->parent->block_ops || !sub->parent->block_ops->write_sectors) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return sub->parent->block_ops->write_sectors(sub->parent->dev_data,
|
||||||
|
abs_lba, count, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sector size (same as parent device).
|
||||||
|
*/
|
||||||
|
static uint32_t mbr_sub_sector_size(void *dev_data) {
|
||||||
|
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
|
||||||
|
if (!sub || !sub->parent || !sub->parent->block_ops) return 512;
|
||||||
|
if (sub->parent->block_ops->sector_size) {
|
||||||
|
return sub->parent->block_ops->sector_size(sub->parent->dev_data);
|
||||||
|
}
|
||||||
|
return 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total sector count of the partition.
|
||||||
|
*/
|
||||||
|
static uint32_t mbr_sub_sector_count(void *dev_data) {
|
||||||
|
mbr_sub_device_t *sub = (mbr_sub_device_t *)dev_data;
|
||||||
|
return sub ? sub->sector_count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Block operations for MBR partition sub-devices. */
|
||||||
|
static devicefs_block_ops_t mbr_sub_ops = {
|
||||||
|
.read_sectors = mbr_sub_read,
|
||||||
|
.write_sectors = mbr_sub_write,
|
||||||
|
.sector_size = mbr_sub_sector_size,
|
||||||
|
.sector_count = mbr_sub_sector_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* MBR scanning
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
int mbr_scan(const char *parent_name) {
|
||||||
|
devicefs_device_t *parent = devicefs_find(parent_name);
|
||||||
|
if (!parent) {
|
||||||
|
offset_print(" MBR: device not found: ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print("\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent->type != DEVICEFS_BLOCK || !parent->block_ops) {
|
||||||
|
offset_print(" MBR: not a block device: ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print("\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent->block_ops->read_sectors) {
|
||||||
|
offset_print(" MBR: device has no read_sectors op\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read sector 0 (MBR) */
|
||||||
|
uint8_t sector[512];
|
||||||
|
if (parent->block_ops->read_sectors(parent->dev_data, 0, 1, sector) != 0) {
|
||||||
|
offset_print(" MBR: failed to read sector 0 of ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print("\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check MBR signature */
|
||||||
|
uint16_t sig = (uint16_t)sector[510] | ((uint16_t)sector[511] << 8);
|
||||||
|
if (sig != MBR_SIGNATURE) {
|
||||||
|
offset_print(" MBR: no valid signature on ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print(" (sig=");
|
||||||
|
print_hex(sig);
|
||||||
|
offset_print(")\n");
|
||||||
|
return 0; /* Not an error, just no MBR */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build the class name for partition sub-devices: parentName + "mbr" */
|
||||||
|
char class_name[DEVICEFS_MAX_DEV_NAME];
|
||||||
|
memset(class_name, 0, sizeof(class_name));
|
||||||
|
strncpy(class_name, parent_name, DEVICEFS_MAX_DEV_NAME - 4);
|
||||||
|
/* Append "mbr" */
|
||||||
|
uint32_t clen = strlen(class_name);
|
||||||
|
if (clen + 3 < DEVICEFS_MAX_DEV_NAME) {
|
||||||
|
class_name[clen] = 'm';
|
||||||
|
class_name[clen + 1] = 'b';
|
||||||
|
class_name[clen + 2] = 'r';
|
||||||
|
class_name[clen + 3] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the 4 partition table entries (at offset 446) */
|
||||||
|
int found = 0;
|
||||||
|
for (int i = 0; i < MBR_MAX_PARTITIONS; i++) {
|
||||||
|
mbr_partition_entry_t *entry =
|
||||||
|
(mbr_partition_entry_t *)(sector + 446 + i * 16);
|
||||||
|
|
||||||
|
/* Skip empty partitions */
|
||||||
|
if (entry->type == MBR_TYPE_EMPTY) continue;
|
||||||
|
if (entry->sector_count == 0) continue;
|
||||||
|
|
||||||
|
/* Skip extended partitions for now */
|
||||||
|
if (entry->type == MBR_TYPE_EXTENDED) {
|
||||||
|
offset_print(" MBR: skipping extended partition on ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print("\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbr_sub_device_t *sub = alloc_sub();
|
||||||
|
if (!sub) {
|
||||||
|
offset_print(" MBR: no free sub-device slots\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub->parent = parent;
|
||||||
|
sub->lba_start = entry->lba_start;
|
||||||
|
sub->sector_count = entry->sector_count;
|
||||||
|
sub->type = entry->type;
|
||||||
|
|
||||||
|
/* Register with devicefs */
|
||||||
|
devicefs_device_t *dev = devicefs_register_block(class_name,
|
||||||
|
&mbr_sub_ops,
|
||||||
|
sub);
|
||||||
|
if (dev) {
|
||||||
|
offset_print(" MBR: ");
|
||||||
|
offset_print(dev->name);
|
||||||
|
offset_print(" type=");
|
||||||
|
print_hex(entry->type);
|
||||||
|
offset_print(" LBA=");
|
||||||
|
print_hex(entry->lba_start);
|
||||||
|
offset_print(" size=");
|
||||||
|
print_hex(entry->sector_count);
|
||||||
|
offset_print(" sectors\n");
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found == 0) {
|
||||||
|
offset_print(" MBR: no partitions on ");
|
||||||
|
offset_print(parent_name);
|
||||||
|
offset_print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
int init_mbr(void) {
|
||||||
|
memset(sub_devices, 0, sizeof(sub_devices));
|
||||||
|
|
||||||
|
/* Scan all "hdd" class devices for MBR partitions */
|
||||||
|
int total_partitions = 0;
|
||||||
|
for (uint32_t i = 1; i <= 16; i++) {
|
||||||
|
/* Build device name: hdd1, hdd2, ... */
|
||||||
|
char name[DEVICEFS_MAX_DEV_NAME];
|
||||||
|
memset(name, 0, sizeof(name));
|
||||||
|
name[0] = 'h'; name[1] = 'd'; name[2] = 'd';
|
||||||
|
/* Append number */
|
||||||
|
if (i < 10) {
|
||||||
|
name[3] = (char)('0' + i);
|
||||||
|
} else {
|
||||||
|
name[3] = (char)('0' + i / 10);
|
||||||
|
name[4] = (char)('0' + i % 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
devicefs_device_t *dev = devicefs_find(name);
|
||||||
|
if (!dev) continue;
|
||||||
|
|
||||||
|
int n = mbr_scan(name);
|
||||||
|
if (n > 0) total_partitions += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" MBR: ");
|
||||||
|
print_hex((uint32_t)total_partitions);
|
||||||
|
offset_print(" partition(s) found\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
67
src/mbr.h
Normal file
67
src/mbr.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* @file mbr.h
|
||||||
|
* @brief MBR (Master Boot Record) partition table driver.
|
||||||
|
*
|
||||||
|
* Scans block devices for MBR partition tables and registers each
|
||||||
|
* discovered partition as a new block device in the devicefs.
|
||||||
|
* For example, hdd1 with two partitions becomes hdd1mbr1 and hdd1mbr2.
|
||||||
|
*
|
||||||
|
* The partition number (Y in hddNmbrY) is assigned by the devicefs
|
||||||
|
* subsystem using the device class "hddNmbr".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MBR_H
|
||||||
|
#define MBR_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** MBR signature bytes (offset 510-511). */
|
||||||
|
#define MBR_SIGNATURE 0xAA55
|
||||||
|
|
||||||
|
/** Maximum partitions in a standard MBR. */
|
||||||
|
#define MBR_MAX_PARTITIONS 4
|
||||||
|
|
||||||
|
/** MBR partition table entry (16 bytes each). */
|
||||||
|
typedef struct __attribute__((packed)) mbr_partition_entry {
|
||||||
|
uint8_t status; /**< 0x80 = bootable, 0x00 = not bootable. */
|
||||||
|
uint8_t chs_first[3]; /**< CHS address of first sector. */
|
||||||
|
uint8_t type; /**< Partition type code. */
|
||||||
|
uint8_t chs_last[3]; /**< CHS address of last sector. */
|
||||||
|
uint32_t lba_start; /**< LBA of first sector. */
|
||||||
|
uint32_t sector_count; /**< Number of sectors. */
|
||||||
|
} mbr_partition_entry_t;
|
||||||
|
|
||||||
|
/** Well-known MBR partition types. */
|
||||||
|
#define MBR_TYPE_EMPTY 0x00
|
||||||
|
#define MBR_TYPE_FAT12 0x01
|
||||||
|
#define MBR_TYPE_FAT16_SM 0x04 /**< FAT16 < 32 MiB. */
|
||||||
|
#define MBR_TYPE_EXTENDED 0x05
|
||||||
|
#define MBR_TYPE_FAT16_LG 0x06 /**< FAT16 >= 32 MiB. */
|
||||||
|
#define MBR_TYPE_FAT32 0x0B
|
||||||
|
#define MBR_TYPE_FAT32_LBA 0x0C
|
||||||
|
#define MBR_TYPE_FAT16_LBA 0x0E
|
||||||
|
#define MBR_TYPE_LINUX 0x83
|
||||||
|
#define MBR_TYPE_LINUX_SWAP 0x82
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan a block device for MBR partitions.
|
||||||
|
*
|
||||||
|
* Reads sector 0 of the given device, validates the MBR signature,
|
||||||
|
* and registers each non-empty partition entry with the devicefs
|
||||||
|
* as a sub-device.
|
||||||
|
*
|
||||||
|
* @param parent_name Name of the parent device (e.g., "hdd1").
|
||||||
|
* @return Number of partitions found, or -1 on error.
|
||||||
|
*/
|
||||||
|
int mbr_scan(const char *parent_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the MBR subsystem.
|
||||||
|
*
|
||||||
|
* Scans all currently registered "hdd" class devices for MBR partitions.
|
||||||
|
*
|
||||||
|
* @return 0 on success.
|
||||||
|
*/
|
||||||
|
int init_mbr(void);
|
||||||
|
|
||||||
|
#endif /* MBR_H */
|
||||||
@@ -15,6 +15,18 @@ static inline uint8_t inb(uint16_t port)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void outw(uint16_t port, uint16_t val)
|
||||||
|
{
|
||||||
|
asm volatile ( "outw %w0, %w1" : : "a"(val), "Nd"(port) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t inw(uint16_t port)
|
||||||
|
{
|
||||||
|
uint16_t ret;
|
||||||
|
asm volatile ( "inw %w1, %w0" : "=a"(ret) : "Nd"(port) );
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void io_wait(void)
|
static inline void io_wait(void)
|
||||||
{
|
{
|
||||||
/* Port 0x80 is used for 'checkpoints' during POST. */
|
/* Port 0x80 is used for 'checkpoints' during POST. */
|
||||||
|
|||||||
189
src/syscall.c
189
src/syscall.c
@@ -12,7 +12,13 @@
|
|||||||
#include "env.h"
|
#include "env.h"
|
||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "vga.h"
|
#include "vga.h"
|
||||||
|
#include "vfs.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. */
|
||||||
@@ -55,16 +61,39 @@ static int32_t sys_write(registers_t *regs) {
|
|||||||
return (int32_t)len;
|
return (int32_t)len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* VFS file descriptors (fd >= 3) */
|
||||||
|
if (fd >= 3) {
|
||||||
|
return vfs_write(fd, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
return -1; /* Invalid fd */
|
return -1; /* Invalid fd */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* VFS file descriptors (fd >= 3) */
|
||||||
|
if (fd >= 3) {
|
||||||
|
return vfs_read(fd, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; /* Invalid fd */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,11 +114,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 +162,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,6 +292,53 @@ static int32_t sys_setenv(registers_t *regs) {
|
|||||||
return env_set(&cur->env, key, value);
|
return env_set(&cur->env, key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_OPEN: open a file by path.
|
||||||
|
* EBX = path string, ECX = flags.
|
||||||
|
* Returns file descriptor (>= 3) on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
static int32_t sys_open(registers_t *regs) {
|
||||||
|
const char *path = (const char *)regs->ebx;
|
||||||
|
uint32_t flags = regs->ecx;
|
||||||
|
return (int32_t)vfs_open(path, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_CLOSE: close a file descriptor.
|
||||||
|
* EBX = fd.
|
||||||
|
* Returns 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
static int32_t sys_close(registers_t *regs) {
|
||||||
|
int fd = (int)regs->ebx;
|
||||||
|
if (fd < 3) return -1; /* Don't close stdin/stdout/stderr */
|
||||||
|
vfs_close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. */
|
/** System call dispatch table. */
|
||||||
typedef int32_t (*syscall_fn)(registers_t *);
|
typedef int32_t (*syscall_fn)(registers_t *);
|
||||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||||
@@ -184,6 +352,9 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
|||||||
[SYS_EXEC] = sys_exec,
|
[SYS_EXEC] = sys_exec,
|
||||||
[SYS_GETENV] = sys_getenv,
|
[SYS_GETENV] = sys_getenv,
|
||||||
[SYS_SETENV] = sys_setenv,
|
[SYS_SETENV] = sys_setenv,
|
||||||
|
[SYS_READDIR] = sys_readdir,
|
||||||
|
[SYS_OPEN] = sys_open,
|
||||||
|
[SYS_CLOSE] = sys_close,
|
||||||
};
|
};
|
||||||
|
|
||||||
void syscall_handler(registers_t *regs) {
|
void syscall_handler(registers_t *regs) {
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
#define SYS_EXEC 7 /**< Execute a program. path=EBX, argv=ECX. */
|
#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_GETENV 8 /**< Get environment variable. key=EBX, buf=ECX, bufsize=EDX. */
|
||||||
#define SYS_SETENV 9 /**< Set environment variable. key=EBX, value=ECX. */
|
#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. */
|
||||||
|
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
|
||||||
|
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
|
||||||
|
|
||||||
/** Total number of system calls. */
|
/** Total number of system calls. */
|
||||||
#define NUM_SYSCALLS 10
|
#define NUM_SYSCALLS 13
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the system call handler.
|
* Initialize the system call handler.
|
||||||
|
|||||||
294
src/sysfs.c
Normal file
294
src/sysfs.c
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/**
|
||||||
|
* @file sysfs.c
|
||||||
|
* @brief System filesystem (sysfs) implementation.
|
||||||
|
*
|
||||||
|
* A VFS driver mounted at /sys that lets kernel drivers expose virtual
|
||||||
|
* text files. Drivers register namespaces (e.g., "ide", "mem") and
|
||||||
|
* provide callbacks for listing entries and reading/writing file content.
|
||||||
|
*
|
||||||
|
* Path structure:
|
||||||
|
* /sys/<namespace>/<subpath...>
|
||||||
|
*
|
||||||
|
* The VFS routes requests through sysfs_readdir, sysfs_finddir, and
|
||||||
|
* sysfs_read/write, which decompose the path and forward it to the
|
||||||
|
* appropriate driver's callbacks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sysfs.h"
|
||||||
|
#include "vfs.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);
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Namespace registry
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** A registered sysfs namespace. */
|
||||||
|
typedef struct sysfs_ns {
|
||||||
|
char name[SYSFS_MAX_NAME]; /**< Namespace name. */
|
||||||
|
sysfs_ops_t *ops; /**< Driver callbacks. */
|
||||||
|
void *ctx; /**< Driver context. */
|
||||||
|
int active; /**< 1 if registered. */
|
||||||
|
} sysfs_ns_t;
|
||||||
|
|
||||||
|
/** Namespace table. */
|
||||||
|
static sysfs_ns_t namespaces[SYSFS_MAX_NAMESPACES];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a namespace by name.
|
||||||
|
* @return Pointer to the namespace, or NULL.
|
||||||
|
*/
|
||||||
|
static sysfs_ns_t *find_ns(const char *name) {
|
||||||
|
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||||
|
if (namespaces[i].active && strcmp(namespaces[i].name, name) == 0) {
|
||||||
|
return &namespaces[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Path helpers
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the first path component from a relative path.
|
||||||
|
*
|
||||||
|
* Given "ide/hdd1/model", sets component="ide" and rest="hdd1/model".
|
||||||
|
* Given "ide", sets component="ide" and rest="".
|
||||||
|
*
|
||||||
|
* @param path Input relative path.
|
||||||
|
* @param component Output buffer for the first component.
|
||||||
|
* @param comp_size Size of component buffer.
|
||||||
|
* @param rest Output pointer to the remainder of the path.
|
||||||
|
*/
|
||||||
|
static void split_first(const char *path, char *component,
|
||||||
|
uint32_t comp_size, const char **rest) {
|
||||||
|
/* Skip leading slashes */
|
||||||
|
while (*path == '/') path++;
|
||||||
|
|
||||||
|
const char *slash = path;
|
||||||
|
while (*slash && *slash != '/') slash++;
|
||||||
|
|
||||||
|
uint32_t len = (uint32_t)(slash - path);
|
||||||
|
if (len >= comp_size) len = comp_size - 1;
|
||||||
|
memcpy(component, path, len);
|
||||||
|
component[len] = '\0';
|
||||||
|
|
||||||
|
/* Skip the slash */
|
||||||
|
if (*slash == '/') slash++;
|
||||||
|
*rest = slash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* VFS callbacks for /sys
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a sysfs virtual file.
|
||||||
|
*
|
||||||
|
* The node's fs_data points to the sysfs_ns_t.
|
||||||
|
* The node's name encodes the sub-path (set by finddir).
|
||||||
|
*
|
||||||
|
* We store the full sub-path (relative to namespace) in node->name.
|
||||||
|
*/
|
||||||
|
static int32_t sysfs_vfs_read(vfs_node_t *node, uint32_t offset,
|
||||||
|
uint32_t size, void *buf) {
|
||||||
|
if (!node || !node->fs_data) return -1;
|
||||||
|
|
||||||
|
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
|
||||||
|
if (!ns->ops || !ns->ops->read) return -1;
|
||||||
|
|
||||||
|
/* Read the full content into a stack buffer */
|
||||||
|
char content[SYSFS_MAX_CONTENT];
|
||||||
|
int total = ns->ops->read(ns->ctx, node->name, content,
|
||||||
|
sizeof(content));
|
||||||
|
if (total < 0) return -1;
|
||||||
|
|
||||||
|
/* Apply offset */
|
||||||
|
if (offset >= (uint32_t)total) return 0;
|
||||||
|
uint32_t avail = (uint32_t)total - offset;
|
||||||
|
if (size > avail) size = avail;
|
||||||
|
memcpy(buf, content + offset, size);
|
||||||
|
return (int32_t)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to a sysfs virtual file.
|
||||||
|
*/
|
||||||
|
static int32_t sysfs_vfs_write(vfs_node_t *node, uint32_t offset,
|
||||||
|
uint32_t size, const void *buf) {
|
||||||
|
if (!node || !node->fs_data) return -1;
|
||||||
|
|
||||||
|
sysfs_ns_t *ns = (sysfs_ns_t *)node->fs_data;
|
||||||
|
if (!ns->ops || !ns->ops->write) return -1;
|
||||||
|
|
||||||
|
return ns->ops->write(ns->ctx, node->name, (const char *)buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read directory entries under /sys or /sys/<namespace>/...
|
||||||
|
*
|
||||||
|
* When the directory node has no fs_data, we're at /sys root → list
|
||||||
|
* namespaces. When fs_data is a namespace, delegate to its list callback.
|
||||||
|
*/
|
||||||
|
static int sysfs_vfs_readdir(vfs_node_t *dir, uint32_t idx,
|
||||||
|
vfs_dirent_t *out) {
|
||||||
|
if (!dir) return -1;
|
||||||
|
|
||||||
|
/* Root of /sys → list registered namespaces */
|
||||||
|
if (dir->fs_data == NULL) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||||
|
if (!namespaces[i].active) continue;
|
||||||
|
if (count == idx) {
|
||||||
|
memset(out, 0, sizeof(vfs_dirent_t));
|
||||||
|
strncpy(out->name, namespaces[i].name,
|
||||||
|
VFS_MAX_NAME - 1);
|
||||||
|
out->type = VFS_DIRECTORY;
|
||||||
|
out->inode = (uint32_t)i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subdirectory within a namespace */
|
||||||
|
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
|
||||||
|
if (!ns->ops || !ns->ops->list) return -1;
|
||||||
|
|
||||||
|
sysfs_entry_t entry;
|
||||||
|
int ret = ns->ops->list(ns->ctx, dir->name, idx, &entry);
|
||||||
|
if (ret != 0) return -1;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(vfs_dirent_t));
|
||||||
|
strncpy(out->name, entry.name, VFS_MAX_NAME - 1);
|
||||||
|
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
|
||||||
|
out->inode = idx;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a child by name inside a sysfs directory.
|
||||||
|
*
|
||||||
|
* At the /sys root (fs_data == NULL), we look up namespace names.
|
||||||
|
* Inside a namespace, we ask the driver's list callback to check
|
||||||
|
* whether the child is a file or directory.
|
||||||
|
*/
|
||||||
|
static int sysfs_vfs_finddir(vfs_node_t *dir, const char *name,
|
||||||
|
vfs_node_t *out) {
|
||||||
|
if (!dir) return -1;
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(vfs_node_t));
|
||||||
|
|
||||||
|
/* Root of /sys → find a namespace */
|
||||||
|
if (dir->fs_data == NULL) {
|
||||||
|
sysfs_ns_t *ns = find_ns(name);
|
||||||
|
if (!ns) return -1;
|
||||||
|
|
||||||
|
strncpy(out->name, "", VFS_MAX_NAME - 1); /* sub-path is "" */
|
||||||
|
out->type = VFS_DIRECTORY;
|
||||||
|
out->fs_data = ns;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inside a namespace: build sub-path */
|
||||||
|
sysfs_ns_t *ns = (sysfs_ns_t *)dir->fs_data;
|
||||||
|
if (!ns->ops || !ns->ops->list) return -1;
|
||||||
|
|
||||||
|
/* The dir->name contains the current sub-path (relative to namespace).
|
||||||
|
* The child path is dir->name + "/" + name (or just name if dir->name
|
||||||
|
* is empty). */
|
||||||
|
char child_path[VFS_MAX_NAME];
|
||||||
|
if (dir->name[0] == '\0') {
|
||||||
|
strncpy(child_path, name, VFS_MAX_NAME - 1);
|
||||||
|
child_path[VFS_MAX_NAME - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
/* Manually concatenate dir->name + "/" + name */
|
||||||
|
uint32_t dlen = strlen(dir->name);
|
||||||
|
uint32_t nlen = strlen(name);
|
||||||
|
if (dlen + 1 + nlen >= VFS_MAX_NAME) return -1;
|
||||||
|
memcpy(child_path, dir->name, dlen);
|
||||||
|
child_path[dlen] = '/';
|
||||||
|
memcpy(child_path + dlen + 1, name, nlen);
|
||||||
|
child_path[dlen + 1 + nlen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate the list callback to see if this child exists */
|
||||||
|
sysfs_entry_t entry;
|
||||||
|
for (uint32_t i = 0; ; i++) {
|
||||||
|
int ret = ns->ops->list(ns->ctx, dir->name, i, &entry);
|
||||||
|
if (ret != 0) break; /* no more entries */
|
||||||
|
if (strcmp(entry.name, name) == 0) {
|
||||||
|
strncpy(out->name, child_path, VFS_MAX_NAME - 1);
|
||||||
|
out->type = entry.is_dir ? VFS_DIRECTORY : VFS_FILE;
|
||||||
|
out->fs_data = ns;
|
||||||
|
|
||||||
|
/* For files, get the content size */
|
||||||
|
if (!entry.is_dir && ns->ops->read) {
|
||||||
|
char tmp[SYSFS_MAX_CONTENT];
|
||||||
|
int sz = ns->ops->read(ns->ctx, child_path, tmp, sizeof(tmp));
|
||||||
|
out->size = (sz > 0) ? (uint32_t)sz : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; /* child not found */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** VFS operations for /sys. */
|
||||||
|
static vfs_fs_ops_t sysfs_vfs_ops = {
|
||||||
|
.open = NULL,
|
||||||
|
.close = NULL,
|
||||||
|
.read = sysfs_vfs_read,
|
||||||
|
.write = sysfs_vfs_write,
|
||||||
|
.readdir = sysfs_vfs_readdir,
|
||||||
|
.finddir = sysfs_vfs_finddir,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Public API
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
void init_sysfs(void) {
|
||||||
|
memset(namespaces, 0, sizeof(namespaces));
|
||||||
|
vfs_mount("/sys", &sysfs_vfs_ops, NULL);
|
||||||
|
offset_print(" Sysfs mounted at /sys\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx) {
|
||||||
|
if (!name || !ops) return -1;
|
||||||
|
|
||||||
|
/* Check for duplicate */
|
||||||
|
if (find_ns(name)) {
|
||||||
|
offset_print(" Sysfs: namespace '");
|
||||||
|
offset_print(name);
|
||||||
|
offset_print("' already exists\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find a free slot */
|
||||||
|
for (int i = 0; i < SYSFS_MAX_NAMESPACES; i++) {
|
||||||
|
if (!namespaces[i].active) {
|
||||||
|
strncpy(namespaces[i].name, name, SYSFS_MAX_NAME - 1);
|
||||||
|
namespaces[i].name[SYSFS_MAX_NAME - 1] = '\0';
|
||||||
|
namespaces[i].ops = ops;
|
||||||
|
namespaces[i].ctx = ctx;
|
||||||
|
namespaces[i].active = 1;
|
||||||
|
|
||||||
|
offset_print(" Sysfs: registered namespace '");
|
||||||
|
offset_print(name);
|
||||||
|
offset_print("'\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" Sysfs: no free namespace slots\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
100
src/sysfs.h
Normal file
100
src/sysfs.h
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* @file sysfs.h
|
||||||
|
* @brief System filesystem (sysfs) interface.
|
||||||
|
*
|
||||||
|
* Allows kernel drivers to expose text/config files to userspace via the
|
||||||
|
* VFS at /sys. Each driver registers a namespace (e.g., "ide") and
|
||||||
|
* provides callbacks for listing, reading, and writing virtual files.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* 1. Define a sysfs_ops_t with list/read/write callbacks.
|
||||||
|
* 2. Call sysfs_register("ide", &my_ops, my_context).
|
||||||
|
* 3. Users can then `ls /sys/ide`, `cat /sys/ide/model`, etc.
|
||||||
|
*
|
||||||
|
* File contents are small text strings stored on the stack; the read
|
||||||
|
* callback fills a caller-provided buffer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SYSFS_H
|
||||||
|
#define SYSFS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/** Maximum number of sysfs namespaces. */
|
||||||
|
#define SYSFS_MAX_NAMESPACES 32
|
||||||
|
|
||||||
|
/** Maximum namespace name length. */
|
||||||
|
#define SYSFS_MAX_NAME 64
|
||||||
|
|
||||||
|
/** Maximum size for sysfs file content (text buffer). */
|
||||||
|
#define SYSFS_MAX_CONTENT 512
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sysfs file entry returned by the list callback.
|
||||||
|
*/
|
||||||
|
typedef struct sysfs_entry {
|
||||||
|
char name[SYSFS_MAX_NAME]; /**< File or directory name. */
|
||||||
|
uint8_t is_dir; /**< 1 if directory, 0 if file. */
|
||||||
|
} sysfs_entry_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks provided by a driver to expose files under its namespace.
|
||||||
|
*
|
||||||
|
* All paths are relative to the namespace root. For a namespace "ide",
|
||||||
|
* a path of "hdd1/model" means the file "model" inside subdirectory "hdd1".
|
||||||
|
*/
|
||||||
|
typedef struct sysfs_ops {
|
||||||
|
/**
|
||||||
|
* List entries in a directory.
|
||||||
|
*
|
||||||
|
* @param ctx Driver context pointer (from registration).
|
||||||
|
* @param path Relative path within the namespace ("" for root).
|
||||||
|
* @param idx Entry index (0-based).
|
||||||
|
* @param out Output entry.
|
||||||
|
* @return 0 on success, -1 when no more entries.
|
||||||
|
*/
|
||||||
|
int (*list)(void *ctx, const char *path, uint32_t idx,
|
||||||
|
sysfs_entry_t *out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a file's content as a text string.
|
||||||
|
*
|
||||||
|
* @param ctx Driver context pointer.
|
||||||
|
* @param path Relative path to the file within the namespace.
|
||||||
|
* @param buf Output buffer for file content.
|
||||||
|
* @param buf_size Buffer size.
|
||||||
|
* @return Number of bytes written to buf, or -1 on error.
|
||||||
|
*/
|
||||||
|
int (*read)(void *ctx, const char *path, char *buf, uint32_t buf_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to a file.
|
||||||
|
*
|
||||||
|
* @param ctx Driver context pointer.
|
||||||
|
* @param path Relative path to the file within the namespace.
|
||||||
|
* @param buf Input data.
|
||||||
|
* @param size Number of bytes to write.
|
||||||
|
* @return Number of bytes consumed, or -1 on error.
|
||||||
|
*/
|
||||||
|
int (*write)(void *ctx, const char *path, const char *buf, uint32_t size);
|
||||||
|
} sysfs_ops_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the sysfs and mount it at /sys.
|
||||||
|
*/
|
||||||
|
void init_sysfs(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a driver namespace in sysfs.
|
||||||
|
*
|
||||||
|
* After registration, files are accessible under /sys/<name>/.
|
||||||
|
*
|
||||||
|
* @param name Namespace name (e.g., "ide").
|
||||||
|
* @param ops Callback operations.
|
||||||
|
* @param ctx Opaque driver context passed to all callbacks.
|
||||||
|
* @return 0 on success, -1 if the table is full or name is invalid.
|
||||||
|
*/
|
||||||
|
int sysfs_register(const char *name, sysfs_ops_t *ops, void *ctx);
|
||||||
|
|
||||||
|
#endif /* SYSFS_H */
|
||||||
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) {
|
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;
|
vfs_node_t node;
|
||||||
if (resolve_path(path, &node) != 0) {
|
if (resolve_path(path, &node) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
407
src/vga.c
407
src/vga.c
@@ -1,10 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* @file vga.c
|
* @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
|
* Supports two modes depending on what GRUB provides:
|
||||||
* is an array of 80×25 16-bit values, where the low byte is the ASCII
|
* - EGA text mode: writes character+attribute pairs to the text buffer
|
||||||
* character and the high byte encodes foreground and background color.
|
* - 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
|
* This driver registers itself via the REGISTER_DRIVER macro and is
|
||||||
* automatically discovered during boot.
|
* automatically discovered during boot.
|
||||||
@@ -14,122 +18,295 @@
|
|||||||
#include "driver.h"
|
#include "driver.h"
|
||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
#include "pmm.h"
|
#include "pmm.h"
|
||||||
|
#include "framebuffer.h"
|
||||||
|
#include "font8x16.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/** Base address of the VGA text-mode framebuffer. */
|
/* Debug helpers defined in kernel.c */
|
||||||
#define VGA_BUFFER 0xB8000
|
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). */
|
/** Current text cursor position. */
|
||||||
static uint8_t cursor_row = 0;
|
static uint32_t cursor_row = 0;
|
||||||
|
static uint32_t cursor_col = 0;
|
||||||
|
|
||||||
/** Current cursor column (0-based). */
|
/** Columns and rows of the text grid. */
|
||||||
static uint8_t cursor_col = 0;
|
static uint32_t text_cols = 80;
|
||||||
|
static uint32_t text_rows = 25;
|
||||||
|
|
||||||
/** Current text attribute byte (foreground | background << 4). */
|
/** Current color attribute (foreground | background << 4). */
|
||||||
static uint8_t text_attr = 0;
|
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) {
|
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
||||||
return (uint16_t)c | ((uint16_t)attr << 8);
|
return (uint16_t)c | ((uint16_t)attr << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void text_update_cursor(void) {
|
||||||
* Create a color attribute byte from foreground and background colors.
|
uint16_t pos = (uint16_t)(cursor_row * text_cols + cursor_col);
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
|
|
||||||
outb(0x3D4, 0x0F);
|
outb(0x3D4, 0x0F);
|
||||||
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
||||||
outb(0x3D4, 0x0E);
|
outb(0x3D4, 0x0E);
|
||||||
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static void text_scroll(void) {
|
||||||
* Scroll the screen up by one line.
|
for (uint32_t i = 0; i < (text_rows - 1) * text_cols; i++) {
|
||||||
*
|
text_buffer[i] = text_buffer[i + text_cols];
|
||||||
* 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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear the last line */
|
|
||||||
uint16_t blank = vga_entry(' ', text_attr);
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
|
for (uint32_t i = (text_rows - 1) * text_cols; i < text_rows * text_cols; i++) {
|
||||||
vga_buffer[i] = blank;
|
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);
|
uint16_t blank = vga_entry(' ', text_attr);
|
||||||
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
for (uint32_t i = 0; i < text_cols * text_rows; i++) {
|
||||||
vga_buffer[i] = blank;
|
text_buffer[i] = blank;
|
||||||
}
|
}
|
||||||
cursor_row = 0;
|
cursor_row = 0;
|
||||||
cursor_col = 0;
|
cursor_col = 0;
|
||||||
update_cursor();
|
text_update_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
static void text_putchar(char c) {
|
||||||
text_attr = vga_color(fg, bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void vga_putchar(char c) {
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
cursor_col = 0;
|
cursor_col = 0;
|
||||||
cursor_row++;
|
cursor_row++;
|
||||||
} else if (c == '\r') {
|
} else if (c == '\r') {
|
||||||
cursor_col = 0;
|
cursor_col = 0;
|
||||||
} else if (c == '\t') {
|
} else if (c == '\t') {
|
||||||
cursor_col = (cursor_col + 8) & ~7;
|
cursor_col = (cursor_col + 8) & ~7u;
|
||||||
if (cursor_col >= VGA_WIDTH) {
|
if (cursor_col >= text_cols) {
|
||||||
cursor_col = 0;
|
cursor_col = 0;
|
||||||
cursor_row++;
|
cursor_row++;
|
||||||
}
|
}
|
||||||
} else if (c == '\b') {
|
} else if (c == '\b') {
|
||||||
if (cursor_col > 0) {
|
if (cursor_col > 0) {
|
||||||
cursor_col--;
|
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 {
|
} 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++;
|
cursor_col++;
|
||||||
if (cursor_col >= VGA_WIDTH) {
|
if (cursor_col >= text_cols) {
|
||||||
cursor_col = 0;
|
cursor_col = 0;
|
||||||
cursor_row++;
|
cursor_row++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cursor_row >= text_rows) {
|
||||||
|
text_scroll();
|
||||||
|
}
|
||||||
|
text_update_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
if (cursor_row >= VGA_HEIGHT) {
|
/* ================================================================
|
||||||
scroll();
|
* 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 */
|
||||||
}
|
}
|
||||||
|
|
||||||
update_cursor();
|
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) {
|
void vga_puts(const char *str) {
|
||||||
@@ -153,26 +330,24 @@ void vga_put_dec(uint32_t val) {
|
|||||||
vga_putchar('0');
|
vga_putchar('0');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char buf[12];
|
char buf[12];
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
while (val > 0) {
|
while (val > 0) {
|
||||||
buf[pos++] = '0' + (val % 10);
|
buf[pos++] = '0' + (val % 10);
|
||||||
val /= 10;
|
val /= 10;
|
||||||
}
|
}
|
||||||
/* Print in reverse */
|
|
||||||
while (pos > 0) {
|
while (pos > 0) {
|
||||||
vga_putchar(buf[--pos]);
|
vga_putchar(buf[--pos]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void vga_show_mem_stats(void) {
|
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_puts("=== ClaudeOS Memory Statistics ===\n");
|
||||||
|
|
||||||
vga_set_color(VGA_WHITE, VGA_BLACK);
|
vga_set_color(VGA_WHITE, VGA_BLUE);
|
||||||
vga_puts(" Total RAM: ");
|
vga_puts(" Total RAM: ");
|
||||||
vga_put_dec(mem_kb);
|
vga_put_dec(mem_kb);
|
||||||
vga_puts(" KiB (");
|
vga_puts(" KiB (");
|
||||||
@@ -194,28 +369,88 @@ void vga_show_mem_stats(void) {
|
|||||||
vga_put_dec(kernel_size / 1024);
|
vga_put_dec(kernel_size / 1024);
|
||||||
vga_puts(" KiB\n");
|
vga_puts(" KiB\n");
|
||||||
|
|
||||||
vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK);
|
vga_set_color(VGA_LIGHT_CYAN, VGA_BLUE);
|
||||||
vga_puts("==================================\n");
|
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) {
|
static driver_probe_result_t vga_probe(void) {
|
||||||
return DRIVER_PROBE_OK;
|
return DRIVER_PROBE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int vga_init(void) {
|
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_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_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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user