]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/tty/n_tty.c
tty: Add diagnostic for halted line discipline
[mirror_ubuntu-bionic-kernel.git] / drivers / tty / n_tty.c
index 05e72bea9b07d87e31c69e291844428cb642d4be..16793eccc6ae5010059790974a2d0d5837470c48 100644 (file)
@@ -153,6 +153,12 @@ static void n_tty_set_room(struct tty_struct *tty)
        if (left && !old_left) {
                WARN_RATELIMIT(tty->port->itty == NULL,
                                "scheduling with invalid itty\n");
+               /* see if ldisc has been killed - if so, this means that
+                * even though the ldisc has been halted and ->buf.work
+                * cancelled, ->buf.work is about to be rescheduled
+                */
+               WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
+                              "scheduling buffer work for halted ldisc\n");
                schedule_work(&tty->port->buf.work);
        }
 }
@@ -188,21 +194,6 @@ static void put_tty_queue(unsigned char c, struct n_tty_data *ldata)
        raw_spin_unlock_irqrestore(&ldata->read_lock, flags);
 }
 
-/**
- *     check_unthrottle        -       allow new receive data
- *     @tty; tty device
- *
- *     Check whether to call the driver unthrottle functions
- *
- *     Can sleep, may be called under the atomic_read_lock mutex but
- *     this is not guaranteed.
- */
-static void check_unthrottle(struct tty_struct *tty)
-{
-       if (tty->count)
-               tty_unthrottle(tty);
-}
-
 /**
  *     reset_buffer_flags      -       reset buffer state
  *     @tty: terminal to reset
@@ -1032,23 +1023,19 @@ static void eraser(unsigned char c, struct tty_struct *tty)
  *     isig            -       handle the ISIG optio
  *     @sig: signal
  *     @tty: terminal
- *     @flush: force flush
  *
- *     Called when a signal is being sent due to terminal input. This
- *     may caus terminal flushing to take place according to the termios
- *     settings and character used. Called from the driver receive_buf
- *     path so serialized.
+ *     Called when a signal is being sent due to terminal input.
+ *     Called from the driver receive_buf path so serialized.
  *
- *     Locking: ctrl_lock, read_lock (both via flush buffer)
+ *     Locking: ctrl_lock
  */
 
-static inline void isig(int sig, struct tty_struct *tty, int flush)
+static inline void isig(int sig, struct tty_struct *tty)
 {
-       if (tty->pgrp)
-               kill_pgrp(tty->pgrp, sig, 1);
-       if (flush || !L_NOFLSH(tty)) {
-               n_tty_flush_buffer(tty);
-               tty_driver_flush_buffer(tty);
+       struct pid *tty_pgrp = tty_get_pgrp(tty);
+       if (tty_pgrp) {
+               kill_pgrp(tty_pgrp, sig, 1);
+               put_pid(tty_pgrp);
        }
 }
 
@@ -1069,7 +1056,11 @@ static inline void n_tty_receive_break(struct tty_struct *tty)
        if (I_IGNBRK(tty))
                return;
        if (I_BRKINT(tty)) {
-               isig(SIGINT, tty, 1);
+               isig(SIGINT, tty);
+               if (!L_NOFLSH(tty)) {
+                       n_tty_flush_buffer(tty);
+                       tty_driver_flush_buffer(tty);
+               }
                return;
        }
        if (I_PARMRK(tty)) {
@@ -1236,11 +1227,6 @@ static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
                signal = SIGTSTP;
                if (c == SUSP_CHAR(tty)) {
 send_signal:
-                       /*
-                        * Note that we do not use isig() here because we want
-                        * the order to be:
-                        * 1) flush, 2) echo, 3) signal
-                        */
                        if (!L_NOFLSH(tty)) {
                                n_tty_flush_buffer(tty);
                                tty_driver_flush_buffer(tty);
@@ -1251,8 +1237,7 @@ send_signal:
                                echo_char(c, tty);
                                process_echoes(tty);
                        }
-                       if (tty->pgrp)
-                               kill_pgrp(tty->pgrp, signal, 1);
+                       isig(signal, tty);
                        return;
                }
        }
@@ -1483,14 +1468,14 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
         * mode.  We don't want to throttle the driver if we're in
         * canonical mode and don't have a newline yet!
         */
-       if (tty->receive_room < TTY_THRESHOLD_THROTTLE)
-               tty_throttle(tty);
-
-        /* FIXME: there is a tiny race here if the receive room check runs
-           before the other work executes and empties the buffer (upping
-           the receiving room and unthrottling. We then throttle and get
-           stuck. This has been observed and traced down by Vincent Pillet/
-           We need to address this when we sort out out the rx path locking */
+       while (1) {
+               tty_set_flow_change(tty, TTY_THROTTLE_SAFE);
+               if (tty->receive_room >= TTY_THRESHOLD_THROTTLE)
+                       break;
+               if (!tty_throttle_safe(tty))
+                       break;
+       }
+       __tty_set_flow_change(tty, 0);
 }
 
 int is_ignored(int sig)
@@ -1645,6 +1630,8 @@ static int n_tty_open(struct tty_struct *tty)
                goto err_free_bufs;
 
        tty->disc_data = ldata;
+       /* indicate buffer work may resume */
+       clear_bit(TTY_LDISC_HALTED, &tty->flags);
        reset_buffer_flags(tty);
        tty_unthrottle(tty);
        ldata->column = 0;
@@ -1740,10 +1727,9 @@ extern ssize_t redirected_tty_write(struct file *, const char __user *,
  *     and if appropriate send any needed signals and return a negative
  *     error code if action should be taken.
  *
- *     FIXME:
- *     Locking: None - redirected write test is safe, testing
- *     current->signal should possibly lock current->sighand
- *     pgrp locking ?
+ *     Locking: redirected write test is safe
+ *              current->signal->tty check is safe
+ *              ctrl_lock to safely reference tty->pgrp
  */
 
 static int job_control(struct tty_struct *tty, struct file *file)
@@ -1753,19 +1739,22 @@ static int job_control(struct tty_struct *tty, struct file *file)
        /* NOTE: not yet done after every sleep pending a thorough
           check of the logic of this change. -- jlc */
        /* don't stop on /dev/console */
-       if (file->f_op->write != redirected_tty_write &&
-           current->signal->tty == tty) {
-               if (!tty->pgrp)
-                       printk(KERN_ERR "n_tty_read: no tty->pgrp!\n");
-               else if (task_pgrp(current) != tty->pgrp) {
-                       if (is_ignored(SIGTTIN) ||
-                           is_current_pgrp_orphaned())
-                               return -EIO;
-                       kill_pgrp(task_pgrp(current), SIGTTIN, 1);
-                       set_thread_flag(TIF_SIGPENDING);
-                       return -ERESTARTSYS;
-               }
+       if (file->f_op->write == redirected_tty_write ||
+           current->signal->tty != tty)
+               return 0;
+
+       spin_lock_irq(&tty->ctrl_lock);
+       if (!tty->pgrp)
+               printk(KERN_ERR "n_tty_read: no tty->pgrp!\n");
+       else if (task_pgrp(current) != tty->pgrp) {
+               spin_unlock_irq(&tty->ctrl_lock);
+               if (is_ignored(SIGTTIN) || is_current_pgrp_orphaned())
+                       return -EIO;
+               kill_pgrp(task_pgrp(current), SIGTTIN, 1);
+               set_thread_flag(TIF_SIGPENDING);
+               return -ERESTARTSYS;
        }
+       spin_unlock_irq(&tty->ctrl_lock);
        return 0;
 }
 
@@ -1959,10 +1948,17 @@ do_it_again:
                 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
                 * we won't get any more characters.
                 */
-               if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) {
+               while (1) {
+                       tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
+                       if (n_tty_chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
+                               break;
+                       if (!tty->count)
+                               break;
                        n_tty_set_room(tty);
-                       check_unthrottle(tty);
+                       if (!tty_unthrottle_safe(tty))
+                               break;
                }
+               __tty_set_flow_change(tty, 0);
 
                if (b - buf >= minimum)
                        break;