[PATCH net-next v4 1/2] net: stmmac: fix TX coalesce race and div-by-zero in XDP/legacy path
Lorenzo Bianconi
lorenzo.bianconi at oss.qualcomm.com
Thu Oct 1 02:26:12 PDT 2026
tx_coal_frames[] is written by ethtool (stmmac_set_coalesce) and read
locklessly from the TX fast path (stmmac_xmit, stmmac_tso_xmit,
stmmac_xdp_xmit_zc and stmmac_xdp_xmit_xdpf), so pair every access
with READ_ONCE()/WRITE_ONCE() to mark the intentional data race and
read a consistent value into a local variable.
Moreover, ethtool allows disabling TX coalescing by setting
tx_max_coalesced_frames to 0: guard the modulo operation in
stmmac_xdp_xmit_xdpf() against a zero tx_coal_frames[queue] to avoid
a division by zero.
Both issues were found by code inspection while extending the XDP TX
path with multi-buff support, and neither has ever been triggered, so
they are only theoretical. Tested on the Qualcomm RB3-gen2 board.
Fixes: db2f2842e6f5 ("net: stmmac: add per-queue TX & RX coalesce ethtool support")
Fixes: be8b38a722e6 ("net: stmmac: Add support for XDP_TX action")
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi at oss.qualcomm.com>
---
.../net/ethernet/stmicro/stmmac/stmmac_ethtool.c | 11 +++---
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 42 ++++++++++------------
2 files changed, 24 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
index 1cf0f8820b33..30e0d499326d 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_ethtool.c
@@ -798,7 +798,8 @@ static int __stmmac_get_coalesce(struct net_device *dev,
if (queue < tx_cnt) {
ec->tx_coalesce_usecs = priv->tx_coal_timer[queue];
- ec->tx_max_coalesced_frames = priv->tx_coal_frames[queue];
+ ec->tx_max_coalesced_frames =
+ READ_ONCE(priv->tx_coal_frames[queue]);
} else {
ec->tx_coalesce_usecs = 0;
ec->tx_max_coalesced_frames = 0;
@@ -887,14 +888,14 @@ static int __stmmac_set_coalesce(struct net_device *dev,
int i;
for (i = 0; i < tx_cnt; i++) {
- priv->tx_coal_frames[i] =
- ec->tx_max_coalesced_frames;
+ WRITE_ONCE(priv->tx_coal_frames[i],
+ ec->tx_max_coalesced_frames);
priv->tx_coal_timer[i] =
ec->tx_coalesce_usecs;
}
} else if (queue < tx_cnt) {
- priv->tx_coal_frames[queue] =
- ec->tx_max_coalesced_frames;
+ WRITE_ONCE(priv->tx_coal_frames[queue],
+ ec->tx_max_coalesced_frames);
priv->tx_coal_timer[queue] =
ec->tx_coalesce_usecs;
}
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
index d95482b7f6e5..df6a329fdb80 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c
@@ -2731,7 +2731,8 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
struct stmmac_metadata_request meta_req;
struct xsk_tx_metadata *meta = NULL;
dma_addr_t dma_addr;
- bool set_ic;
+ bool set_ic = false;
+ u32 tx_coal;
/* We are sharing with slow path and stop XSK TX desc submission when
* available TX ring is less than threshold.
@@ -2772,12 +2773,9 @@ static bool stmmac_xdp_xmit_zc(struct stmmac_priv *priv, u32 queue, u32 budget)
tx_q->tx_count_frames++;
- if (!priv->tx_coal_frames[queue])
- set_ic = false;
- else if (tx_q->tx_count_frames % priv->tx_coal_frames[queue] == 0)
+ tx_coal = READ_ONCE(priv->tx_coal_frames[queue]);
+ if (tx_coal && !(tx_q->tx_count_frames % tx_coal))
set_ic = true;
- else
- set_ic = false;
meta_req.priv = priv;
meta_req.tx_desc = tx_desc;
@@ -3419,7 +3417,7 @@ static void stmmac_init_coalesce(struct stmmac_priv *priv)
for (chan = 0; chan < tx_channel_count; chan++) {
struct stmmac_tx_queue *tx_q = &priv->dma_conf.tx_queue[chan];
- priv->tx_coal_frames[chan] = STMMAC_TX_FRAMES;
+ WRITE_ONCE(priv->tx_coal_frames[chan], STMMAC_TX_FRAMES);
priv->tx_coal_timer[chan] = STMMAC_COAL_TX_TIMER;
hrtimer_setup(&tx_q->txtimer, stmmac_tx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
@@ -4557,10 +4555,10 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
struct dma_desc *desc, *first, *mss_desc = NULL;
struct stmmac_priv *priv = netdev_priv(dev);
struct stmmac_txq_stats *txq_stats;
+ u32 tx_coal, pay_len, mss, queue;
int i, first_tx, nfrags, ndesc;
struct stmmac_tx_queue *tx_q;
bool set_ic, is_last_segment;
- u32 pay_len, mss, queue;
dma_addr_t des;
u8 hdr;
@@ -4679,16 +4677,16 @@ static netdev_tx_t stmmac_tso_xmit(struct sk_buff *skb, struct net_device *dev)
priv->dma_conf.dma_tx_size);
tx_q->tx_count_frames += tx_packets;
+ tx_coal = READ_ONCE(priv->tx_coal_frames[queue]);
if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
set_ic = true;
- else if (!priv->tx_coal_frames[queue])
+ else if (!tx_coal)
set_ic = false;
else if (!netdev_xmit_more())
set_ic = true;
- else if (tx_packets > priv->tx_coal_frames[queue])
+ else if (tx_packets > tx_coal)
set_ic = true;
- else if ((tx_q->tx_count_frames %
- priv->tx_coal_frames[queue]) < tx_packets)
+ else if ((tx_q->tx_count_frames % tx_coal) < tx_packets)
set_ic = true;
else
set_ic = false;
@@ -4843,9 +4841,9 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
struct dma_desc *desc, *first_desc;
struct stmmac_tx_queue *tx_q;
int i, csum_insertion = 0;
+ u32 tx_coal, sdu_len;
int entry, first_tx;
dma_addr_t dma_addr;
- u32 sdu_len;
if (priv->tx_path_in_lpi_mode && priv->eee_sw_timer_en)
stmmac_stop_sw_lpi(priv);
@@ -4986,16 +4984,16 @@ static netdev_tx_t stmmac_xmit(struct sk_buff *skb, struct net_device *dev)
tx_packets = CIRC_CNT(entry + 1, first_tx, priv->dma_conf.dma_tx_size);
tx_q->tx_count_frames += tx_packets;
+ tx_coal = READ_ONCE(priv->tx_coal_frames[queue]);
if ((skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) && priv->hwts_tx_en)
set_ic = true;
- else if (!priv->tx_coal_frames[queue])
+ else if (!tx_coal)
set_ic = false;
else if (!netdev_xmit_more())
set_ic = true;
- else if (tx_packets > priv->tx_coal_frames[queue])
+ else if (tx_packets > tx_coal)
set_ic = true;
- else if ((tx_q->tx_count_frames %
- priv->tx_coal_frames[queue]) < tx_packets)
+ else if ((tx_q->tx_count_frames % tx_coal) < tx_packets)
set_ic = true;
else
set_ic = false;
@@ -5258,7 +5256,7 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
enum stmmac_txbuf_type buf_type;
struct dma_desc *tx_desc;
dma_addr_t dma_addr;
- bool set_ic;
+ u32 tx_coal;
if (stmmac_tx_avail(priv, queue) < STMMAC_TX_THRESH(priv))
return STMMAC_XDP_CONSUMED;
@@ -5303,12 +5301,8 @@ static int stmmac_xdp_xmit_xdpf(struct stmmac_priv *priv, int queue,
tx_q->tx_count_frames++;
- if (tx_q->tx_count_frames % priv->tx_coal_frames[queue] == 0)
- set_ic = true;
- else
- set_ic = false;
-
- if (set_ic) {
+ tx_coal = READ_ONCE(priv->tx_coal_frames[queue]);
+ if (tx_coal && !(tx_q->tx_count_frames % tx_coal)) {
tx_q->tx_count_frames = 0;
stmmac_set_tx_ic(priv, tx_desc);
u64_stats_update_begin(&txq_stats->q_syncp);
--
2.55.0
More information about the linux-arm-kernel
mailing list