Commit 90cec07f authored by Justin Tee's avatar Justin Tee Committed by Martin K. Petersen

scsi: lpfc: Revise ndlp kref handling for dev_loss_tmo_callbk and lpfc_drop_node

The ndlp kref count implementation in lpfc_dev_loss_tmo_callbk() removes
the initial node reference when a vport is unloading.  When lpfc_cleanup()
sends a DEVICE_RM event and is in NPR state, the driver calls
lpfc_drop_node().  Subsequently, lpfc_drop_node() also removes an ndlp kref
thinking it is the initial reference.  This unintentionally introduces an
extra kref decrement on the ndlp object.

Fix by using the NLP_DROPPED node flag in lpfc_dev_loss_tmo_callbk() and
lpfc_drop_node() to coordinate the removal of the initial node reference.

In lpfc_dev_loss_tmo_callbk(), remove the SCSI transport reference provided
the node is registered in the dev_loss context because the driver cannot
call the SCSI transport in dev_loss context or afterwards.  And, have
lpfc_drop_node() not remove a reference if another thread is acting or has
already acted on it.
Signed-off-by: default avatarJustin Tee <justin.tee@broadcom.com>
Link: https://lore.kernel.org/r/20230712180522.112722-6-justintee8345@gmail.comSigned-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 377d7aba
...@@ -169,29 +169,44 @@ lpfc_dev_loss_tmo_callbk(struct fc_rport *rport) ...@@ -169,29 +169,44 @@ lpfc_dev_loss_tmo_callbk(struct fc_rport *rport)
lpfc_printf_vlog(ndlp->vport, KERN_INFO, LOG_NODE, lpfc_printf_vlog(ndlp->vport, KERN_INFO, LOG_NODE,
"3181 dev_loss_callbk x%06x, rport x%px flg x%x " "3181 dev_loss_callbk x%06x, rport x%px flg x%x "
"load_flag x%x refcnt %d state %d xpt x%x\n", "load_flag x%x refcnt %u state %d xpt x%x\n",
ndlp->nlp_DID, ndlp->rport, ndlp->nlp_flag, ndlp->nlp_DID, ndlp->rport, ndlp->nlp_flag,
vport->load_flag, kref_read(&ndlp->kref), vport->load_flag, kref_read(&ndlp->kref),
ndlp->nlp_state, ndlp->fc4_xpt_flags); ndlp->nlp_state, ndlp->fc4_xpt_flags);
/* Don't schedule a worker thread event if the vport is going down. /* Don't schedule a worker thread event if the vport is going down. */
* The teardown process cleans up the node via lpfc_drop_node.
*/
if (vport->load_flag & FC_UNLOADING) { if (vport->load_flag & FC_UNLOADING) {
((struct lpfc_rport_data *)rport->dd_data)->pnode = NULL; spin_lock_irqsave(&ndlp->lock, iflags);
ndlp->rport = NULL; ndlp->rport = NULL;
/* The scsi_transport is done with the rport so lpfc cannot
* call to unregister. Remove the scsi transport reference
* and clean up the SCSI transport node details.
*/
if (ndlp->fc4_xpt_flags & (NLP_XPT_REGD | SCSI_XPT_REGD)) {
ndlp->fc4_xpt_flags &= ~SCSI_XPT_REGD; ndlp->fc4_xpt_flags &= ~SCSI_XPT_REGD;
/* clear the NLP_XPT_REGD if the node is not registered
* with nvme-fc /* NVME transport-registered rports need the
* NLP_XPT_REGD flag to complete an unregister.
*/ */
if (ndlp->fc4_xpt_flags == NLP_XPT_REGD) if (!(ndlp->fc4_xpt_flags & NVME_XPT_REGD))
ndlp->fc4_xpt_flags &= ~NLP_XPT_REGD; ndlp->fc4_xpt_flags &= ~NLP_XPT_REGD;
spin_unlock_irqrestore(&ndlp->lock, iflags);
lpfc_nlp_put(ndlp);
spin_lock_irqsave(&ndlp->lock, iflags);
}
/* Remove the node reference from remote_port_add now. /* Only 1 thread can drop the initial node reference. If
* The driver will not call remote_port_delete. * another thread has set NLP_DROPPED, this thread is done.
*/ */
if (!(ndlp->nlp_flag & NLP_DROPPED)) {
ndlp->nlp_flag |= NLP_DROPPED;
spin_unlock_irqrestore(&ndlp->lock, iflags);
lpfc_nlp_put(ndlp); lpfc_nlp_put(ndlp);
spin_lock_irqsave(&ndlp->lock, iflags);
}
spin_unlock_irqrestore(&ndlp->lock, iflags);
return; return;
} }
...@@ -4686,7 +4701,8 @@ lpfc_nlp_unreg_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) ...@@ -4686,7 +4701,8 @@ lpfc_nlp_unreg_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
spin_lock_irqsave(&ndlp->lock, iflags); spin_lock_irqsave(&ndlp->lock, iflags);
if (!(ndlp->fc4_xpt_flags & NLP_XPT_REGD)) { if (!(ndlp->fc4_xpt_flags & NLP_XPT_REGD)) {
spin_unlock_irqrestore(&ndlp->lock, iflags); spin_unlock_irqrestore(&ndlp->lock, iflags);
lpfc_printf_vlog(vport, KERN_INFO, LOG_SLI, lpfc_printf_vlog(vport, KERN_INFO,
LOG_ELS | LOG_NODE | LOG_DISCOVERY,
"0999 %s Not regd: ndlp x%px rport x%px DID " "0999 %s Not regd: ndlp x%px rport x%px DID "
"x%x FLG x%x XPT x%x\n", "x%x FLG x%x XPT x%x\n",
__func__, ndlp, ndlp->rport, ndlp->nlp_DID, __func__, ndlp, ndlp->rport, ndlp->nlp_DID,
...@@ -4702,9 +4718,10 @@ lpfc_nlp_unreg_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) ...@@ -4702,9 +4718,10 @@ lpfc_nlp_unreg_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
vport->phba->nport_event_cnt++; vport->phba->nport_event_cnt++;
lpfc_unregister_remote_port(ndlp); lpfc_unregister_remote_port(ndlp);
} else if (!ndlp->rport) { } else if (!ndlp->rport) {
lpfc_printf_vlog(vport, KERN_INFO, LOG_SLI, lpfc_printf_vlog(vport, KERN_INFO,
LOG_ELS | LOG_NODE | LOG_DISCOVERY,
"1999 %s NDLP in devloss x%px DID x%x FLG x%x" "1999 %s NDLP in devloss x%px DID x%x FLG x%x"
" XPT x%x refcnt %d\n", " XPT x%x refcnt %u\n",
__func__, ndlp, ndlp->nlp_DID, ndlp->nlp_flag, __func__, ndlp, ndlp->nlp_DID, ndlp->nlp_flag,
ndlp->fc4_xpt_flags, ndlp->fc4_xpt_flags,
kref_read(&ndlp->kref)); kref_read(&ndlp->kref));
...@@ -4954,22 +4971,29 @@ lpfc_drop_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) ...@@ -4954,22 +4971,29 @@ lpfc_drop_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
{ {
/* /*
* Use of lpfc_drop_node and UNUSED list: lpfc_drop_node should * Use of lpfc_drop_node and UNUSED list: lpfc_drop_node should
* be used if we wish to issue the "last" lpfc_nlp_put() to remove * be used when lpfc wants to remove the "last" lpfc_nlp_put() to
* the ndlp from the vport. The ndlp marked as UNUSED on the list * release the ndlp from the vport when conditions are correct.
* until ALL other outstanding threads have completed. We check
* that the ndlp not already in the UNUSED state before we proceed.
*/ */
if (ndlp->nlp_state == NLP_STE_UNUSED_NODE) if (ndlp->nlp_state == NLP_STE_UNUSED_NODE)
return; return;
lpfc_nlp_set_state(vport, ndlp, NLP_STE_UNUSED_NODE); lpfc_nlp_set_state(vport, ndlp, NLP_STE_UNUSED_NODE);
ndlp->nlp_flag |= NLP_DROPPED;
if (vport->phba->sli_rev == LPFC_SLI_REV4) { if (vport->phba->sli_rev == LPFC_SLI_REV4) {
lpfc_cleanup_vports_rrqs(vport, ndlp); lpfc_cleanup_vports_rrqs(vport, ndlp);
lpfc_unreg_rpi(vport, ndlp); lpfc_unreg_rpi(vport, ndlp);
} }
/* NLP_DROPPED means another thread already removed the initial
* reference from lpfc_nlp_init. If set, don't drop it again and
* introduce an imbalance.
*/
spin_lock_irq(&ndlp->lock);
if (!(ndlp->nlp_flag & NLP_DROPPED)) {
ndlp->nlp_flag |= NLP_DROPPED;
spin_unlock_irq(&ndlp->lock);
lpfc_nlp_put(ndlp); lpfc_nlp_put(ndlp);
return; return;
}
spin_unlock_irq(&ndlp->lock);
} }
/* /*
......
...@@ -2503,8 +2503,9 @@ lpfc_nvme_register_port(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) ...@@ -2503,8 +2503,9 @@ lpfc_nvme_register_port(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp)
lpfc_printf_vlog(vport, KERN_ERR, lpfc_printf_vlog(vport, KERN_ERR,
LOG_TRACE_EVENT, LOG_TRACE_EVENT,
"6031 RemotePort Registration failed " "6031 RemotePort Registration failed "
"err: %d, DID x%06x\n", "err: %d, DID x%06x ref %u\n",
ret, ndlp->nlp_DID); ret, ndlp->nlp_DID, kref_read(&ndlp->kref));
lpfc_nlp_put(ndlp);
} }
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment