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