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 a19f68f5862d7e8632b770eb9faaae430ebeb150..415694d37989cc69c950707e7acae4e27b1331d9 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 f872a7db2ca8b6ad6701158ec25a6b953936f731..3c9d484dbd4ea92044bad498ff045296a1323eff 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;