Commit 0db6ca8a authored by Bart Van Assche's avatar Bart Van Assche Committed by Martin K. Petersen
Browse files

scsi: Protect SCSI device state changes with a mutex



Serializing SCSI device state changes avoids that two state changes can
occur concurrently, e.g. the state changes in scsi_target_block() and
__scsi_remove_device(). This serialization is essential to make patch
"Make __scsi_remove_device go straight from BLOCKED to DEL" work
reliably.

Enable this mechanism for all scsi_target_*block() callers but not for
the scsi_internal_device_unblock() calls from the mpt3sas driver because
that driver can call scsi_internal_device_unblock() from atomic context.

Signed-off-by: default avatarBart Van Assche <bart.vanassche@sandisk.com>
Cc: Christoph Hellwig <hch@lst.de>
Cc: Hannes Reinecke <hare@suse.com>
Cc: Johannes Thumshirn <jthumshirn@suse.de>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 43f7571b
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -1628,11 +1628,17 @@ static void scsi_eh_offline_sdevs(struct list_head *work_q,
				  struct list_head *done_q)
{
	struct scsi_cmnd *scmd, *next;
	struct scsi_device *sdev;

	list_for_each_entry_safe(scmd, next, work_q, eh_entry) {
		sdev_printk(KERN_INFO, scmd->device, "Device offlined - "
			    "not ready after error recovery\n");
		scsi_device_set_state(scmd->device, SDEV_OFFLINE);
		sdev = scmd->device;

		mutex_lock(&sdev->state_mutex);
		scsi_device_set_state(sdev, SDEV_OFFLINE);
		mutex_unlock(&sdev->state_mutex);

		scsi_eh_finish_cmd(scmd, done_q);
	}
	return;
+21 −6
Original line number Diff line number Diff line
@@ -2882,7 +2882,12 @@ static void scsi_wait_for_queuecommand(struct scsi_device *sdev)
int
scsi_device_quiesce(struct scsi_device *sdev)
{
	int err = scsi_device_set_state(sdev, SDEV_QUIESCE);
	int err;

	mutex_lock(&sdev->state_mutex);
	err = scsi_device_set_state(sdev, SDEV_QUIESCE);
	mutex_unlock(&sdev->state_mutex);

	if (err)
		return err;

@@ -2910,10 +2915,11 @@ void scsi_device_resume(struct scsi_device *sdev)
	 * so assume the state is being managed elsewhere (for example
	 * device deleted during suspend)
	 */
	if (sdev->sdev_state != SDEV_QUIESCE ||
	    scsi_device_set_state(sdev, SDEV_RUNNING))
		return;
	mutex_lock(&sdev->state_mutex);
	if (sdev->sdev_state == SDEV_QUIESCE &&
	    scsi_device_set_state(sdev, SDEV_RUNNING) == 0)
		scsi_run_queue(sdev->request_queue);
	mutex_unlock(&sdev->state_mutex);
}
EXPORT_SYMBOL(scsi_device_resume);

@@ -3012,6 +3018,7 @@ static int scsi_internal_device_block(struct scsi_device *sdev)
	struct request_queue *q = sdev->request_queue;
	int err;

	mutex_lock(&sdev->state_mutex);
	err = scsi_internal_device_block_nowait(sdev);
	if (err == 0) {
		if (q->mq_ops)
@@ -3019,6 +3026,8 @@ static int scsi_internal_device_block(struct scsi_device *sdev)
		else
			scsi_wait_for_queuecommand(sdev);
	}
	mutex_unlock(&sdev->state_mutex);

	return err;
}
 
@@ -3089,7 +3098,13 @@ EXPORT_SYMBOL_GPL(scsi_internal_device_unblock_nowait);
static int scsi_internal_device_unblock(struct scsi_device *sdev,
					enum scsi_device_state new_state)
{
	return scsi_internal_device_unblock_nowait(sdev, new_state);
	int ret;

	mutex_lock(&sdev->state_mutex);
	ret = scsi_internal_device_unblock_nowait(sdev, new_state);
	mutex_unlock(&sdev->state_mutex);

	return ret;
}

static void
+9 −7
Original line number Diff line number Diff line
@@ -231,6 +231,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
	sdev->id = starget->id;
	sdev->lun = lun;
	sdev->channel = starget->channel;
	mutex_init(&sdev->state_mutex);
	sdev->sdev_state = SDEV_CREATED;
	INIT_LIST_HEAD(&sdev->siblings);
	INIT_LIST_HEAD(&sdev->same_target_siblings);
@@ -943,9 +944,11 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,

	/* set the device running here so that slave configure
	 * may do I/O */
	mutex_lock(&sdev->state_mutex);
	ret = scsi_device_set_state(sdev, SDEV_RUNNING);
	if (ret) {
	if (ret)
		ret = scsi_device_set_state(sdev, SDEV_BLOCK);
	mutex_unlock(&sdev->state_mutex);

	if (ret) {
		sdev_printk(KERN_ERR, sdev,
@@ -953,7 +956,6 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
			    scsi_device_state_name(sdev->sdev_state));
		return SCSI_SCAN_NO_RESPONSE;
	}
	}

	if (*bflags & BLIST_MS_192_BYTES_FOR_3F)
		sdev->use_192_bytes_for_3f = 1;
+19 −5
Original line number Diff line number Diff line
@@ -719,7 +719,7 @@ static ssize_t
store_state_field(struct device *dev, struct device_attribute *attr,
		  const char *buf, size_t count)
{
	int i;
	int i, ret;
	struct scsi_device *sdev = to_scsi_device(dev);
	enum scsi_device_state state = 0;

@@ -734,9 +734,11 @@ store_state_field(struct device *dev, struct device_attribute *attr,
	if (!state)
		return -EINVAL;

	if (scsi_device_set_state(sdev, state))
		return -EINVAL;
	return count;
	mutex_lock(&sdev->state_mutex);
	ret = scsi_device_set_state(sdev, state);
	mutex_unlock(&sdev->state_mutex);

	return ret == 0 ? count : -EINVAL;
}

static ssize_t
@@ -1272,6 +1274,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
void __scsi_remove_device(struct scsi_device *sdev)
{
	struct device *dev = &sdev->sdev_gendev;
	int res;

	/*
	 * This cleanup path is not reentrant and while it is impossible
@@ -1282,7 +1285,15 @@ void __scsi_remove_device(struct scsi_device *sdev)
		return;

	if (sdev->is_visible) {
		if (scsi_device_set_state(sdev, SDEV_CANCEL) != 0)
		/*
		 * If scsi_internal_target_block() is running concurrently,
		 * wait until it has finished before changing the device state.
		 */
		mutex_lock(&sdev->state_mutex);
		res = scsi_device_set_state(sdev, SDEV_CANCEL);
		mutex_unlock(&sdev->state_mutex);

		if (res != 0)
			return;

		bsg_unregister_queue(sdev->request_queue);
@@ -1298,7 +1309,10 @@ void __scsi_remove_device(struct scsi_device *sdev)
	 * scsi_run_queue() invocations have finished before tearing down the
	 * device.
	 */
	mutex_lock(&sdev->state_mutex);
	scsi_device_set_state(sdev, SDEV_DEL);
	mutex_unlock(&sdev->state_mutex);

	blk_cleanup_queue(sdev->request_queue);
	cancel_work_sync(&sdev->requeue_work);

+4 −3
Original line number Diff line number Diff line
@@ -554,11 +554,12 @@ int srp_reconnect_rport(struct srp_rport *rport)
		 * invoking scsi_target_unblock() won't change the state of
		 * these devices into running so do that explicitly.
		 */
		spin_lock_irq(shost->host_lock);
		__shost_for_each_device(sdev, shost)
		shost_for_each_device(sdev, shost) {
			mutex_lock(&sdev->state_mutex);
			if (sdev->sdev_state == SDEV_OFFLINE)
				sdev->sdev_state = SDEV_RUNNING;
		spin_unlock_irq(shost->host_lock);
			mutex_unlock(&sdev->state_mutex);
		}
	} else if (rport->state == SRP_RPORT_RUNNING) {
		/*
		 * srp_reconnect_rport() has been invoked with fast_io_fail
Loading