| /* |
| * Atheros AR71xx built-in ethernet mac driver |
| * |
| * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> |
| * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org> |
| * |
| * Based on Atheros' AG7100 driver |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published |
| * by the Free Software Foundation. |
| */ |
| |
| #include <linux/debugfs.h> |
| |
| #include "ag71xx.h" |
| |
| static struct dentry *ag71xx_debugfs_root; |
| |
| static int ag71xx_debugfs_generic_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = inode->i_private; |
| return 0; |
| } |
| |
| void ag71xx_debugfs_update_int_stats(struct ag71xx *ag, u32 status) |
| { |
| if (status) |
| ag->debug.int_stats.total++; |
| if (status & AG71XX_INT_TX_PS) |
| ag->debug.int_stats.tx_ps++; |
| if (status & AG71XX_INT_TX_UR) |
| ag->debug.int_stats.tx_ur++; |
| if (status & AG71XX_INT_TX_BE) |
| ag->debug.int_stats.tx_be++; |
| if (status & AG71XX_INT_RX_PR) |
| ag->debug.int_stats.rx_pr++; |
| if (status & AG71XX_INT_RX_OF) |
| ag->debug.int_stats.rx_of++; |
| if (status & AG71XX_INT_RX_BE) |
| ag->debug.int_stats.rx_be++; |
| } |
| |
| static ssize_t read_file_int_stats(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| #define PR_INT_STAT(_label, _field) \ |
| len += snprintf(buf + len, sizeof(buf) - len, \ |
| "%20s: %10lu\n", _label, ag->debug.int_stats._field); |
| |
| struct ag71xx *ag = file->private_data; |
| char buf[256]; |
| unsigned int len = 0; |
| |
| PR_INT_STAT("TX Packet Sent", tx_ps); |
| PR_INT_STAT("TX Underrun", tx_ur); |
| PR_INT_STAT("TX Bus Error", tx_be); |
| PR_INT_STAT("RX Packet Received", rx_pr); |
| PR_INT_STAT("RX Overflow", rx_of); |
| PR_INT_STAT("RX Bus Error", rx_be); |
| len += snprintf(buf + len, sizeof(buf) - len, "\n"); |
| PR_INT_STAT("Total", total); |
| |
| return simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| #undef PR_INT_STAT |
| } |
| |
| static const struct file_operations ag71xx_fops_int_stats = { |
| .open = ag71xx_debugfs_generic_open, |
| .read = read_file_int_stats, |
| .owner = THIS_MODULE |
| }; |
| |
| void ag71xx_debugfs_update_napi_stats(struct ag71xx *ag, int rx, int tx) |
| { |
| struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; |
| |
| if (rx) { |
| stats->rx_count++; |
| stats->rx_packets += rx; |
| if (rx <= AG71XX_NAPI_WEIGHT) |
| stats->rx[rx]++; |
| if (rx > stats->rx_packets_max) |
| stats->rx_packets_max = rx; |
| } |
| |
| if (tx) { |
| stats->tx_count++; |
| stats->tx_packets += tx; |
| if (tx <= AG71XX_NAPI_WEIGHT) |
| stats->tx[tx]++; |
| if (tx > stats->tx_packets_max) |
| stats->tx_packets_max = tx; |
| } |
| } |
| |
| static ssize_t read_file_napi_stats(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ag71xx *ag = file->private_data; |
| struct ag71xx_napi_stats *stats = &ag->debug.napi_stats; |
| char *buf; |
| unsigned int buflen; |
| unsigned int len = 0; |
| unsigned long rx_avg = 0; |
| unsigned long tx_avg = 0; |
| int ret; |
| int i; |
| |
| buflen = 2048; |
| buf = kmalloc(buflen, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| if (stats->rx_count) |
| rx_avg = stats->rx_packets / stats->rx_count; |
| |
| if (stats->tx_count) |
| tx_avg = stats->tx_packets / stats->tx_count; |
| |
| len += snprintf(buf + len, buflen - len, "%3s %10s %10s\n", |
| "len", "rx", "tx"); |
| |
| for (i = 1; i <= AG71XX_NAPI_WEIGHT; i++) |
| len += snprintf(buf + len, buflen - len, |
| "%3d: %10lu %10lu\n", |
| i, stats->rx[i], stats->tx[i]); |
| |
| len += snprintf(buf + len, buflen - len, "\n"); |
| |
| len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", |
| "sum", stats->rx_count, stats->tx_count); |
| len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", |
| "avg", rx_avg, tx_avg); |
| len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", |
| "max", stats->rx_packets_max, stats->tx_packets_max); |
| len += snprintf(buf + len, buflen - len, "%3s: %10lu %10lu\n", |
| "pkt", stats->rx_packets, stats->tx_packets); |
| |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| kfree(buf); |
| |
| return ret; |
| } |
| |
| static const struct file_operations ag71xx_fops_napi_stats = { |
| .open = ag71xx_debugfs_generic_open, |
| .read = read_file_napi_stats, |
| .owner = THIS_MODULE |
| }; |
| |
| #define DESC_PRINT_LEN 64 |
| |
| static ssize_t read_file_ring(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos, |
| struct ag71xx *ag, |
| struct ag71xx_ring *ring, |
| unsigned desc_reg) |
| { |
| char *buf; |
| unsigned int buflen; |
| unsigned int len = 0; |
| unsigned long flags; |
| ssize_t ret; |
| u32 desc_hw; |
| int i; |
| |
| buflen = (ring->size * DESC_PRINT_LEN); |
| buf = kmalloc(buflen, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| len += snprintf(buf + len, buflen - len, |
| "Idx ... %-8s %-8s %-8s %-8s\n", |
| "desc", "next", "data", "ctrl"); |
| |
| spin_lock_irqsave(&ag->lock, flags); |
| |
| desc_hw = ag71xx_rr(ag, desc_reg); |
| for (i = 0; i < ring->size; i++) { |
| struct ag71xx_buf *ab = &ring->buf[i]; |
| u32 desc_dma = ((u32) ring->descs_dma) + i * ring->desc_size; |
| |
| len += snprintf(buf + len, buflen - len, |
| "%3d %c%c%c %08x %08x %08x %08x %c\n", |
| i, |
| (&ring->buf[i] == ring->curr) ? 'C' : ' ', |
| (&ring->buf[i] == ring->dirty) ? 'D' : ' ', |
| (desc_hw == desc_dma) ? 'H' : ' ', |
| desc_dma, |
| ab->desc->next, |
| ab->desc->data, |
| ab->desc->ctrl, |
| (ab->desc->ctrl & DESC_EMPTY) ? 'E' : '*'); |
| } |
| |
| spin_unlock_irqrestore(&ag->lock, flags); |
| |
| ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); |
| kfree(buf); |
| |
| return ret; |
| } |
| |
| static ssize_t read_file_tx_ring(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ag71xx *ag = file->private_data; |
| |
| return read_file_ring(file, user_buf, count, ppos, ag, &ag->tx_ring, |
| AG71XX_REG_TX_DESC); |
| } |
| |
| static const struct file_operations ag71xx_fops_tx_ring = { |
| .open = ag71xx_debugfs_generic_open, |
| .read = read_file_tx_ring, |
| .owner = THIS_MODULE |
| }; |
| |
| static ssize_t read_file_rx_ring(struct file *file, char __user *user_buf, |
| size_t count, loff_t *ppos) |
| { |
| struct ag71xx *ag = file->private_data; |
| |
| return read_file_ring(file, user_buf, count, ppos, ag, &ag->rx_ring, |
| AG71XX_REG_RX_DESC); |
| } |
| |
| static const struct file_operations ag71xx_fops_rx_ring = { |
| .open = ag71xx_debugfs_generic_open, |
| .read = read_file_rx_ring, |
| .owner = THIS_MODULE |
| }; |
| |
| void ag71xx_debugfs_exit(struct ag71xx *ag) |
| { |
| debugfs_remove_recursive(ag->debug.debugfs_dir); |
| } |
| |
| int ag71xx_debugfs_init(struct ag71xx *ag) |
| { |
| ag->debug.debugfs_dir = debugfs_create_dir(ag->dev->name, |
| ag71xx_debugfs_root); |
| if (!ag->debug.debugfs_dir) |
| return -ENOMEM; |
| |
| debugfs_create_file("int_stats", S_IRUGO, ag->debug.debugfs_dir, |
| ag, &ag71xx_fops_int_stats); |
| debugfs_create_file("napi_stats", S_IRUGO, ag->debug.debugfs_dir, |
| ag, &ag71xx_fops_napi_stats); |
| debugfs_create_file("tx_ring", S_IRUGO, ag->debug.debugfs_dir, |
| ag, &ag71xx_fops_tx_ring); |
| debugfs_create_file("rx_ring", S_IRUGO, ag->debug.debugfs_dir, |
| ag, &ag71xx_fops_rx_ring); |
| |
| return 0; |
| } |
| |
| int ag71xx_debugfs_root_init(void) |
| { |
| if (ag71xx_debugfs_root) |
| return -EBUSY; |
| |
| ag71xx_debugfs_root = debugfs_create_dir(KBUILD_MODNAME, NULL); |
| if (!ag71xx_debugfs_root) |
| return -ENOENT; |
| |
| return 0; |
| } |
| |
| void ag71xx_debugfs_root_exit(void) |
| { |
| debugfs_remove(ag71xx_debugfs_root); |
| ag71xx_debugfs_root = NULL; |
| } |