]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
scsi: lpfc: Prevent NDLP reference count underflow in dev_loss_tmo callback
authorJustin Tee <justin.tee@broadcom.com>
Thu, 31 Oct 2024 22:32:15 +0000 (15:32 -0700)
committerMartin K. Petersen <martin.petersen@oracle.com>
Sun, 3 Nov 2024 01:45:24 +0000 (21:45 -0400)
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>
Link: https://lore.kernel.org/r/20241031223219.152342-8-justintee8345@gmail.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/lpfc/lpfc_hbadisc.c

index a434faec3c92bd9d1df5bd5eff0f1a378982e4bc..3c8cb6ecc0ac08adcc19b521d6f8b3a93b138282 100644 (file)
@@ -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;
        }