As reported by Sashiko [1], IDPF_DESC_UNUSED() evaluates
txq->next_to_clean twice, and next_to_clean can be concurrently updated
by NAPI.  next_to_clean can change between evaluations, resulting in a
miscalculated free descriptor count that is larger than the ring
capacity. Consequently, netif_subqueue_maybe_stop() could incorrectly
determine there is room in the queue, bypassing the stop mechanism and
allowing active in-flight descriptors to be overwritten.

This patch is based on commit 9eab46b7cb8d ("e1000: fix data race
between tx_ring->next_to_clean").

Fixes: 6818c4d5b3c2 ("idpf: add splitq start_xmit")
Signed-off-by: Joshua Hay <[email protected]>
Reviewed-by: Aleksandr Loktionov <[email protected]>
Link: 
https://sashiko.dev/#/patchset/20260504-jk-iwl-net-2026-05-04-v1-0-a222a88bd962%40intel.com
 [1]
---
 drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c | 3 ++-
 drivers/net/ethernet/intel/idpf/idpf_txrx.c         | 9 ++++++---
 drivers/net/ethernet/intel/idpf/idpf_txrx.h         | 9 ++++++---
 3 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c 
b/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c
index e3ddf18dcbf5..a0e3de3ed0a9 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c
@@ -558,7 +558,8 @@ static bool idpf_tx_singleq_clean(struct idpf_tx_queue 
*tx_q, int napi_budget,
        } while (likely(budget));
 
        ntc += tx_q->desc_count;
-       tx_q->next_to_clean = ntc;
+       /* Sync with IDPF_DESC_UNUSED called from idpf_tx_singleq_frame. */
+       smp_store_release(&tx_q->next_to_clean, ntc);
 
        *cleaned += ss.packets;
 
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c 
b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
index 34fc85cbb3f4..9cc4fbd13313 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
@@ -2073,8 +2073,10 @@ static void idpf_tx_splitq_clean(struct idpf_tx_queue 
*tx_q, u16 end,
        struct idpf_tx_buf *tx_buf;
 
        if (descs_only) {
-               /* Bump ring index to mark as cleaned. */
-               tx_q->next_to_clean = end;
+               /* Bump ring index to mark as cleaned and sync with
+                * IDPF_DESC_UNUSED called from idpf_txq_has_room.
+                */
+               smp_store_release(&tx_q->next_to_clean, end);
                return;
        }
 
@@ -2111,7 +2113,8 @@ static void idpf_tx_splitq_clean(struct idpf_tx_queue 
*tx_q, u16 end,
                idpf_tx_splitq_clean_bump_ntc(tx_q, ntc, tx_desc, tx_buf);
        }
 
-       tx_q->next_to_clean = ntc;
+       /* Sync with IDPF_DESC_UNUSED called from idpf_txq_has_room. */
+       smp_store_release(&tx_q->next_to_clean, ntc);
 }
 
 /**
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h 
b/drivers/net/ethernet/intel/idpf/idpf_txrx.h
index 4be5b3b6d3ed..8eadc2682c96 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h
+++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h
@@ -119,9 +119,12 @@ do {                                                       
        \
 #define IDPF_RXD_EOF_SPLITQ            VIRTCHNL2_RX_FLEX_DESC_ADV_STATUS0_EOF_M
 #define IDPF_RXD_EOF_SINGLEQ           VIRTCHNL2_RX_BASE_DESC_STATUS_EOF_M
 
-#define IDPF_DESC_UNUSED(txq)     \
-       ((((txq)->next_to_clean > (txq)->next_to_use) ? 0 : (txq)->desc_count) 
+ \
-       (txq)->next_to_clean - (txq)->next_to_use - 1)
+#define IDPF_DESC_UNUSED(txq)                                          \
+({                                                                     \
+       unsigned int ntc = smp_load_acquire(&(txq)->next_to_clean);     \
+       unsigned int ntu = READ_ONCE((txq)->next_to_use);               \
+       (ntc > ntu ? 0 : (txq)->desc_count) + ntc - ntu - 1;            \
+})
 
 #define IDPF_TX_COMPLQ_OVERFLOW_THRESH(txcq)   ((txcq)->desc_count >> 1)
 /* Determine the absolute number of completions pending, i.e. the number of
-- 
2.39.2

Reply via email to