diff options
author | Rob Herring <rob.herring@calxeda.com> | 2013-08-30 17:49:21 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-09-03 22:21:15 -0400 |
commit | 8746f671ef04114ab25f5a35ec6219efbdf3703e (patch) | |
tree | 1abd7ba59fd2cb956e99afee1f199e4fac92ecac /drivers/net | |
parent | ef07387faf33a95e011993200902d490b605407d (diff) |
net: calxedaxgmac: fix race between xgmac_tx_complete and xgmac_tx_err
It is possible for the xgmac_tx_complete to run concurrently with
xgmac_tx_err since there are no locks. Fix this by moving the tx
error handling to a workqueue so we can disable napi while we reset
the transmitter.
Reported-by: Andreas Herrmann <andreas.herrmann@calxeda.com>
Signed-off-by: Rob Herring <rob.herring@calxeda.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/ethernet/calxeda/xgmac.c | 38 |
1 files changed, 18 insertions, 20 deletions
diff --git a/drivers/net/ethernet/calxeda/xgmac.c b/drivers/net/ethernet/calxeda/xgmac.c index d41af68ccb63..df7e3e2579f1 100644 --- a/drivers/net/ethernet/calxeda/xgmac.c +++ b/drivers/net/ethernet/calxeda/xgmac.c | |||
@@ -393,6 +393,7 @@ struct xgmac_priv { | |||
393 | char rx_pause; | 393 | char rx_pause; |
394 | char tx_pause; | 394 | char tx_pause; |
395 | int wolopts; | 395 | int wolopts; |
396 | struct work_struct tx_timeout_work; | ||
396 | }; | 397 | }; |
397 | 398 | ||
398 | /* XGMAC Configuration Settings */ | 399 | /* XGMAC Configuration Settings */ |
@@ -897,21 +898,18 @@ static void xgmac_tx_complete(struct xgmac_priv *priv) | |||
897 | netif_wake_queue(priv->dev); | 898 | netif_wake_queue(priv->dev); |
898 | } | 899 | } |
899 | 900 | ||
900 | /** | 901 | static void xgmac_tx_timeout_work(struct work_struct *work) |
901 | * xgmac_tx_err: | ||
902 | * @priv: pointer to the private device structure | ||
903 | * Description: it cleans the descriptors and restarts the transmission | ||
904 | * in case of errors. | ||
905 | */ | ||
906 | static void xgmac_tx_err(struct xgmac_priv *priv) | ||
907 | { | 902 | { |
908 | u32 reg, value, inten; | 903 | u32 reg, value; |
904 | struct xgmac_priv *priv = | ||
905 | container_of(work, struct xgmac_priv, tx_timeout_work); | ||
909 | 906 | ||
910 | netif_stop_queue(priv->dev); | 907 | napi_disable(&priv->napi); |
911 | 908 | ||
912 | inten = readl(priv->base + XGMAC_DMA_INTR_ENA); | ||
913 | writel(0, priv->base + XGMAC_DMA_INTR_ENA); | 909 | writel(0, priv->base + XGMAC_DMA_INTR_ENA); |
914 | 910 | ||
911 | netif_tx_lock(priv->dev); | ||
912 | |||
915 | reg = readl(priv->base + XGMAC_DMA_CONTROL); | 913 | reg = readl(priv->base + XGMAC_DMA_CONTROL); |
916 | writel(reg & ~DMA_CONTROL_ST, priv->base + XGMAC_DMA_CONTROL); | 914 | writel(reg & ~DMA_CONTROL_ST, priv->base + XGMAC_DMA_CONTROL); |
917 | do { | 915 | do { |
@@ -927,9 +925,15 @@ static void xgmac_tx_err(struct xgmac_priv *priv) | |||
927 | 925 | ||
928 | writel(DMA_STATUS_TU | DMA_STATUS_TPS | DMA_STATUS_NIS | DMA_STATUS_AIS, | 926 | writel(DMA_STATUS_TU | DMA_STATUS_TPS | DMA_STATUS_NIS | DMA_STATUS_AIS, |
929 | priv->base + XGMAC_DMA_STATUS); | 927 | priv->base + XGMAC_DMA_STATUS); |
930 | writel(inten, priv->base + XGMAC_DMA_INTR_ENA); | ||
931 | 928 | ||
929 | netif_tx_unlock(priv->dev); | ||
932 | netif_wake_queue(priv->dev); | 930 | netif_wake_queue(priv->dev); |
931 | |||
932 | napi_enable(&priv->napi); | ||
933 | |||
934 | /* Enable interrupts */ | ||
935 | writel(DMA_INTR_DEFAULT_MASK, priv->base + XGMAC_DMA_STATUS); | ||
936 | writel(DMA_INTR_DEFAULT_MASK, priv->base + XGMAC_DMA_INTR_ENA); | ||
933 | } | 937 | } |
934 | 938 | ||
935 | static int xgmac_hw_init(struct net_device *dev) | 939 | static int xgmac_hw_init(struct net_device *dev) |
@@ -1225,9 +1229,7 @@ static int xgmac_poll(struct napi_struct *napi, int budget) | |||
1225 | static void xgmac_tx_timeout(struct net_device *dev) | 1229 | static void xgmac_tx_timeout(struct net_device *dev) |
1226 | { | 1230 | { |
1227 | struct xgmac_priv *priv = netdev_priv(dev); | 1231 | struct xgmac_priv *priv = netdev_priv(dev); |
1228 | 1232 | schedule_work(&priv->tx_timeout_work); | |
1229 | /* Clear Tx resources and restart transmitting again */ | ||
1230 | xgmac_tx_err(priv); | ||
1231 | } | 1233 | } |
1232 | 1234 | ||
1233 | /** | 1235 | /** |
@@ -1366,7 +1368,6 @@ static irqreturn_t xgmac_pmt_interrupt(int irq, void *dev_id) | |||
1366 | static irqreturn_t xgmac_interrupt(int irq, void *dev_id) | 1368 | static irqreturn_t xgmac_interrupt(int irq, void *dev_id) |
1367 | { | 1369 | { |
1368 | u32 intr_status; | 1370 | u32 intr_status; |
1369 | bool tx_err = false; | ||
1370 | struct net_device *dev = (struct net_device *)dev_id; | 1371 | struct net_device *dev = (struct net_device *)dev_id; |
1371 | struct xgmac_priv *priv = netdev_priv(dev); | 1372 | struct xgmac_priv *priv = netdev_priv(dev); |
1372 | struct xgmac_extra_stats *x = &priv->xstats; | 1373 | struct xgmac_extra_stats *x = &priv->xstats; |
@@ -1396,16 +1397,12 @@ static irqreturn_t xgmac_interrupt(int irq, void *dev_id) | |||
1396 | if (intr_status & DMA_STATUS_TPS) { | 1397 | if (intr_status & DMA_STATUS_TPS) { |
1397 | netdev_err(priv->dev, "transmit process stopped\n"); | 1398 | netdev_err(priv->dev, "transmit process stopped\n"); |
1398 | x->tx_process_stopped++; | 1399 | x->tx_process_stopped++; |
1399 | tx_err = true; | 1400 | schedule_work(&priv->tx_timeout_work); |
1400 | } | 1401 | } |
1401 | if (intr_status & DMA_STATUS_FBI) { | 1402 | if (intr_status & DMA_STATUS_FBI) { |
1402 | netdev_err(priv->dev, "fatal bus error\n"); | 1403 | netdev_err(priv->dev, "fatal bus error\n"); |
1403 | x->fatal_bus_error++; | 1404 | x->fatal_bus_error++; |
1404 | tx_err = true; | ||
1405 | } | 1405 | } |
1406 | |||
1407 | if (tx_err) | ||
1408 | xgmac_tx_err(priv); | ||
1409 | } | 1406 | } |
1410 | 1407 | ||
1411 | /* TX/RX NORMAL interrupts */ | 1408 | /* TX/RX NORMAL interrupts */ |
@@ -1708,6 +1705,7 @@ static int xgmac_probe(struct platform_device *pdev) | |||
1708 | ndev->netdev_ops = &xgmac_netdev_ops; | 1705 | ndev->netdev_ops = &xgmac_netdev_ops; |
1709 | SET_ETHTOOL_OPS(ndev, &xgmac_ethtool_ops); | 1706 | SET_ETHTOOL_OPS(ndev, &xgmac_ethtool_ops); |
1710 | spin_lock_init(&priv->stats_lock); | 1707 | spin_lock_init(&priv->stats_lock); |
1708 | INIT_WORK(&priv->tx_timeout_work, xgmac_tx_timeout_work); | ||
1711 | 1709 | ||
1712 | priv->device = &pdev->dev; | 1710 | priv->device = &pdev->dev; |
1713 | priv->dev = ndev; | 1711 | priv->dev = ndev; |