Skip to contentSkip to conversation

libfc, fcoe and fnci fixes for 2.6.32 RC

PATCH New Robert Love 1 participant 21 messages Oct 21, 2009

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover libfc, fcoe and fnci fixes for 2.6.32 RC

The following series fixes some bugs in the 2.6.32 kernel. A few of the patches fix regressions in fcoe offloads as previously mentioned on linux-scsi.

This series reorders patches previously mailed to the list. Please disregard previously mailed patches from me. I will resend the other patches for the 2.6.33 merge window.

This series is tested against today's scsi-rc-fixes tree.

---

Abhijeet Joglekar (3):
fnic: Pad the unused bytes of CDB to 0s
fnic: Set max_cmd_len to driver supported CDB length
fnic: Process all cq entries per ISR

Chris Leech (1):
libfc: fix memory corruption caused by double frees and bad error handling

Joe Eykholt (4):
libfc: fix free of fc_rport_priv with timer pending
libfc: don't WARN_ON in lport_timeout for RESET state
libfc: lport: fix minor documentation errors
libfc: fix typo in retry check on received PRLI

Mike Christie (1):
fcoe: initialize return value in fcoe_destroy

Robert Love (2):
libfc: Remove unused fc_lport pointer from fc_fcp_pkt_abort libfc, fcoe: Don't EXPORT_SYMBOLS unnecessarily

Vasu Dev (3):
libfc: removes unused disc_work and ex_list
libfc: adds missing exch release for accepted RRQ
libfc: removes initializing fc_cpu_order and fc_cpu_mask per lport

Yi Zou (6):
fcoe: Call ndo_fcoe_enable/disable to turn FCoE feature on/off in LLD
libfc: Fix frags in frame exceeding SKB_MAX_FRAGS in fc_fcp_send_data
fcoe: Use NETIF_F_FCOE_MTU flag to set up max frame size (lport->mfs)
libfc: Fix wrong scsi return status under FC_DATA_UNDRUN
fcoe: remove redundant checking of netdev->netdev_ops
libfc: fix ddp in fc_fcp for 0 xid

 drivers/scsi/fcoe/fcoe.c      |   34 ++++++++++++++---
 drivers/scsi/fcoe/fcoe.h      |    6 +++
 drivers/scsi/fnic/fnic.h      |    2 -
 drivers/scsi/fnic/fnic_isr.c  |   18 ++++-----
 drivers/scsi/fnic/fnic_main.c |    1 
 drivers/scsi/fnic/fnic_res.h  |    4 +-
 drivers/scsi/fnic/fnic_scsi.c |    3 +
 drivers/scsi/libfc/fc_disc.c  |    2 -
 drivers/scsi/libfc/fc_elsct.c |    4 +-
 drivers/scsi/libfc/fc_exch.c  |   35 +++++++++--------
 drivers/scsi/libfc/fc_fcp.c   |   26 +++++++------
 drivers/scsi/libfc/fc_frame.c |    1 
 drivers/scsi/libfc/fc_lport.c |   33 +++++++++-------
 drivers/scsi/libfc/fc_rport.c |   83 ++++++++++++++++++++++++++++-------------
 include/scsi/fc_frame.h       |    3 +
 include/scsi/libfc.h          |    2 -
 16 files changed, 166 insertions(+), 91 deletions(-)
Signature · 1 line
//Rob
#1 lore raw

PATCH 1/20 libfc: fix typo in retry check on received PRLI

From: Joe Eykholt <[email protected]>

A received Fibre Channel ELS PRLI request contains a bit that indicates whether the remote port supports certain retry processing sequences. The test for this bit was somehow coded to use multiply instead of AND!

This case would apply only for target mode operation, and it is unlikely to be noticed as an initiator.

---

 drivers/scsi/libfc/fc_rport.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
drivers/scsi/libfc/fc_rport.c +1 -1
index 03ea674..bdc9732 100644
@@ -1402,7 +1402,7 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata,
 				break;
 			case FC_TYPE_FCP:
 				fcp_parm = ntohl(rspp->spp_params);
-				if (fcp_parm * FCP_SPPF_RETRY)
+				if (fcp_parm & FCP_SPPF_RETRY)
 					rdata->flags |= FC_RP_FLAGS_RETRY;
 				rdata->supported_classes = FC_COS_CLASS3;
 				if (fcp_parm & FCP_SPPF_INIT_FCN)
#2 lore raw

PATCH 2/20 libfc: fix ddp in fc_fcp for 0 xid

In reply to Robert Love

From: Yi Zou <[email protected]>

xid 0 was used as an indication of invalid xid before but now xid 0 can be used as a valid exchange i. This patch fixes the ddp completion in fcp layer, i.e., in fc_fcp.c:fc_fcp_ddp_done() function, to make sure it does not use xid 0 for indication of an invalid xid, instead, it now uses use FC_XID_UNKNOWN for such indication.

---

 drivers/scsi/libfc/fc_fcp.c |    8 ++++++--
 1 files changed, 6 insertions(+), 2 deletions(-)
drivers/scsi/libfc/fc_fcp.c +6 -2
index 59a4408..e6c6f47 100644
@@ -302,10 +302,13 @@ static void fc_fcp_ddp_done(struct fc_fcp_pkt *fsp)
 	if (!fsp)
 		return;
 
+	if (fsp->xfer_ddp == FC_XID_UNKNOWN)
+		return;
+
 	lp = fsp->lp;
-	if (fsp->xfer_ddp && lp->tt.ddp_done) {
+	if (lp->tt.ddp_done) {
 		fsp->xfer_len = lp->tt.ddp_done(lp, fsp->xfer_ddp);
-		fsp->xfer_ddp = 0;
+		fsp->xfer_ddp = FC_XID_UNKNOWN;
 	}
 }
 
@@ -1708,6 +1711,7 @@ int fc_queuecommand(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *))
 	fsp->cmd = sc_cmd;	/* save the cmd */
 	fsp->lp = lp;		/* save the softc ptr */
 	fsp->rport = rport;	/* set the remote port ptr */
+	fsp->xfer_ddp = FC_XID_UNKNOWN;
 	sc_cmd->scsi_done = done;
 
 	/*
#3 lore raw

PATCH 3/20 fcoe: remove redundant checking of netdev->netdev_ops

In reply to Robert Love

From: Yi Zou <[email protected]>

Remove the redundant checking of netdev->netdev_ops as it will never be NULL.

---

 drivers/scsi/fcoe/fcoe.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
drivers/scsi/fcoe/fcoe.c +2 -2
index 704b8e0..7c89887 100644
@@ -664,7 +664,7 @@ static int fcoe_ddp_setup(struct fc_lport *lp, u16 xid,
 {
 	struct net_device *n = fcoe_netdev(lp);
 
-	if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_setup)
+	if (n->netdev_ops->ndo_fcoe_ddp_setup)
 		return n->netdev_ops->ndo_fcoe_ddp_setup(n, xid, sgl, sgc);
 
 	return 0;
@@ -681,7 +681,7 @@ static int fcoe_ddp_done(struct fc_lport *lp, u16 xid)
 {
 	struct net_device *n = fcoe_netdev(lp);
 
-	if (n->netdev_ops && n->netdev_ops->ndo_fcoe_ddp_done)
+	if (n->netdev_ops->ndo_fcoe_ddp_done)
 		return n->netdev_ops->ndo_fcoe_ddp_done(n, xid);
 	return 0;
 }
#4 lore raw

PATCH 4/20 libfc, fcoe: Don't EXPORT_SYMBOLS unnecessarily

In reply to Robert Love

These are a few functions that were not used by other modules. They did not need to be exported so this patch removes the EXPORT_SYMBOLS call for each.

---

 drivers/scsi/libfc/fc_exch.c  |    1 -
 drivers/scsi/libfc/fc_fcp.c   |    1 -
 drivers/scsi/libfc/fc_frame.c |    1 -
 drivers/scsi/libfc/fc_rport.c |    2 --
 4 files changed, 0 insertions(+), 5 deletions(-)
drivers/scsi/libfc/fc_exch.c +0 -1
index c1c1574..ae8f9e9 100644
@@ -422,7 +422,6 @@ int fc_seq_exch_abort(const struct fc_seq *req_sp, unsigned int timer_msec)
 		error = -ENOBUFS;
 	return error;
 }
-EXPORT_SYMBOL(fc_seq_exch_abort);
 
 /*
  * Exchange timeout - handle exchange timer expiration.
drivers/scsi/libfc/fc_fcp.c +0 -1
index e6c6f47..e613eb8 100644
@@ -285,7 +285,6 @@ void fc_fcp_ddp_setup(struct fc_fcp_pkt *fsp, u16 xid)
 			fsp->xfer_ddp = xid;
 	}
 }
-EXPORT_SYMBOL(fc_fcp_ddp_setup);
 
 /*
  * fc_fcp_ddp_done - calls to LLD's ddp_done to release any
drivers/scsi/libfc/fc_frame.c +0 -1
index 63fe00c..ac3681a 100644
@@ -69,7 +69,6 @@ struct fc_frame *__fc_frame_alloc(size_t len)
 }
 EXPORT_SYMBOL(__fc_frame_alloc);
 
-
 struct fc_frame *fc_frame_alloc_fill(struct fc_lport *lp, size_t payload_len)
 {
 	struct fc_frame *fp;
drivers/scsi/libfc/fc_rport.c +0 -2
index bdc9732..1f795e4 100644
@@ -1565,13 +1565,11 @@ int fc_setup_rport(void)
 		return -ENOMEM;
 	return 0;
 }
-EXPORT_SYMBOL(fc_setup_rport);
 
 void fc_destroy_rport(void)
 {
 	destroy_workqueue(rport_event_queue);
 }
-EXPORT_SYMBOL(fc_destroy_rport);
 
 void fc_rport_terminate_io(struct fc_rport *rport)
 {
#5 lore raw

PATCH 5/20 libfc: Remove unused fc_lport pointer from fc_fcp_pkt_abort

In reply to Robert Love

This argument isn't used, let's not pass it into the routine.

---

 drivers/scsi/libfc/fc_fcp.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
drivers/scsi/libfc/fc_fcp.c +2 -2
index e613eb8..ade962d 100644
@@ -1097,7 +1097,7 @@ unlock:
  * Scsi abort handler- calls to send an abort
  * and then wait for abort completion
  */
-static int fc_fcp_pkt_abort(struct fc_lport *lp, struct fc_fcp_pkt *fsp)
+static int fc_fcp_pkt_abort(struct fc_fcp_pkt *fsp)
 {
 	int rc = FAILED;
 
@@ -1945,7 +1945,7 @@ int fc_eh_abort(struct scsi_cmnd *sc_cmd)
 		goto release_pkt;
 	}
 
-	rc = fc_fcp_pkt_abort(lp, fsp);
+	rc = fc_fcp_pkt_abort(fsp);
 	fc_fcp_unlock_pkt(fsp);
 
 release_pkt:
#6 lore raw

PATCH 6/20 libfc: Fix wrong scsi return status under FC_DATA_UNDRUN

In reply to Robert Love

From: Yi Zou <[email protected]>

This bug is exposed when there is a link flap in LLD. Particularly, when it happens right after a SCSI write command is sent out, no FCP_DATA is sent, causing fsp->status_code to be set as FC_DATA_UNDRUN in fc_fcp_complete_locked even no SCSI status is received. Consequently, fc_io_compl treats this as DID_OK. This results in SCSI returning successful to the initial I/O request even there is no DATA actually sent. Particularly, if you run an I/O tool w/ data verification on, the read back for verification is gonna fail.

This is fixed here by checking when FC_DATA_UNDRUN happens, SCSI status is received w/ FC_SRB_RCV_STATUS set in fsp->state.

---

 drivers/scsi/libfc/fc_fcp.c |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
drivers/scsi/libfc/fc_fcp.c +2 -1
index ade962d..40ed744 100644
@@ -1849,7 +1849,8 @@ static void fc_io_compl(struct fc_fcp_pkt *fsp)
 			 * scsi status is good but transport level
 			 * underrun.
 			 */
-			sc_cmd->result = DID_OK << 16;
+			sc_cmd->result = (fsp->state & FC_SRB_RCV_STATUS ?
+					  DID_OK : DID_ERROR) << 16;
 		} else {
 			/*
 			 * scsi got underrun, this is an error
#7 lore raw

PATCH 7/20 libfc: lport: fix minor documentation errors

In reply to Robert Love

From: Joe Eykholt <[email protected]>

Fix minor errors.
A debug message said an RLIR was received instead of ECHO. "Expected" was misspelled in several places.
Fix a type cast from u32 to __be32.

Rob, Some of these may have been also taken care of in your other doc cleanup patch. Feel free to fold them in.

---

 drivers/scsi/libfc/fc_lport.c |   20 ++++++++++----------
 1 files changed, 10 insertions(+), 10 deletions(-)
drivers/scsi/libfc/fc_lport.c +10 -10
index bd2f771..eefe87d 100644
@@ -329,7 +329,7 @@ static void fc_lport_add_fc4_type(struct fc_lport *lport, enum fc_fh_type type)
  * @sp: current sequence in the RLIR exchange
  * @fp: RLIR request frame
  *
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
  * this function.
  */
 static void fc_lport_recv_rlir_req(struct fc_seq *sp, struct fc_frame *fp,
@@ -348,7 +348,7 @@ static void fc_lport_recv_rlir_req(struct fc_seq *sp, struct fc_frame *fp,
  * @sp: current sequence in the ECHO exchange
  * @fp: ECHO request frame
  *
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
  * this function.
  */
 static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
@@ -361,7 +361,7 @@ static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
 	void *dp;
 	u32 f_ctl;
 
-	FC_LPORT_DBG(lport, "Received RLIR request while in state %s\n",
+	FC_LPORT_DBG(lport, "Received ECHO request while in state %s\n",
 		     fc_lport_state(lport));
 
 	len = fr_len(in_fp) - sizeof(struct fc_frame_header);
@@ -374,7 +374,7 @@ static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
 	if (fp) {
 		dp = fc_frame_payload_get(fp, len);
 		memcpy(dp, pp, len);
-		*((u32 *)dp) = htonl(ELS_LS_ACC << 24);
+		*((__be32 *)dp) = htonl(ELS_LS_ACC << 24);
 		sp = lport->tt.seq_start_next(sp);
 		f_ctl = FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ;
 		fc_fill_fc_hdr(fp, FC_RCTL_ELS_REP, ep->did, ep->sid,
@@ -385,12 +385,12 @@ static void fc_lport_recv_echo_req(struct fc_seq *sp, struct fc_frame *in_fp,
 }
 
 /**
- * fc_lport_recv_echo_req() - Handle received Request Node ID data request
- * @lport: Fibre Channel local port recieving the RNID
- * @sp: current sequence in the RNID exchange
- * @fp: RNID request frame
+ * fc_lport_recv_rnid_req() - Handle received Request Node ID data request
+ * @sp:	   The sequence in the RNID exchange
+ * @fp:	   The RNID request frame
+ * @lport: The local port recieving the RNID
  *
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
  * this function.
  */
 static void fc_lport_recv_rnid_req(struct fc_seq *sp, struct fc_frame *in_fp,
@@ -667,7 +667,7 @@ static void fc_lport_enter_ready(struct fc_lport *lport)
  * Accept it with the common service parameters indicating our N port.
  * Set up to do a PLOGI if we have the higher-number WWPN.
  *
- * Locking Note: The lport lock is exected to be held before calling
+ * Locking Note: The lport lock is expected to be held before calling
  * this function.
  */
 static void fc_lport_recv_flogi_req(struct fc_seq *sp_in,
#8 lore raw

PATCH 8/20 libfc: don't WARN_ON in lport_timeout for RESET state

In reply to Robert Love

From: Joe Eykholt <[email protected]>

It's possible and harmless to get FLOGI timeouts while in RESET state. Don't do a WARN_ON in that case.

Also, split out the other WARN_ONs in fc_lport_timeout, so we can tell which one is hit by its line number.

---

 drivers/scsi/libfc/fc_lport.c |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)
drivers/scsi/libfc/fc_lport.c +4 -1
index eefe87d..0d19ffa 100644
@@ -1237,10 +1237,13 @@ static void fc_lport_timeout(struct work_struct *work)
 
 	switch (lport->state) {
 	case LPORT_ST_DISABLED:
+		WARN_ON(1);
+		break;
 	case LPORT_ST_READY:
-	case LPORT_ST_RESET:
 		WARN_ON(1);
 		break;
+	case LPORT_ST_RESET:
+		break;
 	case LPORT_ST_FLOGI:
 		fc_lport_enter_flogi(lport);
 		break;
#9 lore raw

PATCH 9/20 libfc: removes initializing fc_cpu_order and fc_cpu_mask per lport

In reply to Robert Love

From: Vasu Dev <[email protected]>

Initializing these libfc globals per lport could mess up exch allocation/free for existing lport.

So this patch moves their initialization to fc_setup_exch_mgr so that these globals gets initialized only once for libfc.

---

 drivers/scsi/libfc/fc_exch.c |   24 ++++++++++++++----------
 1 files changed, 14 insertions(+), 10 deletions(-)
drivers/scsi/libfc/fc_exch.c +14 -10
index ae8f9e9..bdae9a9 100644
@@ -2046,6 +2046,20 @@ int fc_exch_init(struct fc_lport *lp)
 	if (!lp->tt.seq_exch_abort)
 		lp->tt.seq_exch_abort = fc_seq_exch_abort;
 
+	return 0;
+}
+EXPORT_SYMBOL(fc_exch_init);
+
+/**
+ * fc_setup_exch_mgr() - Setup an exchange manager
+ */
+int fc_setup_exch_mgr()
+{
+	fc_em_cachep = kmem_cache_create("libfc_em", sizeof(struct fc_exch),
+					 0, SLAB_HWCACHE_ALIGN, NULL);
+	if (!fc_em_cachep)
+		return -ENOMEM;
+
 	/*
 	 * Initialize fc_cpu_mask and fc_cpu_order. The
 	 * fc_cpu_mask is set for nr_cpu_ids rounded up
@@ -2070,16 +2084,6 @@ int fc_exch_init(struct fc_lport *lp)
 
 	return 0;
 }
-EXPORT_SYMBOL(fc_exch_init);
-
-int fc_setup_exch_mgr(void)
-{
-	fc_em_cachep = kmem_cache_create("libfc_em", sizeof(struct fc_exch),
-					 0, SLAB_HWCACHE_ALIGN, NULL);
-	if (!fc_em_cachep)
-		return -ENOMEM;
-	return 0;
-}
 
 void fc_destroy_exch_mgr(void)
 {
#10 lore raw

PATCH 10/20 libfc: adds missing exch release for accepted RRQ

In reply to Robert Love

From: Vasu Dev <[email protected]>

Adds missing exch release when RRQ is accepted by calling fc_seq_ls_acc. Adds common exch release for fc_exch_els_rrq by use of out label.

---

 drivers/scsi/libfc/fc_exch.c |    9 +++++----
 1 files changed, 5 insertions(+), 4 deletions(-)
drivers/scsi/libfc/fc_exch.c +5 -4
index bdae9a9..8ce4182 100644
@@ -1718,7 +1718,7 @@ retry:
  */
 static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
 {
-	struct fc_exch *ep;		/* request or subject exchange */
+	struct fc_exch *ep = NULL;	/* request or subject exchange */
 	struct fc_els_rrq *rp;
 	u32 sid;
 	u16 xid;
@@ -1768,15 +1768,16 @@ static void fc_exch_els_rrq(struct fc_seq *sp, struct fc_frame *fp)
 	 * Send LS_ACC.
 	 */
 	fc_seq_ls_acc(sp);
-	fc_frame_free(fp);
-	return;
+	goto out;
 
 unlock_reject:
 	spin_unlock_bh(&ep->ex_lock);
-	fc_exch_release(ep);	/* drop hold from fc_exch_find */
 reject:
 	fc_seq_ls_rjt(sp, ELS_RJT_LOGIC, explan);
+out:
 	fc_frame_free(fp);
+	if (ep)
+		fc_exch_release(ep);	/* drop hold from fc_exch_find */
 }
 
 struct fc_exch_mgr_anchor *fc_exch_mgr_add(struct fc_lport *lport,
#11 lore raw

PATCH 11/20 libfc: removes unused disc_work and ex_list

In reply to Robert Love

From: Vasu Dev <[email protected]>

---

 drivers/scsi/libfc/fc_exch.c |    1 -
 include/scsi/libfc.h         |    1 -
 2 files changed, 0 insertions(+), 2 deletions(-)
drivers/scsi/libfc/fc_exch.c +0 -1
index 8ce4182..170cdf4 100644
@@ -75,7 +75,6 @@ struct fc_exch_mgr {
 	struct kref	kref;		/* exchange mgr reference count */
 	u16		min_xid;	/* min exchange ID */
 	u16		max_xid;	/* max exchange ID */
-	struct list_head	ex_list;	/* allocated exchanges list */
 	mempool_t	*ep_pool;	/* reserve ep's */
 	u16		pool_max_index;	/* max exch array index in exch pool */
 	struct fc_exch_pool *pool;	/* per cpu exch pool */
include/scsi/libfc.h +0 -1
index 65dc9aa..4ff1485 100644
@@ -742,7 +742,6 @@ struct fc_lport {
 
 	/* Miscellaneous */
 	struct delayed_work	retry_work;
-	struct delayed_work	disc_work;
 };
 
 /*
#12 lore raw

PATCH 12/20 fcoe: initialize return value in fcoe_destroy

In reply to Robert Love

From: Mike Christie <[email protected]>

When doing echo ethX > /sys..../destroy I am getting errors when the tear down succeeds. It looks like the reason for this is because the rc var is not getting set when the destruction works. This just sets it to zero.

---

 drivers/scsi/fcoe/fcoe.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
drivers/scsi/fcoe/fcoe.c +1 -1
index 7c89887..8702c8d 100644
@@ -1631,7 +1631,7 @@ static int fcoe_destroy(const char *buffer, struct kernel_param *kp)
 {
 	struct fcoe_interface *fcoe;
 	struct net_device *netdev;
-	int rc;
+	int rc = 0;
 
 	mutex_lock(&fcoe_config_mutex);
 #ifdef CONFIG_FCOE_MODULE
#13 lore raw

PATCH 13/20 fcoe: Use NETIF_F_FCOE_MTU flag to set up max frame size (lport->mfs)

In reply to Robert Love

From: Yi Zou <[email protected]>

Add a define of FCOE_MTU as 2158 bytes and use FCOE_MTU when the LLD is found to support NETIF_F_FCOE_MTU. The lport->mfs is then calculated out of the 2158 FCOE_MTU. Otherwise, we stick with the netdev->mtu, i.e., LAN MTU. Also, change the notification on NETDEV_CHANGEMTU event to bypass changing mfs when LAN MTU is changed if NETIF_F_FCOE_MTU is supported.

---

 drivers/scsi/fcoe/fcoe.c |   10 ++++++++--
 drivers/scsi/fcoe/fcoe.h |    6 ++++++
 2 files changed, 14 insertions(+), 2 deletions(-)
drivers/scsi/fcoe/fcoe.c +8 -2
index 8702c8d..c66b9fa 100644
@@ -439,8 +439,12 @@ static int fcoe_netdev_config(struct fc_lport *lp, struct net_device *netdev)
 	 * user-configured limit.  If the MFS is too low, fcoe_link_ok()
 	 * will return 0, so do this first.
 	 */
-	mfs = netdev->mtu - (sizeof(struct fcoe_hdr) +
-			     sizeof(struct fcoe_crc_eof));
+	mfs = netdev->mtu;
+	if (netdev->features & NETIF_F_FCOE_MTU) {
+		mfs = FCOE_MTU;
+		FCOE_NETDEV_DBG(netdev, "Supports FCOE_MTU of %d bytes\n", mfs);
+	}
+	mfs -= (sizeof(struct fcoe_hdr) + sizeof(struct fcoe_crc_eof));
 	if (fc_set_mfs(lp, mfs))
 		return -EINVAL;
 
@@ -1570,6 +1574,8 @@ static int fcoe_device_notification(struct notifier_block *notifier,
 	case NETDEV_CHANGE:
 		break;
 	case NETDEV_CHANGEMTU:
+		if (netdev->features & NETIF_F_FCOE_MTU)
+			break;
 		mfs = netdev->mtu - (sizeof(struct fcoe_hdr) +
 				     sizeof(struct fcoe_crc_eof));
 		if (mfs >= FC_MIN_MAX_FRAME)
drivers/scsi/fcoe/fcoe.h +6 -0
index ce7f60f..c578082 100644
@@ -40,6 +40,12 @@
 #define FCOE_MIN_XID		0x0000	/* the min xid supported by fcoe_sw */
 #define FCOE_MAX_XID		0x0FFF	/* the max xid supported by fcoe_sw */
 
+/*
+ * Max MTU for FCoE: 14 (FCoE header) + 24 (FC header) + 2112 (max FC payload)
+ * + 4 (FC CRC) + 4 (FCoE trailer) =  2158 bytes
+ */
+#define FCOE_MTU	2158
+
 unsigned int fcoe_debug_logging;
 module_param_named(debug_logging, fcoe_debug_logging, int, S_IRUGO|S_IWUSR);
 MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels");
#14 lore raw

PATCH 14/20 libfc: Fix frags in frame exceeding SKB_MAX_FRAGS in fc_fcp_send_data

In reply to Robert Love

From: Yi Zou <[email protected]>

In case of sequence offload, in fc_fcp_send_data(), the skb_fill_page_info() called may end up adding more frags to the skb_shinfo(fp_skb(fp))->frags[], exceeding SKB_MAX_FRAGS, this eventually corrupts the memory. I am adding the FR_FRAME_SG_LEN back, but as SKB_MAX_FRAGS -1, leaving 1 for our fcoe_eof_crc page. And send will be broken into multiple large sends if the frame already contains more frags than skb handle.

---

 drivers/scsi/libfc/fc_fcp.c |    3 ++-
 include/scsi/fc_frame.h     |    3 +++
 2 files changed, 5 insertions(+), 1 deletions(-)
drivers/scsi/libfc/fc_fcp.c +2 -1
index 40ed744..28bfe1c 100644
@@ -574,7 +574,8 @@ static int fc_fcp_send_data(struct fc_fcp_pkt *fsp, struct fc_seq *seq,
 		tlen -= sg_bytes;
 		remaining -= sg_bytes;
 
-		if (tlen)
+		if ((skb_shinfo(fp_skb(fp))->nr_frags < FC_FRAME_SG_LEN) &&
+		    (tlen))
 			continue;
 
 		/*
include/scsi/fc_frame.h +3 -0
index c35d238..148126d 100644
@@ -37,6 +37,9 @@
 #define	FC_FRAME_HEADROOM	32	/* headroom for VLAN + FCoE headers */
 #define	FC_FRAME_TAILROOM	8	/* trailer space for FCoE */
 
+/* Max number of skb frags allowed, reserving one for fcoe_crc_eof page */
+#define FC_FRAME_SG_LEN		(MAX_SKB_FRAGS - 1)
+
 #define fp_skb(fp)	(&((fp)->skb))
 #define fr_hdr(fp)	((fp)->skb.data)
 #define fr_len(fp)	((fp)->skb.len)
#15 lore raw

PATCH 15/20 fcoe: Call ndo_fcoe_enable/disable to turn FCoE feature on/off in LLD

In reply to Robert Love

From: Yi Zou <[email protected]>

Calls ndo_fcoe_enabled() of the associated netdev upon creating the FCoE instance to make sure LLD has all necessary resources allocated and setup properly before passing FCoE traffic. Similarly, calls ndo_fcoe_disable() upon destroying the FCoE instance on the associated netdev to allow the LLD to release all allocated resources for FCoE.

---

 drivers/scsi/fcoe/fcoe.c |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)
drivers/scsi/fcoe/fcoe.c +18 -0
index c66b9fa..aef29af 100644
@@ -161,9 +161,18 @@ static int fcoe_interface_setup(struct fcoe_interface *fcoe,
 	struct fcoe_ctlr *fip = &fcoe->ctlr;
 	struct netdev_hw_addr *ha;
 	u8 flogi_maddr[ETH_ALEN];
+	const struct net_device_ops *ops;
 
 	fcoe->netdev = netdev;
 
+	/* Let LLD initialize for FCoE */
+	ops = netdev->netdev_ops;
+	if (ops->ndo_fcoe_enable) {
+		if (ops->ndo_fcoe_enable(netdev))
+			FCOE_NETDEV_DBG(netdev, "Failed to enable FCoE"
+					" specific feature for LLD.\n");
+	}
+
 	/* Do not support for bonding device */
 	if ((netdev->priv_flags & IFF_MASTER_ALB) ||
 	    (netdev->priv_flags & IFF_SLAVE_INACTIVE) ||
@@ -262,6 +271,7 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
 	struct net_device *netdev = fcoe->netdev;
 	struct fcoe_ctlr *fip = &fcoe->ctlr;
 	u8 flogi_maddr[ETH_ALEN];
+	const struct net_device_ops *ops;
 
 	/*
 	 * Don't listen for Ethernet packets anymore.
@@ -281,6 +291,14 @@ void fcoe_interface_cleanup(struct fcoe_interface *fcoe)
 	if (fip->spma)
 		dev_unicast_delete(netdev, fip->ctl_src_addr);
 	dev_mc_delete(netdev, FIP_ALL_ENODE_MACS, ETH_ALEN, 0);
+
+	/* Tell the LLD we are done w/ FCoE */
+	ops = netdev->netdev_ops;
+	if (ops->ndo_fcoe_disable) {
+		if (ops->ndo_fcoe_disable(netdev))
+			FCOE_NETDEV_DBG(netdev, "Failed to disable FCoE"
+					" specific feature for LLD.\n");
+	}
 }
 
 /**
#16 lore raw

PATCH 16/20 libfc: fix memory corruption caused by double frees and bad error handling

In reply to Robert Love

From: Chris Leech <[email protected]>

I was running into several different panics under stress, which I traced down to a few different possible slab corruption issues in error handling paths. I have not yet looked into why these exchange sends fail, but with these fixes my test system is much more stable under stress than before.

fc_elsct_send() could fail and either leave the passed in frame intact (failure in fc_ct/els_fill) or the frame could have been freed if the failure was is fc_exch_seq_send(). The caller had no way of knowing, and there was a potential double free in the error handling in fc_fcp_rec().

Make fc_elsct_send() always free the frame before returning, and remove the fc_frame_free() call in fc_fcp_rec().

While fc_exch_seq_send() did always consume the frame, there were double free bugs in the error handling of fc_fcp_cmd_send() and fc_fcp_srr() as well.

Numerous calls to error handling routines (fc_disc_error(), fc_lport_error(), fc_rport_error_retry() ) were passing in a frame pointer that had already been freed in the case of an error. I have changed the call sites to pass in a NULL pointer, but there may be more appropriate error codes to use.

Question: Why do these error routines take a frame pointer anyway? I understand passing in a pointer encoded error to the response handlers, but the error routines take no action on a valid pointer and should never be called that way.

---

 drivers/scsi/libfc/fc_disc.c  |    2 +-
 drivers/scsi/libfc/fc_elsct.c |    4 +++-
 drivers/scsi/libfc/fc_fcp.c   |    7 ++-----
 drivers/scsi/libfc/fc_lport.c |    8 ++++----
 drivers/scsi/libfc/fc_rport.c |   10 +++++-----
 5 files changed, 15 insertions(+), 16 deletions(-)
drivers/scsi/libfc/fc_disc.c +1 -1
index c48799e..d4cb3f9 100644
@@ -371,7 +371,7 @@ static void fc_disc_gpn_ft_req(struct fc_disc *disc)
 				 disc, lport->e_d_tov))
 		return;
 err:
-	fc_disc_error(disc, fp);
+	fc_disc_error(disc, NULL);
 }
 
 /**
drivers/scsi/libfc/fc_elsct.c +3 -1
index 5cfa687..9298458 100644
@@ -53,8 +53,10 @@ static struct fc_seq *fc_elsct_send(struct fc_lport *lport,
 		did = FC_FID_DIR_SERV;
 	}
 
-	if (rc)
+	if (rc) {
+		fc_frame_free(fp);
 		return NULL;
+	}
 
 	fc_fill_fc_hdr(fp, r_ctl, did, fc_host_port_id(lport->host), fh_type,
 		       FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0);
drivers/scsi/libfc/fc_fcp.c +2 -5
index 28bfe1c..a67f53a 100644
@@ -1051,7 +1051,6 @@ static int fc_fcp_cmd_send(struct fc_lport *lp, struct fc_fcp_pkt *fsp,
 
 	seq = lp->tt.exch_seq_send(lp, fp, resp, fc_fcp_pkt_destroy, fsp, 0);
 	if (!seq) {
-		fc_frame_free(fp);
 		rc = -1;
 		goto unlock;
 	}
@@ -1316,7 +1315,6 @@ static void fc_fcp_rec(struct fc_fcp_pkt *fsp)
 		fc_fcp_pkt_hold(fsp);		/* hold while REC outstanding */
 		return;
 	}
-	fc_frame_free(fp);
 retry:
 	if (fsp->recov_retry++ < FC_MAX_RECOV_RETRY)
 		fc_fcp_timer_set(fsp, FC_SCSI_REC_TOV);
@@ -1564,10 +1562,9 @@ static void fc_fcp_srr(struct fc_fcp_pkt *fsp, enum fc_rctl r_ctl, u32 offset)
 
 	seq = lp->tt.exch_seq_send(lp, fp, fc_fcp_srr_resp, NULL,
 				   fsp, jiffies_to_msecs(FC_SCSI_REC_TOV));
-	if (!seq) {
-		fc_frame_free(fp);
+	if (!seq)
 		goto retry;
-	}
+
 	fsp->recov_seq = seq;
 	fsp->xfer_len = offset;
 	fsp->xfer_contig_end = offset;
drivers/scsi/libfc/fc_lport.c +4 -4
index 0d19ffa..536492a 100644
@@ -1115,7 +1115,7 @@ static void fc_lport_enter_scr(struct fc_lport *lport)
 
 	if (!lport->tt.elsct_send(lport, FC_FID_FCTRL, fp, ELS_SCR,
 				  fc_lport_scr_resp, lport, lport->e_d_tov))
-		fc_lport_error(lport, fp);
+		fc_lport_error(lport, NULL);
 }
 
 /**
@@ -1186,7 +1186,7 @@ static void fc_lport_enter_rpn_id(struct fc_lport *lport)
 	if (!lport->tt.elsct_send(lport, FC_FID_DIR_SERV, fp, FC_NS_RPN_ID,
 				  fc_lport_rpn_id_resp,
 				  lport, lport->e_d_tov))
-		fc_lport_error(lport, fp);
+		fc_lport_error(lport, NULL);
 }
 
 static struct fc_rport_operations fc_lport_rport_ops = {
@@ -1340,7 +1340,7 @@ static void fc_lport_enter_logo(struct fc_lport *lport)
 
 	if (!lport->tt.elsct_send(lport, FC_FID_FLOGI, fp, ELS_LOGO,
 				  fc_lport_logo_resp, lport, lport->e_d_tov))
-		fc_lport_error(lport, fp);
+		fc_lport_error(lport, NULL);
 }
 
 /**
@@ -1456,7 +1456,7 @@ void fc_lport_enter_flogi(struct fc_lport *lport)
 
 	if (!lport->tt.elsct_send(lport, FC_FID_FLOGI, fp, ELS_FLOGI,
 				  fc_lport_flogi_resp, lport, lport->e_d_tov))
-		fc_lport_error(lport, fp);
+		fc_lport_error(lport, NULL);
 }
 
 /* Configure a fc_lport */
drivers/scsi/libfc/fc_rport.c +5 -5
index 1f795e4..49abb83 100644
@@ -632,7 +632,7 @@ static void fc_rport_enter_plogi(struct fc_rport_priv *rdata)
 
 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_PLOGI,
 				  fc_rport_plogi_resp, rdata, lport->e_d_tov))
-		fc_rport_error_retry(rdata, fp);
+		fc_rport_error_retry(rdata, NULL);
 	else
 		kref_get(&rdata->kref);
 }
@@ -793,7 +793,7 @@ static void fc_rport_enter_prli(struct fc_rport_priv *rdata)
 
 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_PRLI,
 				  fc_rport_prli_resp, rdata, lport->e_d_tov))
-		fc_rport_error_retry(rdata, fp);
+		fc_rport_error_retry(rdata, NULL);
 	else
 		kref_get(&rdata->kref);
 }
@@ -889,7 +889,7 @@ static void fc_rport_enter_rtv(struct fc_rport_priv *rdata)
 
 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_RTV,
 				     fc_rport_rtv_resp, rdata, lport->e_d_tov))
-		fc_rport_error_retry(rdata, fp);
+		fc_rport_error_retry(rdata, NULL);
 	else
 		kref_get(&rdata->kref);
 }
@@ -919,7 +919,7 @@ static void fc_rport_enter_logo(struct fc_rport_priv *rdata)
 
 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_LOGO,
 				  fc_rport_logo_resp, rdata, lport->e_d_tov))
-		fc_rport_error_retry(rdata, fp);
+		fc_rport_error_retry(rdata, NULL);
 	else
 		kref_get(&rdata->kref);
 }
@@ -1006,7 +1006,7 @@ static void fc_rport_enter_adisc(struct fc_rport_priv *rdata)
 	}
 	if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_ADISC,
 				  fc_rport_adisc_resp, rdata, lport->e_d_tov))
-		fc_rport_error_retry(rdata, fp);
+		fc_rport_error_retry(rdata, NULL);
 	else
 		kref_get(&rdata->kref);
 }
#17 lore raw

PATCH 17/20 fnic: Process all cq entries per ISR

In reply to Robert Love

From: Abhijeet Joglekar <[email protected]>

Driver was processing a fixed max number of cq descriptors per ISR. For instance, for the SCSI IO queue, number of IOs processed per ISR were 8. If hardware writes 9 cq descriptors to the cq and generates an interrupt, driver would process only 8 descriptors and decrement the outstanding credit count by 8. Unless another interrupt event happens, the hw does not generate any additional interrupt. This results in the cq descriptor sitting in the queue without being procesed and can cause IO timeouts and aborts.

Modify all ISR functions to process all queued cq descriptors in one shot. Since bulk of ELS frame processing is done in thread context and bulk of SCSI IO processing is done in soft ISR deferred context, the cycles spent in the ISR per cq descriptor is small.

---

 drivers/scsi/fnic/fnic_isr.c |   18 +++++++++---------
 1 files changed, 9 insertions(+), 9 deletions(-)
drivers/scsi/fnic/fnic_isr.c +9 -9
index 2b30648..5c1f223 100644
@@ -48,9 +48,9 @@ static irqreturn_t fnic_isr_legacy(int irq, void *data)
 	}
 
 	if (pba & (1 << FNIC_INTX_WQ_RQ_COPYWQ)) {
-		work_done += fnic_wq_copy_cmpl_handler(fnic, 8);
-		work_done += fnic_wq_cmpl_handler(fnic, 4);
-		work_done += fnic_rq_cmpl_handler(fnic, 4);
+		work_done += fnic_wq_copy_cmpl_handler(fnic, -1);
+		work_done += fnic_wq_cmpl_handler(fnic, -1);
+		work_done += fnic_rq_cmpl_handler(fnic, -1);
 
 		vnic_intr_return_credits(&fnic->intr[FNIC_INTX_WQ_RQ_COPYWQ],
 					 work_done,
@@ -66,9 +66,9 @@ static irqreturn_t fnic_isr_msi(int irq, void *data)
 	struct fnic *fnic = data;
 	unsigned long work_done = 0;
 
-	work_done += fnic_wq_copy_cmpl_handler(fnic, 8);
-	work_done += fnic_wq_cmpl_handler(fnic, 4);
-	work_done += fnic_rq_cmpl_handler(fnic, 4);
+	work_done += fnic_wq_copy_cmpl_handler(fnic, -1);
+	work_done += fnic_wq_cmpl_handler(fnic, -1);
+	work_done += fnic_rq_cmpl_handler(fnic, -1);
 
 	vnic_intr_return_credits(&fnic->intr[0],
 				 work_done,
@@ -83,7 +83,7 @@ static irqreturn_t fnic_isr_msix_rq(int irq, void *data)
 	struct fnic *fnic = data;
 	unsigned long rq_work_done = 0;
 
-	rq_work_done = fnic_rq_cmpl_handler(fnic, 4);
+	rq_work_done = fnic_rq_cmpl_handler(fnic, -1);
 	vnic_intr_return_credits(&fnic->intr[FNIC_MSIX_RQ],
 				 rq_work_done,
 				 1 /* unmask intr */,
@@ -97,7 +97,7 @@ static irqreturn_t fnic_isr_msix_wq(int irq, void *data)
 	struct fnic *fnic = data;
 	unsigned long wq_work_done = 0;
 
-	wq_work_done = fnic_wq_cmpl_handler(fnic, 4);
+	wq_work_done = fnic_wq_cmpl_handler(fnic, -1);
 	vnic_intr_return_credits(&fnic->intr[FNIC_MSIX_WQ],
 				 wq_work_done,
 				 1 /* unmask intr */,
@@ -110,7 +110,7 @@ static irqreturn_t fnic_isr_msix_wq_copy(int irq, void *data)
 	struct fnic *fnic = data;
 	unsigned long wq_copy_work_done = 0;
 
-	wq_copy_work_done = fnic_wq_copy_cmpl_handler(fnic, 8);
+	wq_copy_work_done = fnic_wq_copy_cmpl_handler(fnic, -1);
 	vnic_intr_return_credits(&fnic->intr[FNIC_MSIX_WQ_COPY],
 				 wq_copy_work_done,
 				 1 /* unmask intr */,
#18 lore raw

PATCH 18/20 fnic: Set max_cmd_len to driver supported CDB length

In reply to Robert Love

From: Abhijeet Joglekar <[email protected]>

---

 drivers/scsi/fnic/fnic.h      |    2 +-
 drivers/scsi/fnic/fnic_main.c |    1 +
 2 files changed, 2 insertions(+), 1 deletions(-)
drivers/scsi/fnic/fnic.h +1 -1
index e4c0a3d..1bc267e 100644
@@ -44,7 +44,7 @@
 #define	FNIC_IO_LOCKS		64 /* IO locks: power of 2 */
 #define FNIC_DFLT_QUEUE_DEPTH	32
 #define	FNIC_STATS_RATE_LIMIT	4 /* limit rate at which stats are pulled up */
-
+#define FNIC_MAX_CMD_LEN        16 /* Supported CDB length */
 /*
  * Tag bits used for special requests.
  */
drivers/scsi/fnic/fnic_main.c +1 -0
index 71c7bbe..b0d425a 100644
@@ -560,6 +560,7 @@ static int __devinit fnic_probe(struct pci_dev *pdev,
 	}
 	host->max_lun = fnic->config.luns_per_tgt;
 	host->max_id = FNIC_MAX_FCP_TARGET;
+	host->max_cmd_len = FNIC_MAX_CMD_LEN;
 
 	fnic_get_res_counts(fnic);
 
#19 lore raw

PATCH 19/20 fnic: Pad the unused bytes of CDB to 0s

In reply to Robert Love

From: Abhijeet Joglekar <[email protected]>

---

 drivers/scsi/fnic/fnic_res.h  |    4 +++-
 drivers/scsi/fnic/fnic_scsi.c |    3 ++-
 2 files changed, 5 insertions(+), 2 deletions(-)
drivers/scsi/fnic/fnic_res.h +3 -1
index b6f3102..88c4471 100644
@@ -58,6 +58,7 @@ static inline void fnic_queue_wq_copy_desc_icmnd_16(struct vnic_wq_copy *wq,
 						    u64 sgl_addr, u64 sns_addr,
 						    u8 crn, u8 pri_ta,
 						    u8 flags, u8 *scsi_cdb,
+						    u8 cdb_len,
 						    u32 data_len, u8 *lun,
 						    u32 d_id, u16 mss,
 						    u32 ratov, u32 edtov)
@@ -82,7 +83,8 @@ static inline void fnic_queue_wq_copy_desc_icmnd_16(struct vnic_wq_copy *wq,
 	desc->u.icmnd_16.pri_ta = pri_ta; 	/* SCSI Pri & Task attribute */
 	desc->u.icmnd_16._resvd1 = 0;           /* reserved: should be 0 */
 	desc->u.icmnd_16.flags = flags;         /* command flags */
-	memcpy(desc->u.icmnd_16.scsi_cdb, scsi_cdb, CDB_16);    /* SCSI CDB */
+	memset(desc->u.icmnd_16.scsi_cdb, 0, CDB_16);
+	memcpy(desc->u.icmnd_16.scsi_cdb, scsi_cdb, cdb_len);    /* SCSI CDB */
 	desc->u.icmnd_16.data_len = data_len;   /* length of data expected */
 	memcpy(desc->u.icmnd_16.lun, lun, LUN_ADDRESS);  /* LUN address */
 	desc->u.icmnd_16._resvd2 = 0;          	/* reserved */
drivers/scsi/fnic/fnic_scsi.c +2 -1
index bfc9969..b5d1738 100644
@@ -319,7 +319,8 @@ static inline int fnic_queue_wq_copy_desc(struct fnic *fnic,
 					 0, /* scsi cmd ref, always 0 */
 					 pri_tag, /* scsi pri and tag */
 					 flags,	/* command flags */
-					 sc->cmnd, scsi_bufflen(sc),
+					 sc->cmnd, sc->cmd_len,
+					 scsi_bufflen(sc),
 					 fc_lun.scsi_lun, io_req->port_id,
 					 rport->maxframe_size, rp->r_a_tov,
 					 rp->e_d_tov);
#20 lore raw

PATCH 20/20 libfc: fix free of fc_rport_priv with timer pending

In reply to Robert Love

From: Joe Eykholt <[email protected]>

Timer crashes were caused by freeing a struct fc_rport_priv with a timer pending, causing the timer facility list to be corrupted. This was during FC uplink flap tests with a lot of targets.

After discovery, we were doing an PLOGI on an rdata that was in DELETE state but not yet removed from the lookup list. This moved the rdata from DELETE state to PLOGI state. If the PLOGI exchange allocation failed and needed to be retried, the timer scheduling could race with the free being done by fc_rport_work().

When fc_rport_login() is called on a rport in DELETE state, move it to a new state RESTART. In fc_rport_work, when handling a LOGO, STOPPED or FAILED event, look for restart state. In the RESTART case, don't take the rdata off the list and after the transport remote port is deleted and exchanges are reset, re-login to the remote port.

Note that the new RESTART state also corrects a problem we had when re-discovering a port that had moved to DELETE state. In that case, a new rdata was created, but the old rdata would do an exchange manager reset affecting the FC_ID for both the new rdata and old rdata. With the new state, the new port isn't logged into until after any old exchanges are reset.

---

 drivers/scsi/libfc/fc_rport.c |   69 ++++++++++++++++++++++++++++++-----------
 include/scsi/libfc.h          |    1 +
 2 files changed, 51 insertions(+), 19 deletions(-)
drivers/scsi/libfc/fc_rport.c +50 -19
index 49abb83..324e156 100644
@@ -86,6 +86,7 @@ static const char *fc_rport_state_names[] = {
 	[RPORT_ST_LOGO] = "LOGO",
 	[RPORT_ST_ADISC] = "ADISC",
 	[RPORT_ST_DELETE] = "Delete",
+	[RPORT_ST_RESTART] = "Restart",
 };
 
 /**
@@ -99,8 +100,7 @@ static struct fc_rport_priv *fc_rport_lookup(const struct fc_lport *lport,
 	struct fc_rport_priv *rdata;
 
 	list_for_each_entry(rdata, &lport->disc.rports, peers)
-		if (rdata->ids.port_id == port_id &&
-		    rdata->rp_state != RPORT_ST_DELETE)
+		if (rdata->ids.port_id == port_id)
 			return rdata;
 	return NULL;
 }
@@ -235,6 +235,7 @@ static void fc_rport_work(struct work_struct *work)
 	struct fc_rport_operations *rport_ops;
 	struct fc_rport_identifiers ids;
 	struct fc_rport *rport;
+	int restart = 0;
 
 	mutex_lock(&rdata->rp_mutex);
 	event = rdata->event;
@@ -287,8 +288,19 @@ static void fc_rport_work(struct work_struct *work)
 		mutex_unlock(&rdata->rp_mutex);
 
 		if (port_id != FC_FID_DIR_SERV) {
+			/*
+			 * We must drop rp_mutex before taking disc_mutex.
+			 * Re-evaluate state to allow for restart.
+			 * A transition to RESTART state must only happen
+			 * while disc_mutex is held and rdata is on the list.
+			 */
 			mutex_lock(&lport->disc.disc_mutex);
-			list_del(&rdata->peers);
+			mutex_lock(&rdata->rp_mutex);
+			if (rdata->rp_state == RPORT_ST_RESTART)
+				restart = 1;
+			else
+				list_del(&rdata->peers);
+			mutex_unlock(&rdata->rp_mutex);
 			mutex_unlock(&lport->disc.disc_mutex);
 		}
 
@@ -312,7 +324,13 @@ static void fc_rport_work(struct work_struct *work)
 			mutex_unlock(&rdata->rp_mutex);
 			fc_remote_port_delete(rport);
 		}
-		kref_put(&rdata->kref, lport->tt.rport_destroy);
+		if (restart) {
+			mutex_lock(&rdata->rp_mutex);
+			FC_RPORT_DBG(rdata, "work restart\n");
+			fc_rport_enter_plogi(rdata);
+			mutex_unlock(&rdata->rp_mutex);
+		} else
+			kref_put(&rdata->kref, lport->tt.rport_destroy);
 		break;
 
 	default:
@@ -342,6 +360,12 @@ int fc_rport_login(struct fc_rport_priv *rdata)
 		FC_RPORT_DBG(rdata, "ADISC port\n");
 		fc_rport_enter_adisc(rdata);
 		break;
+	case RPORT_ST_RESTART:
+		break;
+	case RPORT_ST_DELETE:
+		FC_RPORT_DBG(rdata, "Restart deleted port\n");
+		fc_rport_state_enter(rdata, RPORT_ST_RESTART);
+		break;
 	default:
 		FC_RPORT_DBG(rdata, "Login to port\n");
 		fc_rport_enter_plogi(rdata);
@@ -397,20 +421,21 @@ int fc_rport_logoff(struct fc_rport_priv *rdata)
 
 	if (rdata->rp_state == RPORT_ST_DELETE) {
 		FC_RPORT_DBG(rdata, "Port in Delete state, not removing\n");
-		mutex_unlock(&rdata->rp_mutex);
 		goto out;
 	}
 
-	fc_rport_enter_logo(rdata);
+	if (rdata->rp_state == RPORT_ST_RESTART)
+		FC_RPORT_DBG(rdata, "Port in Restart state, deleting\n");
+	else
+		fc_rport_enter_logo(rdata);
 
 	/*
 	 * Change the state to Delete so that we discard
 	 * the response.
 	 */
 	fc_rport_enter_delete(rdata, RPORT_EV_STOP);
-	mutex_unlock(&rdata->rp_mutex);
-
 out:
+	mutex_unlock(&rdata->rp_mutex);
 	return 0;
 }
 
@@ -466,6 +491,7 @@ static void fc_rport_timeout(struct work_struct *work)
 	case RPORT_ST_READY:
 	case RPORT_ST_INIT:
 	case RPORT_ST_DELETE:
+	case RPORT_ST_RESTART:
 		break;
 	}
 
@@ -499,6 +525,7 @@ static void fc_rport_error(struct fc_rport_priv *rdata, struct fc_frame *fp)
 		fc_rport_enter_logo(rdata);
 		break;
 	case RPORT_ST_DELETE:
+	case RPORT_ST_RESTART:
 	case RPORT_ST_READY:
 	case RPORT_ST_INIT:
 		break;
@@ -1248,6 +1275,7 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport,
 		}
 		break;
 	case RPORT_ST_PRLI:
+	case RPORT_ST_RTV:
 	case RPORT_ST_READY:
 	case RPORT_ST_ADISC:
 		FC_RPORT_DBG(rdata, "Received PLOGI in logged-in state %d "
@@ -1255,11 +1283,14 @@ static void fc_rport_recv_plogi_req(struct fc_lport *lport,
 		/* XXX TBD - should reset */
 		break;
 	case RPORT_ST_DELETE:
-	default:
-		FC_RPORT_DBG(rdata, "Received PLOGI in unexpected state %d\n",
-			     rdata->rp_state);
-		fc_frame_free(rx_fp);
-		goto out;
+	case RPORT_ST_LOGO:
+	case RPORT_ST_RESTART:
+		FC_RPORT_DBG(rdata, "Received PLOGI in state %s - send busy\n",
+			     fc_rport_state(rdata));
+		mutex_unlock(&rdata->rp_mutex);
+		rjt_data.reason = ELS_RJT_BUSY;
+		rjt_data.explan = ELS_EXPL_NONE;
+		goto reject;
 	}
 
 	/*
@@ -1510,14 +1541,14 @@ static void fc_rport_recv_logo_req(struct fc_lport *lport,
 		FC_RPORT_DBG(rdata, "Received LOGO request while in state %s\n",
 			     fc_rport_state(rdata));
 
+		fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
+
 		/*
-		 * If the remote port was created due to discovery,
-		 * log back in.  It may have seen a stale RSCN about us.
+		 * If the remote port was created due to discovery, set state
+		 * to log back in.  It may have seen a stale RSCN about us.
 		 */
-		if (rdata->rp_state != RPORT_ST_DELETE && rdata->disc_id)
-			fc_rport_enter_plogi(rdata);
-		else
-			fc_rport_enter_delete(rdata, RPORT_EV_LOGO);
+		if (rdata->disc_id)
+			fc_rport_state_enter(rdata, RPORT_ST_RESTART);
 		mutex_unlock(&rdata->rp_mutex);
 	} else
 		FC_RPORT_ID_DBG(lport, sid,
include/scsi/libfc.h +1 -0
index 4ff1485..1662d73 100644
@@ -145,6 +145,7 @@ enum fc_rport_state {
 	RPORT_ST_LOGO,		/* port logout sent */
 	RPORT_ST_ADISC,		/* Discover Address sent */
 	RPORT_ST_DELETE,	/* port being deleted */
+	RPORT_ST_RESTART,       /* remote port being deleted and will restart */
 };
 
 /**