sky2: debug interface
authorStephen Hemminger <shemminger@linux-foundation.org>
Mon, 9 Jul 2007 22:33:35 +0000 (15:33 -0700)
committerJeff Garzik <jeff@garzik.org>
Tue, 10 Jul 2007 16:22:28 +0000 (12:22 -0400)
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>
drivers/net/Kconfig
drivers/net/sky2.c
drivers/net/sky2.h

index 5cc3d517e39b8edb76bda661b447738e3ebf9bcc..58c6aa28baff8f79a2cb957a5c176848eab932cc 100644 (file)
@@ -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
index f6fe2861cc4c41e28c25192e1d8c7210eac424bc..90b1b970817853fd25fc6d14d92465a8859ce476 100644 (file)
@@ -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);
index 8df4643493d19408b81c33418a942ac378408274..dce4d276d4435e685232c7073ae98d26b9e1f818 100644 (file)
@@ -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;
 
 };