]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - block/blk-mq-debugfs.c
mq-deadline: add debugfs attributes
[mirror_ubuntu-artful-kernel.git] / block / blk-mq-debugfs.c
index f6d917977b3318689f1a1ef387bae47abb03ab0b..803aed4d72216f5e23a585cb6d027115fa603ff9 100644 (file)
 #include <linux/blk-mq.h>
 #include "blk.h"
 #include "blk-mq.h"
+#include "blk-mq-debugfs.h"
 #include "blk-mq-tag.h"
 
-struct blk_mq_debugfs_attr {
-       const char *name;
-       umode_t mode;
-       const struct file_operations *fops;
-};
-
-static int blk_mq_debugfs_seq_open(struct inode *inode, struct file *file,
-                                  const struct seq_operations *ops)
+static int blk_flags_show(struct seq_file *m, const unsigned long flags,
+                         const char *const *flag_name, int flag_name_count)
 {
-       struct seq_file *m;
-       int ret;
+       bool sep = false;
+       int i;
 
-       ret = seq_open(file, ops);
-       if (!ret) {
-               m = file->private_data;
-               m->private = inode->i_private;
+       for (i = 0; i < sizeof(flags) * BITS_PER_BYTE; i++) {
+               if (!(flags & BIT(i)))
+                       continue;
+               if (sep)
+                       seq_puts(m, "|");
+               sep = true;
+               if (i < flag_name_count && flag_name[i])
+                       seq_puts(m, flag_name[i]);
+               else
+                       seq_printf(m, "%d", i);
        }
-       return ret;
+       return 0;
 }
 
-static int hctx_state_show(struct seq_file *m, void *v)
+#define QUEUE_FLAG_NAME(name) [QUEUE_FLAG_##name] = #name
+static const char *const blk_queue_flag_name[] = {
+       QUEUE_FLAG_NAME(QUEUED),
+       QUEUE_FLAG_NAME(STOPPED),
+       QUEUE_FLAG_NAME(SYNCFULL),
+       QUEUE_FLAG_NAME(ASYNCFULL),
+       QUEUE_FLAG_NAME(DYING),
+       QUEUE_FLAG_NAME(BYPASS),
+       QUEUE_FLAG_NAME(BIDI),
+       QUEUE_FLAG_NAME(NOMERGES),
+       QUEUE_FLAG_NAME(SAME_COMP),
+       QUEUE_FLAG_NAME(FAIL_IO),
+       QUEUE_FLAG_NAME(STACKABLE),
+       QUEUE_FLAG_NAME(NONROT),
+       QUEUE_FLAG_NAME(IO_STAT),
+       QUEUE_FLAG_NAME(DISCARD),
+       QUEUE_FLAG_NAME(NOXMERGES),
+       QUEUE_FLAG_NAME(ADD_RANDOM),
+       QUEUE_FLAG_NAME(SECERASE),
+       QUEUE_FLAG_NAME(SAME_FORCE),
+       QUEUE_FLAG_NAME(DEAD),
+       QUEUE_FLAG_NAME(INIT_DONE),
+       QUEUE_FLAG_NAME(NO_SG_MERGE),
+       QUEUE_FLAG_NAME(POLL),
+       QUEUE_FLAG_NAME(WC),
+       QUEUE_FLAG_NAME(FUA),
+       QUEUE_FLAG_NAME(FLUSH_NQ),
+       QUEUE_FLAG_NAME(DAX),
+       QUEUE_FLAG_NAME(STATS),
+       QUEUE_FLAG_NAME(POLL_STATS),
+       QUEUE_FLAG_NAME(REGISTERED),
+};
+#undef QUEUE_FLAG_NAME
+
+static int queue_state_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct request_queue *q = data;
 
-       seq_printf(m, "0x%lx\n", hctx->state);
+       blk_flags_show(m, q->queue_flags, blk_queue_flag_name,
+                      ARRAY_SIZE(blk_queue_flag_name));
+       seq_puts(m, "\n");
        return 0;
 }
 
-static int hctx_state_open(struct inode *inode, struct file *file)
+static ssize_t queue_state_write(void *data, const char __user *buf,
+                                size_t count, loff_t *ppos)
 {
-       return single_open(file, hctx_state_show, inode->i_private);
+       struct request_queue *q = data;
+       char opbuf[16] = { }, *op;
+
+       /*
+        * The "state" attribute is removed after blk_cleanup_queue() has called
+        * blk_mq_free_queue(). Return if QUEUE_FLAG_DEAD has been set to avoid
+        * triggering a use-after-free.
+        */
+       if (blk_queue_dead(q))
+               return -ENOENT;
+
+       if (count >= sizeof(opbuf)) {
+               pr_err("%s: operation too long\n", __func__);
+               goto inval;
+       }
+
+       if (copy_from_user(opbuf, buf, count))
+               return -EFAULT;
+       op = strstrip(opbuf);
+       if (strcmp(op, "run") == 0) {
+               blk_mq_run_hw_queues(q, true);
+       } else if (strcmp(op, "start") == 0) {
+               blk_mq_start_stopped_hw_queues(q, true);
+       } else {
+               pr_err("%s: unsupported operation '%s'\n", __func__, op);
+inval:
+               pr_err("%s: use either 'run' or 'start'\n", __func__);
+               return -EINVAL;
+       }
+       return count;
 }
 
-static const struct file_operations hctx_state_fops = {
-       .open           = hctx_state_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
+static void print_stat(struct seq_file *m, struct blk_rq_stat *stat)
+{
+       if (stat->nr_samples) {
+               seq_printf(m, "samples=%d, mean=%lld, min=%llu, max=%llu",
+                          stat->nr_samples, stat->mean, stat->min, stat->max);
+       } else {
+               seq_puts(m, "samples=0");
+       }
+}
 
-static int hctx_flags_show(struct seq_file *m, void *v)
+static int queue_poll_stat_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct request_queue *q = data;
+       int bucket;
 
-       seq_printf(m, "0x%lx\n", hctx->flags);
+       for (bucket = 0; bucket < BLK_MQ_POLL_STATS_BKTS/2; bucket++) {
+               seq_printf(m, "read  (%d Bytes): ", 1 << (9+bucket));
+               print_stat(m, &q->poll_stat[2*bucket]);
+               seq_puts(m, "\n");
+
+               seq_printf(m, "write (%d Bytes): ",  1 << (9+bucket));
+               print_stat(m, &q->poll_stat[2*bucket+1]);
+               seq_puts(m, "\n");
+       }
        return 0;
 }
 
-static int hctx_flags_open(struct inode *inode, struct file *file)
+#define HCTX_STATE_NAME(name) [BLK_MQ_S_##name] = #name
+static const char *const hctx_state_name[] = {
+       HCTX_STATE_NAME(STOPPED),
+       HCTX_STATE_NAME(TAG_ACTIVE),
+       HCTX_STATE_NAME(SCHED_RESTART),
+       HCTX_STATE_NAME(TAG_WAITING),
+       HCTX_STATE_NAME(START_ON_RUN),
+};
+#undef HCTX_STATE_NAME
+
+static int hctx_state_show(void *data, struct seq_file *m)
 {
-       return single_open(file, hctx_flags_show, inode->i_private);
+       struct blk_mq_hw_ctx *hctx = data;
+
+       blk_flags_show(m, hctx->state, hctx_state_name,
+                      ARRAY_SIZE(hctx_state_name));
+       seq_puts(m, "\n");
+       return 0;
 }
 
-static const struct file_operations hctx_flags_fops = {
-       .open           = hctx_flags_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
+#define BLK_TAG_ALLOC_NAME(name) [BLK_TAG_ALLOC_##name] = #name
+static const char *const alloc_policy_name[] = {
+       BLK_TAG_ALLOC_NAME(FIFO),
+       BLK_TAG_ALLOC_NAME(RR),
 };
+#undef BLK_TAG_ALLOC_NAME
+
+#define HCTX_FLAG_NAME(name) [ilog2(BLK_MQ_F_##name)] = #name
+static const char *const hctx_flag_name[] = {
+       HCTX_FLAG_NAME(SHOULD_MERGE),
+       HCTX_FLAG_NAME(TAG_SHARED),
+       HCTX_FLAG_NAME(SG_MERGE),
+       HCTX_FLAG_NAME(BLOCKING),
+       HCTX_FLAG_NAME(NO_SCHED),
+};
+#undef HCTX_FLAG_NAME
+
+static int hctx_flags_show(void *data, struct seq_file *m)
+{
+       struct blk_mq_hw_ctx *hctx = data;
+       const int alloc_policy = BLK_MQ_FLAG_TO_ALLOC_POLICY(hctx->flags);
+
+       seq_puts(m, "alloc_policy=");
+       if (alloc_policy < ARRAY_SIZE(alloc_policy_name) &&
+           alloc_policy_name[alloc_policy])
+               seq_puts(m, alloc_policy_name[alloc_policy]);
+       else
+               seq_printf(m, "%d", alloc_policy);
+       seq_puts(m, " ");
+       blk_flags_show(m,
+                      hctx->flags ^ BLK_ALLOC_POLICY_TO_MQ_FLAG(alloc_policy),
+                      hctx_flag_name, ARRAY_SIZE(hctx_flag_name));
+       seq_puts(m, "\n");
+       return 0;
+}
 
-static int blk_mq_debugfs_rq_show(struct seq_file *m, void *v)
-{
-       struct request *rq = list_entry_rq(v);
-
-       seq_printf(m, "%p {.cmd_flags=0x%x, .rq_flags=0x%x, .tag=%d, .internal_tag=%d}\n",
-                  rq, rq->cmd_flags, (__force unsigned int)rq->rq_flags,
-                  rq->tag, rq->internal_tag);
+#define REQ_OP_NAME(name) [REQ_OP_##name] = #name
+static const char *const op_name[] = {
+       REQ_OP_NAME(READ),
+       REQ_OP_NAME(WRITE),
+       REQ_OP_NAME(FLUSH),
+       REQ_OP_NAME(DISCARD),
+       REQ_OP_NAME(ZONE_REPORT),
+       REQ_OP_NAME(SECURE_ERASE),
+       REQ_OP_NAME(ZONE_RESET),
+       REQ_OP_NAME(WRITE_SAME),
+       REQ_OP_NAME(WRITE_ZEROES),
+       REQ_OP_NAME(SCSI_IN),
+       REQ_OP_NAME(SCSI_OUT),
+       REQ_OP_NAME(DRV_IN),
+       REQ_OP_NAME(DRV_OUT),
+};
+#undef REQ_OP_NAME
+
+#define CMD_FLAG_NAME(name) [__REQ_##name] = #name
+static const char *const cmd_flag_name[] = {
+       CMD_FLAG_NAME(FAILFAST_DEV),
+       CMD_FLAG_NAME(FAILFAST_TRANSPORT),
+       CMD_FLAG_NAME(FAILFAST_DRIVER),
+       CMD_FLAG_NAME(SYNC),
+       CMD_FLAG_NAME(META),
+       CMD_FLAG_NAME(PRIO),
+       CMD_FLAG_NAME(NOMERGE),
+       CMD_FLAG_NAME(IDLE),
+       CMD_FLAG_NAME(INTEGRITY),
+       CMD_FLAG_NAME(FUA),
+       CMD_FLAG_NAME(PREFLUSH),
+       CMD_FLAG_NAME(RAHEAD),
+       CMD_FLAG_NAME(BACKGROUND),
+       CMD_FLAG_NAME(NOUNMAP),
+};
+#undef CMD_FLAG_NAME
+
+#define RQF_NAME(name) [ilog2((__force u32)RQF_##name)] = #name
+static const char *const rqf_name[] = {
+       RQF_NAME(SORTED),
+       RQF_NAME(STARTED),
+       RQF_NAME(QUEUED),
+       RQF_NAME(SOFTBARRIER),
+       RQF_NAME(FLUSH_SEQ),
+       RQF_NAME(MIXED_MERGE),
+       RQF_NAME(MQ_INFLIGHT),
+       RQF_NAME(DONTPREP),
+       RQF_NAME(PREEMPT),
+       RQF_NAME(COPY_USER),
+       RQF_NAME(FAILED),
+       RQF_NAME(QUIET),
+       RQF_NAME(ELVPRIV),
+       RQF_NAME(IO_STAT),
+       RQF_NAME(ALLOCED),
+       RQF_NAME(PM),
+       RQF_NAME(HASHED),
+       RQF_NAME(STATS),
+       RQF_NAME(SPECIAL_PAYLOAD),
+};
+#undef RQF_NAME
+
+int __blk_mq_debugfs_rq_show(struct seq_file *m, struct request *rq)
+{
+       const struct blk_mq_ops *const mq_ops = rq->q->mq_ops;
+       const unsigned int op = rq->cmd_flags & REQ_OP_MASK;
+
+       seq_printf(m, "%p {.op=", rq);
+       if (op < ARRAY_SIZE(op_name) && op_name[op])
+               seq_printf(m, "%s", op_name[op]);
+       else
+               seq_printf(m, "%d", op);
+       seq_puts(m, ", .cmd_flags=");
+       blk_flags_show(m, rq->cmd_flags & ~REQ_OP_MASK, cmd_flag_name,
+                      ARRAY_SIZE(cmd_flag_name));
+       seq_puts(m, ", .rq_flags=");
+       blk_flags_show(m, (__force unsigned int)rq->rq_flags, rqf_name,
+                      ARRAY_SIZE(rqf_name));
+       seq_printf(m, ", .tag=%d, .internal_tag=%d", rq->tag,
+                  rq->internal_tag);
+       if (mq_ops->show_rq)
+               mq_ops->show_rq(m, rq);
+       seq_puts(m, "}\n");
        return 0;
 }
+EXPORT_SYMBOL_GPL(__blk_mq_debugfs_rq_show);
+
+int blk_mq_debugfs_rq_show(struct seq_file *m, void *v)
+{
+       return __blk_mq_debugfs_rq_show(m, list_entry_rq(v));
+}
+EXPORT_SYMBOL_GPL(blk_mq_debugfs_rq_show);
 
 static void *hctx_dispatch_start(struct seq_file *m, loff_t *pos)
        __acquires(&hctx->lock)
@@ -124,38 +329,14 @@ static const struct seq_operations hctx_dispatch_seq_ops = {
        .show   = blk_mq_debugfs_rq_show,
 };
 
-static int hctx_dispatch_open(struct inode *inode, struct file *file)
-{
-       return blk_mq_debugfs_seq_open(inode, file, &hctx_dispatch_seq_ops);
-}
-
-static const struct file_operations hctx_dispatch_fops = {
-       .open           = hctx_dispatch_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = seq_release,
-};
-
-static int hctx_ctx_map_show(struct seq_file *m, void *v)
+static int hctx_ctx_map_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        sbitmap_bitmap_show(&hctx->ctx_map, m);
        return 0;
 }
 
-static int hctx_ctx_map_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_ctx_map_show, inode->i_private);
-}
-
-static const struct file_operations hctx_ctx_map_fops = {
-       .open           = hctx_ctx_map_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
 static void blk_mq_debugfs_tags_show(struct seq_file *m,
                                     struct blk_mq_tags *tags)
 {
@@ -173,9 +354,9 @@ static void blk_mq_debugfs_tags_show(struct seq_file *m,
        }
 }
 
-static int hctx_tags_show(struct seq_file *m, void *v)
+static int hctx_tags_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        struct request_queue *q = hctx->queue;
        int res;
 
@@ -190,21 +371,9 @@ out:
        return res;
 }
 
-static int hctx_tags_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_tags_show, inode->i_private);
-}
-
-static const struct file_operations hctx_tags_fops = {
-       .open           = hctx_tags_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_tags_bitmap_show(struct seq_file *m, void *v)
+static int hctx_tags_bitmap_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        struct request_queue *q = hctx->queue;
        int res;
 
@@ -219,21 +388,9 @@ out:
        return res;
 }
 
-static int hctx_tags_bitmap_open(struct inode *inode, struct file *file)
+static int hctx_sched_tags_show(void *data, struct seq_file *m)
 {
-       return single_open(file, hctx_tags_bitmap_show, inode->i_private);
-}
-
-static const struct file_operations hctx_tags_bitmap_fops = {
-       .open           = hctx_tags_bitmap_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_sched_tags_show(struct seq_file *m, void *v)
-{
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        struct request_queue *q = hctx->queue;
        int res;
 
@@ -248,21 +405,9 @@ out:
        return res;
 }
 
-static int hctx_sched_tags_open(struct inode *inode, struct file *file)
+static int hctx_sched_tags_bitmap_show(void *data, struct seq_file *m)
 {
-       return single_open(file, hctx_sched_tags_show, inode->i_private);
-}
-
-static const struct file_operations hctx_sched_tags_fops = {
-       .open           = hctx_sched_tags_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_sched_tags_bitmap_show(struct seq_file *m, void *v)
-{
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        struct request_queue *q = hctx->queue;
        int res;
 
@@ -277,21 +422,9 @@ out:
        return res;
 }
 
-static int hctx_sched_tags_bitmap_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_sched_tags_bitmap_show, inode->i_private);
-}
-
-static const struct file_operations hctx_sched_tags_bitmap_fops = {
-       .open           = hctx_sched_tags_bitmap_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_io_poll_show(struct seq_file *m, void *v)
+static int hctx_io_poll_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        seq_printf(m, "considered=%lu\n", hctx->poll_considered);
        seq_printf(m, "invoked=%lu\n", hctx->poll_invoked);
@@ -299,86 +432,18 @@ static int hctx_io_poll_show(struct seq_file *m, void *v)
        return 0;
 }
 
-static int hctx_io_poll_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_io_poll_show, inode->i_private);
-}
-
-static ssize_t hctx_io_poll_write(struct file *file, const char __user *buf,
+static ssize_t hctx_io_poll_write(void *data, const char __user *buf,
                                  size_t count, loff_t *ppos)
 {
-       struct seq_file *m = file->private_data;
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        hctx->poll_considered = hctx->poll_invoked = hctx->poll_success = 0;
        return count;
 }
 
-static const struct file_operations hctx_io_poll_fops = {
-       .open           = hctx_io_poll_open,
-       .read           = seq_read,
-       .write          = hctx_io_poll_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static void print_stat(struct seq_file *m, struct blk_rq_stat *stat)
-{
-       seq_printf(m, "samples=%d, mean=%lld, min=%llu, max=%llu",
-                  stat->nr_samples, stat->mean, stat->min, stat->max);
-}
-
-static int hctx_stats_show(struct seq_file *m, void *v)
-{
-       struct blk_mq_hw_ctx *hctx = m->private;
-       struct blk_rq_stat stat[2];
-
-       blk_stat_init(&stat[BLK_STAT_READ]);
-       blk_stat_init(&stat[BLK_STAT_WRITE]);
-
-       blk_hctx_stat_get(hctx, stat);
-
-       seq_puts(m, "read: ");
-       print_stat(m, &stat[BLK_STAT_READ]);
-       seq_puts(m, "\n");
-
-       seq_puts(m, "write: ");
-       print_stat(m, &stat[BLK_STAT_WRITE]);
-       seq_puts(m, "\n");
-       return 0;
-}
-
-static int hctx_stats_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_stats_show, inode->i_private);
-}
-
-static ssize_t hctx_stats_write(struct file *file, const char __user *buf,
-                               size_t count, loff_t *ppos)
-{
-       struct seq_file *m = file->private_data;
-       struct blk_mq_hw_ctx *hctx = m->private;
-       struct blk_mq_ctx *ctx;
-       int i;
-
-       hctx_for_each_ctx(hctx, ctx, i) {
-               blk_stat_init(&ctx->stat[BLK_STAT_READ]);
-               blk_stat_init(&ctx->stat[BLK_STAT_WRITE]);
-       }
-       return count;
-}
-
-static const struct file_operations hctx_stats_fops = {
-       .open           = hctx_stats_open,
-       .read           = seq_read,
-       .write          = hctx_stats_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_dispatched_show(struct seq_file *m, void *v)
+static int hctx_dispatched_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        int i;
 
        seq_printf(m, "%8u\t%lu\n", 0U, hctx->dispatched[0]);
@@ -393,16 +458,10 @@ static int hctx_dispatched_show(struct seq_file *m, void *v)
        return 0;
 }
 
-static int hctx_dispatched_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_dispatched_show, inode->i_private);
-}
-
-static ssize_t hctx_dispatched_write(struct file *file, const char __user *buf,
+static ssize_t hctx_dispatched_write(void *data, const char __user *buf,
                                     size_t count, loff_t *ppos)
 {
-       struct seq_file *m = file->private_data;
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
        int i;
 
        for (i = 0; i < BLK_MQ_MAX_DISPATCH_ORDER; i++)
@@ -410,96 +469,48 @@ static ssize_t hctx_dispatched_write(struct file *file, const char __user *buf,
        return count;
 }
 
-static const struct file_operations hctx_dispatched_fops = {
-       .open           = hctx_dispatched_open,
-       .read           = seq_read,
-       .write          = hctx_dispatched_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_queued_show(struct seq_file *m, void *v)
+static int hctx_queued_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        seq_printf(m, "%lu\n", hctx->queued);
        return 0;
 }
 
-static int hctx_queued_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_queued_show, inode->i_private);
-}
-
-static ssize_t hctx_queued_write(struct file *file, const char __user *buf,
+static ssize_t hctx_queued_write(void *data, const char __user *buf,
                                 size_t count, loff_t *ppos)
 {
-       struct seq_file *m = file->private_data;
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        hctx->queued = 0;
        return count;
 }
 
-static const struct file_operations hctx_queued_fops = {
-       .open           = hctx_queued_open,
-       .read           = seq_read,
-       .write          = hctx_queued_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_run_show(struct seq_file *m, void *v)
+static int hctx_run_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        seq_printf(m, "%lu\n", hctx->run);
        return 0;
 }
 
-static int hctx_run_open(struct inode *inode, struct file *file)
+static ssize_t hctx_run_write(void *data, const char __user *buf, size_t count,
+                             loff_t *ppos)
 {
-       return single_open(file, hctx_run_show, inode->i_private);
-}
-
-static ssize_t hctx_run_write(struct file *file, const char __user *buf,
-                                size_t count, loff_t *ppos)
-{
-       struct seq_file *m = file->private_data;
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        hctx->run = 0;
        return count;
 }
 
-static const struct file_operations hctx_run_fops = {
-       .open           = hctx_run_open,
-       .read           = seq_read,
-       .write          = hctx_run_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int hctx_active_show(struct seq_file *m, void *v)
+static int hctx_active_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_hw_ctx *hctx = m->private;
+       struct blk_mq_hw_ctx *hctx = data;
 
        seq_printf(m, "%d\n", atomic_read(&hctx->nr_active));
        return 0;
 }
 
-static int hctx_active_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, hctx_active_show, inode->i_private);
-}
-
-static const struct file_operations hctx_active_fops = {
-       .open           = hctx_active_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
 static void *ctx_rq_list_start(struct seq_file *m, loff_t *pos)
        __acquires(&ctx->lock)
 {
@@ -530,150 +541,192 @@ static const struct seq_operations ctx_rq_list_seq_ops = {
        .stop   = ctx_rq_list_stop,
        .show   = blk_mq_debugfs_rq_show,
 };
-
-static int ctx_rq_list_open(struct inode *inode, struct file *file)
-{
-       return blk_mq_debugfs_seq_open(inode, file, &ctx_rq_list_seq_ops);
-}
-
-static const struct file_operations ctx_rq_list_fops = {
-       .open           = ctx_rq_list_open,
-       .read           = seq_read,
-       .llseek         = seq_lseek,
-       .release        = seq_release,
-};
-
-static int ctx_dispatched_show(struct seq_file *m, void *v)
+static int ctx_dispatched_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_ctx *ctx = m->private;
+       struct blk_mq_ctx *ctx = data;
 
        seq_printf(m, "%lu %lu\n", ctx->rq_dispatched[1], ctx->rq_dispatched[0]);
        return 0;
 }
 
-static int ctx_dispatched_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, ctx_dispatched_show, inode->i_private);
-}
-
-static ssize_t ctx_dispatched_write(struct file *file, const char __user *buf,
+static ssize_t ctx_dispatched_write(void *data, const char __user *buf,
                                    size_t count, loff_t *ppos)
 {
-       struct seq_file *m = file->private_data;
-       struct blk_mq_ctx *ctx = m->private;
+       struct blk_mq_ctx *ctx = data;
 
        ctx->rq_dispatched[0] = ctx->rq_dispatched[1] = 0;
        return count;
 }
 
-static const struct file_operations ctx_dispatched_fops = {
-       .open           = ctx_dispatched_open,
-       .read           = seq_read,
-       .write          = ctx_dispatched_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int ctx_merged_show(struct seq_file *m, void *v)
+static int ctx_merged_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_ctx *ctx = m->private;
+       struct blk_mq_ctx *ctx = data;
 
        seq_printf(m, "%lu\n", ctx->rq_merged);
        return 0;
 }
 
-static int ctx_merged_open(struct inode *inode, struct file *file)
-{
-       return single_open(file, ctx_merged_show, inode->i_private);
-}
-
-static ssize_t ctx_merged_write(struct file *file, const char __user *buf,
-                                   size_t count, loff_t *ppos)
+static ssize_t ctx_merged_write(void *data, const char __user *buf,
+                               size_t count, loff_t *ppos)
 {
-       struct seq_file *m = file->private_data;
-       struct blk_mq_ctx *ctx = m->private;
+       struct blk_mq_ctx *ctx = data;
 
        ctx->rq_merged = 0;
        return count;
 }
 
-static const struct file_operations ctx_merged_fops = {
-       .open           = ctx_merged_open,
-       .read           = seq_read,
-       .write          = ctx_merged_write,
-       .llseek         = seq_lseek,
-       .release        = single_release,
-};
-
-static int ctx_completed_show(struct seq_file *m, void *v)
+static int ctx_completed_show(void *data, struct seq_file *m)
 {
-       struct blk_mq_ctx *ctx = m->private;
+       struct blk_mq_ctx *ctx = data;
 
        seq_printf(m, "%lu %lu\n", ctx->rq_completed[1], ctx->rq_completed[0]);
        return 0;
 }
 
-static int ctx_completed_open(struct inode *inode, struct file *file)
+static ssize_t ctx_completed_write(void *data, const char __user *buf,
+                                  size_t count, loff_t *ppos)
+{
+       struct blk_mq_ctx *ctx = data;
+
+       ctx->rq_completed[0] = ctx->rq_completed[1] = 0;
+       return count;
+}
+
+static int blk_mq_debugfs_show(struct seq_file *m, void *v)
 {
-       return single_open(file, ctx_completed_show, inode->i_private);
+       const struct blk_mq_debugfs_attr *attr = m->private;
+       void *data = d_inode(m->file->f_path.dentry->d_parent)->i_private;
+
+       return attr->show(data, m);
 }
 
-static ssize_t ctx_completed_write(struct file *file, const char __user *buf,
-                                  size_t count, loff_t *ppos)
+static ssize_t blk_mq_debugfs_write(struct file *file, const char __user *buf,
+                                   size_t count, loff_t *ppos)
 {
        struct seq_file *m = file->private_data;
-       struct blk_mq_ctx *ctx = m->private;
+       const struct blk_mq_debugfs_attr *attr = m->private;
+       void *data = d_inode(file->f_path.dentry->d_parent)->i_private;
 
-       ctx->rq_completed[0] = ctx->rq_completed[1] = 0;
-       return count;
+       if (!attr->write)
+               return -EPERM;
+
+       return attr->write(data, buf, count, ppos);
+}
+
+static int blk_mq_debugfs_open(struct inode *inode, struct file *file)
+{
+       const struct blk_mq_debugfs_attr *attr = inode->i_private;
+       void *data = d_inode(file->f_path.dentry->d_parent)->i_private;
+       struct seq_file *m;
+       int ret;
+
+       if (attr->seq_ops) {
+               ret = seq_open(file, attr->seq_ops);
+               if (!ret) {
+                       m = file->private_data;
+                       m->private = data;
+               }
+               return ret;
+       }
+
+       if (WARN_ON_ONCE(!attr->show))
+               return -EPERM;
+
+       return single_open(file, blk_mq_debugfs_show, inode->i_private);
 }
 
-static const struct file_operations ctx_completed_fops = {
-       .open           = ctx_completed_open,
+static int blk_mq_debugfs_release(struct inode *inode, struct file *file)
+{
+       const struct blk_mq_debugfs_attr *attr = inode->i_private;
+
+       if (attr->show)
+               return single_release(inode, file);
+       else
+               return seq_release(inode, file);
+}
+
+const struct file_operations blk_mq_debugfs_fops = {
+       .open           = blk_mq_debugfs_open,
        .read           = seq_read,
-       .write          = ctx_completed_write,
+       .write          = blk_mq_debugfs_write,
        .llseek         = seq_lseek,
-       .release        = single_release,
+       .release        = blk_mq_debugfs_release,
+};
+
+static const struct blk_mq_debugfs_attr blk_mq_debugfs_queue_attrs[] = {
+       {"poll_stat", 0400, queue_poll_stat_show},
+       {"state", 0600, queue_state_show, queue_state_write},
+       {},
 };
 
 static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = {
-       {"state", 0400, &hctx_state_fops},
-       {"flags", 0400, &hctx_flags_fops},
-       {"dispatch", 0400, &hctx_dispatch_fops},
-       {"ctx_map", 0400, &hctx_ctx_map_fops},
-       {"tags", 0400, &hctx_tags_fops},
-       {"tags_bitmap", 0400, &hctx_tags_bitmap_fops},
-       {"sched_tags", 0400, &hctx_sched_tags_fops},
-       {"sched_tags_bitmap", 0400, &hctx_sched_tags_bitmap_fops},
-       {"io_poll", 0600, &hctx_io_poll_fops},
-       {"stats", 0600, &hctx_stats_fops},
-       {"dispatched", 0600, &hctx_dispatched_fops},
-       {"queued", 0600, &hctx_queued_fops},
-       {"run", 0600, &hctx_run_fops},
-       {"active", 0400, &hctx_active_fops},
+       {"state", 0400, hctx_state_show},
+       {"flags", 0400, hctx_flags_show},
+       {"dispatch", 0400, .seq_ops = &hctx_dispatch_seq_ops},
+       {"ctx_map", 0400, hctx_ctx_map_show},
+       {"tags", 0400, hctx_tags_show},
+       {"tags_bitmap", 0400, hctx_tags_bitmap_show},
+       {"sched_tags", 0400, hctx_sched_tags_show},
+       {"sched_tags_bitmap", 0400, hctx_sched_tags_bitmap_show},
+       {"io_poll", 0600, hctx_io_poll_show, hctx_io_poll_write},
+       {"dispatched", 0600, hctx_dispatched_show, hctx_dispatched_write},
+       {"queued", 0600, hctx_queued_show, hctx_queued_write},
+       {"run", 0600, hctx_run_show, hctx_run_write},
+       {"active", 0400, hctx_active_show},
        {},
 };
 
 static const struct blk_mq_debugfs_attr blk_mq_debugfs_ctx_attrs[] = {
-       {"rq_list", 0400, &ctx_rq_list_fops},
-       {"dispatched", 0600, &ctx_dispatched_fops},
-       {"merged", 0600, &ctx_merged_fops},
-       {"completed", 0600, &ctx_completed_fops},
+       {"rq_list", 0400, .seq_ops = &ctx_rq_list_seq_ops},
+       {"dispatched", 0600, ctx_dispatched_show, ctx_dispatched_write},
+       {"merged", 0600, ctx_merged_show, ctx_merged_write},
+       {"completed", 0600, ctx_completed_show, ctx_completed_write},
        {},
 };
 
-int blk_mq_debugfs_register(struct request_queue *q, const char *name)
+static bool debugfs_create_files(struct dentry *parent, void *data,
+                                const struct blk_mq_debugfs_attr *attr)
+{
+       d_inode(parent)->i_private = data;
+
+       for (; attr->name; attr++) {
+               if (!debugfs_create_file(attr->name, attr->mode, parent,
+                                        (void *)attr, &blk_mq_debugfs_fops))
+                       return false;
+       }
+       return true;
+}
+
+int blk_mq_debugfs_register(struct request_queue *q)
 {
+       struct blk_mq_hw_ctx *hctx;
+       int i;
+
        if (!blk_debugfs_root)
                return -ENOENT;
 
-       q->debugfs_dir = debugfs_create_dir(name, blk_debugfs_root);
+       q->debugfs_dir = debugfs_create_dir(kobject_name(q->kobj.parent),
+                                           blk_debugfs_root);
        if (!q->debugfs_dir)
-               goto err;
+               return -ENOMEM;
 
-       if (blk_mq_debugfs_register_hctxs(q))
+       if (!debugfs_create_files(q->debugfs_dir, q,
+                                 blk_mq_debugfs_queue_attrs))
                goto err;
 
+       /*
+        * blk_mq_init_hctx() attempted to do this already, but q->debugfs_dir
+        * didn't exist yet (because we don't know what to name the directory
+        * until the queue is registered to a gendisk).
+        */
+       queue_for_each_hw_ctx(q, hctx, i) {
+               if (!hctx->debugfs_dir && blk_mq_debugfs_register_hctx(q, hctx))
+                       goto err;
+               if (q->elevator && !hctx->sched_debugfs_dir &&
+                   blk_mq_debugfs_register_sched_hctx(q, hctx))
+                       goto err;
+       }
+
        return 0;
 
 err:
@@ -684,30 +737,18 @@ err:
 void blk_mq_debugfs_unregister(struct request_queue *q)
 {
        debugfs_remove_recursive(q->debugfs_dir);
-       q->mq_debugfs_dir = NULL;
+       q->sched_debugfs_dir = NULL;
        q->debugfs_dir = NULL;
 }
 
-static bool debugfs_create_files(struct dentry *parent, void *data,
-                               const struct blk_mq_debugfs_attr *attr)
-{
-       for (; attr->name; attr++) {
-               if (!debugfs_create_file(attr->name, attr->mode, parent,
-                                        data, attr->fops))
-                       return false;
-       }
-       return true;
-}
-
-static int blk_mq_debugfs_register_ctx(struct request_queue *q,
-                                      struct blk_mq_ctx *ctx,
-                                      struct dentry *hctx_dir)
+static int blk_mq_debugfs_register_ctx(struct blk_mq_hw_ctx *hctx,
+                                      struct blk_mq_ctx *ctx)
 {
        struct dentry *ctx_dir;
        char name[20];
 
        snprintf(name, sizeof(name), "cpu%u", ctx->cpu);
-       ctx_dir = debugfs_create_dir(name, hctx_dir);
+       ctx_dir = debugfs_create_dir(name, hctx->debugfs_dir);
        if (!ctx_dir)
                return -ENOMEM;
 
@@ -717,28 +758,42 @@ static int blk_mq_debugfs_register_ctx(struct request_queue *q,
        return 0;
 }
 
-static int blk_mq_debugfs_register_hctx(struct request_queue *q,
-                                       struct blk_mq_hw_ctx *hctx)
+int blk_mq_debugfs_register_hctx(struct request_queue *q,
+                                struct blk_mq_hw_ctx *hctx)
 {
        struct blk_mq_ctx *ctx;
-       struct dentry *hctx_dir;
        char name[20];
        int i;
 
-       snprintf(name, sizeof(name), "%u", hctx->queue_num);
-       hctx_dir = debugfs_create_dir(name, q->mq_debugfs_dir);
-       if (!hctx_dir)
-               return -ENOMEM;
+       if (!q->debugfs_dir)
+               return -ENOENT;
 
-       if (!debugfs_create_files(hctx_dir, hctx, blk_mq_debugfs_hctx_attrs))
+       snprintf(name, sizeof(name), "hctx%u", hctx->queue_num);
+       hctx->debugfs_dir = debugfs_create_dir(name, q->debugfs_dir);
+       if (!hctx->debugfs_dir)
                return -ENOMEM;
 
+       if (!debugfs_create_files(hctx->debugfs_dir, hctx,
+                                 blk_mq_debugfs_hctx_attrs))
+               goto err;
+
        hctx_for_each_ctx(hctx, ctx, i) {
-               if (blk_mq_debugfs_register_ctx(q, ctx, hctx_dir))
-                       return -ENOMEM;
+               if (blk_mq_debugfs_register_ctx(hctx, ctx))
+                       goto err;
        }
 
        return 0;
+
+err:
+       blk_mq_debugfs_unregister_hctx(hctx);
+       return -ENOMEM;
+}
+
+void blk_mq_debugfs_unregister_hctx(struct blk_mq_hw_ctx *hctx)
+{
+       debugfs_remove_recursive(hctx->debugfs_dir);
+       hctx->sched_debugfs_dir = NULL;
+       hctx->debugfs_dir = NULL;
 }
 
 int blk_mq_debugfs_register_hctxs(struct request_queue *q)
@@ -746,27 +801,79 @@ int blk_mq_debugfs_register_hctxs(struct request_queue *q)
        struct blk_mq_hw_ctx *hctx;
        int i;
 
+       queue_for_each_hw_ctx(q, hctx, i) {
+               if (blk_mq_debugfs_register_hctx(q, hctx))
+                       return -ENOMEM;
+       }
+
+       return 0;
+}
+
+void blk_mq_debugfs_unregister_hctxs(struct request_queue *q)
+{
+       struct blk_mq_hw_ctx *hctx;
+       int i;
+
+       queue_for_each_hw_ctx(q, hctx, i)
+               blk_mq_debugfs_unregister_hctx(hctx);
+}
+
+int blk_mq_debugfs_register_sched(struct request_queue *q)
+{
+       struct elevator_type *e = q->elevator->type;
+
        if (!q->debugfs_dir)
                return -ENOENT;
 
-       q->mq_debugfs_dir = debugfs_create_dir("mq", q->debugfs_dir);
-       if (!q->mq_debugfs_dir)
-               goto err;
+       if (!e->queue_debugfs_attrs)
+               return 0;
 
-       queue_for_each_hw_ctx(q, hctx, i) {
-               if (blk_mq_debugfs_register_hctx(q, hctx))
-                       goto err;
-       }
+       q->sched_debugfs_dir = debugfs_create_dir("sched", q->debugfs_dir);
+       if (!q->sched_debugfs_dir)
+               return -ENOMEM;
+
+       if (!debugfs_create_files(q->sched_debugfs_dir, q,
+                                 e->queue_debugfs_attrs))
+               goto err;
 
        return 0;
 
 err:
-       blk_mq_debugfs_unregister_hctxs(q);
+       blk_mq_debugfs_unregister_sched(q);
        return -ENOMEM;
 }
 
-void blk_mq_debugfs_unregister_hctxs(struct request_queue *q)
+void blk_mq_debugfs_unregister_sched(struct request_queue *q)
+{
+       debugfs_remove_recursive(q->sched_debugfs_dir);
+       q->sched_debugfs_dir = NULL;
+}
+
+int blk_mq_debugfs_register_sched_hctx(struct request_queue *q,
+                                      struct blk_mq_hw_ctx *hctx)
+{
+       struct elevator_type *e = q->elevator->type;
+
+       if (!hctx->debugfs_dir)
+               return -ENOENT;
+
+       if (!e->hctx_debugfs_attrs)
+               return 0;
+
+       hctx->sched_debugfs_dir = debugfs_create_dir("sched",
+                                                    hctx->debugfs_dir);
+       if (!hctx->sched_debugfs_dir)
+               return -ENOMEM;
+
+       if (!debugfs_create_files(hctx->sched_debugfs_dir, hctx,
+                                 e->hctx_debugfs_attrs))
+               return -ENOMEM;
+
+       return 0;
+}
+
+void blk_mq_debugfs_unregister_sched_hctx(struct blk_mq_hw_ctx *hctx)
 {
-       debugfs_remove_recursive(q->mq_debugfs_dir);
-       q->mq_debugfs_dir = NULL;
+       debugfs_remove_recursive(hctx->sched_debugfs_dir);
+       hctx->sched_debugfs_dir = NULL;
 }