linux 2.6.16.38 w/ vs2.0.3-rc1
[linux-2.6.git] / drivers / scsi / scsi_transport_fc.c
index b03aa85..13ea641 100644 (file)
 #include <scsi/scsi_host.h>
 #include <scsi/scsi_transport.h>
 #include <scsi/scsi_transport_fc.h>
-#include <scsi/scsi_cmnd.h>
 #include "scsi_priv.h"
 
-static int fc_queue_work(struct Scsi_Host *, struct work_struct *);
-
 /*
  * Redefine so that we can have same named attributes in the
  * sdev/starget/host objects.
@@ -50,7 +47,7 @@ static const char *get_fc_##title##_name(enum table_type table_key)   \
        int i;                                                          \
        char *name = NULL;                                              \
                                                                        \
-       for (i = 0; i < ARRAY_SIZE(table); i++) {                       \
+       for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) {          \
                if (table[i].value == table_key) {                      \
                        name = table[i].name;                           \
                        break;                                          \
@@ -65,7 +62,7 @@ static int get_fc_##title##_match(const char *table_key,              \
 {                                                                      \
        int i;                                                          \
                                                                        \
-       for (i = 0; i < ARRAY_SIZE(table); i++) {                       \
+       for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) {          \
                if (strncmp(table_key, table[i].name,                   \
                                table[i].matchlen) == 0) {              \
                        *value = table[i].value;                        \
@@ -140,7 +137,7 @@ get_fc_##title##_names(u32 table_key, char *buf)            \
        ssize_t len = 0;                                        \
        int i;                                                  \
                                                                \
-       for (i = 0; i < ARRAY_SIZE(table); i++) {               \
+       for (i = 0; i < sizeof(table)/sizeof(table[0]); i++) {  \
                if (table[i].value & table_key) {               \
                        len += sprintf(buf + len, "%s%s",       \
                                prefix, table[i].name);         \
@@ -215,8 +212,10 @@ fc_bitfield_name_search(remote_port_roles, fc_remote_port_role_names)
 #define FC_MGMTSRVR_PORTID             0x00000a
 
 
+static void fc_shost_remove_rports(void  *data);
 static void fc_timeout_deleted_rport(void *data);
 static void fc_scsi_scan_rport(void *data);
+static void fc_rport_terminate(struct fc_rport  *rport);
 
 /*
  * Attribute counts pre object type...
@@ -288,58 +287,42 @@ static int fc_host_setup(struct transport_container *tc, struct device *dev,
                         struct class_device *cdev)
 {
        struct Scsi_Host *shost = dev_to_shost(dev);
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
 
        /* 
         * Set default values easily detected by the midlayer as
         * failure cases.  The scsi lldd is responsible for initializing
         * all transport attributes to valid values per host.
         */
-       fc_host->node_name = -1;
-       fc_host->port_name = -1;
-       fc_host->permanent_port_name = -1;
-       fc_host->supported_classes = FC_COS_UNSPECIFIED;
-       memset(fc_host->supported_fc4s, 0,
-               sizeof(fc_host->supported_fc4s));
-       memset(fc_host->symbolic_name, 0,
-               sizeof(fc_host->symbolic_name));
-       fc_host->supported_speeds = FC_PORTSPEED_UNKNOWN;
-       fc_host->maxframe_size = -1;
-       memset(fc_host->serial_number, 0,
-               sizeof(fc_host->serial_number));
-
-       fc_host->port_id = -1;
-       fc_host->port_type = FC_PORTTYPE_UNKNOWN;
-       fc_host->port_state = FC_PORTSTATE_UNKNOWN;
-       memset(fc_host->active_fc4s, 0,
-               sizeof(fc_host->active_fc4s));
-       fc_host->speed = FC_PORTSPEED_UNKNOWN;
-       fc_host->fabric_name = -1;
-
-       fc_host->tgtid_bind_type = FC_TGTID_BIND_BY_WWPN;
-
-       INIT_LIST_HEAD(&fc_host->rports);
-       INIT_LIST_HEAD(&fc_host->rport_bindings);
-       fc_host->next_rport_number = 0;
-       fc_host->next_target_id = 0;
-
-       snprintf(fc_host->work_q_name, KOBJ_NAME_LEN, "fc_wq_%d",
-               shost->host_no);
-       fc_host->work_q = create_singlethread_workqueue(
-                                       fc_host->work_q_name);
-       if (!fc_host->work_q)
-               return -ENOMEM;
-
-       snprintf(fc_host->devloss_work_q_name, KOBJ_NAME_LEN, "fc_dl_%d",
-               shost->host_no);
-       fc_host->devloss_work_q = create_singlethread_workqueue(
-                                       fc_host->devloss_work_q_name);
-       if (!fc_host->devloss_work_q) {
-               destroy_workqueue(fc_host->work_q);
-               fc_host->work_q = NULL;
-               return -ENOMEM;
-       }
-
+       fc_host_node_name(shost) = -1;
+       fc_host_port_name(shost) = -1;
+       fc_host_permanent_port_name(shost) = -1;
+       fc_host_supported_classes(shost) = FC_COS_UNSPECIFIED;
+       memset(fc_host_supported_fc4s(shost), 0,
+               sizeof(fc_host_supported_fc4s(shost)));
+       memset(fc_host_symbolic_name(shost), 0,
+               sizeof(fc_host_symbolic_name(shost)));
+       fc_host_supported_speeds(shost) = FC_PORTSPEED_UNKNOWN;
+       fc_host_maxframe_size(shost) = -1;
+       memset(fc_host_serial_number(shost), 0,
+               sizeof(fc_host_serial_number(shost)));
+
+       fc_host_port_id(shost) = -1;
+       fc_host_port_type(shost) = FC_PORTTYPE_UNKNOWN;
+       fc_host_port_state(shost) = FC_PORTSTATE_UNKNOWN;
+       memset(fc_host_active_fc4s(shost), 0,
+               sizeof(fc_host_active_fc4s(shost)));
+       fc_host_speed(shost) = FC_PORTSPEED_UNKNOWN;
+       fc_host_fabric_name(shost) = -1;
+
+       fc_host_tgtid_bind_type(shost) = FC_TGTID_BIND_BY_WWPN;
+
+       INIT_LIST_HEAD(&fc_host_rports(shost));
+       INIT_LIST_HEAD(&fc_host_rport_bindings(shost));
+       fc_host_next_rport_number(shost) = 0;
+       fc_host_next_target_id(shost) = 0;
+
+       fc_host_flags(shost) = 0;
+       INIT_WORK(&fc_host_rport_del_work(shost), fc_shost_remove_rports, shost);
        return 0;
 }
 
@@ -368,7 +351,7 @@ static DECLARE_TRANSPORT_CLASS(fc_rport_class,
  *   should insulate the loss of a remote port.
  *   The maximum will be capped by the value of SCSI_DEVICE_BLOCK_MAX_TIMEOUT.
  */
-static unsigned int fc_dev_loss_tmo = 60;              /* seconds */
+static unsigned int fc_dev_loss_tmo = SCSI_DEVICE_BLOCK_MAX_TIMEOUT;
 
 module_param_named(dev_loss_tmo, fc_dev_loss_tmo, int, S_IRUGO|S_IWUSR);
 MODULE_PARM_DESC(dev_loss_tmo,
@@ -895,9 +878,9 @@ store_fc_private_host_tgtid_bind_type(struct class_device *cdev,
                while (!list_empty(&fc_host_rport_bindings(shost))) {
                        get_list_head_entry(rport,
                                &fc_host_rport_bindings(shost), peers);
-                       list_del(&rport->peers);
-                       rport->port_state = FC_PORTSTATE_DELETED;
-                       fc_queue_work(shost, &rport->rport_delete_work);
+                       spin_unlock_irqrestore(shost->host_lock, flags);
+                       fc_rport_terminate(rport);
+                       spin_lock_irqsave(shost->host_lock, flags);
                }
                spin_unlock_irqrestore(shost->host_lock, flags);
        }
@@ -1107,40 +1090,6 @@ static int fc_rport_match(struct attribute_container *cont,
 }
 
 
-/**
- * fc_timed_out - FC Transport I/O timeout intercept handler
- *
- * @scmd:      The SCSI command which timed out
- *
- * This routine protects against error handlers getting invoked while a
- * rport is in a blocked state, typically due to a temporarily loss of
- * connectivity. If the error handlers are allowed to proceed, requests
- * to abort i/o, reset the target, etc will likely fail as there is no way
- * to communicate with the device to perform the requested function. These
- * failures may result in the midlayer taking the device offline, requiring
- * manual intervention to restore operation.
- *
- * This routine, called whenever an i/o times out, validates the state of
- * the underlying rport. If the rport is blocked, it returns
- * EH_RESET_TIMER, which will continue to reschedule the timeout.
- * Eventually, either the device will return, or devloss_tmo will fire,
- * and when the timeout then fires, it will be handled normally.
- * If the rport is not blocked, normal error handling continues.
- *
- * Notes:
- *     This routine assumes no locks are held on entry.
- **/
-static enum scsi_eh_timer_return
-fc_timed_out(struct scsi_cmnd *scmd)
-{
-       struct fc_rport *rport = starget_to_rport(scsi_target(scmd->device));
-
-       if (rport->port_state == FC_PORTSTATE_BLOCKED)
-               return EH_RESET_TIMER;
-
-       return EH_NOT_HANDLED;
-}
-
 /*
  * Must be called with shost->host_lock held
  */
@@ -1166,13 +1115,15 @@ static int fc_user_scan(struct Scsi_Host *shost, uint channel,
 struct scsi_transport_template *
 fc_attach_transport(struct fc_function_template *ft)
 {
-       int count;
-       struct fc_internal *i = kzalloc(sizeof(struct fc_internal),
+       struct fc_internal *i = kmalloc(sizeof(struct fc_internal),
                                        GFP_KERNEL);
+       int count;
 
        if (unlikely(!i))
                return NULL;
 
+       memset(i, 0, sizeof(struct fc_internal));
+
        i->t.target_attrs.ac.attrs = &i->starget_attrs[0];
        i->t.target_attrs.ac.class = &fc_transport_class.class;
        i->t.target_attrs.ac.match = fc_target_match;
@@ -1197,8 +1148,6 @@ fc_attach_transport(struct fc_function_template *ft)
        /* Transport uses the shost workq for scsi scanning */
        i->t.create_work_queue = 1;
 
-       i->t.eh_timed_out = fc_timed_out;
-
        i->t.user_scan = fc_user_scan;
        
        /*
@@ -1278,92 +1227,6 @@ void fc_release_transport(struct scsi_transport_template *t)
 }
 EXPORT_SYMBOL(fc_release_transport);
 
-/**
- * fc_queue_work - Queue work to the fc_host workqueue.
- * @shost:     Pointer to Scsi_Host bound to fc_host.
- * @work:      Work to queue for execution.
- *
- * Return value:
- *     1 - work queued for execution
- *     0 - work is already queued
- *     -EINVAL - work queue doesn't exist
- **/
-static int
-fc_queue_work(struct Scsi_Host *shost, struct work_struct *work)
-{
-       if (unlikely(!fc_host_work_q(shost))) {
-               printk(KERN_ERR
-                       "ERROR: FC host '%s' attempted to queue work, "
-                       "when no workqueue created.\n", shost->hostt->name);
-               dump_stack();
-
-               return -EINVAL;
-       }
-
-       return queue_work(fc_host_work_q(shost), work);
-}
-
-/**
- * fc_flush_work - Flush a fc_host's workqueue.
- * @shost:     Pointer to Scsi_Host bound to fc_host.
- **/
-static void
-fc_flush_work(struct Scsi_Host *shost)
-{
-       if (!fc_host_work_q(shost)) {
-               printk(KERN_ERR
-                       "ERROR: FC host '%s' attempted to flush work, "
-                       "when no workqueue created.\n", shost->hostt->name);
-               dump_stack();
-               return;
-       }
-
-       flush_workqueue(fc_host_work_q(shost));
-}
-
-/**
- * fc_queue_devloss_work - Schedule work for the fc_host devloss workqueue.
- * @shost:     Pointer to Scsi_Host bound to fc_host.
- * @work:      Work to queue for execution.
- * @delay:     jiffies to delay the work queuing
- *
- * Return value:
- *     0 on success / != 0 for error
- **/
-static int
-fc_queue_devloss_work(struct Scsi_Host *shost, struct work_struct *work,
-                               unsigned long delay)
-{
-       if (unlikely(!fc_host_devloss_work_q(shost))) {
-               printk(KERN_ERR
-                       "ERROR: FC host '%s' attempted to queue work, "
-                       "when no workqueue created.\n", shost->hostt->name);
-               dump_stack();
-
-               return -EINVAL;
-       }
-
-       return queue_delayed_work(fc_host_devloss_work_q(shost), work, delay);
-}
-
-/**
- * fc_flush_devloss - Flush a fc_host's devloss workqueue.
- * @shost:     Pointer to Scsi_Host bound to fc_host.
- **/
-static void
-fc_flush_devloss(struct Scsi_Host *shost)
-{
-       if (!fc_host_devloss_work_q(shost)) {
-               printk(KERN_ERR
-                       "ERROR: FC host '%s' attempted to flush work, "
-                       "when no workqueue created.\n", shost->hostt->name);
-               dump_stack();
-               return;
-       }
-
-       flush_workqueue(fc_host_devloss_work_q(shost));
-}
-
 
 /**
  * fc_remove_host - called to terminate any fc_transport-related elements
@@ -1385,102 +1248,36 @@ void
 fc_remove_host(struct Scsi_Host *shost)
 {
        struct fc_rport *rport, *next_rport;
-       struct workqueue_struct *work_q;
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
 
        /* Remove any remote ports */
        list_for_each_entry_safe(rport, next_rport,
-                       &fc_host->rports, peers) {
-               list_del(&rport->peers);
-               rport->port_state = FC_PORTSTATE_DELETED;
-               fc_queue_work(shost, &rport->rport_delete_work);
-       }
-
+                       &fc_host_rports(shost), peers)
+               fc_rport_terminate(rport);
        list_for_each_entry_safe(rport, next_rport,
-                       &fc_host->rport_bindings, peers) {
-               list_del(&rport->peers);
-               rport->port_state = FC_PORTSTATE_DELETED;
-               fc_queue_work(shost, &rport->rport_delete_work);
-       }
-
-       /* flush all scan work items */
-       scsi_flush_work(shost);
-
-       /* flush all stgt delete, and rport delete work items, then kill it  */
-       if (fc_host->work_q) {
-               work_q = fc_host->work_q;
-               fc_host->work_q = NULL;
-               destroy_workqueue(work_q);
-       }
-
-       /* flush all devloss work items, then kill it  */
-       if (fc_host->devloss_work_q) {
-               work_q = fc_host->devloss_work_q;
-               fc_host->devloss_work_q = NULL;
-               destroy_workqueue(work_q);
-       }
+                       &fc_host_rport_bindings(shost), peers)
+               fc_rport_terminate(rport);
 }
 EXPORT_SYMBOL(fc_remove_host);
 
-
-/**
- * fc_starget_delete - called to delete the scsi decendents of an rport
- *                  (target and all sdevs)
- *
- * @data:      remote port to be operated on.
- **/
-static void
-fc_starget_delete(void *data)
-{
-       struct fc_rport *rport = (struct fc_rport *)data;
-       struct Scsi_Host *shost = rport_to_shost(rport);
-       unsigned long flags;
-
-       spin_lock_irqsave(shost->host_lock, flags);
-       if (rport->flags & FC_RPORT_DEVLOSS_PENDING) {
-               spin_unlock_irqrestore(shost->host_lock, flags);
-               if (!cancel_delayed_work(&rport->dev_loss_work))
-                       fc_flush_devloss(shost);
-               spin_lock_irqsave(shost->host_lock, flags);
-               rport->flags &= ~FC_RPORT_DEVLOSS_PENDING;
-       }
-       spin_unlock_irqrestore(shost->host_lock, flags);
-
-       scsi_remove_target(&rport->dev);
-}
-
-
-/**
- * fc_rport_final_delete - finish rport termination and delete it.
- *
- * @data:      remote port to be deleted.
- **/
+/*
+ * fc_rport_tgt_remove - Removes the scsi target on the remote port
+ * @rport:     The remote port to be operated on
+ */
 static void
-fc_rport_final_delete(void *data)
+fc_rport_tgt_remove(struct fc_rport *rport)
 {
-       struct fc_rport *rport = (struct fc_rport *)data;
-       struct device *dev = &rport->dev;
        struct Scsi_Host *shost = rport_to_shost(rport);
 
-       /* Delete SCSI target and sdevs */
-       if (rport->scsi_target_id != -1)
-               fc_starget_delete(data);
+       scsi_target_unblock(&rport->dev);
 
-       /*
-        * if a scan is pending, flush the SCSI Host work_q so that 
-        * that we can reclaim the rport scan work element.
-        */
-       if (rport->flags & FC_RPORT_SCAN_PENDING)
-               scsi_flush_work(shost);
+       /* Stop anything on the workq */
+       if (!cancel_delayed_work(&rport->dev_loss_work))
+               flush_scheduled_work();
+       scsi_flush_work(shost);
 
-       transport_remove_device(dev);
-       device_del(dev);
-       transport_destroy_device(dev);
-       put_device(&shost->shost_gendev);       /* for fc_host->rport list */
-       put_device(dev);                        /* for self-reference */
+       scsi_remove_target(&rport->dev);
 }
 
-
 /**
  * fc_rport_create - allocates and creates a remote FC port.
  * @shost:     scsi host the remote port is connected to.
@@ -1498,7 +1295,8 @@ struct fc_rport *
 fc_rport_create(struct Scsi_Host *shost, int channel,
        struct fc_rport_identifiers  *ids)
 {
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
+       struct fc_host_attrs *fc_host =
+                       (struct fc_host_attrs *)shost->shost_data;
        struct fc_internal *fci = to_fc_internal(shost->transportt);
        struct fc_rport *rport;
        struct device *dev;
@@ -1507,11 +1305,12 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
        size_t size;
 
        size = (sizeof(struct fc_rport) + fci->f->dd_fcrport_size);
-       rport = kzalloc(size, GFP_KERNEL);
+       rport = kmalloc(size, GFP_KERNEL);
        if (unlikely(!rport)) {
                printk(KERN_ERR "%s: allocation failure\n", __FUNCTION__);
                return NULL;
        }
+       memset(rport, 0, size);
 
        rport->maxframe_size = -1;
        rport->supported_classes = FC_COS_UNSPECIFIED;
@@ -1527,8 +1326,6 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
 
        INIT_WORK(&rport->dev_loss_work, fc_timeout_deleted_rport, rport);
        INIT_WORK(&rport->scan_work, fc_scsi_scan_rport, rport);
-       INIT_WORK(&rport->stgt_delete_work, fc_starget_delete, rport);
-       INIT_WORK(&rport->rport_delete_work, fc_rport_final_delete, rport);
 
        spin_lock_irqsave(shost->host_lock, flags);
 
@@ -1537,14 +1334,14 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
                rport->scsi_target_id = fc_host->next_target_id++;
        else
                rport->scsi_target_id = -1;
-       list_add_tail(&rport->peers, &fc_host->rports);
-       get_device(&shost->shost_gendev);       /* for fc_host->rport list */
+       list_add_tail(&rport->peers, &fc_host_rports(shost));
+       get_device(&shost->shost_gendev);
 
        spin_unlock_irqrestore(shost->host_lock, flags);
 
        dev = &rport->dev;
-       device_initialize(dev);                 /* takes self reference */
-       dev->parent = get_device(&shost->shost_gendev); /* parent reference */
+       device_initialize(dev);
+       dev->parent = get_device(&shost->shost_gendev);
        dev->release = fc_rport_dev_release;
        sprintf(dev->bus_id, "rport-%d:%d-%d",
                shost->host_no, channel, rport->number);
@@ -1558,19 +1355,18 @@ fc_rport_create(struct Scsi_Host *shost, int channel,
        transport_add_device(dev);
        transport_configure_device(dev);
 
-       if (rport->roles & FC_RPORT_ROLE_FCP_TARGET) {
+       if (rport->roles & FC_RPORT_ROLE_FCP_TARGET)
                /* initiate a scan of the target */
-               rport->flags |= FC_RPORT_SCAN_PENDING;
                scsi_queue_work(shost, &rport->scan_work);
-       }
 
        return rport;
 
 delete_rport:
        transport_destroy_device(dev);
+       put_device(dev->parent);
        spin_lock_irqsave(shost->host_lock, flags);
        list_del(&rport->peers);
-       put_device(&shost->shost_gendev);       /* for fc_host->rport list */
+       put_device(&shost->shost_gendev);
        spin_unlock_irqrestore(shost->host_lock, flags);
        put_device(dev->parent);
        kfree(rport);
@@ -1621,14 +1417,10 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
        struct fc_rport_identifiers  *ids)
 {
        struct fc_internal *fci = to_fc_internal(shost->transportt);
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
        struct fc_rport *rport;
        unsigned long flags;
        int match = 0;
 
-       /* ensure any stgt delete functions are done */
-       fc_flush_work(shost);
-
        /*
         * Search the list of "active" rports, for an rport that has been
         * deleted, but we've held off the real delete while the target
@@ -1636,12 +1428,12 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
         */
        spin_lock_irqsave(shost->host_lock, flags);
 
-       list_for_each_entry(rport, &fc_host->rports, peers) {
+       list_for_each_entry(rport, &fc_host_rports(shost), peers) {
 
                if ((rport->port_state == FC_PORTSTATE_BLOCKED) &&
                        (rport->channel == channel)) {
 
-                       switch (fc_host->tgtid_bind_type) {
+                       switch (fc_host_tgtid_bind_type(shost)) {
                        case FC_TGTID_BIND_BY_WWPN:
                        case FC_TGTID_BIND_NONE:
                                if (rport->port_name == ids->port_name)
@@ -1695,36 +1487,27 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
                                 * transaction.
                                 */
                                if (!cancel_delayed_work(work))
-                                       fc_flush_devloss(shost);
-
-                               spin_lock_irqsave(shost->host_lock, flags);
-
-                               rport->flags &= ~FC_RPORT_DEVLOSS_PENDING;
+                                       flush_scheduled_work();
 
                                /* initiate a scan of the target */
-                               rport->flags |= FC_RPORT_SCAN_PENDING;
                                scsi_queue_work(shost, &rport->scan_work);
 
-                               spin_unlock_irqrestore(shost->host_lock, flags);
-
-                               scsi_target_unblock(&rport->dev);
-
                                return rport;
                        }
                }
        }
 
        /* Search the bindings array */
-       if (fc_host->tgtid_bind_type != FC_TGTID_BIND_NONE) {
+       if (fc_host_tgtid_bind_type(shost) != FC_TGTID_BIND_NONE) {
 
                /* search for a matching consistent binding */
 
-               list_for_each_entry(rport, &fc_host->rport_bindings,
+               list_for_each_entry(rport, &fc_host_rport_bindings(shost),
                                        peers) {
                        if (rport->channel != channel)
                                continue;
 
-                       switch (fc_host->tgtid_bind_type) {
+                       switch (fc_host_tgtid_bind_type(shost)) {
                        case FC_TGTID_BIND_BY_WWPN:
                                if (rport->port_name == ids->port_name)
                                        match = 1;
@@ -1742,7 +1525,8 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
                        }
 
                        if (match) {
-                               list_move_tail(&rport->peers, &fc_host->rports);
+                               list_move_tail(&rport->peers,
+                                       &fc_host_rports(shost));
                                break;
                        }
                }
@@ -1756,18 +1540,15 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
                        rport->roles = ids->roles;
                        rport->port_state = FC_PORTSTATE_ONLINE;
 
+                       spin_unlock_irqrestore(shost->host_lock, flags);
+
                        if (fci->f->dd_fcrport_size)
                                memset(rport->dd_data, 0,
                                                fci->f->dd_fcrport_size);
 
-                       if (rport->roles & FC_RPORT_ROLE_FCP_TARGET) {
+                       if (rport->roles & FC_RPORT_ROLE_FCP_TARGET)
                                /* initiate a scan of the target */
-                               rport->flags |= FC_RPORT_SCAN_PENDING;
                                scsi_queue_work(shost, &rport->scan_work);
-                               spin_unlock_irqrestore(shost->host_lock, flags);
-                               scsi_target_unblock(&rport->dev);
-                       } else
-                               spin_unlock_irqrestore(shost->host_lock, flags);
 
                        return rport;
                }
@@ -1782,6 +1563,30 @@ fc_remote_port_add(struct Scsi_Host *shost, int channel,
 }
 EXPORT_SYMBOL(fc_remote_port_add);
 
+/*
+ * fc_rport_terminate - this routine tears down and deallocates a remote port.
+ * @rport:     The remote port to be terminated
+ *
+ * Notes:
+ *     This routine assumes no locks are held on entry.
+ */
+static void
+fc_rport_terminate(struct fc_rport  *rport)
+{
+       struct Scsi_Host *shost = rport_to_shost(rport);
+       struct device *dev = &rport->dev;
+       unsigned long flags;
+
+       fc_rport_tgt_remove(rport);
+
+       transport_remove_device(dev);
+       device_del(dev);
+       transport_destroy_device(dev);
+       spin_lock_irqsave(shost->host_lock, flags);
+       list_del(&rport->peers);
+       spin_unlock_irqrestore(shost->host_lock, flags);
+       put_device(&shost->shost_gendev);
+}
 
 /**
  * fc_remote_port_delete - notifies the fc transport that a remote
@@ -1836,39 +1641,20 @@ EXPORT_SYMBOL(fc_remote_port_add);
 void
 fc_remote_port_delete(struct fc_rport  *rport)
 {
-       struct Scsi_Host *shost = rport_to_shost(rport);
        int timeout = rport->dev_loss_tmo;
-       unsigned long flags;
-
-       /*
-        * No need to flush the fc_host work_q's, as all adds are synchronous.
-        *
-        * We do need to reclaim the rport scan work element, so eventually
-        * (in fc_rport_final_delete()) we'll flush the scsi host work_q if
-        * there's still a scan pending.
-        */
-
-       spin_lock_irqsave(shost->host_lock, flags);
 
        /* If no scsi target id mapping, delete it */
        if (rport->scsi_target_id == -1) {
-               list_del(&rport->peers);
-               rport->port_state = FC_PORTSTATE_DELETED;
-               fc_queue_work(shost, &rport->rport_delete_work);
-               spin_unlock_irqrestore(shost->host_lock, flags);
+               fc_rport_terminate(rport);
                return;
        }
 
-       rport->port_state = FC_PORTSTATE_BLOCKED;
-
-       rport->flags |= FC_RPORT_DEVLOSS_PENDING;
-
-       spin_unlock_irqrestore(shost->host_lock, flags);
-
        scsi_target_block(&rport->dev);
 
        /* cap the length the devices can be blocked until they are deleted */
-       fc_queue_devloss_work(shost, &rport->dev_loss_work, timeout * HZ);
+       schedule_delayed_work(&rport->dev_loss_work, timeout * HZ);
+
+       rport->port_state = FC_PORTSTATE_BLOCKED;
 }
 EXPORT_SYMBOL(fc_remote_port_delete);
 
@@ -1896,7 +1682,8 @@ void
 fc_remote_port_rolechg(struct fc_rport  *rport, u32 roles)
 {
        struct Scsi_Host *shost = rport_to_shost(rport);
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
+       struct fc_host_attrs *fc_host =
+                       (struct fc_host_attrs *)shost->shost_data;
        unsigned long flags;
        int create = 0;
 
@@ -1908,11 +1695,10 @@ fc_remote_port_rolechg(struct fc_rport  *rport, u32 roles)
                } else if (!(rport->roles & FC_RPORT_ROLE_FCP_TARGET))
                        create = 1;
        }
+       spin_unlock_irqrestore(shost->host_lock, flags);
 
        rport->roles = roles;
 
-       spin_unlock_irqrestore(shost->host_lock, flags);
-
        if (create) {
                /*
                 * There may have been a delete timer running on the
@@ -1927,21 +1713,10 @@ fc_remote_port_rolechg(struct fc_rport  *rport, u32 roles)
                 * transaction.
                 */
                if (!cancel_delayed_work(&rport->dev_loss_work))
-                       fc_flush_devloss(shost);
-
-               spin_lock_irqsave(shost->host_lock, flags);
-               rport->flags &= ~FC_RPORT_DEVLOSS_PENDING;
-               spin_unlock_irqrestore(shost->host_lock, flags);
-
-               /* ensure any stgt delete functions are done */
-               fc_flush_work(shost);
+                       flush_scheduled_work();
 
                /* initiate a scan of the target */
-               spin_lock_irqsave(shost->host_lock, flags);
-               rport->flags |= FC_RPORT_SCAN_PENDING;
                scsi_queue_work(shost, &rport->scan_work);
-               spin_unlock_irqrestore(shost->host_lock, flags);
-               scsi_target_unblock(&rport->dev);
        }
 }
 EXPORT_SYMBOL(fc_remote_port_rolechg);
@@ -1958,25 +1733,22 @@ fc_timeout_deleted_rport(void  *data)
 {
        struct fc_rport *rport = (struct fc_rport *)data;
        struct Scsi_Host *shost = rport_to_shost(rport);
-       struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
        unsigned long flags;
 
        spin_lock_irqsave(shost->host_lock, flags);
 
-       rport->flags &= ~FC_RPORT_DEVLOSS_PENDING;
-
        /*
-        * If the port is ONLINE, then it came back. Validate it's still an
-        * FCP target. If not, tear down the scsi_target on it.
+        * If the port is ONLINE, then it came back, but was no longer an
+        * FCP target. Thus we need to tear down the scsi_target on it.
         */
-       if ((rport->port_state == FC_PORTSTATE_ONLINE) &&
-           !(rport->roles & FC_RPORT_ROLE_FCP_TARGET)) {
-               dev_printk(KERN_ERR, &rport->dev,
-                       "blocked FC remote port time out: no longer"
-                       " a FCP target, removing starget\n");
+       if (rport->port_state == FC_PORTSTATE_ONLINE) {
                spin_unlock_irqrestore(shost->host_lock, flags);
-               scsi_target_unblock(&rport->dev);
-               fc_queue_work(shost, &rport->stgt_delete_work);
+
+               dev_printk(KERN_ERR, &rport->dev,
+                       "blocked FC remote port time out: removing target\n");
+
+               fc_rport_tgt_remove(rport);
+
                return;
        }
 
@@ -1987,13 +1759,11 @@ fc_timeout_deleted_rport(void  *data)
                return;
        }
 
-       if (fc_host->tgtid_bind_type == FC_TGTID_BIND_NONE) {
-               list_del(&rport->peers);
-               rport->port_state = FC_PORTSTATE_DELETED;
+       if (fc_host_tgtid_bind_type(shost) == FC_TGTID_BIND_NONE) {
+               spin_unlock_irqrestore(shost->host_lock, flags);
                dev_printk(KERN_ERR, &rport->dev,
                        "blocked FC remote port time out: removing target\n");
-               fc_queue_work(shost, &rport->rport_delete_work);
-               spin_unlock_irqrestore(shost->host_lock, flags);
+               fc_rport_terminate(rport);
                return;
        }
 
@@ -2001,7 +1771,7 @@ fc_timeout_deleted_rport(void  *data)
                "blocked FC remote port time out: removing target and "
                "saving binding\n");
 
-       list_move_tail(&rport->peers, &fc_host->rport_bindings);
+       list_move_tail(&rport->peers, &fc_host_rport_bindings(shost));
 
        /*
         * Note: We do not remove or clear the hostdata area. This allows
@@ -2015,10 +1785,10 @@ fc_timeout_deleted_rport(void  *data)
        rport->maxframe_size = -1;
        rport->supported_classes = FC_COS_UNSPECIFIED;
        rport->roles = FC_RPORT_ROLE_UNKNOWN;
-       rport->port_state = FC_PORTSTATE_NOTPRESENT;
+       rport->port_state = FC_PORTSTATE_DELETED;
 
        /* remove the identifiers that aren't used in the consisting binding */
-       switch (fc_host->tgtid_bind_type) {
+       switch (fc_host_tgtid_bind_type(shost)) {
        case FC_TGTID_BIND_BY_WWPN:
                rport->node_name = -1;
                rport->port_id = -1;
@@ -2039,33 +1809,71 @@ fc_timeout_deleted_rport(void  *data)
         * As this only occurs if the remote port (scsi target)
         * went away and didn't come back - we'll remove
         * all attached scsi devices.
+        *
+        * We'll schedule the shost work item to perform the actual removal
+        * to avoid recursion in the different flush calls if we perform
+        * the removal in each target - and there are lots of targets
+        * whose timeouts fire at the same time.
         */
-       spin_unlock_irqrestore(shost->host_lock, flags);
 
-       scsi_target_unblock(&rport->dev);
-       fc_queue_work(shost, &rport->stgt_delete_work);
+       if ( !(fc_host_flags(shost) & FC_SHOST_RPORT_DEL_SCHEDULED)) {
+               fc_host_flags(shost) |= FC_SHOST_RPORT_DEL_SCHEDULED;
+               scsi_queue_work(shost, &fc_host_rport_del_work(shost));
+       }
+
+       spin_unlock_irqrestore(shost->host_lock, flags);
 }
 
 /**
  * fc_scsi_scan_rport - called to perform a scsi scan on a remote port.
  *
+ * Will unblock the target (in case it went away and has now come back),
+ * then invoke a scan.
+ *
  * @data:      remote port to be scanned.
  **/
 static void
 fc_scsi_scan_rport(void *data)
 {
        struct fc_rport *rport = (struct fc_rport *)data;
-       struct Scsi_Host *shost = rport_to_shost(rport);
-       unsigned long flags;
 
-       if ((rport->port_state == FC_PORTSTATE_ONLINE) &&
-           (rport->roles & FC_RPORT_ROLE_FCP_TARGET)) {
-               scsi_scan_target(&rport->dev, rport->channel,
-                       rport->scsi_target_id, SCAN_WILD_CARD, 1);
-       }
+       scsi_target_unblock(&rport->dev);
+       scsi_scan_target(&rport->dev, rport->channel, rport->scsi_target_id,
+                       SCAN_WILD_CARD, 1);
+}
+
+
+/**
+ * fc_shost_remove_rports - called to remove all rports that are marked
+ *                       as in a deleted (not connected) state.
+ * 
+ * @data:      shost whose rports are to be looked at
+ **/
+static void
+fc_shost_remove_rports(void  *data)
+{
+       struct Scsi_Host *shost = (struct Scsi_Host *)data;
+       struct fc_rport *rport, *next_rport;
+       unsigned long flags;
 
        spin_lock_irqsave(shost->host_lock, flags);
-       rport->flags &= ~FC_RPORT_SCAN_PENDING;
+       while (fc_host_flags(shost) & FC_SHOST_RPORT_DEL_SCHEDULED) {
+
+               fc_host_flags(shost) &= ~FC_SHOST_RPORT_DEL_SCHEDULED;
+
+restart_search:
+               list_for_each_entry_safe(rport, next_rport,
+                               &fc_host_rport_bindings(shost), peers) {
+                       if (rport->port_state == FC_PORTSTATE_DELETED) {
+                               rport->port_state = FC_PORTSTATE_NOTPRESENT;
+                               spin_unlock_irqrestore(shost->host_lock, flags);
+                               fc_rport_tgt_remove(rport);
+                               spin_lock_irqsave(shost->host_lock, flags);
+                               goto restart_search;
+                       }
+               }
+
+       }
        spin_unlock_irqrestore(shost->host_lock, flags);
 }