toaruos/kernel/fs/tty.c
2013-04-02 18:06:06 -07:00

360 lines
8.8 KiB
C

#include <system.h>
#include <fs.h>
#include <pipe.h>
#include <logging.h>
#include <termios.h>
#define TTY_BUFFER_SIZE 512
#define M_ICANON 0x01
#define M_RAW 0x02
#define M_RRAW 0x04
typedef struct {
unsigned char * buffer;
size_t write_ptr;
size_t read_ptr;
size_t size;
uint8_t volatile lock;
list_t * wait_queue;
} ring_buffer_t;
typedef struct pty {
/* the PTY number */
int name;
/* Master and slave endpoints */
fs_node_t * master;
fs_node_t * slave;
/* term io "window size" struct (width/height) */
struct winsize size;
/* termios data structure */
struct termios tios;
/* directional pipes */
ring_buffer_t in;
ring_buffer_t out;
/* line discipline modes */
unsigned char mode;
pid_t ct_proc; /* Controlling process (shell) */
pid_t fg_proc; /* Foreground process (might also be shell) */
} pty_t;
list_t * pty_list = NULL;
int pty_ioctl(pty_t * pty, int request, void * argp) {
debug_print(WARNING, "Incoming IOCTL request %d", request);
switch (request) {
case TIOCSWINSZ:
if (!argp) return -1;
validate(argp);
memcpy(&pty->size, argp, sizeof(struct winsize));
/* TODO send sigwinch to fg_prog */
return 0;
case TIOCGWINSZ:
if (!argp) return -1;
validate(argp);
memcpy(argp, &pty->size, sizeof(struct winsize));
return 0;
default:
return -1; /* TODO EINV... something or other */
}
return -1;
}
static inline size_t ring_buffer_unread(ring_buffer_t * ring_buffer) {
if (ring_buffer->read_ptr == ring_buffer->write_ptr) {
return 0;
}
if (ring_buffer->read_ptr > ring_buffer->write_ptr) {
return (ring_buffer->size - ring_buffer->read_ptr) + ring_buffer->write_ptr;
} else {
return (ring_buffer->write_ptr - ring_buffer->read_ptr);
}
}
size_t ring_buffer_size(fs_node_t * node) {
ring_buffer_t * ring_buffer = (ring_buffer_t *)node->device;
return ring_buffer_unread(ring_buffer);
}
static inline size_t ring_buffer_available(ring_buffer_t * ring_buffer) {
if (ring_buffer->read_ptr == ring_buffer->write_ptr) {
return ring_buffer->size - 1;
}
if (ring_buffer->read_ptr > ring_buffer->write_ptr) {
return ring_buffer->read_ptr - ring_buffer->write_ptr - 1;
} else {
return (ring_buffer->size - ring_buffer->write_ptr) + ring_buffer->read_ptr - 1;
}
}
static inline void ring_buffer_increment_read(ring_buffer_t * ring_buffer) {
ring_buffer->read_ptr++;
if (ring_buffer->read_ptr == ring_buffer->size) {
ring_buffer->read_ptr = 0;
}
}
static inline void ring_buffer_increment_write(ring_buffer_t * ring_buffer) {
ring_buffer->write_ptr++;
if (ring_buffer->write_ptr == ring_buffer->size) {
ring_buffer->write_ptr = 0;
}
}
uint32_t read_pty_master(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
pty_t * pty = (pty_t *)node->device;
/* Standard pipe read */
size_t collected = 0;
while (collected == 0) {
spin_lock(&pty->out.lock);
while (ring_buffer_unread(&pty->out) > 0 && collected < size) {
buffer[collected] = pty->out.buffer[pty->out.read_ptr];
ring_buffer_increment_read(&pty->out);
collected++;
}
spin_unlock(&pty->out.lock);
wakeup_queue(pty->out.wait_queue);
if (collected == 0) {
sleep_on(pty->out.wait_queue);
}
}
return collected;
}
uint32_t write_pty_master(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
pty_t * pty = (pty_t *)node->device;
size_t written = 0;
while (written < size) {
spin_lock(&pty->in.lock);
while (ring_buffer_available(&pty->in) > 0 && written < size) {
unsigned char c = buffer[written];
/* Implement line discipline stuff here */
pty->in.buffer[pty->in.write_ptr] = c;
ring_buffer_increment_write(&pty->in);
written++;
}
spin_unlock(&pty->in.lock);
wakeup_queue(pty->in.wait_queue);
if (written < size) {
sleep_on(pty->in.wait_queue);
}
}
return written;
}
void open_pty_master(fs_node_t * node, uint8_t read, uint8_t write) {
return;
}
void close_pty_master(fs_node_t * node) {
return;
}
uint32_t read_pty_slave(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
pty_t * pty = (pty_t *)node->device;
debug_print(INFO, "tty read at offset=%d, size=%d", offset, size);
/* Standard pipe read */
size_t collected = 0;
while (collected == 0) {
spin_lock(&pty->in.lock);
while (ring_buffer_unread(&pty->in) > 0 && collected < size) {
buffer[collected] = pty->in.buffer[pty->in.read_ptr];
ring_buffer_increment_read(&pty->in);
collected++;
}
spin_unlock(&pty->in.lock);
wakeup_queue(pty->in.wait_queue);
if (collected == 0) {
sleep_on(pty->in.wait_queue);
}
}
debug_print(INFO, "} completed");
return collected;
}
uint32_t write_pty_slave(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) {
pty_t * pty = (pty_t *)node->device;
size_t written = 0;
while (written < size) {
spin_lock(&pty->out.lock);
while (ring_buffer_available(&pty->out) > 0 && written < size) {
pty->out.buffer[pty->out.write_ptr] = buffer[written];
ring_buffer_increment_write(&pty->out);
written++;
}
spin_unlock(&pty->out.lock);
wakeup_queue(pty->out.wait_queue);
if (written < size) {
sleep_on(pty->out.wait_queue);
}
}
return written;
}
void open_pty_slave(fs_node_t * node, uint8_t read, uint8_t write) {
return;
}
void close_pty_slave(fs_node_t * node) {
return;
}
/*
* These are separate functions just in case I ever feel the need to do
* things differently in the slave or master.
*/
int ioctl_pty_master(fs_node_t * node, int request, void * argp) {
pty_t * pty = (pty_t *)node->device;
return pty_ioctl(pty, request, argp);
}
int ioctl_pty_slave(fs_node_t * node, int request, void * argp) {
pty_t * pty = (pty_t *)node->device;
return pty_ioctl(pty, request, argp);
}
int pty_available_input(fs_node_t * node) {
pty_t * pty = (pty_t *)node->device;
return ring_buffer_unread(&pty->in);
}
int pty_available_output(fs_node_t * node) {
pty_t * pty = (pty_t *)node->device;
return ring_buffer_unread(&pty->out);
}
fs_node_t * pty_master_create(pty_t * pty) {
fs_node_t * fnode = malloc(sizeof(fs_node_t));
memset(fnode, 0x00, sizeof(fs_node_t));
fnode->name[0] = '\0';
sprintf(fnode->name, "pty master");
fnode->uid = 0;
fnode->gid = 0;
fnode->flags = FS_PIPE;
fnode->read = read_pty_master;
fnode->write = write_pty_master;
fnode->open = open_pty_master;
fnode->close = close_pty_master;
fnode->readdir = NULL;
fnode->finddir = NULL;
fnode->ioctl = ioctl_pty_master;
fnode->get_size = pty_available_output;
fnode->device = pty;
return fnode;
}
fs_node_t * pty_slave_create(pty_t * pty) {
fs_node_t * fnode = malloc(sizeof(fs_node_t));
memset(fnode, 0x00, sizeof(fs_node_t));
fnode->name[0] = '\0';
sprintf(fnode->name, "pty slave");
fnode->uid = 0;
fnode->gid = 0;
fnode->flags = FS_PIPE;
fnode->read = read_pty_slave;
fnode->write = write_pty_slave;
fnode->open = open_pty_slave;
fnode->close = close_pty_slave;
fnode->readdir = NULL;
fnode->finddir = NULL;
fnode->ioctl = ioctl_pty_slave;
fnode->get_size = pty_available_input;
fnode->device = pty;
return fnode;
}
void pty_install(void) {
pty_list = list_create();
}
pty_t * pty_new(struct winsize * size) {
pty_t * pty = malloc(sizeof(pty_t));
/* stdin linkage; characters from terminal → PTY slave */
pty->in.buffer = malloc(TTY_BUFFER_SIZE);
pty->in.write_ptr = 0;
pty->in.read_ptr = 0;
pty->in.lock = 0;
pty->in.size = TTY_BUFFER_SIZE;
pty->in.wait_queue = list_create();
/* stdout linkage; characters from client application → terminal */
pty->out.buffer = malloc(TTY_BUFFER_SIZE);
pty->out.write_ptr = 0;
pty->out.read_ptr = 0;
pty->out.lock = 0;
pty->out.wait_queue = list_create();
pty->out.size = TTY_BUFFER_SIZE;
/* Master endpoint - writes go to stdin, reads come from stdout */
pty->master = pty_master_create(pty);
/* Slave endpoint, reads come from stdin, writes go to stdout */
pty->slave = pty_slave_create(pty);
/* TODO PTY name */
pty->name = 0;
if (size) {
memcpy(&pty->size, size, sizeof(struct winsize));
} else {
/* Sane defaults */
pty->size.ws_row = 25;
pty->size.ws_col = 80;
}
/* tty mode (cooked, raw, etc.) */
pty->mode = M_ICANON;
/* Controlling and foreground processes are set to 0 by default */
pty->ct_proc = 0;
pty->fg_proc = 0;
return pty;
}
int openpty(int * master, int * slave, char * name, void * _ign0, void * size) {
/* We require a place to put these when we are done. */
if (!master || !slave) return -1;
if (validate_safe(master) || validate_safe(slave)) return -1;
if (validate_safe(size)) return -1;
/* Create a new pseudo terminal */
pty_t * pty = pty_new(size);
/* Append the master and slave to the calling process */
*master = process_append_fd((process_t *)current_process, pty->master);
*slave = process_append_fd((process_t *)current_process, pty->slave);
/* Return success */
return 0;
}