diff options
author | Claudiu Manoil <claudiu.manoil@freescale.com> | 2014-02-24 14:13:45 +0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-02-25 04:38:20 +0400 |
commit | 0851133bb5ad9d95fceccac9fc67b798041b73e2 (patch) | |
tree | d595058108186c8731b361f53bad30c0f75fb5ee /drivers/net/ethernet/freescale/gianfar_ethtool.c | |
parent | 80ec396cb6b522b23c69dfff32a2d12993e4bb30 (diff) | |
download | linux-0851133bb5ad9d95fceccac9fc67b798041b73e2.tar.xz |
gianfar: Fix device reset races (oops) for Tx
The device reset procedure, stop_gfar()/startup_gfar(), has
concurrency issues.
"Kernel access of bad area" oopses show up during Tx timeout
device reset or other reset cases (like changing MTU) that
happen while the interface still has traffic. The oopses
happen in start_xmit and clean_tx_ring when accessing tx_queue->
tx_skbuff which is NULL. The race comes from de-allocating the
tx_skbuff while transmission and napi processing are still
active. Though the Tx queues get temoprarily stopped when Tx
timeout occurs, they get re-enabled as a result of Tx congestion
handling inside the napi context (see clean_tx_ring()). Not
disabling the napi during reset is also a bug, because
clean_tx_ring() will try to access tx_skbuff while it is being
de-alloc'ed and re-alloc'ed.
To fix this, stop_gfar() needs to disable napi processing
after stopping the Tx queues. However, in order to prevent
clean_tx_ring() to re-enable the Tx queue before the napi
gets disabled, the device state DOWN has been introduced.
It prevents the Tx congestion management from re-enabling the
de-congested Tx queue while the device is brought down.
An additional locking state, RESETTING, has been introduced
to prevent simultaneous resets or to prevent configuring the
device while it is resetting.
The bogus 'rxlock's (for each Rx queue) have been removed since
their purpose is not justified, as they don't prevent nor are
suited to prevent device reset/reconfig races (such as this one).
Signed-off-by: Claudiu Manoil <claudiu.manoil@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet/freescale/gianfar_ethtool.c')
-rw-r--r-- | drivers/net/ethernet/freescale/gianfar_ethtool.c | 28 |
1 files changed, 19 insertions, 9 deletions
diff --git a/drivers/net/ethernet/freescale/gianfar_ethtool.c b/drivers/net/ethernet/freescale/gianfar_ethtool.c index dd7ccec506f1..45219d4d09b4 100644 --- a/drivers/net/ethernet/freescale/gianfar_ethtool.c +++ b/drivers/net/ethernet/freescale/gianfar_ethtool.c @@ -487,6 +487,9 @@ static int gfar_sringparam(struct net_device *dev, return -EINVAL; } + while (test_and_set_bit_lock(GFAR_RESETTING, &priv->state)) + cpu_relax(); + if (dev->flags & IFF_UP) stop_gfar(dev); @@ -498,10 +501,11 @@ static int gfar_sringparam(struct net_device *dev, priv->tx_queue[i]->tx_ring_size = rvals->tx_pending; /* Rebuild the rings with the new size */ - if (dev->flags & IFF_UP) { + if (dev->flags & IFF_UP) err = startup_gfar(dev); - netif_tx_wake_all_queues(dev); - } + + clear_bit_unlock(GFAR_RESETTING, &priv->state); + return err; } @@ -580,20 +584,28 @@ static int gfar_spauseparam(struct net_device *dev, int gfar_set_features(struct net_device *dev, netdev_features_t features) { netdev_features_t changed = dev->features ^ features; + struct gfar_private *priv = netdev_priv(dev); int err = 0; if (!(changed & (NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_RXCSUM))) return 0; + while (test_and_set_bit_lock(GFAR_RESETTING, &priv->state)) + cpu_relax(); + dev->features = features; if (dev->flags & IFF_UP) { /* Now we take down the rings to rebuild them */ stop_gfar(dev); err = startup_gfar(dev); - netif_tx_wake_all_queues(dev); + } else { + gfar_mac_reset(priv); } + + clear_bit_unlock(GFAR_RESETTING, &priv->state); + return err; } @@ -1559,9 +1571,6 @@ static int gfar_write_filer_table(struct gfar_private *priv, if (tab->index > MAX_FILER_IDX - 1) return -EBUSY; - /* Avoid inconsistent filer table to be processed */ - lock_rx_qs(priv); - /* Fill regular entries */ for (; i < MAX_FILER_IDX - 1 && (tab->fe[i].ctrl | tab->fe[i].ctrl); i++) @@ -1574,8 +1583,6 @@ static int gfar_write_filer_table(struct gfar_private *priv, */ gfar_write_filer(priv, i, 0x20, 0x0); - unlock_rx_qs(priv); - return 0; } @@ -1780,6 +1787,9 @@ static int gfar_set_nfc(struct net_device *dev, struct ethtool_rxnfc *cmd) struct gfar_private *priv = netdev_priv(dev); int ret = 0; + if (test_bit(GFAR_RESETTING, &priv->state)) + return -EBUSY; + mutex_lock(&priv->rx_queue_access); switch (cmd->cmd) { |