null_blk: Fix changing the number of hardware queues
[platform/kernel/linux-starfive.git] / drivers / block / null_blk_main.c
index b8a08b8..83e31b8 100644 (file)
@@ -302,6 +302,12 @@ static int nullb_apply_submit_queues(struct nullb_device *dev,
        if (!nullb)
                return 0;
 
+       /*
+        * Make sure that null_init_hctx() does not access nullb->queues[] past
+        * the end of that array.
+        */
+       if (submit_queues > nr_cpu_ids)
+               return -EINVAL;
        set = nullb->tag_set;
        blk_mq_update_nr_hw_queues(set, submit_queues);
        return set->nr_hw_queues == submit_queues ? 0 : -ENOMEM;
@@ -1408,12 +1414,6 @@ static blk_status_t null_queue_rq(struct blk_mq_hw_ctx *hctx,
        return null_handle_cmd(cmd, sector, nr_sectors, req_op(bd->rq));
 }
 
-static const struct blk_mq_ops null_mq_ops = {
-       .queue_rq       = null_queue_rq,
-       .complete       = null_complete_rq,
-       .timeout        = null_timeout_rq,
-};
-
 static void cleanup_queue(struct nullb_queue *nq)
 {
        kfree(nq->tag_map);
@@ -1430,6 +1430,43 @@ static void cleanup_queues(struct nullb *nullb)
        kfree(nullb->queues);
 }
 
+static void null_exit_hctx(struct blk_mq_hw_ctx *hctx, unsigned int hctx_idx)
+{
+       struct nullb_queue *nq = hctx->driver_data;
+       struct nullb *nullb = nq->dev->nullb;
+
+       nullb->nr_queues--;
+}
+
+static void null_init_queue(struct nullb *nullb, struct nullb_queue *nq)
+{
+       init_waitqueue_head(&nq->wait);
+       nq->queue_depth = nullb->queue_depth;
+       nq->dev = nullb->dev;
+}
+
+static int null_init_hctx(struct blk_mq_hw_ctx *hctx, void *driver_data,
+                         unsigned int hctx_idx)
+{
+       struct nullb *nullb = hctx->queue->queuedata;
+       struct nullb_queue *nq;
+
+       nq = &nullb->queues[hctx_idx];
+       hctx->driver_data = nq;
+       null_init_queue(nullb, nq);
+       nullb->nr_queues++;
+
+       return 0;
+}
+
+static const struct blk_mq_ops null_mq_ops = {
+       .queue_rq       = null_queue_rq,
+       .complete       = null_complete_rq,
+       .timeout        = null_timeout_rq,
+       .init_hctx      = null_init_hctx,
+       .exit_hctx      = null_exit_hctx,
+};
+
 static void null_del_dev(struct nullb *nullb)
 {
        struct nullb_device *dev = nullb->dev;
@@ -1473,33 +1510,6 @@ static const struct block_device_operations null_ops = {
        .report_zones   = null_report_zones,
 };
 
-static void null_init_queue(struct nullb *nullb, struct nullb_queue *nq)
-{
-       BUG_ON(!nullb);
-       BUG_ON(!nq);
-
-       init_waitqueue_head(&nq->wait);
-       nq->queue_depth = nullb->queue_depth;
-       nq->dev = nullb->dev;
-}
-
-static void null_init_queues(struct nullb *nullb)
-{
-       struct request_queue *q = nullb->q;
-       struct blk_mq_hw_ctx *hctx;
-       struct nullb_queue *nq;
-       int i;
-
-       queue_for_each_hw_ctx(q, hctx, i) {
-               if (!hctx->nr_ctx || !hctx->tags)
-                       continue;
-               nq = &nullb->queues[i];
-               hctx->driver_data = nq;
-               null_init_queue(nullb, nq);
-               nullb->nr_queues++;
-       }
-}
-
 static int setup_commands(struct nullb_queue *nq)
 {
        struct nullb_cmd *cmd;
@@ -1526,8 +1536,7 @@ static int setup_commands(struct nullb_queue *nq)
 
 static int setup_queues(struct nullb *nullb)
 {
-       nullb->queues = kcalloc(nullb->dev->submit_queues,
-                               sizeof(struct nullb_queue),
+       nullb->queues = kcalloc(nr_cpu_ids, sizeof(struct nullb_queue),
                                GFP_KERNEL);
        if (!nullb->queues)
                return -ENOMEM;
@@ -1673,6 +1682,27 @@ static bool null_setup_fault(void)
        return true;
 }
 
+/*
+ * This function is identical to blk_mq_init_queue() except that it sets
+ * queuedata before .init_hctx is called.
+ */
+static struct request_queue *nullb_alloc_queue(struct nullb *nullb)
+{
+       struct request_queue *uninit_q, *q;
+       struct blk_mq_tag_set *set = nullb->tag_set;
+
+       uninit_q = blk_alloc_queue_node(GFP_KERNEL, set->numa_node);
+       if (!uninit_q)
+               return ERR_PTR(-ENOMEM);
+
+       uninit_q->queuedata = nullb;
+       q = blk_mq_init_allocated_queue(set, uninit_q, false);
+       if (IS_ERR(q))
+               blk_cleanup_queue(uninit_q);
+
+       return q;
+}
+
 static int null_add_dev(struct nullb_device *dev)
 {
        struct nullb *nullb;
@@ -1712,12 +1742,11 @@ static int null_add_dev(struct nullb_device *dev)
                        goto out_cleanup_queues;
 
                nullb->tag_set->timeout = 5 * HZ;
-               nullb->q = blk_mq_init_queue(nullb->tag_set);
+               nullb->q = nullb_alloc_queue(nullb);
                if (IS_ERR(nullb->q)) {
                        rv = -ENOMEM;
                        goto out_cleanup_tags;
                }
-               null_init_queues(nullb);
        } else if (dev->queue_mode == NULL_Q_BIO) {
                nullb->q = blk_alloc_queue_node(GFP_KERNEL, dev->home_node);
                if (!nullb->q) {