bnxt_en: Fix race conditions in .ndo_get_stats64().
authorMichael Chan <michael.chan@broadcom.com>
Tue, 11 Jul 2017 17:05:34 +0000 (13:05 -0400)
committerDavid S. Miller <davem@davemloft.net>
Tue, 11 Jul 2017 17:32:11 +0000 (10:32 -0700)
.ndo_get_stats64() may not be protected by RTNL and can race with
.ndo_stop() or other ethtool operations that can free the statistics
memory.  Fix it by setting a new flag BNXT_STATE_READ_STATS and then
proceeding to read statistics memory only if the state is OPEN.  The
close path that frees the memory clears the OPEN state and then waits
for the BNXT_STATE_READ_STATS to clear before proceeding to free the
statistics memory.

Fixes: c0c050c58d84 ("bnxt_en: New Broadcom ethernet driver.")
Signed-off-by: Michael Chan <michael.chan@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/broadcom/bnxt/bnxt.c
drivers/net/ethernet/broadcom/bnxt/bnxt.h

index a19f68f..415694d 100644 (file)
@@ -6279,6 +6279,12 @@ static int bnxt_open(struct net_device *dev)
        return __bnxt_open_nic(bp, true, true);
 }
 
+static bool bnxt_drv_busy(struct bnxt *bp)
+{
+       return (test_bit(BNXT_STATE_IN_SP_TASK, &bp->state) ||
+               test_bit(BNXT_STATE_READ_STATS, &bp->state));
+}
+
 int bnxt_close_nic(struct bnxt *bp, bool irq_re_init, bool link_re_init)
 {
        int rc = 0;
@@ -6297,7 +6303,7 @@ int bnxt_close_nic(struct bnxt *bp, bool irq_re_init, bool link_re_init)
 
        clear_bit(BNXT_STATE_OPEN, &bp->state);
        smp_mb__after_atomic();
-       while (test_bit(BNXT_STATE_IN_SP_TASK, &bp->state))
+       while (bnxt_drv_busy(bp))
                msleep(20);
 
        /* Flush rings and and disable interrupts */
@@ -6358,8 +6364,15 @@ bnxt_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
        u32 i;
        struct bnxt *bp = netdev_priv(dev);
 
-       if (!bp->bnapi)
+       set_bit(BNXT_STATE_READ_STATS, &bp->state);
+       /* Make sure bnxt_close_nic() sees that we are reading stats before
+        * we check the BNXT_STATE_OPEN flag.
+        */
+       smp_mb__after_atomic();
+       if (!test_bit(BNXT_STATE_OPEN, &bp->state)) {
+               clear_bit(BNXT_STATE_READ_STATS, &bp->state);
                return;
+       }
 
        /* TODO check if we need to synchronize with bnxt_close path */
        for (i = 0; i < bp->cp_nr_rings; i++) {
@@ -6406,6 +6419,7 @@ bnxt_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
                stats->tx_fifo_errors = le64_to_cpu(tx->tx_fifo_underruns);
                stats->tx_errors = le64_to_cpu(tx->tx_err);
        }
+       clear_bit(BNXT_STATE_READ_STATS, &bp->state);
 }
 
 static bool bnxt_mc_list_updated(struct bnxt *bp, u32 *rx_mask)
index f872a7d..3c9d484 100644 (file)
@@ -1117,6 +1117,7 @@ struct bnxt {
        unsigned long           state;
 #define BNXT_STATE_OPEN                0
 #define BNXT_STATE_IN_SP_TASK  1
+#define BNXT_STATE_READ_STATS  2
 
        struct bnxt_irq *irq_tbl;
        int                     total_irqs;