]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
pty: add minimal /dev/ptmx and /dev/pts/0
authorTulio A M Mendes <[email protected]>
Mon, 9 Feb 2026 23:41:42 +0000 (20:41 -0300)
committerTulio A M Mendes <[email protected]>
Mon, 9 Feb 2026 23:41:42 +0000 (20:41 -0300)
include/pty.h [new file with mode: 0644]
src/kernel/devfs.c
src/kernel/init.c
src/kernel/pty.c [new file with mode: 0644]
src/kernel/syscall.c
user/init.c

diff --git a/include/pty.h b/include/pty.h
new file mode 100644 (file)
index 0000000..cde5919
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef PTY_H
+#define PTY_H
+
+#include <stdint.h>
+
+void pty_init(void);
+
+int pty_master_read_kbuf(void* kbuf, uint32_t len);
+int pty_master_write_kbuf(const void* kbuf, uint32_t len);
+
+int pty_slave_read_kbuf(void* kbuf, uint32_t len);
+int pty_slave_write_kbuf(const void* kbuf, uint32_t len);
+
+int pty_master_can_read(void);
+int pty_master_can_write(void);
+
+int pty_slave_can_read(void);
+int pty_slave_can_write(void);
+
+int pty_slave_ioctl(uint32_t cmd, void* user_arg);
+
+#endif
index e7b356bff831fa097b0c3d0d8928f2235e97cef5..91d992d70fded887c9463f89903bd6903c9e4d22 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "errno.h"
 #include "tty.h"
+#include "pty.h"
 #include "utils.h"
 
 struct devfs_root {
@@ -11,6 +12,9 @@ struct devfs_root {
 static struct devfs_root g_dev_root;
 static fs_node_t g_dev_null;
 static fs_node_t g_dev_tty;
+static fs_node_t g_dev_ptmx;
+static fs_node_t g_dev_pts_dir;
+static fs_node_t g_dev_pts0;
 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) {
@@ -44,12 +48,53 @@ static uint32_t dev_tty_write(fs_node_t* node, uint32_t offset, uint32_t size, c
     return (uint32_t)rc;
 }
 
+static uint32_t dev_ptmx_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    (void)node;
+    (void)offset;
+    int rc = pty_master_read_kbuf(buffer, size);
+    if (rc < 0) return 0;
+    return (uint32_t)rc;
+}
+
+static uint32_t dev_ptmx_write(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
+    (void)node;
+    (void)offset;
+    int rc = pty_master_write_kbuf(buffer, size);
+    if (rc < 0) return 0;
+    return (uint32_t)rc;
+}
+
+static uint32_t dev_pts0_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    (void)node;
+    (void)offset;
+    int rc = pty_slave_read_kbuf(buffer, size);
+    if (rc < 0) return 0;
+    return (uint32_t)rc;
+}
+
+static uint32_t dev_pts0_write(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
+    (void)node;
+    (void)offset;
+    int rc = pty_slave_write_kbuf(buffer, size);
+    if (rc < 0) return 0;
+    return (uint32_t)rc;
+}
+
 static struct fs_node* devfs_finddir_impl(struct fs_node* node, const 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;
+    if (strcmp(name, "ptmx") == 0) return &g_dev_ptmx;
+    if (strcmp(name, "pts") == 0) return &g_dev_pts_dir;
+    return 0;
+}
+
+static struct fs_node* devfs_pts_finddir_impl(struct fs_node* node, const char* name) {
+    (void)node;
+    if (!name || name[0] == 0) return 0;
+    if (strcmp(name, "0") == 0) return &g_dev_pts0;
     return 0;
 }
 
@@ -89,6 +134,39 @@ static void devfs_init_once(void) {
     g_dev_tty.open = 0;
     g_dev_tty.close = 0;
     g_dev_tty.finddir = 0;
+
+    memset(&g_dev_ptmx, 0, sizeof(g_dev_ptmx));
+    strcpy(g_dev_ptmx.name, "ptmx");
+    g_dev_ptmx.flags = FS_CHARDEVICE;
+    g_dev_ptmx.inode = 4;
+    g_dev_ptmx.length = 0;
+    g_dev_ptmx.read = &dev_ptmx_read;
+    g_dev_ptmx.write = &dev_ptmx_write;
+    g_dev_ptmx.open = 0;
+    g_dev_ptmx.close = 0;
+    g_dev_ptmx.finddir = 0;
+
+    memset(&g_dev_pts_dir, 0, sizeof(g_dev_pts_dir));
+    strcpy(g_dev_pts_dir.name, "pts");
+    g_dev_pts_dir.flags = FS_DIRECTORY;
+    g_dev_pts_dir.inode = 5;
+    g_dev_pts_dir.length = 0;
+    g_dev_pts_dir.read = 0;
+    g_dev_pts_dir.write = 0;
+    g_dev_pts_dir.open = 0;
+    g_dev_pts_dir.close = 0;
+    g_dev_pts_dir.finddir = &devfs_pts_finddir_impl;
+
+    memset(&g_dev_pts0, 0, sizeof(g_dev_pts0));
+    strcpy(g_dev_pts0.name, "0");
+    g_dev_pts0.flags = FS_CHARDEVICE;
+    g_dev_pts0.inode = 6;
+    g_dev_pts0.length = 0;
+    g_dev_pts0.read = &dev_pts0_read;
+    g_dev_pts0.write = &dev_pts0_write;
+    g_dev_pts0.open = 0;
+    g_dev_pts0.close = 0;
+    g_dev_pts0.finddir = 0;
 }
 
 fs_node_t* devfs_create_root(void) {
index c6a7dc37de5b14e770fd728f0c0636d6e5219364..601d859cfe2527cdc70fbae34ec5886ef7b9d1fc 100644 (file)
@@ -8,6 +8,7 @@
 #include "tmpfs.h"
 #include "devfs.h"
 #include "tty.h"
+#include "pty.h"
 #include "persistfs.h"
 #include "uart_console.h"
 
@@ -64,6 +65,7 @@ int init_start(const struct boot_info* bi) {
     }
 
     tty_init();
+    pty_init();
 
     fs_node_t* dev = devfs_create_root();
     if (dev) {
diff --git a/src/kernel/pty.c b/src/kernel/pty.c
new file mode 100644 (file)
index 0000000..78f2f29
--- /dev/null
@@ -0,0 +1,300 @@
+#include "pty.h"
+
+#include "errno.h"
+#include "process.h"
+#include "spinlock.h"
+#include "uaccess.h"
+
+#include "hal/cpu.h"
+
+#include <stddef.h>
+
+#define PTY_BUF_CAP 1024
+#define PTY_WAITQ_MAX 16
+
+static spinlock_t pty_lock = {0};
+
+static uint8_t m2s_buf[PTY_BUF_CAP];
+static uint32_t m2s_head = 0;
+static uint32_t m2s_tail = 0;
+
+static uint8_t s2m_buf[PTY_BUF_CAP];
+static uint32_t s2m_head = 0;
+static uint32_t s2m_tail = 0;
+
+static struct process* m2s_waitq[PTY_WAITQ_MAX];
+static uint32_t m2s_wq_head = 0;
+static uint32_t m2s_wq_tail = 0;
+
+static struct process* s2m_waitq[PTY_WAITQ_MAX];
+static uint32_t s2m_wq_head = 0;
+static uint32_t s2m_wq_tail = 0;
+
+static uint32_t pty_session_id = 0;
+static uint32_t pty_fg_pgrp = 0;
+
+enum {
+    SIGTTIN = 21,
+    SIGTTOU = 22,
+};
+
+enum {
+    TTY_TIOCGPGRP = 0x540F,
+    TTY_TIOCSPGRP = 0x5410,
+};
+
+static uint32_t rb_count(uint32_t head, uint32_t tail) {
+    if (head >= tail) return head - tail;
+    return (PTY_BUF_CAP - tail) + head;
+}
+
+static uint32_t rb_free(uint32_t head, uint32_t tail) {
+    return (PTY_BUF_CAP - 1U) - rb_count(head, tail);
+}
+
+static void rb_push(uint8_t* buf, uint32_t* head, uint32_t* tail, uint8_t c) {
+    uint32_t next = (*head + 1U) % PTY_BUF_CAP;
+    if (next == *tail) {
+        *tail = (*tail + 1U) % PTY_BUF_CAP;
+    }
+    buf[*head] = c;
+    *head = next;
+}
+
+static int rb_pop(uint8_t* buf, uint32_t* head, uint32_t* tail, uint8_t* out) {
+    if (*head == *tail) return 0;
+    *out = buf[*tail];
+    *tail = (*tail + 1U) % PTY_BUF_CAP;
+    return 1;
+}
+
+static int waitq_push(struct process** q, uint32_t* head, uint32_t* tail, struct process* p) {
+    uint32_t next = (*head + 1U) % PTY_WAITQ_MAX;
+    if (next == *tail) return -1;
+    q[*head] = p;
+    *head = next;
+    return 0;
+}
+
+static struct process* waitq_pop(struct process** q, uint32_t* head, uint32_t* tail) {
+    if (*head == *tail) return NULL;
+    struct process* p = q[*tail];
+    *tail = (*tail + 1U) % PTY_WAITQ_MAX;
+    return p;
+}
+
+static void waitq_wake_one(struct process** q, uint32_t* head, uint32_t* tail) {
+    struct process* p = waitq_pop(q, head, tail);
+    if (p && p->state == PROCESS_BLOCKED) {
+        p->state = PROCESS_READY;
+    }
+}
+
+void pty_init(void) {
+    spinlock_init(&pty_lock);
+    m2s_head = m2s_tail = 0;
+    s2m_head = s2m_tail = 0;
+    m2s_wq_head = m2s_wq_tail = 0;
+    s2m_wq_head = s2m_wq_tail = 0;
+    pty_session_id = 0;
+    pty_fg_pgrp = 0;
+}
+
+static int pty_jobctl_write_check(void) {
+    if (current_process && pty_session_id != 0 && current_process->session_id == pty_session_id &&
+        pty_fg_pgrp != 0 && current_process->pgrp_id != pty_fg_pgrp) {
+        (void)process_kill(current_process->pid, SIGTTOU);
+        return -EINTR;
+    }
+    return 0;
+}
+
+static int pty_jobctl_read_check(void) {
+    if (!current_process) return -ECHILD;
+    if (pty_session_id != 0 && current_process->session_id == pty_session_id &&
+        pty_fg_pgrp != 0 && current_process->pgrp_id != pty_fg_pgrp) {
+        (void)process_kill(current_process->pid, SIGTTIN);
+        return -EINTR;
+    }
+    return 0;
+}
+
+int pty_master_can_read(void) {
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    int ready = (rb_count(s2m_head, s2m_tail) != 0U) ? 1 : 0;
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return ready;
+}
+
+int pty_master_can_write(void) {
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    int ready = (rb_free(m2s_head, m2s_tail) != 0U) ? 1 : 0;
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return ready;
+}
+
+int pty_slave_can_read(void) {
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    int ready = (rb_count(m2s_head, m2s_tail) != 0U) ? 1 : 0;
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return ready;
+}
+
+int pty_slave_can_write(void) {
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    int ready = (rb_free(s2m_head, s2m_tail) != 0U) ? 1 : 0;
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return ready;
+}
+
+int pty_master_read_kbuf(void* kbuf, uint32_t len) {
+    if (!kbuf) return -EFAULT;
+    if (len > 1024 * 1024) return -EINVAL;
+
+    int jc = pty_jobctl_read_check();
+    if (jc < 0) return jc;
+
+    while (1) {
+        uintptr_t flags = spin_lock_irqsave(&pty_lock);
+        uint32_t avail = rb_count(s2m_head, s2m_tail);
+        if (avail != 0U) {
+            uint32_t to_read = len;
+            if (to_read > avail) to_read = avail;
+            for (uint32_t i = 0; i < to_read; i++) {
+                uint8_t c = 0;
+                (void)rb_pop(s2m_buf, &s2m_head, &s2m_tail, &c);
+                ((uint8_t*)kbuf)[i] = c;
+            }
+            spin_unlock_irqrestore(&pty_lock, flags);
+            return (int)to_read;
+        }
+
+        if (current_process) {
+            if (waitq_push(s2m_waitq, &s2m_wq_head, &s2m_wq_tail, current_process) == 0) {
+                current_process->state = PROCESS_BLOCKED;
+            }
+        }
+
+        spin_unlock_irqrestore(&pty_lock, flags);
+        hal_cpu_enable_interrupts();
+        schedule();
+    }
+}
+
+int pty_master_write_kbuf(const void* kbuf, uint32_t len) {
+    if (!kbuf) return -EFAULT;
+    if (len > 1024 * 1024) return -EINVAL;
+
+    int jc = pty_jobctl_write_check();
+    if (jc < 0) return jc;
+
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    uint32_t free = rb_free(m2s_head, m2s_tail);
+    uint32_t to_write = len;
+    if (to_write > free) to_write = free;
+
+    for (uint32_t i = 0; i < to_write; i++) {
+        rb_push(m2s_buf, &m2s_head, &m2s_tail, ((const uint8_t*)kbuf)[i]);
+    }
+
+    if (to_write) {
+        waitq_wake_one(m2s_waitq, &m2s_wq_head, &m2s_wq_tail);
+    }
+
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return (int)to_write;
+}
+
+int pty_slave_read_kbuf(void* kbuf, uint32_t len) {
+    if (!kbuf) return -EFAULT;
+    if (len > 1024 * 1024) return -EINVAL;
+
+    int jc = pty_jobctl_read_check();
+    if (jc < 0) return jc;
+
+    while (1) {
+        uintptr_t flags = spin_lock_irqsave(&pty_lock);
+        uint32_t avail = rb_count(m2s_head, m2s_tail);
+        if (avail != 0U) {
+            uint32_t to_read = len;
+            if (to_read > avail) to_read = avail;
+            for (uint32_t i = 0; i < to_read; i++) {
+                uint8_t c = 0;
+                (void)rb_pop(m2s_buf, &m2s_head, &m2s_tail, &c);
+                ((uint8_t*)kbuf)[i] = c;
+            }
+            spin_unlock_irqrestore(&pty_lock, flags);
+            return (int)to_read;
+        }
+
+        if (current_process) {
+            if (waitq_push(m2s_waitq, &m2s_wq_head, &m2s_wq_tail, current_process) == 0) {
+                current_process->state = PROCESS_BLOCKED;
+            }
+        }
+
+        spin_unlock_irqrestore(&pty_lock, flags);
+        hal_cpu_enable_interrupts();
+        schedule();
+    }
+}
+
+int pty_slave_write_kbuf(const void* kbuf, uint32_t len) {
+    if (!kbuf) return -EFAULT;
+    if (len > 1024 * 1024) return -EINVAL;
+
+    int jc = pty_jobctl_write_check();
+    if (jc < 0) return jc;
+
+    uintptr_t flags = spin_lock_irqsave(&pty_lock);
+    uint32_t free = rb_free(s2m_head, s2m_tail);
+    uint32_t to_write = len;
+    if (to_write > free) to_write = free;
+
+    for (uint32_t i = 0; i < to_write; i++) {
+        rb_push(s2m_buf, &s2m_head, &s2m_tail, ((const uint8_t*)kbuf)[i]);
+    }
+
+    if (to_write) {
+        waitq_wake_one(s2m_waitq, &s2m_wq_head, &s2m_wq_tail);
+    }
+
+    spin_unlock_irqrestore(&pty_lock, flags);
+    return (int)to_write;
+}
+
+int pty_slave_ioctl(uint32_t cmd, void* user_arg) {
+    if (!user_arg) return -EFAULT;
+
+    if (current_process && pty_session_id == 0 && current_process->session_id != 0) {
+        pty_session_id = current_process->session_id;
+        pty_fg_pgrp = current_process->pgrp_id;
+    }
+
+    if (cmd == TTY_TIOCGPGRP) {
+        if (user_range_ok(user_arg, sizeof(int)) == 0) return -EFAULT;
+        int fg = (int)pty_fg_pgrp;
+        if (copy_to_user(user_arg, &fg, sizeof(fg)) < 0) return -EFAULT;
+        return 0;
+    }
+
+    if (cmd == TTY_TIOCSPGRP) {
+        if (user_range_ok(user_arg, sizeof(int)) == 0) return -EFAULT;
+        int fg = 0;
+        if (copy_from_user(&fg, user_arg, sizeof(fg)) < 0) return -EFAULT;
+        if (!current_process) return -EINVAL;
+
+        if (pty_session_id == 0) {
+            if (fg != 0) return -EPERM;
+            pty_fg_pgrp = 0;
+            return 0;
+        }
+
+        if (current_process->session_id != pty_session_id) return -EPERM;
+        if (fg < 0) return -EINVAL;
+        pty_fg_pgrp = (uint32_t)fg;
+        return 0;
+    }
+
+    return -EINVAL;
+}
index 1e42f847f676e235f5f77cd852b78cc83f331545..8af6c2eb1363abd4031318b4deec0b26094d9f08 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "heap.h"
 #include "tty.h"
+#include "pty.h"
 
 #include "errno.h"
 #include "elf.h"
@@ -236,6 +237,12 @@ static int poll_wait_kfds(struct pollfd* kfds, uint32_t nfds, int32_t timeout) {
                 } else if (n->inode == 3) {
                     if ((kfds[i].events & POLLIN) && tty_can_read()) kfds[i].revents |= POLLIN;
                     if ((kfds[i].events & POLLOUT) && tty_can_write()) kfds[i].revents |= POLLOUT;
+                } else if (n->inode == 4) {
+                    if ((kfds[i].events & POLLIN) && pty_master_can_read()) kfds[i].revents |= POLLIN;
+                    if ((kfds[i].events & POLLOUT) && pty_master_can_write()) kfds[i].revents |= POLLOUT;
+                } else if (n->inode == 6) {
+                    if ((kfds[i].events & POLLIN) && pty_slave_can_read()) kfds[i].revents |= POLLIN;
+                    if ((kfds[i].events & POLLOUT) && pty_slave_can_write()) kfds[i].revents |= POLLOUT;
                 }
             } else {
                 // Regular files are always readable/writable (best-effort).
@@ -870,9 +877,9 @@ static int syscall_ioctl_impl(int fd, uint32_t cmd, void* user_arg) {
 
     fs_node_t* n = f->node;
     if (n->flags != FS_CHARDEVICE) return -ENOTTY;
-    if (n->inode != 3) return -ENOTTY;
-
-    return tty_ioctl(cmd, user_arg);
+    if (n->inode == 3) return tty_ioctl(cmd, user_arg);
+    if (n->inode == 6) return pty_slave_ioctl(cmd, user_arg);
+    return -ENOTTY;
 }
 
 static int syscall_setsid_impl(void) {
index 4095acb7cae46d21e13aa6062660276a153f9a28..070c3ae4ea78e7d5e0fe098764be9f11f448ebf3 100644 (file)
@@ -175,6 +175,15 @@ static void write_hex32(uint32_t v) {
     (void)sys_write(1, b, 8);
 }
 
+static int memeq(const void* a, const void* b, uint32_t n) {
+    const uint8_t* pa = (const uint8_t*)a;
+    const uint8_t* pb = (const uint8_t*)b;
+    for (uint32_t i = 0; i < n; i++) {
+        if (pa[i] != pb[i]) return 0;
+    }
+    return 1;
+}
+
 static int sys_sigaction2(int sig, const struct sigaction* act, struct sigaction* oldact) {
     int ret;
     __asm__ volatile(
@@ -1033,6 +1042,70 @@ void _start(void) {
                   (uint32_t)(sizeof("[init] poll(/dev/null) OK\n") - 1));
     }
 
+    {
+        int mfd = sys_open("/dev/ptmx", 0);
+        int sfd = sys_open("/dev/pts/0", 0);
+        if (mfd < 0 || sfd < 0) {
+            sys_write(1, "[init] pty open failed\n",
+                      (uint32_t)(sizeof("[init] pty open failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char m2s[] = "m2s";
+        if (sys_write(mfd, m2s, (uint32_t)(sizeof(m2s) - 1)) != (int)(sizeof(m2s) - 1)) {
+            sys_write(1, "[init] pty write master failed\n",
+                      (uint32_t)(sizeof("[init] pty write master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        struct pollfd p;
+        p.fd = sfd;
+        p.events = POLLIN;
+        p.revents = 0;
+        int rc = sys_poll(&p, 1, 50);
+        if (rc != 1 || (p.revents & POLLIN) == 0) {
+            sys_write(1, "[init] pty poll slave failed\n",
+                      (uint32_t)(sizeof("[init] pty poll slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        char buf[8];
+        int rd = sys_read(sfd, buf, (uint32_t)(sizeof(m2s) - 1));
+        if (rd != (int)(sizeof(m2s) - 1) || !memeq(buf, m2s, (uint32_t)(sizeof(m2s) - 1))) {
+            sys_write(1, "[init] pty read slave failed\n",
+                      (uint32_t)(sizeof("[init] pty read slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        static const char s2m[] = "s2m";
+        if (sys_write(sfd, s2m, (uint32_t)(sizeof(s2m) - 1)) != (int)(sizeof(s2m) - 1)) {
+            sys_write(1, "[init] pty write slave failed\n",
+                      (uint32_t)(sizeof("[init] pty write slave failed\n") - 1));
+            sys_exit(1);
+        }
+
+        p.fd = mfd;
+        p.events = POLLIN;
+        p.revents = 0;
+        rc = sys_poll(&p, 1, 50);
+        if (rc != 1 || (p.revents & POLLIN) == 0) {
+            sys_write(1, "[init] pty poll master failed\n",
+                      (uint32_t)(sizeof("[init] pty poll master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        rd = sys_read(mfd, buf, (uint32_t)(sizeof(s2m) - 1));
+        if (rd != (int)(sizeof(s2m) - 1) || !memeq(buf, s2m, (uint32_t)(sizeof(s2m) - 1))) {
+            sys_write(1, "[init] pty read master failed\n",
+                      (uint32_t)(sizeof("[init] pty read master failed\n") - 1));
+            sys_exit(1);
+        }
+
+        (void)sys_close(mfd);
+        (void)sys_close(sfd);
+        sys_write(1, "[init] pty OK\n", (uint32_t)(sizeof("[init] pty OK\n") - 1));
+    }
+
     {
         sys_write(1, "[init] setsid test: before fork\n",
                   (uint32_t)(sizeof("[init] setsid test: before fork\n") - 1));