#include <linux/string.h>
#include <linux/mm.h>
#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
#include <asm/io.h>
-#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define TERMIOS_WAIT 2
#define TERMIOS_TERMIO 4
+
+/**
+ * tty_wait_until_sent - wait for I/O to finish
+ * @tty: tty we are waiting for
+ * @timeout: how long we will wait
+ *
+ * Wait for characters pending in a tty driver to hit the wire, or
+ * for a timeout to occur (eg due to flow control)
+ *
+ * Locking: none
+ */
+
void tty_wait_until_sent(struct tty_struct * tty, long timeout)
{
DECLARE_WAITQUEUE(wait, current);
old->c_cc[i] : termios->c_cc[i];
}
+/**
+ * change_termios - update termios values
+ * @tty: tty to update
+ * @new_termios: desired new value
+ *
+ * Perform updates to the termios values set on this terminal. There
+ * is a bit of layering violation here with n_tty in terms of the
+ * internal knowledge of this function.
+ *
+ * Locking: termios_sem
+ */
+
static void change_termios(struct tty_struct * tty, struct termios * new_termios)
{
int canon_change;
struct termios old_termios = *tty->termios;
+ struct tty_ldisc *ld;
+
+ /*
+ * Perform the actual termios internal changes under lock.
+ */
+
+
+ /* FIXME: we need to decide on some locking/ordering semantics
+ for the set_termios notification eventually */
+ mutex_lock(&tty->termios_mutex);
- local_irq_disable(); // FIXME: is this safe?
*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;
tty->canon_data = 0;
tty->erasing = 0;
}
- local_irq_enable(); // FIXME: is this safe?
+
+
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 */
+ /* See if packet mode change of state. */
if (tty->link && tty->link->packet) {
int old_flow = ((old_termios.c_iflag & IXON) &&
wake_up_interruptible(&tty->link->read_wait);
}
}
-
+
if (tty->driver->set_termios)
(*tty->driver->set_termios)(tty, &old_termios);
- if (tty->ldisc.set_termios)
- (*tty->ldisc.set_termios)(tty, &old_termios);
+ ld = tty_ldisc_ref(tty);
+ if (ld != NULL) {
+ if (ld->set_termios)
+ (ld->set_termios)(tty, &old_termios);
+ tty_ldisc_deref(ld);
+ }
+ mutex_unlock(&tty->termios_mutex);
}
-static int set_termios(struct tty_struct * tty, unsigned long arg, int opt)
+/**
+ * set_termios - set termios values for a tty
+ * @tty: terminal device
+ * @arg: user data
+ * @opt: option information
+ *
+ * Helper function to prepare termios data and run neccessary other
+ * functions before using change_termios to do the actual changes.
+ *
+ * Locking:
+ * Called functions take ldisc and termios_sem locks
+ */
+
+static int set_termios(struct tty_struct * tty, void __user *arg, int opt)
{
struct termios tmp_termios;
+ struct tty_ldisc *ld;
int retval = tty_check_change(tty);
if (retval)
if (opt & TERMIOS_TERMIO) {
memcpy(&tmp_termios, tty->termios, sizeof(struct termios));
if (user_termio_to_kernel_termios(&tmp_termios,
- (struct termio *) arg))
+ (struct termio __user *)arg))
return -EFAULT;
} else {
if (user_termios_to_kernel_termios(&tmp_termios,
- (struct termios *) arg))
+ (struct termios __user *)arg))
return -EFAULT;
}
- if ((opt & TERMIOS_FLUSH) && tty->ldisc.flush_buffer)
- tty->ldisc.flush_buffer(tty);
-
+ ld = tty_ldisc_ref(tty);
+
+ if (ld != NULL) {
+ if ((opt & TERMIOS_FLUSH) && ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+
if (opt & TERMIOS_WAIT) {
tty_wait_until_sent(tty, 0);
if (signal_pending(current))
return 0;
}
-static int get_termio(struct tty_struct * tty, struct termio * termio)
+static int get_termio(struct tty_struct * tty, struct termio __user * termio)
{
if (kernel_termios_to_user_termio(termio, tty->termios))
return -EFAULT;
return flags;
}
-static int get_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
+static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
{
struct sgttyb tmp;
+ mutex_lock(&tty->termios_mutex);
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);
+ mutex_unlock(&tty->termios_mutex);
+
return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
}
}
}
-static int set_sgttyb(struct tty_struct * tty, struct sgttyb * sgttyb)
+/**
+ * set_sgttyb - set legacy terminal values
+ * @tty: tty structure
+ * @sgttyb: pointer to old style terminal structure
+ *
+ * Updates a terminal from the legacy BSD style terminal information
+ * structure.
+ *
+ * Locking: termios_sem
+ */
+
+static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
{
int retval;
struct sgttyb tmp;
retval = tty_check_change(tty);
if (retval)
return retval;
- termios = *tty->termios;
+
if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
return -EFAULT;
+
+ mutex_lock(&tty->termios_mutex);
+ termios = *tty->termios;
termios.c_cc[VERASE] = tmp.sg_erase;
termios.c_cc[VKILL] = tmp.sg_kill;
set_sgflags(&termios, tmp.sg_flags);
+ mutex_unlock(&tty->termios_mutex);
change_termios(tty, &termios);
return 0;
}
#endif
#ifdef TIOCGETC
-static int get_tchars(struct tty_struct * tty, struct tchars * tchars)
+static int get_tchars(struct tty_struct * tty, struct tchars __user * tchars)
{
struct tchars tmp;
return copy_to_user(tchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
}
-static int set_tchars(struct tty_struct * tty, struct tchars * tchars)
+static int set_tchars(struct tty_struct * tty, struct tchars __user * tchars)
{
struct tchars tmp;
#endif
#ifdef TIOCGLTC
-static int get_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
+static int get_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
{
struct ltchars tmp;
return copy_to_user(ltchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
}
-static int set_ltchars(struct tty_struct * tty, struct ltchars * ltchars)
+static int set_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
{
struct ltchars tmp;
}
#endif
-/*
- * Send a high priority character to the tty.
+/**
+ * send_prio_char - send priority character
+ *
+ * Send a high priority character to the tty even if stopped
+ *
+ * Locking: none for xchar method, write ordering for write method.
*/
-void send_prio_char(struct tty_struct *tty, char ch)
+
+static int send_prio_char(struct tty_struct *tty, char ch)
{
int was_stopped = tty->stopped;
if (tty->driver->send_xchar) {
tty->driver->send_xchar(tty, ch);
- return;
+ return 0;
}
+
+ if (mutex_lock_interruptible(&tty->atomic_write_lock))
+ return -ERESTARTSYS;
+
if (was_stopped)
start_tty(tty);
- tty->driver->write(tty, 0, &ch, 1);
+ tty->driver->write(tty, &ch, 1);
if (was_stopped)
stop_tty(tty);
+ mutex_unlock(&tty->atomic_write_lock);
+ return 0;
}
int n_tty_ioctl(struct tty_struct * tty, struct file * file,
unsigned int cmd, unsigned long arg)
{
struct tty_struct * real_tty;
+ void __user *p = (void __user *)arg;
int retval;
+ struct tty_ldisc *ld;
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
tty->driver->subtype == PTY_TYPE_MASTER)
switch (cmd) {
#ifdef TIOCGETP
case TIOCGETP:
- return get_sgttyb(real_tty, (struct sgttyb *) arg);
+ return get_sgttyb(real_tty, (struct sgttyb __user *) arg);
case TIOCSETP:
case TIOCSETN:
- return set_sgttyb(real_tty, (struct sgttyb *) arg);
+ return set_sgttyb(real_tty, (struct sgttyb __user *) arg);
#endif
#ifdef TIOCGETC
case TIOCGETC:
- return get_tchars(real_tty, (struct tchars *) arg);
+ return get_tchars(real_tty, p);
case TIOCSETC:
- return set_tchars(real_tty, (struct tchars *) arg);
+ return set_tchars(real_tty, p);
#endif
#ifdef TIOCGLTC
case TIOCGLTC:
- return get_ltchars(real_tty, (struct ltchars *) arg);
+ return get_ltchars(real_tty, p);
case TIOCSLTC:
- return set_ltchars(real_tty, (struct ltchars *) arg);
+ return set_ltchars(real_tty, p);
#endif
case TCGETS:
- if (kernel_termios_to_user_termios((struct termios *)arg, real_tty->termios))
+ if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios))
return -EFAULT;
return 0;
case TCSETSF:
- return set_termios(real_tty, arg, TERMIOS_FLUSH | TERMIOS_WAIT);
+ return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT);
case TCSETSW:
- return set_termios(real_tty, arg, TERMIOS_WAIT);
+ return set_termios(real_tty, p, TERMIOS_WAIT);
case TCSETS:
- return set_termios(real_tty, arg, 0);
+ return set_termios(real_tty, p, 0);
case TCGETA:
- return get_termio(real_tty,(struct termio *) arg);
+ return get_termio(real_tty, p);
case TCSETAF:
- return set_termios(real_tty, arg, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO);
+ return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO);
case TCSETAW:
- return set_termios(real_tty, arg, TERMIOS_WAIT | TERMIOS_TERMIO);
+ return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_TERMIO);
case TCSETA:
- return set_termios(real_tty, arg, TERMIOS_TERMIO);
+ return set_termios(real_tty, p, TERMIOS_TERMIO);
case TCXONC:
retval = tty_check_change(tty);
if (retval)
break;
case TCIOFF:
if (STOP_CHAR(tty) != __DISABLED_CHAR)
- send_prio_char(tty, STOP_CHAR(tty));
+ return send_prio_char(tty, STOP_CHAR(tty));
break;
case TCION:
if (START_CHAR(tty) != __DISABLED_CHAR)
- send_prio_char(tty, START_CHAR(tty));
+ return send_prio_char(tty, START_CHAR(tty));
break;
default:
return -EINVAL;
retval = tty_check_change(tty);
if (retval)
return retval;
+
+ ld = tty_ldisc_ref(tty);
switch (arg) {
case TCIFLUSH:
- if (tty->ldisc.flush_buffer)
- tty->ldisc.flush_buffer(tty);
+ if (ld && ld->flush_buffer)
+ ld->flush_buffer(tty);
break;
case TCIOFLUSH:
- if (tty->ldisc.flush_buffer)
- tty->ldisc.flush_buffer(tty);
+ if (ld && ld->flush_buffer)
+ ld->flush_buffer(tty);
/* fall through */
case TCOFLUSH:
if (tty->driver->flush_buffer)
tty->driver->flush_buffer(tty);
break;
default:
+ tty_ldisc_deref(ld);
return -EINVAL;
}
+ tty_ldisc_deref(ld);
return 0;
case TIOCOUTQ:
return put_user(tty->driver->chars_in_buffer ?
tty->driver->chars_in_buffer(tty) : 0,
- (int *) arg);
+ (int __user *) arg);
case TIOCINQ:
retval = tty->read_cnt;
if (L_ICANON(tty))
retval = inq_canon(tty);
- return put_user(retval, (unsigned int *) arg);
+ return put_user(retval, (unsigned int __user *) arg);
case TIOCGLCKTRMIOS:
- if (kernel_termios_to_user_termios((struct termios *)arg, real_tty->termios_locked))
+ if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios_locked))
return -EFAULT;
return 0;
case TIOCSLCKTRMIOS:
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
- if (user_termios_to_kernel_termios(real_tty->termios_locked, (struct termios *) arg))
+ if (user_termios_to_kernel_termios(real_tty->termios_locked, (struct termios __user *) arg))
return -EFAULT;
return 0;
if (tty->driver->type != TTY_DRIVER_TYPE_PTY ||
tty->driver->subtype != PTY_TYPE_MASTER)
return -ENOTTY;
- if (get_user(pktmode, (int *) arg))
+ if (get_user(pktmode, (int __user *) arg))
return -EFAULT;
if (pktmode) {
if (!tty->packet) {
return 0;
}
case TIOCGSOFTCAR:
- return put_user(C_CLOCAL(tty) ? 1 : 0, (int *) arg);
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (int __user *)arg);
case TIOCSSOFTCAR:
- if (get_user(arg, (unsigned int *) arg))
+ if (get_user(arg, (unsigned int __user *) arg))
return -EFAULT;
+ mutex_lock(&tty->termios_mutex);
tty->termios->c_cflag =
((tty->termios->c_cflag & ~CLOCAL) |
(arg ? CLOCAL : 0));
+ mutex_unlock(&tty->termios_mutex);
return 0;
default:
return -ENOIOCTLCMD;