block: fix race between adding/removing rq qos and normal IO
[linux-2.6-microblaze.git] / block / blk-rq-qos.h
index 2bc43e9..2bcb349 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/blk_types.h>
 #include <linux/atomic.h>
 #include <linux/wait.h>
+#include <linux/blk-mq.h>
 
 #include "blk-mq-debugfs.h"
 
@@ -99,8 +100,21 @@ static inline void rq_wait_init(struct rq_wait *rq_wait)
 
 static inline void rq_qos_add(struct request_queue *q, struct rq_qos *rqos)
 {
+       /*
+        * No IO can be in-flight when adding rqos, so freeze queue, which
+        * is fine since we only support rq_qos for blk-mq queue.
+        *
+        * Reuse ->queue_lock for protecting against other concurrent
+        * rq_qos adding/deleting
+        */
+       blk_mq_freeze_queue(q);
+
+       spin_lock_irq(&q->queue_lock);
        rqos->next = q->rq_qos;
        q->rq_qos = rqos;
+       spin_unlock_irq(&q->queue_lock);
+
+       blk_mq_unfreeze_queue(q);
 
        if (rqos->ops->debugfs_attrs)
                blk_mq_debugfs_register_rqos(rqos);
@@ -110,12 +124,22 @@ static inline void rq_qos_del(struct request_queue *q, struct rq_qos *rqos)
 {
        struct rq_qos **cur;
 
+       /*
+        * See comment in rq_qos_add() about freezing queue & using
+        * ->queue_lock.
+        */
+       blk_mq_freeze_queue(q);
+
+       spin_lock_irq(&q->queue_lock);
        for (cur = &q->rq_qos; *cur; cur = &(*cur)->next) {
                if (*cur == rqos) {
                        *cur = rqos->next;
                        break;
                }
        }
+       spin_unlock_irq(&q->queue_lock);
+
+       blk_mq_unfreeze_queue(q);
 
        blk_mq_debugfs_unregister_rqos(rqos);
 }