Linux 6.9-rc1
[linux-2.6-microblaze.git] / block / elevator.c
index c319765..5ff093c 100644 (file)
@@ -57,7 +57,7 @@ static LIST_HEAD(elv_list);
  * Query io scheduler to see if the current process issuing bio may be
  * merged with rq.
  */
-static int elv_iosched_allow_bio_merge(struct request *rq, struct bio *bio)
+static bool elv_iosched_allow_bio_merge(struct request *rq, struct bio *bio)
 {
        struct request_queue *q = rq->q;
        struct elevator_queue *e = q->elevator;
@@ -65,7 +65,7 @@ static int elv_iosched_allow_bio_merge(struct request *rq, struct bio *bio)
        if (e->type->ops.allow_merge)
                return e->type->ops.allow_merge(q, rq, bio);
 
-       return 1;
+       return true;
 }
 
 /*
@@ -83,83 +83,50 @@ bool elv_bio_merge_ok(struct request *rq, struct bio *bio)
 }
 EXPORT_SYMBOL(elv_bio_merge_ok);
 
-static inline bool elv_support_features(unsigned int elv_features,
-                                       unsigned int required_features)
+static inline bool elv_support_features(struct request_queue *q,
+               const struct elevator_type *e)
 {
-       return (required_features & elv_features) == required_features;
+       return (q->required_elevator_features & e->elevator_features) ==
+               q->required_elevator_features;
 }
 
 /**
- * elevator_match - Test an elevator name and features
+ * elevator_match - Check whether @e's name or alias matches @name
  * @e: Scheduler to test
  * @name: Elevator name to test
- * @required_features: Features that the elevator must provide
  *
- * Return true if the elevator @e name matches @name and if @e provides all
- * the features specified by @required_features.
+ * Return true if the elevator @e's name or alias matches @name.
  */
-static bool elevator_match(const struct elevator_type *e, const char *name,
-                          unsigned int required_features)
+static bool elevator_match(const struct elevator_type *e, const char *name)
 {
-       if (!elv_support_features(e->elevator_features, required_features))
-               return false;
-       if (!strcmp(e->elevator_name, name))
-               return true;
-       if (e->elevator_alias && !strcmp(e->elevator_alias, name))
-               return true;
-
-       return false;
+       return !strcmp(e->elevator_name, name) ||
+               (e->elevator_alias && !strcmp(e->elevator_alias, name));
 }
 
-/**
- * elevator_find - Find an elevator
- * @name: Name of the elevator to find
- * @required_features: Features that the elevator must provide
- *
- * Return the first registered scheduler with name @name and supporting the
- * features @required_features and NULL otherwise.
- */
-static struct elevator_type *elevator_find(const char *name,
-                                          unsigned int required_features)
+static struct elevator_type *__elevator_find(const char *name)
 {
        struct elevator_type *e;
 
-       list_for_each_entry(e, &elv_list, list) {
-               if (elevator_match(e, name, required_features))
+       list_for_each_entry(e, &elv_list, list)
+               if (elevator_match(e, name))
                        return e;
-       }
-
        return NULL;
 }
 
-static void elevator_put(struct elevator_type *e)
-{
-       module_put(e->elevator_owner);
-}
-
-static struct elevator_type *elevator_get(struct request_queue *q,
-                                         const char *name, bool try_loading)
+static struct elevator_type *elevator_find_get(struct request_queue *q,
+               const char *name)
 {
        struct elevator_type *e;
 
        spin_lock(&elv_list_lock);
-
-       e = elevator_find(name, q->required_elevator_features);
-       if (!e && try_loading) {
-               spin_unlock(&elv_list_lock);
-               request_module("%s-iosched", name);
-               spin_lock(&elv_list_lock);
-               e = elevator_find(name, q->required_elevator_features);
-       }
-
-       if (e && !try_module_get(e->elevator_owner))
+       e = __elevator_find(name);
+       if (e && (!elv_support_features(q, e) || !elevator_tryget(e)))
                e = NULL;
-
        spin_unlock(&elv_list_lock);
        return e;
 }
 
-static struct kobj_type elv_ktype;
+static const struct kobj_type elv_ktype;
 
 struct elevator_queue *elevator_alloc(struct request_queue *q,
                                  struct elevator_type *e)
@@ -170,6 +137,7 @@ struct elevator_queue *elevator_alloc(struct request_queue *q,
        if (unlikely(!eq))
                return NULL;
 
+       __elevator_get(e);
        eq->type = e;
        kobject_init(&eq->kobj, &elv_ktype);
        mutex_init(&eq->sysfs_lock);
@@ -487,7 +455,7 @@ static const struct sysfs_ops elv_sysfs_ops = {
        .store  = elv_attr_store,
 };
 
-static struct kobj_type elv_ktype = {
+static const struct kobj_type elv_ktype = {
        .sysfs_ops      = &elv_sysfs_ops,
        .release        = elevator_release,
 };
@@ -499,7 +467,7 @@ int elv_register_queue(struct request_queue *q, bool uevent)
 
        lockdep_assert_held(&q->sysfs_lock);
 
-       error = kobject_add(&e->kobj, &q->kobj, "%s", "iosched");
+       error = kobject_add(&e->kobj, &q->disk->queue_kobj, "iosched");
        if (!error) {
                struct elv_fs_entry *attr = e->type->elevator_attrs;
                if (attr) {
@@ -512,7 +480,7 @@ int elv_register_queue(struct request_queue *q, bool uevent)
                if (uevent)
                        kobject_uevent(&e->kobj, KOBJ_ADD);
 
-               e->registered = 1;
+               set_bit(ELEVATOR_FLAG_REGISTERED, &e->flags);
        }
        return error;
 }
@@ -523,18 +491,17 @@ void elv_unregister_queue(struct request_queue *q)
 
        lockdep_assert_held(&q->sysfs_lock);
 
-       if (e && e->registered) {
-               struct elevator_queue *e = q->elevator;
-
+       if (e && test_and_clear_bit(ELEVATOR_FLAG_REGISTERED, &e->flags)) {
                kobject_uevent(&e->kobj, KOBJ_REMOVE);
                kobject_del(&e->kobj);
-
-               e->registered = 0;
        }
 }
 
 int elv_register(struct elevator_type *e)
 {
+       /* finish request is mandatory */
+       if (WARN_ON_ONCE(!e->ops.finish_request))
+               return -EINVAL;
        /* insert_requests and dispatch_request are mandatory */
        if (WARN_ON_ONCE(!e->ops.insert_requests || !e->ops.dispatch_request))
                return -EINVAL;
@@ -555,7 +522,7 @@ int elv_register(struct elevator_type *e)
 
        /* register, don't allow duplicate names */
        spin_lock(&elv_list_lock);
-       if (elevator_find(e->elevator_name, 0)) {
+       if (__elevator_find(e->elevator_name)) {
                spin_unlock(&elv_list_lock);
                kmem_cache_destroy(e->icq_cache);
                return -EBUSY;
@@ -588,39 +555,6 @@ void elv_unregister(struct elevator_type *e)
 }
 EXPORT_SYMBOL_GPL(elv_unregister);
 
-int elevator_switch_mq(struct request_queue *q,
-                             struct elevator_type *new_e)
-{
-       int ret;
-
-       lockdep_assert_held(&q->sysfs_lock);
-
-       if (q->elevator) {
-               elv_unregister_queue(q);
-               elevator_exit(q);
-       }
-
-       ret = blk_mq_init_sched(q, new_e);
-       if (ret)
-               goto out;
-
-       if (new_e) {
-               ret = elv_register_queue(q, true);
-               if (ret) {
-                       elevator_exit(q);
-                       goto out;
-               }
-       }
-
-       if (new_e)
-               blk_add_trace_msg(q, "elv switch: %s", new_e->elevator_name);
-       else
-               blk_add_trace_msg(q, "elv switch: none");
-
-out:
-       return ret;
-}
-
 static inline bool elv_support_iosched(struct request_queue *q)
 {
        if (!queue_is_mq(q) ||
@@ -642,7 +576,7 @@ static struct elevator_type *elevator_get_default(struct request_queue *q)
            !blk_mq_is_shared_tags(q->tag_set->flags))
                return NULL;
 
-       return elevator_get(q, "mq-deadline", false);
+       return elevator_find_get(q, "mq-deadline");
 }
 
 /*
@@ -656,14 +590,13 @@ static struct elevator_type *elevator_get_by_features(struct request_queue *q)
        spin_lock(&elv_list_lock);
 
        list_for_each_entry(e, &elv_list, list) {
-               if (elv_support_features(e->elevator_features,
-                                        q->required_elevator_features)) {
+               if (elv_support_features(q, e)) {
                        found = e;
                        break;
                }
        }
 
-       if (found && !try_module_get(found->elevator_owner))
+       if (found && !elevator_tryget(found))
                found = NULL;
 
        spin_unlock(&elv_list_lock);
@@ -713,115 +646,147 @@ void elevator_init_mq(struct request_queue *q)
        if (err) {
                pr_warn("\"%s\" elevator initialization failed, "
                        "falling back to \"none\"\n", e->elevator_name);
-               elevator_put(e);
        }
+
+       elevator_put(e);
 }
 
 /*
- * switch to new_e io scheduler. be careful not to introduce deadlocks -
- * we don't free the old io scheduler, before we have allocated what we
- * need for the new one. this way we have a chance of going back to the old
- * one, if the new one fails init for some reason.
+ * Switch to new_e io scheduler.
+ *
+ * If switching fails, we are most likely running out of memory and not able
+ * to restore the old io scheduler, so leaving the io scheduler being none.
  */
-static int elevator_switch(struct request_queue *q, struct elevator_type *new_e)
+int elevator_switch(struct request_queue *q, struct elevator_type *new_e)
 {
-       int err;
+       int ret;
 
        lockdep_assert_held(&q->sysfs_lock);
 
        blk_mq_freeze_queue(q);
        blk_mq_quiesce_queue(q);
 
-       err = elevator_switch_mq(q, new_e);
+       if (q->elevator) {
+               elv_unregister_queue(q);
+               elevator_exit(q);
+       }
+
+       ret = blk_mq_init_sched(q, new_e);
+       if (ret)
+               goto out_unfreeze;
+
+       ret = elv_register_queue(q, true);
+       if (ret) {
+               elevator_exit(q);
+               goto out_unfreeze;
+       }
+       blk_add_trace_msg(q, "elv switch: %s", new_e->elevator_name);
 
+out_unfreeze:
        blk_mq_unquiesce_queue(q);
        blk_mq_unfreeze_queue(q);
 
-       return err;
+       if (ret) {
+               pr_warn("elv: switch to \"%s\" failed, falling back to \"none\"\n",
+                       new_e->elevator_name);
+       }
+
+       return ret;
+}
+
+void elevator_disable(struct request_queue *q)
+{
+       lockdep_assert_held(&q->sysfs_lock);
+
+       blk_mq_freeze_queue(q);
+       blk_mq_quiesce_queue(q);
+
+       elv_unregister_queue(q);
+       elevator_exit(q);
+       blk_queue_flag_clear(QUEUE_FLAG_SQ_SCHED, q);
+       q->elevator = NULL;
+       q->nr_requests = q->tag_set->queue_depth;
+       blk_add_trace_msg(q, "elv switch: none");
+
+       blk_mq_unquiesce_queue(q);
+       blk_mq_unfreeze_queue(q);
 }
 
 /*
  * Switch this queue to the given IO scheduler.
  */
-static int __elevator_change(struct request_queue *q, const char *name)
+static int elevator_change(struct request_queue *q, const char *elevator_name)
 {
-       char elevator_name[ELV_NAME_MAX];
        struct elevator_type *e;
+       int ret;
 
        /* Make sure queue is not in the middle of being removed */
        if (!blk_queue_registered(q))
                return -ENOENT;
 
-       /*
-        * Special case for mq, turn off scheduling
-        */
-       if (!strncmp(name, "none", 4)) {
-               if (!q->elevator)
-                       return 0;
-               return elevator_switch(q, NULL);
+       if (!strncmp(elevator_name, "none", 4)) {
+               if (q->elevator)
+                       elevator_disable(q);
+               return 0;
        }
 
-       strlcpy(elevator_name, name, sizeof(elevator_name));
-       e = elevator_get(q, strstrip(elevator_name), true);
-       if (!e)
-               return -EINVAL;
-
-       if (q->elevator &&
-           elevator_match(q->elevator->type, elevator_name, 0)) {
-               elevator_put(e);
+       if (q->elevator && elevator_match(q->elevator->type, elevator_name))
                return 0;
-       }
 
-       return elevator_switch(q, e);
+       e = elevator_find_get(q, elevator_name);
+       if (!e) {
+               request_module("%s-iosched", elevator_name);
+               e = elevator_find_get(q, elevator_name);
+               if (!e)
+                       return -EINVAL;
+       }
+       ret = elevator_switch(q, e);
+       elevator_put(e);
+       return ret;
 }
 
-ssize_t elv_iosched_store(struct request_queue *q, const char *name,
+ssize_t elv_iosched_store(struct request_queue *q, const char *buf,
                          size_t count)
 {
+       char elevator_name[ELV_NAME_MAX];
        int ret;
 
        if (!elv_support_iosched(q))
                return count;
 
-       ret = __elevator_change(q, name);
+       strscpy(elevator_name, buf, sizeof(elevator_name));
+       ret = elevator_change(q, strstrip(elevator_name));
        if (!ret)
                return count;
-
        return ret;
 }
 
 ssize_t elv_iosched_show(struct request_queue *q, char *name)
 {
-       struct elevator_queue *e = q->elevator;
-       struct elevator_type *elv = NULL;
-       struct elevator_type *__e;
+       struct elevator_queue *eq = q->elevator;
+       struct elevator_type *cur = NULL, *e;
        int len = 0;
 
-       if (!queue_is_mq(q))
+       if (!elv_support_iosched(q))
                return sprintf(name, "none\n");
 
-       if (!q->elevator)
+       if (!q->elevator) {
                len += sprintf(name+len, "[none] ");
-       else
-               elv = e->type;
+       } else {
+               len += sprintf(name+len, "none ");
+               cur = eq->type;
+       }
 
        spin_lock(&elv_list_lock);
-       list_for_each_entry(__e, &elv_list, list) {
-               if (elv && elevator_match(elv, __e->elevator_name, 0)) {
-                       len += sprintf(name+len, "[%s] ", elv->elevator_name);
-                       continue;
-               }
-               if (elv_support_iosched(q) &&
-                   elevator_match(__e, __e->elevator_name,
-                                  q->required_elevator_features))
-                       len += sprintf(name+len, "%s ", __e->elevator_name);
+       list_for_each_entry(e, &elv_list, list) {
+               if (e == cur)
+                       len += sprintf(name+len, "[%s] ", e->elevator_name);
+               else if (elv_support_features(q, e))
+                       len += sprintf(name+len, "%s ", e->elevator_name);
        }
        spin_unlock(&elv_list_lock);
 
-       if (q->elevator)
-               len += sprintf(name+len, "none");
-
-       len += sprintf(len+name, "\n");
+       len += sprintf(name+len, "\n");
        return len;
 }