From 75e806838a3327d4ca9030e588d34de11b04f341 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 19 Sep 2007 15:36:46 -0700 Subject: sky2: receive FIFO checking 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 Signed-off-by: Jeff Garzik --- drivers/net/sky2.c | 74 ++++++++++++++++++++++++++++++++++++++++++------------ drivers/net/sky2.h | 9 ++++++- 2 files changed, 66 insertions(+), 17 deletions(-) (limited to 'drivers/net') diff --git a/drivers/net/sky2.c b/drivers/net/sky2.c index d0e5875..2831f44 100644 --- a/drivers/net/sky2.c +++ b/drivers/net/sky2.c @@ -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 */ diff --git a/drivers/net/sky2.h b/drivers/net/sky2.h index a05b30b..69cd984 100644 --- a/drivers/net/sky2.h +++ b/drivers/net/sky2.h @@ -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; -- cgit v1.1