From c556f7db546ad36bb6cb72801dfbe0eaba2b7236 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Sun, 8 Feb 2026 00:31:57 -0300 Subject: [PATCH] posix: poll syscall (pipes + devfs tty/null) Add a minimal poll() syscall (POLLIN/POLLOUT) with timeout semantics (0 non-blocking, <0 blocking). Support pipe endpoints and devfs char devices (/dev/null,/dev/tty) and add userland smoke tests. --- include/syscall.h | 1 + include/tty.h | 3 ++ src/kernel/syscall.c | 107 +++++++++++++++++++++++++++++++++++++++++++ src/kernel/tty.c | 11 +++++ user/init.c | 84 +++++++++++++++++++++++++++++++++ 5 files changed, 206 insertions(+) diff --git a/include/syscall.h b/include/syscall.h index 47eda77..6c22752 100644 --- a/include/syscall.h +++ b/include/syscall.h @@ -26,6 +26,7 @@ enum { SYSCALL_EXECVE = 15, SYSCALL_FORK = 16, SYSCALL_GETPPID = 17, + SYSCALL_POLL = 18, }; #endif diff --git a/include/tty.h b/include/tty.h index 4d39686..31f1370 100644 --- a/include/tty.h +++ b/include/tty.h @@ -12,6 +12,9 @@ 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); +int tty_can_read(void); +int tty_can_write(void); + void tty_input_char(char c); #endif diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 510c7ab..b85be67 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -19,6 +19,20 @@ static int fd_alloc(struct file* f); static int fd_close(int fd); +static struct file* fd_get(int fd); + +struct pollfd { + int fd; + int16_t events; + int16_t revents; +}; + +enum { + POLLIN = 0x0001, + POLLOUT = 0x0004, + POLLERR = 0x0008, + POLLHUP = 0x0010, +}; static int execve_copy_user_str(char* out, size_t out_sz, const char* user_s) { if (!out || out_sz == 0 || !user_s) return -EFAULT; @@ -84,6 +98,91 @@ struct pipe_node { uint32_t is_read_end; }; +static int syscall_poll_impl(struct pollfd* user_fds, uint32_t nfds, int32_t timeout) { + if (!user_fds) return -EFAULT; + if (nfds > 64U) return -EINVAL; + if (user_range_ok(user_fds, sizeof(struct pollfd) * (size_t)nfds) == 0) return -EFAULT; + + // timeout semantics (minimal): + // - timeout == 0 : non-blocking + // - timeout < 0 : block forever + // - timeout > 0 : treated as "ticks" (best-effort) + extern uint32_t get_tick_count(void); + uint32_t start_tick = get_tick_count(); + + struct pollfd kfds[64]; + + for (;;) { + if (copy_from_user(kfds, user_fds, sizeof(struct pollfd) * (size_t)nfds) < 0) return -EFAULT; + + int ready = 0; + for (uint32_t i = 0; i < nfds; i++) { + kfds[i].revents = 0; + int fd = kfds[i].fd; + if (fd < 0) continue; + + struct file* f = fd_get(fd); + if (!f || !f->node) { + kfds[i].revents |= POLLERR; + continue; + } + + fs_node_t* n = f->node; + + // Pipes (identified by node name prefix). + if (n->name[0] == 'p' && n->name[1] == 'i' && n->name[2] == 'p' && n->name[3] == 'e' && n->name[4] == ':') { + struct pipe_node* pn = (struct pipe_node*)n; + struct pipe_state* ps = pn->ps; + if (!ps) { + kfds[i].revents |= POLLERR; + } else if (pn->is_read_end) { + if ((kfds[i].events & POLLIN) && (ps->count > 0 || ps->writers == 0)) { + kfds[i].revents |= POLLIN; + if (ps->writers == 0) kfds[i].revents |= POLLHUP; + } + } else { + if (ps->readers == 0) { + if (kfds[i].events & POLLOUT) kfds[i].revents |= POLLERR; + } else { + uint32_t free = ps->cap - ps->count; + if ((kfds[i].events & POLLOUT) && free > 0) { + kfds[i].revents |= POLLOUT; + } + } + } + } else if (n->flags == FS_CHARDEVICE) { + // devfs devices: inode 2=/dev/null, 3=/dev/tty + if (n->inode == 2) { + if (kfds[i].events & POLLIN) kfds[i].revents |= POLLIN | POLLHUP; + if (kfds[i].events & POLLOUT) kfds[i].revents |= POLLOUT; + } 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 { + // Regular files are always readable/writable (best-effort). + if (kfds[i].events & POLLIN) kfds[i].revents |= POLLIN; + if (kfds[i].events & POLLOUT) kfds[i].revents |= POLLOUT; + } + + if (kfds[i].revents) ready++; + } + + if (copy_to_user(user_fds, kfds, sizeof(struct pollfd) * (size_t)nfds) < 0) return -EFAULT; + + if (ready) return ready; + if (timeout == 0) return 0; + + if (timeout > 0) { + uint32_t now = get_tick_count(); + uint32_t elapsed = now - start_tick; + if (elapsed >= (uint32_t)timeout) return 0; + } + + process_sleep(1); + } +} + static uint32_t pipe_read(fs_node_t* n, uint32_t offset, uint32_t size, uint8_t* buffer) { (void)offset; struct pipe_node* pn = (struct pipe_node*)n; @@ -807,6 +906,14 @@ static void syscall_handler(struct registers* regs) { return; } + if (syscall_no == SYSCALL_POLL) { + struct pollfd* fds = (struct pollfd*)regs->ebx; + uint32_t nfds = regs->ecx; + int32_t timeout = (int32_t)regs->edx; + regs->eax = (uint32_t)syscall_poll_impl(fds, nfds, timeout); + return; + } + regs->eax = (uint32_t)-ENOSYS; } diff --git a/src/kernel/tty.c b/src/kernel/tty.c index 60189ec..d240392 100644 --- a/src/kernel/tty.c +++ b/src/kernel/tty.c @@ -89,6 +89,17 @@ static uint32_t canon_count(void) { return (TTY_CANON_BUF - canon_tail) + canon_head; } +int tty_can_read(void) { + uintptr_t flags = spin_lock_irqsave(&tty_lock); + int ready = canon_empty() ? 0 : 1; + spin_unlock_irqrestore(&tty_lock, flags); + return ready; +} + +int tty_can_write(void) { + return 1; +} + static void canon_push(char c) { uint32_t next = (canon_head + 1U) % TTY_CANON_BUF; if (next == canon_tail) { diff --git a/user/init.c b/user/init.c index 5fe3db9..defaead 100644 --- a/user/init.c +++ b/user/init.c @@ -18,6 +18,18 @@ enum { SYSCALL_EXECVE = 15, SYSCALL_FORK = 16, SYSCALL_GETPPID = 17, + SYSCALL_POLL = 18, +}; + +struct pollfd { + int fd; + int16_t events; + int16_t revents; +}; + +enum { + POLLIN = 0x0001, + POLLOUT = 0x0004, }; enum { @@ -51,6 +63,17 @@ static int sys_write(int fd, const void* buf, uint32_t len) { return ret; } +static int sys_poll(struct pollfd* fds, uint32_t nfds, int32_t timeout) { + int ret; + __asm__ volatile( + "int $0x80" + : "=a"(ret) + : "a"(SYSCALL_POLL), "b"(fds), "c"(nfds), "d"(timeout) + : "memory" + ); + return ret; +} + static int sys_getpid(void) { int ret; __asm__ volatile( @@ -444,6 +467,67 @@ void _start(void) { (void)sys_close(pfds[1]); sys_write(1, "[init] pipe OK\n", (uint32_t)(sizeof("[init] pipe OK\n") - 1)); + + { + int fds[2]; + if (sys_pipe(fds) < 0) { + sys_write(1, "[init] poll pipe setup failed\n", + (uint32_t)(sizeof("[init] poll pipe setup failed\n") - 1)); + sys_exit(1); + } + + struct pollfd p; + p.fd = fds[0]; + p.events = POLLIN; + p.revents = 0; + int rc = sys_poll(&p, 1, 0); + if (rc != 0) { + sys_write(1, "[init] poll(pipe) expected 0\n", + (uint32_t)(sizeof("[init] poll(pipe) expected 0\n") - 1)); + sys_exit(1); + } + + static const char a = 'A'; + if (sys_write(fds[1], &a, 1) != 1) { + sys_write(1, "[init] poll pipe write failed\n", + (uint32_t)(sizeof("[init] poll pipe write failed\n") - 1)); + sys_exit(1); + } + + p.revents = 0; + rc = sys_poll(&p, 1, 0); + if (rc != 1 || (p.revents & POLLIN) == 0) { + sys_write(1, "[init] poll(pipe) expected POLLIN\n", + (uint32_t)(sizeof("[init] poll(pipe) expected POLLIN\n") - 1)); + sys_exit(1); + } + + (void)sys_close(fds[0]); + (void)sys_close(fds[1]); + sys_write(1, "[init] poll(pipe) OK\n", (uint32_t)(sizeof("[init] poll(pipe) OK\n") - 1)); + } + + { + int fd = sys_open("/dev/null", 0); + if (fd < 0) { + sys_write(1, "[init] poll(/dev/null) open failed\n", + (uint32_t)(sizeof("[init] poll(/dev/null) open failed\n") - 1)); + sys_exit(1); + } + struct pollfd p; + p.fd = fd; + p.events = POLLOUT; + p.revents = 0; + int rc = sys_poll(&p, 1, 0); + if (rc != 1 || (p.revents & POLLOUT) == 0) { + sys_write(1, "[init] poll(/dev/null) expected POLLOUT\n", + (uint32_t)(sizeof("[init] poll(/dev/null) expected POLLOUT\n") - 1)); + sys_exit(1); + } + (void)sys_close(fd); + sys_write(1, "[init] poll(/dev/null) OK\n", + (uint32_t)(sizeof("[init] poll(/dev/null) OK\n") - 1)); + } } fd = sys_open("/tmp/hello.txt", 0); -- 2.43.0