n_gsm: race between ld close and gsmtty open
[firefly-linux-kernel-4.4.55.git] / drivers / tty / n_gsm.c
index c0f76da553042db2f3eadb91cfb9ff08bc60e43d..069bfd6544856fdcfdf90f7ea6f7f5da774b899a 100644 (file)
@@ -2054,9 +2054,11 @@ void gsm_cleanup_mux(struct gsm_mux *gsm)
                                        dlci->state == DLCI_CLOSED);
        }
        /* Free up any link layer users */
+       spin_lock(&gsm->lock);
        for (i = 0; i < NUM_DLCI; i++)
                if (gsm->dlci[i])
                        gsm_dlci_release(gsm->dlci[i]);
+       spin_unlock(&gsm->lock);
        /* Now wipe the queues */
        list_for_each_entry_safe(txq, ntxq, &gsm->tx_list, list)
                kfree(txq);
@@ -2909,23 +2911,33 @@ static int gsmtty_install(struct tty_driver *driver, struct tty_struct *tty)
        This is ok from a locking
        perspective as we don't have to worry about this
        if DLCI0 is lost */
-       if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN)
+       spin_lock(&gsm->lock);
+       if (gsm->dlci[0] && gsm->dlci[0]->state != DLCI_OPEN) {
+               spin_unlock(&gsm->lock);
                return -EL2NSYNC;
+       }
        dlci = gsm->dlci[line];
        if (dlci == NULL) {
                alloc = true;
                dlci = gsm_dlci_alloc(gsm, line);
        }
-       if (dlci == NULL)
+       if (dlci == NULL) {
+               spin_unlock(&gsm->lock);
                return -ENOMEM;
+       }
        ret = tty_port_install(&dlci->port, driver, tty);
        if (ret) {
                if (alloc)
                        dlci_put(dlci);
+               spin_unlock(&gsm->lock);
                return ret;
        }
 
+       dlci_get(dlci);
+       dlci_get(gsm->dlci[0]);
+       mux_get(gsm);
        tty->driver_data = dlci;
+       spin_unlock(&gsm->lock);
 
        return 0;
 }
@@ -2936,9 +2948,6 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp)
        struct tty_port *port = &dlci->port;
 
        port->count++;
-       dlci_get(dlci);
-       dlci_get(dlci->gsm->dlci[0]);
-       mux_get(dlci->gsm);
        tty_port_tty_set(port, tty);
 
        dlci->modem_rx = 0;
@@ -2965,7 +2974,7 @@ static void gsmtty_close(struct tty_struct *tty, struct file *filp)
        mutex_unlock(&dlci->mutex);
        gsm = dlci->gsm;
        if (tty_port_close_start(&dlci->port, tty, filp) == 0)
-               goto out;
+               return;
        gsm_dlci_begin_close(dlci);
        if (test_bit(ASYNCB_INITIALIZED, &dlci->port.flags)) {
                if (C_HUPCL(tty))
@@ -2973,10 +2982,7 @@ static void gsmtty_close(struct tty_struct *tty, struct file *filp)
        }
        tty_port_close_end(&dlci->port, tty);
        tty_port_tty_set(&dlci->port, NULL);
-out:
-       dlci_put(dlci);
-       dlci_put(gsm->dlci[0]);
-       mux_put(gsm);
+       return;
 }
 
 static void gsmtty_hangup(struct tty_struct *tty)
@@ -3153,6 +3159,16 @@ static int gsmtty_break_ctl(struct tty_struct *tty, int state)
        return gsmtty_modem_update(dlci, encode);
 }
 
+static void gsmtty_remove(struct tty_driver *driver, struct tty_struct *tty)
+{
+       struct gsm_dlci *dlci = tty->driver_data;
+       struct gsm_mux *gsm = dlci->gsm;
+
+       dlci_put(dlci);
+       dlci_put(gsm->dlci[0]);
+       mux_put(gsm);
+       driver->ttys[tty->index] = NULL;
+}
 
 /* Virtual ttys for the demux */
 static const struct tty_operations gsmtty_ops = {
@@ -3172,6 +3188,7 @@ static const struct tty_operations gsmtty_ops = {
        .tiocmget               = gsmtty_tiocmget,
        .tiocmset               = gsmtty_tiocmset,
        .break_ctl              = gsmtty_break_ctl,
+       .remove                 = gsmtty_remove,
 };