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.h | |
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.h')
-rw-r--r-- | drivers/net/ethernet/freescale/gianfar.h | 16 |
1 files changed, 9 insertions, 7 deletions
diff --git a/drivers/net/ethernet/freescale/gianfar.h b/drivers/net/ethernet/freescale/gianfar.h index 9db95563f8aa..1e16216d4150 100644 --- a/drivers/net/ethernet/freescale/gianfar.h +++ b/drivers/net/ethernet/freescale/gianfar.h @@ -965,7 +965,6 @@ struct rx_q_stats { /** * struct gfar_priv_rx_q - per rx queue structure - * @rxlock: per queue rx spin lock * @rx_skbuff: skb pointers * @skb_currx: currently use skb pointer * @rx_bd_base: First rx buffer descriptor @@ -978,8 +977,7 @@ struct rx_q_stats { */ struct gfar_priv_rx_q { - spinlock_t rxlock __attribute__ ((aligned (SMP_CACHE_BYTES))); - struct sk_buff ** rx_skbuff; + struct sk_buff **rx_skbuff __aligned(SMP_CACHE_BYTES); dma_addr_t rx_bd_dma_base; struct rxbd8 *rx_bd_base; struct rxbd8 *cur_rx; @@ -1040,6 +1038,11 @@ enum gfar_errata { GFAR_ERRATA_12 = 0x08, /* a.k.a errata eTSEC49 */ }; +enum gfar_dev_state { + GFAR_DOWN = 1, + GFAR_RESETTING +}; + /* Struct stolen almost completely (and shamelessly) from the FCC enet source * (Ok, that's not so true anymore, but there is a family resemblance) * The GFAR buffer descriptors track the ring buffers. The rx_bd_base @@ -1068,6 +1071,7 @@ struct gfar_private { struct gfar_priv_rx_q *rx_queue[MAX_RX_QS]; struct gfar_priv_grp gfargrp[MAXGROUPS]; + unsigned long state; u32 device_flags; unsigned int mode; @@ -1198,13 +1202,11 @@ static inline void gfar_write_isrg(struct gfar_private *priv) } } -void lock_rx_qs(struct gfar_private *priv); -void lock_tx_qs(struct gfar_private *priv); -void unlock_rx_qs(struct gfar_private *priv); -void unlock_tx_qs(struct gfar_private *priv); irqreturn_t gfar_receive(int irq, void *dev_id); int startup_gfar(struct net_device *dev); void stop_gfar(struct net_device *dev); +void reset_gfar(struct net_device *dev); +void gfar_mac_reset(struct gfar_private *priv); void gfar_halt(struct gfar_private *priv); void gfar_start(struct gfar_private *priv); void gfar_phy_test(struct mii_bus *bus, struct phy_device *phydev, int enable, |