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 | ||
950cd7e9 OS |
32 | static int blk_mq_debugfs_seq_open(struct inode *inode, struct file *file, |
33 | const struct seq_operations *ops) | |
34 | { | |
35 | struct seq_file *m; | |
36 | int ret; | |
37 | ||
38 | ret = seq_open(file, ops); | |
39 | if (!ret) { | |
40 | m = file->private_data; | |
41 | m->private = inode->i_private; | |
42 | } | |
43 | return ret; | |
44 | } | |
45 | ||
9abb2ad2 OS |
46 | static int hctx_state_show(struct seq_file *m, void *v) |
47 | { | |
48 | struct blk_mq_hw_ctx *hctx = m->private; | |
49 | ||
50 | seq_printf(m, "0x%lx\n", hctx->state); | |
51 | return 0; | |
52 | } | |
53 | ||
54 | static int hctx_state_open(struct inode *inode, struct file *file) | |
55 | { | |
56 | return single_open(file, hctx_state_show, inode->i_private); | |
57 | } | |
58 | ||
59 | static const struct file_operations hctx_state_fops = { | |
60 | .open = hctx_state_open, | |
61 | .read = seq_read, | |
62 | .llseek = seq_lseek, | |
63 | .release = single_release, | |
64 | }; | |
65 | ||
66 | static int hctx_flags_show(struct seq_file *m, void *v) | |
67 | { | |
68 | struct blk_mq_hw_ctx *hctx = m->private; | |
69 | ||
70 | seq_printf(m, "0x%lx\n", hctx->flags); | |
71 | return 0; | |
72 | } | |
73 | ||
74 | static int hctx_flags_open(struct inode *inode, struct file *file) | |
75 | { | |
76 | return single_open(file, hctx_flags_show, inode->i_private); | |
77 | } | |
78 | ||
79 | static const struct file_operations hctx_flags_fops = { | |
80 | .open = hctx_flags_open, | |
81 | .read = seq_read, | |
82 | .llseek = seq_lseek, | |
83 | .release = single_release, | |
84 | }; | |
85 | ||
950cd7e9 OS |
86 | static int blk_mq_debugfs_rq_show(struct seq_file *m, void *v) |
87 | { | |
88 | struct request *rq = list_entry_rq(v); | |
89 | ||
7b393852 OS |
90 | seq_printf(m, "%p {.cmd_type=%u, .cmd_flags=0x%x, .rq_flags=0x%x, .tag=%d, .internal_tag=%d}\n", |
91 | rq, rq->cmd_type, rq->cmd_flags, (unsigned int)rq->rq_flags, | |
92 | rq->tag, rq->internal_tag); | |
950cd7e9 OS |
93 | return 0; |
94 | } | |
95 | ||
96 | static void *hctx_dispatch_start(struct seq_file *m, loff_t *pos) | |
97 | { | |
98 | struct blk_mq_hw_ctx *hctx = m->private; | |
99 | ||
100 | spin_lock(&hctx->lock); | |
101 | return seq_list_start(&hctx->dispatch, *pos); | |
102 | } | |
103 | ||
104 | static void *hctx_dispatch_next(struct seq_file *m, void *v, loff_t *pos) | |
105 | { | |
106 | struct blk_mq_hw_ctx *hctx = m->private; | |
107 | ||
108 | return seq_list_next(v, &hctx->dispatch, pos); | |
109 | } | |
110 | ||
111 | static void hctx_dispatch_stop(struct seq_file *m, void *v) | |
112 | { | |
113 | struct blk_mq_hw_ctx *hctx = m->private; | |
114 | ||
115 | spin_unlock(&hctx->lock); | |
116 | } | |
117 | ||
118 | static const struct seq_operations hctx_dispatch_seq_ops = { | |
119 | .start = hctx_dispatch_start, | |
120 | .next = hctx_dispatch_next, | |
121 | .stop = hctx_dispatch_stop, | |
122 | .show = blk_mq_debugfs_rq_show, | |
123 | }; | |
124 | ||
125 | static int hctx_dispatch_open(struct inode *inode, struct file *file) | |
126 | { | |
127 | return blk_mq_debugfs_seq_open(inode, file, &hctx_dispatch_seq_ops); | |
128 | } | |
129 | ||
130 | static const struct file_operations hctx_dispatch_fops = { | |
131 | .open = hctx_dispatch_open, | |
132 | .read = seq_read, | |
133 | .llseek = seq_lseek, | |
134 | .release = seq_release, | |
135 | }; | |
136 | ||
137 | static void *ctx_rq_list_start(struct seq_file *m, loff_t *pos) | |
138 | { | |
139 | struct blk_mq_ctx *ctx = m->private; | |
140 | ||
141 | spin_lock(&ctx->lock); | |
142 | return seq_list_start(&ctx->rq_list, *pos); | |
143 | } | |
144 | ||
145 | static void *ctx_rq_list_next(struct seq_file *m, void *v, loff_t *pos) | |
146 | { | |
147 | struct blk_mq_ctx *ctx = m->private; | |
148 | ||
149 | return seq_list_next(v, &ctx->rq_list, pos); | |
150 | } | |
151 | ||
152 | static void ctx_rq_list_stop(struct seq_file *m, void *v) | |
153 | { | |
154 | struct blk_mq_ctx *ctx = m->private; | |
155 | ||
156 | spin_unlock(&ctx->lock); | |
157 | } | |
158 | ||
159 | static const struct seq_operations ctx_rq_list_seq_ops = { | |
160 | .start = ctx_rq_list_start, | |
161 | .next = ctx_rq_list_next, | |
162 | .stop = ctx_rq_list_stop, | |
163 | .show = blk_mq_debugfs_rq_show, | |
164 | }; | |
165 | ||
166 | static int ctx_rq_list_open(struct inode *inode, struct file *file) | |
167 | { | |
168 | return blk_mq_debugfs_seq_open(inode, file, &ctx_rq_list_seq_ops); | |
169 | } | |
170 | ||
171 | static const struct file_operations ctx_rq_list_fops = { | |
172 | .open = ctx_rq_list_open, | |
173 | .read = seq_read, | |
174 | .llseek = seq_lseek, | |
175 | .release = seq_release, | |
176 | }; | |
177 | ||
07e4fead | 178 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_hctx_attrs[] = { |
9abb2ad2 OS |
179 | {"state", 0400, &hctx_state_fops}, |
180 | {"flags", 0400, &hctx_flags_fops}, | |
950cd7e9 | 181 | {"dispatch", 0400, &hctx_dispatch_fops}, |
07e4fead OS |
182 | }; |
183 | ||
184 | static const struct blk_mq_debugfs_attr blk_mq_debugfs_ctx_attrs[] = { | |
950cd7e9 | 185 | {"rq_list", 0400, &ctx_rq_list_fops}, |
07e4fead OS |
186 | }; |
187 | ||
188 | int blk_mq_debugfs_register(struct request_queue *q, const char *name) | |
189 | { | |
190 | if (!block_debugfs_root) | |
191 | return -ENOENT; | |
192 | ||
193 | q->debugfs_dir = debugfs_create_dir(name, block_debugfs_root); | |
194 | if (!q->debugfs_dir) | |
195 | goto err; | |
196 | ||
197 | if (blk_mq_debugfs_register_hctxs(q)) | |
198 | goto err; | |
199 | ||
200 | return 0; | |
201 | ||
202 | err: | |
203 | blk_mq_debugfs_unregister(q); | |
204 | return -ENOMEM; | |
205 | } | |
206 | ||
207 | void blk_mq_debugfs_unregister(struct request_queue *q) | |
208 | { | |
209 | debugfs_remove_recursive(q->debugfs_dir); | |
210 | q->mq_debugfs_dir = NULL; | |
211 | q->debugfs_dir = NULL; | |
212 | } | |
213 | ||
214 | static int blk_mq_debugfs_register_ctx(struct request_queue *q, | |
215 | struct blk_mq_ctx *ctx, | |
216 | struct dentry *hctx_dir) | |
217 | { | |
218 | struct dentry *ctx_dir; | |
219 | char name[20]; | |
220 | int i; | |
221 | ||
222 | snprintf(name, sizeof(name), "cpu%u", ctx->cpu); | |
223 | ctx_dir = debugfs_create_dir(name, hctx_dir); | |
224 | if (!ctx_dir) | |
225 | return -ENOMEM; | |
226 | ||
227 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_ctx_attrs); i++) { | |
228 | const struct blk_mq_debugfs_attr *attr; | |
229 | ||
230 | attr = &blk_mq_debugfs_ctx_attrs[i]; | |
231 | if (!debugfs_create_file(attr->name, attr->mode, ctx_dir, ctx, | |
232 | attr->fops)) | |
233 | return -ENOMEM; | |
234 | } | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | static int blk_mq_debugfs_register_hctx(struct request_queue *q, | |
240 | struct blk_mq_hw_ctx *hctx) | |
241 | { | |
242 | struct blk_mq_ctx *ctx; | |
243 | struct dentry *hctx_dir; | |
244 | char name[20]; | |
245 | int i; | |
246 | ||
247 | snprintf(name, sizeof(name), "%u", hctx->queue_num); | |
248 | hctx_dir = debugfs_create_dir(name, q->mq_debugfs_dir); | |
249 | if (!hctx_dir) | |
250 | return -ENOMEM; | |
251 | ||
252 | for (i = 0; i < ARRAY_SIZE(blk_mq_debugfs_hctx_attrs); i++) { | |
253 | const struct blk_mq_debugfs_attr *attr; | |
254 | ||
255 | attr = &blk_mq_debugfs_hctx_attrs[i]; | |
256 | if (!debugfs_create_file(attr->name, attr->mode, hctx_dir, hctx, | |
257 | attr->fops)) | |
258 | return -ENOMEM; | |
259 | } | |
260 | ||
261 | hctx_for_each_ctx(hctx, ctx, i) { | |
262 | if (blk_mq_debugfs_register_ctx(q, ctx, hctx_dir)) | |
263 | return -ENOMEM; | |
264 | } | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | int blk_mq_debugfs_register_hctxs(struct request_queue *q) | |
270 | { | |
271 | struct blk_mq_hw_ctx *hctx; | |
272 | int i; | |
273 | ||
274 | if (!q->debugfs_dir) | |
275 | return -ENOENT; | |
276 | ||
277 | q->mq_debugfs_dir = debugfs_create_dir("mq", q->debugfs_dir); | |
278 | if (!q->mq_debugfs_dir) | |
279 | goto err; | |
280 | ||
281 | queue_for_each_hw_ctx(q, hctx, i) { | |
282 | if (blk_mq_debugfs_register_hctx(q, hctx)) | |
283 | goto err; | |
284 | } | |
285 | ||
286 | return 0; | |
287 | ||
288 | err: | |
289 | blk_mq_debugfs_unregister_hctxs(q); | |
290 | return -ENOMEM; | |
291 | } | |
292 | ||
293 | void blk_mq_debugfs_unregister_hctxs(struct request_queue *q) | |
294 | { | |
295 | debugfs_remove_recursive(q->mq_debugfs_dir); | |
296 | q->mq_debugfs_dir = NULL; | |
297 | } | |
298 | ||
299 | void blk_mq_debugfs_init(void) | |
300 | { | |
301 | block_debugfs_root = debugfs_create_dir("block", NULL); | |
302 | } |