sky2: receive FIFO checking
Stephen Hemminger [Wed, 19 Sep 2007 22:36:46 +0000 (15:36 -0700)]
A driver writer from another operating system hinted that
the versions of Yukon 2 chip with rambuffer (EC and XL) have
a hardware bug that if the FIFO ever gets completely full it
will hang. Sounds like a classic ring full vs ring empty wrap around
bug.

As a workaround, use the existing watchdog timer to check for
ring full lockup.

Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: Jeff Garzik <jeff@garzik.org>

drivers/net/sky2.c
drivers/net/sky2.h

index d0e5875..2831f44 100644 (file)
@@ -1648,9 +1648,6 @@ static int sky2_down(struct net_device *dev)
        if (netif_msg_ifdown(sky2))
                printk(KERN_INFO PFX "%s: disabling interface\n", dev->name);
 
-       if (netif_carrier_ok(dev) && --hw->active == 0)
-               del_timer(&hw->watchdog_timer);
-
        /* Stop more packets from being queued */
        netif_stop_queue(dev);
 
@@ -1775,9 +1772,7 @@ static void sky2_link_up(struct sky2_port *sky2)
 
        netif_carrier_on(sky2->netdev);
 
-       if (hw->active++ == 0)
-               mod_timer(&hw->watchdog_timer, jiffies + 1);
-
+       mod_timer(&hw->watchdog_timer, jiffies + 1);
 
        /* Turn on link LED */
        sky2_write8(hw, SK_REG(port, LNK_LED_REG),
@@ -1828,11 +1823,6 @@ static void sky2_link_down(struct sky2_port *sky2)
 
        netif_carrier_off(sky2->netdev);
 
-       /* Stop watchdog if both ports are not active */
-       if (--hw->active == 0)
-               del_timer(&hw->watchdog_timer);
-
-
        /* Turn on link LED */
        sky2_write8(hw, SK_REG(port, LNK_LED_REG), LINKLED_OFF);
 
@@ -2484,20 +2474,72 @@ static void sky2_le_error(struct sky2_hw *hw, unsigned port,
        sky2_write32(hw, Q_ADDR(q, Q_CSR), BMU_CLR_IRQ_CHK);
 }
 
-/* Check for lost IRQ once a second */
+static int sky2_rx_hung(struct net_device *dev)
+{
+       struct sky2_port *sky2 = netdev_priv(dev);
+       struct sky2_hw *hw = sky2->hw;
+       unsigned port = sky2->port;
+       unsigned rxq = rxqaddr[port];
+       u32 mac_rp = sky2_read32(hw, SK_REG(port, RX_GMF_RP));
+       u8 mac_lev = sky2_read8(hw, SK_REG(port, RX_GMF_RLEV));
+       u8 fifo_rp = sky2_read8(hw, Q_ADDR(rxq, Q_RP));
+       u8 fifo_lev = sky2_read8(hw, Q_ADDR(rxq, Q_RL));
+
+       /* If idle and MAC or PCI is stuck */
+       if (sky2->check.last == dev->last_rx &&
+           ((mac_rp == sky2->check.mac_rp &&
+             mac_lev != 0 && mac_lev >= sky2->check.mac_lev) ||
+            /* Check if the PCI RX hang */
+            (fifo_rp == sky2->check.fifo_rp &&
+             fifo_lev != 0 && fifo_lev >= sky2->check.fifo_lev))) {
+               printk(KERN_DEBUG PFX "%s: hung mac %d:%d fifo %d (%d:%d)\n",
+                      dev->name, mac_lev, mac_rp, fifo_lev, fifo_rp,
+                      sky2_read8(hw, Q_ADDR(rxq, Q_WP)));
+               return 1;
+       } else {
+               sky2->check.last = dev->last_rx;
+               sky2->check.mac_rp = mac_rp;
+               sky2->check.mac_lev = mac_lev;
+               sky2->check.fifo_rp = fifo_rp;
+               sky2->check.fifo_lev = fifo_lev;
+               return 0;
+       }
+}
+
 static void sky2_watchdog(unsigned long arg)
 {
        struct sky2_hw *hw = (struct sky2_hw *) arg;
+       struct net_device *dev;
 
+       /* Check for lost IRQ once a second */
        if (sky2_read32(hw, B0_ISRC)) {
-               struct net_device *dev = hw->dev[0];
-
+               dev = hw->dev[0];
                if (__netif_rx_schedule_prep(dev))
                        __netif_rx_schedule(dev);
+       } else {
+               int i, active = 0;
+
+               for (i = 0; i < hw->ports; i++) {
+                       dev = hw->dev[i];
+                       if (!netif_running(dev))
+                               continue;
+                       ++active;
+
+                       /* For chips with Rx FIFO, check if stuck */
+                       if ((hw->flags & SKY2_HW_RAMBUFFER) &&
+                            sky2_rx_hung(dev)) {
+                               pr_info(PFX "%s: receiver hang detected\n",
+                                       dev->name);
+                               schedule_work(&hw->restart_work);
+                               return;
+                       }
+               }
+
+               if (active == 0)
+                       return;
        }
 
-       if (hw->active > 0)
-               mod_timer(&hw->watchdog_timer, round_jiffies(jiffies + HZ));
+       mod_timer(&hw->watchdog_timer, round_jiffies(jiffies + HZ));
 }
 
 /* Hardware/software error handling */
index a05b30b..69cd984 100644 (file)
@@ -2027,6 +2027,14 @@ struct sky2_port {
        u16                  rx_tag;
        struct vlan_group    *vlgrp;
 #endif
+       struct {
+               unsigned long last;
+               u32     mac_rp;
+               u8      mac_lev;
+               u8      fifo_rp;
+               u8      fifo_lev;
+       } check;
+
 
        dma_addr_t           rx_le_map;
        dma_addr_t           tx_le_map;
@@ -2064,7 +2072,6 @@ struct sky2_hw {
        u8                   chip_rev;
        u8                   pmd_type;
        u8                   ports;
-       u8                   active;
 
        struct sky2_status_le *st_le;
        u32                  st_idx;