--- /dev/null
+From: Vasu Dev <vasu.dev@intel.com>
+Subject: libfc, fcoe: fixed locking issues with lport->lp_mutex around lport->link_status
+Patch-mainline: 6d235742e63f6b8912d8b200b75f9aa6d48f3e07
+References: bnc #468053
+
+The fcoe_xmit could call fc_pause in case the pending skb queue len is larger
+than FCOE_MAX_QUEUE_DEPTH, the fc_pause was trying to grab lport->lp_muex to
+change lport->link_status and that had these issues :-
+
+1. The fcoe_xmit was getting called with bh disabled, thus causing
+"BUG: scheduling while atomic" when grabbing lport->lp_muex with bh disabled.
+
+2. fc_linkup and fc_linkdown function calls lport_enter function with
+lport->lp_mutex held and these enter function in turn calls fcoe_xmit to send
+lport related FC frame, e.g. fc_linkup => fc_lport_enter_flogi to send flogi
+req. In this case grabbing the same lport->lp_mutex again in fc_puase from
+fcoe_xmit would cause deadlock.
+
+The lport->lp_mutex was used for setting FC_PAUSE in fcoe_xmit path but
+FC_PAUSE bit was not used anywhere beside just setting and clear this
+bit in lport->link_status, instead used a separate field qfull in fc_lport
+to eliminate need for lport->lp_mutex to track pending queue full condition
+and in turn avoid above described two locking issues.
+
+Also added check for lp->qfull in fc_fcp_lport_queue_ready to trigger
+SCSI_MLQUEUE_HOST_BUSY when lp->qfull is set to prevent more scsi-ml cmds
+while lp->qfull is set.
+
+This patch eliminated FC_LINK_UP and FC_PAUSE and instead used dedicated
+fields in fc_lport for this, this simplified all related conditional
+code.
+
+Also removed fc_pause and fc_unpause functions and instead used newly added
+lport->qfull directly in fcoe.
+
+Also fixed a circular locking in fc_exch_recv_abts.
+
+These issues were blocking large file copy to a 2TB lun.
+
+Signed-off-by: Vasu Dev <vasu.dev@intel.com>
+Acked-by: Bernhard Walle <bwalle@suse.de>
+---
+ drivers/scsi/fcoe/fcoe_sw.c | 6 +++---
+ drivers/scsi/fcoe/libfcoe.c | 41 +++++++++++++++++------------------------
+ drivers/scsi/libfc/fc_exch.c | 2 +-
+ drivers/scsi/libfc/fc_fcp.c | 6 +++---
+ drivers/scsi/libfc/fc_lport.c | 38 +++++++-------------------------------
+ drivers/scsi/libfc/fc_rport.c | 2 +-
+ include/scsi/libfc.h | 12 ++----------
+ 7 files changed, 34 insertions(+), 73 deletions(-)
+
+--- a/drivers/scsi/fcoe/fcoe_sw.c
++++ b/drivers/scsi/fcoe/fcoe_sw.c
+@@ -116,7 +116,8 @@ static int fcoe_sw_lport_config(struct f
+ {
+ int i = 0;
+
+- lp->link_status = 0;
++ lp->link_up = 0;
++ lp->qfull = 0;
+ lp->max_retry_count = 3;
+ lp->e_d_tov = 2 * 1000; /* FC-FS default */
+ lp->r_a_tov = 2 * 2 * 1000;
+@@ -181,9 +182,8 @@ static int fcoe_sw_netdev_config(struct
+ if (fc_set_mfs(lp, mfs))
+ return -EINVAL;
+
+- lp->link_status = ~FC_PAUSE & ~FC_LINK_UP;
+ if (!fcoe_link_ok(lp))
+- lp->link_status |= FC_LINK_UP;
++ lp->link_up = 1;
+
+ /* offload features support */
+ if (fc->real_dev->features & NETIF_F_SG)
+--- a/drivers/scsi/fcoe/libfcoe.c
++++ b/drivers/scsi/fcoe/libfcoe.c
+@@ -505,7 +505,7 @@ int fcoe_xmit(struct fc_lport *lp, struc
+ if (rc) {
+ fcoe_insert_wait_queue(lp, skb);
+ if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH)
+- fc_pause(lp);
++ lp->qfull = 1;
+ }
+
+ return 0;
+@@ -719,7 +719,7 @@ static void fcoe_recv_flogi(struct fcoe_
+ * fcoe_watchdog - fcoe timer callback
+ * @vp:
+ *
+- * This checks the pending queue length for fcoe and put fcoe to be paused state
++ * This checks the pending queue length for fcoe and set lport qfull
+ * if the FCOE_MAX_QUEUE_DEPTH is reached. This is done for all fc_lport on the
+ * fcoe_hostlist.
+ *
+@@ -729,17 +729,17 @@ void fcoe_watchdog(ulong vp)
+ {
+ struct fc_lport *lp;
+ struct fcoe_softc *fc;
+- int paused = 0;
++ int qfilled = 0;
+
+ read_lock(&fcoe_hostlist_lock);
+ list_for_each_entry(fc, &fcoe_hostlist, list) {
+ lp = fc->lp;
+ if (lp) {
+ if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH)
+- paused = 1;
++ qfilled = 1;
+ if (fcoe_check_wait_queue(lp) < FCOE_MAX_QUEUE_DEPTH) {
+- if (paused)
+- fc_unpause(lp);
++ if (qfilled)
++ lp->qfull = 0;
+ }
+ }
+ }
+@@ -768,8 +768,7 @@ void fcoe_watchdog(ulong vp)
+ **/
+ static int fcoe_check_wait_queue(struct fc_lport *lp)
+ {
+- int rc, unpause = 0;
+- int paused = 0;
++ int rc;
+ struct sk_buff *skb;
+ struct fcoe_softc *fc;
+
+@@ -777,10 +776,10 @@ static int fcoe_check_wait_queue(struct
+ spin_lock_bh(&fc->fcoe_pending_queue.lock);
+
+ /*
+- * is this interface paused?
++ * if interface pending queue full then set qfull in lport.
+ */
+ if (fc->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH)
+- paused = 1;
++ lp->qfull = 1;
+ if (fc->fcoe_pending_queue.qlen) {
+ while ((skb = __skb_dequeue(&fc->fcoe_pending_queue)) != NULL) {
+ spin_unlock_bh(&fc->fcoe_pending_queue.lock);
+@@ -792,11 +791,9 @@ static int fcoe_check_wait_queue(struct
+ spin_lock_bh(&fc->fcoe_pending_queue.lock);
+ }
+ if (fc->fcoe_pending_queue.qlen < FCOE_MAX_QUEUE_DEPTH)
+- unpause = 1;
++ lp->qfull = 0;
+ }
+ spin_unlock_bh(&fc->fcoe_pending_queue.lock);
+- if ((unpause) && (paused))
+- fc_unpause(lp);
+ return fc->fcoe_pending_queue.qlen;
+ }
+
+@@ -874,7 +871,7 @@ static int fcoe_device_notification(stru
+ struct net_device *real_dev = ptr;
+ struct fcoe_softc *fc;
+ struct fcoe_dev_stats *stats;
+- u16 new_status;
++ u32 new_link_up;
+ u32 mfs;
+ int rc = NOTIFY_OK;
+
+@@ -891,17 +888,15 @@ static int fcoe_device_notification(stru
+ goto out;
+ }
+
+- new_status = lp->link_status;
++ new_link_up = lp->link_up;
+ switch (event) {
+ case NETDEV_DOWN:
+ case NETDEV_GOING_DOWN:
+- new_status &= ~FC_LINK_UP;
++ new_link_up = 0;
+ break;
+ case NETDEV_UP:
+ case NETDEV_CHANGE:
+- new_status &= ~FC_LINK_UP;
+- if (!fcoe_link_ok(lp))
+- new_status |= FC_LINK_UP;
++ new_link_up = !fcoe_link_ok(lp);
+ break;
+ case NETDEV_CHANGEMTU:
+ mfs = fc->real_dev->mtu -
+@@ -909,17 +904,15 @@ static int fcoe_device_notification(stru
+ sizeof(struct fcoe_crc_eof));
+ if (mfs >= FC_MIN_MAX_FRAME)
+ fc_set_mfs(lp, mfs);
+- new_status &= ~FC_LINK_UP;
+- if (!fcoe_link_ok(lp))
+- new_status |= FC_LINK_UP;
++ new_link_up = !fcoe_link_ok(lp);
+ break;
+ case NETDEV_REGISTER:
+ break;
+ default:
+ FC_DBG("unknown event %ld call", event);
+ }
+- if (lp->link_status != new_status) {
+- if ((new_status & FC_LINK_UP) == FC_LINK_UP)
++ if (lp->link_up != new_link_up) {
++ if (new_link_up)
+ fc_linkup(lp);
+ else {
+ stats = lp->dev_stats[smp_processor_id()];
+--- a/drivers/scsi/libfc/fc_exch.c
++++ b/drivers/scsi/libfc/fc_exch.c
+@@ -1096,7 +1096,7 @@ static void fc_exch_recv_abts(struct fc_
+ ap->ba_high_seq_cnt = fh->fh_seq_cnt;
+ ap->ba_low_seq_cnt = htons(sp->cnt);
+ }
+- sp = fc_seq_start_next(sp);
++ sp = fc_seq_start_next_locked(sp);
+ spin_unlock_bh(&ep->ex_lock);
+ fc_seq_send_last(sp, fp, FC_RCTL_BA_ACC, FC_TYPE_BLS);
+ fc_frame_free(rx_fp);
+--- a/drivers/scsi/libfc/fc_fcp.c
++++ b/drivers/scsi/libfc/fc_fcp.c
+@@ -20,13 +20,13 @@
+ */
+
+ #include <linux/module.h>
++#include <linux/delay.h>
+ #include <linux/kernel.h>
+ #include <linux/types.h>
+ #include <linux/spinlock.h>
+ #include <linux/scatterlist.h>
+ #include <linux/err.h>
+ #include <linux/crc32.h>
+-#include <linux/delay.h>
+
+ #include <scsi/scsi_tcq.h>
+ #include <scsi/scsi.h>
+@@ -1622,7 +1622,7 @@ out:
+ static inline int fc_fcp_lport_queue_ready(struct fc_lport *lp)
+ {
+ /* lock ? */
+- return (lp->state == LPORT_ST_READY) && (lp->link_status & FC_LINK_UP);
++ return (lp->state == LPORT_ST_READY) && lp->link_up && !lp->qfull;
+ }
+
+ /**
+@@ -1891,7 +1891,7 @@ int fc_eh_abort(struct scsi_cmnd *sc_cmd
+ lp = shost_priv(sc_cmd->device->host);
+ if (lp->state != LPORT_ST_READY)
+ return rc;
+- else if (!(lp->link_status & FC_LINK_UP))
++ else if (!lp->link_up)
+ return rc;
+
+ spin_lock_irqsave(lp->host->host_lock, flags);
+--- a/drivers/scsi/libfc/fc_lport.c
++++ b/drivers/scsi/libfc/fc_lport.c
+@@ -250,7 +250,7 @@ void fc_get_host_port_state(struct Scsi_
+ {
+ struct fc_lport *lp = shost_priv(shost);
+
+- if ((lp->link_status & FC_LINK_UP) == FC_LINK_UP)
++ if (lp->link_up)
+ fc_host_port_state(shost) = FC_PORTSTATE_ONLINE;
+ else
+ fc_host_port_state(shost) = FC_PORTSTATE_OFFLINE;
+@@ -484,7 +484,7 @@ static void fc_lport_recv_rnid_req(struc
+ * @sp: current sequence in the ADISC exchange
+ * @fp: ADISC 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_adisc_req(struct fc_seq *sp, struct fc_frame *in_fp,
+@@ -577,8 +577,8 @@ void fc_linkup(struct fc_lport *lport)
+ fc_host_port_id(lport->host));
+
+ mutex_lock(&lport->lp_mutex);
+- if ((lport->link_status & FC_LINK_UP) != FC_LINK_UP) {
+- lport->link_status |= FC_LINK_UP;
++ if (!lport->link_up) {
++ lport->link_up = 1;
+
+ if (lport->state == LPORT_ST_RESET)
+ fc_lport_enter_flogi(lport);
+@@ -597,8 +597,8 @@ void fc_linkdown(struct fc_lport *lport)
+ FC_DEBUG_LPORT("Link is down for port (%6x)\n",
+ fc_host_port_id(lport->host));
+
+- if ((lport->link_status & FC_LINK_UP) == FC_LINK_UP) {
+- lport->link_status &= ~(FC_LINK_UP);
++ if (lport->link_up) {
++ lport->link_up = 0;
+ fc_lport_enter_reset(lport);
+ lport->tt.fcp_cleanup(lport);
+ }
+@@ -607,30 +607,6 @@ void fc_linkdown(struct fc_lport *lport)
+ EXPORT_SYMBOL(fc_linkdown);
+
+ /**
+- * fc_pause - Pause the flow of frames
+- * @lport: The lport to be paused
+- */
+-void fc_pause(struct fc_lport *lport)
+-{
+- mutex_lock(&lport->lp_mutex);
+- lport->link_status |= FC_PAUSE;
+- mutex_unlock(&lport->lp_mutex);
+-}
+-EXPORT_SYMBOL(fc_pause);
+-
+-/**
+- * fc_unpause - Unpause the flow of frames
+- * @lport: The lport to be unpaused
+- */
+-void fc_unpause(struct fc_lport *lport)
+-{
+- mutex_lock(&lport->lp_mutex);
+- lport->link_status &= ~(FC_PAUSE);
+- mutex_unlock(&lport->lp_mutex);
+-}
+-EXPORT_SYMBOL(fc_unpause);
+-
+-/**
+ * fc_fabric_logoff - Logout of the fabric
+ * @lport: fc_lport pointer to logoff the fabric
+ *
+@@ -977,7 +953,7 @@ static void fc_lport_enter_reset(struct
+ fc_host_fabric_name(lport->host) = 0;
+ fc_host_port_id(lport->host) = 0;
+
+- if ((lport->link_status & FC_LINK_UP) == FC_LINK_UP)
++ if (lport->link_up)
+ fc_lport_enter_flogi(lport);
+ }
+
+--- a/drivers/scsi/libfc/fc_rport.c
++++ b/drivers/scsi/libfc/fc_rport.c
+@@ -425,7 +425,7 @@ static void fc_rport_error(struct fc_rpo
+ PTR_ERR(fp), fc_rport_state(rport), rdata->retries);
+
+ if (!fp || PTR_ERR(fp) == -FC_EX_TIMEOUT) {
+- /*
++ /*
+ * Memory allocation failure, or the exchange timed out.
+ * Retry after delay
+ */
+--- a/include/scsi/libfc.h
++++ b/include/scsi/libfc.h
+@@ -68,9 +68,6 @@
+ /*
+ * FC HBA status
+ */
+-#define FC_PAUSE (1 << 1)
+-#define FC_LINK_UP (1 << 0)
+-
+ enum fc_lport_state {
+ LPORT_ST_NONE = 0,
+ LPORT_ST_FLOGI,
+@@ -603,7 +600,8 @@ struct fc_lport {
+
+ /* Operational Information */
+ struct libfc_function_template tt;
+- u16 link_status;
++ u8 link_up;
++ u8 qfull;
+ enum fc_lport_state state;
+ unsigned long boot_time;
+
+@@ -704,12 +702,6 @@ void fc_linkup(struct fc_lport *);
+ void fc_linkdown(struct fc_lport *);
+
+ /*
+- * Pause and unpause traffic.
+- */
+-void fc_pause(struct fc_lport *);
+-void fc_unpause(struct fc_lport *);
+-
+-/*
+ * Configure the local port.
+ */
+ int fc_lport_config(struct fc_lport *);