Freezing the request queue from inside sysfs store callbacks may cause a deadlock in combination with the dm-multipath driver and the queue_if_no_path option. Fix this by restricting how long to wait until the queue is frozen inside the remaining sysfs callback methods that freeze the request queue. Cc: Christoph Hellwig <hch@xxxxxx> Cc: Nilay Shroff <nilay@xxxxxxxxxxxxx> Fixes: af2814149883 ("block: freeze the queue in queue_attr_store") Signed-off-by: Bart Van Assche <bvanassche@xxxxxxx> --- block/blk-mq.c | 14 ++++++++++++++ block/blk-settings.c | 32 ++++++++++++++++++++++++++++++++ block/blk-sysfs.c | 19 +++++++++++-------- include/linux/blk-mq.h | 2 ++ include/linux/blkdev.h | 2 ++ 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index 532acdbe9e16..23fd8db663d9 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -191,6 +191,7 @@ void blk_mq_freeze_queue_wait(struct request_queue *q) } EXPORT_SYMBOL_GPL(blk_mq_freeze_queue_wait); +/* Returns > 0 upon success; 0 upon failure. */ int blk_mq_freeze_queue_wait_timeout(struct request_queue *q, unsigned long timeout) { @@ -207,6 +208,19 @@ void blk_mq_freeze_queue_nomemsave(struct request_queue *q) } EXPORT_SYMBOL_GPL(blk_mq_freeze_queue_nomemsave); +/* + * Try to freeze @q. Returns 0 if successful or a negative value if freezing @q + * did not succeed before the timeout expired. + */ +int blk_mq_freeze_queue_nomemsave_timeout(struct request_queue *q, + unsigned long timeout) +{ + blk_freeze_queue_start(q); + if (blk_mq_freeze_queue_wait_timeout(q, timeout) <= 0) + return -EAGAIN; + return 0; +} + bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic) { bool unfreeze; diff --git a/block/blk-settings.c b/block/blk-settings.c index a000daafbfb4..93225762a40d 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -487,6 +487,38 @@ int queue_limits_commit_update_frozen(struct request_queue *q, } EXPORT_SYMBOL_GPL(queue_limits_commit_update_frozen); +/** + * queue_limits_commit_update_frozen_timeout - commit an atomic update of queue + * limits + * @q: queue to update + * @lim: limits to apply + * @timeout: maximum time to wait until @q is frozen + * + * Apply the limits in @lim that were obtained from queue_limits_start_update() + * and updated with the new values by the caller to @q. Freezes the queue + * before the update and unfreezes it after. + * + * Returns 0 if successful, else a negative error code. + */ +int queue_limits_commit_update_frozen_timeout(struct request_queue *q, + struct queue_limits *lim, unsigned long timeout) +{ + unsigned int memflags; + int ret; + + memflags = memalloc_noio_save(); + ret = blk_mq_freeze_queue_nomemsave_timeout(q, timeout); + if (ret < 0) { + memalloc_flags_restore(memflags); + queue_limits_cancel_update(q); + return ret; + } + ret = queue_limits_commit_update(q, lim); + blk_mq_unfreeze_queue(q, memflags); + + return ret; +} + /** * queue_limits_set - apply queue limits to queue * @q: queue to update diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c index ab34fe62f4da..c75901a55497 100644 --- a/block/blk-sysfs.c +++ b/block/blk-sysfs.c @@ -65,7 +65,7 @@ static ssize_t queue_requests_store(struct gendisk *disk, const char *page, size_t count) { unsigned long nr; - int ret, err; + int ret; unsigned int memflags; struct request_queue *q = disk->queue; @@ -76,17 +76,18 @@ queue_requests_store(struct gendisk *disk, const char *page, size_t count) if (ret < 0) return ret; - memflags = blk_mq_freeze_queue(q); + memflags = memalloc_noio_save(); + ret = blk_mq_freeze_queue_nomemsave_timeout(q, q->rq_timeout); + if (ret < 0) + return ret; mutex_lock(&q->elevator_lock); if (nr < BLKDEV_MIN_RQ) nr = BLKDEV_MIN_RQ; - err = blk_mq_update_nr_requests(disk->queue, nr); - if (err) - ret = err; + ret = blk_mq_update_nr_requests(disk->queue, nr); mutex_unlock(&q->elevator_lock); blk_mq_unfreeze_queue(q, memflags); - return ret; + return ret < 0 ? ret : count; } static ssize_t queue_ra_show(struct gendisk *disk, char *page) @@ -582,7 +583,8 @@ static ssize_t queue_wb_lat_store(struct gendisk *disk, const char *page, if (val < -1) return -EINVAL; - memflags = blk_mq_freeze_queue(q); + memflags = memalloc_noio_save(); + ret = blk_mq_freeze_queue_nomemsave_timeout(q, q->rq_timeout); rqos = wbt_rq_qos(q); if (!rqos) { @@ -782,7 +784,8 @@ queue_attr_store(struct kobject *kobj, struct attribute *attr, return res; } - res = queue_limits_commit_update_frozen(q, &lim); + res = queue_limits_commit_update_frozen_timeout(q, &lim, + q->rq_timeout); if (res) return res; return length; diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h index 2a5a828f19a0..6665de1cd75e 100644 --- a/include/linux/blk-mq.h +++ b/include/linux/blk-mq.h @@ -925,6 +925,8 @@ void blk_mq_tagset_busy_iter(struct blk_mq_tag_set *tagset, busy_tag_iter_fn *fn, void *priv); void blk_mq_tagset_wait_completed_request(struct blk_mq_tag_set *tagset); void blk_mq_freeze_queue_nomemsave(struct request_queue *q); +int blk_mq_freeze_queue_nomemsave_timeout(struct request_queue *q, + unsigned long timeout); void blk_mq_unfreeze_queue_nomemrestore(struct request_queue *q); static inline unsigned int __must_check blk_mq_freeze_queue(struct request_queue *q) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index a51f92b6c340..dc00a0f4bd28 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1059,6 +1059,8 @@ queue_limits_start_update(struct request_queue *q) } int queue_limits_commit_update_frozen(struct request_queue *q, struct queue_limits *lim); +int queue_limits_commit_update_frozen_timeout(struct request_queue *q, + struct queue_limits *lim, unsigned long timeout); int queue_limits_commit_update(struct request_queue *q, struct queue_limits *lim); int queue_limits_set(struct request_queue *q, struct queue_limits *lim);