]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/tty/tty_ldisc.c
tty: Destroy ldisc instance on hangup
[karo-tx-linux.git] / drivers / tty / tty_ldisc.c
index eac33e104ccc29603de52b581a60e544c2dfca71..c6f970d63060253fdc236b182e62865c3f3439e8 100644 (file)
@@ -257,6 +257,9 @@ const struct file_operations tty_ldiscs_proc_fops = {
  *     reference to it. If the line discipline is in flux then
  *     wait patiently until it changes.
  *
+ *     Returns: NULL if the tty has been hungup and not re-opened with
+ *              a new file descriptor, otherwise valid ldisc reference
+ *
  *     Note: Must not be called from an IRQ/timer context. The caller
  *     must also be careful not to hold other locks that will deadlock
  *     against a discipline change, such as an existing ldisc reference
@@ -417,7 +420,7 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush);
 /**
  *     tty_set_termios_ldisc           -       set ldisc field
  *     @tty: tty structure
- *     @num: line discipline number
+ *     @disc: line discipline number
  *
  *     This is probably overkill for real world processors but
  *     they are not on hot paths so a little discipline won't do
@@ -430,10 +433,10 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush);
  *     Locking: takes termios_rwsem
  */
 
-static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
+static void tty_set_termios_ldisc(struct tty_struct *tty, int disc)
 {
        down_write(&tty->termios_rwsem);
-       tty->termios.c_line = num;
+       tty->termios.c_line = disc;
        up_write(&tty->termios_rwsem);
 
        tty->disc_data = NULL;
@@ -531,12 +534,12 @@ static void tty_ldisc_restore(struct tty_struct *tty, struct tty_ldisc *old)
  *     the close of one side of a tty/pty pair, and eventually hangup.
  */
 
-int tty_set_ldisc(struct tty_struct *tty, int ldisc)
+int tty_set_ldisc(struct tty_struct *tty, int disc)
 {
        int retval;
        struct tty_ldisc *old_ldisc, *new_ldisc;
 
-       new_ldisc = tty_ldisc_get(tty, ldisc);
+       new_ldisc = tty_ldisc_get(tty, disc);
        if (IS_ERR(new_ldisc))
                return PTR_ERR(new_ldisc);
 
@@ -551,7 +554,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
        }
 
        /* Check the no-op case */
-       if (tty->ldisc->ops->num == ldisc)
+       if (tty->ldisc->ops->num == disc)
                goto out;
 
        if (test_bit(TTY_HUPPED, &tty->flags)) {
@@ -567,7 +570,7 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc)
 
        /* Now set up the new line discipline. */
        tty->ldisc = new_ldisc;
-       tty_set_termios_ldisc(tty, ldisc);
+       tty_set_termios_ldisc(tty, disc);
 
        retval = tty_ldisc_open(tty, new_ldisc);
        if (retval < 0) {
@@ -639,28 +642,44 @@ static void tty_reset_termios(struct tty_struct *tty)
 /**
  *     tty_ldisc_reinit        -       reinitialise the tty ldisc
  *     @tty: tty to reinit
- *     @ldisc: line discipline to reinitialize
+ *     @disc: line discipline to reinitialize
+ *
+ *     Completely reinitialize the line discipline state, by closing the
+ *     current instance, if there is one, and opening a new instance. If
+ *     an error occurs opening the new non-N_TTY instance, the instance
+ *     is dropped and tty->ldisc reset to NULL. The caller can then retry
+ *     with N_TTY instead.
  *
- *     Switch the tty to a line discipline and leave the ldisc
- *     state closed
+ *     Returns 0 if successful, otherwise error code < 0
  */
 
-static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
+int tty_ldisc_reinit(struct tty_struct *tty, int disc)
 {
-       struct tty_ldisc *ld = tty_ldisc_get(tty, ldisc);
+       struct tty_ldisc *ld;
+       int retval;
 
-       if (IS_ERR(ld))
-               return -1;
+       ld = tty_ldisc_get(tty, disc);
+       if (IS_ERR(ld)) {
+               BUG_ON(disc == N_TTY);
+               return PTR_ERR(ld);
+       }
 
-       tty_ldisc_close(tty, tty->ldisc);
-       tty_ldisc_put(tty->ldisc);
-       /*
-        *      Switch the line discipline back
-        */
-       tty->ldisc = ld;
-       tty_set_termios_ldisc(tty, ldisc);
+       if (tty->ldisc) {
+               tty_ldisc_close(tty, tty->ldisc);
+               tty_ldisc_put(tty->ldisc);
+       }
 
-       return 0;
+       /* switch the line discipline */
+       tty->ldisc = ld;
+       tty_set_termios_ldisc(tty, disc);
+       retval = tty_ldisc_open(tty, tty->ldisc);
+       if (retval) {
+               if (!WARN_ON(disc == N_TTY)) {
+                       tty_ldisc_put(tty->ldisc);
+                       tty->ldisc = NULL;
+               }
+       }
+       return retval;
 }
 
 /**
@@ -678,11 +697,9 @@ static int tty_ldisc_reinit(struct tty_struct *tty, int ldisc)
  *     tty itself so we must be careful about locking rules.
  */
 
-void tty_ldisc_hangup(struct tty_struct *tty)
+void tty_ldisc_hangup(struct tty_struct *tty, bool reinit)
 {
        struct tty_ldisc *ld;
-       int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS;
-       int err = 0;
 
        tty_ldisc_debug(tty, "%p: hangup\n", tty->ldisc);
 
@@ -710,31 +727,17 @@ void tty_ldisc_hangup(struct tty_struct *tty)
         */
        tty_ldisc_lock(tty, MAX_SCHEDULE_TIMEOUT);
 
-       if (tty->ldisc) {
-
-               /* At this point we have a halted ldisc; we want to close it and
-                  reopen a new ldisc. We could defer the reopen to the next
-                  open but it means auditing a lot of other paths so this is
-                  a FIXME */
-               if (reset == 0) {
+       if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+               tty_reset_termios(tty);
 
-                       if (!tty_ldisc_reinit(tty, tty->termios.c_line))
-                               err = tty_ldisc_open(tty, tty->ldisc);
-                       else
-                               err = 1;
-               }
-               /* If the re-open fails or we reset then go to N_TTY. The
-                  N_TTY open cannot fail */
-               if (reset || err) {
-                       BUG_ON(tty_ldisc_reinit(tty, N_TTY));
-                       WARN_ON(tty_ldisc_open(tty, tty->ldisc));
-               }
+       if (tty->ldisc) {
+               if (reinit) {
+                       if (tty_ldisc_reinit(tty, tty->termios.c_line) < 0)
+                               tty_ldisc_reinit(tty, N_TTY);
+               } else
+                       tty_ldisc_kill(tty);
        }
        tty_ldisc_unlock(tty);
-       if (reset)
-               tty_reset_termios(tty);
-
-       tty_ldisc_debug(tty, "%p: re-opened\n", tty->ldisc);
 }
 
 /**