@@ -161,6 +161,7 @@ lpfc_dev_loss_tmo_callbk(struct fc_rport *rport)
struct lpfc_hba *phba;
struct lpfc_work_evt *evtp;
unsigned long iflags;
+ bool nvme_reg = false;
ndlp = ((struct lpfc_rport_data *)rport->dd_data)->pnode;
if (!ndlp)
@@ -183,38 +184,49 @@ lpfc_dev_loss_tmo_callbk(struct fc_rport *rport)
/* Don't schedule a worker thread event if the vport is going down. */
if (test_bit(FC_UNLOADING, &vport->load_flag) ||
!test_bit(HBA_SETUP, &phba->hba_flag)) {
+
spin_lock_irqsave(&ndlp->lock, iflags);
ndlp->rport = NULL;
+ if (ndlp->fc4_xpt_flags & NVME_XPT_REGD)
+ nvme_reg = true;
+
/* 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.
+ * call to unregister.
*/
- if (ndlp->fc4_xpt_flags & (NLP_XPT_REGD | SCSI_XPT_REGD)) {
+ if (ndlp->fc4_xpt_flags & SCSI_XPT_REGD) {
ndlp->fc4_xpt_flags &= ~SCSI_XPT_REGD;
- /* NVME transport-registered rports need the
- * NLP_XPT_REGD flag to complete an unregister.
+ /* If NLP_XPT_REGD was cleared in lpfc_nlp_unreg_node,
+ * unregister calls were made to the scsi and nvme
+ * transports and refcnt was already decremented. Clear
+ * the NLP_XPT_REGD flag only if the NVME Rport is
+ * confirmed unregistered.
*/
- if (!(ndlp->fc4_xpt_flags & NVME_XPT_REGD))
+ if (!nvme_reg && ndlp->fc4_xpt_flags & NLP_XPT_REGD) {
ndlp->fc4_xpt_flags &= ~NLP_XPT_REGD;
+ spin_unlock_irqrestore(&ndlp->lock, iflags);
+ lpfc_nlp_put(ndlp); /* may free ndlp */
+ } else {
+ spin_unlock_irqrestore(&ndlp->lock, iflags);
+ }
+ } else {
spin_unlock_irqrestore(&ndlp->lock, iflags);
- lpfc_nlp_put(ndlp);
- spin_lock_irqsave(&ndlp->lock, iflags);
}
+ spin_lock_irqsave(&ndlp->lock, iflags);
+
/* Only 1 thread can drop the initial node reference. If
* another thread has set NLP_DROPPED, this thread is done.
*/
- if (!(ndlp->fc4_xpt_flags & NVME_XPT_REGD) &&
- !(ndlp->nlp_flag & NLP_DROPPED)) {
- ndlp->nlp_flag |= NLP_DROPPED;
+ if (nvme_reg || (ndlp->nlp_flag & NLP_DROPPED)) {
spin_unlock_irqrestore(&ndlp->lock, iflags);
- lpfc_nlp_put(ndlp);
return;
}
+ ndlp->nlp_flag |= NLP_DROPPED;
spin_unlock_irqrestore(&ndlp->lock, iflags);
+ lpfc_nlp_put(ndlp);
return;
}
Current dev_loss_tmo handling checks whether there has been a previous call to unregister with SCSI transport. If so, the ndlp kref count is decremented a second time in dev_loss_tmo as the final kref release. However, this can sometimes result in a reference count underflow if there is also a race to unregister with NVME transport as well. Add a check for NVME transport registration before decrementing the final kref. If NVME transport is still registered, then the NVME transport unregistration is designated as the final kref decrement. Signed-off-by: Justin Tee <justin.tee@broadcom.com> --- drivers/scsi/lpfc/lpfc_hbadisc.c | 36 +++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-)