| /* |
| * 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; |
| } |