struct elv_change_ctx {
        const char *name;
        bool no_uevent;
+
+       /* for unregistering old elevator */
+       struct elevator_queue *old;
+       /* for registering new elevator */
+       struct elevator_queue *new;
 };
 
 static DEFINE_SPINLOCK(elv_list_lock);
 {
        struct elevator_queue *e = q->elevator;
 
+       lockdep_assert_held(&q->elevator_lock);
+
        ioc_clear_queue(q);
        blk_mq_sched_free_rqs(q);
 
        mutex_lock(&e->sysfs_lock);
        blk_mq_exit_sched(q, e);
        mutex_unlock(&e->sysfs_lock);
-
-       kobject_put(&e->kobj);
 }
 
 static inline void __elv_rqhash_del(struct request *rq)
 {
        int error;
 
-       lockdep_assert_held(&q->elevator_lock);
-
        error = kobject_add(&e->kobj, &q->disk->queue_kobj, "iosched");
        if (!error) {
                const struct elv_fs_entry *attr = e->type->elevator_attrs;
 static void elv_unregister_queue(struct request_queue *q,
                                 struct elevator_queue *e)
 {
-       lockdep_assert_held(&q->elevator_lock);
-
        if (e && test_and_clear_bit(ELEVATOR_FLAG_REGISTERED, &e->flags)) {
                kobject_uevent(&e->kobj, KOBJ_REMOVE);
                kobject_del(&e->kobj);
        blk_mq_quiesce_queue(q);
 
        if (q->elevator) {
-               elv_unregister_queue(q, q->elevator);
+               ctx->old = q->elevator;
                elevator_exit(q);
        }
 
                ret = blk_mq_init_sched(q, new_e);
                if (ret)
                        goto out_unfreeze;
-               ret = elv_register_queue(q, q->elevator, !ctx->no_uevent);
-               if (ret) {
-                       elevator_exit(q);
-                       goto out_unfreeze;
-               }
+               ctx->new = q->elevator;
        } else {
                blk_queue_flag_clear(QUEUE_FLAG_SQ_SCHED, q);
                q->elevator = NULL;
        return ret;
 }
 
+static void elv_exit_and_release(struct request_queue *q)
+{
+       struct elevator_queue *e;
+       unsigned memflags;
+
+       memflags = blk_mq_freeze_queue(q);
+       mutex_lock(&q->elevator_lock);
+       e = q->elevator;
+       elevator_exit(q);
+       mutex_unlock(&q->elevator_lock);
+       blk_mq_unfreeze_queue(q, memflags);
+       if (e)
+               kobject_put(&e->kobj);
+}
+
+static int elevator_change_done(struct request_queue *q,
+                               struct elv_change_ctx *ctx)
+{
+       int ret = 0;
+
+       if (ctx->old) {
+               elv_unregister_queue(q, ctx->old);
+               kobject_put(&ctx->old->kobj);
+       }
+       if (ctx->new) {
+               ret = elv_register_queue(q, ctx->new, !ctx->no_uevent);
+               if (ret)
+                       elv_exit_and_release(q);
+       }
+       return ret;
+}
+
 /*
  * Switch this queue to the given IO scheduler.
  */
                ret = elevator_switch(q, ctx);
        mutex_unlock(&q->elevator_lock);
        blk_mq_unfreeze_queue(q, memflags);
+       if (!ret)
+               ret = elevator_change_done(q, ctx);
+
        return ret;
 }
 
  */
 void elv_update_nr_hw_queues(struct request_queue *q)
 {
+       struct elv_change_ctx ctx = {};
+       int ret = -ENODEV;
+
        WARN_ON_ONCE(q->mq_freeze_depth == 0);
 
        mutex_lock(&q->elevator_lock);
        if (q->elevator && !blk_queue_dying(q) && !blk_queue_registered(q)) {
-               struct elv_change_ctx ctx = {
-                       .name = q->elevator->type->elevator_name,
-               };
+               ctx.name = q->elevator->type->elevator_name;
 
                /* force to reattach elevator after nr_hw_queue is updated */
-               elevator_switch(q, &ctx);
+               ret = elevator_switch(q, &ctx);
        }
        mutex_unlock(&q->elevator_lock);
+       blk_mq_unfreeze_queue_nomemrestore(q);
+       if (!ret)
+               WARN_ON_ONCE(elevator_change_done(q, &ctx));
 }
 
 /*