diff --git a/kernel-src/arch/x86-64/main.c b/kernel-src/arch/x86-64/main.c index b859a758..326390dd 100644 --- a/kernel-src/arch/x86-64/main.c +++ b/kernel-src/arch/x86-64/main.c @@ -36,6 +36,7 @@ #include #include #include +#include static cpu_t bsp_cpu; @@ -71,6 +72,7 @@ void kernel_entry() { ext2_init(); tty_init(); + pty_init(); netdev_init(); loopback_init(); diff --git a/kernel-src/include/kernel/pty.h b/kernel-src/include/kernel/pty.h new file mode 100644 index 00000000..531b23c3 --- /dev/null +++ b/kernel-src/include/kernel/pty.h @@ -0,0 +1,21 @@ +#ifndef _PTY_H +#define _PTY_H + +#include +#include +#include +#include + +typedef struct { + bool hangup; + tty_t *tty; + int minor; + vnode_t *mastervnode; + ringbuffer_t ringbuffer; + pollheader_t pollheader; + spinlock_t lock; +} pty_t; + +void pty_init(); + +#endif diff --git a/kernel-src/include/kernel/tty.h b/kernel-src/include/kernel/tty.h index b20021d0..9b8a0880 100644 --- a/kernel-src/include/kernel/tty.h +++ b/kernel-src/include/kernel/tty.h @@ -10,7 +10,8 @@ #define DEVICE_BUFFER_SIZE 512 #define READ_BUFFER_SIZE 4096 -typedef void (*ttydevicewritefn_t)(char *str, size_t size); +typedef size_t (*ttydevicewritefn_t)(void *internal, char *str, size_t size); +typedef void (*ttyinactivefn_t)(void *internal); typedef struct { pollheader_t pollheader; @@ -19,17 +20,19 @@ typedef struct { mutex_t readmutex; mutex_t writemutex; termios_t termios; + void *deviceinternal; char *devicebuffer; int devicepos; ttydevicewritefn_t writetodevice; + ttyinactivefn_t inactivedevice; int minor; winsize_t winsize; vnode_t *mastervnode; } tty_t; void tty_init(); -tty_t *tty_create(char *name, ttydevicewritefn_t fn); -void tty_destroy(char *name, tty_t *tty); +tty_t *tty_create(char *name, ttydevicewritefn_t writefn, ttyinactivefn_t inactivefn, void *internal); void tty_process(tty_t *tty, char c); +void tty_unregister(tty_t *tty); #endif diff --git a/kernel-src/io/console.c b/kernel-src/io/console.c index f056d599..83b9a219 100644 --- a/kernel-src/io/console.c +++ b/kernel-src/io/console.c @@ -14,12 +14,17 @@ static mutex_t writemutex; static thread_t *thread; static tty_t *tty; -void console_write(char *str, size_t count) { +size_t console_write(char *str, size_t count) { MUTEX_ACQUIRE(&writemutex, false); term_write(str, count); MUTEX_RELEASE(&writemutex); + return count; +} + +size_t console_ttywrite(void *internal, char *str, size_t count) { + return console_write(str, count); } void console_putc(char c) { @@ -109,7 +114,7 @@ void console_init() { __assert(thread); sched_queue(thread); - tty = tty_create("console", console_write); + tty = tty_create("console", console_ttywrite, NULL, NULL); __assert(tty); size_t x, y, fbx, fby; diff --git a/kernel-src/io/pty.c b/kernel-src/io/pty.c new file mode 100644 index 00000000..84b5e09c --- /dev/null +++ b/kernel-src/io/pty.c @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include + +#define PTY_BUFFER 4096 +#define PTY_MAX 1024 +#define PTMX_MINOR PTY_MAX +static spinlock_t listlock; +static pty_t *ptylist[PTY_MAX]; + +static inline pty_t *ptyget(int minor) { + if (minor >= PTY_MAX) + return NULL; + + return ptylist[minor]; +} + +static int allocateptyminor(pty_t *pty) { + bool intstatus = interrupt_set(false); + spinlock_acquire(&listlock); + + int i; + for (i = 0; i < PTY_MAX; ++i) { + if (ptylist[i] == NULL) { + ptylist[i] = pty; + break; + } + } + + spinlock_release(&listlock); + interrupt_set(intstatus); + return i; +} + +static void freeptyminor(int pty) { + bool intstatus = interrupt_set(false); + spinlock_acquire(&listlock); + + ptylist[pty] = NULL; + + spinlock_release(&listlock); + interrupt_set(intstatus); +} + +static pty_t *allocpty() { + pty_t *pty = alloc(sizeof(pty_t)); + if (pty == NULL) + return NULL; + + if (ringbuffer_init(&pty->ringbuffer, PTY_BUFFER)) { + free(pty); + return NULL; + } + + POLL_INITHEADER(&pty->pollheader); + SPINLOCK_INIT(pty->lock); + + return pty; +} + +static void freepty(pty_t *pty) { + ringbuffer_destroy(&pty->ringbuffer); + free(pty); +} + +static size_t writetopty(void *_pty, char *data, size_t size) { + pty_t *pty = _pty; + bool intstatus = interrupt_set(false); + spinlock_acquire(&pty->lock); + + size_t written = ringbuffer_write(&pty->ringbuffer, data, size); + poll_event(&pty->pollheader, POLLIN); + + spinlock_release(&pty->lock); + interrupt_set(intstatus); + + return written; +} + +static void inactivepty(void *_pty) { + pty_t *pty = _pty; + freeptyminor(pty->minor); + freepty(pty); +} + +static int isatty(int minor) { + return 0; +} + +static int open(int minor, vnode_t **vnode, int flags); + +static int close(int minor, int flags) { + pty_t *pty = ptyget(minor); + if (pty == NULL) + return ENODEV; + + // TODO do tty cleanup stuff like sending sighup + bool intstatus = interrupt_set(false); + spinlock_acquire(&pty->lock); + pty->hangup = true; + spinlock_release(&pty->lock); + interrupt_set(intstatus); + + // this should remove the implicit pty -> tty master vnode reference + // the master vnode of the pty will be unheld once the close exits + // the pty_t structure and minor number will be free once the tty calls the device inactive function, + // in case there are still processes with the pts open + tty_unregister(pty->tty); + + return 0; +} + +static int internalpoll(pty_t *pty, polldata_t *data, int events) { + int revents = 0; + + if (events & POLLOUT) + revents |= POLLOUT; + + if ((events & POLLIN) && RINGBUFFER_DATACOUNT(&pty->ringbuffer)) + revents |= POLLIN; + + if (revents == 0 && data) + poll_add(&pty->pollheader, data, events); + + return revents; +} + +static int read(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *readc) { + pty_t *pty = ptyget(minor); + if (pty == NULL) + return ENODEV; + + + for (;;) { + polldesc_t desc = {0}; + int e = poll_initdesc(&desc, 1); + if (e) + return e; + + bool intstatus = interrupt_set(false); + spinlock_acquire(&pty->lock); + + int revents = internalpoll(pty, &desc.data[0], POLLIN); + + if (revents) { + *readc = ringbuffer_read(&pty->ringbuffer, buffer, size); + + spinlock_release(&pty->lock); + interrupt_set(intstatus); + + poll_leave(&desc); + poll_destroydesc(&desc); + break; + } + + spinlock_release(&pty->lock); + interrupt_set(intstatus); + + if (flags & V_FFLAGS_NONBLOCKING) { + poll_leave(&desc); + poll_destroydesc(&desc); + return EAGAIN; + } + + e = poll_dowait(&desc, 0); + + poll_leave(&desc); + poll_destroydesc(&desc); + + if (e) + return e; + } + + return 0; +} + +static int poll(int minor, polldata_t *data, int events) { + pty_t *pty = ptyget(minor); + if (pty == NULL) + return POLLERR; + + bool intstatus = interrupt_set(false); + spinlock_acquire(&pty->lock); + + int revents = internalpoll(pty, data, events); + + spinlock_release(&pty->lock); + interrupt_set(intstatus); + + return revents; +} +#define TIOCGPTN 0x80045430 +static int ioctl(int minor, unsigned long request, void *_arg, int *result) { + pty_t *pty = ptyget(minor); + if (pty == NULL) + return ENODEV; + + switch (request) { + case TIOCGPTN: + { + int *arg = _arg; + *arg = pty->minor; + break; + } + default: + return ENOTTY; + } + + return 0; +} + +static int write(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *writec) { + pty_t *pty = ptyget(minor); + if (pty == NULL) + return ENODEV; + + char *str = buffer; + + for (int i = 0; i < size; ++i) + tty_process(pty->tty, str[i]); + + *writec = size; + return 0; +} + +static devops_t devops = { + .open = open, + .isatty = isatty, + .write = write, + .close = close, + .poll = poll, + .read = read, + .ioctl = ioctl +}; + +static int open(int oldminor, vnode_t **vnode, int flags) { + if (oldminor != PTMX_MINOR) + return EINVAL; + + pty_t *pty = allocpty(); + if (pty == NULL) + return ENOMEM; + + int newminor = allocateptyminor(pty); + if (newminor == PTMX_MINOR) { + freepty(pty); + return ENOSPC; + } + + pty->minor = newminor; + + char tmpname[20]; + snprintf(tmpname, 20, ".ptmx%d", newminor); + + // register a master device and immediatelly remove it from the filesystem and tables + // while holding the refcount + int error = devfs_register(&devops, tmpname, V_TYPE_CHDEV, DEV_MAJOR_PTY, newminor, 0644); + if (error) { + freeptyminor(newminor); + freepty(pty); + return error; + } + + __assert(devfs_getbyname(tmpname, &pty->mastervnode) == 0); + + devfs_remove(tmpname, DEV_MAJOR_PTY, newminor); + + // we are now the only ones to hold the master vnode and its inaccessible from devfs + // create a pairing slave device + + snprintf(tmpname, 20, "pts/%d", newminor); + pty->tty = tty_create(tmpname, writetopty, inactivepty, pty); + if (pty->tty == NULL) { + VOP_RELEASE(pty->mastervnode); + return ENOMEM; + } + + // theres an implicit pty -> tty reference to the master vnode, + // as the pts/* link will ALWAYS exist as long as the pty has not closed + + VOP_RELEASE(*vnode); + *vnode = pty->mastervnode; + + return 0; +} + +void pty_init() { + __assert(devfs_register(&devops, "ptmx", V_TYPE_CHDEV, DEV_MAJOR_PTY, PTMX_MINOR, 0644) == 0); + __assert(devfs_createdir("pts") == 0); + SPINLOCK_INIT(listlock); +} diff --git a/kernel-src/io/tty.c b/kernel-src/io/tty.c index d1dfee31..67071e9c 100644 --- a/kernel-src/io/tty.c +++ b/kernel-src/io/tty.c @@ -31,7 +31,7 @@ void tty_process(tty_t *tty, char c) { tty->devicebuffer[tty->devicepos] = '\0'; if (tty->termios.c_lflag & ECHO) - tty->writetodevice("\b \b", 3); + tty->writetodevice(tty->deviceinternal, "\b \b", 3); return; } else if (c == '\n') { // newline @@ -39,7 +39,7 @@ void tty_process(tty_t *tty, char c) { } if (tty->termios.c_lflag & ECHO) - tty->writetodevice(&c, 1); + tty->writetodevice(tty->deviceinternal, &c, 1); // check if buffer is full tty->devicebuffer[tty->devicepos++] = c; @@ -59,7 +59,7 @@ void tty_process(tty_t *tty, char c) { } } else { if (tty->termios.c_lflag & ECHO) - tty->writetodevice(&c, 1); + tty->writetodevice(tty->deviceinternal, &c, 1); MUTEX_ACQUIRE(&tty->readmutex, false); @@ -89,11 +89,11 @@ static int internalpoll(tty_t *tty, polldata_t *data, int events) { if ((events & POLLIN) && RINGBUFFER_DATACOUNT(&tty->readbuffer)) revents |= POLLIN; - MUTEX_RELEASE(&tty->readmutex); - if (revents == 0 && data) poll_add(&tty->pollheader, data, events); + MUTEX_RELEASE(&tty->readmutex); + return revents; } @@ -199,7 +199,7 @@ static int write(int minor, void *buffer, size_t size, uintmax_t offset, int fla // TODO background process stuff etc *writec = size; - tty->writetodevice(buffer, size); + tty->writetodevice(tty->deviceinternal, buffer, size); return 0; } @@ -283,16 +283,35 @@ static void freeminor(int minor) { MUTEX_RELEASE(&listmutex); } +static void tty_destroy(tty_t *tty) { + freeminor(tty->minor); + ringbuffer_destroy(&tty->readbuffer); + free(tty->devicebuffer); + free(tty->name); + free(tty); +} + +static void inactive(int minor) { + tty_t *tty = ttyget(minor); + __assert(tty); + + if (tty->inactivedevice) + tty->inactivedevice(tty->deviceinternal); + + tty_destroy(tty); +} + static devops_t devops = { .write = write, .poll = poll, .read = read, .ioctl = ioctl, .isatty = isatty, - .open = open + .open = open, + .inactive = inactive }; -tty_t *tty_create(char *name, ttydevicewritefn_t fn) { +tty_t *tty_create(char *name, ttydevicewritefn_t writefn, ttyinactivefn_t inactivefn, void *internal) { tty_t *tty = alloc(sizeof(tty_t)); if (tty == NULL) return NULL; @@ -332,14 +351,15 @@ tty_t *tty_create(char *name, ttydevicewritefn_t fn) { tty->termios.obaud = 38400; tty->termios.c_cc[VMIN] = 1; - tty->writetodevice = fn; + tty->writetodevice = writefn; + tty->inactivedevice = inactivefn; tty->minor = minor; + tty->deviceinternal = internal; vnode_t *newnode; __assert(devfs_getnode(NULL, DEV_MAJOR_TTY, minor, &newnode) == 0); tty->mastervnode = (vnode_t *)((devnode_t *)newnode)->master; - VOP_HOLD(tty->mastervnode); VOP_RELEASE(newnode); @@ -355,13 +375,10 @@ tty_t *tty_create(char *name, ttydevicewritefn_t fn) { return NULL; } -void tty_unregister(char *name, tty_t *tty) { - +void tty_unregister(tty_t *tty) { + devfs_remove(tty->name, DEV_MAJOR_TTY, tty->minor); } -void tty_destroy(char *name, tty_t *tty) { - -} void tty_init() { __assert(devfs_register(&devops, "tty", V_TYPE_CHDEV, DEV_MAJOR_TTY, CONTROLLING_TTY_MINOR, 0644) == 0);