mirror of
https://github.com/Mathewnd/Astral.git
synced 2026-10-27 20:58:22 +00:00
581 lines
14 KiB
C
581 lines
14 KiB
C
#include <kernel/tty.h>
|
|
#include <kernel/devfs.h>
|
|
#include <logging.h>
|
|
#include <kernel/alloc.h>
|
|
#include <kernel/jobctl.h>
|
|
#include <kernel/usercopy.h>
|
|
#include <kernel/init.h>
|
|
|
|
#define TTY_MAX_COUNT 4096
|
|
#define CONTROLLING_TTY_MINOR TTY_MAX_COUNT
|
|
static mutex_t listmutex;
|
|
static tty_t *ttylist[TTY_MAX_COUNT];
|
|
|
|
static proc_t *getsession(tty_t *tty) {
|
|
bool intstatus = interrupt_set(false);
|
|
spinlock_acquire(&tty->sessionlock);
|
|
proc_t *session = tty->session;
|
|
if (session) {
|
|
PROC_HOLD(session);
|
|
}
|
|
spinlock_release(&tty->sessionlock);
|
|
interrupt_set(intstatus);
|
|
return session;
|
|
}
|
|
|
|
static proc_t *getforeground(tty_t *tty) {
|
|
proc_t *session = getsession(tty);
|
|
proc_t *foreground = NULL;
|
|
if (session) {
|
|
foreground = jobctl_getforeground(session);
|
|
PROC_RELEASE(session);
|
|
}
|
|
return foreground;
|
|
}
|
|
|
|
static int setforeground(tty_t *tty, proc_t *proc) {
|
|
proc_t *session = getsession(tty);
|
|
if (session) {
|
|
int error = jobctl_setforeground(session, proc);
|
|
PROC_RELEASE(session);
|
|
return error;
|
|
}
|
|
return ENOTTY;
|
|
}
|
|
|
|
// TODO: make this interrupt safe
|
|
// this would mean:
|
|
// - preventing the leaders from reaching refcnt = 1 during the getforeground() call until it is done
|
|
// - list mutex -> list spinlock
|
|
//
|
|
// however, for it to truly be interrupt safe, the tty->writetodevice callback would also need to be interrupt safe,
|
|
// which means that any drivers expecting to call this from an interrupt context would REQUIRE to have the callback to also be interrupt safe
|
|
// the main console is an example of a driver which would not be able to do so.
|
|
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 VSTART VSTOP
|
|
|
|
char echoc = (tty->termios.c_lflag & ECHO) ? c : '\0';
|
|
|
|
// echo control characters
|
|
if ((tty->termios.c_lflag & ECHOCTL) && (tty->termios.c_lflag & ECHO) && c < 32 && c != '\n' && c != '\r' && c != '\b' && c != '\t' && c != '\e') {
|
|
char tmp[2] = {'^', c + 0x40};
|
|
tty->writetodevice(tty->deviceinternal, tmp, 2);
|
|
echoc = false;
|
|
}
|
|
|
|
if (tty->termios.c_lflag & ISIG) {
|
|
int signal = -1;
|
|
if (tty->termios.c_cc[VINTR] == c)
|
|
signal = SIGINT;
|
|
else if (tty->termios.c_cc[VQUIT] == c)
|
|
signal = SIGQUIT;
|
|
else if (tty->termios.c_cc[VSUSP] == c)
|
|
signal = SIGTSTP;
|
|
|
|
if (signal >= 0) {
|
|
proc_t *pgrp = getforeground(tty);
|
|
if (pgrp) {
|
|
jobctl_signal(pgrp, signal, NULL);
|
|
PROC_RELEASE(pgrp);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tty->termios.c_lflag & ICANON) {
|
|
bool flush = false;
|
|
if (c == tty->termios.c_cc[VERASE]) {
|
|
// backspace
|
|
if (tty->devicepos == 0)
|
|
return;
|
|
|
|
--tty->devicepos;
|
|
tty->devicebuffer[tty->devicepos] = '\0';
|
|
|
|
if (tty->termios.c_lflag & ECHO)
|
|
tty->writetodevice(tty->deviceinternal, "\b \b", 3);
|
|
return;
|
|
} else if (c == tty->termios.c_cc[VKILL]) {
|
|
// erase everything
|
|
for (int i = 0; i < tty->devicepos + ((tty->termios.c_lflag & ECHOCTL) ? 2 : 0); ++i)
|
|
tty->writetodevice(tty->deviceinternal, "\b \b", 3);
|
|
|
|
tty->devicepos = 0;
|
|
memset(tty->devicebuffer, 0, TTY_DEVICE_BUFFER_SIZE);
|
|
return;
|
|
} else if (c == tty->termios.c_cc[VEOF]) {
|
|
// end of file TODO make read return 0
|
|
flush = true;
|
|
} else if (c == '\n' || c == tty->termios.c_cc[VEOL] || c == tty->termios.c_cc[VEOL2]) {
|
|
// newline
|
|
flush = true;
|
|
}
|
|
|
|
if (echoc)
|
|
tty->writetodevice(tty->deviceinternal, &echoc, 1);
|
|
|
|
// check if buffer is full
|
|
tty->devicebuffer[tty->devicepos++] = c;
|
|
if (tty->devicepos == TTY_DEVICE_BUFFER_SIZE)
|
|
flush = true;
|
|
|
|
if (flush) {
|
|
MUTEX_ACQUIRE(&tty->readmutex);
|
|
|
|
ringbuffer_write(&tty->readbuffer, tty->devicebuffer, tty->devicepos);
|
|
|
|
memset(tty->devicebuffer, 0, TTY_DEVICE_BUFFER_SIZE);
|
|
|
|
tty->devicepos = 0;
|
|
poll_event(&tty->pollheader, POLLIN);
|
|
|
|
MUTEX_RELEASE(&tty->readmutex);
|
|
}
|
|
} else {
|
|
if (echoc)
|
|
tty->writetodevice(tty->deviceinternal, &c, 1);
|
|
|
|
MUTEX_ACQUIRE(&tty->readmutex);
|
|
|
|
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);
|
|
|
|
if ((events & POLLIN) && RINGBUFFER_DATACOUNT(&tty->readbuffer))
|
|
revents |= POLLIN;
|
|
|
|
if (revents == 0 && data)
|
|
poll_add(&tty->pollheader, data, events);
|
|
|
|
MUTEX_RELEASE(&tty->readmutex);
|
|
|
|
return revents;
|
|
}
|
|
|
|
static int read(int minor, iovec_iterator_t *iovec_iterator, 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
|
|
// SIGTTIN SIGTTOUT
|
|
|
|
// read data and if theres nothing just return
|
|
if (min == 0 && time == 0) {
|
|
MUTEX_ACQUIRE(&tty->readmutex);
|
|
*readc = iovec_iterator_read_from_ringbuffer(iovec_iterator, &tty->readbuffer, size);
|
|
|
|
int error = 0;
|
|
if (*readc == RINGBUFFER_USER_COPY_FAILED)
|
|
error = EFAULT;
|
|
|
|
MUTEX_RELEASE(&tty->readmutex);
|
|
return error;
|
|
}
|
|
|
|
// 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;
|
|
|
|
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);
|
|
|
|
size_t actuallyread = iovec_iterator_read_from_ringbuffer(iovec_iterator, &tty->readbuffer, spaceleft);
|
|
|
|
MUTEX_RELEASE(&tty->readmutex);
|
|
|
|
if (actuallyread == RINGBUFFER_USER_COPY_FAILED) {
|
|
error = EFAULT;
|
|
} else {
|
|
readcount += actuallyread;
|
|
spaceleft -= actuallyread;
|
|
|
|
// XXX is this valid?
|
|
if (spaceleft > 0 && readcount < min && (revents & POLLHUP) == 0)
|
|
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);
|
|
|
|
poll_leave(&desc);
|
|
poll_destroydesc(&desc);
|
|
|
|
if (error == ETIMEDOUT) {
|
|
// this does not return an error on timeout.
|
|
// instead, it just returns the number of bytes read.
|
|
error = 0;
|
|
break;
|
|
}
|
|
|
|
if (error)
|
|
goto leave;
|
|
}
|
|
|
|
*readc = readcount;
|
|
|
|
leave:
|
|
return error;
|
|
}
|
|
|
|
static int write(int minor, iovec_iterator_t *iovec_iterator, 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;
|
|
for (int i = 0; i < size; ++i) {
|
|
// bruh
|
|
char c;
|
|
int error = iovec_iterator_copy_to_buffer(iovec_iterator, &c, sizeof(char));
|
|
if (error)
|
|
return error;
|
|
|
|
if (c == '\n' && (tty->termios.c_oflag & ONLCR)) {
|
|
char cr = '\r';
|
|
tty->writetodevice(tty->deviceinternal, &cr, 1);
|
|
}
|
|
|
|
tty->writetodevice(tty->deviceinternal, &c, 1);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#define TIOCSCTTY 0x540E
|
|
#define TIOCGPGRP 0x540F
|
|
#define TIOCSPGRP 0x5410
|
|
#define TTY_IOCTL_NAME 0x771101141113l
|
|
#define TTY_NAME_MAX 32
|
|
|
|
int tty_ioctl(tty_t *tty, unsigned long req, void *arg, int *result, cred_t *cred) {
|
|
switch (req) {
|
|
case TIOCGWINSZ:
|
|
return USERCOPY_POSSIBLY_TO_USER(arg, &tty->winsize, sizeof(winsize_t));
|
|
case TIOCSWINSZ: {
|
|
int e = USERCOPY_POSSIBLY_FROM_USER(&tty->winsize, arg, sizeof(winsize_t));
|
|
if (e)
|
|
return e;
|
|
|
|
proc_t *foreground = getforeground(tty);
|
|
if (foreground) {
|
|
jobctl_signal(foreground, SIGWINCH, NULL);
|
|
PROC_RELEASE(foreground);
|
|
}
|
|
break;
|
|
}
|
|
case TCGETS:
|
|
return USERCOPY_POSSIBLY_TO_USER(arg, &tty->termios, sizeof(termios_t));
|
|
case TCSETS: {
|
|
int e = USERCOPY_POSSIBLY_FROM_USER(&tty->termios, arg, sizeof(termios_t));
|
|
|
|
if (e == 0 && tty->termios_callback)
|
|
tty->termios_callback(tty->deviceinternal, &tty->termios);
|
|
|
|
return e;
|
|
}
|
|
case TIOCSCTTY: {
|
|
// set as controlling tty
|
|
return jobctl_setctty(current_thread()->proc, tty, (uintptr_t)arg == 1);
|
|
break;
|
|
}
|
|
case TIOCGPGRP: {
|
|
proc_t *foreground = getforeground(tty);
|
|
if (foreground == NULL)
|
|
return ENOTTY;
|
|
|
|
int pgrp = foreground->pid;
|
|
PROC_RELEASE(foreground);
|
|
|
|
return USERCOPY_POSSIBLY_TO_USER(arg, &pgrp, sizeof(int));
|
|
}
|
|
case TIOCSPGRP: {
|
|
int pgrp;
|
|
int e = USERCOPY_POSSIBLY_FROM_USER(&pgrp, arg, sizeof(int));
|
|
if (e)
|
|
return e;
|
|
|
|
proc_t *proc = proc_get_from_pid(pgrp);
|
|
if (proc == NULL)
|
|
return ESRCH;
|
|
|
|
int error = setforeground(tty, proc);
|
|
PROC_RELEASE(proc);
|
|
return error;
|
|
}
|
|
case TTY_IOCTL_NAME: {
|
|
size_t len = min(strlen(tty->name) + 1, TTY_NAME_MAX);
|
|
return USERCOPY_POSSIBLY_TO_USER(arg, tty->name, len);
|
|
}
|
|
case FIONREAD: {
|
|
MUTEX_ACQUIRE(&tty->readmutex);
|
|
int count = RINGBUFFER_DATACOUNT(&tty->readbuffer);
|
|
MUTEX_RELEASE(&tty->readmutex);
|
|
int status = USERCOPY_POSSIBLY_TO_USER(arg, &count, sizeof(count));
|
|
return status;
|
|
}
|
|
default:
|
|
return ENOTTY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ioctl(int minor, unsigned long req, void *arg, int *result, cred_t *cred) {
|
|
tty_t *tty = ttyget(minor);
|
|
if (tty == NULL)
|
|
return ENODEV;
|
|
|
|
return tty_ioctl(tty, req, arg, result, cred);
|
|
}
|
|
|
|
static int isatty(int minor) {
|
|
return 0;
|
|
}
|
|
|
|
static int open(int minor, vnode_t **vnode, int flags) {
|
|
tty_t *tty = ttyget(minor);
|
|
|
|
if (minor == CONTROLLING_TTY_MINOR) {
|
|
// return controlling tty
|
|
tty_t *ctty = jobctl_getctty(current_thread()->proc);
|
|
|
|
if (ctty == NULL) {
|
|
return ENXIO;
|
|
}
|
|
|
|
VOP_RELEASE(*vnode);
|
|
*vnode = (vnode_t *)(ctty->mastervnode);
|
|
} else if (tty == NULL) {
|
|
return ENODEV;
|
|
} else if ((flags & V_FFLAGS_NOCTTY) == 0) {
|
|
// set as controlling tty
|
|
jobctl_setctty(current_thread()->proc, tty, false);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int allocminor(tty_t *tty) {
|
|
MUTEX_ACQUIRE(&listmutex);
|
|
|
|
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);
|
|
__assert(ttylist[minor]);
|
|
ttylist[minor] = NULL;
|
|
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,
|
|
.inactive = inactive
|
|
};
|
|
|
|
tty_t *tty_create(char *name, ttydevicewritefn_t writefn, ttyinactivefn_t inactivefn, tty_termios_callback_t termios_callback, void *internal) {
|
|
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(TTY_DEVICE_BUFFER_SIZE);
|
|
if (tty->devicebuffer == NULL)
|
|
goto error;
|
|
|
|
if (ringbuffer_init(&tty->readbuffer, TTY_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, 0600, current_thread()->proc ? ¤t_thread()->proc->cred : NULL)) {
|
|
freeminor(minor);
|
|
ringbuffer_destroy(&tty->readbuffer);
|
|
goto error;
|
|
}
|
|
|
|
POLL_INITHEADER(&tty->pollheader);
|
|
strcpy(tty->name, name);
|
|
MUTEX_INIT(&tty->readmutex);
|
|
MUTEX_INIT(&tty->writemutex);
|
|
SPINLOCK_INIT(tty->sessionlock);
|
|
|
|
tty->termios.c_iflag = ICRNL;
|
|
tty->termios.c_oflag = ONLCR;
|
|
tty->termios.c_lflag = ECHO | ICANON | ISIG | ECHOCTL;
|
|
tty->termios.c_cflag = CS8 | B38400;
|
|
tty->termios.c_cc[VMIN] = 1;
|
|
tty->termios.c_cc[VINTR] = 0x03;
|
|
tty->termios.c_cc[VQUIT] = 0x1c;
|
|
tty->termios.c_cc[VERASE] = '\b';
|
|
tty->termios.c_cc[VKILL] = 0x15;
|
|
tty->termios.c_cc[VEOF] = 0x04;
|
|
tty->termios.c_cc[VSTART] = 0x11;
|
|
tty->termios.c_cc[VSTOP] = 0x13;
|
|
tty->termios.c_cc[VSUSP] = 0x1a;
|
|
|
|
tty->writetodevice = writefn;
|
|
tty->inactivedevice = inactivefn;
|
|
tty->minor = minor;
|
|
tty->deviceinternal = internal;
|
|
tty->termios_callback = termios_callback;
|
|
|
|
vnode_t *newnode;
|
|
__assert(devfs_getnode(NULL, DEV_MAJOR_TTY, minor, &newnode) == 0);
|
|
|
|
tty->mastervnode = (vnode_t *)((devnode_t *)newnode)->master;
|
|
|
|
VOP_RELEASE(newnode);
|
|
|
|
// set up basic state for the tty
|
|
if (termios_callback)
|
|
termios_callback(internal, &tty->termios);
|
|
|
|
return tty;
|
|
error:
|
|
if (tty->name)
|
|
free(tty->name);
|
|
|
|
if (tty->devicebuffer)
|
|
free(tty->devicebuffer);
|
|
|
|
free(tty);
|
|
return NULL;
|
|
}
|
|
|
|
void tty_unregister(tty_t *tty) {
|
|
devfs_remove(tty->name, DEV_MAJOR_TTY, tty->minor);
|
|
|
|
proc_t *foreground = getforeground(tty);
|
|
if (foreground) {
|
|
jobctl_signal(foreground, SIGHUP, NULL);
|
|
PROC_RELEASE(foreground);
|
|
}
|
|
}
|
|
|
|
|
|
void tty_init() {
|
|
__assert(devfs_register(&devops, "tty", V_TYPE_CHDEV, DEV_MAJOR_TTY, CONTROLLING_TTY_MINOR, 0666, NULL) == 0);
|
|
MUTEX_INIT(&listmutex);
|
|
}
|
|
|
|
INIT_ROUTINE_DEFINE(tty, INIT_ROUTINE_FLAGS_NONE, tty_init, devfs);
|