1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | Copyright (C) 2004 - 2009 Ivo van Doorn <IvDoorn@gmail.com> |
4 | <http://rt2x00.serialmonkey.com> |
5 | |
6 | */ |
7 | |
8 | /* |
9 | Module: rt2x00lib |
10 | Abstract: rt2x00 debugfs specific routines. |
11 | */ |
12 | |
13 | #include <linux/debugfs.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/poll.h> |
17 | #include <linux/sched.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/uaccess.h> |
20 | |
21 | #include "rt2x00.h" |
22 | #include "rt2x00lib.h" |
23 | #include "rt2x00dump.h" |
24 | |
25 | #define MAX_LINE_LENGTH 64 |
26 | |
27 | struct rt2x00debug_crypto { |
28 | unsigned long success; |
29 | unsigned long icv_error; |
30 | unsigned long mic_error; |
31 | unsigned long key_error; |
32 | }; |
33 | |
34 | struct rt2x00debug_intf { |
35 | /* |
36 | * Pointer to driver structure where |
37 | * this debugfs entry belongs to. |
38 | */ |
39 | struct rt2x00_dev *rt2x00dev; |
40 | |
41 | /* |
42 | * Reference to the rt2x00debug structure |
43 | * which can be used to communicate with |
44 | * the registers. |
45 | */ |
46 | const struct rt2x00debug *debug; |
47 | |
48 | /* |
49 | * Debugfs entries for: |
50 | * - driver folder |
51 | * - driver file |
52 | * - chipset file |
53 | * - device state flags file |
54 | * - device capability flags file |
55 | * - hardware restart file |
56 | * - register folder |
57 | * - csr offset/value files |
58 | * - eeprom offset/value files |
59 | * - bbp offset/value files |
60 | * - rf offset/value files |
61 | * - rfcsr offset/value files |
62 | * - queue folder |
63 | * - frame dump file |
64 | * - queue stats file |
65 | * - crypto stats file |
66 | */ |
67 | struct dentry *driver_folder; |
68 | |
69 | /* |
70 | * The frame dump file only allows a single reader, |
71 | * so we need to store the current state here. |
72 | */ |
73 | unsigned long frame_dump_flags; |
74 | #define FRAME_DUMP_FILE_OPEN 1 |
75 | |
76 | /* |
77 | * We queue each frame before dumping it to the user, |
78 | * per read command we will pass a single skb structure |
79 | * so we should be prepared to queue multiple sk buffers |
80 | * before sending it to userspace. |
81 | */ |
82 | struct sk_buff_head frame_dump_skbqueue; |
83 | wait_queue_head_t frame_dump_waitqueue; |
84 | |
85 | /* |
86 | * HW crypto statistics. |
87 | * All statistics are stored separately per cipher type. |
88 | */ |
89 | struct rt2x00debug_crypto crypto_stats[CIPHER_MAX]; |
90 | |
91 | /* |
92 | * Driver and chipset files will use a data buffer |
93 | * that has been created in advance. This will simplify |
94 | * the code since we can use the debugfs functions. |
95 | */ |
96 | struct debugfs_blob_wrapper driver_blob; |
97 | struct debugfs_blob_wrapper chipset_blob; |
98 | |
99 | /* |
100 | * Requested offset for each register type. |
101 | */ |
102 | unsigned int offset_csr; |
103 | unsigned int offset_eeprom; |
104 | unsigned int offset_bbp; |
105 | unsigned int offset_rf; |
106 | unsigned int offset_rfcsr; |
107 | }; |
108 | |
109 | void rt2x00debug_update_crypto(struct rt2x00_dev *rt2x00dev, |
110 | struct rxdone_entry_desc *rxdesc) |
111 | { |
112 | struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; |
113 | enum cipher cipher = rxdesc->cipher; |
114 | enum rx_crypto status = rxdesc->cipher_status; |
115 | |
116 | if (cipher == CIPHER_TKIP_NO_MIC) |
117 | cipher = CIPHER_TKIP; |
118 | if (cipher == CIPHER_NONE || cipher >= CIPHER_MAX) |
119 | return; |
120 | |
121 | /* Remove CIPHER_NONE index */ |
122 | cipher--; |
123 | |
124 | intf->crypto_stats[cipher].success += (status == RX_CRYPTO_SUCCESS); |
125 | intf->crypto_stats[cipher].icv_error += (status == RX_CRYPTO_FAIL_ICV); |
126 | intf->crypto_stats[cipher].mic_error += (status == RX_CRYPTO_FAIL_MIC); |
127 | intf->crypto_stats[cipher].key_error += (status == RX_CRYPTO_FAIL_KEY); |
128 | } |
129 | |
130 | void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev, |
131 | enum rt2x00_dump_type type, struct queue_entry *entry) |
132 | { |
133 | struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; |
134 | struct sk_buff *skb = entry->skb; |
135 | struct skb_frame_desc *skbdesc = get_skb_frame_desc(skb); |
136 | struct sk_buff *skbcopy; |
137 | struct rt2x00dump_hdr *dump_hdr; |
138 | struct timespec64 timestamp; |
139 | u32 data_len; |
140 | |
141 | if (likely(!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags))) |
142 | return; |
143 | |
144 | ktime_get_ts64(ts: ×tamp); |
145 | |
146 | if (skb_queue_len(list_: &intf->frame_dump_skbqueue) > 20) { |
147 | rt2x00_dbg(rt2x00dev, "txrx dump queue length exceeded\n" ); |
148 | return; |
149 | } |
150 | |
151 | data_len = skb->len; |
152 | if (skbdesc->flags & SKBDESC_DESC_IN_SKB) |
153 | data_len -= skbdesc->desc_len; |
154 | |
155 | skbcopy = alloc_skb(size: sizeof(*dump_hdr) + skbdesc->desc_len + data_len, |
156 | GFP_ATOMIC); |
157 | if (!skbcopy) { |
158 | rt2x00_dbg(rt2x00dev, "Failed to copy skb for dump\n" ); |
159 | return; |
160 | } |
161 | |
162 | dump_hdr = skb_put(skb: skbcopy, len: sizeof(*dump_hdr)); |
163 | dump_hdr->version = cpu_to_le32(DUMP_HEADER_VERSION); |
164 | dump_hdr->header_length = cpu_to_le32(sizeof(*dump_hdr)); |
165 | dump_hdr->desc_length = cpu_to_le32(skbdesc->desc_len); |
166 | dump_hdr->data_length = cpu_to_le32(data_len); |
167 | dump_hdr->chip_rt = cpu_to_le16(rt2x00dev->chip.rt); |
168 | dump_hdr->chip_rf = cpu_to_le16(rt2x00dev->chip.rf); |
169 | dump_hdr->chip_rev = cpu_to_le16(rt2x00dev->chip.rev); |
170 | dump_hdr->type = cpu_to_le16(type); |
171 | dump_hdr->queue_index = entry->queue->qid; |
172 | dump_hdr->entry_index = entry->entry_idx; |
173 | dump_hdr->timestamp_sec = cpu_to_le32(timestamp.tv_sec); |
174 | dump_hdr->timestamp_usec = cpu_to_le32(timestamp.tv_nsec / |
175 | NSEC_PER_USEC); |
176 | |
177 | if (!(skbdesc->flags & SKBDESC_DESC_IN_SKB)) |
178 | skb_put_data(skb: skbcopy, data: skbdesc->desc, len: skbdesc->desc_len); |
179 | skb_put_data(skb: skbcopy, data: skb->data, len: skb->len); |
180 | |
181 | skb_queue_tail(list: &intf->frame_dump_skbqueue, newsk: skbcopy); |
182 | wake_up_interruptible(&intf->frame_dump_waitqueue); |
183 | |
184 | /* |
185 | * Verify that the file has not been closed while we were working. |
186 | */ |
187 | if (!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags)) |
188 | skb_queue_purge(list: &intf->frame_dump_skbqueue); |
189 | } |
190 | EXPORT_SYMBOL_GPL(rt2x00debug_dump_frame); |
191 | |
192 | static int rt2x00debug_file_open(struct inode *inode, struct file *file) |
193 | { |
194 | struct rt2x00debug_intf *intf = inode->i_private; |
195 | |
196 | file->private_data = inode->i_private; |
197 | |
198 | if (!try_module_get(module: intf->debug->owner)) |
199 | return -EBUSY; |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static int rt2x00debug_file_release(struct inode *inode, struct file *file) |
205 | { |
206 | struct rt2x00debug_intf *intf = file->private_data; |
207 | |
208 | module_put(module: intf->debug->owner); |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int rt2x00debug_open_queue_dump(struct inode *inode, struct file *file) |
214 | { |
215 | struct rt2x00debug_intf *intf = inode->i_private; |
216 | int retval; |
217 | |
218 | retval = rt2x00debug_file_open(inode, file); |
219 | if (retval) |
220 | return retval; |
221 | |
222 | if (test_and_set_bit(FRAME_DUMP_FILE_OPEN, addr: &intf->frame_dump_flags)) { |
223 | rt2x00debug_file_release(inode, file); |
224 | return -EBUSY; |
225 | } |
226 | |
227 | return 0; |
228 | } |
229 | |
230 | static int rt2x00debug_release_queue_dump(struct inode *inode, struct file *file) |
231 | { |
232 | struct rt2x00debug_intf *intf = inode->i_private; |
233 | |
234 | skb_queue_purge(list: &intf->frame_dump_skbqueue); |
235 | |
236 | clear_bit(FRAME_DUMP_FILE_OPEN, addr: &intf->frame_dump_flags); |
237 | |
238 | return rt2x00debug_file_release(inode, file); |
239 | } |
240 | |
241 | static ssize_t rt2x00debug_read_queue_dump(struct file *file, |
242 | char __user *buf, |
243 | size_t length, |
244 | loff_t *offset) |
245 | { |
246 | struct rt2x00debug_intf *intf = file->private_data; |
247 | struct sk_buff *skb; |
248 | size_t status; |
249 | int retval; |
250 | |
251 | if (file->f_flags & O_NONBLOCK) |
252 | return -EAGAIN; |
253 | |
254 | retval = |
255 | wait_event_interruptible(intf->frame_dump_waitqueue, |
256 | (skb = |
257 | skb_dequeue(&intf->frame_dump_skbqueue))); |
258 | if (retval) |
259 | return retval; |
260 | |
261 | status = min_t(size_t, skb->len, length); |
262 | if (copy_to_user(to: buf, from: skb->data, n: status)) { |
263 | status = -EFAULT; |
264 | goto exit; |
265 | } |
266 | |
267 | *offset += status; |
268 | |
269 | exit: |
270 | kfree_skb(skb); |
271 | |
272 | return status; |
273 | } |
274 | |
275 | static __poll_t rt2x00debug_poll_queue_dump(struct file *file, |
276 | poll_table *wait) |
277 | { |
278 | struct rt2x00debug_intf *intf = file->private_data; |
279 | |
280 | poll_wait(filp: file, wait_address: &intf->frame_dump_waitqueue, p: wait); |
281 | |
282 | if (!skb_queue_empty(list: &intf->frame_dump_skbqueue)) |
283 | return EPOLLOUT | EPOLLWRNORM; |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static const struct file_operations rt2x00debug_fop_queue_dump = { |
289 | .owner = THIS_MODULE, |
290 | .read = rt2x00debug_read_queue_dump, |
291 | .poll = rt2x00debug_poll_queue_dump, |
292 | .open = rt2x00debug_open_queue_dump, |
293 | .release = rt2x00debug_release_queue_dump, |
294 | .llseek = default_llseek, |
295 | }; |
296 | |
297 | static ssize_t rt2x00debug_read_queue_stats(struct file *file, |
298 | char __user *buf, |
299 | size_t length, |
300 | loff_t *offset) |
301 | { |
302 | struct rt2x00debug_intf *intf = file->private_data; |
303 | struct data_queue *queue; |
304 | unsigned long irqflags; |
305 | unsigned int lines = 1 + intf->rt2x00dev->data_queues; |
306 | size_t size; |
307 | char *data; |
308 | char *temp; |
309 | |
310 | if (*offset) |
311 | return 0; |
312 | |
313 | data = kcalloc(n: lines, MAX_LINE_LENGTH, GFP_KERNEL); |
314 | if (!data) |
315 | return -ENOMEM; |
316 | |
317 | temp = data + |
318 | sprintf(buf: data, fmt: "qid\tflags\t\tcount\tlimit\tlength\tindex\tdma done\tdone\n" ); |
319 | |
320 | queue_for_each(intf->rt2x00dev, queue) { |
321 | spin_lock_irqsave(&queue->index_lock, irqflags); |
322 | |
323 | temp += sprintf(buf: temp, fmt: "%d\t0x%.8x\t%d\t%d\t%d\t%d\t%d\t\t%d\n" , |
324 | queue->qid, (unsigned int)queue->flags, |
325 | queue->count, queue->limit, queue->length, |
326 | queue->index[Q_INDEX], |
327 | queue->index[Q_INDEX_DMA_DONE], |
328 | queue->index[Q_INDEX_DONE]); |
329 | |
330 | spin_unlock_irqrestore(lock: &queue->index_lock, flags: irqflags); |
331 | } |
332 | |
333 | size = strlen(data); |
334 | size = min(size, length); |
335 | |
336 | if (copy_to_user(to: buf, from: data, n: size)) { |
337 | kfree(objp: data); |
338 | return -EFAULT; |
339 | } |
340 | |
341 | kfree(objp: data); |
342 | |
343 | *offset += size; |
344 | return size; |
345 | } |
346 | |
347 | static const struct file_operations rt2x00debug_fop_queue_stats = { |
348 | .owner = THIS_MODULE, |
349 | .read = rt2x00debug_read_queue_stats, |
350 | .open = rt2x00debug_file_open, |
351 | .release = rt2x00debug_file_release, |
352 | .llseek = default_llseek, |
353 | }; |
354 | |
355 | #ifdef CONFIG_RT2X00_LIB_CRYPTO |
356 | static ssize_t rt2x00debug_read_crypto_stats(struct file *file, |
357 | char __user *buf, |
358 | size_t length, |
359 | loff_t *offset) |
360 | { |
361 | struct rt2x00debug_intf *intf = file->private_data; |
362 | static const char * const name[] = { "WEP64" , "WEP128" , "TKIP" , "AES" }; |
363 | char *data; |
364 | char *temp; |
365 | size_t size; |
366 | unsigned int i; |
367 | |
368 | if (*offset) |
369 | return 0; |
370 | |
371 | data = kcalloc(n: 1 + CIPHER_MAX, MAX_LINE_LENGTH, GFP_KERNEL); |
372 | if (!data) |
373 | return -ENOMEM; |
374 | |
375 | temp = data; |
376 | temp += sprintf(buf: data, fmt: "cipher\tsuccess\ticv err\tmic err\tkey err\n" ); |
377 | |
378 | for (i = 0; i < CIPHER_MAX; i++) { |
379 | temp += sprintf(buf: temp, fmt: "%s\t%lu\t%lu\t%lu\t%lu\n" , name[i], |
380 | intf->crypto_stats[i].success, |
381 | intf->crypto_stats[i].icv_error, |
382 | intf->crypto_stats[i].mic_error, |
383 | intf->crypto_stats[i].key_error); |
384 | } |
385 | |
386 | size = strlen(data); |
387 | size = min(size, length); |
388 | |
389 | if (copy_to_user(to: buf, from: data, n: size)) { |
390 | kfree(objp: data); |
391 | return -EFAULT; |
392 | } |
393 | |
394 | kfree(objp: data); |
395 | |
396 | *offset += size; |
397 | return size; |
398 | } |
399 | |
400 | static const struct file_operations rt2x00debug_fop_crypto_stats = { |
401 | .owner = THIS_MODULE, |
402 | .read = rt2x00debug_read_crypto_stats, |
403 | .open = rt2x00debug_file_open, |
404 | .release = rt2x00debug_file_release, |
405 | .llseek = default_llseek, |
406 | }; |
407 | #endif |
408 | |
409 | #define RT2X00DEBUGFS_OPS_READ(__name, __format, __type) \ |
410 | static ssize_t rt2x00debug_read_##__name(struct file *file, \ |
411 | char __user *buf, \ |
412 | size_t length, \ |
413 | loff_t *offset) \ |
414 | { \ |
415 | struct rt2x00debug_intf *intf = file->private_data; \ |
416 | const struct rt2x00debug *debug = intf->debug; \ |
417 | char line[16]; \ |
418 | size_t size; \ |
419 | unsigned int index = intf->offset_##__name; \ |
420 | __type value; \ |
421 | \ |
422 | if (*offset) \ |
423 | return 0; \ |
424 | \ |
425 | if (index >= debug->__name.word_count) \ |
426 | return -EINVAL; \ |
427 | \ |
428 | index += (debug->__name.word_base / \ |
429 | debug->__name.word_size); \ |
430 | \ |
431 | if (debug->__name.flags & RT2X00DEBUGFS_OFFSET) \ |
432 | index *= debug->__name.word_size; \ |
433 | \ |
434 | value = debug->__name.read(intf->rt2x00dev, index); \ |
435 | \ |
436 | size = sprintf(line, __format, value); \ |
437 | \ |
438 | return simple_read_from_buffer(buf, length, offset, line, size); \ |
439 | } |
440 | |
441 | #define RT2X00DEBUGFS_OPS_WRITE(__name, __type) \ |
442 | static ssize_t rt2x00debug_write_##__name(struct file *file, \ |
443 | const char __user *buf,\ |
444 | size_t length, \ |
445 | loff_t *offset) \ |
446 | { \ |
447 | struct rt2x00debug_intf *intf = file->private_data; \ |
448 | const struct rt2x00debug *debug = intf->debug; \ |
449 | char line[17]; \ |
450 | size_t size; \ |
451 | unsigned int index = intf->offset_##__name; \ |
452 | __type value; \ |
453 | \ |
454 | if (*offset) \ |
455 | return 0; \ |
456 | \ |
457 | if (index >= debug->__name.word_count) \ |
458 | return -EINVAL; \ |
459 | \ |
460 | if (length > sizeof(line)) \ |
461 | return -EINVAL; \ |
462 | \ |
463 | if (copy_from_user(line, buf, length)) \ |
464 | return -EFAULT; \ |
465 | line[16] = 0; \ |
466 | \ |
467 | size = strlen(line); \ |
468 | value = simple_strtoul(line, NULL, 0); \ |
469 | \ |
470 | index += (debug->__name.word_base / \ |
471 | debug->__name.word_size); \ |
472 | \ |
473 | if (debug->__name.flags & RT2X00DEBUGFS_OFFSET) \ |
474 | index *= debug->__name.word_size; \ |
475 | \ |
476 | debug->__name.write(intf->rt2x00dev, index, value); \ |
477 | \ |
478 | *offset += size; \ |
479 | return size; \ |
480 | } |
481 | |
482 | #define RT2X00DEBUGFS_OPS(__name, __format, __type) \ |
483 | RT2X00DEBUGFS_OPS_READ(__name, __format, __type); \ |
484 | RT2X00DEBUGFS_OPS_WRITE(__name, __type); \ |
485 | \ |
486 | static const struct file_operations rt2x00debug_fop_##__name = {\ |
487 | .owner = THIS_MODULE, \ |
488 | .read = rt2x00debug_read_##__name, \ |
489 | .write = rt2x00debug_write_##__name, \ |
490 | .open = rt2x00debug_file_open, \ |
491 | .release = rt2x00debug_file_release, \ |
492 | .llseek = generic_file_llseek, \ |
493 | }; |
494 | |
495 | RT2X00DEBUGFS_OPS(csr, "0x%.8x\n" , u32); |
496 | RT2X00DEBUGFS_OPS(eeprom, "0x%.4x\n" , u16); |
497 | RT2X00DEBUGFS_OPS(bbp, "0x%.2x\n" , u8); |
498 | RT2X00DEBUGFS_OPS(rf, "0x%.8x\n" , u32); |
499 | RT2X00DEBUGFS_OPS(rfcsr, "0x%.2x\n" , u8); |
500 | |
501 | static ssize_t rt2x00debug_read_dev_flags(struct file *file, |
502 | char __user *buf, |
503 | size_t length, |
504 | loff_t *offset) |
505 | { |
506 | struct rt2x00debug_intf *intf = file->private_data; |
507 | char line[16]; |
508 | size_t size; |
509 | |
510 | if (*offset) |
511 | return 0; |
512 | |
513 | size = sprintf(buf: line, fmt: "0x%.8x\n" , (unsigned int)intf->rt2x00dev->flags); |
514 | |
515 | return simple_read_from_buffer(to: buf, count: length, ppos: offset, from: line, available: size); |
516 | } |
517 | |
518 | static const struct file_operations rt2x00debug_fop_dev_flags = { |
519 | .owner = THIS_MODULE, |
520 | .read = rt2x00debug_read_dev_flags, |
521 | .open = rt2x00debug_file_open, |
522 | .release = rt2x00debug_file_release, |
523 | .llseek = default_llseek, |
524 | }; |
525 | |
526 | static ssize_t rt2x00debug_read_cap_flags(struct file *file, |
527 | char __user *buf, |
528 | size_t length, |
529 | loff_t *offset) |
530 | { |
531 | struct rt2x00debug_intf *intf = file->private_data; |
532 | char line[16]; |
533 | size_t size; |
534 | |
535 | if (*offset) |
536 | return 0; |
537 | |
538 | size = sprintf(buf: line, fmt: "0x%.8x\n" , (unsigned int)intf->rt2x00dev->cap_flags); |
539 | |
540 | return simple_read_from_buffer(to: buf, count: length, ppos: offset, from: line, available: size); |
541 | } |
542 | |
543 | static const struct file_operations rt2x00debug_fop_cap_flags = { |
544 | .owner = THIS_MODULE, |
545 | .read = rt2x00debug_read_cap_flags, |
546 | .open = rt2x00debug_file_open, |
547 | .release = rt2x00debug_file_release, |
548 | .llseek = default_llseek, |
549 | }; |
550 | |
551 | static ssize_t rt2x00debug_write_restart_hw(struct file *file, |
552 | const char __user *buf, |
553 | size_t length, |
554 | loff_t *offset) |
555 | { |
556 | struct rt2x00debug_intf *intf = file->private_data; |
557 | struct rt2x00_dev *rt2x00dev = intf->rt2x00dev; |
558 | static unsigned long last_reset = INITIAL_JIFFIES; |
559 | |
560 | if (!rt2x00_has_cap_restart_hw(rt2x00dev)) |
561 | return -EOPNOTSUPP; |
562 | |
563 | if (time_before(jiffies, last_reset + msecs_to_jiffies(2000))) |
564 | return -EBUSY; |
565 | |
566 | last_reset = jiffies; |
567 | |
568 | ieee80211_restart_hw(hw: rt2x00dev->hw); |
569 | return length; |
570 | } |
571 | |
572 | static const struct file_operations rt2x00debug_restart_hw = { |
573 | .owner = THIS_MODULE, |
574 | .write = rt2x00debug_write_restart_hw, |
575 | .open = simple_open, |
576 | .llseek = generic_file_llseek, |
577 | }; |
578 | |
579 | static void rt2x00debug_create_file_driver(const char *name, |
580 | struct rt2x00debug_intf *intf, |
581 | struct debugfs_blob_wrapper *blob) |
582 | { |
583 | char *data; |
584 | |
585 | data = kzalloc(size: 3 * MAX_LINE_LENGTH, GFP_KERNEL); |
586 | if (!data) |
587 | return; |
588 | |
589 | blob->data = data; |
590 | data += sprintf(buf: data, fmt: "driver:\t%s\n" , intf->rt2x00dev->ops->name); |
591 | data += sprintf(buf: data, fmt: "version:\t%s\n" , DRV_VERSION); |
592 | blob->size = strlen(blob->data); |
593 | |
594 | debugfs_create_blob(name, mode: 0400, parent: intf->driver_folder, blob); |
595 | } |
596 | |
597 | static void rt2x00debug_create_file_chipset(const char *name, |
598 | struct rt2x00debug_intf *intf, |
599 | struct debugfs_blob_wrapper *blob) |
600 | { |
601 | const struct rt2x00debug *debug = intf->debug; |
602 | char *data; |
603 | |
604 | data = kzalloc(size: 9 * MAX_LINE_LENGTH, GFP_KERNEL); |
605 | if (!data) |
606 | return; |
607 | |
608 | blob->data = data; |
609 | data += sprintf(buf: data, fmt: "rt chip:\t%04x\n" , intf->rt2x00dev->chip.rt); |
610 | data += sprintf(buf: data, fmt: "rf chip:\t%04x\n" , intf->rt2x00dev->chip.rf); |
611 | data += sprintf(buf: data, fmt: "revision:\t%04x\n" , intf->rt2x00dev->chip.rev); |
612 | data += sprintf(buf: data, fmt: "\n" ); |
613 | data += sprintf(buf: data, fmt: "register\tbase\twords\twordsize\n" ); |
614 | #define RT2X00DEBUGFS_SPRINTF_REGISTER(__name) \ |
615 | { \ |
616 | if (debug->__name.read) \ |
617 | data += sprintf(data, __stringify(__name) \ |
618 | "\t%d\t%d\t%d\n", \ |
619 | debug->__name.word_base, \ |
620 | debug->__name.word_count, \ |
621 | debug->__name.word_size); \ |
622 | } |
623 | RT2X00DEBUGFS_SPRINTF_REGISTER(csr); |
624 | RT2X00DEBUGFS_SPRINTF_REGISTER(eeprom); |
625 | RT2X00DEBUGFS_SPRINTF_REGISTER(bbp); |
626 | RT2X00DEBUGFS_SPRINTF_REGISTER(rf); |
627 | RT2X00DEBUGFS_SPRINTF_REGISTER(rfcsr); |
628 | #undef RT2X00DEBUGFS_SPRINTF_REGISTER |
629 | |
630 | blob->size = strlen(blob->data); |
631 | |
632 | debugfs_create_blob(name, mode: 0400, parent: intf->driver_folder, blob); |
633 | } |
634 | |
635 | void rt2x00debug_register(struct rt2x00_dev *rt2x00dev) |
636 | { |
637 | const struct rt2x00debug *debug = rt2x00dev->ops->debugfs; |
638 | struct rt2x00debug_intf *intf; |
639 | struct dentry *queue_folder; |
640 | struct dentry *register_folder; |
641 | |
642 | intf = kzalloc(size: sizeof(struct rt2x00debug_intf), GFP_KERNEL); |
643 | if (!intf) { |
644 | rt2x00_err(rt2x00dev, "Failed to allocate debug handler\n" ); |
645 | return; |
646 | } |
647 | |
648 | intf->debug = debug; |
649 | intf->rt2x00dev = rt2x00dev; |
650 | rt2x00dev->debugfs_intf = intf; |
651 | |
652 | intf->driver_folder = |
653 | debugfs_create_dir(name: intf->rt2x00dev->ops->name, |
654 | parent: rt2x00dev->hw->wiphy->debugfsdir); |
655 | |
656 | rt2x00debug_create_file_driver(name: "driver" , intf, blob: &intf->driver_blob); |
657 | rt2x00debug_create_file_chipset(name: "chipset" , intf, blob: &intf->chipset_blob); |
658 | debugfs_create_file(name: "dev_flags" , mode: 0400, parent: intf->driver_folder, data: intf, |
659 | fops: &rt2x00debug_fop_dev_flags); |
660 | debugfs_create_file(name: "cap_flags" , mode: 0400, parent: intf->driver_folder, data: intf, |
661 | fops: &rt2x00debug_fop_cap_flags); |
662 | debugfs_create_file(name: "restart_hw" , mode: 0200, parent: intf->driver_folder, data: intf, |
663 | fops: &rt2x00debug_restart_hw); |
664 | |
665 | register_folder = debugfs_create_dir(name: "register" , parent: intf->driver_folder); |
666 | |
667 | #define RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(__intf, __name) \ |
668 | ({ \ |
669 | if (debug->__name.read) { \ |
670 | debugfs_create_u32(__stringify(__name) "_offset", 0600, \ |
671 | register_folder, \ |
672 | &(__intf)->offset_##__name); \ |
673 | \ |
674 | debugfs_create_file(__stringify(__name) "_value", 0600, \ |
675 | register_folder, (__intf), \ |
676 | &rt2x00debug_fop_##__name); \ |
677 | } \ |
678 | }) |
679 | |
680 | RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, csr); |
681 | RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, eeprom); |
682 | RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, bbp); |
683 | RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, rf); |
684 | RT2X00DEBUGFS_CREATE_REGISTER_ENTRY(intf, rfcsr); |
685 | |
686 | #undef RT2X00DEBUGFS_CREATE_REGISTER_ENTRY |
687 | |
688 | queue_folder = debugfs_create_dir(name: "queue" , parent: intf->driver_folder); |
689 | |
690 | debugfs_create_file(name: "dump" , mode: 0400, parent: queue_folder, data: intf, |
691 | fops: &rt2x00debug_fop_queue_dump); |
692 | |
693 | skb_queue_head_init(list: &intf->frame_dump_skbqueue); |
694 | init_waitqueue_head(&intf->frame_dump_waitqueue); |
695 | |
696 | debugfs_create_file(name: "queue" , mode: 0400, parent: queue_folder, data: intf, |
697 | fops: &rt2x00debug_fop_queue_stats); |
698 | |
699 | #ifdef CONFIG_RT2X00_LIB_CRYPTO |
700 | if (rt2x00_has_cap_hw_crypto(rt2x00dev)) |
701 | debugfs_create_file(name: "crypto" , mode: 0444, parent: queue_folder, data: intf, |
702 | fops: &rt2x00debug_fop_crypto_stats); |
703 | #endif |
704 | |
705 | return; |
706 | } |
707 | |
708 | void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev) |
709 | { |
710 | struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf; |
711 | |
712 | if (unlikely(!intf)) |
713 | return; |
714 | |
715 | skb_queue_purge(list: &intf->frame_dump_skbqueue); |
716 | |
717 | debugfs_remove_recursive(dentry: intf->driver_folder); |
718 | kfree(objp: intf->chipset_blob.data); |
719 | kfree(objp: intf->driver_blob.data); |
720 | kfree(objp: intf); |
721 | |
722 | rt2x00dev->debugfs_intf = NULL; |
723 | } |
724 | |