Add a minimal devfs mounted at /dev providing /dev/null and /dev/tty char devices. Implement tty_read_kbuf/tty_write_kbuf to support VFS-backed tty IO and allow open/read/write through FS_CHARDEVICE nodes. Add userland smoke tests.
--- /dev/null
+#ifndef DEVFS_H
+#define DEVFS_H
+
+#include "fs.h"
+
+fs_node_t* devfs_create_root(void);
+
+#endif
int tty_read(void* user_buf, uint32_t len);
int tty_write(const void* user_buf, uint32_t len);
+int tty_read_kbuf(void* kbuf, uint32_t len);
+int tty_write_kbuf(const void* kbuf, uint32_t len);
+
void tty_input_char(char c);
#endif
--- /dev/null
+#include "devfs.h"
+
+#include "errno.h"
+#include "tty.h"
+#include "utils.h"
+
+struct devfs_root {
+ fs_node_t vfs;
+};
+
+static struct devfs_root g_dev_root;
+static fs_node_t g_dev_null;
+static fs_node_t g_dev_tty;
+static uint32_t g_devfs_inited = 0;
+
+static uint32_t dev_null_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+ (void)node;
+ (void)offset;
+ (void)size;
+ (void)buffer;
+ return 0;
+}
+
+static uint32_t dev_null_write(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+ (void)node;
+ (void)offset;
+ (void)buffer;
+ return size;
+}
+
+static uint32_t dev_tty_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+ (void)node;
+ (void)offset;
+ int rc = tty_read_kbuf(buffer, size);
+ if (rc < 0) return 0;
+ return (uint32_t)rc;
+}
+
+static uint32_t dev_tty_write(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+ (void)node;
+ (void)offset;
+ int rc = tty_write_kbuf(buffer, size);
+ if (rc < 0) return 0;
+ return (uint32_t)rc;
+}
+
+static struct fs_node* devfs_finddir_impl(struct fs_node* node, char* name) {
+ (void)node;
+ if (!name || name[0] == 0) return 0;
+
+ if (strcmp(name, "null") == 0) return &g_dev_null;
+ if (strcmp(name, "tty") == 0) return &g_dev_tty;
+ return 0;
+}
+
+static void devfs_init_once(void) {
+ if (g_devfs_inited) return;
+ g_devfs_inited = 1;
+
+ memset(&g_dev_root, 0, sizeof(g_dev_root));
+ strcpy(g_dev_root.vfs.name, "dev");
+ g_dev_root.vfs.flags = FS_DIRECTORY;
+ g_dev_root.vfs.inode = 1;
+ g_dev_root.vfs.length = 0;
+ g_dev_root.vfs.read = 0;
+ g_dev_root.vfs.write = 0;
+ g_dev_root.vfs.open = 0;
+ g_dev_root.vfs.close = 0;
+ g_dev_root.vfs.finddir = &devfs_finddir_impl;
+
+ memset(&g_dev_null, 0, sizeof(g_dev_null));
+ strcpy(g_dev_null.name, "null");
+ g_dev_null.flags = FS_CHARDEVICE;
+ g_dev_null.inode = 2;
+ g_dev_null.length = 0;
+ g_dev_null.read = &dev_null_read;
+ g_dev_null.write = &dev_null_write;
+ g_dev_null.open = 0;
+ g_dev_null.close = 0;
+ g_dev_null.finddir = 0;
+
+ memset(&g_dev_tty, 0, sizeof(g_dev_tty));
+ strcpy(g_dev_tty.name, "tty");
+ g_dev_tty.flags = FS_CHARDEVICE;
+ g_dev_tty.inode = 3;
+ g_dev_tty.length = 0;
+ g_dev_tty.read = &dev_tty_read;
+ g_dev_tty.write = &dev_tty_write;
+ g_dev_tty.open = 0;
+ g_dev_tty.close = 0;
+ g_dev_tty.finddir = 0;
+}
+
+fs_node_t* devfs_create_root(void) {
+ devfs_init_once();
+ return &g_dev_root.vfs;
+}
#include "initrd.h"
#include "overlayfs.h"
#include "tmpfs.h"
+#include "devfs.h"
#include "tty.h"
#include "uart_console.h"
tty_init();
+ fs_node_t* dev = devfs_create_root();
+ if (dev) {
+ (void)vfs_mount("/dev", dev);
+ }
+
int user_ret = arch_platform_start_userspace(bi);
if (bi && cmdline_has_token(bi->cmdline, "ring3")) {
struct file* f = fd_get(fd);
if (!f || !f->node) return -EBADF;
+ if (f->node->flags == FS_CHARDEVICE) {
+ uint8_t kbuf[256];
+ uint32_t total = 0;
+ while (total < len) {
+ uint32_t chunk = len - total;
+ if (chunk > sizeof(kbuf)) chunk = (uint32_t)sizeof(kbuf);
+
+ uint32_t rd = vfs_read(f->node, 0, chunk, kbuf);
+ if (rd == 0) break;
+
+ if (copy_to_user((uint8_t*)user_buf + total, kbuf, rd) < 0) {
+ return -EFAULT;
+ }
+
+ total += rd;
+ if (rd < chunk) break;
+ }
+ return (int)total;
+ }
+
uint8_t kbuf[256];
uint32_t total = 0;
while (total < len) {
return -EFAULT;
}
- f->offset += rd;
+ if (f->node->flags == FS_FILE) {
+ f->offset += rd;
+ }
total += rd;
if (rd < chunk) break;
struct file* f = fd_get(fd);
if (!f || !f->node) return -EBADF;
- if (f->node->flags != FS_FILE) return -ESPIPE;
+ if (f->node->flags != FS_FILE && f->node->flags != FS_CHARDEVICE) return -ESPIPE;
if (!f->node->write) return -ESPIPE;
uint8_t kbuf[256];
return -EFAULT;
}
- uint32_t wr = vfs_write(f->node, f->offset, chunk, kbuf);
+ uint32_t wr = vfs_write(f->node, (f->node->flags == FS_FILE) ? f->offset : 0, chunk, kbuf);
if (wr == 0) break;
- f->offset += wr;
+ if (f->node->flags == FS_FILE) f->offset += wr;
total += wr;
if (wr < chunk) break;
}
return canon_head == canon_tail;
}
+static uint32_t canon_count(void);
+static int waitq_push(struct process* p);
+
+int tty_write_kbuf(const void* kbuf, uint32_t len) {
+ if (!kbuf) return -EFAULT;
+ if (len > 1024 * 1024) return -EINVAL;
+
+ const char* p = (const char*)kbuf;
+ for (uint32_t i = 0; i < len; i++) {
+ uart_put_char(p[i]);
+ }
+ return (int)len;
+}
+
+int tty_read_kbuf(void* kbuf, uint32_t len) {
+ if (!kbuf) return -EFAULT;
+ if (len > 1024 * 1024) return -EINVAL;
+ if (!current_process) return -ECHILD;
+
+ while (1) {
+ uintptr_t flags = spin_lock_irqsave(&tty_lock);
+
+ if (!canon_empty()) {
+ uint32_t avail = canon_count();
+ uint32_t to_read = len;
+ if (to_read > avail) to_read = avail;
+
+ uint32_t total = 0;
+ while (total < to_read) {
+ uint32_t chunk = to_read - total;
+ if (chunk > 256U) chunk = 256U;
+
+ for (uint32_t i = 0; i < chunk; i++) {
+ ((char*)kbuf)[total + i] = canon_buf[canon_tail];
+ canon_tail = (canon_tail + 1U) % TTY_CANON_BUF;
+ }
+ total += chunk;
+ }
+
+ spin_unlock_irqrestore(&tty_lock, flags);
+ return (int)to_read;
+ }
+
+ if (waitq_push(current_process) == 0) {
+ current_process->state = PROCESS_BLOCKED;
+ }
+
+ spin_unlock_irqrestore(&tty_lock, flags);
+
+ hal_cpu_enable_interrupts();
+ schedule();
+ }
+}
+
static uint32_t canon_count(void) {
if (canon_head >= canon_tail) return canon_head - canon_tail;
return (TTY_CANON_BUF - canon_tail) + canon_head;
sys_write(1, "[init] tmpfs/mount OK\n", (uint32_t)(sizeof("[init] tmpfs/mount OK\n") - 1));
+ {
+ int fd = sys_open("/dev/null", 0);
+ if (fd < 0) {
+ sys_write(1, "[init] /dev/null open failed\n",
+ (uint32_t)(sizeof("[init] /dev/null open failed\n") - 1));
+ sys_exit(1);
+ }
+ static const char z[] = "discard me";
+ int wr = sys_write(fd, z, (uint32_t)(sizeof(z) - 1));
+ if (wr != (int)(sizeof(z) - 1)) {
+ sys_write(1, "[init] /dev/null write failed\n",
+ (uint32_t)(sizeof("[init] /dev/null write failed\n") - 1));
+ sys_exit(1);
+ }
+ (void)sys_close(fd);
+ sys_write(1, "[init] /dev/null OK\n", (uint32_t)(sizeof("[init] /dev/null OK\n") - 1));
+ }
+
+ {
+ int fd = sys_open("/dev/tty", 0);
+ if (fd < 0) {
+ sys_write(1, "[init] /dev/tty open failed\n",
+ (uint32_t)(sizeof("[init] /dev/tty open failed\n") - 1));
+ sys_exit(1);
+ }
+ static const char m[] = "[init] /dev/tty write OK\n";
+ int wr = sys_write(fd, m, (uint32_t)(sizeof(m) - 1));
+ if (wr != (int)(sizeof(m) - 1)) {
+ sys_write(1, "[init] /dev/tty write failed\n",
+ (uint32_t)(sizeof("[init] /dev/tty write failed\n") - 1));
+ sys_exit(1);
+ }
+ (void)sys_close(fd);
+ }
+
enum { NCHILD = 100 };
int children[NCHILD];
for (int i = 0; i < NCHILD; i++) {