diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath6kl/debug.c')
-rw-r--r-- | drivers/net/wireless/ath/ath6kl/debug.c | 784 |
1 files changed, 784 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath6kl/debug.c b/drivers/net/wireless/ath/ath6kl/debug.c index 316136c8b903..ba3f23d71150 100644 --- a/drivers/net/wireless/ath/ath6kl/debug.c +++ b/drivers/net/wireless/ath/ath6kl/debug.c | |||
@@ -15,7 +15,26 @@ | |||
15 | */ | 15 | */ |
16 | 16 | ||
17 | #include "core.h" | 17 | #include "core.h" |
18 | |||
19 | #include <linux/circ_buf.h> | ||
20 | #include <linux/fs.h> | ||
21 | #include <linux/vmalloc.h> | ||
22 | |||
18 | #include "debug.h" | 23 | #include "debug.h" |
24 | #include "target.h" | ||
25 | |||
26 | struct ath6kl_fwlog_slot { | ||
27 | __le32 timestamp; | ||
28 | __le32 length; | ||
29 | |||
30 | /* max ATH6KL_FWLOG_PAYLOAD_SIZE bytes */ | ||
31 | u8 payload[0]; | ||
32 | }; | ||
33 | |||
34 | #define ATH6KL_FWLOG_SIZE 32768 | ||
35 | #define ATH6KL_FWLOG_SLOT_SIZE (sizeof(struct ath6kl_fwlog_slot) + \ | ||
36 | ATH6KL_FWLOG_PAYLOAD_SIZE) | ||
37 | #define ATH6KL_FWLOG_VALID_MASK 0x1ffff | ||
19 | 38 | ||
20 | int ath6kl_printk(const char *level, const char *fmt, ...) | 39 | int ath6kl_printk(const char *level, const char *fmt, ...) |
21 | { | 40 | { |
@@ -36,6 +55,27 @@ int ath6kl_printk(const char *level, const char *fmt, ...) | |||
36 | } | 55 | } |
37 | 56 | ||
38 | #ifdef CONFIG_ATH6KL_DEBUG | 57 | #ifdef CONFIG_ATH6KL_DEBUG |
58 | |||
59 | #define REG_OUTPUT_LEN_PER_LINE 25 | ||
60 | #define REGTYPE_STR_LEN 100 | ||
61 | |||
62 | struct ath6kl_diag_reg_info { | ||
63 | u32 reg_start; | ||
64 | u32 reg_end; | ||
65 | const char *reg_info; | ||
66 | }; | ||
67 | |||
68 | static const struct ath6kl_diag_reg_info diag_reg[] = { | ||
69 | { 0x20000, 0x200fc, "General DMA and Rx registers" }, | ||
70 | { 0x28000, 0x28900, "MAC PCU register & keycache" }, | ||
71 | { 0x20800, 0x20a40, "QCU" }, | ||
72 | { 0x21000, 0x212f0, "DCU" }, | ||
73 | { 0x4000, 0x42e4, "RTC" }, | ||
74 | { 0x540000, 0x540000 + (256 * 1024), "RAM" }, | ||
75 | { 0x29800, 0x2B210, "Base Band" }, | ||
76 | { 0x1C000, 0x1C748, "Analog" }, | ||
77 | }; | ||
78 | |||
39 | void ath6kl_dump_registers(struct ath6kl_device *dev, | 79 | void ath6kl_dump_registers(struct ath6kl_device *dev, |
40 | struct ath6kl_irq_proc_registers *irq_proc_reg, | 80 | struct ath6kl_irq_proc_registers *irq_proc_reg, |
41 | struct ath6kl_irq_enable_reg *irq_enable_reg) | 81 | struct ath6kl_irq_enable_reg *irq_enable_reg) |
@@ -147,4 +187,748 @@ void dump_cred_dist_stats(struct htc_target *target) | |||
147 | target->cred_dist_cntxt->cur_free_credits); | 187 | target->cred_dist_cntxt->cur_free_credits); |
148 | } | 188 | } |
149 | 189 | ||
190 | static int ath6kl_debugfs_open(struct inode *inode, struct file *file) | ||
191 | { | ||
192 | file->private_data = inode->i_private; | ||
193 | return 0; | ||
194 | } | ||
195 | |||
196 | void ath6kl_debug_war(struct ath6kl *ar, enum ath6kl_war war) | ||
197 | { | ||
198 | switch (war) { | ||
199 | case ATH6KL_WAR_INVALID_RATE: | ||
200 | ar->debug.war_stats.invalid_rate++; | ||
201 | break; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | static ssize_t read_file_war_stats(struct file *file, char __user *user_buf, | ||
206 | size_t count, loff_t *ppos) | ||
207 | { | ||
208 | struct ath6kl *ar = file->private_data; | ||
209 | char *buf; | ||
210 | unsigned int len = 0, buf_len = 1500; | ||
211 | ssize_t ret_cnt; | ||
212 | |||
213 | buf = kzalloc(buf_len, GFP_KERNEL); | ||
214 | if (!buf) | ||
215 | return -ENOMEM; | ||
216 | |||
217 | len += scnprintf(buf + len, buf_len - len, "\n"); | ||
218 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
219 | "Workaround stats"); | ||
220 | len += scnprintf(buf + len, buf_len - len, "%25s\n\n", | ||
221 | "================="); | ||
222 | len += scnprintf(buf + len, buf_len - len, "%20s %10u\n", | ||
223 | "Invalid rates", ar->debug.war_stats.invalid_rate); | ||
224 | |||
225 | if (WARN_ON(len > buf_len)) | ||
226 | len = buf_len; | ||
227 | |||
228 | ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
229 | |||
230 | kfree(buf); | ||
231 | return ret_cnt; | ||
232 | } | ||
233 | |||
234 | static const struct file_operations fops_war_stats = { | ||
235 | .read = read_file_war_stats, | ||
236 | .open = ath6kl_debugfs_open, | ||
237 | .owner = THIS_MODULE, | ||
238 | .llseek = default_llseek, | ||
239 | }; | ||
240 | |||
241 | static void ath6kl_debug_fwlog_add(struct ath6kl *ar, const void *buf, | ||
242 | size_t buf_len) | ||
243 | { | ||
244 | struct circ_buf *fwlog = &ar->debug.fwlog_buf; | ||
245 | size_t space; | ||
246 | int i; | ||
247 | |||
248 | /* entries must all be equal size */ | ||
249 | if (WARN_ON(buf_len != ATH6KL_FWLOG_SLOT_SIZE)) | ||
250 | return; | ||
251 | |||
252 | space = CIRC_SPACE(fwlog->head, fwlog->tail, ATH6KL_FWLOG_SIZE); | ||
253 | if (space < buf_len) | ||
254 | /* discard oldest slot */ | ||
255 | fwlog->tail = (fwlog->tail + ATH6KL_FWLOG_SLOT_SIZE) & | ||
256 | (ATH6KL_FWLOG_SIZE - 1); | ||
257 | |||
258 | for (i = 0; i < buf_len; i += space) { | ||
259 | space = CIRC_SPACE_TO_END(fwlog->head, fwlog->tail, | ||
260 | ATH6KL_FWLOG_SIZE); | ||
261 | |||
262 | if ((size_t) space > buf_len - i) | ||
263 | space = buf_len - i; | ||
264 | |||
265 | memcpy(&fwlog->buf[fwlog->head], buf, space); | ||
266 | fwlog->head = (fwlog->head + space) & (ATH6KL_FWLOG_SIZE - 1); | ||
267 | } | ||
268 | |||
269 | } | ||
270 | |||
271 | void ath6kl_debug_fwlog_event(struct ath6kl *ar, const void *buf, size_t len) | ||
272 | { | ||
273 | struct ath6kl_fwlog_slot *slot = ar->debug.fwlog_tmp; | ||
274 | size_t slot_len; | ||
275 | |||
276 | if (WARN_ON(len > ATH6KL_FWLOG_PAYLOAD_SIZE)) | ||
277 | return; | ||
278 | |||
279 | spin_lock_bh(&ar->debug.fwlog_lock); | ||
280 | |||
281 | slot->timestamp = cpu_to_le32(jiffies); | ||
282 | slot->length = cpu_to_le32(len); | ||
283 | memcpy(slot->payload, buf, len); | ||
284 | |||
285 | slot_len = sizeof(*slot) + len; | ||
286 | |||
287 | if (slot_len < ATH6KL_FWLOG_SLOT_SIZE) | ||
288 | memset(slot->payload + len, 0, | ||
289 | ATH6KL_FWLOG_SLOT_SIZE - slot_len); | ||
290 | |||
291 | ath6kl_debug_fwlog_add(ar, slot, ATH6KL_FWLOG_SLOT_SIZE); | ||
292 | |||
293 | spin_unlock_bh(&ar->debug.fwlog_lock); | ||
294 | } | ||
295 | |||
296 | static bool ath6kl_debug_fwlog_empty(struct ath6kl *ar) | ||
297 | { | ||
298 | return CIRC_CNT(ar->debug.fwlog_buf.head, | ||
299 | ar->debug.fwlog_buf.tail, | ||
300 | ATH6KL_FWLOG_SLOT_SIZE) == 0; | ||
301 | } | ||
302 | |||
303 | static ssize_t ath6kl_fwlog_read(struct file *file, char __user *user_buf, | ||
304 | size_t count, loff_t *ppos) | ||
305 | { | ||
306 | struct ath6kl *ar = file->private_data; | ||
307 | struct circ_buf *fwlog = &ar->debug.fwlog_buf; | ||
308 | size_t len = 0, buf_len = count; | ||
309 | ssize_t ret_cnt; | ||
310 | char *buf; | ||
311 | int ccnt; | ||
312 | |||
313 | buf = vmalloc(buf_len); | ||
314 | if (!buf) | ||
315 | return -ENOMEM; | ||
316 | |||
317 | /* read undelivered logs from firmware */ | ||
318 | ath6kl_read_fwlogs(ar); | ||
319 | |||
320 | spin_lock_bh(&ar->debug.fwlog_lock); | ||
321 | |||
322 | while (len < buf_len && !ath6kl_debug_fwlog_empty(ar)) { | ||
323 | ccnt = CIRC_CNT_TO_END(fwlog->head, fwlog->tail, | ||
324 | ATH6KL_FWLOG_SIZE); | ||
325 | |||
326 | if ((size_t) ccnt > buf_len - len) | ||
327 | ccnt = buf_len - len; | ||
328 | |||
329 | memcpy(buf + len, &fwlog->buf[fwlog->tail], ccnt); | ||
330 | len += ccnt; | ||
331 | |||
332 | fwlog->tail = (fwlog->tail + ccnt) & | ||
333 | (ATH6KL_FWLOG_SIZE - 1); | ||
334 | } | ||
335 | |||
336 | spin_unlock_bh(&ar->debug.fwlog_lock); | ||
337 | |||
338 | if (WARN_ON(len > buf_len)) | ||
339 | len = buf_len; | ||
340 | |||
341 | ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
342 | |||
343 | vfree(buf); | ||
344 | |||
345 | return ret_cnt; | ||
346 | } | ||
347 | |||
348 | static const struct file_operations fops_fwlog = { | ||
349 | .open = ath6kl_debugfs_open, | ||
350 | .read = ath6kl_fwlog_read, | ||
351 | .owner = THIS_MODULE, | ||
352 | .llseek = default_llseek, | ||
353 | }; | ||
354 | |||
355 | static ssize_t ath6kl_fwlog_mask_read(struct file *file, char __user *user_buf, | ||
356 | size_t count, loff_t *ppos) | ||
357 | { | ||
358 | struct ath6kl *ar = file->private_data; | ||
359 | char buf[16]; | ||
360 | int len; | ||
361 | |||
362 | len = snprintf(buf, sizeof(buf), "0x%x\n", ar->debug.fwlog_mask); | ||
363 | |||
364 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
365 | } | ||
366 | |||
367 | static ssize_t ath6kl_fwlog_mask_write(struct file *file, | ||
368 | const char __user *user_buf, | ||
369 | size_t count, loff_t *ppos) | ||
370 | { | ||
371 | struct ath6kl *ar = file->private_data; | ||
372 | int ret; | ||
373 | |||
374 | ret = kstrtou32_from_user(user_buf, count, 0, &ar->debug.fwlog_mask); | ||
375 | if (ret) | ||
376 | return ret; | ||
377 | |||
378 | ret = ath6kl_wmi_config_debug_module_cmd(ar->wmi, | ||
379 | ATH6KL_FWLOG_VALID_MASK, | ||
380 | ar->debug.fwlog_mask); | ||
381 | if (ret) | ||
382 | return ret; | ||
383 | |||
384 | return count; | ||
385 | } | ||
386 | |||
387 | static const struct file_operations fops_fwlog_mask = { | ||
388 | .open = ath6kl_debugfs_open, | ||
389 | .read = ath6kl_fwlog_mask_read, | ||
390 | .write = ath6kl_fwlog_mask_write, | ||
391 | .owner = THIS_MODULE, | ||
392 | .llseek = default_llseek, | ||
393 | }; | ||
394 | |||
395 | static ssize_t read_file_tgt_stats(struct file *file, char __user *user_buf, | ||
396 | size_t count, loff_t *ppos) | ||
397 | { | ||
398 | struct ath6kl *ar = file->private_data; | ||
399 | struct target_stats *tgt_stats = &ar->target_stats; | ||
400 | char *buf; | ||
401 | unsigned int len = 0, buf_len = 1500; | ||
402 | int i; | ||
403 | long left; | ||
404 | ssize_t ret_cnt; | ||
405 | |||
406 | buf = kzalloc(buf_len, GFP_KERNEL); | ||
407 | if (!buf) | ||
408 | return -ENOMEM; | ||
409 | |||
410 | if (down_interruptible(&ar->sem)) { | ||
411 | kfree(buf); | ||
412 | return -EBUSY; | ||
413 | } | ||
414 | |||
415 | set_bit(STATS_UPDATE_PEND, &ar->flag); | ||
416 | |||
417 | if (ath6kl_wmi_get_stats_cmd(ar->wmi)) { | ||
418 | up(&ar->sem); | ||
419 | kfree(buf); | ||
420 | return -EIO; | ||
421 | } | ||
422 | |||
423 | left = wait_event_interruptible_timeout(ar->event_wq, | ||
424 | !test_bit(STATS_UPDATE_PEND, | ||
425 | &ar->flag), WMI_TIMEOUT); | ||
426 | |||
427 | up(&ar->sem); | ||
428 | |||
429 | if (left <= 0) { | ||
430 | kfree(buf); | ||
431 | return -ETIMEDOUT; | ||
432 | } | ||
433 | |||
434 | len += scnprintf(buf + len, buf_len - len, "\n"); | ||
435 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
436 | "Target Tx stats"); | ||
437 | len += scnprintf(buf + len, buf_len - len, "%25s\n\n", | ||
438 | "================="); | ||
439 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
440 | "Ucast packets", tgt_stats->tx_ucast_pkt); | ||
441 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
442 | "Bcast packets", tgt_stats->tx_bcast_pkt); | ||
443 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
444 | "Ucast byte", tgt_stats->tx_ucast_byte); | ||
445 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
446 | "Bcast byte", tgt_stats->tx_bcast_byte); | ||
447 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
448 | "Rts success cnt", tgt_stats->tx_rts_success_cnt); | ||
449 | for (i = 0; i < 4; i++) | ||
450 | len += scnprintf(buf + len, buf_len - len, | ||
451 | "%18s %d %10llu\n", "PER on ac", | ||
452 | i, tgt_stats->tx_pkt_per_ac[i]); | ||
453 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
454 | "Error", tgt_stats->tx_err); | ||
455 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
456 | "Fail count", tgt_stats->tx_fail_cnt); | ||
457 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
458 | "Retry count", tgt_stats->tx_retry_cnt); | ||
459 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
460 | "Multi retry cnt", tgt_stats->tx_mult_retry_cnt); | ||
461 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
462 | "Rts fail cnt", tgt_stats->tx_rts_fail_cnt); | ||
463 | len += scnprintf(buf + len, buf_len - len, "%25s %10llu\n\n", | ||
464 | "TKIP counter measure used", | ||
465 | tgt_stats->tkip_cnter_measures_invoked); | ||
466 | |||
467 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
468 | "Target Rx stats"); | ||
469 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
470 | "================="); | ||
471 | |||
472 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
473 | "Ucast packets", tgt_stats->rx_ucast_pkt); | ||
474 | len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", | ||
475 | "Ucast Rate", tgt_stats->rx_ucast_rate); | ||
476 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
477 | "Bcast packets", tgt_stats->rx_bcast_pkt); | ||
478 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
479 | "Ucast byte", tgt_stats->rx_ucast_byte); | ||
480 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
481 | "Bcast byte", tgt_stats->rx_bcast_byte); | ||
482 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
483 | "Fragmented pkt", tgt_stats->rx_frgment_pkt); | ||
484 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
485 | "Error", tgt_stats->rx_err); | ||
486 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
487 | "CRC Err", tgt_stats->rx_crc_err); | ||
488 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
489 | "Key chache miss", tgt_stats->rx_key_cache_miss); | ||
490 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
491 | "Decrypt Err", tgt_stats->rx_decrypt_err); | ||
492 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
493 | "Duplicate frame", tgt_stats->rx_dupl_frame); | ||
494 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
495 | "Tkip Mic failure", tgt_stats->tkip_local_mic_fail); | ||
496 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
497 | "TKIP format err", tgt_stats->tkip_fmt_err); | ||
498 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
499 | "CCMP format Err", tgt_stats->ccmp_fmt_err); | ||
500 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n\n", | ||
501 | "CCMP Replay Err", tgt_stats->ccmp_replays); | ||
502 | |||
503 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
504 | "Misc Target stats"); | ||
505 | len += scnprintf(buf + len, buf_len - len, "%25s\n", | ||
506 | "================="); | ||
507 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
508 | "Beacon Miss count", tgt_stats->cs_bmiss_cnt); | ||
509 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
510 | "Num Connects", tgt_stats->cs_connect_cnt); | ||
511 | len += scnprintf(buf + len, buf_len - len, "%20s %10llu\n", | ||
512 | "Num disconnects", tgt_stats->cs_discon_cnt); | ||
513 | len += scnprintf(buf + len, buf_len - len, "%20s %10d\n", | ||
514 | "Beacon avg rssi", tgt_stats->cs_ave_beacon_rssi); | ||
515 | |||
516 | if (len > buf_len) | ||
517 | len = buf_len; | ||
518 | |||
519 | ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
520 | |||
521 | kfree(buf); | ||
522 | return ret_cnt; | ||
523 | } | ||
524 | |||
525 | static const struct file_operations fops_tgt_stats = { | ||
526 | .read = read_file_tgt_stats, | ||
527 | .open = ath6kl_debugfs_open, | ||
528 | .owner = THIS_MODULE, | ||
529 | .llseek = default_llseek, | ||
530 | }; | ||
531 | |||
532 | #define print_credit_info(fmt_str, ep_list_field) \ | ||
533 | (len += scnprintf(buf + len, buf_len - len, fmt_str, \ | ||
534 | ep_list->ep_list_field)) | ||
535 | #define CREDIT_INFO_DISPLAY_STRING_LEN 200 | ||
536 | #define CREDIT_INFO_LEN 128 | ||
537 | |||
538 | static ssize_t read_file_credit_dist_stats(struct file *file, | ||
539 | char __user *user_buf, | ||
540 | size_t count, loff_t *ppos) | ||
541 | { | ||
542 | struct ath6kl *ar = file->private_data; | ||
543 | struct htc_target *target = ar->htc_target; | ||
544 | struct htc_endpoint_credit_dist *ep_list; | ||
545 | char *buf; | ||
546 | unsigned int buf_len, len = 0; | ||
547 | ssize_t ret_cnt; | ||
548 | |||
549 | buf_len = CREDIT_INFO_DISPLAY_STRING_LEN + | ||
550 | get_queue_depth(&target->cred_dist_list) * CREDIT_INFO_LEN; | ||
551 | buf = kzalloc(buf_len, GFP_KERNEL); | ||
552 | if (!buf) | ||
553 | return -ENOMEM; | ||
554 | |||
555 | len += scnprintf(buf + len, buf_len - len, "%25s%5d\n", | ||
556 | "Total Avail Credits: ", | ||
557 | target->cred_dist_cntxt->total_avail_credits); | ||
558 | len += scnprintf(buf + len, buf_len - len, "%25s%5d\n", | ||
559 | "Free credits :", | ||
560 | target->cred_dist_cntxt->cur_free_credits); | ||
561 | |||
562 | len += scnprintf(buf + len, buf_len - len, | ||
563 | " Epid Flags Cred_norm Cred_min Credits Cred_assngd" | ||
564 | " Seek_cred Cred_sz Cred_per_msg Cred_to_dist" | ||
565 | " qdepth\n"); | ||
566 | |||
567 | list_for_each_entry(ep_list, &target->cred_dist_list, list) { | ||
568 | print_credit_info(" %2d", endpoint); | ||
569 | print_credit_info("%10x", dist_flags); | ||
570 | print_credit_info("%8d", cred_norm); | ||
571 | print_credit_info("%9d", cred_min); | ||
572 | print_credit_info("%9d", credits); | ||
573 | print_credit_info("%10d", cred_assngd); | ||
574 | print_credit_info("%13d", seek_cred); | ||
575 | print_credit_info("%12d", cred_sz); | ||
576 | print_credit_info("%9d", cred_per_msg); | ||
577 | print_credit_info("%14d", cred_to_dist); | ||
578 | len += scnprintf(buf + len, buf_len - len, "%12d\n", | ||
579 | get_queue_depth(&((struct htc_endpoint *) | ||
580 | ep_list->htc_rsvd)->txq)); | ||
581 | } | ||
582 | |||
583 | if (len > buf_len) | ||
584 | len = buf_len; | ||
585 | |||
586 | ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
587 | kfree(buf); | ||
588 | return ret_cnt; | ||
589 | } | ||
590 | |||
591 | static const struct file_operations fops_credit_dist_stats = { | ||
592 | .read = read_file_credit_dist_stats, | ||
593 | .open = ath6kl_debugfs_open, | ||
594 | .owner = THIS_MODULE, | ||
595 | .llseek = default_llseek, | ||
596 | }; | ||
597 | |||
598 | static unsigned long ath6kl_get_num_reg(void) | ||
599 | { | ||
600 | int i; | ||
601 | unsigned long n_reg = 0; | ||
602 | |||
603 | for (i = 0; i < ARRAY_SIZE(diag_reg); i++) | ||
604 | n_reg = n_reg + | ||
605 | (diag_reg[i].reg_end - diag_reg[i].reg_start) / 4 + 1; | ||
606 | |||
607 | return n_reg; | ||
608 | } | ||
609 | |||
610 | static bool ath6kl_dbg_is_diag_reg_valid(u32 reg_addr) | ||
611 | { | ||
612 | int i; | ||
613 | |||
614 | for (i = 0; i < ARRAY_SIZE(diag_reg); i++) { | ||
615 | if (reg_addr >= diag_reg[i].reg_start && | ||
616 | reg_addr <= diag_reg[i].reg_end) | ||
617 | return true; | ||
618 | } | ||
619 | |||
620 | return false; | ||
621 | } | ||
622 | |||
623 | static ssize_t ath6kl_regread_read(struct file *file, char __user *user_buf, | ||
624 | size_t count, loff_t *ppos) | ||
625 | { | ||
626 | struct ath6kl *ar = file->private_data; | ||
627 | u8 buf[50]; | ||
628 | unsigned int len = 0; | ||
629 | |||
630 | if (ar->debug.dbgfs_diag_reg) | ||
631 | len += scnprintf(buf + len, sizeof(buf) - len, "0x%x\n", | ||
632 | ar->debug.dbgfs_diag_reg); | ||
633 | else | ||
634 | len += scnprintf(buf + len, sizeof(buf) - len, | ||
635 | "All diag registers\n"); | ||
636 | |||
637 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
638 | } | ||
639 | |||
640 | static ssize_t ath6kl_regread_write(struct file *file, | ||
641 | const char __user *user_buf, | ||
642 | size_t count, loff_t *ppos) | ||
643 | { | ||
644 | struct ath6kl *ar = file->private_data; | ||
645 | u8 buf[50]; | ||
646 | unsigned int len; | ||
647 | unsigned long reg_addr; | ||
648 | |||
649 | len = min(count, sizeof(buf) - 1); | ||
650 | if (copy_from_user(buf, user_buf, len)) | ||
651 | return -EFAULT; | ||
652 | |||
653 | buf[len] = '\0'; | ||
654 | |||
655 | if (strict_strtoul(buf, 0, ®_addr)) | ||
656 | return -EINVAL; | ||
657 | |||
658 | if ((reg_addr % 4) != 0) | ||
659 | return -EINVAL; | ||
660 | |||
661 | if (reg_addr && !ath6kl_dbg_is_diag_reg_valid(reg_addr)) | ||
662 | return -EINVAL; | ||
663 | |||
664 | ar->debug.dbgfs_diag_reg = reg_addr; | ||
665 | |||
666 | return count; | ||
667 | } | ||
668 | |||
669 | static const struct file_operations fops_diag_reg_read = { | ||
670 | .read = ath6kl_regread_read, | ||
671 | .write = ath6kl_regread_write, | ||
672 | .open = ath6kl_debugfs_open, | ||
673 | .owner = THIS_MODULE, | ||
674 | .llseek = default_llseek, | ||
675 | }; | ||
676 | |||
677 | static int ath6kl_regdump_open(struct inode *inode, struct file *file) | ||
678 | { | ||
679 | struct ath6kl *ar = inode->i_private; | ||
680 | u8 *buf; | ||
681 | unsigned long int reg_len; | ||
682 | unsigned int len = 0, n_reg; | ||
683 | u32 addr; | ||
684 | __le32 reg_val; | ||
685 | int i, status; | ||
686 | |||
687 | /* Dump all the registers if no register is specified */ | ||
688 | if (!ar->debug.dbgfs_diag_reg) | ||
689 | n_reg = ath6kl_get_num_reg(); | ||
690 | else | ||
691 | n_reg = 1; | ||
692 | |||
693 | reg_len = n_reg * REG_OUTPUT_LEN_PER_LINE; | ||
694 | if (n_reg > 1) | ||
695 | reg_len += REGTYPE_STR_LEN; | ||
696 | |||
697 | buf = vmalloc(reg_len); | ||
698 | if (!buf) | ||
699 | return -ENOMEM; | ||
700 | |||
701 | if (n_reg == 1) { | ||
702 | addr = ar->debug.dbgfs_diag_reg; | ||
703 | |||
704 | status = ath6kl_diag_read32(ar, | ||
705 | TARG_VTOP(ar->target_type, addr), | ||
706 | (u32 *)®_val); | ||
707 | if (status) | ||
708 | goto fail_reg_read; | ||
709 | |||
710 | len += scnprintf(buf + len, reg_len - len, | ||
711 | "0x%06x 0x%08x\n", addr, le32_to_cpu(reg_val)); | ||
712 | goto done; | ||
713 | } | ||
714 | |||
715 | for (i = 0; i < ARRAY_SIZE(diag_reg); i++) { | ||
716 | len += scnprintf(buf + len, reg_len - len, | ||
717 | "%s\n", diag_reg[i].reg_info); | ||
718 | for (addr = diag_reg[i].reg_start; | ||
719 | addr <= diag_reg[i].reg_end; addr += 4) { | ||
720 | status = ath6kl_diag_read32(ar, | ||
721 | TARG_VTOP(ar->target_type, addr), | ||
722 | (u32 *)®_val); | ||
723 | if (status) | ||
724 | goto fail_reg_read; | ||
725 | |||
726 | len += scnprintf(buf + len, reg_len - len, | ||
727 | "0x%06x 0x%08x\n", | ||
728 | addr, le32_to_cpu(reg_val)); | ||
729 | } | ||
730 | } | ||
731 | |||
732 | done: | ||
733 | file->private_data = buf; | ||
734 | return 0; | ||
735 | |||
736 | fail_reg_read: | ||
737 | ath6kl_warn("Unable to read memory:%u\n", addr); | ||
738 | vfree(buf); | ||
739 | return -EIO; | ||
740 | } | ||
741 | |||
742 | static ssize_t ath6kl_regdump_read(struct file *file, char __user *user_buf, | ||
743 | size_t count, loff_t *ppos) | ||
744 | { | ||
745 | u8 *buf = file->private_data; | ||
746 | return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); | ||
747 | } | ||
748 | |||
749 | static int ath6kl_regdump_release(struct inode *inode, struct file *file) | ||
750 | { | ||
751 | vfree(file->private_data); | ||
752 | return 0; | ||
753 | } | ||
754 | |||
755 | static const struct file_operations fops_reg_dump = { | ||
756 | .open = ath6kl_regdump_open, | ||
757 | .read = ath6kl_regdump_read, | ||
758 | .release = ath6kl_regdump_release, | ||
759 | .owner = THIS_MODULE, | ||
760 | .llseek = default_llseek, | ||
761 | }; | ||
762 | |||
763 | static ssize_t ath6kl_lrssi_roam_write(struct file *file, | ||
764 | const char __user *user_buf, | ||
765 | size_t count, loff_t *ppos) | ||
766 | { | ||
767 | struct ath6kl *ar = file->private_data; | ||
768 | unsigned long lrssi_roam_threshold; | ||
769 | char buf[32]; | ||
770 | ssize_t len; | ||
771 | |||
772 | len = min(count, sizeof(buf) - 1); | ||
773 | if (copy_from_user(buf, user_buf, len)) | ||
774 | return -EFAULT; | ||
775 | |||
776 | buf[len] = '\0'; | ||
777 | if (strict_strtoul(buf, 0, &lrssi_roam_threshold)) | ||
778 | return -EINVAL; | ||
779 | |||
780 | ar->lrssi_roam_threshold = lrssi_roam_threshold; | ||
781 | |||
782 | ath6kl_wmi_set_roam_lrssi_cmd(ar->wmi, ar->lrssi_roam_threshold); | ||
783 | |||
784 | return count; | ||
785 | } | ||
786 | |||
787 | static ssize_t ath6kl_lrssi_roam_read(struct file *file, | ||
788 | char __user *user_buf, | ||
789 | size_t count, loff_t *ppos) | ||
790 | { | ||
791 | struct ath6kl *ar = file->private_data; | ||
792 | char buf[32]; | ||
793 | unsigned int len; | ||
794 | |||
795 | len = snprintf(buf, sizeof(buf), "%u\n", ar->lrssi_roam_threshold); | ||
796 | |||
797 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
798 | } | ||
799 | |||
800 | static const struct file_operations fops_lrssi_roam_threshold = { | ||
801 | .read = ath6kl_lrssi_roam_read, | ||
802 | .write = ath6kl_lrssi_roam_write, | ||
803 | .open = ath6kl_debugfs_open, | ||
804 | .owner = THIS_MODULE, | ||
805 | .llseek = default_llseek, | ||
806 | }; | ||
807 | |||
808 | static ssize_t ath6kl_regwrite_read(struct file *file, | ||
809 | char __user *user_buf, | ||
810 | size_t count, loff_t *ppos) | ||
811 | { | ||
812 | struct ath6kl *ar = file->private_data; | ||
813 | u8 buf[32]; | ||
814 | unsigned int len = 0; | ||
815 | |||
816 | len = scnprintf(buf, sizeof(buf), "Addr: 0x%x Val: 0x%x\n", | ||
817 | ar->debug.diag_reg_addr_wr, ar->debug.diag_reg_val_wr); | ||
818 | |||
819 | return simple_read_from_buffer(user_buf, count, ppos, buf, len); | ||
820 | } | ||
821 | |||
822 | static ssize_t ath6kl_regwrite_write(struct file *file, | ||
823 | const char __user *user_buf, | ||
824 | size_t count, loff_t *ppos) | ||
825 | { | ||
826 | struct ath6kl *ar = file->private_data; | ||
827 | char buf[32]; | ||
828 | char *sptr, *token; | ||
829 | unsigned int len = 0; | ||
830 | u32 reg_addr, reg_val; | ||
831 | |||
832 | len = min(count, sizeof(buf) - 1); | ||
833 | if (copy_from_user(buf, user_buf, len)) | ||
834 | return -EFAULT; | ||
835 | |||
836 | buf[len] = '\0'; | ||
837 | sptr = buf; | ||
838 | |||
839 | token = strsep(&sptr, "="); | ||
840 | if (!token) | ||
841 | return -EINVAL; | ||
842 | |||
843 | if (kstrtou32(token, 0, ®_addr)) | ||
844 | return -EINVAL; | ||
845 | |||
846 | if (!ath6kl_dbg_is_diag_reg_valid(reg_addr)) | ||
847 | return -EINVAL; | ||
848 | |||
849 | if (kstrtou32(sptr, 0, ®_val)) | ||
850 | return -EINVAL; | ||
851 | |||
852 | ar->debug.diag_reg_addr_wr = reg_addr; | ||
853 | ar->debug.diag_reg_val_wr = reg_val; | ||
854 | |||
855 | if (ath6kl_diag_write32(ar, ar->debug.diag_reg_addr_wr, | ||
856 | cpu_to_le32(ar->debug.diag_reg_val_wr))) | ||
857 | return -EIO; | ||
858 | |||
859 | return count; | ||
860 | } | ||
861 | |||
862 | static const struct file_operations fops_diag_reg_write = { | ||
863 | .read = ath6kl_regwrite_read, | ||
864 | .write = ath6kl_regwrite_write, | ||
865 | .open = ath6kl_debugfs_open, | ||
866 | .owner = THIS_MODULE, | ||
867 | .llseek = default_llseek, | ||
868 | }; | ||
869 | |||
870 | int ath6kl_debug_init(struct ath6kl *ar) | ||
871 | { | ||
872 | ar->debug.fwlog_buf.buf = vmalloc(ATH6KL_FWLOG_SIZE); | ||
873 | if (ar->debug.fwlog_buf.buf == NULL) | ||
874 | return -ENOMEM; | ||
875 | |||
876 | ar->debug.fwlog_tmp = kmalloc(ATH6KL_FWLOG_SLOT_SIZE, GFP_KERNEL); | ||
877 | if (ar->debug.fwlog_tmp == NULL) { | ||
878 | vfree(ar->debug.fwlog_buf.buf); | ||
879 | return -ENOMEM; | ||
880 | } | ||
881 | |||
882 | spin_lock_init(&ar->debug.fwlog_lock); | ||
883 | |||
884 | /* | ||
885 | * Actually we are lying here but don't know how to read the mask | ||
886 | * value from the firmware. | ||
887 | */ | ||
888 | ar->debug.fwlog_mask = 0; | ||
889 | |||
890 | ar->debugfs_phy = debugfs_create_dir("ath6kl", | ||
891 | ar->wdev->wiphy->debugfsdir); | ||
892 | if (!ar->debugfs_phy) { | ||
893 | vfree(ar->debug.fwlog_buf.buf); | ||
894 | kfree(ar->debug.fwlog_tmp); | ||
895 | return -ENOMEM; | ||
896 | } | ||
897 | |||
898 | debugfs_create_file("tgt_stats", S_IRUSR, ar->debugfs_phy, ar, | ||
899 | &fops_tgt_stats); | ||
900 | |||
901 | debugfs_create_file("credit_dist_stats", S_IRUSR, ar->debugfs_phy, ar, | ||
902 | &fops_credit_dist_stats); | ||
903 | |||
904 | debugfs_create_file("fwlog", S_IRUSR, ar->debugfs_phy, ar, | ||
905 | &fops_fwlog); | ||
906 | |||
907 | debugfs_create_file("fwlog_mask", S_IRUSR | S_IWUSR, ar->debugfs_phy, | ||
908 | ar, &fops_fwlog_mask); | ||
909 | |||
910 | debugfs_create_file("reg_addr", S_IRUSR | S_IWUSR, ar->debugfs_phy, ar, | ||
911 | &fops_diag_reg_read); | ||
912 | |||
913 | debugfs_create_file("reg_dump", S_IRUSR, ar->debugfs_phy, ar, | ||
914 | &fops_reg_dump); | ||
915 | |||
916 | debugfs_create_file("lrssi_roam_threshold", S_IRUSR | S_IWUSR, | ||
917 | ar->debugfs_phy, ar, &fops_lrssi_roam_threshold); | ||
918 | |||
919 | debugfs_create_file("reg_write", S_IRUSR | S_IWUSR, | ||
920 | ar->debugfs_phy, ar, &fops_diag_reg_write); | ||
921 | |||
922 | debugfs_create_file("war_stats", S_IRUSR, ar->debugfs_phy, ar, | ||
923 | &fops_war_stats); | ||
924 | |||
925 | return 0; | ||
926 | } | ||
927 | |||
928 | void ath6kl_debug_cleanup(struct ath6kl *ar) | ||
929 | { | ||
930 | vfree(ar->debug.fwlog_buf.buf); | ||
931 | kfree(ar->debug.fwlog_tmp); | ||
932 | } | ||
933 | |||
150 | #endif | 934 | #endif |