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" | |
d96b37c0 | 23 | #include "blk-mq-tag.h" |
07e4fead OS |
24 | |
25 | struct blk_mq_debugfs_attr { | |
26 | const char *name; | |
27 | umode_t mode; | |
28 | const struct file_operations *fops; | |
29 | }; | |
30 | ||
31 | static struct dentry *block_debugfs_root; | |
32 | ||
950cd7e9 OS |
33 | static int blk_mq_debugfs_seq_open(struct inode *inode, struct file *file, |
34 | const struct seq_operations *ops) | |
35 | { | |
36 | struct seq_file *m; | |
37 | int ret; | |
38 | ||
39 | ret = seq_open(file, ops); | |
40 | if (!ret) { | |
41 | m = file->private_data; | |
42 | m->private = inode->i_private; | |
43 | } | |
44 | return ret; | |
45 | } | |
46 | ||
9abb2ad2 OS |
47 | static int hctx_state_show(struct seq_file *m, void *v) |
48 | { | |
49 | struct blk_mq_hw_ctx *hctx = m->private; | |
50 | ||
51 | seq_printf(m, "0x%lx\n", hctx->state); | |
52 | return 0; | |
53 | } | |
54 | ||
55 | static int hctx_state_open(struct inode *inode, struct file *file) | |
56 | { | |
57 | return single_open(file, hctx_state_show, inode->i_private); | |
58 | } | |
59 | ||
60 | static const struct file_operations hctx_state_fops = { | |
61 | .open = hctx_state_open, | |
62 | .read = seq_read, | |
63 | .llseek = seq_lseek, | |
64 | .release = single_release, | |
65 | }; | |
66 | ||
67 | static int hctx_flags_show(struct seq_file *m, void *v) | |
68 | { | |
69 | struct blk_mq_hw_ctx *hctx = m->private; | |
70 | ||
71 | seq_printf(m, "0x%lx\n", hctx->flags); | |
72 | return 0; | |
73 | } | |
74 | ||
75 | static int hctx_flags_open(struct inode *inode, struct file *file) | |
76 | { | |
77 | return single_open(file, hctx_flags_show, inode->i_private); | |
78 | } | |
79 | ||
80 | static const struct file_operations hctx_flags_fops = { | |
81 | .open = hctx_flags_open, | |
82 | .read = seq_read, | |
83 | .llseek = seq_lseek, | |
84 | .release = single_release, | |
85 | }; | |
86 | ||
950cd7e9 OS |
87 | static int blk_mq_debugfs_rq_show(struct seq_file *m, void *v) |
88 | { | |
89 | struct request *rq = list_entry_rq(v); | |
90 | ||
7b393852 OS |
91 | seq_printf(m, "%p {.cmd_type=%u, .cmd_flags=0x%x, .rq_flags=0x%x, .tag=%d, .internal_tag=%d}\n", |
92 | rq, rq->cmd_type, rq->cmd_flags, (unsigned int)rq->rq_flags, | |
93 | rq->tag, rq->internal_tag); | |
950cd7e9 OS |
94 | return 0; |
95 | } | |
96 | ||
97 | static void *hctx_dispatch_start(struct seq_file *m, loff_t *pos) | |
98 | { | |
99 | struct blk_mq_hw_ctx *hctx = m->private; | |
100 | ||
101 | spin_lock(&hctx->lock); | |
102 | return seq_list_start(&hctx->dispatch, *pos); | |
103 | } | |
104 | ||
105 | static void *hctx_dispatch_next(struct seq_file *m, void *v, loff_t *pos) | |
106 | { | |
107 | struct blk_mq_hw_ctx *hctx = m->private; | |
108 | ||
109 | return seq_list_next(v, &hctx->dispatch, pos); | |
110 | } | |
111 | ||
112 | static void hctx_dispatch_stop(struct seq_file *m, void *v) | |
113 | { | |
114 | struct blk_mq_hw_ctx *hctx = m->private; | |
115 | ||
116 | spin_unlock(&hctx->lock); | |
117 | } | |
118 | ||
119 | static const struct seq_operations hctx_dispatch_seq_ops = { | |
120 | .start = hctx_dispatch_start, | |
121 | .next = hctx_dispatch_next, | |
122 | .stop = hctx_dispatch_stop, | |
123 | .show = blk_mq_debugfs_rq_show, | |
124 | }; | |
125 | ||
126 | static int hctx_dispatch_open(struct inode *inode, struct file *file) | |
127 | { | |
128 | return blk_mq_debugfs_seq_open(inode, file, &hctx_dispatch_seq_ops); | |
129 | } | |
130 | ||
131 | static const struct file_operations hctx_dispatch_fops = { | |
132 | .open = hctx_dispatch_open, | |
133 | .read = seq_read, | |
134 | .llseek = seq_lseek, | |
135 | .release = seq_release, | |
136 | }; | |
137 | ||
0bfa5288 OS |
138 | static int hctx_ctx_map_show(struct seq_file *m, void *v) |
139 | { | |
140 | struct blk_mq_hw_ctx *hctx = m->private; | |
141 | ||
142 | sbitmap_bitmap_show(&hctx->ctx_map, m); | |
143 | return 0; | |
144 | } | |
145 | ||
146 | static int hctx_ctx_map_open(struct inode *inode, struct file *file) | |
147 | { | |
148 | return single_open(file, hctx_ctx_map_show, inode->i_private); | |
149 | } | |
150 | ||
151 | static const struct file_operations hctx_ctx_map_fops = { | |
152 | .open = hctx_ctx_map_open, | |
153 | .read = seq_read, | |
154 | .llseek = seq_lseek, | |
155 | .release = single_release, | |
156 | }; | |
157 | ||
d96b37c0 OS |
158 | static void blk_mq_debugfs_tags_show(struct seq_file *m, |
159 | struct blk_mq_tags *tags) | |
160 | { | |
161 | seq_printf(m, "nr_tags=%u\n", tags->nr_tags); | |
162 | seq_printf(m, "nr_reserved_tags=%u\n", tags->nr_reserved_tags); | |
163 | seq_printf(m, "active_queues=%d\n", | |
164 | atomic_read(&tags->active_queues)); | |
165 | ||
166 | seq_puts(m, "\nbitmap_tags:\n"); | |
167 | sbitmap_queue_show(&tags->bitmap_tags, m); | |
168 | ||
169 | if (tags->nr_reserved_tags) { | |
170 | seq_puts(m, "\nbreserved_tags:\n"); | |
171 | sbitmap_queue_show(&tags->breserved_tags, m); | |
172 | } | |
173 | } | |
174 | ||
175 | static int hctx_tags_show(struct seq_file *m, void *v) | |
176 | { | |
177 | struct blk_mq_hw_ctx *hctx = m->private; | |
178 | struct request_queue *q = hctx->queue; | |
179 | ||
180 | mutex_lock(&q->sysfs_lock); | |
181 | if (hctx->tags) | |
182 | blk_mq_debugfs_tags_show(m, hctx->tags); | |
183 | mutex_unlock(&q->sysfs_lock); | |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
188 | static int hctx_tags_open(struct inode *inode, struct file *file) | |
189 | { | |
190 | return single_open(file, hctx_tags_show, inode->i_private); | |
191 | } | |
192 | ||
193 | static const struct file_operations hctx_tags_fops = { | |
194 | .open = hctx_tags_open, | |
195 | .read = seq_read, | |
196 | .llseek = seq_lseek, | |
197 | .release = single_release, | |
198 | }; | |
199 | ||
200 | static int hctx_sched_tags_show(struct seq_file *m, void *v) | |
201 | { | |
202 | struct blk_mq_hw_ctx *hctx = m->private; | |
203 | struct request_queue *q = hctx->queue; | |
204 | ||
205 | mutex_lock(&q->sysfs_lock); | |
206 | if (hctx->sched_tags) | |
207 | blk_mq_debugfs_tags_show(m, hctx->sched_tags); | |
208 | mutex_unlock(&q->sysfs_lock); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int hctx_sched_tags_open(struct inode *inode, struct file *file) | |
214 | { | |
215 | return single_open(file, hctx_sched_tags_show, inode->i_private); | |
216 | } | |
217 | ||
218 | static const struct file_operations hctx_sched_tags_fops = { | |
219 | .open = hctx_sched_tags_open, | |
220 | .read = seq_read, | |
221 | .llseek = seq_lseek, | |
222 | .release = single_release, | |
223 | }; | |
224 | ||
950cd7e9 OS |
225 | static void *ctx_rq_list_start(struct seq_file *m, loff_t *pos) |
226 | { | |
227 | struct blk_mq_ctx *ctx = m->private; | |
228 | ||
229 | spin_lock(&ctx->lock); | |
230 | return seq_list_start(&ctx->rq_list, *pos); | |
231 | } | |
232 | ||
233 | static void *ctx_rq_list_next(struct seq_file *m, void *v, loff_t *pos) | |
234 | { | |
235 | struct blk_mq_ctx *ctx = m->private; | |
236 | ||
237 | return seq_list_next(v, &ctx->rq_list, pos); | |
238 | } | |
239 | ||
240 | static void ctx_rq_list_stop(struct seq_file *m, void *v) | |
241 | { | |
242 | struct blk_mq_ctx *ctx = m->private; | |
243 | ||
244 | spin_unlock(&ctx->lock); | |
245 | } | |
246 | ||
247 | static const struct seq_operations ctx_rq_list_seq_ops = { | |
248 | .start = ctx_rq_list_start, | |
249 | .next = ctx_rq_list_next, | |
250 | .stop = ctx_rq_list_stop, | |
251 | .show = blk_mq_debugfs_rq_show, | |
252 | }; | |
253 | ||
254 | static int ctx_rq_list_open(struct inode *inode, struct file *file) | |
255 | { | |
256 | return blk_mq_debugfs_seq_open(inode, file, &ctx_rq_list_seq_ops); | |
257 | } | |
258 | ||
259 | static const struct file_operations ctx_rq_list_fops = { | |
260 | .open = ctx_rq_list_open, | |
261 | .read = seq_read, | |
262 | .llseek = seq_lseek, | |
263 | .release = seq_release, | |
264 | }; | |
265 | ||
07e4fead | 266 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = { |
9abb2ad2 OS |
267 | {"state", 0400, &hctx_state_fops}, |
268 | {"flags", 0400, &hctx_flags_fops}, | |
950cd7e9 | 269 | {"dispatch", 0400, &hctx_dispatch_fops}, |
0bfa5288 | 270 | {"ctx_map", 0400, &hctx_ctx_map_fops}, |
d96b37c0 OS |
271 | {"tags", 0400, &hctx_tags_fops}, |
272 | {"sched_tags", 0400, &hctx_sched_tags_fops}, | |
07e4fead OS |
273 | }; |
274 | ||
275 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_ctx_attrs[] = { | |
950cd7e9 | 276 | {"rq_list", 0400, &ctx_rq_list_fops}, |
07e4fead OS |
277 | }; |
278 | ||
279 | int blk_mq_debugfs_register(struct request_queue *q, const char *name) | |
280 | { | |
281 | if (!block_debugfs_root) | |
282 | return -ENOENT; | |
283 | ||
284 | q->debugfs_dir = debugfs_create_dir(name, block_debugfs_root); | |
285 | if (!q->debugfs_dir) | |
286 | goto err; | |
287 | ||
288 | if (blk_mq_debugfs_register_hctxs(q)) | |
289 | goto err; | |
290 | ||
291 | return 0; | |
292 | ||
293 | err: | |
294 | blk_mq_debugfs_unregister(q); | |
295 | return -ENOMEM; | |
296 | } | |
297 | ||
298 | void blk_mq_debugfs_unregister(struct request_queue *q) | |
299 | { | |
300 | debugfs_remove_recursive(q->debugfs_dir); | |
301 | q->mq_debugfs_dir = NULL; | |
302 | q->debugfs_dir = NULL; | |
303 | } | |
304 | ||
305 | static int blk_mq_debugfs_register_ctx(struct request_queue *q, | |
306 | struct blk_mq_ctx *ctx, | |
307 | struct dentry *hctx_dir) | |
308 | { | |
309 | struct dentry *ctx_dir; | |
310 | char name[20]; | |
311 | int i; | |
312 | ||
313 | snprintf(name, sizeof(name), "cpu%u", ctx->cpu); | |
314 | ctx_dir = debugfs_create_dir(name, hctx_dir); | |
315 | if (!ctx_dir) | |
316 | return -ENOMEM; | |
317 | ||
318 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_ctx_attrs); i++) { | |
319 | const struct blk_mq_debugfs_attr *attr; | |
320 | ||
321 | attr = &blk_mq_debugfs_ctx_attrs[i]; | |
322 | if (!debugfs_create_file(attr->name, attr->mode, ctx_dir, ctx, | |
323 | attr->fops)) | |
324 | return -ENOMEM; | |
325 | } | |
326 | ||
327 | return 0; | |
328 | } | |
329 | ||
330 | static int blk_mq_debugfs_register_hctx(struct request_queue *q, | |
331 | struct blk_mq_hw_ctx *hctx) | |
332 | { | |
333 | struct blk_mq_ctx *ctx; | |
334 | struct dentry *hctx_dir; | |
335 | char name[20]; | |
336 | int i; | |
337 | ||
338 | snprintf(name, sizeof(name), "%u", hctx->queue_num); | |
339 | hctx_dir = debugfs_create_dir(name, q->mq_debugfs_dir); | |
340 | if (!hctx_dir) | |
341 | return -ENOMEM; | |
342 | ||
343 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_hctx_attrs); i++) { | |
344 | const struct blk_mq_debugfs_attr *attr; | |
345 | ||
346 | attr = &blk_mq_debugfs_hctx_attrs[i]; | |
347 | if (!debugfs_create_file(attr->name, attr->mode, hctx_dir, hctx, | |
348 | attr->fops)) | |
349 | return -ENOMEM; | |
350 | } | |
351 | ||
352 | hctx_for_each_ctx(hctx, ctx, i) { | |
353 | if (blk_mq_debugfs_register_ctx(q, ctx, hctx_dir)) | |
354 | return -ENOMEM; | |
355 | } | |
356 | ||
357 | return 0; | |
358 | } | |
359 | ||
360 | int blk_mq_debugfs_register_hctxs(struct request_queue *q) | |
361 | { | |
362 | struct blk_mq_hw_ctx *hctx; | |
363 | int i; | |
364 | ||
365 | if (!q->debugfs_dir) | |
366 | return -ENOENT; | |
367 | ||
368 | q->mq_debugfs_dir = debugfs_create_dir("mq", q->debugfs_dir); | |
369 | if (!q->mq_debugfs_dir) | |
370 | goto err; | |
371 | ||
372 | queue_for_each_hw_ctx(q, hctx, i) { | |
373 | if (blk_mq_debugfs_register_hctx(q, hctx)) | |
374 | goto err; | |
375 | } | |
376 | ||
377 | return 0; | |
378 | ||
379 | err: | |
380 | blk_mq_debugfs_unregister_hctxs(q); | |
381 | return -ENOMEM; | |
382 | } | |
383 | ||
384 | void blk_mq_debugfs_unregister_hctxs(struct request_queue *q) | |
385 | { | |
386 | debugfs_remove_recursive(q->mq_debugfs_dir); | |
387 | q->mq_debugfs_dir = NULL; | |
388 | } | |
389 | ||
390 | void blk_mq_debugfs_init(void) | |
391 | { | |
392 | block_debugfs_root = debugfs_create_dir("block", NULL); | |
393 | } |