Commit | Line | Data |
---|---|---|
07e4fead OS |
1 | /* |
2 | * Copyright (C) 2017 Facebook | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public | |
6 | * License v2 as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
11 | * General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
15 | */ | |
16 | ||
17 | #include <linux/kernel.h> | |
18 | #include <linux/blkdev.h> | |
19 | #include <linux/debugfs.h> | |
20 | ||
21 | #include <linux/blk-mq.h> | |
22 | #include "blk-mq.h" | |
23 | ||
24 | struct blk_mq_debugfs_attr { | |
25 | const char *name; | |
26 | umode_t mode; | |
27 | const struct file_operations *fops; | |
28 | }; | |
29 | ||
30 | static struct dentry *block_debugfs_root; | |
31 | ||
9abb2ad2 OS |
32 | static int hctx_state_show(struct seq_file *m, void *v) |
33 | { | |
34 | struct blk_mq_hw_ctx *hctx = m->private; | |
35 | ||
36 | seq_printf(m, "0x%lx\n", hctx->state); | |
37 | return 0; | |
38 | } | |
39 | ||
40 | static int hctx_state_open(struct inode *inode, struct file *file) | |
41 | { | |
42 | return single_open(file, hctx_state_show, inode->i_private); | |
43 | } | |
44 | ||
45 | static const struct file_operations hctx_state_fops = { | |
46 | .open = hctx_state_open, | |
47 | .read = seq_read, | |
48 | .llseek = seq_lseek, | |
49 | .release = single_release, | |
50 | }; | |
51 | ||
52 | static int hctx_flags_show(struct seq_file *m, void *v) | |
53 | { | |
54 | struct blk_mq_hw_ctx *hctx = m->private; | |
55 | ||
56 | seq_printf(m, "0x%lx\n", hctx->flags); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | static int hctx_flags_open(struct inode *inode, struct file *file) | |
61 | { | |
62 | return single_open(file, hctx_flags_show, inode->i_private); | |
63 | } | |
64 | ||
65 | static const struct file_operations hctx_flags_fops = { | |
66 | .open = hctx_flags_open, | |
67 | .read = seq_read, | |
68 | .llseek = seq_lseek, | |
69 | .release = single_release, | |
70 | }; | |
71 | ||
07e4fead | 72 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = { |
9abb2ad2 OS |
73 | {"state", 0400, &hctx_state_fops}, |
74 | {"flags", 0400, &hctx_flags_fops}, | |
07e4fead OS |
75 | }; |
76 | ||
77 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_ctx_attrs[] = { | |
78 | }; | |
79 | ||
80 | int blk_mq_debugfs_register(struct request_queue *q, const char *name) | |
81 | { | |
82 | if (!block_debugfs_root) | |
83 | return -ENOENT; | |
84 | ||
85 | q->debugfs_dir = debugfs_create_dir(name, block_debugfs_root); | |
86 | if (!q->debugfs_dir) | |
87 | goto err; | |
88 | ||
89 | if (blk_mq_debugfs_register_hctxs(q)) | |
90 | goto err; | |
91 | ||
92 | return 0; | |
93 | ||
94 | err: | |
95 | blk_mq_debugfs_unregister(q); | |
96 | return -ENOMEM; | |
97 | } | |
98 | ||
99 | void blk_mq_debugfs_unregister(struct request_queue *q) | |
100 | { | |
101 | debugfs_remove_recursive(q->debugfs_dir); | |
102 | q->mq_debugfs_dir = NULL; | |
103 | q->debugfs_dir = NULL; | |
104 | } | |
105 | ||
106 | static int blk_mq_debugfs_register_ctx(struct request_queue *q, | |
107 | struct blk_mq_ctx *ctx, | |
108 | struct dentry *hctx_dir) | |
109 | { | |
110 | struct dentry *ctx_dir; | |
111 | char name[20]; | |
112 | int i; | |
113 | ||
114 | snprintf(name, sizeof(name), "cpu%u", ctx->cpu); | |
115 | ctx_dir = debugfs_create_dir(name, hctx_dir); | |
116 | if (!ctx_dir) | |
117 | return -ENOMEM; | |
118 | ||
119 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_ctx_attrs); i++) { | |
120 | const struct blk_mq_debugfs_attr *attr; | |
121 | ||
122 | attr = &blk_mq_debugfs_ctx_attrs[i]; | |
123 | if (!debugfs_create_file(attr->name, attr->mode, ctx_dir, ctx, | |
124 | attr->fops)) | |
125 | return -ENOMEM; | |
126 | } | |
127 | ||
128 | return 0; | |
129 | } | |
130 | ||
131 | static int blk_mq_debugfs_register_hctx(struct request_queue *q, | |
132 | struct blk_mq_hw_ctx *hctx) | |
133 | { | |
134 | struct blk_mq_ctx *ctx; | |
135 | struct dentry *hctx_dir; | |
136 | char name[20]; | |
137 | int i; | |
138 | ||
139 | snprintf(name, sizeof(name), "%u", hctx->queue_num); | |
140 | hctx_dir = debugfs_create_dir(name, q->mq_debugfs_dir); | |
141 | if (!hctx_dir) | |
142 | return -ENOMEM; | |
143 | ||
144 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_hctx_attrs); i++) { | |
145 | const struct blk_mq_debugfs_attr *attr; | |
146 | ||
147 | attr = &blk_mq_debugfs_hctx_attrs[i]; | |
148 | if (!debugfs_create_file(attr->name, attr->mode, hctx_dir, hctx, | |
149 | attr->fops)) | |
150 | return -ENOMEM; | |
151 | } | |
152 | ||
153 | hctx_for_each_ctx(hctx, ctx, i) { | |
154 | if (blk_mq_debugfs_register_ctx(q, ctx, hctx_dir)) | |
155 | return -ENOMEM; | |
156 | } | |
157 | ||
158 | return 0; | |
159 | } | |
160 | ||
161 | int blk_mq_debugfs_register_hctxs(struct request_queue *q) | |
162 | { | |
163 | struct blk_mq_hw_ctx *hctx; | |
164 | int i; | |
165 | ||
166 | if (!q->debugfs_dir) | |
167 | return -ENOENT; | |
168 | ||
169 | q->mq_debugfs_dir = debugfs_create_dir("mq", q->debugfs_dir); | |
170 | if (!q->mq_debugfs_dir) | |
171 | goto err; | |
172 | ||
173 | queue_for_each_hw_ctx(q, hctx, i) { | |
174 | if (blk_mq_debugfs_register_hctx(q, hctx)) | |
175 | goto err; | |
176 | } | |
177 | ||
178 | return 0; | |
179 | ||
180 | err: | |
181 | blk_mq_debugfs_unregister_hctxs(q); | |
182 | return -ENOMEM; | |
183 | } | |
184 | ||
185 | void blk_mq_debugfs_unregister_hctxs(struct request_queue *q) | |
186 | { | |
187 | debugfs_remove_recursive(q->mq_debugfs_dir); | |
188 | q->mq_debugfs_dir = NULL; | |
189 | } | |
190 | ||
191 | void blk_mq_debugfs_init(void) | |
192 | { | |
193 | block_debugfs_root = debugfs_create_dir("block", NULL); | |
194 | } |