/*
       *  linux/drivers/char/tty_ioctl.c
       *
       *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
       *
       * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
       * which can be dynamically activated and de-activated by the line
       * discipline handling modules (like SLIP).
       */
      
      #include <linux/types.h>
      #include <linux/termios.h>
      #include <linux/errno.h>
      #include <linux/sched.h>
      #include <linux/kernel.h>
      #include <linux/major.h>
      #include <linux/tty.h>
      #include <linux/fcntl.h>
      #include <linux/string.h>
      #include <linux/mm.h>
      
      #include <asm/io.h>
      #include <asm/bitops.h>
      #include <asm/uaccess.h>
      #include <asm/system.h>
      
      #undef TTY_DEBUG_WAIT_UNTIL_SENT
      
      #undef	DEBUG
      #ifdef DEBUG
      # define	PRINTK(x)	printk (x)
      #else
      # define	PRINTK(x)	/**/
      #endif
      
      /*
       * Internal flag options for termios setting behavior
       */
      #define TERMIOS_FLUSH	1
      #define TERMIOS_WAIT	2
      #define TERMIOS_TERMIO	4
      
  43  void tty_wait_until_sent(struct tty_struct * tty, long timeout)
      {
      	DECLARE_WAITQUEUE(wait, current);
      
      #ifdef TTY_DEBUG_WAIT_UNTIL_SENT
      	char buf[64];
      	
      	printk("%s wait until sent...\n", tty_name(tty, buf));
      #endif
  52  	if (!tty->driver.chars_in_buffer)
  53  		return;
      	add_wait_queue(&tty->write_wait, &wait);
  55  	if (!timeout)
      		timeout = MAX_SCHEDULE_TIMEOUT;
  57  	do {
      #ifdef TTY_DEBUG_WAIT_UNTIL_SENT
      		printk("waiting %s...(%d)\n", tty_name(tty, buf),
      		       tty->driver.chars_in_buffer(tty));
      #endif
  62  		set_current_state(TASK_INTERRUPTIBLE);
  63  		if (signal_pending(current))
  64  			goto stop_waiting;
  65  		if (!tty->driver.chars_in_buffer(tty))
  66  			break;
      		timeout = schedule_timeout(timeout);
  68  	} while (timeout);
  69  	if (tty->driver.wait_until_sent)
      		tty->driver.wait_until_sent(tty, timeout);
      stop_waiting:
      	current->state = TASK_RUNNING;
      	remove_wait_queue(&tty->write_wait, &wait);
      }
      
  76  static void unset_locked_termios(struct termios *termios,
      				 struct termios *old,
      				 struct termios *locked)
      {
      	int	i;
      	
      #define NOSET_MASK(x,y,z) (x = ((x) & ~(z)) | ((y) & (z)))
      
  84  	if (!locked) {
      		printk("Warning?!? termios_locked is NULL.\n");
  86  		return;
      	}
      
      	NOSET_MASK(termios->c_iflag, old->c_iflag, locked->c_iflag);
      	NOSET_MASK(termios->c_oflag, old->c_oflag, locked->c_oflag);
      	NOSET_MASK(termios->c_cflag, old->c_cflag, locked->c_cflag);
      	NOSET_MASK(termios->c_lflag, old->c_lflag, locked->c_lflag);
      	termios->c_line = locked->c_line ? old->c_line : termios->c_line;
  94  	for (i=0; i < NCCS; i++)
      		termios->c_cc[i] = locked->c_cc[i] ?
      			old->c_cc[i] : termios->c_cc[i];
      }
      
  99  static void change_termios(struct tty_struct * tty, struct termios * new_termios)
      {
      	int canon_change;
      	struct termios old_termios = *tty->termios;
      
      	cli();
      	*tty->termios = *new_termios;
      	unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
      	canon_change = (old_termios.c_lflag ^ tty->termios->c_lflag) & ICANON;
 108  	if (canon_change) {
      		memset(&tty->read_flags, 0, sizeof tty->read_flags);
      		tty->canon_head = tty->read_tail;
      		tty->canon_data = 0;
      		tty->erasing = 0;
      	}
      	sti();
 115  	if (canon_change && !L_ICANON(tty) && tty->read_cnt)
      		/* Get characters left over from canonical mode. */
      		wake_up_interruptible(&tty->read_wait);
      
      	/* see if packet mode change of state */
      
 121  	if (tty->link && tty->link->packet) {
      		int old_flow = ((old_termios.c_iflag & IXON) &&
      				(old_termios.c_cc[VSTOP] == '\023') &&
      				(old_termios.c_cc[VSTART] == '\021'));
      		int new_flow = (I_IXON(tty) &&
      				STOP_CHAR(tty) == '\023' &&
      				START_CHAR(tty) == '\021');
 128  		if (old_flow != new_flow) {
      			tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
 130  			if (new_flow)
      				tty->ctrl_status |= TIOCPKT_DOSTOP;
 132  			else
      				tty->ctrl_status |= TIOCPKT_NOSTOP;
      			wake_up_interruptible(&tty->link->read_wait);
      		}
      	}
      
 138  	if (tty->driver.set_termios)
      		(*tty->driver.set_termios)(tty, &old_termios);
      
 141  	if (tty->ldisc.set_termios)
      		(*tty->ldisc.set_termios)(tty, &old_termios);
      }
      
 145  static int set_termios(struct tty_struct * tty, unsigned long arg, int opt)
      {
      	struct termios tmp_termios;
      	int retval;
      
      	retval = tty_check_change(tty);
 151  	if (retval)
 152  		return retval;
      
 154  	if (opt & TERMIOS_TERMIO) {
      		memcpy(&tmp_termios, tty->termios, sizeof(struct termios));
 156  		if (user_termio_to_kernel_termios(&tmp_termios, (struct termio *) arg))
 157  			return -EFAULT;
 158  	} else {
 159  		if (user_termios_to_kernel_termios(&tmp_termios, (struct termios *) arg))
 160  			return -EFAULT;
      	}
      
 163  	if ((opt & TERMIOS_FLUSH) && tty->ldisc.flush_buffer)
      		tty->ldisc.flush_buffer(tty);
      
 166  	if (opt & TERMIOS_WAIT) {
      		tty_wait_until_sent(tty, 0);
 168  		if (signal_pending(current))
 169  			return -EINTR;
      	}
      
      	change_termios(tty, &tmp_termios);
 173  	return 0;
      }
      
 176  static int get_termio(struct tty_struct * tty, struct termio * termio)
      {
 178  	if (kernel_termios_to_user_termio(termio, tty->termios))
 179  		return -EFAULT;
 180  	return 0;
      }
      
 183  static unsigned long inq_canon(struct tty_struct * tty)
      {
      	int nr, head, tail;
      
 187  	if (!tty->canon_data || !tty->read_buf)
 188  		return 0;
      	head = tty->canon_head;
      	tail = tty->read_tail;
      	nr = (head - tail) & (N_TTY_BUF_SIZE-1);
      	/* Skip EOF-chars.. */
 193  	while (head != tail) {
      		if (test_bit(tail, &tty->read_flags) &&
 195  		    tty->read_buf[tail] == __DISABLED_CHAR)
      			nr--;
      		tail = (tail+1) & (N_TTY_BUF_SIZE-1);
      	}
 199  	return nr;
      }
      
      #ifdef TIOCGETP
      /*
       * These are deprecated, but there is limited support..
       *
       * The "sg_flags" translation is a joke..
       */
      static int get_sgflags(struct tty_struct * tty)
      {
      	int flags = 0;
      
      	if (!(tty->termios->c_lflag & ICANON)) {
      		if (tty->termios->c_lflag & ISIG)
      			flags |= 0x02;		/* cbreak */
      		else
      			flags |= 0x20;		/* raw */
      	}
      	if (tty->termios->c_lflag & ECHO)
      		flags |= 0x08;			/* echo */
      	if (tty->termios->c_oflag & OPOST)
      		if (tty->termios->c_oflag & ONLCR)
      			flags |= 0x10;		/* crmod */
      	return flags;
      }
      
      static int get_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
      {
      	struct sgttyb tmp;
      
      	tmp.sg_ispeed = 0;
      	tmp.sg_ospeed = 0;
      	tmp.sg_erase = tty->termios->c_cc[VERASE];
      	tmp.sg_kill = tty->termios->c_cc[VKILL];
      	tmp.sg_flags = get_sgflags(tty);
      	if (copy_to_user(sgttyb, &tmp, sizeof(tmp)))
      		return -EFAULT;
      	return 0;
      }
      
      static void set_sgflags(struct termios * termios, int flags)
      {
      	termios->c_iflag = ICRNL | IXON;
      	termios->c_oflag = 0;
      	termios->c_lflag = ISIG | ICANON;
      	if (flags & 0x02) {	/* cbreak */
      		termios->c_iflag = 0;
      		termios->c_lflag &= ~ICANON;
      	}
      	if (flags & 0x08) {		/* echo */
      		termios->c_lflag |= ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN;
      	}
      	if (flags & 0x10) {		/* crmod */
      		termios->c_oflag |= OPOST | ONLCR;
      	}
      	if (flags & 0x20) {	/* raw */
      		termios->c_iflag = 0;
      		termios->c_lflag &= ~(ISIG | ICANON);
      	}
      	if (!(termios->c_lflag & ICANON)) {
      		termios->c_cc[VMIN] = 1;
      		termios->c_cc[VTIME] = 0;
      	}
      }
      
      static int set_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
      {
      	int retval;
      	struct sgttyb tmp;
      	struct termios termios;
      
      	retval = tty_check_change(tty);
      	if (retval)
      		return retval;
      	termios =  *tty->termios;
      	if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
      		return -EFAULT;
      	termios.c_cc[VERASE] = tmp.sg_erase;
      	termios.c_cc[VKILL] = tmp.sg_kill;
      	set_sgflags(&termios, tmp.sg_flags);
      	change_termios(tty, &termios);
      	return 0;
      }
      #endif
      
      #ifdef TIOCGETC
      static int get_tchars(struct tty_struct * tty, struct tchars * tchars)
      {
      	struct tchars tmp;
      
      	tmp.t_intrc = tty->termios->c_cc[VINTR];
      	tmp.t_quitc = tty->termios->c_cc[VQUIT];
      	tmp.t_startc = tty->termios->c_cc[VSTART];
      	tmp.t_stopc = tty->termios->c_cc[VSTOP];
      	tmp.t_eofc = tty->termios->c_cc[VEOF];
      	tmp.t_brkc = tty->termios->c_cc[VEOL2];	/* what is brkc anyway? */
      	if (copy_to_user(tchars, &tmp, sizeof(tmp)))
      		return -EFAULT;
      	return 0;
      }
      
      static int set_tchars(struct tty_struct * tty, struct tchars * tchars)
      {
      	struct tchars tmp;
      
      	if (copy_from_user(&tmp, tchars, sizeof(tmp)))
      		return -EFAULT;
      	tty->termios->c_cc[VINTR] = tmp.t_intrc;
      	tty->termios->c_cc[VQUIT] = tmp.t_quitc;
      	tty->termios->c_cc[VSTART] = tmp.t_startc;
      	tty->termios->c_cc[VSTOP] = tmp.t_stopc;
      	tty->termios->c_cc[VEOF] = tmp.t_eofc;
      	tty->termios->c_cc[VEOL2] = tmp.t_brkc;	/* what is brkc anyway? */
      	return 0;
      }
      #endif
      
      #ifdef TIOCGLTC
      static int get_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
      {
      	struct ltchars tmp;
      
      	tmp.t_suspc = tty->termios->c_cc[VSUSP];
      	tmp.t_dsuspc = tty->termios->c_cc[VSUSP];	/* what is dsuspc anyway? */
      	tmp.t_rprntc = tty->termios->c_cc[VREPRINT];
      	tmp.t_flushc = tty->termios->c_cc[VEOL2];	/* what is flushc anyway? */
      	tmp.t_werasc = tty->termios->c_cc[VWERASE];
      	tmp.t_lnextc = tty->termios->c_cc[VLNEXT];
      	if (copy_to_user(ltchars, &tmp, sizeof(tmp)))
      		return -EFAULT;
      	return 0;
      }
      
      static int set_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
      {
      	struct ltchars tmp;
      
      	if (copy_from_user(&tmp, ltchars, sizeof(tmp)))
      		return -EFAULT;
      
      	tty->termios->c_cc[VSUSP] = tmp.t_suspc;
      	tty->termios->c_cc[VEOL2] = tmp.t_dsuspc;	/* what is dsuspc anyway? */
      	tty->termios->c_cc[VREPRINT] = tmp.t_rprntc;
      	tty->termios->c_cc[VEOL2] = tmp.t_flushc;	/* what is flushc anyway? */
      	tty->termios->c_cc[VWERASE] = tmp.t_werasc;
      	tty->termios->c_cc[VLNEXT] = tmp.t_lnextc;
      	return 0;
      }
      #endif
      
      /*
       * Send a high priority character to the tty.
       */
 353  void send_prio_char(struct tty_struct *tty, char ch)
      {
      	int	was_stopped = tty->stopped;
      
 357  	if (tty->driver.send_xchar) {
      		tty->driver.send_xchar(tty, ch);
 359  		return;
      	}
 361  	if (was_stopped)
      		start_tty(tty);
      	tty->driver.write(tty, 0, &ch, 1);
 364  	if (was_stopped)
      		stop_tty(tty);
      }
      
 368  int n_tty_ioctl(struct tty_struct * tty, struct file * file,
      		       unsigned int cmd, unsigned long arg)
      {
      	struct tty_struct * real_tty;
      	int retval;
      
      	if (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
 375  	    tty->driver.subtype == PTY_TYPE_MASTER)
      		real_tty = tty->link;
 377  	else
      		real_tty = tty;
      
 380  	switch (cmd) {
      #ifdef TIOCGETP
      		case TIOCGETP:
      			return get_sgttyb(real_tty, (struct sgttyb *) arg);
      		case TIOCSETP:
      		case TIOCSETN:
      			return set_sgttyb(real_tty, (struct sgttyb *) arg);
      #endif
      #ifdef TIOCGETC
      		case TIOCGETC:
      			return get_tchars(real_tty, (struct tchars *) arg);
      		case TIOCSETC:
      			return set_tchars(real_tty, (struct tchars *) arg);
      #endif
      #ifdef TIOCGLTC
      		case TIOCGLTC:
      			return get_ltchars(real_tty, (struct ltchars *) arg);
      		case TIOCSLTC:
      			return set_ltchars(real_tty, (struct ltchars *) arg);
      #endif
 400  		case TCGETS:
 401  			if (kernel_termios_to_user_termios((struct termios *)arg, real_tty->termios))
 402  				return -EFAULT;
 403  			return 0;
 404  		case TCSETSF:
 405  			return set_termios(real_tty, arg,  TERMIOS_FLUSH);
 406  		case TCSETSW:
 407  			return set_termios(real_tty, arg, TERMIOS_WAIT);
 408  		case TCSETS:
 409  			return set_termios(real_tty, arg, 0);
 410  		case TCGETA:
 411  			return get_termio(real_tty,(struct termio *) arg);
 412  		case TCSETAF:
 413  			return set_termios(real_tty, arg, TERMIOS_FLUSH | TERMIOS_TERMIO);
 414  		case TCSETAW:
 415  			return set_termios(real_tty, arg, TERMIOS_WAIT | TERMIOS_TERMIO);
 416  		case TCSETA:
 417  			return set_termios(real_tty, arg, TERMIOS_TERMIO);
 418  		case TCXONC:
      			retval = tty_check_change(tty);
 420  			if (retval)
 421  				return retval;
 422  			switch (arg) {
 423  			case TCOOFF:
 424  				if (!tty->flow_stopped) {
      					tty->flow_stopped = 1;
      					stop_tty(tty);
      				}
 428  				break;
 429  			case TCOON:
 430  				if (tty->flow_stopped) {
      					tty->flow_stopped = 0;
      					start_tty(tty);
      				}
 434  				break;
 435  			case TCIOFF:
 436  				if (STOP_CHAR(tty) != __DISABLED_CHAR)
      					send_prio_char(tty, STOP_CHAR(tty));
 438  				break;
 439  			case TCION:
 440  				if (START_CHAR(tty) != __DISABLED_CHAR)
      					send_prio_char(tty, START_CHAR(tty));
 442  				break;
 443  			default:
 444  				return -EINVAL;
      			}
 446  			return 0;
 447  		case TCFLSH:
      			retval = tty_check_change(tty);
 449  			if (retval)
 450  				return retval;
 451  			switch (arg) {
 452  			case TCIFLUSH:
 453  				if (tty->ldisc.flush_buffer)
      					tty->ldisc.flush_buffer(tty);
 455  				break;
 456  			case TCIOFLUSH:
 457  				if (tty->ldisc.flush_buffer)
      					tty->ldisc.flush_buffer(tty);
      				/* fall through */
 460  			case TCOFLUSH:
 461  				if (tty->driver.flush_buffer)
      					tty->driver.flush_buffer(tty);
 463  				break;
 464  			default:
 465  				return -EINVAL;
      			}
 467  			return 0;
 468  		case TIOCOUTQ:
      			return put_user(tty->driver.chars_in_buffer ?
      					tty->driver.chars_in_buffer(tty) : 0,
 471  					(int *) arg);
 472  		case TIOCINQ:
      			retval = tty->read_cnt;
 474  			if (L_ICANON(tty))
      				retval = inq_canon(tty);
 476  			return put_user(retval, (unsigned int *) arg);
 477  		case TIOCGLCKTRMIOS:
 478  			if (kernel_termios_to_user_termios((struct termios *)arg, real_tty->termios_locked))
 479  				return -EFAULT;
 480  			return 0;
      
 482  		case TIOCSLCKTRMIOS:
 483  			if (!suser())
 484  				return -EPERM;
 485  			if (user_termios_to_kernel_termios(real_tty->termios_locked, (struct termios *) arg))
 486  				return -EFAULT;
 487  			return 0;
      
 489  		case TIOCPKT:
      		{
      			int pktmode;
      
      			if (tty->driver.type != TTY_DRIVER_TYPE_PTY ||
 494  			    tty->driver.subtype != PTY_TYPE_MASTER)
 495  				return -ENOTTY;
      			retval = get_user(pktmode, (int *) arg);
 497  			if (retval)
 498  				return retval;
 499  			if (pktmode) {
 500  				if (!tty->packet) {
      					tty->packet = 1;
      					tty->link->ctrl_status = 0;
      				}
 504  			} else
      				tty->packet = 0;
 506  			return 0;
      		}
 508  		case TIOCGSOFTCAR:
 509  			return put_user(C_CLOCAL(tty) ? 1 : 0, (int *) arg);
 510  		case TIOCSSOFTCAR:
      			retval = get_user(arg, (unsigned int *) arg);
 512  			if (retval)
 513  				return retval;
      			tty->termios->c_cflag =
      				((tty->termios->c_cflag & ~CLOCAL) |
      				 (arg ? CLOCAL : 0));
 517  			return 0;
 518  		default:
 519  			return -ENOIOCTLCMD;
      		}
      }