Commit 82096b6c authored by Léo Grange's avatar Léo Grange

minimalist implementation of TTY termios support

Support of local modes and control characters is now centralized in
generic TTY driver (line control is working well on VTs).
Implementation of the new TTY input interface is done only for VTs.
parent eb355cc3
......@@ -104,7 +104,7 @@ int kdb_convert_keymatrix_code(int matrixcode) {
case K_NEG: return KEY_; break;
case K_EXE: return '\n'; break;
case K_AC: return '\x7F'; break;
case K_AC: return ASCII_CTRL('U'); break;
}
}
else if(_kbd_status & KBD_STATE_SHIFT) {
......@@ -167,7 +167,7 @@ int kdb_convert_keymatrix_code(int matrixcode) {
case K_NEG: return KEY_; break;
case K_EXE: return '\n'; break;
case K_AC: return '\x7F'; break;
case K_AC: return ASCII_CTRL('U'); break;
}
}
else {
......@@ -230,7 +230,7 @@ int kdb_convert_keymatrix_code(int matrixcode) {
case K_NEG: return '_'; break;
case K_EXE: return '\n'; break;
case K_AC: return '\x7F'; break;
case K_AC: return ASCII_CTRL('U'); break;
}
}
return -1;
......
#include "virtual_term.h"
#include <utils/cyclic_fifo.h>
#include <fs/file_operations.h>
#include <sys/waitqueue.h>
#include <interface/fixos/errno.h>
#include <sys/tty.h>
#include <sys/process.h>
......@@ -53,15 +51,6 @@ struct vt_instance {
int saved_posx;
int saved_posy;
// for input control :
char fifo_buf[VT_INPUT_BUFFER];
struct cyclic_fifo fifo;
char line_buf[VT_LINE_BUFFER];
int line_pos;
struct wait_queue wqueue;
struct tty tty;
struct vt100_esc_state esc_state;
......@@ -101,16 +90,21 @@ static int vt_tty_is_ready(struct tty *tty) {
return 1;
}
static int vt_tty_putchar(struct tty *tty, char c);
static int vt_tty_write(struct tty *tty, const char *data, size_t len);
static const struct tty_ops _vt_tty_ops = {
.ioctl_setwinsize = &vt_ioctl_setwinsize,
.ioctl_getwinsize = &vt_ioctl_getwinsize,
.is_ready = &vt_tty_is_ready,
.tty_write = &vt_tty_write
.tty_write = &vt_tty_write,
.putchar = &vt_tty_putchar
};
/**
* Clear the given VT escape parser data to get a clean state again.
*/
......@@ -132,22 +126,13 @@ void vt_init() {
for(i=0; i<VT_MAX_TERMINALS; i++) {
_tdisp->init_disp(& _vts[i].disp);
_vts[i].fifo.buffer = _vts[i].fifo_buf;
_vts[i].fifo.max_size = VT_INPUT_BUFFER;
_vts[i].fifo.size = 0;
_vts[i].fifo.top = 0;
_vts[i].line_pos = 0;
_vts[i].posx = 0;
_vts[i].posy = 0;
_vts[i].saved_posx = 0;
_vts[i].saved_posy = 0;
INIT_WAIT_QUEUE(& _vts[i].wqueue);
_vts[i].tty.controler = 0;
_vts[i].tty.fpgid = 0;
// set TTY default settings
tty_default_init(&_vts[i].tty);
_vts[i].tty.private = & _vts[i];
_vts[i].tty.ops = &_vt_tty_ops;
......@@ -373,6 +358,58 @@ static void vt_read_escape_code(struct vt_instance *term, char str_char) {
term->esc_state.discovery_state = newstate;
}
/**
* Echo a character to the screen, without flushing to display driver
*/
static void vt_echo_char(struct vt_instance *term, char c, int mayesc) {
if(!mayesc || (term->esc_state.discovery_state == VT100_STATE_NONE
&& c != '\x1B'))
{
// We aren't in a vt100 escape code
if(c == '\b') {
// backspace, should only go back (non destructive)
_tdisp->print_char(& term->disp, term->posx, term->posy, ' ');
term->posx--;
if(term->posx < 0) {
// this behavior is not POSIX, but (very?) useful
term->posx = _tdisp->cwidth - 1;
term->posy--;
}
// print cursor at current position
_tdisp->print_char(& term->disp, term->posx, term->posy,
VT_CURSOR_CHAR);
}
else if(c == '\n') {
// remove the current cursor display before line feed
_tdisp->print_char(& term->disp, term->posx, term->posy, ' ');
term->posx = 0;
term->posy++;
}
else if(c == '\r') term->posx=0;
else {
_tdisp->print_char(& term->disp, term->posx, term->posy, c);
term->posx++;
}
if(term->posx >= _tdisp->cwidth) {
term->posx = 0;
term->posy++;
}
if(term->posy >= _tdisp->cheight) {
_tdisp->scroll(&term->disp);
term->posy = _tdisp->cheight - 1;
}
}
else {
// parse the character as a part of a VT100-like escape sequence
vt_read_escape_code(term, c);
}
}
/**
* Function used to print characters (as well written to term and echoed input from
* keyboard).
......@@ -385,34 +422,7 @@ static void vt_term_print(struct vt_instance *term, const void *source, size_t l
const unsigned char *str = source;
for(i=0; i<len; i++) {
if(!mayesc || (term->esc_state.discovery_state == VT100_STATE_NONE && str[i] != '\x1B')) {
// We aren't in a vt100 escape code
if(str[i] == '\n') {
// remove the current cursor display before line feed
_tdisp->print_char(& term->disp, term->posx, term->posy, ' ');
term->posx = 0;
term->posy++;
}
else if(str[i] == '\r') term->posx=0;
else {
_tdisp->print_char(& term->disp, term->posx, term->posy, str[i]);
term->posx++;
}
if(term->posx >= _tdisp->cwidth) {
term->posx = 0;
term->posy++;
}
if(term->posy >= _tdisp->cheight) {
_tdisp->scroll(&term->disp);
term->posy = _tdisp->cheight - 1;
}
}
else {
// parse the character as a part of a VT100-like escape sequence
vt_read_escape_code(term, str[i]);
}
vt_echo_char(term, str[i], mayesc);
}
// print cursor at current position
......@@ -424,91 +434,15 @@ static void vt_term_print(struct vt_instance *term, const void *source, size_t l
// remove the previous echoed character from the display
static void vt_unwind_echo_char(struct vt_instance *term) {
_tdisp->print_char(& term->disp, term->posx, term->posy, ' ');
term->posx--;
if(term->posx < 0) {
term->posx = _tdisp->cwidth - 1;
term->posy--;
}
// print cursor at current position
_tdisp->print_char(& term->disp, term->posx, term->posy,
VT_CURSOR_CHAR);
_tdisp->flush(& term->disp);
}
// add the given character to line buffer and echo it if needed
static void vt_add_character(struct vt_instance *term, char c) {
term->line_buf[term->line_pos] = c;
term->line_pos++;
// called by TTY driver to display a single character, be kind and flush disp
static int vt_tty_putchar(struct tty *tty, char c) {
struct vt_instance *term = tty->private;
vt_echo_char((struct vt_instance *)(tty->private), c, 1);
// basic echo, need to be improved (do not copy_to_dd() each time...)
if(IS_ESC_CTRL(c)) {
char esc[2] = {'^', ASCII_UNCTRL(c)};
vt_term_print(term, esc, 2, 0);
}
else {
vt_term_print(term, &c, 1, 0);
}
// TODO flush only if active?
_tdisp->print_char(& term->disp, term->posx, term->posy, VT_CURSOR_CHAR);
_tdisp->flush(& term->disp);
}
// do special action for character < 0x20 (special ASCII chars)
static void vt_do_special(struct vt_instance *term, char spe) {
/*
char str[3] = {'^', ' ', '\0'};
str[1] = ASCII_UNCTRL(spe);
printk(LOG_DEBUG, "tty: received %s, pgid=%d\n", str, term->tty.fpgid);
*/
char spestr[2] = {'^', ASCII_UNCTRL(spe)};
switch(spe) {
case ASCII_CTRL('C'):
// kill group
if(term->tty.fpgid != 0)
signal_pgid_raise(term->tty.fpgid, SIGINT);
vt_term_print(term, spestr, 2, 0);
_tdisp->flush(& term->disp);
break;
case ASCII_CTRL('Z'):
// stop foreground
if(term->tty.fpgid != 0)
signal_pgid_raise(term->tty.fpgid, SIGSTOP);
vt_term_print(term, spestr, 2, 0);
_tdisp->flush(& term->disp);
break;
case '\n':
vt_add_character(term, '\n');
cfifo_push(& term->fifo, term->line_buf, term->line_pos);
term->line_pos = 0;
wqueue_wakeup(& term->wqueue);
break;
case ASCII_CTRL('H'):
// backspace, remove last buffered char and echo space instead
if(term->line_pos > 0) {
char removed = term->line_buf[term->line_pos-1];
// removed 2 characters if it was displayed as "^<char>"
if(IS_ESC_CTRL(removed))
vt_unwind_echo_char(term);
vt_unwind_echo_char(term);
term->line_pos--;
}
break;
default:
vt_add_character(term, spe);
}
return 0;
}
......@@ -520,17 +454,7 @@ void vt_key_stroke(int code) {
struct vt_instance *term;
term = & _vts[_vt_current];
// check the line buffer, and add the char to it if possible
if(term->line_pos < VT_LINE_BUFFER || code == 0x08) {
if(code < 0x20) {
// special character (should be improved)
vt_do_special(term, (char)code);
}
else if(code < 0x80) {
vt_add_character(term, (char)code);
}
}
tty_input_char(& term->tty, (char)code);
}
}
......@@ -600,23 +524,7 @@ ssize_t vt_read(struct file *filep, void *dest, size_t len) {
term = (int)(filep->private_data);
if(term >= 0 && term <VT_MAX_TERMINALS) {
// TODO atomic fifo access
int curlen = 0;
volatile size_t *fifo_size = &(_vts[term].fifo.size);
// FIXME check behavior : should return the first line available
//while(curlen < len) {
size_t readlen;
wait_until_condition(& _vts[term].wqueue, (readlen=*fifo_size) > 0);
// at least 1 byte available in the FIFO
readlen = readlen + curlen > len ? len - curlen : readlen;
cfifo_pop(& _vts[term].fifo, dest, readlen);
curlen += readlen;
//}
return curlen;
return tty_read(& _vts[term].tty, dest, len);
}
return -EINVAL;
......
#include "tty.h"
// default termios settings used
static const struct termios _tty_default_termios = {
.c_cc = {
ASCII_CTRL('D'), // VEOF
'\n', // VEOL
'\b', // VERASE
ASCII_CTRL('C'), // VINTR
ASCII_CTRL('U'), // VKILL
1, // VMIN
ASCII_CTRL('\\'), // VQUIT
'\0', // VSTART
'\0', // VSTOP
ASCII_CTRL('Z'), // VSUSP
0, // VTIME
},
.c_iflag = 0,
.c_oflag = 0,
.c_cflag = 0,
.c_lflag = ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG | ECHOCTL
};
int tty_ioctl(struct tty *tty, int cmd, void *data) {
switch(cmd) {
......@@ -28,3 +50,183 @@ int tty_ioctl(struct tty *tty, int cmd, void *data) {
return -EFAULT;
}
}
int tty_default_init(struct tty *tty) {
tty->fifo.buffer = tty->fifo_buf;
tty->fifo.max_size = TTY_INPUT_BUFFER;
tty->fifo.size = 0;
tty->fifo.top = 0;
tty->line_pos = 0;
INIT_WAIT_QUEUE(& tty->wqueue);
tty->controler = 0;
tty->fpgid = 0;
// use an acceptable default for termios
tty->termios = _tty_default_termios;
return 0;
}
static void tty_echo_char(struct tty *tty, char c) {
if(tty->termios.c_lflag & ECHOCTL && ASCII_IS_CTRL(c))
{
// display as ^<char>, like ^C
tty->ops->putchar(tty, '^');
if(c < 0x20)
c += 0x40;
else
c -= 0x40;
}
tty->ops->putchar(tty, c);
}
// erase a character previsously echoed
static inline void tty_echo_erase(struct tty *tty, char c) {
// erase with BS, SPACE and BS
tty->ops->tty_write(tty, "\b \b", 3);
// twice if it was an escaped ASCII char
if(tty->termios.c_lflag & ECHOCTL && ASCII_IS_CTRL(c))
tty->ops->tty_write(tty, "\b \b", 3);
}
int tty_input_char(struct tty *tty, char c) {
int discard=0;
const struct termios *ios = & tty->termios;
// do not check character '\0', which is used to disable a control char
if(c != '\0') {
// two main cases where we have to check the added character
if(ios->c_lflag & ISIG) {
// check for generating signals
if(c == ios->c_cc[VINTR]) {
// SIGINT
if(tty->fpgid != 0)
signal_pgid_raise(tty->fpgid, SIGINT);
discard = 1;
tty_echo_char(tty, c);
}
else if(c == ios->c_cc[VQUIT]) {
// SIGQUIT TODO
discard = 1;
tty_echo_char(tty, c);
}
else if(c == ios->c_cc[VSUSP]) {
// SIGTSTP (FIXME instead of SIGSTOP?)
if(tty->fpgid != 0)
signal_pgid_raise(tty->fpgid, SIGSTOP);
discard = 1;
tty_echo_char(tty, c);
}
}
if(ios->c_lflag & ICANON) {
// check for canonical processing
if(c == ios->c_cc[VERASE]) {
// remove a single char
discard = 1;
if(tty->line_pos > 0) {
tty->line_pos--;
if(ios->c_lflag & ECHOE)
tty_echo_erase(tty, tty->line_buf[tty->line_pos]);
}
}
else if(c == ios->c_cc[VKILL]) {
// remove the whole line
discard = 1;
// under linux, this is ECHOKE behavior (but POSIX-compliant)
if(ios->c_lflag & ECHOK) {
while(tty->line_pos > 0) {
tty->line_pos--;
tty_echo_erase(tty, tty->line_buf[tty->line_pos]);
}
}
}
else if(c == ios->c_cc[VEOF]) {
// end the line and force return 0-count value if needed!
// TODO
discard = 1;
}
else if(c == '\n' || c == ios->c_cc[VEOL]
|| (c == '\r'/*&& TODO check ICRNL set and IGNCR isn't*/) ) {
// end the line (done after)
c = '\n';
}
// VSTART and VSTOP not checked (useless?)
}
// if not discared, add the character to the line buffer
if(!discard) {
if(tty->line_pos + 1 < TTY_LINE_BUFFER) {
int flush_line = 0;
tty->line_buf[tty->line_pos] = c;
tty->line_pos++;
if(ios->c_lflag & ECHO)
tty_echo_char(tty, c);
// determine if line buffer contains a valid input
if(ios->c_lflag & ICANON) {
// after a newline
if(c == '\n') {
flush_line = 1;
// NL should be echoed if ECHONL is set
if(!(ios->c_lflag & ECHO) && ios->c_lflag & ECHONL)
tty_echo_char(tty, c);
}
}
else {
// should check conditions on VMIN and VTIME
// FIXME VTIME not handled at all!
if(tty->line_pos >= ios->c_cc[VMIN])
flush_line = 1;
}
if(flush_line == 1) {
// add the line buffer to the input one, wakeup waiters
cfifo_push(& tty->fifo, tty->line_buf, tty->line_pos);
tty->line_pos = 0;
wqueue_wakeup(& tty->wqueue);
}
}
}
}
return 0;
}
int tty_read(struct tty *tty, char *dest, size_t len) {
// TODO atomic fifo access
int curlen = 0;
volatile size_t *fifo_size = &(tty->fifo.size);
// FIXME check behavior : should return the first line available
//while(curlen < len) {
size_t readlen;
wait_until_condition(& tty->wqueue, (readlen=*fifo_size) > 0);
// at least 1 byte available in the FIFO
readlen = readlen + curlen > len ? len - curlen : readlen;
cfifo_pop(& tty->fifo, dest, readlen);
curlen += readlen;
//}
return curlen;
}
......@@ -10,9 +10,16 @@
#include <interface/fixos/tty.h>
#include <sys/process.h>
#include <interface/fixos/errno.h>
#include <utils/cyclic_fifo.h>
#include <sys/waitqueue.h>
struct tty_ops;
// size of the input buffers
#define TTY_INPUT_BUFFER 256
#define TTY_LINE_BUFFER 128
struct tty {
// pid of the process that control the given TTY (should be a session leader,
// but sessions are not implemented currently)
......@@ -21,6 +28,20 @@ struct tty {
// foreground process group
pid_t fpgid;
// terminal I/O control settings
struct termios termios;
// input buffering with cyclic buffer
char fifo_buf[TTY_INPUT_BUFFER];
struct cyclic_fifo fifo;
// for line edition (canonical mode...)
char line_buf[TTY_LINE_BUFFER];
int line_pos;
// wait queue used to wait for available input
struct wait_queue wqueue;
// tty-specific operations
const struct tty_ops *ops;
......@@ -32,6 +53,10 @@ struct tty {
struct tty_ops {
int (*is_ready) (struct tty *tty);
int (*tty_write) (struct tty *tty, const char *data, size_t len);
// read/write a single character
int (*getchar) (struct tty *tty);
int (*putchar) (struct tty *tty, char c);
// ioctl support
int (*ioctl_setwinsize)(struct tty *tty, const struct winsize *size);
......@@ -39,6 +64,12 @@ struct tty_ops {
};
/**
* Initialize given tty structure with default values.
*/
int tty_default_init(struct tty *tty);
extern inline int tty_is_ready(struct tty *tty) {
if(tty->ops->is_ready == NULL)
return 1;
......@@ -51,6 +82,8 @@ extern inline int tty_write(struct tty *tty, const char *data, size_t len) {
return tty->ops->tty_write(tty, data, len);
}
int tty_read(struct tty *tty, char *dest, size_t len);
extern inline int tty_getwinsize(struct tty *tty, struct winsize *size) {
if(tty->ops->ioctl_getwinsize == NULL) {
......@@ -118,8 +151,17 @@ extern inline int tty_getsid(struct tty *tty, pid_t *sid) {
}
// used to dispatch tty-level ioctls using tty->ops and tty data
// return special value -EFAULT if ioctl is not tty-level command
/**
* Used to dispatch tty-level ioctls using tty->ops and tty data.
* return special value -EFAULT if ioctl is not tty-level command
*/
int tty_ioctl(struct tty *tty, int cmd, void *data);
/**
* Add a character to given tty input, and do appropriate job with it
* (canonical mode, echo, signal generation...) depending termios values.
*/
int tty_input_char(struct tty *tty, char c);
#endif //_SYS_TTY_H
......@@ -61,11 +61,16 @@ typedef __kernel_time_t time_t;
( (type *) ((void*)(element) - offsetof(type, field)) )
// special control characters (^A, ^B,... ^[)
// special control characters (^A, ^B,... ^[ and ^?)
#define ASCII_CTRL(c) \
((c) - '@')
((c) == '?' ? 0x7f : (c) - '@')
#define ASCII_UNCTRL(c) \
((c) + '@')
((c) == 0x7f ? '?' : (c) + '@')
#define ASCII_IS_CTRL(c) \
( ((unsigned char)(c) < 0x20 && (c) != '\n' && (c) != '\t') \
|| (c) == '\x7f' \
)
// temporary location
// TODO move them to a more consistant place
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment