qla2xxx: added sess generations to detect RSCN update races

RSCN processing in qla2xxx driver can run in parallel with ELS/IO
processing. As such the decision to remove disappeared fc port's
session could be stale, because a new login sequence has occurred
since and created a brand new session.

Previous mechanism of dealing with this by delaying deletion request
was prone to erroneous deletions if the event that was supposed to
cancel the deletion never arrived or has been delayed in processing.

New mechanism relies on a time-like generation counter to serialize
RSCN updates relative to ELS/IO updates.

Cc: <stable@vger.kernel.org> # v3.18+
Signed-off-by: Alexei Potashnik <alexei@purestorage.com>
Signed-off-by: Himanshu Madhani <himanshu.madhani@qlogic.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c
index 0b08beb..0b5bd9c 100644
--- a/drivers/scsi/qla2xxx/qla_target.c
+++ b/drivers/scsi/qla2xxx/qla_target.c
@@ -127,6 +127,16 @@
 static DEFINE_MUTEX(qla_tgt_mutex);
 static LIST_HEAD(qla_tgt_glist);
 
+/* This API intentionally takes dest as a parameter, rather than returning
+ * int value to avoid caller forgetting to issue wmb() after the store */
+void qlt_do_generation_tick(struct scsi_qla_host *vha, int *dest)
+{
+	scsi_qla_host_t *base_vha = pci_get_drvdata(vha->hw->pdev);
+	*dest = atomic_inc_return(&base_vha->generation_tick);
+	/* memory barrier */
+	wmb();
+}
+
 /* ha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) */
 static struct qla_tgt_sess *qlt_find_sess_by_port_name(
 	struct qla_tgt *tgt,
@@ -576,10 +586,12 @@
 	sess->expires = jiffies + dev_loss_tmo * HZ;
 
 	ql_dbg(ql_dbg_tgt, sess->vha, 0xe048,
-	    "qla_target(%d): session for port %8phC (loop ID %d) scheduled for "
-	    "deletion in %u secs (expires: %lu) immed: %d, logout: %d\n",
-	    sess->vha->vp_idx, sess->port_name, sess->loop_id, dev_loss_tmo,
-	    sess->expires, immediate, sess->logout_on_delete);
+	    "qla_target(%d): session for port %8phC (loop ID %d s_id %02x:%02x:%02x)"
+	    " scheduled for deletion in %u secs (expires: %lu) immed: %d, logout: %d, gen: %#x\n",
+	    sess->vha->vp_idx, sess->port_name, sess->loop_id,
+	    sess->s_id.b.domain, sess->s_id.b.area, sess->s_id.b.al_pa,
+	    dev_loss_tmo, sess->expires, immediate, sess->logout_on_delete,
+	    sess->generation);
 
 	if (immediate)
 		mod_delayed_work(system_wq, &tgt->sess_del_work, 0);
@@ -734,6 +746,9 @@
 
 			if (sess->local && !local)
 				sess->local = 0;
+
+			qlt_do_generation_tick(vha, &sess->generation);
+
 			spin_unlock_irqrestore(&ha->hardware_lock, flags);
 
 			return sess;
@@ -795,6 +810,7 @@
 	spin_lock_irqsave(&ha->hardware_lock, flags);
 	list_add_tail(&sess->sess_list_entry, &vha->vha_tgt.qla_tgt->sess_list);
 	vha->vha_tgt.qla_tgt->sess_count++;
+	qlt_do_generation_tick(vha, &sess->generation);
 	spin_unlock_irqrestore(&ha->hardware_lock, flags);
 
 	ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04b,
@@ -808,7 +824,7 @@
 }
 
 /*
- * Called from drivers/scsi/qla2xxx/qla_init.c:qla2x00_reg_remote_port()
+ * Called from qla2x00_reg_remote_port()
  */
 void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport)
 {
@@ -874,7 +890,12 @@
 	spin_unlock_irqrestore(&ha->hardware_lock, flags);
 }
 
-void qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport)
+/*
+ * max_gen - specifies maximum session generation
+ * at which this deletion requestion is still valid
+ */
+void
+qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport, int max_gen)
 {
 	struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
 	struct qla_tgt_sess *sess;
@@ -893,6 +914,15 @@
 		return;
 	}
 
+	if (max_gen - sess->generation < 0) {
+		ql_dbg(ql_dbg_tgt_mgt, vha, 0xf092,
+		    "Ignoring stale deletion request for se_sess %p / sess %p"
+		    " for port %8phC, req_gen %d, sess_gen %d\n",
+		    sess->se_sess, sess, sess->port_name, max_gen,
+		    sess->generation);
+		return;
+	}
+
 	ql_dbg(ql_dbg_tgt_mgt, vha, 0xf008, "qla_tgt_fc_port_deleted %p", sess);
 
 	sess->local = 1;
@@ -3970,7 +4000,7 @@
 {
 	if (fcport->tgt_session) {
 		if (rc != MBS_COMMAND_COMPLETE) {
-			ql_dbg(ql_dbg_tgt_mgt, fcport->vha, 0xf088,
+			ql_dbg(ql_dbg_tgt_mgt, fcport->vha, 0xf093,
 				"%s: se_sess %p / sess %p from"
 				" port %8phC loop_id %#04x s_id %02x:%02x:%02x"
 				" LOGO failed: %#x\n",
@@ -4099,6 +4129,7 @@
 	struct imm_ntfy_from_isp *iocb)
 {
 	struct qla_tgt *tgt = vha->vha_tgt.qla_tgt;
+	struct qla_hw_data *ha = vha->hw;
 	struct qla_tgt_sess *sess = NULL;
 	uint64_t wwn;
 	port_id_t port_id;
@@ -4144,7 +4175,7 @@
 			 * without acking, new one will get acked when session
 			 * deletion completes.
 			 */
-			ql_log(ql_log_warn, sess->vha, 0xf089,
+			ql_log(ql_log_warn, sess->vha, 0xf094,
 			    "sess %p received double plogi.\n", sess);
 
 			qlt_swap_imm_ntfy_iocb(iocb, &sess->tm_iocb);
@@ -4201,7 +4232,7 @@
 				 * PLOGI could finish. Will force him to re-try,
 				 * while last one finishes.
 				 */
-				ql_log(ql_log_warn, sess->vha, 0xf090,
+				ql_log(ql_log_warn, sess->vha, 0xf095,
 				    "sess %p PRLI received, before plogi ack.\n",
 				    sess);
 				qlt_send_term_imm_notif(vha, iocb, 1);
@@ -4213,7 +4244,7 @@
 			 * This shouldn't happen under normal circumstances,
 			 * since we have deleted the old session during PLOGI
 			 */
-			ql_dbg(ql_dbg_tgt_mgt, vha, 0xf091,
+			ql_dbg(ql_dbg_tgt_mgt, vha, 0xf096,
 			    "PRLI (loop_id %#04x) for existing sess %p (loop_id %#04x)\n",
 			    sess->loop_id, sess, iocb->u.isp24.nport_handle);
 
@@ -4224,7 +4255,14 @@
 			if (wd3_lo & BIT_7)
 				sess->conf_compl_supported = 1;
 
-			res = 1;
+		}
+		res = 1; /* send notify ack */
+
+		/* Make session global (not used in fabric mode) */
+		if (ha->current_topology != ISP_CFG_F) {
+			set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags);
+			set_bit(LOCAL_LOOP_UPDATE, &vha->dpc_flags);
+			qla2xxx_wake_dpc(vha);
 		} else {
 			/* todo: else - create sess here. */
 			res = 1; /* send notify ack */