[SCSI] scsi_transport_fc: Fix synchronization issue while deleting vport
authorGal Rosen <galr@storwize.com>
Thu, 21 Jan 2010 08:15:32 +0000 (10:15 +0200)
committerJames Bottomley <James.Bottomley@suse.de>
Mon, 8 Mar 2010 17:06:53 +0000 (22:36 +0530)
The issue occur while deleting 60 virtual ports through the sys
interface /sys/class/fc_vports/vport-X/vport_delete. It happen while in
a mistake each request sent twice for the same vport. This interface is
asynchronous, entering the delete request into a work queue, allowing
more than one request to enter to the delete work queue. The result is a
NULL pointer. The first request already delete the vport, while the
second request got a pointer to the vport before the device destroyed.
Re-create vport later cause system freeze.

Solution: Check vport flags before entering the request to the work queue.

[jejb: fixed int<->long problem on spinlock flags variable]
Signed-off-by: Gal Rosen <galr@storwize.com>
Acked-by: James Smart <james.smart@emulex.com>
Cc: Stable Tree <stable@kernel.org>
Signed-off-by: James Bottomley <James.Bottomley@suse.de>
drivers/scsi/scsi_transport_fc.c

index 79660ee3e2113affe5735c4c97786f5142b1f10c..1d5b72173dd8fb9a3df7ed1901716b2722a5a467 100644 (file)
@@ -1232,6 +1232,15 @@ store_fc_vport_delete(struct device *dev, struct device_attribute *attr,
 {
        struct fc_vport *vport = transport_class_to_vport(dev);
        struct Scsi_Host *shost = vport_to_shost(vport);
+       unsigned long flags;
+
+       spin_lock_irqsave(shost->host_lock, flags);
+       if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING)) {
+               spin_unlock_irqrestore(shost->host_lock, flags);
+               return -EBUSY;
+       }
+       vport->flags |= FC_VPORT_DELETING;
+       spin_unlock_irqrestore(shost->host_lock, flags);
 
        fc_queue_work(shost, &vport->vport_delete_work);
        return count;
@@ -1821,6 +1830,9 @@ store_fc_host_vport_delete(struct device *dev, struct device_attribute *attr,
        list_for_each_entry(vport, &fc_host->vports, peers) {
                if ((vport->channel == 0) &&
                    (vport->port_name == wwpn) && (vport->node_name == wwnn)) {
+                       if (vport->flags & (FC_VPORT_DEL | FC_VPORT_CREATING))
+                               break;
+                       vport->flags |= FC_VPORT_DELETING;
                        match = 1;
                        break;
                }
@@ -3370,18 +3382,6 @@ fc_vport_terminate(struct fc_vport *vport)
        unsigned long flags;
        int stat;
 
-       spin_lock_irqsave(shost->host_lock, flags);
-       if (vport->flags & FC_VPORT_CREATING) {
-               spin_unlock_irqrestore(shost->host_lock, flags);
-               return -EBUSY;
-       }
-       if (vport->flags & (FC_VPORT_DEL)) {
-               spin_unlock_irqrestore(shost->host_lock, flags);
-               return -EALREADY;
-       }
-       vport->flags |= FC_VPORT_DELETING;
-       spin_unlock_irqrestore(shost->host_lock, flags);
-
        if (i->f->vport_delete)
                stat = i->f->vport_delete(vport);
        else