diff options
author | Stephen Hemminger <shemminger@linux-foundation.org> | 2007-07-09 15:33:35 -0700 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2007-07-10 12:22:28 -0400 |
commit | 3cf267539f1f133eb6ba63d074da18cb58cdf89a (patch) | |
tree | c35a52a717702fdade349c1af0d7013bb7c51115 /drivers | |
parent | 55d7b4e6ed6ad3ec5e5e30b3b4515a0a6a53e344 (diff) |
sky2: debug interface
Add an optional debug interface for displaying state of transmit/receive
rings. Creates a file debugfs/sky2/ethX for each device that is up.
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/sky2.c | 196 | ||||
-rw-r--r-- | drivers/net/sky2.h | 4 |
3 files changed, 208 insertions, 2 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5cc3d517e39b..58c6aa28baff 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2133,6 +2133,16 @@ config SKY2 To compile this driver as a module, choose M here: the module will be called sky2. This is recommended. +config SKY2_DEBUG + bool "Debugging interface" + depends on SKY2 && DEBUG_FS + help + This option adds the ability to dump driver state for debugging. + The file debugfs/sky2/ethX displays the state of the internal + transmit and receive rings. + + If unsure, say N. + config SK98LIN tristate "Marvell Yukon Chipset / SysKonnect SK-98xx Support (DEPRECATED)" depends on PCI diff --git a/drivers/net/sky2.c b/drivers/net/sky2.c index f6fe2861cc4c..90b1b9708178 100644 --- a/drivers/net/sky2.c +++ b/drivers/net/sky2.c @@ -39,6 +39,7 @@ #include <linux/workqueue.h> #include <linux/if_vlan.h> #include <linux/prefetch.h> +#include <linux/debugfs.h> #include <linux/mii.h> #include <asm/irq.h> @@ -1574,13 +1575,13 @@ static void sky2_tx_complete(struct sky2_port *sky2, u16 done) if (unlikely(netif_msg_tx_done(sky2))) printk(KERN_DEBUG "%s: tx done %u\n", dev->name, idx); + sky2->net_stats.tx_packets++; sky2->net_stats.tx_bytes += re->skb->len; dev_kfree_skb_any(re->skb); + sky2->tx_next = RING_NEXT(idx, TX_RING_SIZE); } - - le->opcode = 0; /* paranoia */ } sky2->tx_cons = idx; @@ -3470,6 +3471,195 @@ static const struct ethtool_ops sky2_ethtool_ops = { .get_perm_addr = ethtool_op_get_perm_addr, }; +#ifdef CONFIG_SKY2_DEBUG + +static struct dentry *sky2_debug; + +static int sky2_debug_show(struct seq_file *seq, void *v) +{ + struct net_device *dev = seq->private; + const struct sky2_port *sky2 = netdev_priv(dev); + const struct sky2_hw *hw = sky2->hw; + unsigned port = sky2->port; + unsigned idx, last; + int sop; + + if (!netif_running(dev)) + return -ENETDOWN; + + seq_printf(seq, "IRQ src=%x mask=%x control=%x\n", + sky2_read32(hw, B0_ISRC), + sky2_read32(hw, B0_IMSK), + sky2_read32(hw, B0_Y2_SP_ICR)); + + netif_poll_disable(hw->dev[0]); + last = sky2_read16(hw, STAT_PUT_IDX); + + if (hw->st_idx == last) + seq_puts(seq, "Status ring (empty)\n"); + else { + seq_puts(seq, "Status ring\n"); + for (idx = hw->st_idx; idx != last && idx < STATUS_RING_SIZE; + idx = RING_NEXT(idx, STATUS_RING_SIZE)) { + const struct sky2_status_le *le = hw->st_le + idx; + seq_printf(seq, "[%d] %#x %d %#x\n", + idx, le->opcode, le->length, le->status); + } + seq_puts(seq, "\n"); + } + + seq_printf(seq, "Tx ring pending=%u...%u report=%d done=%d\n", + sky2->tx_cons, sky2->tx_prod, + sky2_read16(hw, port == 0 ? STAT_TXA1_RIDX : STAT_TXA2_RIDX), + sky2_read16(hw, Q_ADDR(txqaddr[port], Q_DONE))); + + /* Dump contents of tx ring */ + sop = 1; + for (idx = sky2->tx_next; idx != sky2->tx_prod && idx < TX_RING_SIZE; + idx = RING_NEXT(idx, TX_RING_SIZE)) { + const struct sky2_tx_le *le = sky2->tx_le + idx; + u32 a = le32_to_cpu(le->addr); + + if (sop) + seq_printf(seq, "%u:", idx); + sop = 0; + + switch(le->opcode & ~HW_OWNER) { + case OP_ADDR64: + seq_printf(seq, " %#x:", a); + break; + case OP_LRGLEN: + seq_printf(seq, " mtu=%d", a); + break; + case OP_VLAN: + seq_printf(seq, " vlan=%d", be16_to_cpu(le->length)); + break; + case OP_TCPLISW: + seq_printf(seq, " csum=%#x", a); + break; + case OP_LARGESEND: + seq_printf(seq, " tso=%#x(%d)", a, le16_to_cpu(le->length)); + break; + case OP_PACKET: + seq_printf(seq, " %#x(%d)", a, le16_to_cpu(le->length)); + break; + case OP_BUFFER: + seq_printf(seq, " frag=%#x(%d)", a, le16_to_cpu(le->length)); + break; + default: + seq_printf(seq, " op=%#x,%#x(%d)", le->opcode, + a, le16_to_cpu(le->length)); + } + + if (le->ctrl & EOP) { + seq_putc(seq, '\n'); + sop = 1; + } + } + + seq_printf(seq, "\nRx ring hw get=%d put=%d last=%d\n", + sky2_read16(hw, Y2_QADDR(rxqaddr[port], PREF_UNIT_GET_IDX)), + last = sky2_read16(hw, Y2_QADDR(rxqaddr[port], PREF_UNIT_PUT_IDX)), + sky2_read16(hw, Y2_QADDR(rxqaddr[port], PREF_UNIT_LAST_IDX))); + + netif_poll_enable(hw->dev[0]); + return 0; +} + +static int sky2_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, sky2_debug_show, inode->i_private); +} + +static const struct file_operations sky2_debug_fops = { + .owner = THIS_MODULE, + .open = sky2_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Use network device events to create/remove/rename + * debugfs file entries + */ +static int sky2_device_event(struct notifier_block *unused, + unsigned long event, void *ptr) +{ + struct net_device *dev = ptr; + + if (dev->open == sky2_up) { + struct sky2_port *sky2 = netdev_priv(dev); + + switch(event) { + case NETDEV_CHANGENAME: + if (!netif_running(dev)) + break; + /* fallthrough */ + case NETDEV_DOWN: + case NETDEV_GOING_DOWN: + if (sky2->debugfs) { + printk(KERN_DEBUG PFX "%s: remove debugfs\n", + dev->name); + debugfs_remove(sky2->debugfs); + sky2->debugfs = NULL; + } + + if (event != NETDEV_CHANGENAME) + break; + /* fallthrough for changename */ + case NETDEV_UP: + if (sky2_debug) { + struct dentry *d; + d = debugfs_create_file(dev->name, S_IRUGO, + sky2_debug, dev, + &sky2_debug_fops); + if (d == NULL || IS_ERR(d)) + printk(KERN_INFO PFX + "%s: debugfs create failed\n", + dev->name); + else + sky2->debugfs = d; + } + break; + } + } + + return NOTIFY_DONE; +} + +static struct notifier_block sky2_notifier = { + .notifier_call = sky2_device_event, +}; + + +static __init void sky2_debug_init(void) +{ + struct dentry *ent; + + ent = debugfs_create_dir("sky2", NULL); + if (!ent || IS_ERR(ent)) + return; + + sky2_debug = ent; + register_netdevice_notifier(&sky2_notifier); +} + +static __exit void sky2_debug_cleanup(void) +{ + if (sky2_debug) { + unregister_netdevice_notifier(&sky2_notifier); + debugfs_remove(sky2_debug); + sky2_debug = NULL; + } +} + +#else +#define sky2_debug_init() +#define sky2_debug_cleanup() +#endif + + /* Initialize network device */ static __devinit struct net_device *sky2_init_netdev(struct sky2_hw *hw, unsigned port, @@ -3960,12 +4150,14 @@ static struct pci_driver sky2_driver = { static int __init sky2_init_module(void) { + sky2_debug_init(); return pci_register_driver(&sky2_driver); } static void __exit sky2_cleanup_module(void) { pci_unregister_driver(&sky2_driver); + sky2_debug_cleanup(); } module_init(sky2_init_module); diff --git a/drivers/net/sky2.h b/drivers/net/sky2.h index 8df4643493d1..dce4d276d443 100644 --- a/drivers/net/sky2.h +++ b/drivers/net/sky2.h @@ -1998,6 +1998,7 @@ struct sky2_port { struct sky2_tx_le *tx_le; u16 tx_cons; /* next le to check */ u16 tx_prod; /* next le to use */ + u16 tx_next; /* debug only */ u32 tx_addr64; u16 tx_pending; u16 tx_last_mss; @@ -2028,6 +2029,9 @@ struct sky2_port { enum flow_control flow_mode; enum flow_control flow_status; +#ifdef CONFIG_SKY2_DEBUG + struct dentry *debugfs; +#endif struct net_device_stats net_stats; }; |