From: Edward Cree <ec...@solarflare.com>

efx_ef10_try_update_nic_stats_vf() used in_interrupt() to figure out
whether it is safe to sleep (for MCDI) or not.

The only caller from which it was not is efx_net_stats(), which can be
invoked under dev_base_lock from net-sysfs::netstat_show().

So add a new update_stats_atomic() method to struct efx_nic_type, and call
it from efx_net_stats(), removing the need for
efx_ef10_try_update_nic_stats_vf() to behave differently for this case
(which it wasn't doing correctly anyway).

For all nic_types other than EF10 VF, this method is NULL so the the
regular update_stats() methods are invoked , which are happy with being
called from atomic contexts.

Fixes: f00bf2305cab ("sfc: don't update stats on VF when called in atomic 
context")
Reported-by: Sebastian Andrzej Siewior <bige...@linutronix.de>
Signed-off-by: Edward Cree <ec...@solarflare.com>
Signed-off-by: Thomas Gleixner <t...@linutronix.de>
Reviewed-by: Martin Habets <mhab...@solarflare.com>

---
Only compile-tested so far, because I'm waiting for my kernel to
 finish rebuilding with CONFIG_DEBUG_ATOMIC_SLEEP which I'm hoping
 is the right thing to detect the bug in the existing code.
I also wasn't quite sure how to give credit to the thorough analysis
 in the commit message of Sebastian's patch.  I don't think we have
 a Whatever-by: tag to cover that, do we?
And this doesn't include your GFP_KERNEL change, which should
 probably go in separately if you take this.

 drivers/net/ethernet/sfc/ef10.c       |   22 +++++++++++++---------
 drivers/net/ethernet/sfc/efx_common.c |    2 +-
 drivers/net/ethernet/sfc/net_driver.h |    5 +++++
 drivers/net/ethernet/sfc/nic_common.h |    7 +++++++
 4 files changed, 26 insertions(+), 10 deletions(-)

--- a/drivers/net/ethernet/sfc/ef10.c
+++ b/drivers/net/ethernet/sfc/ef10.c
@@ -1871,15 +1871,6 @@ static int efx_ef10_try_update_nic_stats
 
        spin_unlock_bh(&efx->stats_lock);
 
-       if (in_interrupt()) {
-               /* If in atomic context, cannot update stats.  Just update the
-                * software stats and return so the caller can continue.
-                */
-               spin_lock_bh(&efx->stats_lock);
-               efx_update_sw_stats(efx, stats);
-               return 0;
-       }
-
        efx_ef10_get_stat_mask(efx, mask);
 
        rc = efx_nic_alloc_buffer(efx, &stats_buf, dma_len, GFP_ATOMIC);
@@ -1938,6 +1929,18 @@ static size_t efx_ef10_update_stats_vf(s
        return efx_ef10_update_stats_common(efx, full_stats, core_stats);
 }
 
+static size_t efx_ef10_update_stats_atomic_vf(struct efx_nic *efx, u64 
*full_stats,
+                                             struct rtnl_link_stats64 
*core_stats)
+{
+       struct efx_ef10_nic_data *nic_data = efx->nic_data;
+
+       /* In atomic context, cannot update HW stats.  Just update the
+        * software stats and return so the caller can continue.
+        */
+       efx_update_sw_stats(efx, nic_data->stats);
+       return efx_ef10_update_stats_common(efx, full_stats, core_stats);
+}
+
 static void efx_ef10_push_irq_moderation(struct efx_channel *channel)
 {
        struct efx_nic *efx = channel->efx;
@@ -3998,6 +4001,7 @@ const struct efx_nic_type efx_hunt_a0_vf
        .finish_flr = efx_port_dummy_op_void,
        .describe_stats = efx_ef10_describe_stats,
        .update_stats = efx_ef10_update_stats_vf,
+       .update_stats_atomic = efx_ef10_update_stats_atomic_vf,
        .start_stats = efx_port_dummy_op_void,
        .pull_stats = efx_port_dummy_op_void,
        .stop_stats = efx_port_dummy_op_void,
--- a/drivers/net/ethernet/sfc/efx_common.c
+++ b/drivers/net/ethernet/sfc/efx_common.c
@@ -602,7 +602,7 @@ void efx_net_stats(struct net_device *ne
        struct efx_nic *efx = netdev_priv(net_dev);
 
        spin_lock_bh(&efx->stats_lock);
-       efx->type->update_stats(efx, NULL, stats);
+       efx_nic_update_stats_atomic(efx, NULL, stats);
        spin_unlock_bh(&efx->stats_lock);
 }
 
--- a/drivers/net/ethernet/sfc/net_driver.h
+++ b/drivers/net/ethernet/sfc/net_driver.h
@@ -1172,6 +1172,9 @@ struct efx_udp_tunnel {
  * @describe_stats: Describe statistics for ethtool
  * @update_stats: Update statistics not provided by event handling.
  *     Either argument may be %NULL.
+ * @update_stats_atomic: Update statistics while in atomic context, if that
+ *     is more limiting than @update_stats.  Otherwise, leave %NULL and
+ *     driver core will call @update_stats.
  * @start_stats: Start the regular fetching of statistics
  * @pull_stats: Pull stats from the NIC and wait until they arrive.
  * @stop_stats: Stop the regular fetching of statistics
@@ -1316,6 +1319,8 @@ struct efx_nic_type {
        size_t (*describe_stats)(struct efx_nic *efx, u8 *names);
        size_t (*update_stats)(struct efx_nic *efx, u64 *full_stats,
                               struct rtnl_link_stats64 *core_stats);
+       size_t (*update_stats_atomic)(struct efx_nic *efx, u64 *full_stats,
+                                     struct rtnl_link_stats64 *core_stats);
        void (*start_stats)(struct efx_nic *efx);
        void (*pull_stats)(struct efx_nic *efx);
        void (*stop_stats)(struct efx_nic *efx);
--- a/drivers/net/ethernet/sfc/nic_common.h
+++ b/drivers/net/ethernet/sfc/nic_common.h
@@ -244,6 +244,13 @@ void efx_nic_update_stats(const struct e
                          const unsigned long *mask, u64 *stats,
                          const void *dma_buf, bool accumulate);
 void efx_nic_fix_nodesc_drop_stat(struct efx_nic *efx, u64 *stat);
+static inline size_t efx_nic_update_stats_atomic(struct efx_nic *efx, u64 
*full_stats,
+                                                struct rtnl_link_stats64 
*core_stats)
+{
+       if (efx->type->update_stats_atomic)
+               return efx->type->update_stats_atomic(efx, full_stats, 
core_stats);
+       return efx->type->update_stats(efx, full_stats, core_stats);
+}
 
 #define EFX_MAX_FLUSH_TIME 5000
 

Reply via email to