diff options
Diffstat (limited to 'drivers/scsi/device_handler/scsi_dh_alua.c')
| -rw-r--r-- | drivers/scsi/device_handler/scsi_dh_alua.c | 66 | 
1 files changed, 44 insertions, 22 deletions
diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c index 610a51538f03..29a2865b8e2e 100644 --- a/drivers/scsi/device_handler/scsi_dh_alua.c +++ b/drivers/scsi/device_handler/scsi_dh_alua.c @@ -354,6 +354,8 @@ static int alua_check_vpd(struct scsi_device *sdev, struct alua_dh_data *h,  			    "%s: port group %x rel port %x\n",  			    ALUA_DH_NAME, group_id, rel_port); +	kref_get(&pg->kref); +  	/* Check for existing port group references */  	spin_lock(&h->pg_lock);  	old_pg = rcu_dereference_protected(h->pg, lockdep_is_held(&h->pg_lock)); @@ -373,11 +375,11 @@ static int alua_check_vpd(struct scsi_device *sdev, struct alua_dh_data *h,  		list_add_rcu(&h->node, &pg->dh_list);  	spin_unlock_irqrestore(&pg->lock, flags); -	alua_rtpg_queue(rcu_dereference_protected(h->pg, -						  lockdep_is_held(&h->pg_lock)), -			sdev, NULL, true);  	spin_unlock(&h->pg_lock); +	alua_rtpg_queue(pg, sdev, NULL, true); +	kref_put(&pg->kref, release_port_group); +  	if (old_pg)  		kref_put(&old_pg->kref, release_port_group); @@ -811,14 +813,19 @@ static unsigned alua_stpg(struct scsi_device *sdev, struct alua_port_group *pg)  	return SCSI_DH_RETRY;  } -static bool alua_rtpg_select_sdev(struct alua_port_group *pg) +/* + * The caller must call scsi_device_put() on the returned pointer if it is not + * NULL. + */ +static struct scsi_device * __must_check +alua_rtpg_select_sdev(struct alua_port_group *pg)  {  	struct alua_dh_data *h; -	struct scsi_device *sdev = NULL; +	struct scsi_device *sdev = NULL, *prev_sdev;  	lockdep_assert_held(&pg->lock);  	if (WARN_ON(!pg->rtpg_sdev)) -		return false; +		return NULL;  	/*  	 * RCU protection isn't necessary for dh_list here @@ -845,22 +852,22 @@ static bool alua_rtpg_select_sdev(struct alua_port_group *pg)  		pr_warn("%s: no device found for rtpg\n",  			(pg->device_id_len ?  			 (char *)pg->device_id_str : "(nameless PG)")); -		return false; +		return NULL;  	}  	sdev_printk(KERN_INFO, sdev, "rtpg retry on different device\n"); -	scsi_device_put(pg->rtpg_sdev); +	prev_sdev = pg->rtpg_sdev;  	pg->rtpg_sdev = sdev; -	return true; +	return prev_sdev;  }  static void alua_rtpg_work(struct work_struct *work)  {  	struct alua_port_group *pg =  		container_of(work, struct alua_port_group, rtpg_work.work); -	struct scsi_device *sdev; +	struct scsi_device *sdev, *prev_sdev = NULL;  	LIST_HEAD(qdata_list);  	int err = SCSI_DH_OK;  	struct alua_queue_data *qdata, *tmp; @@ -901,7 +908,7 @@ static void alua_rtpg_work(struct work_struct *work)  		/* If RTPG failed on the current device, try using another */  		if (err == SCSI_DH_RES_TEMP_UNAVAIL && -		    alua_rtpg_select_sdev(pg)) +		    (prev_sdev = alua_rtpg_select_sdev(pg)))  			err = SCSI_DH_IMM_RETRY;  		if (err == SCSI_DH_RETRY || err == SCSI_DH_IMM_RETRY || @@ -913,9 +920,7 @@ static void alua_rtpg_work(struct work_struct *work)  				pg->interval = ALUA_RTPG_RETRY_DELAY;  			pg->flags |= ALUA_PG_RUN_RTPG;  			spin_unlock_irqrestore(&pg->lock, flags); -			queue_delayed_work(kaluad_wq, &pg->rtpg_work, -					   pg->interval * HZ); -			return; +			goto queue_rtpg;  		}  		if (err != SCSI_DH_OK)  			pg->flags &= ~ALUA_PG_RUN_STPG; @@ -930,9 +935,7 @@ static void alua_rtpg_work(struct work_struct *work)  			pg->interval = 0;  			pg->flags &= ~ALUA_PG_RUNNING;  			spin_unlock_irqrestore(&pg->lock, flags); -			queue_delayed_work(kaluad_wq, &pg->rtpg_work, -					   pg->interval * HZ); -			return; +			goto queue_rtpg;  		}  	} @@ -946,6 +949,9 @@ static void alua_rtpg_work(struct work_struct *work)  	pg->rtpg_sdev = NULL;  	spin_unlock_irqrestore(&pg->lock, flags); +	if (prev_sdev) +		scsi_device_put(prev_sdev); +  	list_for_each_entry_safe(qdata, tmp, &qdata_list, entry) {  		list_del(&qdata->entry);  		if (qdata->callback_fn) @@ -957,6 +963,12 @@ static void alua_rtpg_work(struct work_struct *work)  	spin_unlock_irqrestore(&pg->lock, flags);  	scsi_device_put(sdev);  	kref_put(&pg->kref, release_port_group); +	return; + +queue_rtpg: +	if (prev_sdev) +		scsi_device_put(prev_sdev); +	queue_delayed_work(kaluad_wq, &pg->rtpg_work, pg->interval * HZ);  }  /** @@ -969,6 +981,9 @@ static void alua_rtpg_work(struct work_struct *work)   *   * Returns true if and only if alua_rtpg_work() will be called asynchronously.   * That function is responsible for calling @qdata->fn(). + * + * Context: may be called from atomic context (alua_check()) only if the caller + *	holds an sdev reference.   */  static bool alua_rtpg_queue(struct alua_port_group *pg,  			    struct scsi_device *sdev, @@ -976,6 +991,7 @@ static bool alua_rtpg_queue(struct alua_port_group *pg,  {  	int start_queue = 0;  	unsigned long flags; +  	if (WARN_ON_ONCE(!pg) || scsi_device_get(sdev))  		return false; @@ -986,11 +1002,17 @@ static bool alua_rtpg_queue(struct alua_port_group *pg,  		force = true;  	}  	if (pg->rtpg_sdev == NULL) { -		pg->interval = 0; -		pg->flags |= ALUA_PG_RUN_RTPG; -		kref_get(&pg->kref); -		pg->rtpg_sdev = sdev; -		start_queue = 1; +		struct alua_dh_data *h = sdev->handler_data; + +		rcu_read_lock(); +		if (h && rcu_dereference(h->pg) == pg) { +			pg->interval = 0; +			pg->flags |= ALUA_PG_RUN_RTPG; +			kref_get(&pg->kref); +			pg->rtpg_sdev = sdev; +			start_queue = 1; +		} +		rcu_read_unlock();  	} else if (!(pg->flags & ALUA_PG_RUN_RTPG) && force) {  		pg->flags |= ALUA_PG_RUN_RTPG;  		/* Do not queue if the worker is already running */  | 
