1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright IBM Corp. 2008, 2009 |
4 | * |
5 | * Author: Jan Glauber (jang@linux.vnet.ibm.com) |
6 | */ |
7 | #include <linux/seq_file.h> |
8 | #include <linux/debugfs.h> |
9 | #include <linux/uaccess.h> |
10 | #include <linux/export.h> |
11 | #include <linux/slab.h> |
12 | #include <asm/debug.h> |
13 | #include "qdio_debug.h" |
14 | #include "qdio.h" |
15 | |
16 | debug_info_t *qdio_dbf_setup; |
17 | debug_info_t *qdio_dbf_error; |
18 | |
19 | static struct dentry *debugfs_root; |
20 | #define QDIO_DEBUGFS_NAME_LEN 10 |
21 | #define QDIO_DBF_NAME_LEN 20 |
22 | |
23 | struct qdio_dbf_entry { |
24 | char dbf_name[QDIO_DBF_NAME_LEN]; |
25 | debug_info_t *dbf_info; |
26 | struct list_head dbf_list; |
27 | }; |
28 | |
29 | static LIST_HEAD(qdio_dbf_list); |
30 | static DEFINE_MUTEX(qdio_dbf_list_mutex); |
31 | |
32 | static debug_info_t *qdio_get_dbf_entry(char *name) |
33 | { |
34 | struct qdio_dbf_entry *entry; |
35 | debug_info_t *rc = NULL; |
36 | |
37 | mutex_lock(&qdio_dbf_list_mutex); |
38 | list_for_each_entry(entry, &qdio_dbf_list, dbf_list) { |
39 | if (strcmp(entry->dbf_name, name) == 0) { |
40 | rc = entry->dbf_info; |
41 | break; |
42 | } |
43 | } |
44 | mutex_unlock(lock: &qdio_dbf_list_mutex); |
45 | return rc; |
46 | } |
47 | |
48 | static void qdio_clear_dbf_list(void) |
49 | { |
50 | struct qdio_dbf_entry *entry, *tmp; |
51 | |
52 | mutex_lock(&qdio_dbf_list_mutex); |
53 | list_for_each_entry_safe(entry, tmp, &qdio_dbf_list, dbf_list) { |
54 | list_del(entry: &entry->dbf_list); |
55 | debug_unregister(entry->dbf_info); |
56 | kfree(objp: entry); |
57 | } |
58 | mutex_unlock(lock: &qdio_dbf_list_mutex); |
59 | } |
60 | |
61 | int qdio_allocate_dbf(struct qdio_irq *irq_ptr) |
62 | { |
63 | char text[QDIO_DBF_NAME_LEN]; |
64 | struct qdio_dbf_entry *new_entry; |
65 | |
66 | DBF_EVENT("irq:%8lx" , (unsigned long)irq_ptr); |
67 | |
68 | /* allocate trace view for the interface */ |
69 | snprintf(buf: text, QDIO_DBF_NAME_LEN, fmt: "qdio_%s" , |
70 | dev_name(dev: &irq_ptr->cdev->dev)); |
71 | irq_ptr->debug_area = qdio_get_dbf_entry(text); |
72 | if (irq_ptr->debug_area) |
73 | DBF_DEV_EVENT(DBF_ERR, irq_ptr, "dbf reused" ); |
74 | else { |
75 | irq_ptr->debug_area = debug_register(text, 2, 1, 16); |
76 | if (!irq_ptr->debug_area) |
77 | return -ENOMEM; |
78 | if (debug_register_view(irq_ptr->debug_area, |
79 | &debug_hex_ascii_view)) { |
80 | debug_unregister(irq_ptr->debug_area); |
81 | return -ENOMEM; |
82 | } |
83 | debug_set_level(irq_ptr->debug_area, DBF_WARN); |
84 | DBF_DEV_EVENT(DBF_ERR, irq_ptr, "dbf created" ); |
85 | new_entry = kzalloc(size: sizeof(struct qdio_dbf_entry), GFP_KERNEL); |
86 | if (!new_entry) { |
87 | debug_unregister(irq_ptr->debug_area); |
88 | return -ENOMEM; |
89 | } |
90 | strscpy(p: new_entry->dbf_name, q: text, QDIO_DBF_NAME_LEN); |
91 | new_entry->dbf_info = irq_ptr->debug_area; |
92 | mutex_lock(&qdio_dbf_list_mutex); |
93 | list_add(new: &new_entry->dbf_list, head: &qdio_dbf_list); |
94 | mutex_unlock(lock: &qdio_dbf_list_mutex); |
95 | } |
96 | return 0; |
97 | } |
98 | |
99 | static int qstat_show(struct seq_file *m, void *v) |
100 | { |
101 | unsigned char state; |
102 | struct qdio_q *q = m->private; |
103 | int i; |
104 | |
105 | if (!q) |
106 | return 0; |
107 | |
108 | seq_printf(m, fmt: "Timestamp: %llx\n" , q->timestamp); |
109 | seq_printf(m, fmt: "Last Data IRQ: %llx Last AI: %llx\n" , |
110 | q->irq_ptr->last_data_irq_time, last_ai_time); |
111 | seq_printf(m, fmt: "nr_used: %d ftc: %d\n" , |
112 | atomic_read(v: &q->nr_buf_used), q->first_to_check); |
113 | if (q->is_input_q) { |
114 | seq_printf(m, fmt: "batch start: %u batch count: %u\n" , |
115 | q->u.in.batch_start, q->u.in.batch_count); |
116 | seq_printf(m, fmt: "DSCI: %x IRQs disabled: %u\n" , |
117 | *(u8 *)q->irq_ptr->dsci, |
118 | test_bit(QDIO_IRQ_DISABLED, |
119 | &q->irq_ptr->poll_state)); |
120 | } |
121 | seq_printf(m, fmt: "SBAL states:\n" ); |
122 | seq_printf(m, fmt: "|0 |8 |16 |24 |32 |40 |48 |56 63|\n" ); |
123 | |
124 | for (i = 0; i < QDIO_MAX_BUFFERS_PER_Q; i++) { |
125 | debug_get_buf_state(q, bufnr: i, state: &state); |
126 | switch (state) { |
127 | case SLSB_P_INPUT_NOT_INIT: |
128 | case SLSB_P_OUTPUT_NOT_INIT: |
129 | seq_printf(m, fmt: "N" ); |
130 | break; |
131 | case SLSB_P_OUTPUT_PENDING: |
132 | seq_printf(m, fmt: "P" ); |
133 | break; |
134 | case SLSB_P_INPUT_PRIMED: |
135 | case SLSB_CU_OUTPUT_PRIMED: |
136 | seq_printf(m, fmt: "+" ); |
137 | break; |
138 | case SLSB_P_INPUT_ACK: |
139 | seq_printf(m, fmt: "A" ); |
140 | break; |
141 | case SLSB_P_INPUT_ERROR: |
142 | case SLSB_P_OUTPUT_ERROR: |
143 | seq_printf(m, fmt: "x" ); |
144 | break; |
145 | case SLSB_CU_INPUT_EMPTY: |
146 | case SLSB_P_OUTPUT_EMPTY: |
147 | seq_printf(m, fmt: "-" ); |
148 | break; |
149 | case SLSB_P_INPUT_HALTED: |
150 | case SLSB_P_OUTPUT_HALTED: |
151 | seq_printf(m, fmt: "." ); |
152 | break; |
153 | default: |
154 | seq_printf(m, fmt: "?" ); |
155 | } |
156 | if (i == 63) |
157 | seq_printf(m, fmt: "\n" ); |
158 | } |
159 | seq_printf(m, fmt: "\n" ); |
160 | seq_printf(m, fmt: "|64 |72 |80 |88 |96 |104 |112 | 127|\n" ); |
161 | |
162 | seq_printf(m, fmt: "\nSBAL statistics:" ); |
163 | if (!q->irq_ptr->perf_stat_enabled) { |
164 | seq_printf(m, fmt: " disabled\n" ); |
165 | return 0; |
166 | } |
167 | |
168 | seq_printf(m, fmt: "\n1 2.. 4.. 8.. " |
169 | "16.. 32.. 64.. 128\n" ); |
170 | for (i = 0; i < ARRAY_SIZE(q->q_stats.nr_sbals); i++) |
171 | seq_printf(m, fmt: "%-10u " , q->q_stats.nr_sbals[i]); |
172 | seq_printf(m, fmt: "\nError NOP Total\n%-10u %-10u %-10u\n\n" , |
173 | q->q_stats.nr_sbal_error, q->q_stats.nr_sbal_nop, |
174 | q->q_stats.nr_sbal_total); |
175 | return 0; |
176 | } |
177 | |
178 | DEFINE_SHOW_ATTRIBUTE(qstat); |
179 | |
180 | static int ssqd_show(struct seq_file *m, void *v) |
181 | { |
182 | struct ccw_device *cdev = m->private; |
183 | struct qdio_ssqd_desc ssqd; |
184 | int rc; |
185 | |
186 | rc = qdio_get_ssqd_desc(cdev, &ssqd); |
187 | if (rc) |
188 | return rc; |
189 | |
190 | seq_hex_dump(m, prefix_str: "" , prefix_type: DUMP_PREFIX_NONE, rowsize: 16, groupsize: 4, buf: &ssqd, len: sizeof(ssqd), |
191 | ascii: false); |
192 | return 0; |
193 | } |
194 | |
195 | DEFINE_SHOW_ATTRIBUTE(ssqd); |
196 | |
197 | static char *qperf_names[] = { |
198 | "Assumed adapter interrupts" , |
199 | "QDIO interrupts" , |
200 | "SIGA read" , |
201 | "SIGA write" , |
202 | "SIGA sync" , |
203 | "Inbound calls" , |
204 | "Inbound stop_polling" , |
205 | "Inbound queue full" , |
206 | "Outbound calls" , |
207 | "Outbound queue full" , |
208 | "Outbound fast_requeue" , |
209 | "Outbound target_full" , |
210 | "QEBSM eqbs" , |
211 | "QEBSM eqbs partial" , |
212 | "QEBSM sqbs" , |
213 | "QEBSM sqbs partial" , |
214 | "Discarded interrupts" |
215 | }; |
216 | |
217 | static int qperf_show(struct seq_file *m, void *v) |
218 | { |
219 | struct qdio_irq *irq_ptr = m->private; |
220 | unsigned int *stat; |
221 | int i; |
222 | |
223 | if (!irq_ptr) |
224 | return 0; |
225 | if (!irq_ptr->perf_stat_enabled) { |
226 | seq_printf(m, fmt: "disabled\n" ); |
227 | return 0; |
228 | } |
229 | stat = (unsigned int *)&irq_ptr->perf_stat; |
230 | |
231 | for (i = 0; i < ARRAY_SIZE(qperf_names); i++) |
232 | seq_printf(m, fmt: "%26s:\t%u\n" , |
233 | qperf_names[i], *(stat + i)); |
234 | return 0; |
235 | } |
236 | |
237 | static ssize_t qperf_seq_write(struct file *file, const char __user *ubuf, |
238 | size_t count, loff_t *off) |
239 | { |
240 | struct seq_file *seq = file->private_data; |
241 | struct qdio_irq *irq_ptr = seq->private; |
242 | struct qdio_q *q; |
243 | unsigned long val; |
244 | int ret, i; |
245 | |
246 | if (!irq_ptr) |
247 | return 0; |
248 | |
249 | ret = kstrtoul_from_user(s: ubuf, count, base: 10, res: &val); |
250 | if (ret) |
251 | return ret; |
252 | |
253 | switch (val) { |
254 | case 0: |
255 | irq_ptr->perf_stat_enabled = 0; |
256 | memset(&irq_ptr->perf_stat, 0, sizeof(irq_ptr->perf_stat)); |
257 | for_each_input_queue(irq_ptr, q, i) |
258 | memset(&q->q_stats, 0, sizeof(q->q_stats)); |
259 | for_each_output_queue(irq_ptr, q, i) |
260 | memset(&q->q_stats, 0, sizeof(q->q_stats)); |
261 | break; |
262 | case 1: |
263 | irq_ptr->perf_stat_enabled = 1; |
264 | break; |
265 | } |
266 | return count; |
267 | } |
268 | |
269 | static int qperf_seq_open(struct inode *inode, struct file *filp) |
270 | { |
271 | return single_open(filp, qperf_show, |
272 | file_inode(f: filp)->i_private); |
273 | } |
274 | |
275 | static const struct file_operations debugfs_perf_fops = { |
276 | .owner = THIS_MODULE, |
277 | .open = qperf_seq_open, |
278 | .read = seq_read, |
279 | .write = qperf_seq_write, |
280 | .llseek = seq_lseek, |
281 | .release = single_release, |
282 | }; |
283 | |
284 | static void setup_debugfs_entry(struct dentry *parent, struct qdio_q *q) |
285 | { |
286 | char name[QDIO_DEBUGFS_NAME_LEN]; |
287 | |
288 | snprintf(buf: name, QDIO_DEBUGFS_NAME_LEN, fmt: "%s_%d" , |
289 | q->is_input_q ? "input" : "output" , |
290 | q->nr); |
291 | debugfs_create_file(name, mode: 0444, parent, data: q, fops: &qstat_fops); |
292 | } |
293 | |
294 | void qdio_setup_debug_entries(struct qdio_irq *irq_ptr) |
295 | { |
296 | struct qdio_q *q; |
297 | int i; |
298 | |
299 | irq_ptr->debugfs_dev = debugfs_create_dir(name: dev_name(dev: &irq_ptr->cdev->dev), |
300 | parent: debugfs_root); |
301 | debugfs_create_file(name: "statistics" , S_IFREG | S_IRUGO | S_IWUSR, |
302 | parent: irq_ptr->debugfs_dev, data: irq_ptr, fops: &debugfs_perf_fops); |
303 | debugfs_create_file(name: "ssqd" , mode: 0444, parent: irq_ptr->debugfs_dev, data: irq_ptr->cdev, |
304 | fops: &ssqd_fops); |
305 | |
306 | for_each_input_queue(irq_ptr, q, i) |
307 | setup_debugfs_entry(parent: irq_ptr->debugfs_dev, q); |
308 | for_each_output_queue(irq_ptr, q, i) |
309 | setup_debugfs_entry(parent: irq_ptr->debugfs_dev, q); |
310 | } |
311 | |
312 | void qdio_shutdown_debug_entries(struct qdio_irq *irq_ptr) |
313 | { |
314 | debugfs_remove_recursive(dentry: irq_ptr->debugfs_dev); |
315 | } |
316 | |
317 | int __init qdio_debug_init(void) |
318 | { |
319 | debugfs_root = debugfs_create_dir(name: "qdio" , NULL); |
320 | |
321 | qdio_dbf_setup = debug_register("qdio_setup" , 16, 1, 16); |
322 | debug_register_view(qdio_dbf_setup, &debug_hex_ascii_view); |
323 | debug_set_level(qdio_dbf_setup, DBF_INFO); |
324 | DBF_EVENT("dbf created\n" ); |
325 | |
326 | qdio_dbf_error = debug_register("qdio_error" , 4, 1, 16); |
327 | debug_register_view(qdio_dbf_error, &debug_hex_ascii_view); |
328 | debug_set_level(qdio_dbf_error, DBF_INFO); |
329 | DBF_ERROR("dbf created\n" ); |
330 | return 0; |
331 | } |
332 | |
333 | void qdio_debug_exit(void) |
334 | { |
335 | qdio_clear_dbf_list(); |
336 | debugfs_remove_recursive(dentry: debugfs_root); |
337 | debug_unregister(qdio_dbf_setup); |
338 | debug_unregister(qdio_dbf_error); |
339 | } |
340 | |