gve: Add Gvnic stats AQ command and ethtool show/set-priv-flags.
authorKuo Zhao <kuozhao@google.com>
Fri, 11 Sep 2020 17:38:47 +0000 (10:38 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 11 Sep 2020 21:31:54 +0000 (14:31 -0700)
This adds functionality to report driver stats to Hypervisor.
(Users may want to turn this feature off as a matter of privacy
so a "report-stats" flag is added as an ethtool priv option.
It is also disabled by default.)
The hypervisor would trigger a stats report in case "too many"
packets dropped; the stats would be useful in debugging stuck
queues.
A "stats_report_trigger_cnt" stat is added to count the number of times
the hypervisor attempts to trigger stats report.

A timer is also added so that when report-stats is enabled, stat are
updated once every 20 seconds.

Reviewed-by: Yangchun Fu <yangchun@google.com>
Signed-off-by: Kuo Zhao <kuozhao@google.com>
Signed-off-by: David Awogbemila <awogbemila@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/google/gve/gve.h
drivers/net/ethernet/google/gve/gve_adminq.c
drivers/net/ethernet/google/gve/gve_adminq.h
drivers/net/ethernet/google/gve/gve_ethtool.c
drivers/net/ethernet/google/gve/gve_main.c
drivers/net/ethernet/google/gve/gve_register.h

index 55b34b4..00697d5 100644 (file)
 /* 1 for management, 1 for rx, 1 for tx */
 #define GVE_MIN_MSIX 3
 
+/* Numbers of gve tx/rx stats in stats report. */
+#define GVE_TX_STATS_REPORT_NUM        5
+#define GVE_RX_STATS_REPORT_NUM        2
+
+/* Interval to schedule a stats report update, 20000ms. */
+#define GVE_STATS_REPORT_TIMER_PERIOD  20000
+
 /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
 struct gve_rx_desc_queue {
        struct gve_rx_desc *desc_ring; /* the descriptor ring */
@@ -220,6 +227,7 @@ struct gve_priv {
        u32 adminq_destroy_rx_queue_cnt;
        u32 adminq_dcfg_device_resources_cnt;
        u32 adminq_set_driver_parameter_cnt;
+       u32 adminq_report_stats_cnt;
 
        /* Global stats */
        u32 interface_up_cnt; /* count of times interface turned up since last reset */
@@ -227,24 +235,39 @@ struct gve_priv {
        u32 reset_cnt; /* count of reset */
        u32 page_alloc_fail; /* count of page alloc fails */
        u32 dma_mapping_error; /* count of dma mapping errors */
-
+       u32 stats_report_trigger_cnt; /* count of device-requested stats-reports since last reset */
        struct workqueue_struct *gve_wq;
        struct work_struct service_task;
+       struct work_struct stats_report_task;
        unsigned long service_task_flags;
        unsigned long state_flags;
+
+       struct gve_stats_report *stats_report;
+       u64 stats_report_len;
+       dma_addr_t stats_report_bus; /* dma address for the stats report */
+       unsigned long ethtool_flags;
+
+       unsigned long stats_report_timer_period;
+       struct timer_list stats_report_timer;
+
 };
 
-enum gve_service_task_flags {
-       GVE_PRIV_FLAGS_DO_RESET                 = BIT(1),
-       GVE_PRIV_FLAGS_RESET_IN_PROGRESS        = BIT(2),
-       GVE_PRIV_FLAGS_PROBE_IN_PROGRESS        = BIT(3),
+enum gve_service_task_flags_bit {
+       GVE_PRIV_FLAGS_DO_RESET                 = 1,
+       GVE_PRIV_FLAGS_RESET_IN_PROGRESS        = 2,
+       GVE_PRIV_FLAGS_PROBE_IN_PROGRESS        = 3,
+       GVE_PRIV_FLAGS_DO_REPORT_STATS = 4,
 };
 
-enum gve_state_flags {
-       GVE_PRIV_FLAGS_ADMIN_QUEUE_OK           = BIT(1),
-       GVE_PRIV_FLAGS_DEVICE_RESOURCES_OK      = BIT(2),
-       GVE_PRIV_FLAGS_DEVICE_RINGS_OK          = BIT(3),
-       GVE_PRIV_FLAGS_NAPI_ENABLED             = BIT(4),
+enum gve_state_flags_bit {
+       GVE_PRIV_FLAGS_ADMIN_QUEUE_OK           = 1,
+       GVE_PRIV_FLAGS_DEVICE_RESOURCES_OK      = 2,
+       GVE_PRIV_FLAGS_DEVICE_RINGS_OK          = 3,
+       GVE_PRIV_FLAGS_NAPI_ENABLED             = 4,
+};
+
+enum gve_ethtool_flags_bit {
+       GVE_PRIV_FLAGS_REPORT_STATS             = 0,
 };
 
 static inline bool gve_get_do_reset(struct gve_priv *priv)
@@ -294,6 +317,22 @@ static inline void gve_clear_probe_in_progress(struct gve_priv *priv)
        clear_bit(GVE_PRIV_FLAGS_PROBE_IN_PROGRESS, &priv->service_task_flags);
 }
 
+static inline bool gve_get_do_report_stats(struct gve_priv *priv)
+{
+       return test_bit(GVE_PRIV_FLAGS_DO_REPORT_STATS,
+                       &priv->service_task_flags);
+}
+
+static inline void gve_set_do_report_stats(struct gve_priv *priv)
+{
+       set_bit(GVE_PRIV_FLAGS_DO_REPORT_STATS, &priv->service_task_flags);
+}
+
+static inline void gve_clear_do_report_stats(struct gve_priv *priv)
+{
+       clear_bit(GVE_PRIV_FLAGS_DO_REPORT_STATS, &priv->service_task_flags);
+}
+
 static inline bool gve_get_admin_queue_ok(struct gve_priv *priv)
 {
        return test_bit(GVE_PRIV_FLAGS_ADMIN_QUEUE_OK, &priv->state_flags);
@@ -354,6 +393,16 @@ static inline void gve_clear_napi_enabled(struct gve_priv *priv)
        clear_bit(GVE_PRIV_FLAGS_NAPI_ENABLED, &priv->state_flags);
 }
 
+static inline bool gve_get_report_stats(struct gve_priv *priv)
+{
+       return test_bit(GVE_PRIV_FLAGS_REPORT_STATS, &priv->ethtool_flags);
+}
+
+static inline void gve_clear_report_stats(struct gve_priv *priv)
+{
+       clear_bit(GVE_PRIV_FLAGS_REPORT_STATS, &priv->ethtool_flags);
+}
+
 /* Returns the address of the ntfy_blocks irq doorbell
  */
 static inline __be32 __iomem *gve_irq_doorbell(struct gve_priv *priv,
@@ -476,6 +525,8 @@ int gve_reset(struct gve_priv *priv, bool attempt_teardown);
 int gve_adjust_queues(struct gve_priv *priv,
                      struct gve_queue_config new_rx_config,
                      struct gve_queue_config new_tx_config);
+/* report stats handling */
+void gve_handle_report_stats(struct gve_priv *priv);
 /* exported by ethtool.c */
 extern const struct ethtool_ops gve_ethtool_ops;
 /* needed by ethtool */
index d9aed21..078e6e4 100644 (file)
@@ -35,6 +35,7 @@ int gve_adminq_alloc(struct device *dev, struct gve_priv *priv)
        priv->adminq_destroy_rx_queue_cnt = 0;
        priv->adminq_dcfg_device_resources_cnt = 0;
        priv->adminq_set_driver_parameter_cnt = 0;
+       priv->adminq_report_stats_cnt = 0;
 
        /* Setup Admin queue with the device */
        iowrite32be(priv->adminq_bus_addr / PAGE_SIZE,
@@ -183,6 +184,9 @@ int gve_adminq_execute_cmd(struct gve_priv *priv,
        case GVE_ADMINQ_SET_DRIVER_PARAMETER:
                priv->adminq_set_driver_parameter_cnt++;
                break;
+       case GVE_ADMINQ_REPORT_STATS:
+               priv->adminq_report_stats_cnt++;
+               break;
        default:
                dev_err(&priv->pdev->dev, "unknown AQ command opcode %d\n", opcode);
        }
@@ -433,3 +437,19 @@ int gve_adminq_set_mtu(struct gve_priv *priv, u64 mtu)
 
        return gve_adminq_execute_cmd(priv, &cmd);
 }
+
+int gve_adminq_report_stats(struct gve_priv *priv, u64 stats_report_len,
+                           dma_addr_t stats_report_addr, u64 interval)
+{
+       union gve_adminq_command cmd;
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.opcode = cpu_to_be32(GVE_ADMINQ_REPORT_STATS);
+       cmd.report_stats = (struct gve_adminq_report_stats) {
+               .stats_report_len = cpu_to_be64(stats_report_len),
+               .stats_report_addr = cpu_to_be64(stats_report_addr),
+               .interval = cpu_to_be64(interval),
+       };
+
+       return gve_adminq_execute_cmd(priv, &cmd);
+}
index 4dfa06e..b81a3bb 100644 (file)
@@ -21,6 +21,7 @@ enum gve_adminq_opcodes {
        GVE_ADMINQ_DESTROY_RX_QUEUE             = 0x8,
        GVE_ADMINQ_DECONFIGURE_DEVICE_RESOURCES = 0x9,
        GVE_ADMINQ_SET_DRIVER_PARAMETER         = 0xB,
+       GVE_ADMINQ_REPORT_STATS                 = 0xC,
 };
 
 /* Admin queue status codes */
@@ -172,6 +173,40 @@ struct gve_adminq_set_driver_parameter {
 
 static_assert(sizeof(struct gve_adminq_set_driver_parameter) == 16);
 
+struct gve_adminq_report_stats {
+       __be64 stats_report_len;
+       __be64 stats_report_addr;
+       __be64 interval;
+};
+
+static_assert(sizeof(struct gve_adminq_report_stats) == 24);
+
+struct stats {
+       __be32 stat_name;
+       __be32 queue_id;
+       __be64 value;
+};
+
+static_assert(sizeof(struct stats) == 16);
+
+struct gve_stats_report {
+       __be64 written_count;
+       struct stats stats[0];
+};
+
+static_assert(sizeof(struct gve_stats_report) == 8);
+
+enum gve_stat_names {
+       // stats from gve
+       TX_WAKE_CNT                     = 1,
+       TX_STOP_CNT                     = 2,
+       TX_FRAMES_SENT                  = 3,
+       TX_BYTES_SENT                   = 4,
+       TX_LAST_COMPLETION_PROCESSED    = 5,
+       RX_NEXT_EXPECTED_SEQUENCE       = 6,
+       RX_BUFFERS_POSTED               = 7,
+};
+
 union gve_adminq_command {
        struct {
                __be32 opcode;
@@ -187,6 +222,7 @@ union gve_adminq_command {
                        struct gve_adminq_register_page_list reg_page_list;
                        struct gve_adminq_unregister_page_list unreg_page_list;
                        struct gve_adminq_set_driver_parameter set_driver_param;
+                       struct gve_adminq_report_stats report_stats;
                };
        };
        u8 reserved[64];
@@ -214,4 +250,6 @@ int gve_adminq_register_page_list(struct gve_priv *priv,
                                  struct gve_queue_page_list *qpl);
 int gve_adminq_unregister_page_list(struct gve_priv *priv, u32 page_list_id);
 int gve_adminq_set_mtu(struct gve_priv *priv, u64 mtu);
+int gve_adminq_report_stats(struct gve_priv *priv, u64 stats_report_len,
+                           dma_addr_t stats_report_addr, u64 interval);
 #endif /* _GVE_ADMINQ_H */
index e9bef1f..67239fa 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <linux/rtnetlink.h>
 #include "gve.h"
+#include "gve_adminq.h"
 
 static void gve_get_drvinfo(struct net_device *netdev,
                            struct ethtool_drvinfo *info)
@@ -36,7 +37,7 @@ static const char gve_gstrings_main_stats[][ETH_GSTRING_LEN] = {
        "rx_dropped", "tx_dropped", "tx_timeouts",
        "rx_skb_alloc_fail", "rx_buf_alloc_fail", "rx_desc_err_dropped_pkt",
        "interface_up_cnt", "interface_down_cnt", "reset_cnt",
-       "page_alloc_fail", "dma_mapping_error",
+       "page_alloc_fail", "dma_mapping_error", "stats_report_trigger_cnt",
 };
 
 static const char gve_gstrings_rx_stats[][ETH_GSTRING_LEN] = {
@@ -56,12 +57,18 @@ static const char gve_gstrings_adminq_stats[][ETH_GSTRING_LEN] = {
        "adminq_create_tx_queue_cnt", "adminq_create_rx_queue_cnt",
        "adminq_destroy_tx_queue_cnt", "adminq_destroy_rx_queue_cnt",
        "adminq_dcfg_device_resources_cnt", "adminq_set_driver_parameter_cnt",
+       "adminq_report_stats_cnt",
+};
+
+static const char gve_gstrings_priv_flags[][ETH_GSTRING_LEN] = {
+       "report-stats",
 };
 
 #define GVE_MAIN_STATS_LEN  ARRAY_SIZE(gve_gstrings_main_stats)
 #define GVE_ADMINQ_STATS_LEN  ARRAY_SIZE(gve_gstrings_adminq_stats)
 #define NUM_GVE_TX_CNTS        ARRAY_SIZE(gve_gstrings_tx_stats)
 #define NUM_GVE_RX_CNTS        ARRAY_SIZE(gve_gstrings_rx_stats)
+#define GVE_PRIV_FLAGS_STR_LEN ARRAY_SIZE(gve_gstrings_priv_flags)
 
 static void gve_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
 {
@@ -69,28 +76,42 @@ static void gve_get_strings(struct net_device *netdev, u32 stringset, u8 *data)
        char *s = (char *)data;
        int i, j;
 
-       if (stringset != ETH_SS_STATS)
-               return;
-
-       memcpy(s, *gve_gstrings_main_stats,
-              sizeof(gve_gstrings_main_stats));
-       s += sizeof(gve_gstrings_main_stats);
-       for (i = 0; i < priv->rx_cfg.num_queues; i++) {
-               for (j = 0; j < NUM_GVE_RX_CNTS; j++) {
-                       snprintf(s, ETH_GSTRING_LEN, gve_gstrings_rx_stats[j], i);
-                       s += ETH_GSTRING_LEN;
+       switch (stringset) {
+       case ETH_SS_STATS:
+               memcpy(s, *gve_gstrings_main_stats,
+                      sizeof(gve_gstrings_main_stats));
+               s += sizeof(gve_gstrings_main_stats);
+
+               for (i = 0; i < priv->rx_cfg.num_queues; i++) {
+                       for (j = 0; j < NUM_GVE_RX_CNTS; j++) {
+                               snprintf(s, ETH_GSTRING_LEN,
+                                        gve_gstrings_rx_stats[j], i);
+                               s += ETH_GSTRING_LEN;
+                       }
                }
-       }
-       for (i = 0; i < priv->tx_cfg.num_queues; i++) {
-               for (j = 0; j < NUM_GVE_TX_CNTS; j++) {
-                       snprintf(s, ETH_GSTRING_LEN, gve_gstrings_tx_stats[j], i);
-                       s += ETH_GSTRING_LEN;
+
+               for (i = 0; i < priv->tx_cfg.num_queues; i++) {
+                       for (j = 0; j < NUM_GVE_TX_CNTS; j++) {
+                               snprintf(s, ETH_GSTRING_LEN,
+                                        gve_gstrings_tx_stats[j], i);
+                               s += ETH_GSTRING_LEN;
+                       }
                }
-       }
 
-       memcpy(s, *gve_gstrings_adminq_stats,
-              sizeof(gve_gstrings_adminq_stats));
-       s += sizeof(gve_gstrings_adminq_stats);
+               memcpy(s, *gve_gstrings_adminq_stats,
+                      sizeof(gve_gstrings_adminq_stats));
+               s += sizeof(gve_gstrings_adminq_stats);
+               break;
+
+       case ETH_SS_PRIV_FLAGS:
+               memcpy(s, *gve_gstrings_priv_flags,
+                      sizeof(gve_gstrings_priv_flags));
+               s += sizeof(gve_gstrings_priv_flags);
+               break;
+
+       default:
+               break;
+       }
 }
 
 static int gve_get_sset_count(struct net_device *netdev, int sset)
@@ -102,6 +123,8 @@ static int gve_get_sset_count(struct net_device *netdev, int sset)
                return GVE_MAIN_STATS_LEN + GVE_ADMINQ_STATS_LEN +
                       (priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS) +
                       (priv->tx_cfg.num_queues * NUM_GVE_TX_CNTS);
+       case ETH_SS_PRIV_FLAGS:
+               return GVE_PRIV_FLAGS_STR_LEN;
        default:
                return -EOPNOTSUPP;
        }
@@ -182,6 +205,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
        data[i++] = priv->reset_cnt;
        data[i++] = priv->page_alloc_fail;
        data[i++] = priv->dma_mapping_error;
+       data[i++] = priv->stats_report_trigger_cnt;
        i = GVE_MAIN_STATS_LEN;
 
        /* walk RX rings */
@@ -248,6 +272,7 @@ gve_get_ethtool_stats(struct net_device *netdev,
        data[i++] = priv->adminq_destroy_rx_queue_cnt;
        data[i++] = priv->adminq_dcfg_device_resources_cnt;
        data[i++] = priv->adminq_set_driver_parameter_cnt;
+       data[i++] = priv->adminq_report_stats_cnt;
 }
 
 static void gve_get_channels(struct net_device *netdev,
@@ -352,6 +377,52 @@ static int gve_set_tunable(struct net_device *netdev,
        }
 }
 
+static u32 gve_get_priv_flags(struct net_device *netdev)
+{
+       struct gve_priv *priv = netdev_priv(netdev);
+       u32 ret_flags = 0;
+
+       /* Only 1 flag exists currently: report-stats (BIT(O)), so set that flag. */
+       if (priv->ethtool_flags & BIT(0))
+               ret_flags |= BIT(0);
+       return ret_flags;
+}
+
+static int gve_set_priv_flags(struct net_device *netdev, u32 flags)
+{
+       struct gve_priv *priv = netdev_priv(netdev);
+       u64 ori_flags, new_flags;
+
+       ori_flags = READ_ONCE(priv->ethtool_flags);
+       new_flags = ori_flags;
+
+       /* Only one priv flag exists: report-stats (BIT(0))*/
+       if (flags & BIT(0))
+               new_flags |= BIT(0);
+       else
+               new_flags &= ~(BIT(0));
+       priv->ethtool_flags = new_flags;
+       /* start report-stats timer when user turns report stats on. */
+       if (flags & BIT(0)) {
+               mod_timer(&priv->stats_report_timer,
+                         round_jiffies(jiffies +
+                                       msecs_to_jiffies(priv->stats_report_timer_period)));
+       }
+       /* Zero off gve stats when report-stats turned off and */
+       /* delete report stats timer. */
+       if (!(flags & BIT(0)) && (ori_flags & BIT(0))) {
+               int tx_stats_num = GVE_TX_STATS_REPORT_NUM *
+                       priv->tx_cfg.num_queues;
+               int rx_stats_num = GVE_RX_STATS_REPORT_NUM *
+                       priv->rx_cfg.num_queues;
+
+               memset(priv->stats_report->stats, 0, (tx_stats_num + rx_stats_num) *
+                                  sizeof(struct stats));
+               del_timer_sync(&priv->stats_report_timer);
+       }
+       return 0;
+}
+
 const struct ethtool_ops gve_ethtool_ops = {
        .get_drvinfo = gve_get_drvinfo,
        .get_strings = gve_get_strings,
@@ -366,4 +437,6 @@ const struct ethtool_ops gve_ethtool_ops = {
        .reset = gve_user_reset,
        .get_tunable = gve_get_tunable,
        .set_tunable = gve_set_tunable,
+       .get_priv_flags = gve_get_priv_flags,
+       .set_priv_flags = gve_set_priv_flags,
 };
index 0fc68f8..936ff4c 100644 (file)
@@ -78,6 +78,66 @@ static void gve_free_counter_array(struct gve_priv *priv)
        priv->counter_array = NULL;
 }
 
+/* NIC requests to report stats */
+static void gve_stats_report_task(struct work_struct *work)
+{
+       struct gve_priv *priv = container_of(work, struct gve_priv,
+                                            stats_report_task);
+       if (gve_get_do_report_stats(priv)) {
+               gve_handle_report_stats(priv);
+               gve_clear_do_report_stats(priv);
+       }
+}
+
+static void gve_stats_report_schedule(struct gve_priv *priv)
+{
+       if (!gve_get_probe_in_progress(priv) &&
+           !gve_get_reset_in_progress(priv)) {
+               gve_set_do_report_stats(priv);
+               queue_work(priv->gve_wq, &priv->stats_report_task);
+       }
+}
+
+static void gve_stats_report_timer(struct timer_list *t)
+{
+       struct gve_priv *priv = from_timer(priv, t, stats_report_timer);
+
+       mod_timer(&priv->stats_report_timer,
+                 round_jiffies(jiffies +
+                 msecs_to_jiffies(priv->stats_report_timer_period)));
+       gve_stats_report_schedule(priv);
+}
+
+static int gve_alloc_stats_report(struct gve_priv *priv)
+{
+       int tx_stats_num, rx_stats_num;
+
+       tx_stats_num = (GVE_TX_STATS_REPORT_NUM) *
+                      priv->tx_cfg.num_queues;
+       rx_stats_num = (GVE_RX_STATS_REPORT_NUM) *
+                      priv->rx_cfg.num_queues;
+       priv->stats_report_len = sizeof(struct gve_stats_report) +
+                                (tx_stats_num + rx_stats_num) *
+                                sizeof(struct stats);
+       priv->stats_report =
+               dma_alloc_coherent(&priv->pdev->dev, priv->stats_report_len,
+                                  &priv->stats_report_bus, GFP_KERNEL);
+       if (!priv->stats_report)
+               return -ENOMEM;
+       /* Set up timer for the report-stats task */
+       timer_setup(&priv->stats_report_timer, gve_stats_report_timer, 0);
+       priv->stats_report_timer_period = GVE_STATS_REPORT_TIMER_PERIOD;
+       return 0;
+}
+
+static void gve_free_stats_report(struct gve_priv *priv)
+{
+       del_timer_sync(&priv->stats_report_timer);
+       dma_free_coherent(&priv->pdev->dev, priv->stats_report_len,
+                         priv->stats_report, priv->stats_report_bus);
+       priv->stats_report = NULL;
+}
+
 static irqreturn_t gve_mgmnt_intr(int irq, void *arg)
 {
        struct gve_priv *priv = arg;
@@ -270,6 +330,9 @@ static int gve_setup_device_resources(struct gve_priv *priv)
        err = gve_alloc_notify_blocks(priv);
        if (err)
                goto abort_with_counter;
+       err = gve_alloc_stats_report(priv);
+       if (err)
+               goto abort_with_ntfy_blocks;
        err = gve_adminq_configure_device_resources(priv,
                                                    priv->counter_array_bus,
                                                    priv->num_event_counters,
@@ -279,10 +342,18 @@ static int gve_setup_device_resources(struct gve_priv *priv)
                dev_err(&priv->pdev->dev,
                        "could not setup device_resources: err=%d\n", err);
                err = -ENXIO;
-               goto abort_with_ntfy_blocks;
+               goto abort_with_stats_report;
        }
+       err = gve_adminq_report_stats(priv, priv->stats_report_len,
+                                     priv->stats_report_bus,
+                                     GVE_STATS_REPORT_TIMER_PERIOD);
+       if (err)
+               dev_err(&priv->pdev->dev,
+                       "Failed to report stats: err=%d\n", err);
        gve_set_device_resources_ok(priv);
        return 0;
+abort_with_stats_report:
+       gve_free_stats_report(priv);
 abort_with_ntfy_blocks:
        gve_free_notify_blocks(priv);
 abort_with_counter:
@@ -298,6 +369,13 @@ static void gve_teardown_device_resources(struct gve_priv *priv)
 
        /* Tell device its resources are being freed */
        if (gve_get_device_resources_ok(priv)) {
+               /* detach the stats report */
+               err = gve_adminq_report_stats(priv, 0, 0x0, GVE_STATS_REPORT_TIMER_PERIOD);
+               if (err) {
+                       dev_err(&priv->pdev->dev,
+                               "Failed to detach stats report: err=%d\n", err);
+                       gve_trigger_reset(priv);
+               }
                err = gve_adminq_deconfigure_device_resources(priv);
                if (err) {
                        dev_err(&priv->pdev->dev,
@@ -308,6 +386,7 @@ static void gve_teardown_device_resources(struct gve_priv *priv)
        }
        gve_free_counter_array(priv);
        gve_free_notify_blocks(priv);
+       gve_free_stats_report(priv);
        gve_clear_device_resources_ok(priv);
 }
 
@@ -699,6 +778,11 @@ static int gve_open(struct net_device *dev)
                goto reset;
        gve_set_device_rings_ok(priv);
 
+       if (gve_get_report_stats(priv))
+               mod_timer(&priv->stats_report_timer,
+                         round_jiffies(jiffies +
+                               msecs_to_jiffies(priv->stats_report_timer_period)));
+
        gve_turnup(priv);
        netif_carrier_on(dev);
        priv->interface_up_cnt++;
@@ -740,6 +824,7 @@ static int gve_close(struct net_device *dev)
                        goto err;
                gve_clear_device_rings_ok(priv);
        }
+       del_timer_sync(&priv->stats_report_timer);
 
        gve_free_rings(priv);
        gve_free_qpls(priv);
@@ -823,6 +908,7 @@ static void gve_turndown(struct gve_priv *priv)
        netif_tx_disable(priv->dev);
 
        gve_clear_napi_enabled(priv);
+       gve_clear_report_stats(priv);
 }
 
 static void gve_turnup(struct gve_priv *priv)
@@ -873,6 +959,10 @@ static void gve_handle_status(struct gve_priv *priv, u32 status)
                dev_info(&priv->pdev->dev, "Device requested reset.\n");
                gve_set_do_reset(priv);
        }
+       if (GVE_DEVICE_STATUS_REPORT_STATS_MASK & status) {
+               priv->stats_report_trigger_cnt++;
+               gve_set_do_report_stats(priv);
+       }
 }
 
 static void gve_handle_reset(struct gve_priv *priv)
@@ -891,7 +981,68 @@ static void gve_handle_reset(struct gve_priv *priv)
        }
 }
 
-/* Handle NIC status register changes and reset requests */
+void gve_handle_report_stats(struct gve_priv *priv)
+{
+       int idx, stats_idx = 0, tx_bytes;
+       unsigned int start = 0;
+       struct stats *stats = priv->stats_report->stats;
+
+       if (!gve_get_report_stats(priv))
+               return;
+
+       be64_add_cpu(&priv->stats_report->written_count, 1);
+       /* tx stats */
+       if (priv->tx) {
+               for (idx = 0; idx < priv->tx_cfg.num_queues; idx++) {
+                       do {
+                               start = u64_stats_fetch_begin(&priv->tx[idx].statss);
+                               tx_bytes = priv->tx[idx].bytes_done;
+                       } while (u64_stats_fetch_retry(&priv->tx[idx].statss, start));
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(TX_WAKE_CNT),
+                               .value = cpu_to_be64(priv->tx[idx].wake_queue),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(TX_STOP_CNT),
+                               .value = cpu_to_be64(priv->tx[idx].stop_queue),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(TX_FRAMES_SENT),
+                               .value = cpu_to_be64(priv->tx[idx].req),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(TX_BYTES_SENT),
+                               .value = cpu_to_be64(tx_bytes),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(TX_LAST_COMPLETION_PROCESSED),
+                               .value = cpu_to_be64(priv->tx[idx].done),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+               }
+       }
+       /* rx stats */
+       if (priv->rx) {
+               for (idx = 0; idx < priv->rx_cfg.num_queues; idx++) {
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(RX_NEXT_EXPECTED_SEQUENCE),
+                               .value = cpu_to_be64(priv->rx[idx].desc.seqno),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+                       stats[stats_idx++] = (struct stats) {
+                               .stat_name = cpu_to_be32(RX_BUFFERS_POSTED),
+                               .value = cpu_to_be64(priv->rx[0].fill_cnt),
+                               .queue_id = cpu_to_be32(idx),
+                       };
+               }
+       }
+}
+
+/* Handle NIC status register changes, reset requests and report stats */
 static void gve_service_task(struct work_struct *work)
 {
        struct gve_priv *priv = container_of(work, struct gve_priv,
@@ -1056,6 +1207,7 @@ int gve_reset(struct gve_priv *priv, bool attempt_teardown)
        priv->reset_cnt++;
        priv->interface_up_cnt = 0;
        priv->interface_down_cnt = 0;
+       priv->stats_report_trigger_cnt = 0;
        return err;
 }
 
@@ -1158,6 +1310,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
        priv->db_bar2 = db_bar;
        priv->service_task_flags = 0x0;
        priv->state_flags = 0x0;
+       priv->ethtool_flags = 0x0;
 
        gve_set_probe_in_progress(priv);
        priv->gve_wq = alloc_ordered_workqueue("gve", 0);
@@ -1167,6 +1320,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
                goto abort_with_netdev;
        }
        INIT_WORK(&priv->service_task, gve_service_task);
+       INIT_WORK(&priv->stats_report_task, gve_stats_report_task);
        priv->tx_cfg.max_queues = max_tx_queues;
        priv->rx_cfg.max_queues = max_rx_queues;
 
index 84ab889..fb65546 100644 (file)
@@ -23,5 +23,6 @@ struct gve_registers {
 enum gve_device_status_flags {
        GVE_DEVICE_STATUS_RESET_MASK            = BIT(1),
        GVE_DEVICE_STATUS_LINK_STATUS_MASK      = BIT(2),
+       GVE_DEVICE_STATUS_REPORT_STATS_MASK     = BIT(3),
 };
 #endif /* _GVE_REGISTER_H_ */