struct mana_rxq and struct mana_txq embed their statistics by value, so
every reconfiguration - ethtool channel count, ring size and private
flags, MTU changes, XDP attach - destroys and recreates them, resetting
the interface counters. rx_bytes can be observed going backwards:

  rx_bytes before: 4475831638
  rx_bytes after:   526629152

Move them into port-context arrays sized to max_queues and allocated for
the lifetime of the port, so a queue set can be freed without losing what
it accumulated. ndo_get_stats64() walks max_queues, so counters from
queues a later reconfiguration removed are still reported and the totals
stay monotonic. The ethtool per-queue statistics keep iterating the
current count, which is what sizes their string table.

Counters reset on detach/attach the same way before this, and are kept
across it now too; it matters more as reconfiguration is hitless and so
typically done on a live link.

Signed-off-by: Long Li <[email protected]>
---
 .../net/ethernet/microsoft/mana/mana_bpf.c    |  4 +-
 drivers/net/ethernet/microsoft/mana/mana_en.c | 93 +++++++++++++++----
 .../ethernet/microsoft/mana/mana_ethtool.c    |  4 +-
 include/net/mana/mana.h                       | 28 ++++--
 4 files changed, 100 insertions(+), 29 deletions(-)

diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c 
b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index 
84b1484faaec8bf411ce402538862347e68954f0..ed9ec6b8af480a303bfa9c0dd9894f5049d8b6d6
 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
@@ -75,7 +75,7 @@ int mana_xdp_xmit(struct net_device *ndev, int n, struct 
xdp_frame **frames,
                count++;
        }
 
-       tx_stats = &apc->tx_qp[q_idx]->txq.stats;
+       tx_stats = apc->tx_qp[q_idx]->txq.stats;
 
        u64_stats_update_begin(&tx_stats->syncp);
        tx_stats->xdp_xmit += count;
@@ -102,7 +102,7 @@ u32 mana_run_xdp(struct net_device *ndev, struct mana_rxq 
*rxq,
 
        act = bpf_prog_run_xdp(prog, xdp);
 
-       rx_stats = &rxq->stats;
+       rx_stats = rxq->stats;
 
        switch (act) {
        case XDP_PASS:
diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c 
b/drivers/net/ethernet/microsoft/mana/mana_en.c
index 
2b3250483b43954c74d8cbf6a9e6e77880eac8ca..27484e5cf71a2216e7dc89c21ee0e9d95786cf30
 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -381,7 +381,7 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct 
net_device *ndev)
        txq = &apc->tx_qp[txq_idx]->txq;
        gdma_sq = txq->gdma_sq;
        cq = &apc->tx_qp[txq_idx]->tx_cq;
-       tx_stats = &txq->stats;
+       tx_stats = txq->stats;
 
        BUILD_BUG_ON(MAX_TX_WQE_SGL_ENTRIES != MANA_MAX_TX_WQE_SGL_ENTRIES);
        if (MAX_SKB_FRAGS + 2 > MAX_TX_WQE_SGL_ENTRIES &&
@@ -560,7 +560,7 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct 
net_device *ndev)
        /* Populated the packet and bytes counters based on post GSO packet
         * calculations
         */
-       tx_stats = &txq->stats;
+       tx_stats = txq->stats;
        u64_stats_update_begin(&tx_stats->syncp);
        tx_stats->packets += num_gso_seg;
        tx_stats->bytes += len + ((num_gso_seg - 1) * gso_hs);
@@ -606,9 +606,9 @@ static void mana_get_stats64(struct net_device *ndev,
                             struct rtnl_link_stats64 *st)
 {
        struct mana_port_context *apc = netdev_priv(ndev);
-       unsigned int num_queues = apc->num_queues;
        struct mana_stats_rx *rx_stats;
        struct mana_stats_tx *tx_stats;
+       unsigned int num_queues;
        unsigned int start;
        u64 packets, bytes;
        int q;
@@ -616,6 +616,12 @@ static void mana_get_stats64(struct net_device *ndev,
        if (!apc->port_is_up)
                return;
 
+       /* Walk every slot, not just the queues currently open: counters
+        * accumulated on queues that a later reconfiguration removed must
+        * still be reported, or the interface totals would go backwards.
+        */
+       num_queues = apc->max_queues;
+
        netdev_stats_to_stats64(st, &ndev->stats);
 
        if (apc->ac->hwc_timeout_occurred)
@@ -624,7 +630,7 @@ static void mana_get_stats64(struct net_device *ndev,
        st->rx_missed_errors = apc->ac->hc_stats.hc_rx_discards_no_wqe;
 
        for (q = 0; q < num_queues; q++) {
-               rx_stats = &apc->rxqs[q]->stats;
+               rx_stats = &apc->rxq_stats[q];
 
                do {
                        start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -637,7 +643,7 @@ static void mana_get_stats64(struct net_device *ndev,
        }
 
        for (q = 0; q < num_queues; q++) {
-               tx_stats = &apc->tx_qp[q]->txq.stats;
+               tx_stats = &apc->txq_stats[q];
 
                do {
                        start = u64_stats_fetch_begin(&tx_stats->syncp);
@@ -1059,6 +1065,48 @@ static void mana_cleanup_port_context(struct 
mana_port_context *apc)
        apc->rxqs = NULL;
 }
 
+/* Counters belong to the port, not the queues, so a queue-set replacement
+ * does not reset them. Sized to max_queues, allocated once.
+ *
+ * A swap adds no writer to a TX slot. RX slots do overlap briefly, since a
+ * retiring rxq keeps its NAPI until mana_free_qset() destroys it. MANA is
+ * 64-bit only, so u64_stats_sync has no seqcount and at worst a few
+ * increments are lost; the alternatives are a lock in the receive path or
+ * per-set slots that make ndo_get_stats64() dip during a swap.
+ */
+static int mana_alloc_queue_stats(struct mana_port_context *apc)
+{
+       unsigned int i;
+
+       apc->rxq_stats = kcalloc(apc->max_queues, sizeof(*apc->rxq_stats),
+                                GFP_KERNEL);
+       if (!apc->rxq_stats)
+               return -ENOMEM;
+
+       apc->txq_stats = kcalloc(apc->max_queues, sizeof(*apc->txq_stats),
+                                GFP_KERNEL);
+       if (!apc->txq_stats) {
+               kfree(apc->rxq_stats);
+               apc->rxq_stats = NULL;
+               return -ENOMEM;
+       }
+
+       for (i = 0; i < apc->max_queues; i++) {
+               u64_stats_init(&apc->rxq_stats[i].syncp);
+               u64_stats_init(&apc->txq_stats[i].syncp);
+       }
+
+       return 0;
+}
+
+static void mana_free_queue_stats(struct mana_port_context *apc)
+{
+       kfree(apc->rxq_stats);
+       apc->rxq_stats = NULL;
+       kfree(apc->txq_stats);
+       apc->txq_stats = NULL;
+}
+
 static void mana_cleanup_indir_table(struct mana_port_context *apc)
 {
        apc->indir_table_sz = 0;
@@ -2114,7 +2162,7 @@ static void mana_rx_skb(void *buf_va, bool from_pool,
                        struct mana_rxcomp_oob *cqe, struct mana_rxq *rxq,
                        u32 pkt_len, u32 pkt_hash)
 {
-       struct mana_stats_rx *rx_stats = &rxq->stats;
+       struct mana_stats_rx *rx_stats = rxq->stats;
        struct net_device *ndev = rxq->ndev;
        u16 rxq_idx = rxq->rxq_idx;
        struct napi_struct *napi;
@@ -2428,13 +2476,13 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, 
struct mana_cq *cq,
         * Coalesced CQEs have at least 2 packets, so index is pkt_i - 2.
         */
        if (pkt_i > 1) {
-               u64_stats_update_begin(&rxq->stats.syncp);
-               rxq->stats.coalesced_cqe[pkt_i - 2]++;
-               u64_stats_update_end(&rxq->stats.syncp);
+               u64_stats_update_begin(&rxq->stats->syncp);
+               rxq->stats->coalesced_cqe[pkt_i - 2]++;
+               u64_stats_update_end(&rxq->stats->syncp);
        } else if (!pkt_i && !pktlen) {
-               u64_stats_update_begin(&rxq->stats.syncp);
-               rxq->stats.pkt_len0_err++;
-               u64_stats_update_end(&rxq->stats.syncp);
+               u64_stats_update_begin(&rxq->stats->syncp);
+               rxq->stats->pkt_len0_err++;
+               u64_stats_update_end(&rxq->stats->syncp);
                netdev_err_once(ndev,
                                "RX pkt len=0, rq=%u, cq=%u, rxobj=0x%llx\n",
                                rxq->gdma_id, cq->gdma_id, rxq->rxobj);
@@ -2566,8 +2614,8 @@ static void mana_update_rx_dim(struct mana_cq *cq)
        if (!smp_load_acquire(&apc->rx_dim_enabled))
                return;
 
-       dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats.packets,
-                         rxq->stats.bytes, &dim_sample);
+       dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats->packets,
+                         rxq->stats->bytes, &dim_sample);
        net_dim(&cq->dim, &dim_sample);
 }
 
@@ -2784,7 +2832,7 @@ static int mana_create_txq(struct mana_port_context *apc,
                /* Create SQ */
                txq = &apc->tx_qp[i]->txq;
 
-               u64_stats_init(&txq->stats.syncp);
+               txq->stats = &apc->txq_stats[i];
                txq->ndev = net;
                txq->net_txq = netdev_get_tx_queue(net, i);
                txq->reset_gen = READ_ONCE(apc->ac->reset_gen);
@@ -3099,6 +3147,8 @@ static struct mana_rxq *mana_create_rxq(struct 
mana_port_context *apc,
                return ERR_PTR(-ENOMEM);
 
        rxq->ndev = ndev;
+       /* Wire up the port-owned statistics before the queue can be polled. */
+       rxq->stats = &apc->rxq_stats[rxq_idx];
        rxq->num_rx_buf = apc->rx_queue_size;
        rxq->rxq_idx = rxq_idx;
        rxq->rxobj = INVALID_MANA_HANDLE;
@@ -3249,7 +3299,6 @@ static int mana_add_rx_queues(struct mana_port_context 
*apc,
                        goto out;
                }
 
-               u64_stats_init(&rxq->stats.syncp);
 
                apc->rxqs[i] = rxq;
 
@@ -4555,6 +4604,10 @@ static int mana_probe_port(struct mana_context *ac, int 
port_idx,
                apc->tx_dim_enabled = MANA_ADAPTIVE_TX_DEF;
        }
 
+       err = mana_alloc_queue_stats(apc);
+       if (err)
+               goto free_net;
+
        mutex_init(&apc->vport_mutex);
        apc->vport_use_count = 0;
 
@@ -4577,7 +4630,7 @@ static int mana_probe_port(struct mana_context *ac, int 
port_idx,
 
        err = mana_init_port(ndev);
        if (err)
-               goto free_net;
+               goto free_stats;
 
        err = mana_rss_table_alloc(apc);
        if (err)
@@ -4614,6 +4667,11 @@ static int mana_probe_port(struct mana_context *ac, int 
port_idx,
        mana_cleanup_indir_table(apc);
 reset_apc:
        mana_cleanup_port_context(apc);
+free_stats:
+       /* The counter arrays are separate allocations, so free_netdev() does
+        * not release them with the port context.
+        */
+       mana_free_queue_stats(apc);
 free_net:
        *ndev_storage = NULL;
        netdev_err(ndev, "Failed to probe vPort %d: %d\n", port_idx, err);
@@ -4954,6 +5012,7 @@ void mana_remove(struct gdma_dev *gd, bool suspending)
 
                unregister_netdevice(ndev);
                mana_cleanup_indir_table(apc);
+               mana_free_queue_stats(apc);
 
                /* Clear the slot before the netdev goes away. A later port
                 * whose teardown has to reset the function walks ac->ports[]
diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c 
b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
index 
0daacee28001ed45a66ef00449b2c2acbf2859d4..26f5ea1f5091a7fab729ecf3468bdc13886fcccb
 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -271,7 +271,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
                data[i++] = *(u64 *)(phy_stats + mana_phy_stats[q].offset);
 
        for (q = 0; q < num_queues; q++) {
-               rx_stats = &apc->rxqs[q]->stats;
+               rx_stats = &apc->rxq_stats[q];
 
                do {
                        start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -296,7 +296,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
        }
 
        for (q = 0; q < num_queues; q++) {
-               tx_stats = &apc->tx_qp[q]->txq.stats;
+               tx_stats = &apc->txq_stats[q];
 
                do {
                        start = u64_stats_fetch_begin(&tx_stats->syncp);
diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h
index 
4fcd5e307a4805b81c5c82482fc65a4ba68eea89..77cec5b44049202e21f919d4336fdf8ebc248a18
 100644
--- a/include/net/mana/mana.h
+++ b/include/net/mana/mana.h
@@ -102,7 +102,10 @@ struct mana_stats_rx {
        u64 pkt_len0_err;
        u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
        struct u64_stats_sync syncp;
-};
+       /* Per-port array indexed by queue, so keep entries on separate cache
+        * lines: queues polled on different CPUs would bounce a shared one.
+        */
+} ____cacheline_aligned_in_smp;
 
 struct mana_stats_tx {
        u64 packets;
@@ -117,7 +120,8 @@ struct mana_stats_tx {
        u64 csum_partial;
        u64 mana_map_err;
        struct u64_stats_sync syncp;
-};
+       /* Per-queue array entry, same cache line reasoning as the RX side. */
+} ____cacheline_aligned_in_smp;
 
 struct mana_txq {
        struct gdma_queue *gdma_sq;
@@ -146,14 +150,14 @@ struct mana_txq {
        /* Value of mana_context.reset_gen when this queue was created. */
        u32 reset_gen;
 
-       /* Set once this queue has been unpublished and is on its way out.
-        * Its completions must not touch flow control any more: net_txq is
-        * shared with the queue that replaced it at the same index, and a
-        * draining queue always looks like it has room.
+       /* Unpublished and draining. Its completions must leave flow control
+        * alone: net_txq is shared with its replacement, and a draining queue
+        * always looks like it has room.
         */
        bool retiring;
 
-       struct mana_stats_tx stats;
+       /* Points into apc->txq_stats[], which outlives the queue. */
+       struct mana_stats_tx *stats;
 };
 
 /* skb data and frags dma mappings */
@@ -415,7 +419,8 @@ struct mana_rxq {
 
        u32 buf_index;
 
-       struct mana_stats_rx stats;
+       /* Points into apc->rxq_stats[], which outlives the queue. */
+       struct mana_stats_rx *stats;
 
        struct bpf_prog __rcu *bpf_prog;
        struct xdp_rxq_info xdp_rxq;
@@ -617,6 +622,13 @@ struct mana_port_context {
        unsigned int max_queues;
        unsigned int num_queues;
 
+       /* Per-queue counters, max_queues entries each. Allocated at probe and
+        * freed at remove, never on queue teardown, so a reconfiguration does
+        * not reset them.
+        */
+       struct mana_stats_rx *rxq_stats;
+       struct mana_stats_tx *txq_stats;
+
        unsigned int rx_queue_size;
        unsigned int tx_queue_size;
 
-- 
2.43.0


Reply via email to