hvcs: Synchronize hotplug remove with port free
authorBrian King <brking@linux.vnet.ibm.com>
Fri, 3 Feb 2023 15:58:02 +0000 (09:58 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Feb 2023 12:09:15 +0000 (13:09 +0100)
Synchronizes hotplug remove with the freeing of the port.
This ensures we have freed all the memory associated with
this port and are not leaking memory.

Signed-off-by: Brian King <brking@linux.vnet.ibm.com>
Link: https://lore.kernel.org/r/20230203155802.404324-6-brking@linux.vnet.ibm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/tty/hvc/hvcs.c

index ecf2419..1de1a09 100644 (file)
@@ -52,6 +52,7 @@
 
 #include <linux/device.h>
 #include <linux/init.h>
+#include <linux/completion.h>
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/kref.h>
@@ -285,6 +286,7 @@ struct hvcs_struct {
        char p_location_code[HVCS_CLC_LENGTH + 1]; /* CLC + Null Term */
        struct list_head next; /* list management */
        struct vio_dev *vdev;
+       struct completion *destroyed;
 };
 
 static LIST_HEAD(hvcs_structs);
@@ -663,11 +665,13 @@ static void hvcs_destruct_port(struct tty_port *p)
 {
        struct hvcs_struct *hvcsd = container_of(p, struct hvcs_struct, port);
        struct vio_dev *vdev;
+       struct completion *comp;
        unsigned long flags;
 
        spin_lock(&hvcs_structs_lock);
        spin_lock_irqsave(&hvcsd->lock, flags);
 
+       comp = hvcsd->destroyed;
        /* the list_del poisons the pointers */
        list_del(&(hvcsd->next));
 
@@ -687,6 +691,7 @@ static void hvcs_destruct_port(struct tty_port *p)
 
        hvcsd->p_unit_address = 0;
        hvcsd->p_partition_ID = 0;
+       hvcsd->destroyed = NULL;
        hvcs_return_index(hvcsd->index);
        memset(&hvcsd->p_location_code[0], 0x00, HVCS_CLC_LENGTH + 1);
 
@@ -694,6 +699,8 @@ static void hvcs_destruct_port(struct tty_port *p)
        spin_unlock(&hvcs_structs_lock);
 
        kfree(hvcsd);
+       if (comp)
+               complete(comp);
 }
 
 static const struct tty_port_operations hvcs_port_ops = {
@@ -792,6 +799,7 @@ static int hvcs_probe(
 static void hvcs_remove(struct vio_dev *dev)
 {
        struct hvcs_struct *hvcsd = dev_get_drvdata(&dev->dev);
+       DECLARE_COMPLETION_ONSTACK(comp);
        unsigned long flags;
        struct tty_struct *tty;
 
@@ -799,17 +807,12 @@ static void hvcs_remove(struct vio_dev *dev)
 
        spin_lock_irqsave(&hvcsd->lock, flags);
 
+       hvcsd->destroyed = &comp;
        tty = tty_port_tty_get(&hvcsd->port);
 
        spin_unlock_irqrestore(&hvcsd->lock, flags);
 
        /*
-        * Let the last holder of this object cause it to be removed, which
-        * would probably be tty_hangup below.
-        */
-       tty_port_put(&hvcsd->port);
-
-       /*
         * The tty should always be valid at this time unless a
         * simultaneous tty close already cleaned up the hvcs_struct.
         */
@@ -818,6 +821,8 @@ static void hvcs_remove(struct vio_dev *dev)
                tty_kref_put(tty);
        }
 
+       tty_port_put(&hvcsd->port);
+       wait_for_completion(&comp);
        printk(KERN_INFO "HVCS: vty-server@%X removed from the"
                        " vio bus.\n", dev->unit_address);
 };
@@ -1171,7 +1176,10 @@ static void hvcs_close(struct tty_struct *tty, struct file *filp)
        hvcsd = tty->driver_data;
 
        spin_lock_irqsave(&hvcsd->lock, flags);
-       if (--hvcsd->port.count == 0) {
+       if (hvcsd->port.count == 0) {
+               spin_unlock_irqrestore(&hvcsd->lock, flags);
+               return;
+       } else if (--hvcsd->port.count == 0) {
 
                vio_disable_interrupts(hvcsd->vdev);
 
@@ -1227,11 +1235,7 @@ static void hvcs_hangup(struct tty_struct * tty)
        vio_disable_interrupts(hvcsd->vdev);
 
        hvcsd->todo_mask = 0;
-
-       /* I don't think the tty needs the hvcs_struct pointer after a hangup */
-       tty->driver_data = NULL;
        hvcsd->port.tty = NULL;
-
        hvcsd->port.count = 0;
 
        /* This will drop any buffered data on the floor which is OK in a hangup