From: Long Li <longli@microsoft.com>
To: Long Li <longli@microsoft.com>,
Konstantin Taranov <kotaranov@microsoft.com>,
Jakub Kicinski <kuba@kernel.org>,
"David S . Miller" <davem@davemloft.net>,
Paolo Abeni <pabeni@redhat.com>,
Eric Dumazet <edumazet@google.com>,
Andrew Lunn <andrew+netdev@lunn.ch>,
Jason Gunthorpe <jgg@ziepe.ca>, Leon Romanovsky <leon@kernel.org>,
Haiyang Zhang <haiyangz@microsoft.com>,
"K . Y . Srinivasan" <kys@microsoft.com>,
Wei Liu <wei.liu@kernel.org>, Dexuan Cui <decui@microsoft.com>,
shradhagupta@linux.microsoft.com, Simon Horman <horms@kernel.org>,
ernis@linux.microsoft.com, stephen@networkplumber.org,
shirazsaleem@microsoft.com
Cc: netdev@vger.kernel.org, linux-rdma@vger.kernel.org,
linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context
Date: Mon, 31 Aug 2026 18:44:38 -0700 [thread overview]
Message-ID: <20260901014442.2945689-10-longli@microsoft.com> (raw)
In-Reply-To: <20260901014442.2945689-1-longli@microsoft.com>
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 <longli@microsoft.com>
---
.../net/ethernet/microsoft/mana/mana_bpf.c | 4 +-
drivers/net/ethernet/microsoft/mana/mana_en.c | 248 ++++++++++++++++--
.../ethernet/microsoft/mana/mana_ethtool.c | 37 ++-
include/net/mana/mana.h | 58 +++-
4 files changed, 308 insertions(+), 39 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c
index d2093625956bbef30d0d6c63121de8363b633489..0981327c284413f8a0e93856939a4b3c0f632d80 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 = mana_rxq_stats(rxq);
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 5c5d1edd20f84d0d1a20d2a5bc0ecefea0d7aaed..69e34fab8fe8ead53ecac5820ab4932c84af5371 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
@@ -414,7 +414,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 &&
@@ -593,7 +593,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);
@@ -639,9 +639,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;
@@ -649,6 +649,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)
@@ -656,8 +662,22 @@ static void mana_get_stats64(struct net_device *ndev,
st->rx_missed_errors = apc->ac->hc_stats.hc_rx_discards_no_wqe;
+ /* The live queue at each index and whatever retired there both count,
+ * so add the two slots.
+ */
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);
+ packets = rx_stats->packets;
+ bytes = rx_stats->bytes;
+ } while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+
+ st->rx_packets += packets;
+ st->rx_bytes += bytes;
+
+ rx_stats = &apc->rxq_stats_ret[q];
do {
start = u64_stats_fetch_begin(&rx_stats->syncp);
@@ -670,7 +690,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);
@@ -1092,6 +1112,122 @@ 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->rxq_stats_ret = kcalloc(apc->max_queues,
+ sizeof(*apc->rxq_stats_ret), GFP_KERNEL);
+ if (!apc->rxq_stats_ret)
+ goto free_rxq_stats;
+
+ apc->txq_stats = kcalloc(apc->max_queues, sizeof(*apc->txq_stats),
+ GFP_KERNEL);
+ if (!apc->txq_stats)
+ goto free_rxq_stats_ret;
+
+ for (i = 0; i < apc->max_queues; i++) {
+ u64_stats_init(&apc->rxq_stats[i].syncp);
+ u64_stats_init(&apc->rxq_stats_ret[i].syncp);
+ u64_stats_init(&apc->txq_stats[i].syncp);
+ }
+
+ return 0;
+
+free_rxq_stats_ret:
+ kfree(apc->rxq_stats_ret);
+ apc->rxq_stats_ret = NULL;
+free_rxq_stats:
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ return -ENOMEM;
+}
+
+static void mana_free_queue_stats(struct mana_port_context *apc)
+{
+ kfree(apc->rxq_stats);
+ apc->rxq_stats = NULL;
+ kfree(apc->rxq_stats_ret);
+ apc->rxq_stats_ret = NULL;
+ kfree(apc->txq_stats);
+ apc->txq_stats = NULL;
+}
+
+/* Add what @rxq counted while retiring to the per-index total. Must run under
+ * RTNL with the queue no longer writing to @drain_stats, so this is the only
+ * writer of the retired slot.
+ *
+ * Clears @drain_stats as it goes: a queue that survives a failed swap resumes
+ * counting into its live slot, and must not have these counts folded a second
+ * time when it is eventually destroyed.
+ */
+static void mana_fold_rxq_stats(struct mana_port_context *apc,
+ struct mana_rxq *rxq)
+{
+ struct mana_stats_rx *src = &rxq->drain_stats;
+ struct mana_stats_rx *dst;
+ unsigned int i;
+
+ ASSERT_RTNL();
+
+ if (!apc->rxq_stats_ret || rxq->rxq_idx >= apc->max_queues)
+ return;
+
+ dst = &apc->rxq_stats_ret[rxq->rxq_idx];
+
+ u64_stats_update_begin(&dst->syncp);
+ dst->packets += src->packets;
+ dst->bytes += src->bytes;
+ dst->xdp_drop += src->xdp_drop;
+ dst->xdp_tx += src->xdp_tx;
+ dst->xdp_redirect += src->xdp_redirect;
+ dst->pkt_len0_err += src->pkt_len0_err;
+ for (i = 0; i < ARRAY_SIZE(dst->coalesced_cqe); i++)
+ dst->coalesced_cqe[i] += src->coalesced_cqe[i];
+ u64_stats_update_end(&dst->syncp);
+
+ src->packets = 0;
+ src->bytes = 0;
+ src->xdp_drop = 0;
+ src->xdp_tx = 0;
+ src->xdp_redirect = 0;
+ src->pkt_len0_err = 0;
+ for (i = 0; i < ARRAY_SIZE(src->coalesced_cqe); i++)
+ src->coalesced_cqe[i] = 0;
+}
+
+/* Publish what every queue in @qset counted while it was marked retiring.
+ * For a set that is being destroyed this happens queue by queue; a set handed
+ * back by a failed swap needs it done in one pass, before it serves again.
+ */
+static void mana_fold_qset_rx_stats(struct mana_port_context *apc,
+ struct mana_qset *qset)
+{
+ unsigned int q;
+
+ if (!qset->rxqs)
+ return;
+
+ for (q = 0; q < qset->num_queues; q++) {
+ if (qset->rxqs[q])
+ mana_fold_rxq_stats(apc, qset->rxqs[q]);
+ }
+}
+
static void mana_cleanup_indir_table(struct mana_port_context *apc)
{
apc->indir_table_sz = 0;
@@ -2220,7 +2356,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 = mana_rxq_stats(rxq);
struct net_device *ndev = rxq->ndev;
u16 rxq_idx = rxq->rxq_idx;
struct napi_struct *napi;
@@ -2453,6 +2589,7 @@ static void mana_process_rx_cqe(struct mana_rxq *rxq, struct mana_cq *cq,
struct net_device *ndev = rxq->ndev;
struct mana_recv_buf_oob *rxbuf_oob;
struct mana_port_context *apc;
+ struct mana_stats_rx *rx_stats;
struct device *dev = gc->dev;
bool coalesced_8 = false;
bool coalesced = false;
@@ -2534,13 +2671,15 @@ 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);
+ rx_stats = mana_rxq_stats(rxq);
+ u64_stats_update_begin(&rx_stats->syncp);
+ rx_stats->coalesced_cqe[pkt_i - 2]++;
+ u64_stats_update_end(&rx_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);
+ rx_stats = mana_rxq_stats(rxq);
+ u64_stats_update_begin(&rx_stats->syncp);
+ rx_stats->pkt_len0_err++;
+ u64_stats_update_end(&rx_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);
@@ -2672,8 +2811,15 @@ 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);
+ /* A retiring queue counts elsewhere and is about to be destroyed, so
+ * there is no moderation left to tune and its samples would step off
+ * the shared slot onto a counter that restarts at zero.
+ */
+ if (READ_ONCE(rxq->retiring))
+ return;
+
+ dim_update_sample(READ_ONCE(cq->dim_event_ctr), rxq->stats->packets,
+ rxq->stats->bytes, &dim_sample);
net_dim(&cq->dim, &dim_sample);
}
@@ -2890,7 +3036,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);
@@ -3017,6 +3163,11 @@ static void mana_destroy_rxq(struct mana_port_context *apc,
netif_napi_del_locked(napi);
}
+ /* No poller left, so this is the last chance to keep what the queue
+ * counted after it stopped being the live one.
+ */
+ mana_fold_rxq_stats(apc, rxq);
+
if (xdp_rxq_info_is_reg(&rxq->xdp_rxq))
xdp_rxq_info_unreg(&rxq->xdp_rxq);
@@ -3205,6 +3356,9 @@ 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];
+ u64_stats_init(&rxq->drain_stats.syncp);
rxq->num_rx_buf = apc->rx_queue_size;
rxq->rxq_idx = rxq_idx;
rxq->rxobj = INVALID_MANA_HANDLE;
@@ -3355,8 +3509,6 @@ static int mana_add_rx_queues(struct mana_port_context *apc,
goto out;
}
- u64_stats_init(&rxq->stats.syncp);
-
apc->rxqs[i] = rxq;
mana_create_rxq_debugfs(apc, i);
@@ -4230,16 +4382,33 @@ static void mana_start_txqs(struct mana_port_context *apc)
* A queue both sets own must end up unmarked, so callers mark the leaving set
* first and unmark the incoming one second.
*/
-static void mana_qset_set_retiring(struct mana_qset *qset, bool retiring)
+static void mana_qset_set_retiring(struct mana_qset *qset,
+ const struct mana_qset *keep, bool retiring)
{
unsigned int q;
- if (!qset->tx_qp)
- return;
-
for (q = 0; q < qset->num_queues; q++) {
- if (qset->tx_qp[q])
+ if (qset->tx_qp && qset->tx_qp[q])
WRITE_ONCE(qset->tx_qp[q]->txq.retiring, retiring);
+
+ if (!qset->rxqs || !qset->rxqs[q])
+ continue;
+
+ /* A queue @keep carries over serves the same index before and
+ * after, so it stays the live writer of that index. Marking it
+ * would strand the counts it takes during the swap in
+ * drain_stats, which only mana_destroy_rxq() drains.
+ */
+ if (retiring && keep && q < keep->num_queues &&
+ keep->rxqs && keep->rxqs[q] == qset->rxqs[q])
+ continue;
+
+ /* Send this queue's counters to its own storage rather than
+ * the shared per-index slot, which its replacement is about
+ * to own. The caller's synchronize_net() makes the change
+ * visible before that replacement can receive.
+ */
+ WRITE_ONCE(qset->rxqs[q]->retiring, retiring);
}
}
@@ -4327,7 +4496,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
* the gate reopens, or it could wake a netdev queue that its
* replacement had stopped on a full ring.
*/
- mana_qset_set_retiring(out_old, true);
+ mana_qset_set_retiring(out_old, newq, true);
/* Wait out any transmit or ndo_xdp_xmit() that was already past the
* port_is_up test before the swap touches apc->tx_qp / the counts,
@@ -4338,7 +4507,7 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
/* Anything the incoming set carries over is staying, so clear the flag
* again - after the marking above, before the gate reopens.
*/
- mana_qset_set_retiring(newq, false);
+ mana_qset_set_retiring(newq, NULL, false);
mana_qset_install(apc, newq);
apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
@@ -4384,8 +4553,23 @@ int mana_publish_qset(struct mana_port_context *apc, struct mana_qset *newq,
/* The roles are swapped now: @newq is the set going away and @out_old
* is live again. Same ordering rule, leaving set first.
*/
- mana_qset_set_retiring(newq, true);
- mana_qset_set_retiring(out_old, false);
+ mana_qset_set_retiring(newq, out_old, true);
+
+ /* Same grace period as the forward path: a poll that sampled the flag
+ * before the line above must finish before @out_old is unmarked, or
+ * both sets would briefly count into apc->rxq_stats[].
+ */
+ synchronize_net();
+
+ mana_qset_set_retiring(out_old, NULL, false);
+
+ /* @out_old counted into drain_stats while it was marked, and it is
+ * about to serve again rather than be destroyed, so nothing else
+ * would ever publish those packets. Fold them now, once the polls
+ * that still saw the flag above have finished writing.
+ */
+ synchronize_net();
+ mana_fold_qset_rx_stats(apc, out_old);
mana_qset_install(apc, out_old);
apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE;
@@ -4647,6 +4831,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;
@@ -4669,7 +4857,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)
@@ -4706,6 +4894,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);
@@ -5046,6 +5239,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 82bfd22cfe820c17aea66d6d3f6289165bf32894..4d7e64b1d32d9eca5fc006e13941f72f2176a131 100644
--- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
+++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c
@@ -242,6 +242,12 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
u64 xdp_tx;
u64 pkt_len0_err;
u64 coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
+ u64 ret_coalesced_cqe[MANA_CQE_COAL_PKTS_8 - 1];
+ u64 ret_packets, ret_bytes;
+ u64 ret_xdp_redirect;
+ u64 ret_pkt_len0_err;
+ u64 ret_xdp_drop;
+ u64 ret_xdp_tx;
u64 tso_packets;
u64 tso_bytes;
u64 tso_inner_packets;
@@ -271,7 +277,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);
@@ -285,6 +291,33 @@ static void mana_get_ethtool_stats(struct net_device *ndev,
coalesced_cqe[j] = rx_stats->coalesced_cqe[j];
} while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+ /* Same index, counted by queues that have since retired. Read
+ * into its own snapshot, since a retry must not add twice.
+ */
+ rx_stats = &apc->rxq_stats_ret[q];
+
+ do {
+ start = u64_stats_fetch_begin(&rx_stats->syncp);
+ ret_packets = rx_stats->packets;
+ ret_bytes = rx_stats->bytes;
+ ret_xdp_drop = rx_stats->xdp_drop;
+ ret_xdp_tx = rx_stats->xdp_tx;
+ ret_xdp_redirect = rx_stats->xdp_redirect;
+ ret_pkt_len0_err = rx_stats->pkt_len0_err;
+ for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++)
+ ret_coalesced_cqe[j] =
+ rx_stats->coalesced_cqe[j];
+ } while (u64_stats_fetch_retry(&rx_stats->syncp, start));
+
+ packets += ret_packets;
+ bytes += ret_bytes;
+ xdp_drop += ret_xdp_drop;
+ xdp_tx += ret_xdp_tx;
+ xdp_redirect += ret_xdp_redirect;
+ pkt_len0_err += ret_pkt_len0_err;
+ for (j = 0; j < MANA_CQE_COAL_PKTS_8 - 1; j++)
+ coalesced_cqe[j] += ret_coalesced_cqe[j];
+
data[i++] = packets;
data[i++] = bytes;
data[i++] = xdp_drop;
@@ -296,7 +329,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 61d136736cf6c455a132d7297badf5ab15b4693d..9a43856760776fb2c23786bbdbb36fdbe5d2be81 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,23 @@ struct mana_rxq {
u32 buf_index;
- struct mana_stats_rx stats;
+ /* Points into apc->rxq_stats[], which outlives the queue. Only the
+ * live queue at this index writes there; once retiring is set this
+ * queue counts into drain_stats instead, so the slot has one writer.
+ * Use mana_rxq_stats() rather than either directly.
+ */
+ struct mana_stats_rx *stats;
+
+ /* Set under RTNL before a different queue takes over this index. A
+ * queue carried across a swap keeps serving its index and is never
+ * marked.
+ */
+ bool retiring;
+
+ /* What this queue counted after it stopped being the live one.
+ * Folded into apc->rxq_stats_ret[] when the queue is destroyed.
+ */
+ struct mana_stats_rx drain_stats;
struct bpf_prog __rcu *bpf_prog;
struct xdp_rxq_info xdp_rxq;
@@ -623,6 +643,19 @@ 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.
+ *
+ * rxq_stats[] is written by the live RX queue at that index and
+ * rxq_stats_ret[] only under RTNL, by mana_destroy_rxq() folding in
+ * what a retiring queue counted while it drained. One writer each;
+ * readers add the two.
+ */
+ struct mana_stats_rx *rxq_stats;
+ struct mana_stats_rx *rxq_stats_ret;
+ struct mana_stats_tx *txq_stats;
+
unsigned int rx_queue_size;
unsigned int tx_queue_size;
@@ -747,6 +780,15 @@ int mana_detach(struct net_device *ndev, bool from_close);
struct mana_port_context *
mana_qset_scratch_alloc(struct mana_port_context *apc);
void mana_qset_scratch_free(struct mana_port_context *scratch);
+/* Where @rxq counts. A retiring queue is no longer the one serving its index,
+ * so it counts into its own storage and leaves the shared slot to whatever
+ * replaced it. Nothing is lost: mana_destroy_rxq() folds it back.
+ */
+static inline struct mana_stats_rx *mana_rxq_stats(struct mana_rxq *rxq)
+{
+ return READ_ONCE(rxq->retiring) ? &rxq->drain_stats : rxq->stats;
+}
+
int mana_alloc_qset(struct mana_port_context *apc,
struct mana_port_context *scratch, unsigned int num_queues,
unsigned int rx_queue_size, unsigned int tx_queue_size,
--
2.43.0
next prev parent reply other threads:[~2026-09-01 1:45 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-01 1:44 [PATCH net-next v3 00/13] net: mana: reconfigure by replacing the queue set Long Li
2026-09-01 1:44 ` [PATCH net-next v3 01/13] net: mana: add queue-set allocation and teardown helpers Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 02/13] net: mana: share the EQ pool across a queue-set swap Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 03/13] net: mana: swap queue sets in mana_set_channels Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 04/13] net: mana: swap queue sets in mana_set_ringparam Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 05/13] net: mana: swap queue sets in mana_set_priv_flags Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 06/13] net: mana: swap queue sets in mana_change_mtu Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 07/13] net: mana: swap queue sets in mana_xdp_set Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 08/13] net: mana: do not bail out of mana_detach on dealloc failure Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` Long Li [this message]
2026-09-04 4:46 ` [PATCH net-next v3 09/13] net: mana: keep per-queue statistics in the port context netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 10/13] net: mana: release EQs left idle by a channel-count reduction Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 11/13] net: mana: keep a user-configured RSS table across a queue rebuild Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 12/13] net: mana: keep the surviving queues when the channel count is reduced Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
2026-09-01 1:44 ` [PATCH net-next v3 13/13] net: mana: keep the existing queues when the channel count is raised Long Li
2026-09-04 4:46 ` netdev-bot+sashiko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260901014442.2945689-10-longli@microsoft.com \
--to=longli@microsoft.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=decui@microsoft.com \
--cc=edumazet@google.com \
--cc=ernis@linux.microsoft.com \
--cc=haiyangz@microsoft.com \
--cc=horms@kernel.org \
--cc=jgg@ziepe.ca \
--cc=kotaranov@microsoft.com \
--cc=kuba@kernel.org \
--cc=kys@microsoft.com \
--cc=leon@kernel.org \
--cc=linux-hyperv@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shirazsaleem@microsoft.com \
--cc=shradhagupta@linux.microsoft.com \
--cc=stephen@networkplumber.org \
--cc=wei.liu@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox