]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
vcc driver fixes
authorAllen Pais <allen.pais@oracle.com>
Thu, 8 Sep 2016 06:01:31 +0000 (11:31 +0530)
committerAllen Pais <allen.pais@oracle.com>
Thu, 15 Sep 2016 06:57:12 +0000 (12:27 +0530)
Orabug:24319080 - hang on a mutex out of vcc_open()
Orabug:24326005 - UEK4 kernel panic tty_ldisc_flush vcc_close

Signed-off-by: Aaron Young <aaron.young@oracle.com>
Reviewed-By: Bijan Mottahedeh <Bijan.Mottahedeh@oracle.com>
Reviewed-By: Liam Merwick <Liam.Merwick@oracle.com>
Signed-off-by: Allen Pais <allen.pais@oracle.com>
drivers/tty/vcc.c

index e3fffc2c1eccb024d31ec13ebff2232e1d156a12..c31cc4e1753fb1b90199d91a70b82c24274c827c 100644 (file)
@@ -1,11 +1,12 @@
 /*
  * vcc.c: sun4v virtual channel concentrator
  *
- * Copyright (C) 2014 Oracle. All rights reserved.
+ * Copyright (C) 2014,2016 Oracle. All rights reserved.
  */
 
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/delay.h>
 #include <linux/sysfs.h>
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
@@ -23,9 +24,13 @@ MODULE_LICENSE("GPL");
 MODULE_VERSION(DRV_MODULE_VERSION);
 
 struct vcc {
-       struct tty_port port;   /* must be first element */
        spinlock_t lock;
        char *domain;
+       struct tty_struct *tty; /* only populated while dev is open */
+       u64 port_id;
+
+       u64 refcnt;
+       bool excl_locked;
 
        /*
         * This buffer is required to support the tty write_room interface
@@ -40,6 +45,9 @@ struct vcc {
        struct vio_driver_state vio;
 };
 
+/* amount of time in ns that thread will delay waiting for a vcc ref */
+#define        VCC_REF_DELAY   100
+
 #define VCC_MAX_PORTS  256
 #define VCC_MINOR_START        0
 #define VCC_BUFF_LEN   VIO_VCC_MTU_SIZE
@@ -55,6 +63,9 @@ static const char vcc_driver_name[] = "vcc";
 static const char vcc_device_node[] = "vcc";
 static struct tty_driver *vcc_tty_driver;
 
+static struct vcc *vcc_table[VCC_MAX_PORTS];
+static DEFINE_SPINLOCK(vcc_table_lock);
+
 int vcc_dbg;
 int vcc_dbg_ldc;
 int vcc_dbg_vio;
@@ -102,6 +113,93 @@ static struct ktermios vcc_tty_termios = {
        .c_ospeed = 38400
 };
 
+static void vcc_table_add(struct vcc *vcc)
+{
+       unsigned long flags;
+
+       BUG_ON(vcc->port_id >= VCC_MAX_PORTS);
+
+       spin_lock_irqsave(&vcc_table_lock, flags);
+       vcc_table[vcc->port_id] = vcc;
+       spin_unlock_irqrestore(&vcc_table_lock, flags);
+}
+
+static void vcc_table_remove(u64 port_id)
+{
+       unsigned long flags;
+
+       BUG_ON(port_id >= VCC_MAX_PORTS);
+
+       spin_lock_irqsave(&vcc_table_lock, flags);
+       vcc_table[port_id] = NULL;
+       spin_unlock_irqrestore(&vcc_table_lock, flags);
+}
+
+static struct vcc *vcc_get(u64 port_id, bool excl)
+{
+       unsigned long flags;
+       struct vcc *vcc;
+
+try_again:
+
+       spin_lock_irqsave(&vcc_table_lock, flags);
+
+       vcc = vcc_table[port_id];
+       if (!vcc) {
+               spin_unlock_irqrestore(&vcc_table_lock, flags);
+               return NULL;
+       }
+
+       if (!excl) {
+               if (vcc->excl_locked) {
+                       spin_unlock_irqrestore(&vcc_table_lock, flags);
+                       udelay(VCC_REF_DELAY);
+                       goto try_again;
+               }
+               vcc->refcnt++;
+               spin_unlock_irqrestore(&vcc_table_lock, flags);
+               return vcc;
+       }
+
+       if (vcc->refcnt) {
+               spin_unlock_irqrestore(&vcc_table_lock, flags);
+               /*
+                * Threads wanting exclusive access to a
+                * vcc will wait half the time - which should give
+                * them higher priority in the case of multiple
+                * waiters.
+                */
+               udelay(VCC_REF_DELAY/2);
+               goto try_again;
+       }
+
+       vcc->refcnt++;
+       vcc->excl_locked = true;
+       spin_unlock_irqrestore(&vcc_table_lock, flags);
+
+       return vcc;
+}
+
+static void vcc_put(struct vcc *vcc, bool excl)
+{
+       unsigned long flags;
+
+       if (!vcc)
+               return;
+
+       spin_lock_irqsave(&vcc_table_lock, flags);
+
+       /* check if caller attempted to put with the wrong flags */
+       BUG_ON((excl && !vcc->excl_locked) || (!excl && vcc->excl_locked));
+
+       vcc->refcnt--;
+
+       if (excl)
+               vcc->excl_locked = false;
+
+       spin_unlock_irqrestore(&vcc_table_lock, flags);
+}
+
 static void vcc_kick_rx(struct vcc *vcc)
 {
        struct vio_driver_state *vio = &vcc->vio;
@@ -137,7 +235,7 @@ static void vcc_kick_tx(struct vcc *vcc)
 
 static int vcc_rx_check(struct tty_struct *tty, int size)
 {
-       BUG_ON(!tty);
+       BUG_ON(!tty || !tty->port);
 
        /*
         * tty_buffer_request_room eventually calls kmalloc with GFP_ATOMIC
@@ -154,7 +252,7 @@ static int vcc_rx(struct tty_struct *tty, char *buf, int size)
 {
        int len;
 
-       BUG_ON(!tty);
+       BUG_ON(!tty || !tty->port);
 
        /*
         * tty_insert_flig_string... calls __tty_buffer_request_room.
@@ -178,7 +276,7 @@ static int vcc_ldc_read(struct vcc *vcc)
        int rv = 0;
        vccdbg("%s\n", __func__);
 
-       tty = vcc->port.tty;
+       tty = vcc->tty;
        if (!tty) {
                rv = ldc_rx_reset(vio->lp);
                vccdbg("%s: reset rx q: rv=%d\n", __func__, rv);
@@ -290,7 +388,7 @@ static void vcc_tx_timer(unsigned long arg)
                vccdbg("%s: ldc_write()=%d\n", __func__, rv);
                vcc_kick_tx(vcc);
        } else {
-               struct tty_struct *tty = vcc->port.tty;
+               struct tty_struct *tty = vcc->tty;
 
                vcc->chars_in_buffer = 0;
 
@@ -356,12 +454,13 @@ static ssize_t vcc_sysfs_domain_show(struct device *device,
        struct device_attribute *attr, char *buf)
 {
        int rv;
-       unsigned long flags;
-       struct vcc *vcc = dev_get_drvdata(device);
+       struct vcc *vcc;
+
+       vcc = dev_get_drvdata(device);
+       if (!vcc)
+               return -ENODEV;
 
-       spin_lock_irqsave(&vcc->lock, flags);
        rv = scnprintf(buf, PAGE_SIZE, "%s\n", vcc->domain);
-       spin_unlock_irqrestore(&vcc->lock, flags);
 
        return rv;
 }
@@ -388,7 +487,11 @@ static ssize_t vcc_sysfs_break_store(struct device *device,
        int rv = count;
        int brk;
        unsigned long flags;
-       struct vcc *vcc = dev_get_drvdata(device);
+       struct vcc *vcc;
+
+       vcc = dev_get_drvdata(device);
+       if (!vcc)
+               return -ENODEV;
 
        spin_lock_irqsave(&vcc->lock, flags);
 
@@ -456,21 +559,27 @@ static int vcc_probe(struct vio_dev *vdev,
                              vcc_versions, ARRAY_SIZE(vcc_versions),
                              NULL, name);
        if (rv)
-               goto free_port;
+               goto free_vcc;
 
        vcc->vio.debug = vcc_dbg_vio;
        vcc_ldc_cfg.debug = vcc_dbg_ldc;
 
        rv = vio_ldc_alloc(&vcc->vio, &vcc_ldc_cfg, vcc);
        if (rv)
-               goto free_port;
+               goto free_vcc;
 
-       tty_port_init(&vcc->port);
        spin_lock_init(&vcc->lock);
-       vcc->port.ops = &vcc_port_ops;
+       vcc->port_id = vdev->port_id;
+       vcc_table_add(vcc);
 
-       dev = tty_port_register_device(&vcc->port, vcc_tty_driver,
-               vdev->port_id, &vdev->dev);
+       /*
+        * Register the device using the port ID as the index.
+        * XXX - Device registration should probably be done last
+        * since the device is "live" as soon as it's called (and
+        * calls into the tty/vcc infrastructure can start
+        * happening for this device immediately afterwards).
+        */
+       dev = tty_register_device(vcc_tty_driver, vdev->port_id, &vdev->dev);
        if (IS_ERR(dev)) {
                rv = PTR_ERR(dev);
                goto free_ldc;
@@ -497,7 +606,7 @@ static int vcc_probe(struct vio_dev *vdev,
 
        rv = sysfs_create_group(&vdev->dev.kobj, &vcc_attribute_group);
        if (rv)
-               goto remove_sysfs;
+               goto free_domain;
 
        init_timer(&vcc->rx_timer);
        vcc->rx_timer.function = vcc_rx_timer;
@@ -525,17 +634,17 @@ static int vcc_probe(struct vio_dev *vdev,
 
        return 0;
 
-remove_sysfs:
-       sysfs_remove_group(&vdev->dev.kobj, &vcc_attribute_group);
+free_domain:
        kfree(vcc->domain);
 
 unreg_tty:
        tty_unregister_device(vcc_tty_driver, vdev->port_id);
 
 free_ldc:
+       vcc_table_remove(vdev->port_id);
        vio_ldc_free(&vcc->vio);
 
-free_port:
+free_vcc:
        kfree(name);
        kfree(vcc);
 
@@ -546,7 +655,6 @@ static int vcc_remove(struct vio_dev *vdev)
 {
        struct vcc *vcc = dev_get_drvdata(&vdev->dev);
        struct tty_struct *tty;
-       unsigned long flags;
 
        vccdbg("%s\n", __func__);
 
@@ -556,13 +664,19 @@ static int vcc_remove(struct vio_dev *vdev)
        del_timer_sync(&vcc->rx_timer);
        del_timer_sync(&vcc->tx_timer);
 
-       spin_lock_irqsave(&vcc->lock, flags);
-       tty = vcc->port.tty;
-       spin_unlock_irqrestore(&vcc->lock, flags);
-
+       /*
+        * If there's a process with the device open,
+        * hangup the tty.
+        */
+       tty = vcc->tty;
        if (tty)
                tty_vhangup(tty);
 
+       /* Get an exclusive ref to the vcc */
+       vcc = vcc_get(vdev->port_id, true);
+
+       BUG_ON(!vcc);
+
        tty_unregister_device(vcc_tty_driver, vdev->port_id);
 
        del_timer_sync(&vcc->vio.timer);
@@ -570,10 +684,22 @@ static int vcc_remove(struct vio_dev *vdev)
        sysfs_remove_group(&vdev->dev.kobj, &vcc_attribute_group);
        dev_set_drvdata(&vdev->dev, NULL);
 
+       vcc_table_remove(vdev->port_id);
+
        kfree(vcc->vio.name);
        kfree(vcc->domain);
        kfree(vcc);
 
+       /*
+        * Since the vcc has been freed and removed from
+        * the table, no need to call vcc_put() here. Any threads
+        * waiting on vcc_get() will notice the vcc removed
+        * from the list and unblock with a NULL returned for
+        * the vcc. Since we grabbed exclusive
+        * access to the vcc, we are guaranteed that no other
+        * access to the vcc will be made - including any puts.
+        */
+
        return 0;
 }
 
@@ -595,57 +721,69 @@ static struct vio_driver vcc_driver = {
 static int vcc_open(struct tty_struct *tty, struct file *filp)
 {
        struct vcc *vcc;
-       int rv, count;
+       struct tty_port *port;
+       int rv;
 
        vccdbg("%s\n", __func__);
+
        if (!tty) {
                pr_err("%s: NULL tty\n", __func__);
                return -ENXIO;
        }
 
-       if (!tty->port) {
-               pr_err("%s: NULL tty port\n", __func__);
-               return -ENXIO;
-       }
-       if (!tty->port->ops) {
-               pr_err("%s: NULL tty port ops\n", __func__);
+       /* Only allow a single open */
+       if (tty->count > 1)
+               return -EBUSY;
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
                return -ENXIO;
        }
 
-       vcc = container_of(tty->port, struct vcc, port);
-
        if (!vcc->vio.lp) {
                pr_err("%s: NULL lp\n", __func__);
+               vcc_put(vcc, false);
                return -ENXIO;
        }
        vccdbgl(vcc->vio.lp);
 
-       /*
-        * vcc_close is called even if vcc_open fails so call
-        * tty_port_open() regardless in case of -EBUSY.
-        */
-       count = tty->port->count;
-       if (count)
-               pr_err("%s: tty port busy\n", __func__);
-       rv = tty_port_open(tty->port, tty, filp);
-       if (rv == 0 && count != 0)
-               rv = -EBUSY;
+       vcc_put(vcc, false);
 
-       return rv;
+       port = tty->port;
+       if (!port) {
+               pr_err("%s: NULL tty port\n", __func__);
+               return -ENXIO;
+       }
 
+       if (!port->ops) {
+               pr_err("%s: NULL tty port ops\n", __func__);
+               return -ENXIO;
+       }
+
+       rv = tty_port_open(port, tty, filp);
+
+       return rv;
 }
 
 static void vcc_close(struct tty_struct *tty, struct file *filp)
 {
        vccdbg("%s\n", __func__);
+
        if (!tty) {
                pr_err("%s: NULL tty\n", __func__);
                return;
        }
+
+       /* if this isn't the last close just return */
+       if (tty->count > 1)
+               return;
+
        if (!tty->port) {
                pr_err("%s: NULL tty port\n", __func__);
                return;
        }
+
        tty_port_close(tty->port, tty, filp);
 }
 
@@ -665,16 +803,40 @@ static void vcc_ldc_hup(struct vcc *vcc)
 
 static void vcc_hangup(struct tty_struct *tty)
 {
-       struct vcc *vcc = container_of(tty->port, struct vcc, port);
+       struct vcc *vcc;
+       struct tty_port *port;
+
+       vccdbg("%s\n", __func__);
+
+       if (!tty) {
+               pr_err("%s: NULL tty\n", __func__);
+               return;
+       }
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
+               return;
+       }
+
+       port = tty->port;
+       if (!port) {
+               pr_err("%s: NULL tty port\n", __func__);
+               vcc_put(vcc, false);
+               return;
+       }
 
        vcc_ldc_hup(vcc);
-       tty_port_hangup(tty->port);
+
+       vcc_put(vcc, false);
+
+       tty_port_hangup(port);
 }
 
 static int vcc_write(struct tty_struct *tty,
                const unsigned char *buf, int count)
 {
-       struct vcc *vcc = container_of(tty->port, struct vcc, port);
+       struct vcc *vcc;
        struct vio_vcc *pkt;
        unsigned long flags;
        int total_sent = 0;
@@ -683,6 +845,17 @@ static int vcc_write(struct tty_struct *tty,
 
        vccdbg("%s\n", __func__);
 
+       if (!tty) {
+               pr_err("%s: NULL tty\n", __func__);
+               return -ENXIO;
+       }
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
+               return -ENXIO;
+       }
+
        spin_lock_irqsave(&vcc->lock, flags);
 
        pkt = &vcc->buffer;
@@ -734,6 +907,8 @@ static int vcc_write(struct tty_struct *tty,
 
        spin_unlock_irqrestore(&vcc->lock, flags);
 
+       vcc_put(vcc, false);
+
        vccdbg("%s: total=%d rv=%d\n", __func__, total_sent, rv);
 
        return total_sent ? total_sent : rv;
@@ -741,27 +916,72 @@ static int vcc_write(struct tty_struct *tty,
 
 static int vcc_write_room(struct tty_struct *tty)
 {
-       struct vcc *vcc = container_of(tty->port, struct vcc, port);
+       struct vcc *vcc;
+       u64 num;
 
-       return VCC_BUFF_LEN - vcc->chars_in_buffer;
+       if (!tty) {
+               pr_err("%s: NULL tty\n", __func__);
+               return -ENXIO;
+       }
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
+               return -ENXIO;
+       }
+
+       num = VCC_BUFF_LEN - vcc->chars_in_buffer;
+
+       vcc_put(vcc, false);
+
+       return num;
 }
 
 static int vcc_chars_in_buffer(struct tty_struct *tty)
 {
-       struct vcc *vcc = container_of(tty->port, struct vcc, port);
+       struct vcc *vcc;
+       u64 num;
+
+       if (!tty) {
+               pr_err("%s: NULL tty\n", __func__);
+               return -ENXIO;
+       }
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
+               return -ENXIO;
+       }
+
+       num = vcc->chars_in_buffer;
 
-       return vcc->chars_in_buffer;
+       vcc_put(vcc, false);
+
+       return num;
 }
 
 static int vcc_break_ctl(struct tty_struct *tty, int state)
 {
-       struct vcc *vcc = container_of(tty->port, struct vcc, port);
+       struct vcc *vcc;
        unsigned long flags;
 
        vccdbg("%s(%d)\n", __func__, state);
 
-       if (state == 0)         /* turn off break */
+       if (!tty) {
+               pr_err("%s: NULL tty\n", __func__);
+               return -ENXIO;
+       }
+
+       vcc = vcc_get(tty->index, false);
+       if (!vcc) {
+               pr_err("%s: NULL vcc\n", __func__);
+               return -ENXIO;
+       }
+
+       if (state == 0) {       /* turn off break */
+               vcc_put(vcc, false);
                return 0;
+       }
 
        spin_lock_irqsave(&vcc->lock, flags);
 
@@ -770,9 +990,64 @@ static int vcc_break_ctl(struct tty_struct *tty, int state)
 
        spin_unlock_irqrestore(&vcc->lock, flags);
 
+       vcc_put(vcc, false);
+
        return 0;
 }
 
+static int vcc_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+       int ret;
+       struct vcc *vcc;
+       struct tty_port *port;
+
+       if (tty->index >= VCC_MAX_PORTS)
+               return -EINVAL;
+
+       ret = tty_standard_install(driver, tty);
+       if (ret)
+               return ret;
+
+       /* alloc and assign a port for the tty */
+       port = kzalloc(sizeof(*port), GFP_KERNEL);
+       if (!port) {
+               pr_err("%s: cannot allocate tty_port\n", __func__);
+               return -ENOMEM;
+       }
+       tty_port_init(port);
+       port->ops = &vcc_port_ops;
+       tty->port = port;
+
+       vcc = vcc_get(tty->index, true);
+       if (!vcc) {
+               tty->port = NULL;
+               kfree(port);
+               return -ENODEV;
+       }
+
+       /* assign the tty to the vcc */
+       vcc->tty = tty;
+
+       vcc_put(vcc, true);
+
+       return 0;
+}
+
+static void vcc_cleanup(struct tty_struct *tty)
+{
+       struct vcc *vcc;
+
+       vcc = vcc_get(tty->index, true);
+       if (vcc) {
+               vcc->tty = NULL;
+               vcc_put(vcc, true);
+       }
+
+       tty_port_destroy(tty->port);
+       kfree(tty->port);
+       tty->port = NULL;
+}
+
 static const struct tty_operations vcc_ops = {
        .open = vcc_open,
        .close = vcc_close,
@@ -780,7 +1055,9 @@ static const struct tty_operations vcc_ops = {
        .write = vcc_write,
        .write_room = vcc_write_room,
        .chars_in_buffer = vcc_chars_in_buffer,
-       .break_ctl = vcc_break_ctl
+       .break_ctl = vcc_break_ctl,
+       .install = vcc_install,
+       .cleanup = vcc_cleanup
 };
 
 /*
@@ -825,7 +1102,6 @@ static int vcc_tty_init(void)
 
        pr_err("%s: tty driver register failed\n", __func__);
 
-       tty_unregister_driver(vcc_tty_driver);
        put_tty_driver(vcc_tty_driver);
        vcc_tty_driver = NULL;