This patch adds support for an alternate I/O path in the scsi midlayer which uses the blk-mq infrastructure instead of the legacy request code. Use of blk-mq is fully transparent to drivers, although for now a host template field is provided to opt out of blk-mq usage in case any unforseen incompatibilities arise. In general replacing the legacy request code with blk-mq is a simple and mostly mechanical transformation. The biggest exception is the new code that deals with the fact the I/O submissions in blk-mq must happen from process context, which slightly complicates the I/O completion handler. The second biggest differences is that blk-mq is build around the concept of preallocated requests that also include driver specific data, which in SCSI context means the scsi_cmnd structure. This completely avoids dynamic memory allocations for the fast path through I/O submission. Due the preallocated requests the MQ code path exclusively uses the host-wide shared tag allocator instead of a per-LUN one. This only affects drivers actually using the block layer provided tag allocator instead of their own. Unlike the old path blk-mq always provides a tag, although drivers don't have to use it. For now the blk-mq path is disable by defauly and must be enabled using the "use_blk_mq" module parameter. Once the remaining work in the block layer to make blk-mq more suitable for slow devices is complete I hope to make it the default and eventually even remove the old code path. Based on the earlier scsi-mq prototype by Nicholas Bellinger. Thanks to Bart Van Assche and Robert Elliot for testing, benchmarking and various sugestions and code contributions. Signed-off-by: Christoph Hellwig <hch@xxxxxx> --- drivers/scsi/hosts.c | 30 ++- drivers/scsi/scsi.c | 5 +- drivers/scsi/scsi_lib.c | 475 +++++++++++++++++++++++++++++++++++++++------ drivers/scsi/scsi_priv.h | 3 + drivers/scsi/scsi_scan.c | 5 +- drivers/scsi/scsi_sysfs.c | 2 + include/scsi/scsi_host.h | 18 +- include/scsi/scsi_tcq.h | 28 ++- 8 files changed, 494 insertions(+), 72 deletions(-) diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 0632eee..6322e6c 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -213,9 +213,24 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, goto fail; } + if (shost_use_blk_mq(shost)) { + error = scsi_mq_setup_tags(shost); + if (error) + goto fail; + } + + /* + * Note that we allocate the freelist even for the MQ case for now, + * as we need a command set aside for scsi_reset_provider. Having + * the full host freelist and one command available for that is a + * little heavy-handed, but avoids introducing a special allocator + * just for this. Eventually the structure of scsi_reset_provider + * will need a major overhaul. + */ error = scsi_setup_command_freelist(shost); if (error) - goto fail; + goto out_destroy_tags; + if (!shost->shost_gendev.parent) shost->shost_gendev.parent = dev ? dev : &platform_bus; @@ -226,7 +241,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, error = device_add(&shost->shost_gendev); if (error) - goto out; + goto out_destroy_freelist; pm_runtime_set_active(&shost->shost_gendev); pm_runtime_enable(&shost->shost_gendev); @@ -279,8 +294,11 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, device_del(&shost->shost_dev); out_del_gendev: device_del(&shost->shost_gendev); - out: + out_destroy_freelist: scsi_destroy_command_freelist(shost); + out_destroy_tags: + if (shost_use_blk_mq(shost)) + scsi_mq_destroy_tags(shost); fail: return error; } @@ -309,7 +327,9 @@ static void scsi_host_dev_release(struct device *dev) } scsi_destroy_command_freelist(shost); - if (shost->bqt) + if (shost_use_blk_mq(shost) && shost->tag_set.tags) + scsi_mq_destroy_tags(shost); + else if (shost->bqt) blk_free_tags(shost->bqt); kfree(shost->shost_data); @@ -436,6 +456,8 @@ struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize) else shost->dma_boundary = 0xffffffff; + shost->use_blk_mq = scsi_use_blk_mq && !shost->hostt->disable_blk_mq; + device_initialize(&shost->shost_gendev); dev_set_name(&shost->shost_gendev, "host%d", shost->host_no); shost->shost_gendev.bus = &scsi_bus_type; diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index b362058..c089812 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -809,7 +809,7 @@ void scsi_adjust_queue_depth(struct scsi_device *sdev, int tagged, int tags) * is more IO than the LLD's can_queue (so there are not enuogh * tags) request_fn's host queue ready check will handle it. */ - if (!sdev->host->bqt) { + if (!shost_use_blk_mq(sdev->host) && !sdev->host->bqt) { if (blk_queue_tagged(sdev->request_queue) && blk_queue_resize_tags(sdev->request_queue, tags) != 0) goto out; @@ -1363,6 +1363,9 @@ MODULE_LICENSE("GPL"); module_param(scsi_logging_level, int, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(scsi_logging_level, "a bit mask of logging levels"); +bool scsi_use_blk_mq = false; +module_param_named(use_blk_mq, scsi_use_blk_mq, bool, S_IWUSR | S_IRUGO); + static int __init init_scsi(void) { int error; diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 900b1c0..5d39cfc 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -1,5 +1,6 @@ /* - * scsi_lib.c Copyright (C) 1999 Eric Youngdale + * Copyright (C) 1999 Eric Youngdale + * Copyright (C) 2014 Christoph Hellwig * * SCSI queueing library. * Initial versions: Eric Youngdale (eric@xxxxxxxxxxx). @@ -20,6 +21,7 @@ #include <linux/delay.h> #include <linux/hardirq.h> #include <linux/scatterlist.h> +#include <linux/blk-mq.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> @@ -113,6 +115,16 @@ scsi_set_blocked(struct scsi_cmnd *cmd, int reason) } } +static void scsi_mq_requeue_cmd(struct scsi_cmnd *cmd) +{ + struct scsi_device *sdev = cmd->device; + struct request_queue *q = cmd->request->q; + + blk_mq_requeue_request(cmd->request); + blk_mq_kick_requeue_list(q); + put_device(&sdev->sdev_gendev); +} + /** * __scsi_queue_insert - private queue insertion * @cmd: The SCSI command being requeued @@ -150,6 +162,10 @@ static void __scsi_queue_insert(struct scsi_cmnd *cmd, int reason, int unbusy) * before blk_cleanup_queue() finishes. */ cmd->result = 0; + if (q->mq_ops) { + scsi_mq_requeue_cmd(cmd); + return; + } spin_lock_irqsave(q->queue_lock, flags); blk_requeue_request(q, cmd->request); kblockd_schedule_work(&device->requeue_work); @@ -308,6 +324,14 @@ void scsi_device_unbusy(struct scsi_device *sdev) atomic_dec(&sdev->device_busy); } +static void scsi_kick_queue(struct request_queue *q) +{ + if (q->mq_ops) + blk_mq_start_hw_queues(q); + else + blk_run_queue(q); +} + /* * Called for single_lun devices on IO completion. Clear starget_sdev_user, * and call blk_run_queue for all the scsi_devices on the target - @@ -332,7 +356,7 @@ static void scsi_single_lun_run(struct scsi_device *current_sdev) * but in most cases, we will be first. Ideally, each LU on the * target would get some limited time or requests on the target. */ - blk_run_queue(current_sdev->request_queue); + scsi_kick_queue(current_sdev->request_queue); spin_lock_irqsave(shost->host_lock, flags); if (starget->starget_sdev_user) @@ -345,7 +369,7 @@ static void scsi_single_lun_run(struct scsi_device *current_sdev) continue; spin_unlock_irqrestore(shost->host_lock, flags); - blk_run_queue(sdev->request_queue); + scsi_kick_queue(sdev->request_queue); spin_lock_irqsave(shost->host_lock, flags); scsi_device_put(sdev); @@ -438,7 +462,7 @@ static void scsi_starved_list_run(struct Scsi_Host *shost) continue; spin_unlock_irqrestore(shost->host_lock, flags); - blk_run_queue(slq); + scsi_kick_queue(slq); blk_put_queue(slq); spin_lock_irqsave(shost->host_lock, flags); @@ -469,7 +493,10 @@ static void scsi_run_queue(struct request_queue *q) if (!list_empty(&sdev->host->starved_list)) scsi_starved_list_run(sdev->host); - blk_run_queue(q); + if (q->mq_ops) + blk_mq_start_stopped_hw_queues(q, false); + else + blk_run_queue(q); } void scsi_requeue_run_queue(struct work_struct *work) @@ -567,25 +594,72 @@ static struct scatterlist *scsi_sg_alloc(unsigned int nents, gfp_t gfp_mask) return mempool_alloc(sgp->pool, gfp_mask); } -static void scsi_free_sgtable(struct scsi_data_buffer *sdb) +static void scsi_free_sgtable(struct scsi_data_buffer *sdb, bool mq) { - __sg_free_table(&sdb->table, SCSI_MAX_SG_SEGMENTS, false, scsi_sg_free); + if (mq && sdb->table.nents <= SCSI_MAX_SG_SEGMENTS) + return; + __sg_free_table(&sdb->table, SCSI_MAX_SG_SEGMENTS, mq, scsi_sg_free); } static int scsi_alloc_sgtable(struct scsi_data_buffer *sdb, int nents, - gfp_t gfp_mask) + gfp_t gfp_mask, bool mq) { + struct scatterlist *first_chunk = NULL; int ret; BUG_ON(!nents); + if (mq) { + if (nents <= SCSI_MAX_SG_SEGMENTS) { + sdb->table.nents = nents; + sg_init_table(sdb->table.sgl, sdb->table.nents); + return 0; + } + first_chunk = sdb->table.sgl; + } + ret = __sg_alloc_table(&sdb->table, nents, SCSI_MAX_SG_SEGMENTS, - NULL, gfp_mask, scsi_sg_alloc); + first_chunk, gfp_mask, scsi_sg_alloc); if (unlikely(ret)) - scsi_free_sgtable(sdb); + scsi_free_sgtable(sdb, mq); return ret; } +static void scsi_uninit_cmd(struct scsi_cmnd *cmd) +{ + if (cmd->request->cmd_type == REQ_TYPE_FS) { + struct scsi_driver *drv = scsi_cmd_to_driver(cmd); + + if (drv->uninit_command) + drv->uninit_command(cmd); + } +} + +static void scsi_mq_free_sgtables(struct scsi_cmnd *cmd) +{ + if (cmd->sdb.table.nents) + scsi_free_sgtable(&cmd->sdb, true); + if (cmd->request->next_rq && cmd->request->next_rq->special) + scsi_free_sgtable(cmd->request->next_rq->special, true); + if (scsi_prot_sg_count(cmd)) + scsi_free_sgtable(cmd->prot_sdb, true); +} + +static void scsi_mq_uninit_cmd(struct scsi_cmnd *cmd) +{ + struct scsi_device *sdev = cmd->device; + unsigned long flags; + + BUG_ON(list_empty(&cmd->list)); + + scsi_mq_free_sgtables(cmd); + scsi_uninit_cmd(cmd); + + spin_lock_irqsave(&sdev->list_lock, flags); + list_del_init(&cmd->list); + spin_unlock_irqrestore(&sdev->list_lock, flags); +} + /* * Function: scsi_release_buffers() * @@ -605,12 +679,12 @@ static int scsi_alloc_sgtable(struct scsi_data_buffer *sdb, int nents, void scsi_release_buffers(struct scsi_cmnd *cmd) { if (cmd->sdb.table.nents) - scsi_free_sgtable(&cmd->sdb); + scsi_free_sgtable(&cmd->sdb, false); memset(&cmd->sdb, 0, sizeof(cmd->sdb)); if (scsi_prot_sg_count(cmd)) - scsi_free_sgtable(cmd->prot_sdb); + scsi_free_sgtable(cmd->prot_sdb, false); } EXPORT_SYMBOL(scsi_release_buffers); @@ -618,7 +692,7 @@ static void scsi_release_bidi_buffers(struct scsi_cmnd *cmd) { struct scsi_data_buffer *bidi_sdb = cmd->request->next_rq->special; - scsi_free_sgtable(bidi_sdb); + scsi_free_sgtable(bidi_sdb, false); kmem_cache_free(scsi_sdb_cache, bidi_sdb); cmd->request->next_rq->special = NULL; } @@ -629,8 +703,6 @@ static bool scsi_end_request(struct request *req, int error, struct scsi_cmnd *cmd = req->special; struct scsi_device *sdev = cmd->device; struct request_queue *q = sdev->request_queue; - unsigned long flags; - if (blk_update_request(req, error, bytes)) return true; @@ -643,14 +715,38 @@ static bool scsi_end_request(struct request *req, int error, if (blk_queue_add_random(q)) add_disk_randomness(req->rq_disk); - spin_lock_irqsave(q->queue_lock, flags); - blk_finish_request(req, error); - spin_unlock_irqrestore(q->queue_lock, flags); + if (req->mq_ctx) { + /* + * In the MQ case the command gets freed by __blk_mq_end_io, + * so we have to do all cleanup that depends on it earlier. + * + * We also can't kick the queues from irq context, so we + * will have to defer it to a workqueue. + */ + scsi_mq_uninit_cmd(cmd); + + __blk_mq_end_io(req, error); + + if (scsi_target(sdev)->single_lun || + !list_empty(&sdev->host->starved_list)) + kblockd_schedule_work(&sdev->requeue_work); + else + blk_mq_start_stopped_hw_queues(q, true); + + put_device(&sdev->sdev_gendev); + } else { + unsigned long flags; + + spin_lock_irqsave(q->queue_lock, flags); + blk_finish_request(req, error); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (bidi_bytes) + scsi_release_bidi_buffers(cmd); + scsi_release_buffers(cmd); + scsi_next_command(cmd); + } - if (bidi_bytes) - scsi_release_bidi_buffers(cmd); - scsi_release_buffers(cmd); - scsi_next_command(cmd); return false; } @@ -981,8 +1077,14 @@ void scsi_io_completion(struct scsi_cmnd *cmd, unsigned int good_bytes) /* Unprep the request and put it back at the head of the queue. * A new command will be prepared and issued. */ - scsi_release_buffers(cmd); - scsi_requeue_command(q, cmd); + if (q->mq_ops) { + cmd->request->cmd_flags &= ~REQ_DONTPREP; + scsi_mq_uninit_cmd(cmd); + scsi_mq_requeue_cmd(cmd); + } else { + scsi_release_buffers(cmd); + scsi_requeue_command(q, cmd); + } break; case ACTION_RETRY: /* Retry the same command immediately */ @@ -1004,9 +1106,8 @@ static int scsi_init_sgtable(struct request *req, struct scsi_data_buffer *sdb, * If sg table allocation fails, requeue request later. */ if (unlikely(scsi_alloc_sgtable(sdb, req->nr_phys_segments, - gfp_mask))) { + gfp_mask, req->mq_ctx != NULL))) return BLKPREP_DEFER; - } /* * Next, walk the list, and fill in the addresses and sizes of @@ -1034,21 +1135,27 @@ int scsi_init_io(struct scsi_cmnd *cmd, gfp_t gfp_mask) { struct scsi_device *sdev = cmd->device; struct request *rq = cmd->request; + bool is_mq = (rq->mq_ctx != NULL); + int error; - int error = scsi_init_sgtable(rq, &cmd->sdb, gfp_mask); + error = scsi_init_sgtable(rq, &cmd->sdb, gfp_mask); if (error) goto err_exit; if (blk_bidi_rq(rq)) { - struct scsi_data_buffer *bidi_sdb = kmem_cache_zalloc( - scsi_sdb_cache, GFP_ATOMIC); - if (!bidi_sdb) { - error = BLKPREP_DEFER; - goto err_exit; + if (!rq->q->mq_ops) { + struct scsi_data_buffer *bidi_sdb = + kmem_cache_zalloc(scsi_sdb_cache, GFP_ATOMIC); + if (!bidi_sdb) { + error = BLKPREP_DEFER; + goto err_exit; + } + + rq->next_rq->special = bidi_sdb; } - rq->next_rq->special = bidi_sdb; - error = scsi_init_sgtable(rq->next_rq, bidi_sdb, GFP_ATOMIC); + error = scsi_init_sgtable(rq->next_rq, rq->next_rq->special, + GFP_ATOMIC); if (error) goto err_exit; } @@ -1060,7 +1167,7 @@ int scsi_init_io(struct scsi_cmnd *cmd, gfp_t gfp_mask) BUG_ON(prot_sdb == NULL); ivecs = blk_rq_count_integrity_sg(rq->q, rq->bio); - if (scsi_alloc_sgtable(prot_sdb, ivecs, gfp_mask)) { + if (scsi_alloc_sgtable(prot_sdb, ivecs, gfp_mask, is_mq)) { error = BLKPREP_DEFER; goto err_exit; } @@ -1074,13 +1181,16 @@ int scsi_init_io(struct scsi_cmnd *cmd, gfp_t gfp_mask) cmd->prot_sdb->table.nents = count; } - return BLKPREP_OK ; - + return BLKPREP_OK; err_exit: - scsi_release_buffers(cmd); - cmd->request->special = NULL; - scsi_put_command(cmd); - put_device(&sdev->sdev_gendev); + if (is_mq) { + scsi_mq_free_sgtables(cmd); + } else { + scsi_release_buffers(cmd); + cmd->request->special = NULL; + scsi_put_command(cmd); + put_device(&sdev->sdev_gendev); + } return error; } EXPORT_SYMBOL(scsi_init_io); @@ -1295,13 +1405,7 @@ out: static void scsi_unprep_fn(struct request_queue *q, struct request *req) { - if (req->cmd_type == REQ_TYPE_FS) { - struct scsi_cmnd *cmd = req->special; - struct scsi_driver *drv = scsi_cmd_to_driver(cmd); - - if (drv->uninit_command) - drv->uninit_command(cmd); - } + scsi_uninit_cmd(req->special); } /* @@ -1318,7 +1422,11 @@ static inline int scsi_dev_queue_ready(struct request_queue *q, busy = atomic_inc_return(&sdev->device_busy) - 1; if (busy == 0 && atomic_read(&sdev->device_blocked) > 0) { if (atomic_dec_return(&sdev->device_blocked) > 0) { - blk_delay_queue(q, SCSI_QUEUE_DELAY); + /* + * For the MQ case we take care of this in the caller. + */ + if (!q->mq_ops) + blk_delay_queue(q, SCSI_QUEUE_DELAY); goto out_dec; } SCSI_LOG_MLQUEUE(3, sdev_printk(KERN_INFO, sdev, @@ -1688,6 +1796,188 @@ out_delay: blk_delay_queue(q, SCSI_QUEUE_DELAY); } +static inline int prep_to_mq(int ret) +{ + switch (ret) { + case BLKPREP_OK: + return 0; + case BLKPREP_DEFER: + return BLK_MQ_RQ_QUEUE_BUSY; + default: + return BLK_MQ_RQ_QUEUE_ERROR; + } +} + +static int scsi_mq_prep_fn(struct request *req) +{ + struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(req); + struct scsi_device *sdev = req->q->queuedata; + struct Scsi_Host *shost = sdev->host; + unsigned char *sense_buf = cmd->sense_buffer; + struct scatterlist *sg; + + memset(cmd, 0, sizeof(struct scsi_cmnd)); + + req->special = cmd; + + cmd->request = req; + cmd->device = sdev; + cmd->sense_buffer = sense_buf; + + cmd->tag = req->tag; + + req->cmd = req->__cmd; + cmd->cmnd = req->cmd; + cmd->prot_op = SCSI_PROT_NORMAL; + + INIT_LIST_HEAD(&cmd->list); + INIT_DELAYED_WORK(&cmd->abort_work, scmd_eh_abort_handler); + cmd->jiffies_at_alloc = jiffies; + + /* + * XXX: cmd_list lookups are only used by two drivers, try to get + * rid of this list in common code. + */ + spin_lock_irq(&sdev->list_lock); + list_add_tail(&cmd->list, &sdev->cmd_list); + spin_unlock_irq(&sdev->list_lock); + + sg = (void *)cmd + sizeof(struct scsi_cmnd) + shost->hostt->cmd_size; + cmd->sdb.table.sgl = sg; + + if (scsi_host_get_prot(shost)) { + cmd->prot_sdb = (void *)sg + + shost->sg_tablesize * sizeof(struct scatterlist); + memset(cmd->prot_sdb, 0, sizeof(struct scsi_data_buffer)); + + cmd->prot_sdb->table.sgl = + (struct scatterlist *)(cmd->prot_sdb + 1); + } + + if (blk_bidi_rq(req)) { + struct request *next_rq = req->next_rq; + struct scsi_data_buffer *bidi_sdb = blk_mq_rq_to_pdu(next_rq); + + memset(bidi_sdb, 0, sizeof(struct scsi_data_buffer)); + bidi_sdb->table.sgl = + (struct scatterlist *)(bidi_sdb + 1); + + next_rq->special = bidi_sdb; + } + + switch (req->cmd_type) { + case REQ_TYPE_FS: + return scsi_cmd_to_driver(cmd)->init_command(cmd); + case REQ_TYPE_BLOCK_PC: + return scsi_setup_blk_pc_cmnd(cmd->device, req); + default: + return BLKPREP_KILL; + } +} + +static void scsi_mq_done(struct scsi_cmnd *cmd) +{ + trace_scsi_dispatch_cmd_done(cmd); + blk_mq_complete_request(cmd->request); +} + +static int scsi_queue_rq(struct blk_mq_hw_ctx *hctx, struct request *req) +{ + struct request_queue *q = req->q; + struct scsi_device *sdev = q->queuedata; + struct Scsi_Host *shost = sdev->host; + struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(req); + int ret; + int reason; + + ret = prep_to_mq(scsi_prep_state_check(sdev, req)); + if (ret) + goto out; + + ret = BLK_MQ_RQ_QUEUE_BUSY; + if (!get_device(&sdev->sdev_gendev)) + goto out; + + if (!scsi_dev_queue_ready(q, sdev)) + goto out_put_device; + if (!scsi_target_queue_ready(shost, sdev)) + goto out_dec_device_busy; + if (!scsi_host_queue_ready(q, shost, sdev)) + goto out_dec_target_busy; + + if (!(req->cmd_flags & REQ_DONTPREP)) { + ret = prep_to_mq(scsi_mq_prep_fn(req)); + if (ret) + goto out_dec_host_busy; + req->cmd_flags |= REQ_DONTPREP; + } + + scsi_init_cmd_errh(cmd); + cmd->scsi_done = scsi_mq_done; + + reason = scsi_dispatch_cmd(cmd); + if (reason) { + scsi_set_blocked(cmd, reason); + ret = BLK_MQ_RQ_QUEUE_BUSY; + goto out_dec_host_busy; + } + + return BLK_MQ_RQ_QUEUE_OK; + +out_dec_host_busy: + cancel_delayed_work(&cmd->abort_work); + atomic_dec(&shost->host_busy); +out_dec_target_busy: + if (scsi_target(sdev)->can_queue > 0) + atomic_dec(&scsi_target(sdev)->target_busy); +out_dec_device_busy: + atomic_dec(&sdev->device_busy); +out_put_device: + put_device(&sdev->sdev_gendev); +out: + switch (ret) { + case BLK_MQ_RQ_QUEUE_BUSY: + blk_mq_stop_hw_queue(hctx); + if (atomic_read(&sdev->device_busy) == 0 && + !scsi_device_blocked(sdev)) + blk_mq_delay_queue(hctx, SCSI_QUEUE_DELAY); + break; + case BLK_MQ_RQ_QUEUE_ERROR: + /* + * Make sure to release all allocated ressources when + * we hit an error, as we will never see this command + * again. + */ + if (req->cmd_flags & REQ_DONTPREP) + scsi_mq_uninit_cmd(cmd); + break; + default: + break; + } + return ret; +} + +static int scsi_init_request(void *data, struct request *rq, + unsigned int hctx_idx, unsigned int request_idx, + unsigned int numa_node) +{ + struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq); + + cmd->sense_buffer = kzalloc_node(SCSI_SENSE_BUFFERSIZE, GFP_KERNEL, + numa_node); + if (!cmd->sense_buffer) + return -ENOMEM; + return 0; +} + +static void scsi_exit_request(void *data, struct request *rq, + unsigned int hctx_idx, unsigned int request_idx) +{ + struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq); + + kfree(cmd->sense_buffer); +} + u64 scsi_calculate_bounce_limit(struct Scsi_Host *shost) { struct device *host_dev; @@ -1710,16 +2000,10 @@ u64 scsi_calculate_bounce_limit(struct Scsi_Host *shost) } EXPORT_SYMBOL(scsi_calculate_bounce_limit); -struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, - request_fn_proc *request_fn) +static void __scsi_init_queue(struct Scsi_Host *shost, struct request_queue *q) { - struct request_queue *q; struct device *dev = shost->dma_dev; - q = blk_init_queue(request_fn, NULL); - if (!q) - return NULL; - /* * this limit is imposed by hardware restrictions */ @@ -1750,7 +2034,17 @@ struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, * blk_queue_update_dma_alignment() later. */ blk_queue_dma_alignment(q, 0x03); +} +struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, + request_fn_proc *request_fn) +{ + struct request_queue *q; + + q = blk_init_queue(request_fn, NULL); + if (!q) + return NULL; + __scsi_init_queue(shost, q); return q; } EXPORT_SYMBOL(__scsi_alloc_queue); @@ -1771,6 +2065,55 @@ struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) return q; } +static struct blk_mq_ops scsi_mq_ops = { + .map_queue = blk_mq_map_queue, + .queue_rq = scsi_queue_rq, + .complete = scsi_softirq_done, + .timeout = scsi_times_out, + .init_request = scsi_init_request, + .exit_request = scsi_exit_request, +}; + +struct request_queue *scsi_mq_alloc_queue(struct scsi_device *sdev) +{ + sdev->request_queue = blk_mq_init_queue(&sdev->host->tag_set); + if (IS_ERR(sdev->request_queue)) + return NULL; + + sdev->request_queue->queuedata = sdev; + __scsi_init_queue(sdev->host, sdev->request_queue); + return sdev->request_queue; +} + +int scsi_mq_setup_tags(struct Scsi_Host *shost) +{ + unsigned int cmd_size, sgl_size, tbl_size; + + tbl_size = shost->sg_tablesize; + if (tbl_size > SCSI_MAX_SG_SEGMENTS) + tbl_size = SCSI_MAX_SG_SEGMENTS; + sgl_size = tbl_size * sizeof(struct scatterlist); + cmd_size = sizeof(struct scsi_cmnd) + shost->hostt->cmd_size + sgl_size; + if (scsi_host_get_prot(shost)) + cmd_size += sizeof(struct scsi_data_buffer) + sgl_size; + + memset(&shost->tag_set, 0, sizeof(shost->tag_set)); + shost->tag_set.ops = &scsi_mq_ops; + shost->tag_set.nr_hw_queues = 1; + shost->tag_set.queue_depth = shost->can_queue; + shost->tag_set.cmd_size = cmd_size; + shost->tag_set.numa_node = NUMA_NO_NODE; + shost->tag_set.flags = BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_SG_MERGE; + shost->tag_set.driver_data = shost; + + return blk_mq_alloc_tag_set(&shost->tag_set); +} + +void scsi_mq_destroy_tags(struct Scsi_Host *shost) +{ + blk_mq_free_tag_set(&shost->tag_set); +} + /* * Function: scsi_block_requests() * @@ -2516,9 +2859,13 @@ scsi_internal_device_block(struct scsi_device *sdev) * block layer from calling the midlayer with this device's * request queue. */ - spin_lock_irqsave(q->queue_lock, flags); - blk_stop_queue(q); - spin_unlock_irqrestore(q->queue_lock, flags); + if (q->mq_ops) { + blk_mq_stop_hw_queues(q); + } else { + spin_lock_irqsave(q->queue_lock, flags); + blk_stop_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + } return 0; } @@ -2564,9 +2911,13 @@ scsi_internal_device_unblock(struct scsi_device *sdev, sdev->sdev_state != SDEV_OFFLINE) return -EINVAL; - spin_lock_irqsave(q->queue_lock, flags); - blk_start_queue(q); - spin_unlock_irqrestore(q->queue_lock, flags); + if (q->mq_ops) { + blk_mq_start_stopped_hw_queues(q, false); + } else { + spin_lock_irqsave(q->queue_lock, flags); + blk_start_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + } return 0; } diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index a45d1c2..12b8e1b 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -88,6 +88,9 @@ extern void scsi_next_command(struct scsi_cmnd *cmd); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int); extern void scsi_run_host_queues(struct Scsi_Host *shost); extern struct request_queue *scsi_alloc_queue(struct scsi_device *sdev); +extern struct request_queue *scsi_mq_alloc_queue(struct scsi_device *sdev); +extern int scsi_mq_setup_tags(struct Scsi_Host *shost); +extern void scsi_mq_destroy_tags(struct Scsi_Host *shost); extern int scsi_init_queue(void); extern void scsi_exit_queue(void); struct request_queue; diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 4a6e4ba..b91cfaf 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -273,7 +273,10 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, */ sdev->borken = 1; - sdev->request_queue = scsi_alloc_queue(sdev); + if (shost_use_blk_mq(shost)) + sdev->request_queue = scsi_mq_alloc_queue(sdev); + else + sdev->request_queue = scsi_alloc_queue(sdev); if (!sdev->request_queue) { /* release fn is set up in scsi_sysfs_device_initialise, so * have to free and put manually here */ diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index deef063..6c9227f 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -333,6 +333,7 @@ store_shost_eh_deadline(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR(eh_deadline, S_IRUGO | S_IWUSR, show_shost_eh_deadline, store_shost_eh_deadline); +shost_rd_attr(use_blk_mq, "%d\n"); shost_rd_attr(unique_id, "%u\n"); shost_rd_attr(cmd_per_lun, "%hd\n"); shost_rd_attr(can_queue, "%hd\n"); @@ -352,6 +353,7 @@ show_host_busy(struct device *dev, struct device_attribute *attr, char *buf) static DEVICE_ATTR(host_busy, S_IRUGO, show_host_busy, NULL); static struct attribute *scsi_sysfs_shost_attrs[] = { + &dev_attr_use_blk_mq.attr, &dev_attr_unique_id.attr, &dev_attr_host_busy.attr, &dev_attr_cmd_per_lun.attr, diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h index 7f9bbda..b54511e 100644 --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -7,6 +7,7 @@ #include <linux/workqueue.h> #include <linux/mutex.h> #include <linux/seq_file.h> +#include <linux/blk-mq.h> #include <scsi/scsi.h> struct request_queue; @@ -531,6 +532,9 @@ struct scsi_host_template { */ unsigned int cmd_size; struct scsi_host_cmd_pool *cmd_pool; + + /* temporary flag to disable blk-mq I/O path */ + bool disable_blk_mq; }; /* @@ -601,7 +605,10 @@ struct Scsi_Host { * Area to keep a shared tag map (if needed, will be * NULL if not). */ - struct blk_queue_tag *bqt; + union { + struct blk_queue_tag *bqt; + struct blk_mq_tag_set tag_set; + }; atomic_t host_busy; /* commands actually active on low-level */ atomic_t host_blocked; @@ -693,6 +700,8 @@ struct Scsi_Host { /* The controller does not support WRITE SAME */ unsigned no_write_same:1; + unsigned use_blk_mq:1; + /* * Optional work queue to be utilized by the transport */ @@ -793,6 +802,13 @@ static inline int scsi_host_in_recovery(struct Scsi_Host *shost) shost->tmf_in_progress; } +extern bool scsi_use_blk_mq; + +static inline bool shost_use_blk_mq(struct Scsi_Host *shost) +{ + return shost->use_blk_mq; +} + extern int scsi_queue_work(struct Scsi_Host *, struct work_struct *); extern void scsi_flush_work(struct Scsi_Host *); diff --git a/include/scsi/scsi_tcq.h b/include/scsi/scsi_tcq.h index 81dd12e..cdcc90b 100644 --- a/include/scsi/scsi_tcq.h +++ b/include/scsi/scsi_tcq.h @@ -67,7 +67,8 @@ static inline void scsi_activate_tcq(struct scsi_device *sdev, int depth) if (!sdev->tagged_supported) return; - if (!blk_queue_tagged(sdev->request_queue)) + if (!shost_use_blk_mq(sdev->host) && + blk_queue_tagged(sdev->request_queue)) blk_queue_init_tags(sdev->request_queue, depth, sdev->host->bqt); @@ -80,7 +81,8 @@ static inline void scsi_activate_tcq(struct scsi_device *sdev, int depth) **/ static inline void scsi_deactivate_tcq(struct scsi_device *sdev, int depth) { - if (blk_queue_tagged(sdev->request_queue)) + if (!shost_use_blk_mq(sdev->host) && + blk_queue_tagged(sdev->request_queue)) blk_queue_free_tags(sdev->request_queue); scsi_adjust_queue_depth(sdev, 0, depth); } @@ -108,6 +110,15 @@ static inline int scsi_populate_tag_msg(struct scsi_cmnd *cmd, char *msg) return 0; } +static inline struct scsi_cmnd *scsi_mq_find_tag(struct Scsi_Host *shost, + unsigned int hw_ctx, int tag) +{ + struct request *req; + + req = blk_mq_tag_to_rq(shost->tag_set.tags[hw_ctx], tag); + return req ? (struct scsi_cmnd *)req->special : NULL; +} + /** * scsi_find_tag - find a tagged command by device * @SDpnt: pointer to the ScSI device @@ -118,10 +129,12 @@ static inline int scsi_populate_tag_msg(struct scsi_cmnd *cmd, char *msg) **/ static inline struct scsi_cmnd *scsi_find_tag(struct scsi_device *sdev, int tag) { - struct request *req; if (tag != SCSI_NO_TAG) { + if (shost_use_blk_mq(sdev->host)) + return scsi_mq_find_tag(sdev->host, 0, tag); + req = blk_queue_find_tag(sdev->request_queue, tag); return req ? (struct scsi_cmnd *)req->special : NULL; } @@ -130,6 +143,7 @@ static inline struct scsi_cmnd *scsi_find_tag(struct scsi_device *sdev, int tag) return sdev->current_cmnd; } + /** * scsi_init_shared_tag_map - create a shared tag map * @shost: the host to share the tag map among all devices @@ -138,6 +152,12 @@ static inline struct scsi_cmnd *scsi_find_tag(struct scsi_device *sdev, int tag) static inline int scsi_init_shared_tag_map(struct Scsi_Host *shost, int depth) { /* + * We always have a shared tag map around when using blk-mq. + */ + if (shost_use_blk_mq(shost)) + return 0; + + /* * If the shared tag map isn't already initialized, do it now. * This saves callers from having to check ->bqt when setting up * devices on the shared host (for libata) @@ -165,6 +185,8 @@ static inline struct scsi_cmnd *scsi_host_find_tag(struct Scsi_Host *shost, struct request *req; if (tag != SCSI_NO_TAG) { + if (shost_use_blk_mq(shost)) + return scsi_mq_find_tag(shost, 0, tag); req = blk_map_queue_find_tag(shost->bqt, tag); return req ? (struct scsi_cmnd *)req->special : NULL; } -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html