On Sun, 2017-03-12 at 12:26 +0200, Israel Rukshin wrote:
> scsi_device_get() affects I/O because scsi_target_unblock() use it and calls to blk_start_queue().
> terminate_rport_io() is called after scsi_target_unblock() and completes all the commands
> including the SYNCHRONIZE CACHE command.
>
> I applied your patch and you can see that QUEUE_FLAG_STOPPED is on.
Hello Israel,
Thanks, that's very interesting feedback. Sorry but I prefer to fix
scsi_target_unblock() instead of changing __scsi_remove_device(). Would it
be possible to verify whether the five attached patches fix the issue you
had reported?
Thanks,
Bart.
From 3c5882c9cd88265ff81f6321edc3caf2a2981f55 Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Date: Fri, 3 Mar 2017 11:13:39 -0800
Subject: [PATCH 1/5] Avoid that scsi_device_put() triggers a use-after-free
Avoid that the following crash occurs, a crash that is easy to
trigger with kernel v4.11-rc1 but that I only saw sporadically
in the past:
general protection fault: 0000 [#1] SMP
RIP: 0010:scsi_device_put+0xb/0x30
Call Trace:
scsi_disk_put+0x2d/0x40
sd_release+0x3d/0xb0
__blkdev_put+0x29e/0x360
blkdev_put+0x49/0x170
dm_put_table_device+0x58/0xc0 [dm_mod]
dm_put_device+0x70/0xc0 [dm_mod]
free_priority_group+0x92/0xc0 [dm_multipath]
free_multipath+0x70/0xc0 [dm_multipath]
multipath_dtr+0x19/0x20 [dm_multipath]
dm_table_destroy+0x67/0x120 [dm_mod]
dev_suspend+0xde/0x240 [dm_mod]
ctl_ioctl+0x1f5/0x520 [dm_mod]
dm_ctl_ioctl+0xe/0x20 [dm_mod]
do_vfs_ioctl+0x8f/0x700
SyS_ioctl+0x3c/0x70
entry_SYSCALL_64_fastpath+0x18/0xad
Signed-off-by: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
---
drivers/scsi/scsi.c | 2 +-
drivers/scsi/scsi_scan.c | 1 +
include/scsi/scsi_device.h | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 7bfbcfa7af40..b3bb49d06943 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -602,7 +602,7 @@ EXPORT_SYMBOL(scsi_device_get);
*/
void scsi_device_put(struct scsi_device *sdev)
{
- module_put(sdev->host->hostt->module);
+ module_put(sdev->hostt->module);
put_device(&sdev->sdev_gendev);
}
EXPORT_SYMBOL(scsi_device_put);
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index 6f7128f49c30..7134487abbb1 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -227,6 +227,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
sdev->model = scsi_null_device_strs;
sdev->rev = scsi_null_device_strs;
sdev->host = shost;
+ sdev->hostt = shost->hostt;
sdev->queue_ramp_up_period = SCSI_DEFAULT_RAMP_UP_PERIOD;
sdev->id = starget->id;
sdev->lun = lun;
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 6f22b39f1b0c..cda620ed5922 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -82,6 +82,7 @@ struct scsi_event {
struct scsi_device {
struct Scsi_Host *host;
+ struct scsi_host_template *hostt;
struct request_queue *request_queue;
/* the next two are protected by the host->host_lock */
--
2.12.0
From ec349df5c6b0336c746c109895d717b2be8151cb Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Date: Fri, 3 Mar 2017 11:05:13 -0800
Subject: [PATCH 2/5] Change sdev->host->hostt into sdev->hostt
Now that the host template pointer is cached in the SCSI device
structure, use that pointer instead of going around through the
SCSI host. This patch does not change any functionality.
Signed-off-by: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
---
drivers/scsi/osst.c | 7 +++----
drivers/scsi/raid_class.c | 2 +-
drivers/scsi/scsi.c | 2 +-
drivers/scsi/scsi_error.c | 10 +++++-----
drivers/scsi/scsi_ioctl.c | 4 ++--
drivers/scsi/scsi_lib.c | 2 +-
drivers/scsi/scsi_scan.c | 4 ++--
drivers/scsi/scsi_sysfs.c | 16 ++++++++--------
drivers/scsi/sd.c | 8 ++++----
drivers/scsi/sg.c | 14 +++++---------
drivers/scsi/st.c | 5 ++---
11 files changed, 34 insertions(+), 40 deletions(-)
diff --git a/drivers/scsi/osst.c b/drivers/scsi/osst.c
index c47f4b349bac..410a5865eae3 100644
--- a/drivers/scsi/osst.c
+++ b/drivers/scsi/osst.c
@@ -5285,11 +5285,10 @@ static long osst_compat_ioctl(struct file * file, unsigned int cmd_in, unsigned
struct osst_tape *STp = file->private_data;
struct scsi_device *sdev = STp->device;
int ret = -ENOIOCTLCMD;
- if (sdev->host->hostt->compat_ioctl) {
- ret = sdev->host->hostt->compat_ioctl(sdev, cmd_in, (void __user *)arg);
-
- }
+ if (sdev->hostt->compat_ioctl)
+ ret = sdev->hostt->compat_ioctl(sdev, cmd_in,
+ (void __user *)arg);
return ret;
}
#endif
diff --git a/drivers/scsi/raid_class.c b/drivers/scsi/raid_class.c
index 2c146b44d95f..ccde6e1d8dd5 100644
--- a/drivers/scsi/raid_class.c
+++ b/drivers/scsi/raid_class.c
@@ -67,7 +67,7 @@ static int raid_match(struct attribute_container *cont, struct device *dev)
if (scsi_is_sdev_device(dev)) {
struct scsi_device *sdev = to_scsi_device(dev);
- if (i->f->cookie != sdev->host->hostt)
+ if (i->f->cookie != sdev->hostt)
return 0;
return i->f->is_raid(dev);
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index b3bb49d06943..59c05384cef1 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -581,7 +581,7 @@ int scsi_device_get(struct scsi_device *sdev)
goto fail;
if (!get_device(&sdev->sdev_gendev))
goto fail;
- if (!try_module_get(sdev->host->hostt->module))
+ if (!try_module_get(sdev->hostt->module))
goto fail_put_device;
return 0;
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index f2cafae150bc..1160aa0c7b58 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -131,7 +131,7 @@ scmd_eh_abort_handler(struct work_struct *work)
SCSI_LOG_ERROR_RECOVERY(3,
scmd_printk(KERN_INFO, scmd,
"aborting command\n"));
- rtn = scsi_try_to_abort_cmd(sdev->host->hostt, scmd);
+ rtn = scsi_try_to_abort_cmd(sdev->hostt, scmd);
if (rtn == SUCCESS) {
set_host_byte(scmd, DID_TIME_OUT);
if (scsi_host_eh_past_deadline(sdev->host)) {
@@ -604,7 +604,7 @@ EXPORT_SYMBOL_GPL(scsi_check_sense);
static void scsi_handle_queue_ramp_up(struct scsi_device *sdev)
{
- struct scsi_host_template *sht = sdev->host->hostt;
+ struct scsi_host_template *sht = sdev->hostt;
struct scsi_device *tmp_sdev;
if (!sht->track_queue_depth ||
@@ -636,7 +636,7 @@ static void scsi_handle_queue_ramp_up(struct scsi_device *sdev)
static void scsi_handle_queue_full(struct scsi_device *sdev)
{
- struct scsi_host_template *sht = sdev->host->hostt;
+ struct scsi_host_template *sht = sdev->hostt;
struct scsi_device *tmp_sdev;
if (!sht->track_queue_depth)
@@ -851,7 +851,7 @@ static int scsi_try_target_reset(struct scsi_cmnd *scmd)
static int scsi_try_bus_device_reset(struct scsi_cmnd *scmd)
{
int rtn;
- struct scsi_host_template *hostt = scmd->device->host->hostt;
+ struct scsi_host_template *hostt = scmd->device->hostt;
if (!hostt->eh_device_reset_handler)
return FAILED;
@@ -890,7 +890,7 @@ static int scsi_try_to_abort_cmd(struct scsi_host_template *hostt,
static void scsi_abort_eh_cmnd(struct scsi_cmnd *scmd)
{
- if (scsi_try_to_abort_cmd(scmd->device->host->hostt, scmd) != SUCCESS)
+ if (scsi_try_to_abort_cmd(scmd->device->hostt, scmd) != SUCCESS)
if (scsi_try_bus_device_reset(scmd) != SUCCESS)
if (scsi_try_target_reset(scmd) != SUCCESS)
if (scsi_try_bus_reset(scmd) != SUCCESS)
diff --git a/drivers/scsi/scsi_ioctl.c b/drivers/scsi/scsi_ioctl.c
index b6bf3f29a12a..4ecb5d48b65b 100644
--- a/drivers/scsi/scsi_ioctl.c
+++ b/drivers/scsi/scsi_ioctl.c
@@ -264,8 +264,8 @@ int scsi_ioctl(struct scsi_device *sdev, int cmd, void __user *arg)
case SG_SCSI_RESET:
return scsi_ioctl_reset(sdev, arg);
default:
- if (sdev->host->hostt->ioctl)
- return sdev->host->hostt->ioctl(sdev, cmd, arg);
+ if (sdev->hostt->ioctl)
+ return sdev->hostt->ioctl(sdev, cmd, arg);
}
return -EINVAL;
}
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index ba2286652ff6..e8176708cc28 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -1141,7 +1141,7 @@ void scsi_init_command(struct scsi_device *dev, struct scsi_cmnd *cmd)
/* zero out the cmd, except for the embedded scsi_request */
memset((char *)cmd + sizeof(cmd->req), 0,
- sizeof(*cmd) - sizeof(cmd->req) + dev->host->hostt->cmd_size);
+ sizeof(*cmd) - sizeof(cmd->req) + dev->hostt->cmd_size);
cmd->device = dev;
cmd->sense_buffer = buf;
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index 7134487abbb1..aa20bfe8bdc2 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -980,8 +980,8 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
transport_configure_device(&sdev->sdev_gendev);
- if (sdev->host->hostt->slave_configure) {
- ret = sdev->host->hostt->slave_configure(sdev);
+ if (sdev->hostt->slave_configure) {
+ ret = sdev->hostt->slave_configure(sdev);
if (ret) {
/*
* if LLDD reports slave not present, don't clutter
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 82dfe07b1d47..4a580657c6f6 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -912,7 +912,7 @@ sdev_store_queue_depth(struct device *dev, struct device_attribute *attr,
{
int depth, retval;
struct scsi_device *sdev = to_scsi_device(dev);
- struct scsi_host_template *sht = sdev->host->hostt;
+ struct scsi_host_template *sht = sdev->hostt;
if (!sht->change_queue_depth)
return -EINVAL;
@@ -1080,11 +1080,11 @@ static umode_t scsi_sdev_attr_is_visible(struct kobject *kobj,
if (attr == &dev_attr_queue_depth.attr &&
- !sdev->host->hostt->change_queue_depth)
+ !sdev->hostt->change_queue_depth)
return S_IRUGO;
if (attr == &dev_attr_queue_ramp_up_period.attr &&
- !sdev->host->hostt->change_queue_depth)
+ !sdev->hostt->change_queue_depth)
return 0;
#ifdef CONFIG_SCSI_DH
@@ -1256,10 +1256,10 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
"Failed to register bsg queue, errno=%d\n", error);
/* add additional host specific attributes */
- if (sdev->host->hostt->sdev_attrs) {
- for (i = 0; sdev->host->hostt->sdev_attrs[i]; i++) {
+ if (sdev->hostt->sdev_attrs) {
+ for (i = 0; sdev->hostt->sdev_attrs[i]; i++) {
error = device_create_file(&sdev->sdev_gendev,
- sdev->host->hostt->sdev_attrs[i]);
+ sdev->hostt->sdev_attrs[i]);
if (error)
return error;
}
@@ -1302,8 +1302,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
blk_cleanup_queue(sdev->request_queue);
cancel_work_sync(&sdev->requeue_work);
- if (sdev->host->hostt->slave_destroy)
- sdev->host->hostt->slave_destroy(sdev);
+ if (sdev->hostt->slave_destroy)
+ sdev->hostt->slave_destroy(sdev);
transport_destroy_device(dev);
/*
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index d277e8620e3e..376cf43febf1 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -1571,9 +1571,9 @@ static int sd_compat_ioctl(struct block_device *bdev, fmode_t mode,
/*
* Let the static ioctl translation table take care of it.
*/
- if (!sdev->host->hostt->compat_ioctl)
+ if (!sdev->hostt->compat_ioctl)
return -ENOIOCTLCMD;
- return sdev->host->hostt->compat_ioctl(sdev, cmd, (void __user *)arg);
+ return sdev->hostt->compat_ioctl(sdev, cmd, (void __user *)arg);
}
#endif
@@ -2968,8 +2968,8 @@ static void sd_unlock_native_capacity(struct gendisk *disk)
{
struct scsi_device *sdev = scsi_disk(disk)->device;
- if (sdev->host->hostt->unlock_native_capacity)
- sdev->host->hostt->unlock_native_capacity(sdev);
+ if (sdev->hostt->unlock_native_capacity)
+ sdev->hostt->unlock_native_capacity(sdev);
}
/**
diff --git a/drivers/scsi/sg.c b/drivers/scsi/sg.c
index 29b86505f796..84b3cdcc4934 100644
--- a/drivers/scsi/sg.c
+++ b/drivers/scsi/sg.c
@@ -1053,7 +1053,7 @@ sg_ioctl(struct file *filp, unsigned int cmd_in, unsigned long arg)
case SG_EMULATED_HOST:
if (atomic_read(&sdp->detaching))
return -ENODEV;
- return put_user(sdp->device->host->hostt->emulated, ip);
+ return put_user(sdp->device->hostt->emulated, ip);
case SCSI_IOCTL_SEND_COMMAND:
if (atomic_read(&sdp->detaching))
return -ENODEV;
@@ -1120,14 +1120,10 @@ static long sg_compat_ioctl(struct file *filp, unsigned int cmd_in, unsigned lon
return -ENXIO;
sdev = sdp->device;
- if (sdev->host->hostt->compat_ioctl) {
- int ret;
+ if (sdev->hostt->compat_ioctl)
+ return sdev->hostt->compat_ioctl(sdev, cmd_in,
+ (void __user *)arg);
- ret = sdev->host->hostt->compat_ioctl(sdev, cmd_in, (void __user *)arg);
-
- return ret;
- }
-
return -ENOIOCTLCMD;
}
#endif
@@ -2689,7 +2685,7 @@ static int sg_proc_seq_show_debug(struct seq_file *s, void *v)
scsidp->host->host_no,
scsidp->channel, scsidp->id,
scsidp->lun,
- scsidp->host->hostt->emulated);
+ scsidp->hostt->emulated);
}
seq_printf(s, " sg_tablesize=%d excl=%d open_cnt=%d\n",
sdp->sg_tablesize, sdp->exclude, sdp->open_cnt);
diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c
index e5ef78a6848e..949473ae8392 100644
--- a/drivers/scsi/st.c
+++ b/drivers/scsi/st.c
@@ -3862,11 +3862,10 @@ static long st_compat_ioctl(struct file *file, unsigned int cmd, unsigned long a
struct scsi_tape *STp = file->private_data;
struct scsi_device *sdev = STp->device;
int ret = -ENOIOCTLCMD;
- if (sdev->host->hostt->compat_ioctl) {
- ret = sdev->host->hostt->compat_ioctl(sdev, cmd, (void __user *)arg);
+ if (sdev->hostt->compat_ioctl)
+ ret = sdev->hostt->compat_ioctl(sdev, cmd, (void __user *)arg);
- }
return ret;
}
#endif
--
2.12.0
From ca5ab7097947667e7d032728cfdb609b315ee76d Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Date: Mon, 13 Mar 2017 10:06:13 -0700
Subject: [PATCH 3/5] Warn if __scsi_remove_device() is called for a stopped
queue
Calling __scsi_remove_device() for a stopped queue is a bug because
the device_del() call can trigger I/O and will trigger e.g. the
following hang:
Call Trace:
[<ffffffff815dd985>] schedule+0x35/0x80
[<ffffffff815e02c7>] schedule_timeout+0x237/0x2d0
[<ffffffff815dcf46>] io_schedule_timeout+0xa6/0x110
[<ffffffff815de2f3>] wait_for_completion_io+0xa3/0x110
[<ffffffff812e66ff>] blk_execute_rq+0xdf/0x120
[<ffffffffa00135de>] scsi_execute+0xce/0x150 [scsi_mod]
[<ffffffffa001548f>] scsi_execute_req_flags+0x8f/0xf0 [scsi_mod]
[<ffffffffa0154849>] sd_sync_cache+0xa9/0x190 [sd_mod]
[<ffffffffa0154c3a>] sd_shutdown+0x6a/0x100 [sd_mod]
[<ffffffffa0154d34>] sd_remove+0x64/0xc0 [sd_mod]
[<ffffffff8144d3fd>] __device_release_driver+0x8d/0x120
[<ffffffff8144d4ae>] device_release_driver+0x1e/0x30
[<ffffffff8144c239>] bus_remove_device+0xf9/0x170
[<ffffffff81448bc7>] device_del+0x127/0x240
[<ffffffffa001c0f1>] __scsi_remove_device+0xc1/0xd0 [scsi_mod]
[<ffffffffa001a5d7>] scsi_forget_host+0x57/0x60 [scsi_mod]
[<ffffffffa000e3d2>] scsi_remove_host+0x72/0x110 [scsi_mod]
[<ffffffffa06f95ab>] srp_remove_work+0x8b/0x200 [ib_srp]
Reported-by: Israel Rukshin <israelr@xxxxxxxxxxxx>
Signed-off-by: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
---
drivers/scsi/scsi_sysfs.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 4a580657c6f6..30b0ffcac575 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1274,6 +1274,12 @@ void __scsi_remove_device(struct scsi_device *sdev)
struct device *dev = &sdev->sdev_gendev;
/*
+ * Calling __scsi_remove_device() for a stopped queue is a bug because
+ * the device_del() call can trigger I/O. See also sd_remove().
+ */
+ WARN_ON_ONCE(blk_queue_stopped(sdev->request_queue));
+
+ /*
* This cleanup path is not reentrant and while it is impossible
* to get a new reference with scsi_device_get() someone can still
* hold a previously acquired one.
--
2.12.0
From 7a83374d9f721a290fc1ed73ac5500e6a37e081a Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Date: Mon, 13 Mar 2017 10:46:42 -0700
Subject: [PATCH 4/5] Ensure that scsi_internal_device_unblock() does not sleep
The header above scsi_internal_device_unblock() mentions that this
function may be called from interrupt context. Hence ensure that
this function does not sleep.
Signed-off-by: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
---
drivers/scsi/scsi_lib.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index e8176708cc28..90931d3f2255 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -3022,10 +3022,10 @@ scsi_internal_device_unblock(struct scsi_device *sdev,
return -EINVAL;
if (q->mq_ops) {
- blk_mq_start_stopped_hw_queues(q, false);
+ blk_mq_start_stopped_hw_queues(q, true);
} else {
spin_lock_irqsave(q->queue_lock, flags);
- blk_start_queue(q);
+ blk_start_queue_async(q);
spin_unlock_irqrestore(q->queue_lock, flags);
}
--
2.12.0
From 1c6137450d6b815145d6b52481053eeb035c3f4a Mon Sep 17 00:00:00 2001
From: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Date: Mon, 13 Mar 2017 10:46:08 -0700
Subject: [PATCH 5/5] Ensure that scsi_target_unblock() examines all devices
Since scsi_target_unblock() uses starget_for_each_device(), since
starget_for_each_device() uses scsi_device_get(), since
scsi_device_get() fails after unloading of the LLD kernel module
has been started scsi_target_unblock() may skip devices that were
affected by scsi_target_block(). Ensure that scsi_target_block()
examines all SCSI devices. This patch avoids that unloading the
ib_srp kernel module can trigger the following hang:
Call Trace:
schedule+0x35/0x80
schedule_timeout+0x237/0x2d0
io_schedule_timeout+0xa6/0x110
wait_for_completion_io+0xa3/0x110
blk_execute_rq+0xdf/0x120
scsi_execute+0xce/0x150 [scsi_mod]
scsi_execute_req_flags+0x8f/0xf0 [scsi_mod]
sd_sync_cache+0xa9/0x190 [sd_mod]
sd_shutdown+0x6a/0x100 [sd_mod]
sd_remove+0x64/0xc0 [sd_mod]
__device_release_driver+0x8d/0x120
device_release_driver+0x1e/0x30
bus_remove_device+0xf9/0x170
device_del+0x127/0x240
__scsi_remove_device+0xc1/0xd0 [scsi_mod]
scsi_forget_host+0x57/0x60 [scsi_mod]
scsi_remove_host+0x72/0x110 [scsi_mod]
srp_remove_work+0x8b/0x200 [ib_srp]
Reported-by: Israel Rukshin <israelr@xxxxxxxxxxxx>
Signed-off-by: Bart Van Assche <bart.vanassche@xxxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
---
drivers/scsi/scsi_lib.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 90931d3f2255..5b57634b25fd 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -3074,14 +3074,30 @@ target_unblock(struct device *dev, void *data)
return 0;
}
+/**
+ * scsi_target_unblock() - unblock all devices associated with a SCSI target
+ * @dev: Either a pointer to the dev member of struct scsi_target or a pointer
+ * to the shost_gendev member of struct Scsi_Host.
+ * @new_state: New SCSI device state.
+ *
+ * Note: do not use scsi_get_device() nor any of the macros that uses this
+ * function from inside scsi_target_block() because otherwise this function
+ * won't have any effect when called while the SCSI LLD is being unloaded.
+ */
void
scsi_target_unblock(struct device *dev, enum scsi_device_state new_state)
{
- if (scsi_is_target_device(dev))
- starget_for_each_device(to_scsi_target(dev), &new_state,
- device_unblock);
- else
+ if (scsi_is_target_device(dev)) {
+ struct scsi_target *starget = to_scsi_target(dev);
+ struct Scsi_Host *shost = dev_to_shost(dev->parent);
+ unsigned long flags;
+
+ spin_lock_irqsave(shost->host_lock, flags);
+ __starget_for_each_device(starget, &new_state, device_unblock);
+ spin_unlock_irqrestore(shost->host_lock, flags);
+ } else {
device_for_each_child(dev, &new_state, target_unblock);
+ }
}
EXPORT_SYMBOL_GPL(scsi_target_unblock);
--
2.12.0