]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
blk-mq: fix potential deadlock while nr_requests grown
authorYu Kuai <yukuai3@huawei.com>
Wed, 10 Sep 2025 08:04:43 +0000 (16:04 +0800)
committerJens Axboe <axboe@kernel.dk>
Wed, 10 Sep 2025 11:25:56 +0000 (05:25 -0600)
Allocate and free sched_tags while queue is freezed can deadlock[1],
this is a long term problem, hence allocate memory before freezing
queue and free memory after queue is unfreezed.

[1] https://lore.kernel.org/all/0659ea8d-a463-47c8-9180-43c719e106eb@linux.ibm.com/
Fixes: e3a2b3f931f5 ("blk-mq: allow changing of queue depth through sysfs")
Signed-off-by: Yu Kuai <yukuai3@huawei.com>
Reviewed-by: Nilay Shroff <nilay@linux.ibm.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
block/blk-mq.c
block/blk-mq.h
block/blk-sysfs.c

index 299aac4581855e967edc80e3a9e39ccf8850cf0b..4ccf11cadf8c7880965cd9db5e62760b38162c03 100644 (file)
@@ -4926,11 +4926,13 @@ void blk_mq_free_tag_set(struct blk_mq_tag_set *set)
 }
 EXPORT_SYMBOL(blk_mq_free_tag_set);
 
-int blk_mq_update_nr_requests(struct request_queue *q, unsigned int nr)
+struct elevator_tags *blk_mq_update_nr_requests(struct request_queue *q,
+                                               struct elevator_tags *et,
+                                               unsigned int nr)
 {
        struct blk_mq_tag_set *set = q->tag_set;
+       struct elevator_tags *old_et = NULL;
        struct blk_mq_hw_ctx *hctx;
-       int ret = 0;
        unsigned long i;
 
        blk_mq_quiesce_queue(q);
@@ -4965,24 +4967,18 @@ int blk_mq_update_nr_requests(struct request_queue *q, unsigned int nr)
                }
        } else {
                /* Non-shared sched tags, and tags grow */
-               queue_for_each_hw_ctx(q, hctx, i) {
-                       if (!hctx->sched_tags)
-                               continue;
-                       ret = blk_mq_tag_update_depth(hctx, &hctx->sched_tags,
-                                                     nr);
-                       if (ret)
-                               goto out;
-               }
+               queue_for_each_hw_ctx(q, hctx, i)
+                       hctx->sched_tags = et->tags[i];
+               old_et =  q->elevator->et;
+               q->elevator->et = et;
        }
 
        q->nr_requests = nr;
        if (q->elevator && q->elevator->type->ops.depth_updated)
                q->elevator->type->ops.depth_updated(q);
 
-out:
        blk_mq_unquiesce_queue(q);
-
-       return ret;
+       return old_et;
 }
 
 /*
index 3a1d4c37d1bc58b2900c4f61869a2a29fbc613d7..0ef96f51047787bd29ac2bb3a56f82f6f5d3a1b9 100644 (file)
@@ -6,6 +6,7 @@
 #include "blk-stat.h"
 
 struct blk_mq_tag_set;
+struct elevator_tags;
 
 struct blk_mq_ctxs {
        struct kobject kobj;
@@ -45,7 +46,9 @@ void blk_mq_submit_bio(struct bio *bio);
 int blk_mq_poll(struct request_queue *q, blk_qc_t cookie, struct io_comp_batch *iob,
                unsigned int flags);
 void blk_mq_exit_queue(struct request_queue *q);
-int blk_mq_update_nr_requests(struct request_queue *q, unsigned int nr);
+struct elevator_tags *blk_mq_update_nr_requests(struct request_queue *q,
+                                               struct elevator_tags *tags,
+                                               unsigned int nr);
 void blk_mq_wake_waiters(struct request_queue *q);
 bool blk_mq_dispatch_rq_list(struct blk_mq_hw_ctx *hctx, struct list_head *,
                             bool);
index ff66d1b471699555ff1112b83ae0a707d448c7da..76c47fe9b8d68c971f015b4c62a7b84a4cf6b22e 100644 (file)
@@ -64,11 +64,12 @@ static ssize_t queue_requests_show(struct gendisk *disk, char *page)
 static ssize_t
 queue_requests_store(struct gendisk *disk, const char *page, size_t count)
 {
-       unsigned long nr;
-       int ret, err;
-       unsigned int memflags;
        struct request_queue *q = disk->queue;
        struct blk_mq_tag_set *set = q->tag_set;
+       struct elevator_tags *et = NULL;
+       unsigned int memflags;
+       unsigned long nr;
+       int ret;
 
        ret = queue_var_store(&nr, page, count);
        if (ret < 0)
@@ -99,16 +100,28 @@ queue_requests_store(struct gendisk *disk, const char *page, size_t count)
                goto unlock;
        }
 
+       if (!blk_mq_is_shared_tags(set->flags) && q->elevator &&
+           nr > q->elevator->et->nr_requests) {
+               /*
+                * Tags will grow, allocate memory before freezing queue to
+                * prevent deadlock.
+                */
+               et = blk_mq_alloc_sched_tags(set, q->nr_hw_queues, nr);
+               if (!et) {
+                       ret = -ENOMEM;
+                       goto unlock;
+               }
+       }
+
        memflags = blk_mq_freeze_queue(q);
        mutex_lock(&q->elevator_lock);
-
-       err = blk_mq_update_nr_requests(disk->queue, nr);
-       if (err)
-               ret = err;
-
+       et = blk_mq_update_nr_requests(q, et, nr);
        mutex_unlock(&q->elevator_lock);
        blk_mq_unfreeze_queue(q, memflags);
 
+       if (et)
+               blk_mq_free_sched_tags(et, set);
+
 unlock:
        up_write(&set->update_nr_hwq_lock);
        return ret;