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

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
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -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;
+70 −162
Original line number Diff line number Diff line
#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,29 +358,39 @@ static void vt_read_escape_code(struct vt_instance *term, char str_char) {
	term->esc_state.discovery_state = newstate;
}


/**
 * Function used to print characters (as well written to term and echoed input from
 * keyboard).
 * If mayesc is not-zero, try to check VT100-like escape sequences from source data.
 * Echo a character to the screen, without flushing to display driver
 */
static void vt_term_print(struct vt_instance *term, const void *source, size_t len,
		int mayesc)
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'))
	{
	int i;
	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') {
		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(str[i] == '\r') term->posx=0;
		else if(c == '\r') term->posx=0;
		else {
				_tdisp->print_char(& term->disp, term->posx, term->posy, str[i]);
			_tdisp->print_char(& term->disp, term->posx, term->posy, c);
			term->posx++;
		}
		if(term->posx >= _tdisp->cwidth) {
@@ -411,104 +406,43 @@ static void vt_term_print(struct vt_instance *term, const void *source, size_t l
	}
	else {
		// parse the character as a part of a VT100-like escape sequence
			vt_read_escape_code(term, str[i]);
		vt_read_escape_code(term, c);
	}
}

	// print cursor at current position
	_tdisp->print_char(& term->disp, term->posx, term->posy, VT_CURSOR_CHAR);

	// TODO flush only if active!
	//disp_mono_copy_to_dd(_term_vram);
}



// 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, ' ');
/**
 * Function used to print characters (as well written to term and echoed input from
 * keyboard).
 * If mayesc is not-zero, try to check VT100-like escape sequences from source data.
 */
static void vt_term_print(struct vt_instance *term, const void *source, size_t len,
		int mayesc)
{
	int i;
	const unsigned char *str = source;

	term->posx--;
	if(term->posx < 0) {
		term->posx = _tdisp->cwidth - 1;
		term->posy--;
	for(i=0; i<len; i++) {
		vt_echo_char(term, str[i], mayesc);
	}

	// print cursor at current position
	_tdisp->print_char(& term->disp, term->posx, term->posy,
			VT_CURSOR_CHAR);
	_tdisp->print_char(& term->disp, term->posx, term->posy, VT_CURSOR_CHAR);

	_tdisp->flush(& term->disp);
	// TODO flush only if active!
	//disp_mono_copy_to_dd(_term_vram);
}


// 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++;

	// 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);
	}
	_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;
// 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);

		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);
	// TODO flush only if active?
	_tdisp->print_char(& term->disp, term->posx, term->posy, VT_CURSOR_CHAR);
	_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;
+202 −0
Original line number Diff line number Diff line
#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;
}
+44 −2
Original line number Diff line number Diff line
@@ -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;

@@ -33,12 +54,22 @@ 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);
	int (*ioctl_getwinsize)(struct tty *tty, struct winsize *size);
};


/**
 * 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
+8 −3
Original line number Diff line number Diff line
@@ -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