From 1fc3791bc96b74e22461208f76332f002d68a17a Mon Sep 17 00:00:00 2001 From: Matheus Date: Sat, 27 Apr 2024 13:42:57 -0300 Subject: [PATCH] tty: tty abstraction initial implementation --- kernel-src/include/kernel/devfs.h | 4 +- kernel-src/include/kernel/tty.h | 33 +++ kernel-src/include/termios.h | 1 + kernel-src/io/console.c | 210 ++----------------- kernel-src/io/tty.c | 332 ++++++++++++++++++++++++++++++ 5 files changed, 388 insertions(+), 192 deletions(-) create mode 100644 kernel-src/include/kernel/tty.h create mode 100644 kernel-src/io/tty.c diff --git a/kernel-src/include/kernel/devfs.h b/kernel-src/include/kernel/devfs.h index 2c0c85d5..8cf42393 100644 --- a/kernel-src/include/kernel/devfs.h +++ b/kernel-src/include/kernel/devfs.h @@ -9,7 +9,7 @@ #define DEV_MAJOR_NULL 1 #define DEV_MAJOR_FULL 2 #define DEV_MAJOR_ZERO 3 -#define DEV_MAJOR_CONSOLE 4 +#define DEV_MAJOR_TTY 4 #define DEV_MAJOR_FB 5 #define DEV_MAJOR_KEYBOARD 6 #define DEV_MAJOR_BLOCK 7 @@ -17,6 +17,7 @@ #define DEV_MAJOR_URANDOM 9 #define DEV_MAJOR_NET 10 #define DEV_MAJOR_MOUSE 11 +#define DEV_MAJOR_CONSOLE 12 typedef struct { int (*open)(int minor, int flags); @@ -29,6 +30,7 @@ typedef struct { int (*isatty)(int minor); int (*ioctl)(int minor, unsigned long request, void *arg, int *result); int (*maxseek)(int minor, size_t *max); + void (*inactive)(int minor); } devops_t; typedef struct devnode_t { diff --git a/kernel-src/include/kernel/tty.h b/kernel-src/include/kernel/tty.h new file mode 100644 index 00000000..2d1b2485 --- /dev/null +++ b/kernel-src/include/kernel/tty.h @@ -0,0 +1,33 @@ +#ifndef _TTY_H +#define _TTY_H + +#include +#include +#include +#include + +#define DEVICE_BUFFER_SIZE 512 +#define READ_BUFFER_SIZE 4096 + +typedef void (*ttydevicewritefn_t)(char *str, size_t size); + +typedef struct { + pollheader_t pollheader; + char *name; + ringbuffer_t readbuffer; + mutex_t readmutex; + mutex_t writemutex; + termios_t termios; + char *devicebuffer; + int devicepos; + ttydevicewritefn_t writetodevice; + int minor; + winsize_t winsize; +} tty_t; + +void tty_init(); +tty_t *tty_create(char *name, ttydevicewritefn_t fn); +void tty_destroy(char *name, tty_t *tty); +void tty_process(tty_t *tty, char c); + +#endif diff --git a/kernel-src/include/termios.h b/kernel-src/include/termios.h index 0625967e..ca31e690 100644 --- a/kernel-src/include/termios.h +++ b/kernel-src/include/termios.h @@ -10,6 +10,7 @@ typedef unsigned int tcflag_t; #define NCCS 32 #define ECHO 0000010 #define ICANON 0000002 +#define VTIME 5 #define VMIN 6 #define INLCR 0000100 #define IGNCR 0000200 diff --git a/kernel-src/io/console.c b/kernel-src/io/console.c index f5a25766..f056d599 100644 --- a/kernel-src/io/console.c +++ b/kernel-src/io/console.c @@ -8,15 +8,11 @@ #include #include #include +#include -#define BUFFER_SIZE 4096 static mutex_t writemutex; -static mutex_t readmutex; -static semaphore_t readsemaphore; -static ringbuffer_t readbuffer; static thread_t *thread; -static volatile termios_t termios; -static pollheader_t pollheader; +static tty_t *tty; void console_write(char *str, size_t count) { MUTEX_ACQUIRE(&writemutex, false); @@ -30,8 +26,6 @@ void console_putc(char c) { console_write(&c, 1); } -#define THREAD_BUFFER_SIZE 128 - // taken from https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences #define HOME_STR "\e[1~" @@ -47,34 +41,25 @@ void console_putc(char c) { #define LEFT_STR "\e[D" static void consolethread() { - char buffer[THREAD_BUFFER_SIZE + 1]; - uintmax_t buffpos = 0; - for (;;) { kbpacket_t packet; keyboard_wait(keyboard_console, &packet); if (packet.flags & KBPACKET_FLAGS_RELEASED) continue; - char asciibuffer[2] = {0, 0}; - char *target = asciibuffer; - if (packet.ascii) { - if (packet.ascii == '\r' && (termios.c_iflag & IGNCR)) - continue; - - if (packet.ascii == '\r' && (termios.c_iflag & ICRNL)) - packet.ascii = '\n'; - else if (packet.ascii == '\n' && (termios.c_iflag & INLCR)) - packet.ascii = '\r'; - + // control characters if (packet.flags & (KBPACKET_FLAGS_LEFTCTRL | KBPACKET_FLAGS_RIGHTCTRL)) { if (!((packet.ascii > 'a' && packet.ascii < 'z') || (packet.ascii > 'A' && packet.ascii < 'Z'))) continue; packet.ascii = packet.ascii > 'a' ? packet.ascii - 0x60 : packet.ascii - 0x40; } + + tty_process(tty, packet.ascii); } else { + // no ascii for key but we can send in a escape sequence for it + char *target = NULL; switch (packet.keycode) { case KEYCODE_HOME: target = HOME_STR; @@ -109,185 +94,28 @@ static void consolethread() { default: continue; } - // TODO SWITCH OF SPECIAL KEYS - } - asciibuffer[0] = packet.ascii; - size_t targetlen = strlen(target); - if (targetlen == 0) - continue; - - if (termios.c_lflag & ICANON) { - bool flush = false; - if (packet.ascii == '\b') { - if (buffpos == 0) - continue; - --buffpos; - buffer[buffpos] = '\0'; - if (termios.c_lflag & ECHO) - console_write("\b \b", 3); - continue; - } else if (packet.ascii == '\n') { - flush = true; - } - - if (termios.c_lflag & ECHO) - console_write(target, targetlen); - - for (size_t i = 0; i < targetlen; ++i) { - buffer[buffpos++] = target[i]; - if (buffpos == THREAD_BUFFER_SIZE) { - flush = true; - break; - } - } - - if (flush) { - MUTEX_ACQUIRE(&readmutex, false); - ringbuffer_write(&readbuffer, buffer, buffpos); - buffpos = 0; - size_t datacount = RINGBUFFER_DATACOUNT(&readbuffer); - if (datacount) { - if (datacount >= termios.c_cc[VMIN]) - poll_event(&pollheader, POLLIN); - - semaphore_signal(&readsemaphore); - } - MUTEX_RELEASE(&readmutex); - } - } else { - if (termios.c_lflag & ECHO) - console_write(target, targetlen); - - MUTEX_ACQUIRE(&readmutex, false); - ringbuffer_write(&readbuffer, target, targetlen); - size_t datacount = RINGBUFFER_DATACOUNT(&readbuffer); - if (datacount) { - if (datacount >= termios.c_cc[VMIN]) - poll_event(&pollheader, POLLIN); - - semaphore_signal(&readsemaphore); - } - MUTEX_RELEASE(&readmutex); + size_t len = strlen(target); + for (int i = 0; i < len; ++i) + tty_process(tty, target[i]); } } } -static int read(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *readc) { - size_t total = 0; - for (;;) { - if (semaphore_test(&readsemaphore) == false) { - *readc = 0; - if (termios.c_cc[VMIN] == 0) - return 0; - - if (flags & V_FFLAGS_NONBLOCKING) - return EAGAIN; - - int i = semaphore_wait(&readsemaphore, true); - if (i) - return i; - } - - MUTEX_ACQUIRE(&readmutex, false); - - size_t read = ringbuffer_read(&readbuffer, buffer, size); - size_t left = readbuffer.write - readbuffer.read; - - MUTEX_RELEASE(&readmutex); - - size -= read; - buffer = (void *)((uintptr_t)buffer + read); - total += read; - - if (size == 0 || total >= termios.c_cc[VMIN]) { - if (left > 0) - semaphore_signal(&readsemaphore); - *readc = total; - return 0; - } - } -} - -static int write(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *writec) { - *writec = size; - console_write(buffer, size); - return 0; -} - -static int poll(int minor, polldata_t *data, int events) { - int revents = 0; - - if (events & POLLOUT) - events |= POLLOUT; - - MUTEX_ACQUIRE(&readmutex, false); - - size_t datacount = RINGBUFFER_DATACOUNT(&readbuffer); - - if ((events & POLLIN) && datacount >= termios.c_cc[VMIN]) - revents |= POLLIN; - - MUTEX_RELEASE(&readmutex); - - if (revents == 0 && data) - poll_add(&pollheader, data, events); - - return revents; -} - -static int ioctl(int minor, unsigned long req, void *arg, int *result) { - switch (req) { - case TIOCGWINSZ: - size_t x, y, fbx, fby; - term_getsize(&x, &y, &fbx, &fby); - winsize_t *w = arg; - w->ws_col = x; - w->ws_row = y; - w->ws_xpixel = fbx; - w->ws_ypixel = fby; - break; - case TCGETS: - *(termios_t *)arg = termios; - break; - case TCSETS: - termios = *(termios_t *)arg; - break; - default: - return ENOTTY; - } - - return 0; -} - -static int isatty(int minor) { - return 0; -} - -static devops_t devops = { - .write = write, - .read = read, - .isatty = isatty, - .poll = poll, - .ioctl = ioctl -}; - void console_init() { - SEMAPHORE_INIT(&readsemaphore, 0); MUTEX_INIT(&writemutex); - MUTEX_INIT(&readmutex); - __assert(ringbuffer_init(&readbuffer, BUFFER_SIZE) == 0); - __assert(devfs_register(&devops, "console", V_TYPE_CHDEV, DEV_MAJOR_CONSOLE, 0, 0644) == 0); + thread = sched_newthread(consolethread, PAGE_SIZE * 16, 0, NULL, NULL); __assert(thread); sched_queue(thread); - termios.c_iflag = ICRNL; - termios.c_lflag = ECHO | ICANON | ISIG; - termios.c_cc[VINTR] = 0x03; - termios.ibaud = 38400; - termios.obaud = 38400; - termios.c_cc[VMIN] = 1; + tty = tty_create("console", console_write); + __assert(tty); - POLL_INITHEADER(&pollheader); + size_t x, y, fbx, fby; + term_getsize(&x, &y, &fbx, &fby); + tty->winsize.ws_col = x; + tty->winsize.ws_row = y; + tty->winsize.ws_xpixel = fbx; + tty->winsize.ws_ypixel = fby; } diff --git a/kernel-src/io/tty.c b/kernel-src/io/tty.c new file mode 100644 index 00000000..9f15451f --- /dev/null +++ b/kernel-src/io/tty.c @@ -0,0 +1,332 @@ +#include +#include +#include +#include + +#define TTY_MAX_COUNT 4096 +#define CONTROLLING_TTY_MINOR TTY_MAX_COUNT +static mutex_t listmutex; +static tty_t *ttylist[TTY_MAX_COUNT]; + +void tty_process(tty_t *tty, char c) { + if (c == '\r' && (tty->termios.c_iflag & IGNCR)) + return; + + if (c == '\r' && (tty->termios.c_iflag & ICRNL)) + c = '\n'; + else if (c == '\n' && (tty->termios.c_iflag & INLCR)) + c = '\r'; + + // TODO signal stuff + + if (tty->termios.c_lflag & ICANON) { + bool flush = false; + if (c == '\b') { + // backspace + if (tty->devicepos == 0) + return; + + --tty->devicepos; + tty->devicebuffer[tty->devicepos] = '\0'; + + if (tty->termios.c_lflag & ECHO) + tty->writetodevice("\b \b", 3); + return; + } else if (c == '\n') { + // newline + flush = true; + } + + if (tty->termios.c_lflag & ECHO) + tty->writetodevice(&c, 1); + + // check if buffer is full + tty->devicebuffer[tty->devicepos++] = c; + if (tty->devicepos == DEVICE_BUFFER_SIZE) + flush = true; + + if (flush) { + MUTEX_ACQUIRE(&tty->readmutex, false); + ringbuffer_write(&tty->readbuffer, tty->devicebuffer, tty->devicepos); + tty->devicepos = 0; + // XXX is this needed? + size_t datacount = RINGBUFFER_DATACOUNT(&tty->readbuffer); + if (datacount) + poll_event(&tty->pollheader, POLLIN); + + MUTEX_RELEASE(&tty->readmutex); + } + } else { + if (tty->termios.c_lflag & ECHO) + tty->writetodevice(&c, 1); + + MUTEX_ACQUIRE(&tty->readmutex, false); + + ringbuffer_write(&tty->readbuffer, &c, 1); + poll_event(&tty->pollheader, POLLIN); + + MUTEX_RELEASE(&tty->readmutex); + } +} + +static inline tty_t *ttyget(int minor) { + if (minor >= TTY_MAX_COUNT) + return NULL; + + return ttylist[minor]; +} + +static int internalpoll(tty_t *tty, polldata_t *data, int events) { + int revents = 0; + + // XXX would the device not be in the foregroup group affect this? + if (events & POLLOUT) + revents |= POLLOUT; + + MUTEX_ACQUIRE(&tty->readmutex, false); + + if ((events & POLLIN) && RINGBUFFER_DATACOUNT(&tty->readbuffer)) + revents |= POLLIN; + + MUTEX_RELEASE(&tty->readmutex); + + if (revents == 0 && data) + poll_add(&tty->pollheader, data, events); + + return revents; +} + +static int read(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *readc) { + tty_t *tty = ttyget(minor); + if (tty == NULL) + return ENODEV; + + // for non canonical read + // VMIN = 0 VTIME = 0: return immediatelly if no data in buffer + // VMIN > 0 VTIME = 0: block until enough data has been read + // VMIN = 0 VTIME > 0: block until either a timeout or until there is data to read + // VMIN > 0 VTIME > 0: wait for a character indefinetly, and then keep reading more + // with an inter byte timeout until either the timeout expires os we have read the minimum amount of data + // for canonical read, min and time will be VMIN 1 VTIME 0 + + // XXX + // does changing the VMIN and VTIME value from another thread change this? + int min = (tty->termios.c_lflag & ICANON) ? 1 : tty->termios.c_cc[VMIN]; + int time = (tty->termios.c_lflag & ICANON) ? 0 : tty->termios.c_cc[VTIME]; + + // TODO + // bg and fg process group stuff + + // read data and if theres nothing just return + if (min == 0 && time == 0) { + MUTEX_ACQUIRE(&tty->readmutex, false); + *readc = ringbuffer_read(&tty->readbuffer, buffer, size); + MUTEX_RELEASE(&tty->readmutex); + return 0; + } + + // time is in 1/10 of a second + size_t timeoutus = time * 100000; + uintmax_t spins = 0; + + int error = 0; + size_t readcount = 0; + size_t spaceleft = size; + + // TODO hangup stuff + + for (;;) { + // for VMIN > 0 and VTIME > 0, the timeout is inter byte only. + size_t effectivetimeout = (min > 0 && time > 0 && spins == 0) ? 0 : timeoutus; + + polldesc_t desc = {0}; + error = poll_initdesc(&desc, 1); + if (error) + goto leave; + + int revents = internalpoll(tty, &desc.data[0], POLLIN); + + if (revents) { + // theres data to read + MUTEX_ACQUIRE(&tty->readmutex, false); + + size_t actuallyread = ringbuffer_read(&tty->readbuffer, (void *)((uintptr_t)buffer + readcount), spaceleft); + readcount += actuallyread; + spaceleft -= actuallyread; + MUTEX_RELEASE(&tty->readmutex); + + // XXX is this valid? + if (spaceleft > 0 && readcount < min) + continue; + + poll_leave(&desc); + poll_destroydesc(&desc); + break; + } + + if (revents == 0 && flags & V_FFLAGS_NONBLOCKING) { + // nothing to read and nonblocking + poll_leave(&desc); + poll_destroydesc(&desc); + error = EAGAIN; + goto leave; + } + + error = poll_dowait(&desc, effectivetimeout); + if (desc.event->revents == 0) { + // timed out! + break; + } + + poll_leave(&desc); + poll_destroydesc(&desc); + + if (error) + goto leave; + } + + *readc = readcount; + + leave: + return error; +} + +static int write(int minor, void *buffer, size_t size, uintmax_t offset, int flags, size_t *writec) { + tty_t *tty = ttyget(minor); + if (tty == NULL) + return ENODEV; + + // TODO background process stuff etc + *writec = size; + tty->writetodevice(buffer, size); + return 0; +} + +static int poll(int minor, polldata_t *data, int events) { + tty_t *tty = ttyget(minor); + if (tty == NULL) + return POLLERR; + + return internalpoll(tty, data, events); +} + +static int ioctl(int minor, unsigned long req, void *arg, int *result) { + tty_t *tty = ttyget(minor); + if (tty == NULL) + return ENODEV; + + switch (req) { + case TIOCGWINSZ: + winsize_t *w = arg; + *w = tty->winsize; + break; + case TCGETS: + *(termios_t *)arg = tty->termios; + break; + case TCSETS: + tty->termios = *(termios_t *)arg; + break; + default: + return ENOTTY; + } + + return 0; +} + +static int isatty(int minor) { + return 0; +} + +static int allocminor(tty_t *tty) { + MUTEX_ACQUIRE(&listmutex, false); + + int i; + for (i = 0; i < TTY_MAX_COUNT; ++i) { + if (ttylist[i] == NULL) + break; + } + + ttylist[i] = tty; + + MUTEX_RELEASE(&listmutex); + return i; +} + +static void freeminor(int minor) { + MUTEX_ACQUIRE(&listmutex, false); + __assert(ttylist[minor]); + ttylist[minor] = NULL; + MUTEX_RELEASE(&listmutex); +} + +static devops_t devops = { + .write = write, + .poll = poll, + .read = read, + .ioctl = ioctl, + .isatty = isatty +}; + +tty_t *tty_create(char *name, ttydevicewritefn_t fn) { + tty_t *tty = alloc(sizeof(tty_t)); + if (tty == NULL) + return NULL; + + tty->name = alloc(strlen(name) + 1); + if (tty->name == NULL) + goto error; + + tty->devicebuffer = alloc(DEVICE_BUFFER_SIZE); + if (tty->devicebuffer == NULL) + goto error; + + if (ringbuffer_init(&tty->readbuffer, READ_BUFFER_SIZE)) + goto error; + + int minor = allocminor(tty); + if (minor == TTY_MAX_COUNT) { + ringbuffer_destroy(&tty->readbuffer); + goto error; + } + + if (devfs_register(&devops, name, V_TYPE_CHDEV, DEV_MAJOR_TTY, minor, 0644)) { + freeminor(minor); + ringbuffer_destroy(&tty->readbuffer); + goto error; + } + + POLL_INITHEADER(&tty->pollheader); + strcpy(tty->name, name); + MUTEX_INIT(&tty->readmutex); + MUTEX_INIT(&tty->writemutex); + + tty->termios.c_iflag = ICRNL; + tty->termios.c_lflag = ECHO | ICANON | ISIG; + tty->termios.c_cc[VINTR] = 0x03; + tty->termios.ibaud = 38400; + tty->termios.obaud = 38400; + tty->termios.c_cc[VMIN] = 1; + + tty->writetodevice = fn; + tty->minor = minor; + + return tty; + error: + if (tty->name) + free(tty->name); + + if (tty->devicebuffer) + free(tty->devicebuffer); + + free(tty); + return NULL; +} + +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); + MUTEX_INIT(&listmutex); +}