summaryrefslogtreecommitdiff
path: root/drivers/scsi
diff options
context:
space:
mode:
authorMartin K. Petersen <martin.petersen@oracle.com>2025-11-12 18:16:05 -0500
committerMartin K. Petersen <martin.petersen@oracle.com>2025-11-12 18:16:05 -0500
commitab57a18665a2adca492dcb80c0997316be06e6c6 (patch)
tree0372b321614c747b4e8925a63f7859743ad9b1c9 /drivers/scsi
parentc53a741a7fd4b8e9d07acf1861b5e4a188c6585a (diff)
parent08b12cda6c44dc015bcc152613c35ee0ae8f37b9 (diff)
Merge patch series "Optimize the hot path in the UFS driver"
Bart Van Assche <bvanassche@acm.org> says: Hi Martin, This patch series optimizes the hot path of the UFS driver by making struct scsi_cmnd and struct ufshcd_lrb adjacent. Making these two data structures adjacent is realized as follows: @@ -9040,6 +9046,7 @@ static const struct scsi_host_template ufshcd_driver_template = { .name = UFSHCD, .proc_name = UFSHCD, .map_queues = ufshcd_map_queues, + .cmd_size = sizeof(struct ufshcd_lrb), .init_cmd_priv = ufshcd_init_cmd_priv, .queuecommand = ufshcd_queuecommand, .mq_poll = ufshcd_poll, The following changes had to be made prior to making these two data structures adjacent: * Add support for driver-internal and reserved commands in the SCSI core. * Instead of making the reserved command slot (hba->reserved_slot) invisible to the SCSI core, let the SCSI core allocate a reserved command. * Remove all UFS data structure members that are no longer needed because struct scsi_cmnd and struct ufshcd_lrb are now adjacent * Call ufshcd_init_lrb() from inside the code for queueing a command instead of calling this function before I/O starts. This is necessary because ufshcd_memory_alloc() allocates fewer instances than the block layer allocates requests. See also the following code in the block layer core: if (blk_mq_init_request(set, hctx->fq->flush_rq, hctx_idx, hctx->numa_node)) Although the UFS driver could be modified such that ufshcd_init_lrb() is called from ufshcd_init_cmd_priv(), realizing this would require moving the memory allocations that happen from inside ufshcd_memory_alloc() into ufshcd_init_cmd_priv(). That would make this patch series even larger. Although ufshcd_init_lrb() is called for each command, the benefits of reduced indirection and better cache efficiency outweigh the small overhead of per-command lrb initialization. * ufshcd_add_scsi_host() happens now before any device management commands are submitted. This change is necessary because this patch makes device management command allocation happen when the SCSI host is allocated. * Allocate as many command slots as the host controller supports. Decrease host->cmds_per_lun if necessary once it is clear whether or not the UFS device supports less command slots than the host controller. Please consider this patch series for the next merge window. Thanks, Bart. Link: https://patch.msgid.link/20251031204029.2883185-1-bvanassche@acm.org Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/scsi')
-rw-r--r--drivers/scsi/hosts.c15
-rw-r--r--drivers/scsi/scsi.c12
-rw-r--r--drivers/scsi/scsi_debug.c116
-rw-r--r--drivers/scsi/scsi_error.c3
-rw-r--r--drivers/scsi/scsi_lib.c104
-rw-r--r--drivers/scsi/scsi_priv.h1
-rw-r--r--drivers/scsi/scsi_scan.c74
-rw-r--r--drivers/scsi/scsi_sysfs.c5
8 files changed, 293 insertions, 37 deletions
diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index eb224a338fa2..e047747d4ecf 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -231,6 +231,12 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
goto fail;
}
+ if (shost->nr_reserved_cmds && !sht->queue_reserved_command) {
+ shost_printk(KERN_ERR, shost,
+ "nr_reserved_cmds set but no method to queue\n");
+ goto fail;
+ }
+
/* Use min_t(int, ...) in case shost->can_queue exceeds SHRT_MAX */
shost->cmd_per_lun = min_t(int, shost->cmd_per_lun,
shost->can_queue);
@@ -307,6 +313,14 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
if (error)
goto out_del_dev;
+ if (shost->nr_reserved_cmds) {
+ shost->pseudo_sdev = scsi_get_pseudo_sdev(shost);
+ if (!shost->pseudo_sdev) {
+ error = -ENOMEM;
+ goto out_del_dev;
+ }
+ }
+
scsi_proc_host_add(shost);
scsi_autopm_put_host(shost);
return error;
@@ -436,6 +450,7 @@ struct Scsi_Host *scsi_host_alloc(const struct scsi_host_template *sht, int priv
shost->hostt = sht;
shost->this_id = sht->this_id;
shost->can_queue = sht->can_queue;
+ shost->nr_reserved_cmds = sht->nr_reserved_cmds;
shost->sg_tablesize = sht->sg_tablesize;
shost->sg_prot_tablesize = sht->sg_prot_tablesize;
shost->cmd_per_lun = sht->cmd_per_lun;
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 9a0f467264b3..76cdad063f7b 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -216,6 +216,9 @@ int scsi_device_max_queue_depth(struct scsi_device *sdev)
*/
int scsi_change_queue_depth(struct scsi_device *sdev, int depth)
{
+ if (!sdev->budget_map.map)
+ return -EINVAL;
+
depth = min_t(int, depth, scsi_device_max_queue_depth(sdev));
if (depth > 0) {
@@ -255,6 +258,8 @@ EXPORT_SYMBOL(scsi_change_queue_depth);
*/
int scsi_track_queue_full(struct scsi_device *sdev, int depth)
{
+ if (!sdev->budget_map.map)
+ return 0;
/*
* Don't let QUEUE_FULLs on the same
@@ -826,8 +831,11 @@ struct scsi_device *__scsi_iterate_devices(struct Scsi_Host *shost,
spin_lock_irqsave(shost->host_lock, flags);
while (list->next != &shost->__devices) {
next = list_entry(list->next, struct scsi_device, siblings);
- /* skip devices that we can't get a reference to */
- if (!scsi_device_get(next))
+ /*
+ * Skip pseudo devices and also devices we can't get a
+ * reference to.
+ */
+ if (!scsi_device_is_pseudo_dev(next) && !scsi_device_get(next))
break;
next = NULL;
list = list->next;
diff --git a/drivers/scsi/scsi_debug.c b/drivers/scsi/scsi_debug.c
index b2ab97be5db3..7291b7a7f1b0 100644
--- a/drivers/scsi/scsi_debug.c
+++ b/drivers/scsi/scsi_debug.c
@@ -6752,20 +6752,59 @@ static bool scsi_debug_stop_cmnd(struct scsi_cmnd *cmnd)
return false;
}
+struct sdebug_abort_cmd {
+ u32 unique_tag;
+};
+
+enum sdebug_internal_cmd_type {
+ SCSI_DEBUG_ABORT_CMD,
+};
+
+struct sdebug_internal_cmd {
+ enum sdebug_internal_cmd_type type;
+
+ union {
+ struct sdebug_abort_cmd abort_cmd;
+ };
+};
+
+union sdebug_priv {
+ struct sdebug_scsi_cmd cmd;
+ struct sdebug_internal_cmd internal_cmd;
+};
+
/*
- * Called from scsi_debug_abort() only, which is for timed-out cmd.
+ * Abort SCSI command @cmnd. Only called from scsi_debug_abort(). Although
+ * it would be possible to call scsi_debug_stop_cmnd() directly, an internal
+ * command is allocated and submitted to trigger the reserved command
+ * infrastructure.
*/
static bool scsi_debug_abort_cmnd(struct scsi_cmnd *cmnd)
{
- struct sdebug_scsi_cmd *sdsc = scsi_cmd_priv(cmnd);
- unsigned long flags;
- bool res;
-
- spin_lock_irqsave(&sdsc->lock, flags);
- res = scsi_debug_stop_cmnd(cmnd);
- spin_unlock_irqrestore(&sdsc->lock, flags);
-
- return res;
+ struct Scsi_Host *shost = cmnd->device->host;
+ struct request *rq = scsi_cmd_to_rq(cmnd);
+ u32 unique_tag = blk_mq_unique_tag(rq);
+ struct sdebug_internal_cmd *internal_cmd;
+ struct scsi_cmnd *abort_cmd;
+ struct request *abort_rq;
+ blk_status_t res;
+
+ abort_cmd = scsi_get_internal_cmd(shost->pseudo_sdev, DMA_NONE,
+ BLK_MQ_REQ_RESERVED);
+ if (!abort_cmd)
+ return false;
+ internal_cmd = scsi_cmd_priv(abort_cmd);
+ *internal_cmd = (struct sdebug_internal_cmd) {
+ .type = SCSI_DEBUG_ABORT_CMD,
+ .abort_cmd = {
+ .unique_tag = unique_tag,
+ },
+ };
+ abort_rq = scsi_cmd_to_rq(abort_cmd);
+ abort_rq->timeout = secs_to_jiffies(3);
+ res = blk_execute_rq(abort_rq, true);
+ scsi_put_internal_cmd(abort_cmd);
+ return res == BLK_STS_OK;
}
/*
@@ -9220,6 +9259,56 @@ out_handle:
return ret;
}
+/* Process @scp, a request to abort a SCSI command by tag. */
+static void scsi_debug_abort_cmd(struct Scsi_Host *shost, struct scsi_cmnd *scp)
+{
+ struct sdebug_internal_cmd *internal_cmd = scsi_cmd_priv(scp);
+ struct sdebug_abort_cmd *abort_cmd = &internal_cmd->abort_cmd;
+ const u32 unique_tag = abort_cmd->unique_tag;
+ struct scsi_cmnd *to_be_aborted_scmd =
+ scsi_host_find_tag(shost, unique_tag);
+ struct sdebug_scsi_cmd *to_be_aborted_sdsc =
+ scsi_cmd_priv(to_be_aborted_scmd);
+ bool res = false;
+
+ if (!to_be_aborted_scmd) {
+ pr_err("%s: command with tag %#x not found\n", __func__,
+ unique_tag);
+ return;
+ }
+
+ scoped_guard(spinlock_irqsave, &to_be_aborted_sdsc->lock)
+ res = scsi_debug_stop_cmnd(to_be_aborted_scmd);
+
+ if (res)
+ pr_info("%s: aborted command with tag %#x\n",
+ __func__, unique_tag);
+ else
+ pr_err("%s: failed to abort command with tag %#x\n",
+ __func__, unique_tag);
+
+ set_host_byte(scp, res ? DID_OK : DID_ERROR);
+}
+
+static int scsi_debug_process_reserved_command(struct Scsi_Host *shost,
+ struct scsi_cmnd *scp)
+{
+ struct sdebug_internal_cmd *internal_cmd = scsi_cmd_priv(scp);
+
+ switch (internal_cmd->type) {
+ case SCSI_DEBUG_ABORT_CMD:
+ scsi_debug_abort_cmd(shost, scp);
+ break;
+ default:
+ WARN_ON_ONCE(true);
+ set_host_byte(scp, DID_ERROR);
+ break;
+ }
+
+ scsi_done(scp);
+ return 0;
+}
+
static int scsi_debug_queuecommand(struct Scsi_Host *shost,
struct scsi_cmnd *scp)
{
@@ -9420,6 +9509,9 @@ static int sdebug_init_cmd_priv(struct Scsi_Host *shost, struct scsi_cmnd *cmd)
struct sdebug_scsi_cmd *sdsc = scsi_cmd_priv(cmd);
struct sdebug_defer *sd_dp = &sdsc->sd_dp;
+ if (blk_mq_is_reserved_rq(scsi_cmd_to_rq(cmd)))
+ return 0;
+
spin_lock_init(&sdsc->lock);
hrtimer_setup(&sd_dp->hrt, sdebug_q_cmd_hrt_complete, CLOCK_MONOTONIC,
HRTIMER_MODE_REL_PINNED);
@@ -9439,6 +9531,7 @@ static const struct scsi_host_template sdebug_driver_template = {
.sdev_destroy = scsi_debug_sdev_destroy,
.ioctl = scsi_debug_ioctl,
.queuecommand = scsi_debug_queuecommand,
+ .queue_reserved_command = scsi_debug_process_reserved_command,
.change_queue_depth = sdebug_change_qdepth,
.map_queues = sdebug_map_queues,
.mq_poll = sdebug_blk_mq_poll,
@@ -9448,6 +9541,7 @@ static const struct scsi_host_template sdebug_driver_template = {
.eh_bus_reset_handler = scsi_debug_bus_reset,
.eh_host_reset_handler = scsi_debug_host_reset,
.can_queue = SDEBUG_CANQUEUE,
+ .nr_reserved_cmds = 1,
.this_id = 7,
.sg_tablesize = SG_MAX_SEGMENTS,
.cmd_per_lun = DEF_CMD_PER_LUN,
@@ -9456,7 +9550,7 @@ static const struct scsi_host_template sdebug_driver_template = {
.module = THIS_MODULE,
.skip_settle_delay = 1,
.track_queue_depth = 1,
- .cmd_size = sizeof(struct sdebug_scsi_cmd),
+ .cmd_size = sizeof(union sdebug_priv),
.init_cmd_priv = sdebug_init_cmd_priv,
.target_alloc = sdebug_target_alloc,
.target_destroy = sdebug_target_destroy,
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
index 746ff6a1f309..87636068cd37 100644
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -749,6 +749,9 @@ static void scsi_handle_queue_ramp_up(struct scsi_device *sdev)
const struct scsi_host_template *sht = sdev->host->hostt;
struct scsi_device *tmp_sdev;
+ if (!sdev->budget_map.map)
+ return;
+
if (!sht->track_queue_depth ||
sdev->queue_depth >= sdev->max_queue_depth)
return;
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index d7e42293b864..51ad2ad07e43 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -396,7 +396,8 @@ void scsi_device_unbusy(struct scsi_device *sdev, struct scsi_cmnd *cmd)
if (starget->can_queue > 0)
atomic_dec(&starget->target_busy);
- sbitmap_put(&sdev->budget_map, cmd->budget_token);
+ if (sdev->budget_map.map)
+ sbitmap_put(&sdev->budget_map, cmd->budget_token);
cmd->budget_token = -1;
}
@@ -1360,6 +1361,9 @@ static inline int scsi_dev_queue_ready(struct request_queue *q,
{
int token;
+ if (!sdev->budget_map.map)
+ return INT_MAX;
+
token = sbitmap_get(&sdev->budget_map);
if (token < 0)
return -1;
@@ -1530,6 +1534,14 @@ static void scsi_complete(struct request *rq)
struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq);
enum scsi_disposition disposition;
+ if (blk_mq_is_reserved_rq(rq)) {
+ /* Only pass-through requests are supported in this code path. */
+ WARN_ON_ONCE(!blk_rq_is_passthrough(scsi_cmd_to_rq(cmd)));
+ scsi_mq_uninit_cmd(cmd);
+ __blk_mq_end_request(rq, scsi_result_to_blk_status(cmd->result));
+ return;
+ }
+
INIT_LIST_HEAD(&cmd->eh_entry);
atomic_inc(&cmd->device->iodone_cnt);
@@ -1749,7 +1761,8 @@ static void scsi_mq_put_budget(struct request_queue *q, int budget_token)
{
struct scsi_device *sdev = q->queuedata;
- sbitmap_put(&sdev->budget_map, budget_token);
+ if (sdev->budget_map.map)
+ sbitmap_put(&sdev->budget_map, budget_token);
}
/*
@@ -1818,25 +1831,31 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx,
WARN_ON_ONCE(cmd->budget_token < 0);
/*
- * If the device is not in running state we will reject some or all
- * commands.
+ * Bypass the SCSI device, SCSI target and SCSI host checks for
+ * reserved commands.
*/
- if (unlikely(sdev->sdev_state != SDEV_RUNNING)) {
- ret = scsi_device_state_check(sdev, req);
- if (ret != BLK_STS_OK)
- goto out_put_budget;
- }
+ if (!blk_mq_is_reserved_rq(req)) {
+ /*
+ * If the device is not in running state we will reject some or
+ * all commands.
+ */
+ if (unlikely(sdev->sdev_state != SDEV_RUNNING)) {
+ ret = scsi_device_state_check(sdev, req);
+ if (ret != BLK_STS_OK)
+ goto out_put_budget;
+ }
- ret = BLK_STS_RESOURCE;
- if (!scsi_target_queue_ready(shost, sdev))
- goto out_put_budget;
- if (unlikely(scsi_host_in_recovery(shost))) {
- if (cmd->flags & SCMD_FAIL_IF_RECOVERING)
- ret = BLK_STS_OFFLINE;
- goto out_dec_target_busy;
+ ret = BLK_STS_RESOURCE;
+ if (!scsi_target_queue_ready(shost, sdev))
+ goto out_put_budget;
+ if (unlikely(scsi_host_in_recovery(shost))) {
+ if (cmd->flags & SCMD_FAIL_IF_RECOVERING)
+ ret = BLK_STS_OFFLINE;
+ goto out_dec_target_busy;
+ }
+ if (!scsi_host_queue_ready(q, shost, sdev, cmd))
+ goto out_dec_target_busy;
}
- if (!scsi_host_queue_ready(q, shost, sdev, cmd))
- goto out_dec_target_busy;
/*
* Only clear the driver-private command data if the LLD does not supply
@@ -1865,6 +1884,14 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx,
cmd->submitter = SUBMITTED_BY_BLOCK_LAYER;
blk_mq_start_request(req);
+ if (blk_mq_is_reserved_rq(req)) {
+ reason = shost->hostt->queue_reserved_command(shost, cmd);
+ if (reason) {
+ ret = BLK_STS_RESOURCE;
+ goto out_put_budget;
+ }
+ return BLK_STS_OK;
+ }
reason = scsi_dispatch_cmd(cmd);
if (reason) {
scsi_set_blocked(cmd, reason);
@@ -2083,7 +2110,8 @@ int scsi_mq_setup_tags(struct Scsi_Host *shost)
tag_set->ops = &scsi_mq_ops_no_commit;
tag_set->nr_hw_queues = shost->nr_hw_queues ? : 1;
tag_set->nr_maps = shost->nr_maps ? : 1;
- tag_set->queue_depth = shost->can_queue;
+ tag_set->queue_depth = shost->can_queue + shost->nr_reserved_cmds;
+ tag_set->reserved_tags = shost->nr_reserved_cmds;
tag_set->cmd_size = cmd_size;
tag_set->numa_node = dev_to_node(shost->dma_dev);
if (shost->hostt->tag_alloc_policy_rr)
@@ -2107,6 +2135,44 @@ void scsi_mq_free_tags(struct kref *kref)
}
/**
+ * scsi_get_internal_cmd() - Allocate an internal SCSI command.
+ * @sdev: SCSI device from which to allocate the command
+ * @data_direction: Data direction for the allocated command
+ * @flags: request allocation flags, e.g. BLK_MQ_REQ_RESERVED or
+ * BLK_MQ_REQ_NOWAIT.
+ *
+ * Allocates a SCSI command for internal LLDD use.
+ */
+struct scsi_cmnd *scsi_get_internal_cmd(struct scsi_device *sdev,
+ enum dma_data_direction data_direction,
+ blk_mq_req_flags_t flags)
+{
+ enum req_op op = data_direction == DMA_TO_DEVICE ? REQ_OP_DRV_OUT :
+ REQ_OP_DRV_IN;
+ struct scsi_cmnd *scmd;
+ struct request *rq;
+
+ rq = scsi_alloc_request(sdev->request_queue, op, flags);
+ if (IS_ERR(rq))
+ return NULL;
+ scmd = blk_mq_rq_to_pdu(rq);
+ scmd->device = sdev;
+
+ return scmd;
+}
+EXPORT_SYMBOL_GPL(scsi_get_internal_cmd);
+
+/**
+ * scsi_put_internal_cmd() - Free an internal SCSI command.
+ * @scmd: SCSI command to be freed
+ */
+void scsi_put_internal_cmd(struct scsi_cmnd *scmd)
+{
+ blk_mq_free_request(blk_mq_rq_from_pdu(scmd));
+}
+EXPORT_SYMBOL_GPL(scsi_put_internal_cmd);
+
+/**
* scsi_device_from_queue - return sdev associated with a request_queue
* @q: The request queue to return the sdev from
*
diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
index 5b2b19f5e8ec..d07ec15d6c00 100644
--- a/drivers/scsi/scsi_priv.h
+++ b/drivers/scsi/scsi_priv.h
@@ -135,6 +135,7 @@ extern int scsi_complete_async_scans(void);
extern int scsi_scan_host_selected(struct Scsi_Host *, unsigned int,
unsigned int, u64, enum scsi_scan_mode);
extern void scsi_forget_host(struct Scsi_Host *);
+struct scsi_device *scsi_get_pseudo_sdev(struct Scsi_Host *);
/* scsi_sysctl.c */
#ifdef CONFIG_SYSCTL
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index 3c6e089e80c3..7acbfcfc2172 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -347,6 +347,11 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
kref_get(&sdev->host->tagset_refcnt);
sdev->request_queue = q;
+ scsi_sysfs_device_initialize(sdev);
+
+ if (scsi_device_is_pseudo_dev(sdev))
+ return sdev;
+
depth = sdev->host->cmd_per_lun ?: 1;
/*
@@ -363,8 +368,6 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
scsi_change_queue_depth(sdev, depth);
- scsi_sysfs_device_initialize(sdev);
-
if (shost->hostt->sdev_init) {
ret = shost->hostt->sdev_init(sdev);
if (ret) {
@@ -1068,6 +1071,11 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
transport_configure_device(&sdev->sdev_gendev);
+ sdev->sdev_bflags = *bflags;
+
+ if (scsi_device_is_pseudo_dev(sdev))
+ return SCSI_SCAN_LUN_PRESENT;
+
/*
* No need to freeze the queue as it isn't reachable to anyone else yet.
*/
@@ -1113,7 +1121,6 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
sdev->max_queue_depth = sdev->queue_depth;
WARN_ON_ONCE(sdev->max_queue_depth > sdev->budget_map.depth);
- sdev->sdev_bflags = *bflags;
/*
* Ok, the device is now all set up, we can
@@ -1212,6 +1219,12 @@ static int scsi_probe_and_add_lun(struct scsi_target *starget,
if (!sdev)
goto out;
+ if (scsi_device_is_pseudo_dev(sdev)) {
+ if (bflagsp)
+ *bflagsp = BLIST_NOLUN;
+ return SCSI_SCAN_LUN_PRESENT;
+ }
+
result = kmalloc(result_len, GFP_KERNEL);
if (!result)
goto out_free_sdev;
@@ -2083,12 +2096,65 @@ void scsi_forget_host(struct Scsi_Host *shost)
restart:
spin_lock_irqsave(shost->host_lock, flags);
list_for_each_entry(sdev, &shost->__devices, siblings) {
- if (sdev->sdev_state == SDEV_DEL)
+ if (scsi_device_is_pseudo_dev(sdev) ||
+ sdev->sdev_state == SDEV_DEL)
continue;
spin_unlock_irqrestore(shost->host_lock, flags);
__scsi_remove_device(sdev);
goto restart;
}
spin_unlock_irqrestore(shost->host_lock, flags);
+
+ /*
+ * Remove the pseudo device last since it may be needed during removal
+ * of other SCSI devices.
+ */
+ if (shost->pseudo_sdev)
+ __scsi_remove_device(shost->pseudo_sdev);
}
+/**
+ * scsi_get_pseudo_sdev() - Attach a pseudo SCSI device to a SCSI host
+ * @shost: Host that needs a pseudo SCSI device
+ *
+ * Lock status: None assumed.
+ *
+ * Returns: The scsi_device or NULL
+ *
+ * Notes:
+ * Attach a single scsi_device to the Scsi_Host. The primary aim for this
+ * device is to serve as a container from which SCSI commands can be
+ * allocated. Each SCSI command will carry a command tag allocated by the
+ * block layer. These SCSI commands can be used by the LLDD to send
+ * internal or passthrough commands without having to manage tag allocation
+ * inside the LLDD.
+ */
+struct scsi_device *scsi_get_pseudo_sdev(struct Scsi_Host *shost)
+{
+ struct scsi_device *sdev = NULL;
+ struct scsi_target *starget;
+
+ guard(mutex)(&shost->scan_mutex);
+
+ if (!scsi_host_scan_allowed(shost))
+ goto out;
+
+ starget = scsi_alloc_target(&shost->shost_gendev, 0, shost->max_id);
+ if (!starget)
+ goto out;
+
+ sdev = scsi_alloc_sdev(starget, U64_MAX, NULL);
+ if (!sdev) {
+ scsi_target_reap(starget);
+ goto put_target;
+ }
+
+ sdev->borken = 0;
+
+put_target:
+ /* See also the get_device(dev) call in scsi_alloc_target(). */
+ put_device(&starget->dev);
+
+out:
+ return sdev;
+}
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 6b347596ef19..99eb0a30df61 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1348,6 +1348,9 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
int error;
struct scsi_target *starget = sdev->sdev_target;
+ if (WARN_ON_ONCE(scsi_device_is_pseudo_dev(sdev)))
+ return -EINVAL;
+
error = scsi_target_add(starget);
if (error)
return error;
@@ -1455,7 +1458,7 @@ void __scsi_remove_device(struct scsi_device *sdev)
kref_put(&sdev->host->tagset_refcnt, scsi_mq_free_tags);
cancel_work_sync(&sdev->requeue_work);
- if (sdev->host->hostt->sdev_destroy)
+ if (!scsi_device_is_pseudo_dev(sdev) && sdev->host->hostt->sdev_destroy)
sdev->host->hostt->sdev_destroy(sdev);
transport_destroy_device(dev);