[BACK]Return to tty.c CVS log [TXT][DIR] Up to [local] / prex / dev / gen

File: [local] / prex / dev / gen / tty.c (download)

Revision 1.1, Tue Aug 19 12:46:47 2008 UTC (15 years, 8 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*-
 * Copyright (c) 1982, 1986, 1990, 1991, 1993
 *	The Regents of the University of California.  All rights reserved.
 * (c) UNIX System Laboratories, Inc.
 * All or some portions of this file are derived from material licensed
 * to the University of California by American Telephone and Telegraph
 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
 * the permission of UNIX System Laboratories, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)tty.c	8.13 (Berkeley) 1/9/95
 */

/*
 * tty.c - TTY device
 */

#include <driver.h>
#include <sys/signal.h>
#include <sys/tty.h>
#include <sys/termios.h>

#define	FREAD		0x0001
#define	FWRITE		0x0002

void tty_output(int c, struct tty *tp);
static int tty_init(void);

/*
 * Driver structure
 */
struct driver tty_drv = {
	/* name */	"TTY device",
	/* order */	10,		/* must be larger than console */
	/* init */	tty_init,
};

static device_t tty_dev;

/* task to dispatch the tty signal */
static task_t sig_task;

/* default control characters */
static cc_t	ttydefchars[NCCS] = TTYDEFCHARS;

#define is_ctrl(c)   ((c) < 32 || (c) == 0x7f)

/*
 * TTY queue operations
 */
#define ttyq_next(i)	(((i) + 1) & (TTYQ_SIZE - 1))
#define ttyq_prev(i)	(((i) - 1) & (TTYQ_SIZE - 1))
#define ttyq_full(q)	((q)->tq_count >= TTYQ_SIZE)
#define ttyq_empty(q)	((q)->tq_count == 0)

/*
 * Get a character from a queue.
 */
int
ttyq_getc(struct tty_queue *tq)
{
	int c;

	if (ttyq_empty(tq))
		return -1;
	irq_lock();
	c = tq->tq_buf[tq->tq_head];
	tq->tq_head = ttyq_next(tq->tq_head);
	tq->tq_count--;
	irq_unlock();
	return c;
}

/*
 * Put a character into a queue.
 */
void
ttyq_putc(int c, struct tty_queue *tq)
{

	if (ttyq_full(tq))
		return;
	irq_lock();
	tq->tq_buf[tq->tq_tail] = c;
	tq->tq_tail = ttyq_next(tq->tq_tail);
	tq->tq_count++;
	irq_unlock();
}

/*
 * Remove the last character in a queue and return it.
 */
int
ttyq_unputc(struct tty_queue *tq)
{
	int c;

	if (ttyq_empty(tq))
		return -1;
	irq_lock();
	tq->tq_tail = ttyq_prev(tq->tq_tail);
	c = tq->tq_buf[tq->tq_tail];
	tq->tq_count--;
	irq_unlock();
	return c;
}

/*
 * Put the chars in the from queue on the end of the to queue.
 */
static void
tty_catq(struct tty_queue *from, struct tty_queue *to)
{
	int c;

	while ((c = ttyq_getc(from)) != -1)
		ttyq_putc(c, to);
}

/*
 * Rubout one character from the rawq of tp
 */
static void
tty_rubout(struct tty *tp)
{

	if (!(tp->t_lflag & ECHO))
		return;
	if (tp->t_lflag & ECHOE) {
		tty_output('\b', tp);
		tty_output(' ', tp);
		tty_output('\b', tp);
	} else
		tty_output(tp->t_cc[VERASE], tp);
}

/*
 * Echo char
 */
static void
tty_echo(int c, struct tty *tp)
{

	if (!(tp->t_lflag & ECHO)) {
		if (c == '\n' && (tp->t_lflag & ECHONL))
			tty_output('\n', tp);
		return;
	}
	if (is_ctrl(c) && c != '\n' && c != '\t' && c != '\b') {
		tty_output('^', tp);
		tty_output(c + 'A' - 1, tp);
	} else
		tty_output(c, tp);
}

/*
 * Start output.
 */
static void
tty_start(struct tty *tp)
{

	if (tp->t_state & TS_TTSTOP)
		return;
	if (tp->t_output != NULL)
		(*tp->t_output)(tp);
}

/*
 * Flush tty read and/or write queues, notifying anyone waiting.
 */
static void
tty_flush(struct tty *tp, int rw)
{

	if (rw & FREAD)
		sched_wakeup(&tp->t_input_event);
	if (rw & FWRITE) {
		tp->t_state &= ~TS_TTSTOP;
		tty_start(tp);
	}
}

/*
 * Process input of a single character received on a tty.
 * echo if required.
 * This will be called with interrupt level.
 */
void
tty_input(int c, struct tty *tp)
{
	unsigned char *cc;
	tcflag_t iflag, lflag;
	int sig = -1;

	lflag = tp->t_lflag;
	iflag = tp->t_iflag;
	cc = tp->t_cc;

	/* IGNCR, ICRNL, INLCR */
	if (c == '\r') {
		if (iflag & IGNCR)
			goto endcase;
		else if (iflag & ICRNL)
			c = '\n';
	} else if (c == '\n' && (iflag & INLCR))
		c = '\r';

	if (iflag & IXON) {
		/* stop (^S) */
		if (c == cc[VSTOP]) {
			if (!(tp->t_state & TS_TTSTOP)) {
				tp->t_state |= TS_TTSTOP;
				return;
			}
			if (c != cc[VSTART])
				return;
			/* if VSTART == VSTOP then toggle */
			goto endcase;
		}
		/* start (^Q) */
		if (c == cc[VSTART])
			goto restartoutput;
	}
	if (lflag & ICANON) {
		/* erase (^H / ^?) or backspace */
		if (c == cc[VERASE] || c == '\b') {
			if (!ttyq_empty(&tp->t_rawq)) {
				ttyq_unputc(&tp->t_rawq);
				tty_rubout(tp);
			}
			goto endcase;
		}
		/* kill (^U) */
		if (c == cc[VKILL]) {
			while (!ttyq_empty(&tp->t_rawq)) {
				ttyq_unputc(&tp->t_rawq);
				tty_rubout(tp);
			}
			goto endcase;
		}
	}
	if (lflag & ISIG) {
		/* quit (^C) */
		if (c == cc[VINTR] || c == cc[VQUIT]) {
			if (!(lflag & NOFLSH))
				tty_flush(tp, FREAD | FWRITE);
			tty_echo(c, tp);
			sig = (c == cc[VINTR]) ? SIGINT : SIGQUIT;
			goto endcase;
		}
		/* suspend (^Z) */
		if (c == cc[VSUSP]) {
			if (!(lflag & NOFLSH))
				tty_flush(tp, FREAD | FWRITE);
			tty_echo(c, tp);
			sig = SIGTSTP;
			goto endcase;
		}
	}

	/*
	 * Check for input buffer overflow
	 */
	if (ttyq_full(&tp->t_rawq)) {
		tty_flush(tp, FREAD | FWRITE);
		goto endcase;
	}
	ttyq_putc(c, &tp->t_rawq);

	if (lflag & ICANON) {
		if (c == '\n' || c == cc[VEOF] || c == cc[VEOL]) {
			tty_catq(&tp->t_rawq, &tp->t_canq);
			sched_wakeup(&tp->t_input_event);
		}
	} else
		sched_wakeup(&tp->t_input_event);

	if (lflag & ECHO)
		tty_echo(c, tp);
 endcase:
	/*
	 * IXANY means allow any character to restart output.
	 */
	if ((tp->t_state & TS_TTSTOP) && (iflag & IXANY) == 0 &&
	    cc[VSTART] != cc[VSTOP])
		return;
 restartoutput:
	tp->t_state &= ~TS_TTSTOP;

	if (sig != -1) {
		if (sig_task)
			exception_post(sig_task, sig);
	}
	tty_start(tp);
}

/*
 * Output a single character on a tty, doing output processing
 * as needed (expanding tabs, newline processing, etc.).
 */
void
tty_output(int c, struct tty *tp)
{
	int i, col;

	if ((tp->t_lflag & ICANON) == 0) {
		ttyq_putc(c, &tp->t_outq);
		return;
	}
	/* Expand tab */
	if (c == '\t' && (tp->t_oflag & OXTABS)) {
		i = 8 - (tp->t_column & 7);
		tp->t_column += i;
		do
			ttyq_putc(' ', &tp->t_outq);
		while (--i > 0);
		return;
	}
	/* Translate newline into "\r\n" */
	if (c == '\n' && (tp->t_oflag & ONLCR))
		ttyq_putc('\r', &tp->t_outq);

	ttyq_putc(c, &tp->t_outq);

	col = tp->t_column;
	switch (c) {
	case '\b':	/* back space */
		if (col > 0)
			--col;
		break;
	case '\t':	/* tab */
		col = (col + 8) & ~7;
		break;
	case '\n':	/* newline */
		col = 0;
		break;
	case '\r':	/* return */
		col = 0;
		break;
	default:
		if (!is_ctrl(c))
		    ++col;
		break;
	}
	tp->t_column = col;
	return;
}

/*
 * Process a read call on a tty device.
 */
int
tty_read(struct tty *tp, char *buf, size_t *nbyte)
{
	unsigned char *cc;
	struct tty_queue *qp;
	int rc, c;
	size_t count = 0;
	tcflag_t lflag;

	lflag = tp->t_lflag;
	cc = tp->t_cc;
	qp = (lflag & ICANON) ? &tp->t_canq : &tp->t_rawq;

	/* If there is no input, wait it */
	while (ttyq_empty(qp)) {
		rc = sched_sleep(&tp->t_input_event);
		if (rc == SLP_INTR)
			return EINTR;
	}
	while (count < *nbyte) {
		if ((c = ttyq_getc(qp)) == -1)
			break;
		if (c == cc[VEOF] && (lflag & ICANON))
			break;
		count++;
		if (umem_copyout(&c, buf, 1))
			return EFAULT;
		if ((lflag & ICANON) && (c == '\n' || c == cc[VEOL]))
			break;
		buf++;
	}
	*nbyte = count;
	return 0;
}

/*
 * Process a write call on a tty device.
 */
int
tty_write(struct tty *tp, char *buf, size_t *nbyte)
{
	size_t remain, count = 0;
	int c;

	remain = *nbyte;
	while (remain > 0) {
		if (tp->t_outq.tq_count >= TTYQ_HIWAT) {
			tty_start(tp);
			continue;
		}
		if (umem_copyin(buf, &c, 1))
			return EFAULT;
		tty_output(c, tp);
		buf++;
		remain--;
		count++;
	}
	tty_start(tp);
	*nbyte = count;
	return 0;
}

/*
 * Ioctls for all tty devices.
 */
int
tty_ioctl(struct tty *tp, u_long cmd, void *data)
{
	int flags;

	switch (cmd) {
	case TIOCGETA:
		if (umem_copyout(&tp->t_termios, data,
				 sizeof(struct termios)))
			return EFAULT;
		break;
	case TIOCSETAW:
	case TIOCSETAF:
		tty_flush(tp, flags);
		/* FALLTHROUGH */
	case TIOCSETA:
		if (umem_copyin(data, &tp->t_termios,
				 sizeof(struct termios)))
			return EFAULT;
		break;
	case TIOCSPGRP:			/* set pgrp of tty */
		if (umem_copyin(data, &tp->t_pgid, sizeof(pid_t)))
			return EFAULT;
		break;
	case TIOCGPGRP:
		if (umem_copyout(&tp->t_pgid, data, sizeof(pid_t)))
			return EFAULT;
		break;
	case TIOCFLUSH:
		if (umem_copyin(data, &flags, sizeof(flags)))
			return EFAULT;
		break;
		if (flags == 0)
			flags = FREAD | FWRITE;
		else
			flags &= FREAD | FWRITE;
		tty_flush(tp, flags);
		break;
	case TIOCSTART:
		if (tp->t_state & TS_TTSTOP) {
			tp->t_state &= ~TS_TTSTOP;
			tty_start(tp);
		}
		break;
	case TIOCSTOP:
		if (!(tp->t_state & TS_TTSTOP)) {
			tp->t_state |= TS_TTSTOP;
		}
		break;
	case TIOCGWINSZ:
		if (umem_copyout(&tp->t_winsize, data,
				 sizeof(struct winsize)))
			return EFAULT;
		break;
	case TIOCSWINSZ:
		if (umem_copyin(&tp->t_winsize, data,
				 sizeof(struct winsize)))
			return EFAULT;
		break;
	case TIOCSETSIGT:
		if (umem_copyin(data, &sig_task, sizeof(task_t)))
			return EFAULT;
		break;
	}
	return 0;
}

/*
 * Register tty device.
 */
int
tty_register(struct devio *io, struct tty *tp, void (*output)(struct tty*))
{

	/* We support only one tty device */
	if (tty_dev != DEVICE_NULL)
		return -1;

	/* Create TTY device as an alias of the registered device. */
	tty_dev = device_create(io, "tty", DF_CHR);
	if (tty_dev == DEVICE_NULL)
		return -1;

	/* Initialize tty */
	memset(tp, 0, sizeof(struct tty));
	memcpy(&tp->t_termios.c_cc, ttydefchars, sizeof(ttydefchars));
	event_init(&tp->t_input_event, "TTY input");
	tp->t_iflag = TTYDEF_IFLAG;
	tp->t_oflag = TTYDEF_OFLAG;
	tp->t_cflag = TTYDEF_CFLAG;
	tp->t_lflag = TTYDEF_LFLAG;
	tp->t_ispeed = TTYDEF_SPEED;
	tp->t_ospeed = TTYDEF_SPEED;
	tp->t_output = output;
	return 0;
}

/*
 * Init
 */
static int
tty_init(void)
{

	return 0;
}