blob: c4b3a7795db0157f7dc29667d2e9baf1e3679986 [file] [log] [blame]
/*
* linux/kernel/tty_io.c
*
* (C) 1991 Linus Torvalds
*/
/*
* 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
* or rs-channels. It also implements echoing, cooked mode etc.
*
* Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0.
*/
#include <errno.h>
#include <signal.h>
#include <linux/fcntl.h>
#define ALRMMASK (1<<(SIGALRM-1))
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include "vt_kern.h"
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#define QUEUES (3*(NR_CONSOLES+NR_SERIALS+2*NR_PTYS))
static struct tty_queue * tty_queues;
struct tty_struct tty_table[256];
#define con_queues tty_queues
#define rs_queues ((3*NR_CONSOLES) + tty_queues)
#define mpty_queues ((3*(NR_CONSOLES+NR_SERIALS)) + tty_queues)
#define spty_queues ((3*(NR_CONSOLES+NR_SERIALS+NR_PTYS)) + tty_queues)
#define con_table tty_table
#define rs_table (64+tty_table)
#define mpty_table (128+tty_table)
#define spty_table (192+tty_table)
/*
* fg_console is the current virtual console,
* redirect is the pseudo-tty that console output
* is redirected to if asked by TIOCCONS.
*/
int fg_console = 0;
struct tty_struct * redirect = NULL;
/*
* these are the tables used by the machine code handlers.
* you can implement virtual consoles.
*/
struct tty_queue * table_list[] = { NULL, NULL };
void put_tty_queue(char c, struct tty_queue * queue)
{
int head;
unsigned long flags;
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
head = (queue->head + 1) & (TTY_BUF_SIZE-1);
if (head != queue->tail) {
queue->buf[queue->head] = c;
queue->head = head;
}
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
}
int get_tty_queue(struct tty_queue * queue)
{
int result = -1;
unsigned long flags;
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
if (queue->tail != queue->head) {
result = 0xff & queue->buf[queue->tail];
queue->tail = (queue->tail + 1) & (TTY_BUF_SIZE-1);
}
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
return result;
}
void tty_write_flush(struct tty_struct * tty)
{
unsigned long flags;
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
if (!EMPTY(tty->write_q) && !(TTY_WRITE_BUSY & tty->flags)) {
tty->flags |= TTY_WRITE_BUSY;
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
tty->write(tty);
cli();
tty->flags &= ~TTY_WRITE_BUSY;
}
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
}
void tty_read_flush(struct tty_struct * tty)
{
unsigned long flags;
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
if (!EMPTY(tty->read_q) && !(TTY_READ_BUSY & tty->flags)) {
tty->flags |= TTY_READ_BUSY;
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
copy_to_cooked(tty);
cli();
tty->flags &= ~TTY_READ_BUSY;
}
__asm__ __volatile__("pushl %0 ; popfl"::"r" (flags));
}
void change_console(unsigned int new_console)
{
if (vt_cons[fg_console].vt_mode == KD_GRAPHICS)
return;
if (new_console == fg_console || new_console >= NR_CONSOLES)
return;
table_list[0] = con_queues + 0 + new_console*3;
table_list[1] = con_queues + 1 + new_console*3;
update_screen(new_console);
}
static void sleep_if_empty(struct tty_queue * queue)
{
cli();
while (!(current->signal & ~current->blocked) && EMPTY(queue))
interruptible_sleep_on(&queue->proc_list);
sti();
}
void wait_for_keypress(void)
{
sleep_if_empty(tty_table[fg_console].secondary);
}
void copy_to_cooked(struct tty_struct * tty)
{
int c;
if (!(tty && tty->write && tty->read_q &&
tty->write_q && tty->secondary)) {
printk("copy_to_cooked: missing queues\n\r");
return;
}
while (1) {
if (FULL(tty->secondary))
break;
c = GETCH(tty->read_q);
if (c < 0)
break;
if (I_STRP(tty))
c &= 0x7f;
if (c==13) {
if (I_CRNL(tty))
c=10;
else if (I_NOCR(tty))
continue;
} else if (c==10 && I_NLCR(tty))
c=13;
if (I_UCLC(tty))
c=tolower(c);
if (L_CANON(tty)) {
if ((KILL_CHAR(tty) != __DISABLED_CHAR) &&
(c==KILL_CHAR(tty))) {
/* deal with killing the input line */
while(!(EMPTY(tty->secondary) ||
(c=LAST(tty->secondary))==10 ||
((EOF_CHAR(tty) != __DISABLED_CHAR) &&
(c==EOF_CHAR(tty))))) {
if (L_ECHO(tty)) {
if (c<32) {
PUTCH(8,tty->write_q);
PUTCH(' ',tty->write_q);
PUTCH(8,tty->write_q);
}
PUTCH(8,tty->write_q);
PUTCH(' ',tty->write_q);
PUTCH(8,tty->write_q);
TTY_WRITE_FLUSH(tty);
}
DEC(tty->secondary->head);
}
continue;
}
if ((ERASE_CHAR(tty) != __DISABLED_CHAR) &&
(c==ERASE_CHAR(tty))) {
if (EMPTY(tty->secondary) ||
(c=LAST(tty->secondary))==10 ||
((EOF_CHAR(tty) != __DISABLED_CHAR) &&
(c==EOF_CHAR(tty))))
continue;
if (L_ECHO(tty)) {
if (c<32) {
PUTCH(8,tty->write_q);
PUTCH(' ',tty->write_q);
PUTCH(8,tty->write_q);
}
PUTCH(8,tty->write_q);
PUTCH(32,tty->write_q);
PUTCH(8,tty->write_q);
TTY_WRITE_FLUSH(tty);
}
DEC(tty->secondary->head);
continue;
}
}
if (I_IXON(tty)) {
if ((STOP_CHAR(tty) != __DISABLED_CHAR) &&
(c==STOP_CHAR(tty))) {
tty->stopped=1;
continue;
}
if ((START_CHAR(tty) != __DISABLED_CHAR) &&
(c==START_CHAR(tty))) {
tty->stopped=0;
TTY_WRITE_FLUSH(tty);
continue;
}
}
if (L_ISIG(tty)) {
if ((INTR_CHAR(tty) != __DISABLED_CHAR) &&
(c==INTR_CHAR(tty))) {
kill_pg(tty->pgrp, SIGINT, 1);
flush_input(tty);
continue;
}
if ((QUIT_CHAR(tty) != __DISABLED_CHAR) &&
(c==QUIT_CHAR(tty))) {
kill_pg(tty->pgrp, SIGQUIT, 1);
flush_input(tty);
continue;
}
if ((SUSPEND_CHAR(tty) != __DISABLED_CHAR) &&
(c==SUSPEND_CHAR(tty))) {
if (!is_orphaned_pgrp(tty->pgrp))
kill_pg(tty->pgrp, SIGTSTP, 1);
continue;
}
}
if (c==10 || (EOF_CHAR(tty) != __DISABLED_CHAR &&
c==EOF_CHAR(tty)))
tty->secondary->data++;
if ((L_ECHO(tty) || (L_CANON(tty) && L_ECHONL(tty))) && (c==10)) {
PUTCH(10,tty->write_q);
PUTCH(13,tty->write_q);
} else if (L_ECHO(tty)) {
if (c<32 && L_ECHOCTL(tty)) {
PUTCH('^',tty->write_q);
PUTCH(c+64,tty->write_q);
} else
PUTCH(c,tty->write_q);
}
PUTCH(c,tty->secondary);
TTY_WRITE_FLUSH(tty);
}
TTY_WRITE_FLUSH(tty);
if (!EMPTY(tty->secondary))
wake_up(&tty->secondary->proc_list);
if (LEFT(tty->write_q) > TTY_BUF_SIZE/2)
wake_up(&tty->write_q->proc_list);
}
int is_ignored(int sig)
{
return ((current->blocked & (1<<(sig-1))) ||
(current->sigaction[sig-1].sa_handler == SIG_IGN));
}
/*
* Called when we need to send a SIGTTIN or SIGTTOU to our process
* group
*
* We only request that a system call be restarted if there was if the
* default signal handler is being used. The reason for this is that if
* a job is catching SIGTTIN or SIGTTOU, the signal handler may not want
* the system call to be restarted blindly. If there is no way to reset the
* terminal pgrp back to the current pgrp (perhaps because the controlling
* tty has been released on logout), we don't want to be in an infinite loop
* while restarting the system call, and have it always generate a SIGTTIN
* or SIGTTOU. The default signal handler will cause the process to stop
* thus avoiding the infinite loop problem. Presumably the job-control
* cognizant parent will fix things up before continuging its child process.
*/
int tty_signal(int sig, struct tty_struct *tty)
{
(void) kill_pg(current->pgrp,sig,1);
return -ERESTARTSYS;
}
static int read_chan(unsigned int channel, struct file * file, char * buf, int nr)
{
struct tty_struct * tty;
struct tty_struct * other_tty = NULL;
int c;
char * b=buf;
int minimum,time;
if (channel > 255)
return -EIO;
tty = TTY_TABLE(channel);
if (!(tty->read_q && tty->secondary))
return -EIO;
if ((tty->pgrp > 0) &&
(current->tty == channel) &&
(tty->pgrp != current->pgrp))
if (is_ignored(SIGTTIN) || is_orphaned_pgrp(current->pgrp))
return -EIO;
else
return(tty_signal(SIGTTIN, tty));
if (channel & 0x80)
other_tty = tty_table + (channel ^ 0x40);
time = 10L*tty->termios.c_cc[VTIME];
minimum = tty->termios.c_cc[VMIN];
if (L_CANON(tty)) {
minimum = nr;
current->timeout = 0xffffffff;
time = 0;
} else if (minimum)
current->timeout = 0xffffffff;
else {
minimum = nr;
if (time)
current->timeout = time + jiffies;
time = 0;
}
if (file->f_flags & O_NONBLOCK)
time = current->timeout = 0;
if (minimum>nr)
minimum = nr;
TTY_READ_FLUSH(tty);
while (nr>0) {
if (other_tty && other_tty->write)
TTY_WRITE_FLUSH(other_tty);
cli();
if (EMPTY(tty->secondary) || (L_CANON(tty) &&
!FULL(tty->read_q) && !tty->secondary->data)) {
if (!current->timeout)
break;
if (current->signal & ~current->blocked)
break;
if (IS_A_PTY_SLAVE(channel) && C_HUP(other_tty))
break;
if (other_tty && !other_tty->count)
break;
interruptible_sleep_on(&tty->secondary->proc_list);
sti();
TTY_READ_FLUSH(tty);
continue;
}
sti();
do {
c = GETCH(tty->secondary);
if ((EOF_CHAR(tty) != __DISABLED_CHAR &&
c==EOF_CHAR(tty)) || c==10)
tty->secondary->data--;
if ((EOF_CHAR(tty) != __DISABLED_CHAR &&
c==EOF_CHAR(tty)) && L_CANON(tty))
break;
else {
put_fs_byte(c,b++);
if (!--nr)
break;
}
if (c==10 && L_CANON(tty))
break;
} while (nr>0 && !EMPTY(tty->secondary));
wake_up(&tty->read_q->proc_list);
if (L_CANON(tty) || b-buf >= minimum)
break;
if (time)
current->timeout = time+jiffies;
}
sti();
TTY_READ_FLUSH(tty);
if (other_tty && other_tty->write)
TTY_WRITE_FLUSH(other_tty);
current->timeout = 0;
if (b-buf)
return b-buf;
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
return 0;
}
static int write_chan(unsigned int channel, struct file * file, char * buf, int nr)
{
struct tty_struct * tty;
char c, *b=buf;
if (channel > 255)
return -EIO;
tty = TTY_TABLE(channel);
if (L_TOSTOP(tty) && (tty->pgrp > 0) &&
(current->tty == channel) && (tty->pgrp != current->pgrp)) {
if (is_orphaned_pgrp(tty->pgrp))
return -EIO;
if (!is_ignored(SIGTTOU))
return tty_signal(SIGTTOU, tty);
}
if (nr < 0)
return -EINVAL;
if (!nr)
return 0;
if (redirect && tty == TTY_TABLE(0))
tty = redirect;
if (!(tty->write_q && tty->write))
return -EIO;
while (nr>0) {
if (current->signal & ~current->blocked)
break;
if (FULL(tty->write_q)) {
TTY_WRITE_FLUSH(tty);
cli();
if (FULL(tty->write_q))
interruptible_sleep_on(&tty->write_q->proc_list);
sti();
continue;
}
while (nr>0 && !FULL(tty->write_q)) {
c=get_fs_byte(b);
if (O_POST(tty)) {
if (c=='\r' && O_CRNL(tty))
c='\n';
else if (c=='\n' && O_NLRET(tty))
c='\r';
if (c=='\n' && !(tty->flags & TTY_CR_PENDING) && O_NLCR(tty)) {
tty->flags |= TTY_CR_PENDING;
PUTCH(13,tty->write_q);
continue;
}
if (O_LCUC(tty))
c=toupper(c);
}
b++; nr--;
tty->flags &= ~TTY_CR_PENDING;
PUTCH(c,tty->write_q);
}
if (nr>0)
schedule();
}
TTY_WRITE_FLUSH(tty);
if (b-buf)
return b-buf;
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
return 0;
}
static int tty_read(struct inode * inode, struct file * file, char * buf, int count)
{
int i;
i = read_chan(current->tty,file,buf,count);
if (i > 0)
inode->i_atime = CURRENT_TIME;
return i;
}
static int ttyx_read(struct inode * inode, struct file * file, char * buf, int count)
{
int i;
i = read_chan(MINOR(inode->i_rdev),file,buf,count);
if (i > 0)
inode->i_atime = CURRENT_TIME;
return i;
}
static int tty_write(struct inode * inode, struct file * file, char * buf, int count)
{
int i;
i = write_chan(current->tty,file,buf,count);
if (i > 0)
inode->i_mtime = CURRENT_TIME;
return i;
}
static int ttyx_write(struct inode * inode, struct file * file, char * buf, int count)
{
int i;
i = write_chan(MINOR(inode->i_rdev),file,buf,count);
if (i > 0)
inode->i_mtime = CURRENT_TIME;
return i;
}
static int tty_lseek(struct inode * inode, struct file * file, off_t offset, int orig)
{
return -EBADF;
}
/*
* tty_open and tty_release keep up the tty count that contains the
* number of opens done on a tty. We cannot use the inode-count, as
* different inodes might point to the same tty.
*
* Open-counting is needed for pty masters, as well as for keeping
* track of serial lines: DTR is dropped when the last close happens.
*/
static int tty_open(struct inode * inode, struct file * filp)
{
struct tty_struct *tty;
int dev, retval;
dev = inode->i_rdev;
if (MAJOR(dev) == 5)
dev = current->tty;
else
dev = MINOR(dev);
if (dev < 0)
return -ENODEV;
tty = TTY_TABLE(dev);
if (IS_A_PTY_MASTER(dev)) {
if (tty->count)
return -EAGAIN;
}
if (!tty->count && (!tty->link || !tty->link->count)) {
flush_input(tty);
flush_output(tty);
}
tty->count++;
retval = 0;
if (!(filp->f_flags & O_NOCTTY) &&
current->leader &&
current->tty<0 &&
tty->session==0) {
current->tty = dev;
tty->session = current->session;
tty->pgrp = current->pgrp;
}
if (IS_A_SERIAL(dev))
retval = serial_open(dev-64,filp);
else if (IS_A_PTY(dev))
retval = pty_open(dev,filp);
if (retval)
tty->count--;
return retval;
}
static void tty_release(struct inode * inode, struct file * filp)
{
int dev;
struct tty_struct * tty;
dev = inode->i_rdev;
if (MAJOR(dev) == 5)
dev = current->tty;
else
dev = MINOR(dev);
if (dev < 0)
return;
tty = TTY_TABLE(dev);
if (--tty->count)
return;
if (tty == redirect)
redirect = NULL;
if (IS_A_SERIAL(dev))
serial_close(dev-64,filp);
else if (IS_A_PTY(dev))
pty_close(dev,filp);
}
static struct file_operations tty_fops = {
tty_lseek,
tty_read,
tty_write,
NULL, /* tty_readdir */
NULL, /* tty_select */
tty_ioctl,
tty_open,
tty_release
};
static struct file_operations ttyx_fops = {
tty_lseek,
ttyx_read,
ttyx_write,
NULL, /* ttyx_readdir */
NULL, /* ttyx_select */
tty_ioctl, /* ttyx_ioctl */
tty_open,
tty_release
};
long tty_init(long kmem_start)
{
int i;
tty_queues = (struct tty_queue *) kmem_start;
kmem_start += QUEUES * (sizeof (struct tty_queue));
table_list[0] = con_queues + 0;
table_list[1] = con_queues + 1;
chrdev_fops[4] = &ttyx_fops;
chrdev_fops[5] = &tty_fops;
for (i=0 ; i < QUEUES ; i++)
tty_queues[i] = (struct tty_queue) {0,0,0,0,""};
for (i=0 ; i<256 ; i++) {
tty_table[i] = (struct tty_struct) {
{0, 0, 0, 0, 0, INIT_C_CC},
-1, 0, 0, 0, 0, {0,0,0,0},
NULL, NULL, NULL, NULL, NULL
};
}
kmem_start = con_init(kmem_start);
for (i = 0 ; i<NR_CONSOLES ; i++) {
con_table[i] = (struct tty_struct) {
{ICRNL, /* change incoming CR to NL */
OPOST|ONLCR, /* change outgoing NL to CRNL */
B38400 | CS8,
IXON | ISIG | ICANON | ECHO | ECHOCTL | ECHOKE,
0, /* console termio */
INIT_C_CC},
-1, /* initial pgrp */
0, /* initial session */
0, /* initial stopped */
0, /* initial flags */
0, /* initial count */
{video_num_lines,video_num_columns,0,0},
con_write,
NULL, /* other-tty */
con_queues+0+i*3,con_queues+1+i*3,con_queues+2+i*3
};
}
for (i = 0 ; i<NR_SERIALS ; i++) {
rs_table[i] = (struct tty_struct) {
{0, /* no translation */
0, /* no translation */
B2400 | CS8,
0,
0,
INIT_C_CC},
-1,
0,
0,
0,
0,
{25,80,0,0},
rs_write,
NULL, /* other-tty */
rs_queues+0+i*3,rs_queues+1+i*3,rs_queues+2+i*3
};
}
for (i = 0 ; i<NR_PTYS ; i++) {
mpty_table[i] = (struct tty_struct) {
{0, /* no translation */
0, /* no translation */
B9600 | CS8,
0,
0,
INIT_C_CC},
-1,
0,
0,
0,
0,
{25,80,0,0},
mpty_write,
spty_table+i,
mpty_queues+0+i*3,mpty_queues+1+i*3,mpty_queues+2+i*3
};
spty_table[i] = (struct tty_struct) {
{0, /* no translation */
0, /* no translation */
B9600 | CS8,
IXON | ISIG | ICANON,
0,
INIT_C_CC},
-1,
0,
0,
0,
0,
{25,80,0,0},
spty_write,
mpty_table+i,
spty_queues+0+i*3,spty_queues+1+i*3,spty_queues+2+i*3
};
}
kmem_start = rs_init(kmem_start);
printk("%d virtual consoles\n\r",NR_CONSOLES);
printk("%d pty's\n\r",NR_PTYS);
return kmem_start;
}