[SCSI] fusion: FC rport code fixes

This fix's problems with recent fc submission regarding
i/o being redirected to the wrong target.

Signed-off-by: Michael Reed <mdr@sgi.com>
Signed-off-by: Eric Moore <Eric.Moore@lsil.com>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
diff --git a/drivers/message/fusion/mptfc.c b/drivers/message/fusion/mptfc.c
index b102c76..c3a3499 100644
--- a/drivers/message/fusion/mptfc.c
+++ b/drivers/message/fusion/mptfc.c
@@ -93,10 +93,11 @@
 static int	mptfcTaskCtx = -1;
 static int	mptfcInternalCtx = -1; /* Used only for internal commands */
 
-int mptfc_slave_alloc(struct scsi_device *device);
+static int mptfc_target_alloc(struct scsi_target *starget);
+static int mptfc_slave_alloc(struct scsi_device *sdev);
 static int mptfc_qcmd(struct scsi_cmnd *SCpnt,
-    void (*done)(struct scsi_cmnd *));
-
+		      void (*done)(struct scsi_cmnd *));
+static void mptfc_target_destroy(struct scsi_target *starget);
 static void mptfc_set_rport_loss_tmo(struct fc_rport *rport, uint32_t timeout);
 static void __devexit mptfc_remove(struct pci_dev *pdev);
 
@@ -107,10 +108,10 @@
 	.name				= "MPT FC Host",
 	.info				= mptscsih_info,
 	.queuecommand			= mptfc_qcmd,
-	.target_alloc			= mptscsih_target_alloc,
+	.target_alloc			= mptfc_target_alloc,
 	.slave_alloc			= mptfc_slave_alloc,
 	.slave_configure		= mptscsih_slave_configure,
-	.target_destroy			= mptscsih_target_destroy,
+	.target_destroy			= mptfc_target_destroy,
 	.slave_destroy			= mptscsih_slave_destroy,
 	.change_queue_depth 		= mptscsih_change_queue_depth,
 	.eh_abort_handler		= mptscsih_abort,
@@ -348,14 +349,33 @@
 }
 
 static void
+mptfc_remap_sdev(struct scsi_device *sdev, void *arg)
+{
+	VirtDevice		*vdev;
+	VirtTarget		*vtarget;
+	struct scsi_target	*starget;
+
+	starget = scsi_target(sdev);
+	if (starget->hostdata == arg) {
+		vtarget = arg;
+		vdev = sdev->hostdata;
+		if (vdev) {
+			vdev->bus_id = vtarget->bus_id;
+			vdev->target_id = vtarget->target_id;
+		}
+	}
+}
+
+static void
 mptfc_register_dev(MPT_ADAPTER *ioc, int channel, FCDevicePage0_t *pg0)
 {
 	struct fc_rport_identifiers rport_ids;
 	struct fc_rport		*rport;
 	struct mptfc_rport_info	*ri;
-	int			match = 0;
-	u64			port_name;
+	int			new_ri = 1;
+	u64			pn;
 	unsigned long		flags;
+	VirtTarget		*vtarget;
 
 	if (mptfc_generate_rport_ids(pg0, &rport_ids) < 0)
 		return;
@@ -363,14 +383,14 @@
 	/* scan list looking for a match */
 	spin_lock_irqsave(&ioc->fc_rport_lock, flags);
 	list_for_each_entry(ri, &ioc->fc_rports, list) {
-		port_name = (u64)ri->pg0.WWPN.High << 32 | (u64)ri->pg0.WWPN.Low;
-		if (port_name == rport_ids.port_name) {	/* match */
+		pn = (u64)ri->pg0.WWPN.High << 32 | (u64)ri->pg0.WWPN.Low;
+		if (pn == rport_ids.port_name) {	/* match */
 			list_move_tail(&ri->list, &ioc->fc_rports);
-			match = 1;
+			new_ri = 0;
 			break;
 		}
 	}
-	if (!match) {	/* allocate one */
+	if (new_ri) {	/* allocate one */
 		spin_unlock_irqrestore(&ioc->fc_rport_lock, flags);
 		ri = kzalloc(sizeof(struct mptfc_rport_info), GFP_KERNEL);
 		if (!ri)
@@ -382,40 +402,43 @@
 	ri->pg0 = *pg0;	/* add/update pg0 data */
 	ri->flags &= ~MPT_RPORT_INFO_FLAGS_MISSING;
 
+	/* MPT_RPORT_INFO_FLAGS_REGISTERED - rport not previously deleted */
 	if (!(ri->flags & MPT_RPORT_INFO_FLAGS_REGISTERED)) {
 		ri->flags |= MPT_RPORT_INFO_FLAGS_REGISTERED;
 		spin_unlock_irqrestore(&ioc->fc_rport_lock, flags);
-		rport = fc_remote_port_add(ioc->sh,channel, &rport_ids);
+		rport = fc_remote_port_add(ioc->sh, channel, &rport_ids);
 		spin_lock_irqsave(&ioc->fc_rport_lock, flags);
 		if (rport) {
-			if (*((struct mptfc_rport_info **)rport->dd_data) != ri) {
-				ri->flags &= ~MPT_RPORT_INFO_FLAGS_MAPPED_VDEV;
-				ri->vdev = NULL;
-				ri->rport = rport;
-				*((struct mptfc_rport_info **)rport->dd_data) = ri;
-			}
-			rport->dev_loss_tmo = mptfc_dev_loss_tmo;
+			ri->rport = rport;
+			if (new_ri) /* may have been reset by user */
+				rport->dev_loss_tmo = mptfc_dev_loss_tmo;
+			*((struct mptfc_rport_info **)rport->dd_data) = ri;
 			/*
 			 * if already mapped, remap here.  If not mapped,
-			 * slave_alloc will allocate vdev and map
+			 * target_alloc will allocate vtarget and map,
+			 * slave_alloc will fill in vdev from vtarget.
 			 */
-			if (ri->flags & MPT_RPORT_INFO_FLAGS_MAPPED_VDEV) {
-				ri->vdev->target_id = ri->pg0.CurrentTargetID;
-				ri->vdev->bus_id = ri->pg0.CurrentBus;
-				ri->vdev->vtarget->target_id = ri->vdev->target_id;
-				ri->vdev->vtarget->bus_id = ri->vdev->bus_id;
+			if (ri->starget) {
+				vtarget = ri->starget->hostdata;
+				if (vtarget) {
+					vtarget->target_id = pg0->CurrentTargetID;
+					vtarget->bus_id = pg0->CurrentBus;
+					starget_for_each_device(ri->starget,
+						vtarget,mptfc_remap_sdev);
+				}
+				ri->remap_needed = 0;
 			}
-			#ifdef MPT_DEBUG
-			printk ("mptfc_reg_dev.%d: %x, %llx / %llx, tid %d, "
+			dfcprintk ((MYIOC_s_INFO_FMT
+				"mptfc_reg_dev.%d: %x, %llx / %llx, tid %d, "
 				"rport tid %d, tmo %d\n",
-					ioc->sh->host_no,
+					ioc->name,
+					oc->sh->host_no,
 					pg0->PortIdentifier,
 					pg0->WWNN,
 					pg0->WWPN,
 					pg0->CurrentTargetID,
 					ri->rport->scsi_target_id,
-					ri->rport->dev_loss_tmo);
-			#endif
+					ri->rport->dev_loss_tmo));
 		} else {
 			list_del(&ri->list);
 			kfree(ri);
@@ -427,6 +450,65 @@
 }
 
 /*
+ *	OS entry point to allow for host driver to free allocated memory
+ *	Called if no device present or device being unloaded
+ */
+static void
+mptfc_target_destroy(struct scsi_target *starget)
+{
+	struct fc_rport		*rport;
+	struct mptfc_rport_info *ri;
+
+	rport = starget_to_rport(starget);
+	if (rport) {
+		ri = *((struct mptfc_rport_info **)rport->dd_data);
+		if (ri)	/* better be! */
+			ri->starget = NULL;
+	}
+	if (starget->hostdata)
+		kfree(starget->hostdata);
+	starget->hostdata = NULL;
+}
+
+/*
+ *	OS entry point to allow host driver to alloc memory
+ *	for each scsi target. Called once per device the bus scan.
+ *	Return non-zero if allocation fails.
+ */
+static int
+mptfc_target_alloc(struct scsi_target *starget)
+{
+	VirtTarget		*vtarget;
+	struct fc_rport		*rport;
+	struct mptfc_rport_info *ri;
+	int			rc;
+
+	vtarget = kzalloc(sizeof(VirtTarget), GFP_KERNEL);
+	if (!vtarget)
+		return -ENOMEM;
+	starget->hostdata = vtarget;
+
+	rc = -ENODEV;
+	rport = starget_to_rport(starget);
+	if (rport) {
+		ri = *((struct mptfc_rport_info **)rport->dd_data);
+		if (ri) {	/* better be! */
+			vtarget->target_id = ri->pg0.CurrentTargetID;
+			vtarget->bus_id = ri->pg0.CurrentBus;
+			ri->starget = starget;
+			ri->remap_needed = 0;
+			rc = 0;
+		}
+	}
+	if (rc != 0) {
+		kfree(vtarget);
+		starget->hostdata = NULL;
+	}
+
+	return rc;
+}
+
+/*
  *	OS entry point to allow host driver to alloc memory
  *	for each scsi device. Called once per device the bus scan.
  *	Return non-zero if allocation fails.
@@ -440,7 +522,6 @@
 	VirtDevice		*vdev;
 	struct scsi_target	*starget;
 	struct fc_rport		*rport;
-	struct mptfc_rport_info *ri;
 	unsigned long		flags;
 
 
@@ -451,55 +532,44 @@
 
 	hd = (MPT_SCSI_HOST *)sdev->host->hostdata;
 
-	vdev = kmalloc(sizeof(VirtDevice), GFP_KERNEL);
+	vdev = kzalloc(sizeof(VirtDevice), GFP_KERNEL);
 	if (!vdev) {
 		printk(MYIOC_s_ERR_FMT "slave_alloc kmalloc(%zd) FAILED!\n",
 				hd->ioc->name, sizeof(VirtDevice));
 		return -ENOMEM;
 	}
-	memset(vdev, 0, sizeof(VirtDevice));
 
 	spin_lock_irqsave(&hd->ioc->fc_rport_lock,flags);
 
-	if (!(ri = *((struct mptfc_rport_info **)rport->dd_data))) {
-		spin_unlock_irqrestore(&hd->ioc->fc_rport_lock,flags);
-		kfree(vdev);
-		return -ENODEV;
-	}
-
 	sdev->hostdata = vdev;
 	starget = scsi_target(sdev);
 	vtarget = starget->hostdata;
+
 	if (vtarget->num_luns == 0) {
+		vtarget->ioc_id = hd->ioc->id;
 		vtarget->tflags = MPT_TARGET_FLAGS_Q_YES |
 		    		  MPT_TARGET_FLAGS_VALID_INQUIRY;
 		hd->Targets[sdev->id] = vtarget;
 	}
 
-	vtarget->target_id = vdev->target_id;
-	vtarget->bus_id = vdev->bus_id;
-
 	vdev->vtarget = vtarget;
 	vdev->ioc_id = hd->ioc->id;
 	vdev->lun = sdev->lun;
-	vdev->target_id = ri->pg0.CurrentTargetID;
-	vdev->bus_id = ri->pg0.CurrentBus;
-
-	ri->flags |= MPT_RPORT_INFO_FLAGS_MAPPED_VDEV;
-	ri->vdev = vdev;
+	vdev->target_id = vtarget->target_id;
+	vdev->bus_id = vtarget->bus_id;
 
 	spin_unlock_irqrestore(&hd->ioc->fc_rport_lock,flags);
 
 	vtarget->num_luns++;
 
-#ifdef MPT_DEBUG
-	printk ("mptfc_slv_alloc.%d: num_luns %d, sdev.id %d, "
+	dfcprintk ((MYIOC_s_INFO_FMT
+		"mptfc_slv_alloc.%d: num_luns %d, sdev.id %d, "
 	        "CurrentTargetID %d, %x %llx %llx\n",
-			sdev->host->host_no,
-			vtarget->num_luns,
-			sdev->id, ri->pg0.CurrentTargetID,
-			ri->pg0.PortIdentifier, ri->pg0.WWPN, ri->pg0.WWNN);
-#endif
+		ioc->name,
+		sdev->host->host_no,
+		vtarget->num_luns,
+		sdev->id, ri->pg0.CurrentTargetID,
+		ri->pg0.PortIdentifier, ri->pg0.WWPN, ri->pg0.WWNN));
 
 	return 0;
 }
@@ -507,6 +577,7 @@
 static int
 mptfc_qcmd(struct scsi_cmnd *SCpnt, void (*done)(struct scsi_cmnd *))
 {
+	struct mptfc_rport_info	*ri;
 	struct fc_rport	*rport = starget_to_rport(scsi_target(SCpnt->device));
 	int		err;
 
@@ -516,6 +587,10 @@
 		done(SCpnt);
 		return 0;
 	}
+	ri = *((struct mptfc_rport_info **)rport->dd_data);
+	if (unlikely(ri->remap_needed))
+		return SCSI_MLQUEUE_HOST_BUSY;
+
 	return mptscsih_qcmd(SCpnt,done);
 }
 
@@ -591,16 +666,20 @@
 
 				ri->flags &= ~(MPT_RPORT_INFO_FLAGS_REGISTERED|
 					       MPT_RPORT_INFO_FLAGS_MISSING);
+				ri->remap_needed = 1;
 				fc_remote_port_delete(ri->rport);
 				/*
 				 * remote port not really deleted 'cause
 				 * binding is by WWPN and driver only
-				 * registers FCP_TARGETs
+				 * registers FCP_TARGETs but cannot trust
+				 * data structures.
 				 */
-				#ifdef MPT_DEBUG
-				printk ("mptfc_rescan.%d: %llx deleted\n",
-					ioc->sh->host_no, ri->pg0.WWPN);
-				#endif
+				ri->rport = NULL;
+				dfcprintk ((MYIOC_s_INFO_FMT
+					"mptfc_rescan.%d: %llx deleted\n",
+					ioc->name,
+					ioc->sh->host_no,
+					ri->pg0.WWPN));
 			}
 		}
 		spin_unlock_irqrestore(&ioc->fc_rport_lock,flags);
@@ -872,9 +951,8 @@
 	}
 
 	error = pci_register_driver(&mptfc_driver);
-	if (error) {
+	if (error)
 		fc_release_transport(mptfc_transport_template);
-	}
 
 	return error;
 }
@@ -885,7 +963,8 @@
  *	@pdev: Pointer to pci_dev structure
  *
  */
-static void __devexit mptfc_remove(struct pci_dev *pdev)
+static void __devexit
+mptfc_remove(struct pci_dev *pdev)
 {
 	MPT_ADAPTER *ioc = pci_get_drvdata(pdev);
 	struct mptfc_rport_info *p, *n;