elevator: Fix a race in elevator switching and md device initialization
[linux-3.10.git] / block / elevator.c
index 11683bb..02d4390 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/blktrace_api.h>
 #include <linux/hash.h>
 #include <linux/uaccess.h>
+#include <linux/pm_runtime.h>
 
 #include <trace/events/block.h>
 
@@ -95,14 +96,14 @@ static void elevator_put(struct elevator_type *e)
        module_put(e->elevator_owner);
 }
 
-static struct elevator_type *elevator_get(const char *name)
+static struct elevator_type *elevator_get(const char *name, bool try_loading)
 {
        struct elevator_type *e;
 
        spin_lock(&elv_list_lock);
 
        e = elevator_find(name);
-       if (!e) {
+       if (!e && try_loading) {
                spin_unlock(&elv_list_lock);
                request_module("%s-iosched", name);
                spin_lock(&elv_list_lock);
@@ -131,9 +132,25 @@ static int __init elevator_setup(char *str)
 
 __setup("elevator=", elevator_setup);
 
+/* called during boot to load the elevator chosen by the elevator param */
+void __init load_default_elevator_module(void)
+{
+       struct elevator_type *e;
+
+       if (!chosen_elevator[0])
+               return;
+
+       spin_lock(&elv_list_lock);
+       e = elevator_find(chosen_elevator);
+       spin_unlock(&elv_list_lock);
+
+       if (!e)
+               request_module("%s-iosched", chosen_elevator);
+}
+
 static struct kobj_type elv_ktype;
 
-static struct elevator_queue *elevator_alloc(struct request_queue *q,
+struct elevator_queue *elevator_alloc(struct request_queue *q,
                                  struct elevator_type *e)
 {
        struct elevator_queue *eq;
@@ -153,6 +170,7 @@ err:
        elevator_put(e);
        return NULL;
 }
+EXPORT_SYMBOL(elevator_alloc);
 
 static void elevator_release(struct kobject *kobj)
 {
@@ -168,6 +186,12 @@ int elevator_init(struct request_queue *q, char *name)
        struct elevator_type *e = NULL;
        int err;
 
+       /*
+        * q->sysfs_lock must be held to provide mutual exclusion between
+        * elevator_switch() and here.
+        */
+       lockdep_assert_held(&q->sysfs_lock);
+
        if (unlikely(q->elevator))
                return 0;
 
@@ -177,38 +201,34 @@ int elevator_init(struct request_queue *q, char *name)
        q->boundary_rq = NULL;
 
        if (name) {
-               e = elevator_get(name);
+               e = elevator_get(name, true);
                if (!e)
                        return -EINVAL;
        }
 
+       /*
+        * Use the default elevator specified by config boot param or
+        * config option.  Don't try to load modules as we could be running
+        * off async and request_module() isn't allowed from async.
+        */
        if (!e && *chosen_elevator) {
-               e = elevator_get(chosen_elevator);
+               e = elevator_get(chosen_elevator, false);
                if (!e)
                        printk(KERN_ERR "I/O scheduler %s not found\n",
                                                        chosen_elevator);
        }
 
        if (!e) {
-               e = elevator_get(CONFIG_DEFAULT_IOSCHED);
+               e = elevator_get(CONFIG_DEFAULT_IOSCHED, false);
                if (!e) {
                        printk(KERN_ERR
                                "Default I/O scheduler not found. " \
                                "Using noop.\n");
-                       e = elevator_get("noop");
+                       e = elevator_get("noop", false);
                }
        }
 
-       q->elevator = elevator_alloc(q, e);
-       if (!q->elevator)
-               return -ENOMEM;
-
-       err = e->ops.elevator_init_fn(q);
-       if (err) {
-               kobject_put(&q->elevator->kobj);
-               return err;
-       }
-
+       err = e->ops.elevator_init_fn(q, e);
        return 0;
 }
 EXPORT_SYMBOL(elevator_init);
@@ -252,10 +272,10 @@ static void elv_rqhash_reposition(struct request_queue *q, struct request *rq)
 static struct request *elv_rqhash_find(struct request_queue *q, sector_t offset)
 {
        struct elevator_queue *e = q->elevator;
-       struct hlist_node *entry, *next;
+       struct hlist_node *next;
        struct request *rq;
 
-       hash_for_each_possible_safe(e->hash, rq, entry, next, hash, offset) {
+       hash_for_each_possible_safe(e->hash, rq, next, hash, offset) {
                BUG_ON(!ELV_ON_HASH(rq));
 
                if (unlikely(!rq_mergeable(rq))) {
@@ -515,6 +535,27 @@ void elv_bio_merged(struct request_queue *q, struct request *rq,
                e->type->ops.elevator_bio_merged_fn(q, rq, bio);
 }
 
+#ifdef CONFIG_PM_RUNTIME
+static void blk_pm_requeue_request(struct request *rq)
+{
+       if (rq->q->dev && !(rq->cmd_flags & REQ_PM))
+               rq->q->nr_pending--;
+}
+
+static void blk_pm_add_request(struct request_queue *q, struct request *rq)
+{
+       if (q->dev && !(rq->cmd_flags & REQ_PM) && q->nr_pending++ == 0 &&
+           (q->rpm_status == RPM_SUSPENDED || q->rpm_status == RPM_SUSPENDING))
+               pm_request_resume(q->dev);
+}
+#else
+static inline void blk_pm_requeue_request(struct request *rq) {}
+static inline void blk_pm_add_request(struct request_queue *q,
+                                     struct request *rq)
+{
+}
+#endif
+
 void elv_requeue_request(struct request_queue *q, struct request *rq)
 {
        /*
@@ -529,6 +570,8 @@ void elv_requeue_request(struct request_queue *q, struct request *rq)
 
        rq->cmd_flags &= ~REQ_STARTED;
 
+       blk_pm_requeue_request(rq);
+
        __elv_add_request(q, rq, ELEVATOR_INSERT_REQUEUE);
 }
 
@@ -551,6 +594,8 @@ void __elv_add_request(struct request_queue *q, struct request *rq, int where)
 {
        trace_block_rq_insert(q, rq);
 
+       blk_pm_add_request(q, rq);
+
        rq->q = q;
 
        if (rq->cmd_flags & REQ_SOFTBARRIER) {
@@ -888,16 +933,9 @@ static int elevator_switch(struct request_queue *q, struct elevator_type *new_e)
        spin_unlock_irq(q->queue_lock);
 
        /* allocate, init and register new elevator */
-       err = -ENOMEM;
-       q->elevator = elevator_alloc(q, new_e);
-       if (!q->elevator)
-               goto fail_init;
-
-       err = new_e->ops.elevator_init_fn(q);
-       if (err) {
-               kobject_put(&q->elevator->kobj);
+       err = new_e->ops.elevator_init_fn(q, new_e);
+       if (err)
                goto fail_init;
-       }
 
        if (registered) {
                err = elv_register_queue(q);
@@ -936,7 +974,7 @@ int elevator_change(struct request_queue *q, const char *name)
                return -ENXIO;
 
        strlcpy(elevator_name, name, sizeof(elevator_name));
-       e = elevator_get(strstrip(elevator_name));
+       e = elevator_get(strstrip(elevator_name), true);
        if (!e) {
                printk(KERN_ERR "elevator: type %s not found\n", elevator_name);
                return -EINVAL;