forked from Minki/linux
blk-mq: introduce BLK_STS_DEV_RESOURCE
This status is returned from driver to block layer if device related resource is unavailable, but driver can guarantee that IO dispatch will be triggered in future when the resource is available. Convert some drivers to return BLK_STS_DEV_RESOURCE. Also, if driver returns BLK_STS_RESOURCE and SCHED_RESTART is set, rerun queue after a delay (BLK_MQ_DELAY_QUEUE) to avoid IO stalls. BLK_MQ_DELAY_QUEUE is 3 ms because both scsi-mq and nvmefc are using that magic value. If a driver can make sure there is in-flight IO, it is safe to return BLK_STS_DEV_RESOURCE because: 1) If all in-flight IOs complete before examining SCHED_RESTART in blk_mq_dispatch_rq_list(), SCHED_RESTART must be cleared, so queue is run immediately in this case by blk_mq_dispatch_rq_list(); 2) if there is any in-flight IO after/when examining SCHED_RESTART in blk_mq_dispatch_rq_list(): - if SCHED_RESTART isn't set, queue is run immediately as handled in 1) - otherwise, this request will be dispatched after any in-flight IO is completed via blk_mq_sched_restart() 3) if SCHED_RESTART is set concurently in context because of BLK_STS_RESOURCE, blk_mq_delay_run_hw_queue() will cover the above two cases and make sure IO hang can be avoided. One invariant is that queue will be rerun if SCHED_RESTART is set. Suggested-by: Jens Axboe <axboe@kernel.dk> Tested-by: Laurence Oberman <loberman@redhat.com> Signed-off-by: Ming Lei <ming.lei@redhat.com> Signed-off-by: Mike Snitzer <snitzer@redhat.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
parent
0a4b6e2f80
commit
86ff7c2a80
@ -145,6 +145,7 @@ static const struct {
|
|||||||
[BLK_STS_MEDIUM] = { -ENODATA, "critical medium" },
|
[BLK_STS_MEDIUM] = { -ENODATA, "critical medium" },
|
||||||
[BLK_STS_PROTECTION] = { -EILSEQ, "protection" },
|
[BLK_STS_PROTECTION] = { -EILSEQ, "protection" },
|
||||||
[BLK_STS_RESOURCE] = { -ENOMEM, "kernel resource" },
|
[BLK_STS_RESOURCE] = { -ENOMEM, "kernel resource" },
|
||||||
|
[BLK_STS_DEV_RESOURCE] = { -EBUSY, "device resource" },
|
||||||
[BLK_STS_AGAIN] = { -EAGAIN, "nonblocking retry" },
|
[BLK_STS_AGAIN] = { -EAGAIN, "nonblocking retry" },
|
||||||
|
|
||||||
/* device mapper special case, should not leak out: */
|
/* device mapper special case, should not leak out: */
|
||||||
|
@ -1162,6 +1162,8 @@ static bool blk_mq_mark_tag_wait(struct blk_mq_hw_ctx **hctx,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define BLK_MQ_RESOURCE_DELAY 3 /* ms units */
|
||||||
|
|
||||||
bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
||||||
bool got_budget)
|
bool got_budget)
|
||||||
{
|
{
|
||||||
@ -1169,6 +1171,7 @@ bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
|||||||
struct request *rq, *nxt;
|
struct request *rq, *nxt;
|
||||||
bool no_tag = false;
|
bool no_tag = false;
|
||||||
int errors, queued;
|
int errors, queued;
|
||||||
|
blk_status_t ret = BLK_STS_OK;
|
||||||
|
|
||||||
if (list_empty(list))
|
if (list_empty(list))
|
||||||
return false;
|
return false;
|
||||||
@ -1181,7 +1184,6 @@ bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
|||||||
errors = queued = 0;
|
errors = queued = 0;
|
||||||
do {
|
do {
|
||||||
struct blk_mq_queue_data bd;
|
struct blk_mq_queue_data bd;
|
||||||
blk_status_t ret;
|
|
||||||
|
|
||||||
rq = list_first_entry(list, struct request, queuelist);
|
rq = list_first_entry(list, struct request, queuelist);
|
||||||
if (!blk_mq_get_driver_tag(rq, &hctx, false)) {
|
if (!blk_mq_get_driver_tag(rq, &hctx, false)) {
|
||||||
@ -1226,7 +1228,7 @@ bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = q->mq_ops->queue_rq(hctx, &bd);
|
ret = q->mq_ops->queue_rq(hctx, &bd);
|
||||||
if (ret == BLK_STS_RESOURCE) {
|
if (ret == BLK_STS_RESOURCE || ret == BLK_STS_DEV_RESOURCE) {
|
||||||
/*
|
/*
|
||||||
* If an I/O scheduler has been configured and we got a
|
* If an I/O scheduler has been configured and we got a
|
||||||
* driver tag for the next request already, free it
|
* driver tag for the next request already, free it
|
||||||
@ -1257,6 +1259,8 @@ bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
|||||||
* that is where we will continue on next queue run.
|
* that is where we will continue on next queue run.
|
||||||
*/
|
*/
|
||||||
if (!list_empty(list)) {
|
if (!list_empty(list)) {
|
||||||
|
bool needs_restart;
|
||||||
|
|
||||||
spin_lock(&hctx->lock);
|
spin_lock(&hctx->lock);
|
||||||
list_splice_init(list, &hctx->dispatch);
|
list_splice_init(list, &hctx->dispatch);
|
||||||
spin_unlock(&hctx->lock);
|
spin_unlock(&hctx->lock);
|
||||||
@ -1280,10 +1284,17 @@ bool blk_mq_dispatch_rq_list(struct request_queue *q, struct list_head *list,
|
|||||||
* - Some but not all block drivers stop a queue before
|
* - Some but not all block drivers stop a queue before
|
||||||
* returning BLK_STS_RESOURCE. Two exceptions are scsi-mq
|
* returning BLK_STS_RESOURCE. Two exceptions are scsi-mq
|
||||||
* and dm-rq.
|
* and dm-rq.
|
||||||
|
*
|
||||||
|
* If driver returns BLK_STS_RESOURCE and SCHED_RESTART
|
||||||
|
* bit is set, run queue after a delay to avoid IO stalls
|
||||||
|
* that could otherwise occur if the queue is idle.
|
||||||
*/
|
*/
|
||||||
if (!blk_mq_sched_needs_restart(hctx) ||
|
needs_restart = blk_mq_sched_needs_restart(hctx);
|
||||||
|
if (!needs_restart ||
|
||||||
(no_tag && list_empty_careful(&hctx->dispatch_wait.entry)))
|
(no_tag && list_empty_careful(&hctx->dispatch_wait.entry)))
|
||||||
blk_mq_run_hw_queue(hctx, true);
|
blk_mq_run_hw_queue(hctx, true);
|
||||||
|
else if (needs_restart && (ret == BLK_STS_RESOURCE))
|
||||||
|
blk_mq_delay_run_hw_queue(hctx, BLK_MQ_RESOURCE_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (queued + errors) != 0;
|
return (queued + errors) != 0;
|
||||||
@ -1764,6 +1775,7 @@ static blk_status_t __blk_mq_issue_directly(struct blk_mq_hw_ctx *hctx,
|
|||||||
*cookie = new_cookie;
|
*cookie = new_cookie;
|
||||||
break;
|
break;
|
||||||
case BLK_STS_RESOURCE:
|
case BLK_STS_RESOURCE:
|
||||||
|
case BLK_STS_DEV_RESOURCE:
|
||||||
__blk_mq_requeue_request(rq);
|
__blk_mq_requeue_request(rq);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -1826,7 +1838,7 @@ static void blk_mq_try_issue_directly(struct blk_mq_hw_ctx *hctx,
|
|||||||
hctx_lock(hctx, &srcu_idx);
|
hctx_lock(hctx, &srcu_idx);
|
||||||
|
|
||||||
ret = __blk_mq_try_issue_directly(hctx, rq, cookie, false);
|
ret = __blk_mq_try_issue_directly(hctx, rq, cookie, false);
|
||||||
if (ret == BLK_STS_RESOURCE)
|
if (ret == BLK_STS_RESOURCE || ret == BLK_STS_DEV_RESOURCE)
|
||||||
blk_mq_sched_insert_request(rq, false, true, false);
|
blk_mq_sched_insert_request(rq, false, true, false);
|
||||||
else if (ret != BLK_STS_OK)
|
else if (ret != BLK_STS_OK)
|
||||||
blk_mq_end_request(rq, ret);
|
blk_mq_end_request(rq, ret);
|
||||||
|
@ -1230,7 +1230,7 @@ static blk_status_t null_handle_cmd(struct nullb_cmd *cmd)
|
|||||||
return BLK_STS_OK;
|
return BLK_STS_OK;
|
||||||
} else
|
} else
|
||||||
/* requeue request */
|
/* requeue request */
|
||||||
return BLK_STS_RESOURCE;
|
return BLK_STS_DEV_RESOURCE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ static blk_status_t virtio_queue_rq(struct blk_mq_hw_ctx *hctx,
|
|||||||
/* Out of mem doesn't actually happen, since we fall back
|
/* Out of mem doesn't actually happen, since we fall back
|
||||||
* to direct descriptors */
|
* to direct descriptors */
|
||||||
if (err == -ENOMEM || err == -ENOSPC)
|
if (err == -ENOMEM || err == -ENOSPC)
|
||||||
return BLK_STS_RESOURCE;
|
return BLK_STS_DEV_RESOURCE;
|
||||||
return BLK_STS_IOERR;
|
return BLK_STS_IOERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -911,7 +911,7 @@ out_err:
|
|||||||
out_busy:
|
out_busy:
|
||||||
blk_mq_stop_hw_queue(hctx);
|
blk_mq_stop_hw_queue(hctx);
|
||||||
spin_unlock_irqrestore(&rinfo->ring_lock, flags);
|
spin_unlock_irqrestore(&rinfo->ring_lock, flags);
|
||||||
return BLK_STS_RESOURCE;
|
return BLK_STS_DEV_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void blkif_complete_rq(struct request *rq)
|
static void blkif_complete_rq(struct request *rq)
|
||||||
|
@ -404,7 +404,7 @@ static blk_status_t dm_dispatch_clone_request(struct request *clone, struct requ
|
|||||||
|
|
||||||
clone->start_time = jiffies;
|
clone->start_time = jiffies;
|
||||||
r = blk_insert_cloned_request(clone->q, clone);
|
r = blk_insert_cloned_request(clone->q, clone);
|
||||||
if (r != BLK_STS_OK && r != BLK_STS_RESOURCE)
|
if (r != BLK_STS_OK && r != BLK_STS_RESOURCE && r != BLK_STS_DEV_RESOURCE)
|
||||||
/* must complete clone in terms of original request */
|
/* must complete clone in terms of original request */
|
||||||
dm_complete_request(rq, r);
|
dm_complete_request(rq, r);
|
||||||
return r;
|
return r;
|
||||||
@ -496,7 +496,7 @@ check_again:
|
|||||||
trace_block_rq_remap(clone->q, clone, disk_devt(dm_disk(md)),
|
trace_block_rq_remap(clone->q, clone, disk_devt(dm_disk(md)),
|
||||||
blk_rq_pos(rq));
|
blk_rq_pos(rq));
|
||||||
ret = dm_dispatch_clone_request(clone, rq);
|
ret = dm_dispatch_clone_request(clone, rq);
|
||||||
if (ret == BLK_STS_RESOURCE) {
|
if (ret == BLK_STS_RESOURCE || ret == BLK_STS_DEV_RESOURCE) {
|
||||||
blk_rq_unprep_clone(clone);
|
blk_rq_unprep_clone(clone);
|
||||||
tio->ti->type->release_clone_rq(clone);
|
tio->ti->type->release_clone_rq(clone);
|
||||||
tio->clone = NULL;
|
tio->clone = NULL;
|
||||||
@ -769,7 +769,6 @@ static blk_status_t dm_mq_queue_rq(struct blk_mq_hw_ctx *hctx,
|
|||||||
/* Undo dm_start_request() before requeuing */
|
/* Undo dm_start_request() before requeuing */
|
||||||
rq_end_stats(md, rq);
|
rq_end_stats(md, rq);
|
||||||
rq_completed(md, rq_data_dir(rq), false);
|
rq_completed(md, rq_data_dir(rq), false);
|
||||||
blk_mq_delay_run_hw_queue(hctx, 100/*ms*/);
|
|
||||||
return BLK_STS_RESOURCE;
|
return BLK_STS_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +35,6 @@ enum nvme_fc_queue_flags {
|
|||||||
NVME_FC_Q_LIVE,
|
NVME_FC_Q_LIVE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define NVMEFC_QUEUE_DELAY 3 /* ms units */
|
|
||||||
|
|
||||||
#define NVME_FC_DEFAULT_DEV_LOSS_TMO 60 /* seconds */
|
#define NVME_FC_DEFAULT_DEV_LOSS_TMO 60 /* seconds */
|
||||||
|
|
||||||
struct nvme_fc_queue {
|
struct nvme_fc_queue {
|
||||||
@ -2231,7 +2229,7 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
|
|||||||
* the target device is present
|
* the target device is present
|
||||||
*/
|
*/
|
||||||
if (ctrl->rport->remoteport.port_state != FC_OBJSTATE_ONLINE)
|
if (ctrl->rport->remoteport.port_state != FC_OBJSTATE_ONLINE)
|
||||||
goto busy;
|
return BLK_STS_RESOURCE;
|
||||||
|
|
||||||
if (!nvme_fc_ctrl_get(ctrl))
|
if (!nvme_fc_ctrl_get(ctrl))
|
||||||
return BLK_STS_IOERR;
|
return BLK_STS_IOERR;
|
||||||
@ -2311,16 +2309,10 @@ nvme_fc_start_fcp_op(struct nvme_fc_ctrl *ctrl, struct nvme_fc_queue *queue,
|
|||||||
ret != -EBUSY)
|
ret != -EBUSY)
|
||||||
return BLK_STS_IOERR;
|
return BLK_STS_IOERR;
|
||||||
|
|
||||||
goto busy;
|
return BLK_STS_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BLK_STS_OK;
|
return BLK_STS_OK;
|
||||||
|
|
||||||
busy:
|
|
||||||
if (!(op->flags & FCOP_FLAGS_AEN) && queue->hctx)
|
|
||||||
blk_mq_delay_run_hw_queue(queue->hctx, NVMEFC_QUEUE_DELAY);
|
|
||||||
|
|
||||||
return BLK_STS_RESOURCE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline blk_status_t nvme_fc_is_ready(struct nvme_fc_queue *queue,
|
static inline blk_status_t nvme_fc_is_ready(struct nvme_fc_queue *queue,
|
||||||
|
@ -2030,9 +2030,9 @@ out_put_budget:
|
|||||||
case BLK_STS_OK:
|
case BLK_STS_OK:
|
||||||
break;
|
break;
|
||||||
case BLK_STS_RESOURCE:
|
case BLK_STS_RESOURCE:
|
||||||
if (atomic_read(&sdev->device_busy) == 0 &&
|
if (atomic_read(&sdev->device_busy) ||
|
||||||
!scsi_device_blocked(sdev))
|
scsi_device_blocked(sdev))
|
||||||
blk_mq_delay_run_hw_queue(hctx, SCSI_QUEUE_DELAY);
|
ret = BLK_STS_DEV_RESOURCE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
|
@ -39,6 +39,24 @@ typedef u8 __bitwise blk_status_t;
|
|||||||
|
|
||||||
#define BLK_STS_AGAIN ((__force blk_status_t)12)
|
#define BLK_STS_AGAIN ((__force blk_status_t)12)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BLK_STS_DEV_RESOURCE is returned from the driver to the block layer if
|
||||||
|
* device related resources are unavailable, but the driver can guarantee
|
||||||
|
* that the queue will be rerun in the future once resources become
|
||||||
|
* available again. This is typically the case for device specific
|
||||||
|
* resources that are consumed for IO. If the driver fails allocating these
|
||||||
|
* resources, we know that inflight (or pending) IO will free these
|
||||||
|
* resource upon completion.
|
||||||
|
*
|
||||||
|
* This is different from BLK_STS_RESOURCE in that it explicitly references
|
||||||
|
* a device specific resource. For resources of wider scope, allocation
|
||||||
|
* failure can happen without having pending IO. This means that we can't
|
||||||
|
* rely on request completions freeing these resources, as IO may not be in
|
||||||
|
* flight. Examples of that are kernel memory allocations, DMA mappings, or
|
||||||
|
* any other system wide resources.
|
||||||
|
*/
|
||||||
|
#define BLK_STS_DEV_RESOURCE ((__force blk_status_t)13)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* blk_path_error - returns true if error may be path related
|
* blk_path_error - returns true if error may be path related
|
||||||
* @error: status the request was completed with
|
* @error: status the request was completed with
|
||||||
|
Loading…
Reference in New Issue
Block a user