diff options
| author | Justin Tee <justin.tee@broadcom.com> | 2024-10-31 15:32:15 -0700 | 
|---|---|---|
| committer | Martin K. Petersen <martin.petersen@oracle.com> | 2024-11-02 21:45:24 -0400 | 
| commit | 4281f44ea8bfedd25938a0031bebba1473ece9ad (patch) | |
| tree | e66f7e4458cc0ad3c9ff31b083093e6642638394 /drivers/scsi | |
| parent | eb038363d8e9ae0d9fa31a0600438d19b283dd41 (diff) | |
scsi: lpfc: Prevent NDLP reference count underflow in dev_loss_tmo callback
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>
Diffstat (limited to 'drivers/scsi')
| -rw-r--r-- | drivers/scsi/lpfc/lpfc_hbadisc.c | 36 | 
1 files changed, 24 insertions, 12 deletions
| diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index a434faec3c92..3c8cb6ecc0ac 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -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;  	} | 
