From: Tulio A M Mendes Date: Mon, 9 Feb 2026 23:41:42 +0000 (-0300) Subject: pty: add minimal /dev/ptmx and /dev/pts/0 X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=149a4c5861894a1e6b5b7829e9b9c9df732ae612;p=AdrOS.git pty: add minimal /dev/ptmx and /dev/pts/0 --- diff --git a/include/pty.h b/include/pty.h new file mode 100644 index 0000000..cde5919 --- /dev/null +++ b/include/pty.h @@ -0,0 +1,22 @@ +#ifndef PTY_H +#define PTY_H + +#include + +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 diff --git a/src/kernel/devfs.c b/src/kernel/devfs.c index e7b356b..91d992d 100644 --- a/src/kernel/devfs.c +++ b/src/kernel/devfs.c @@ -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) { diff --git a/src/kernel/init.c b/src/kernel/init.c index c6a7dc3..601d859 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -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 index 0000000..78f2f29 --- /dev/null +++ b/src/kernel/pty.c @@ -0,0 +1,300 @@ +#include "pty.h" + +#include "errno.h" +#include "process.h" +#include "spinlock.h" +#include "uaccess.h" + +#include "hal/cpu.h" + +#include + +#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; +} diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 1e42f84..8af6c2e 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -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) { diff --git a/user/init.c b/user/init.c index 4095acb..070c3ae 100644 --- a/user/init.c +++ b/user/init.c @@ -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));