* [PATCH net-next 0/3] net: prevent lockless data races in net_device TC structures
@ 2026-08-12 8:54 Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 1/3] net: prevent torn reads in netdev_tc_txq Eric Dumazet
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-08-12 8:54 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet,
syzbot+a181d44496a497911353
This patch series resolves lockless data races between fast-path packet
processing / qdisc schedulers (e.g. taprio advance_sched(), XPS queue
lookups, skb_tx_hash()) and control-path updates modifying traffic class
configurations on a net_device.
syzbot / KCSAN reported a data-race between advance_sched() reading
dev->num_tc in netdev_get_num_tc() and control-path updates writing
dev->num_tc in netdev_set_num_tc():
==================================================================
BUG: KCSAN: data-race in advance_sched / netdev_set_num_tc
write to 0xffff88811ac5c036 of 2 bytes by task 4434 on cpu 0:
netdev_set_num_tc+0x... net/core/dev.c:3158
...
tc_modify_qdisc+0x102a/0x1550 net/sched/sch_api.c:1844
rtnetlink_rcv_msg+0x6a7/0x720 net/core/rtnetlink.c:7085
read to 0xffff88811ac5c036 of 2 bytes by interrupt on cpu 1:
netdev_get_num_tc include/linux/netdevice.h:2684 [inline]
taprio_set_budgets net/sched/sch_taprio.c:667 [inline]
advance_sched+0x58f/0x730 net/sched/sch_taprio.c:984
__run_hrtimer kernel/time/hrtimer.c:2032 [inline]
__hrtimer_run_queues+0x1f8/0x510 kernel/time/hrtimer.c:2096
value changed: 0x0000 -> 0x0001
==================================================================
Further inspection of the TC metadata structures on struct net_device
revealed three separate issues under concurrent lockless access:
1. struct netdev_tc_txq holds adjacent 16-bit offset and count fields
that are written separately in netdev_set_tc_queue() (and cleared
via memset() during reset), allowing lockless readers in fast-path
helpers and drivers to observe torn/inconsistent states. This is fixed
in Patch 1 by wrapping count and offset in a union with a u32
combined field manipulated atomically via READ_ONCE()/WRITE_ONCE().
2. dev->num_tc is read locklessly in fast-path lookups and timer
interrupts without READ_ONCE() annotations, while control paths modify
it using plain writes. Patch 2 adds READ_ONCE()/WRITE_ONCE()
annotations across core networking code and drivers.
3. dev->prio_tc_map is similarly read locklessly in fast-path helpers
such as skb_tx_hash() while control paths update entries or clear the
map via memset(). Patch 3 adds READ_ONCE()/WRITE_ONCE() annotations
to netdev_get_prio_tc_map() and netdev_set_prio_tc_map() and replaces
memset() with explicit atomic store loops.
Reported-by: syzbot+a181d44496a497911353@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/6a7c3457.d5f0ebe7.22d851.000a.GAE@google.com/T/#u
Eric Dumazet (3):
net: prevent torn reads in netdev_tc_txq
net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc
net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map
.../net/ethernet/chelsio/cxgb4/cxgb4_main.c | 2 +-
.../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 12 +--
drivers/net/ethernet/intel/igc/igc_tsn.c | 8 +-
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 7 +-
.../net/ethernet/mellanox/mlx5/core/en_main.c | 4 +-
drivers/net/ethernet/sfc/falcon/net_driver.h | 2 +-
drivers/net/ethernet/sfc/falcon/tx.c | 16 ++--
drivers/net/ethernet/sfc/siena/tx.c | 12 ++-
.../net/ethernet/stmicro/stmmac/stmmac_fpe.c | 14 +++-
drivers/net/ethernet/ti/cpsw_priv.c | 2 +-
include/linux/netdevice.h | 21 +++--
net/core/dev.c | 77 ++++++++++++-------
net/core/net-sysfs.c | 2 +-
net/sched/sch_mqprio.c | 4 +-
net/sched/sch_mqprio_lib.c | 10 ++-
net/sched/sch_taprio.c | 35 +++++----
16 files changed, 143 insertions(+), 85 deletions(-)
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net-next 1/3] net: prevent torn reads in netdev_tc_txq
2026-08-12 8:54 [PATCH net-next 0/3] net: prevent lockless data races in net_device TC structures Eric Dumazet
@ 2026-08-12 8:54 ` Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 2/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 3/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map Eric Dumazet
2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-08-12 8:54 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet
netdev_set_tc_queue() (and related helpers/drivers such as
netdev_bind_sb_channel_queue(), netdev_reset_tc(), and
netdev_unbind_sb_channel()) perform separate 16-bit writes to
dev->tc_to_txq[tc].count and dev->tc_to_txq[tc].offset.
Furthermore, memset() in netdev_reset_tc() and
netdev_unbind_sb_channel() provides no guarantee of performing
full 32-bit word stores.
Concurrent lockless readers (e.g. skb_tx_hash(), netdev_txq_to_tc(),
ixgbe_select_queue(), taprio, mqprio, FPE drivers) can observe torn
values where offset and count belong to inconsistent configurations.
Redefine struct netdev_tc_txq to embed count and offset inside a union
with a u32 combined field, allowing atomic manipulation via
READ_ONCE() and WRITE_ONCE().
Update all lockless readers and writers across the kernel to use
READ_ONCE() and WRITE_ONCE() on the combined field.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
drivers/net/ethernet/intel/igc/igc_tsn.c | 6 ++-
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 7 +--
.../net/ethernet/mellanox/mlx5/core/en_main.c | 2 +-
drivers/net/ethernet/sfc/falcon/tx.c | 8 +++-
drivers/net/ethernet/sfc/siena/tx.c | 8 +++-
.../net/ethernet/stmicro/stmmac/stmmac_fpe.c | 14 ++++--
include/linux/netdevice.h | 9 +++-
net/core/dev.c | 46 +++++++++++++------
net/sched/sch_mqprio.c | 4 +-
net/sched/sch_mqprio_lib.c | 7 ++-
net/sched/sch_taprio.c | 26 ++++++-----
11 files changed, 94 insertions(+), 43 deletions(-)
diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c
index 52de2bcbadbec7a4443e8754eb914fe8b892c71d..0c08650d3bb2d95f63529e9a950d6a00cd7d6eca 100644
--- a/drivers/net/ethernet/intel/igc/igc_tsn.c
+++ b/drivers/net/ethernet/intel/igc/igc_tsn.c
@@ -183,13 +183,15 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter,
u32 i, queue = 0;
for (i = 0; i < dev->num_tc; i++) {
+ struct netdev_tc_txq res;
u32 offset, count;
if (!(preemptible_tcs & BIT(i)))
continue;
- offset = dev->tc_to_txq[i].offset;
- count = dev->tc_to_txq[i].count;
+ res.combined = READ_ONCE(dev->tc_to_txq[i].combined);
+ offset = res.offset;
+ count = res.count;
queue |= GENMASK(offset + count - 1, offset);
}
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 8873a8cc4a1851fc108cbf8d603f8b3e7ecc29a9..f91856498eb2d9e0e4173153c8c00bc2248bb708 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -9273,10 +9273,11 @@ static u16 ixgbe_select_queue(struct net_device *dev, struct sk_buff *skb,
if (sb_dev) {
u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
struct net_device *vdev = sb_dev;
+ struct netdev_tc_txq res;
- txq = vdev->tc_to_txq[tc].offset;
- txq += reciprocal_scale(skb_get_hash(skb),
- vdev->tc_to_txq[tc].count);
+ res.combined = READ_ONCE(vdev->tc_to_txq[tc].combined);
+ txq = res.offset;
+ txq += reciprocal_scale(skb_get_hash(skb), res.count);
return txq;
}
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
index ca3d7c6b5210e4b057ffb0becf6775a8f559247d..8a877891e690d4250ac0e610553d65a686a3ed4d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
@@ -3247,7 +3247,7 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv)
old_num_txqs = netdev->real_num_tx_queues;
old_ntc = netdev->num_tc ? : 1;
for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++)
- old_tc_to_txq[i] = netdev->tc_to_txq[i];
+ old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined);
nch = priv->channels.params.num_channels;
ntc = priv->channels.params.mqprio.num_tc;
diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c
index 9e18aaf44baddeab3e933eb8cad0b07ecb134b16..e4d47d26a87a09ada5c1b85da099ff9147a25fe6 100644
--- a/drivers/net/ethernet/sfc/falcon/tx.c
+++ b/drivers/net/ethernet/sfc/falcon/tx.c
@@ -439,8 +439,12 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
return 0;
for (tc = 0; tc < num_tc; tc++) {
- net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels;
- net_dev->tc_to_txq[tc].count = efx->n_tx_channels;
+ struct netdev_tc_txq res = {
+ .offset = tc * efx->n_tx_channels,
+ .count = efx->n_tx_channels,
+ };
+
+ WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined);
}
if (num_tc > net_dev->num_tc) {
diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c
index 91e87594ed1eac18e8dbec04c9b29161a3ef0b54..1ce98f8fdaf81cffc0ff7d25e4cf3bb1f12acae3 100644
--- a/drivers/net/ethernet/sfc/siena/tx.c
+++ b/drivers/net/ethernet/sfc/siena/tx.c
@@ -380,8 +380,12 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
return 0;
for (tc = 0; tc < num_tc; tc++) {
- net_dev->tc_to_txq[tc].offset = tc * efx->n_tx_channels;
- net_dev->tc_to_txq[tc].count = efx->n_tx_channels;
+ struct netdev_tc_txq res = {
+ .offset = tc * efx->n_tx_channels,
+ .count = efx->n_tx_channels,
+ };
+
+ WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined);
}
net_dev->num_tc = num_tc;
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
index c54c702243517d502c1e14641cf859b35f508681..c889204a7aa5d2e74f7834ed1a22252e6c51ca36 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_fpe.c
@@ -217,8 +217,11 @@ int dwmac5_fpe_map_preemption_class(struct net_device *ndev,
* and is direct one-to-one mapping."
*/
for (u32 tc = 0; tc < num_tc; tc++) {
- count = ndev->tc_to_txq[tc].count;
- offset = ndev->tc_to_txq[tc].offset;
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined);
+ count = res.count;
+ offset = res.offset;
if (pclass & BIT(tc))
preemptible_txqs |= GENMASK(offset + count - 1, offset);
@@ -275,8 +278,11 @@ int dwxgmac3_fpe_map_preemption_class(struct net_device *ndev,
* any of the scheduling algorithms."
*/
for (u32 tc = 0; tc < num_tc; tc++) {
- count = ndev->tc_to_txq[tc].count;
- offset = ndev->tc_to_txq[tc].offset;
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(ndev->tc_to_txq[tc].combined);
+ count = res.count;
+ offset = res.offset;
if (pclass & BIT(tc))
preemptible_txqs |= GENMASK(offset + count - 1, offset);
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index db9dce7f0aa65fbd3056425a8a3a4e26659d43b9..ccb3da375f193251fa177b8dbdb0073e8e906735 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -832,8 +832,13 @@ struct xps_dev_maps {
#define TC_BITMASK 15
/* HW offloaded queuing disciplines txq count and offset maps */
struct netdev_tc_txq {
- u16 count;
- u16 offset;
+ union {
+ struct {
+ u16 count;
+ u16 offset;
+ };
+ u32 combined;
+ };
};
#if defined(CONFIG_FCOE) || defined(CONFIG_FCOE_MODULE)
diff --git a/net/core/dev.c b/net/core/dev.c
index fd0b445f5d38c2a2087318f029c86ffdab38aad4..6fab5f3046f9a64573121796bf4889d8611f49ff 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2656,11 +2656,13 @@ EXPORT_SYMBOL_GPL(dev_queue_xmit_nit);
*/
static void netif_setup_tc(struct net_device *dev, unsigned int txq)
{
+ struct netdev_tc_txq res;
int i;
- struct netdev_tc_txq *tc = &dev->tc_to_txq[0];
+
+ res.combined = READ_ONCE(dev->tc_to_txq[0].combined);
/* If TC0 is invalidated disable TC mapping */
- if (tc->offset + tc->count > txq) {
+ if (res.offset + res.count > txq) {
netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n");
dev->num_tc = 0;
return;
@@ -2670,8 +2672,8 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq)
for (i = 1; i < TC_BITMASK + 1; i++) {
int q = netdev_get_prio_tc_map(dev, i);
- tc = &dev->tc_to_txq[q];
- if (tc->offset + tc->count > txq) {
+ res.combined = READ_ONCE(dev->tc_to_txq[q].combined);
+ if (res.offset + res.count > txq) {
netdev_warn(dev, "Number of in use tx queues changed. Priority %i to tc mapping %i is no longer valid. Setting map to 0\n",
i, q);
netdev_set_prio_tc_map(dev, i, 0);
@@ -2687,7 +2689,10 @@ int netdev_txq_to_tc(struct net_device *dev, unsigned int txq)
/* walk through the TCs and see if it falls into any of them */
for (i = 0; i < TC_MAX_QUEUE; i++, tc++) {
- if ((txq - tc->offset) < tc->count)
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(tc->combined);
+ if ((txq - res.offset) < res.count)
return i;
}
@@ -3107,6 +3112,8 @@ static void netdev_unbind_all_sb_channels(struct net_device *dev)
void netdev_reset_tc(struct net_device *dev)
{
+ int i;
+
#ifdef CONFIG_XPS
netif_reset_xps_queues_gt(dev, 0);
#endif
@@ -3114,21 +3121,26 @@ void netdev_reset_tc(struct net_device *dev)
/* Reset TC configuration of device */
dev->num_tc = 0;
- memset(dev->tc_to_txq, 0, sizeof(dev->tc_to_txq));
+ for (i = 0; i < TC_MAX_QUEUE; i++)
+ WRITE_ONCE(dev->tc_to_txq[i].combined, 0);
memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map));
}
EXPORT_SYMBOL(netdev_reset_tc);
int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset)
{
+ struct netdev_tc_txq res = {
+ .count = count,
+ .offset = offset,
+ };
+
if (tc >= dev->num_tc)
return -EINVAL;
#ifdef CONFIG_XPS
netif_reset_xps_queues(dev, offset, count);
#endif
- dev->tc_to_txq[tc].count = count;
- dev->tc_to_txq[tc].offset = offset;
+ WRITE_ONCE(dev->tc_to_txq[tc].combined, res.combined);
return 0;
}
EXPORT_SYMBOL(netdev_set_tc_queue);
@@ -3152,11 +3164,13 @@ void netdev_unbind_sb_channel(struct net_device *dev,
struct net_device *sb_dev)
{
struct netdev_queue *txq = &dev->_tx[dev->num_tx_queues];
+ int i;
#ifdef CONFIG_XPS
netif_reset_xps_queues_gt(sb_dev, 0);
#endif
- memset(sb_dev->tc_to_txq, 0, sizeof(sb_dev->tc_to_txq));
+ for (i = 0; i < TC_MAX_QUEUE; i++)
+ WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0);
memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map));
while (txq-- != &dev->_tx[0]) {
@@ -3179,8 +3193,12 @@ int netdev_bind_sb_channel_queue(struct net_device *dev,
return -EINVAL;
/* Record the mapping */
- sb_dev->tc_to_txq[tc].count = count;
- sb_dev->tc_to_txq[tc].offset = offset;
+ struct netdev_tc_txq res = {
+ .count = count,
+ .offset = offset,
+ };
+
+ WRITE_ONCE(sb_dev->tc_to_txq[tc].combined, res.combined);
/* Provide a way for Tx queue to find the tc_to_txq map or
* XPS map for itself.
@@ -3546,9 +3564,11 @@ static u16 skb_tx_hash(const struct net_device *dev,
if (dev->num_tc) {
u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
+ struct netdev_tc_txq res;
- qoffset = sb_dev->tc_to_txq[tc].offset;
- qcount = sb_dev->tc_to_txq[tc].count;
+ res.combined = READ_ONCE(sb_dev->tc_to_txq[tc].combined);
+ qoffset = res.offset;
+ qcount = res.count;
if (unlikely(!qcount)) {
net_warn_ratelimited("%s: invalid qcount, qoffset %u for tc %u\n",
sb_dev->name, qoffset, tc);
diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c
index ae991fc25b43f24efb3b53fce6f6b53718a9b774..6ced7008ef5c8413c76c3122c9d1d4b824702e99 100644
--- a/net/sched/sch_mqprio.c
+++ b/net/sched/sch_mqprio.c
@@ -679,12 +679,14 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl,
rcu_read_lock();
if (cl >= TC_H_MIN_PRIORITY) {
struct net_device *dev = qdisc_dev(sch);
- struct netdev_tc_txq tc = dev->tc_to_txq[cl & TC_BITMASK];
+ struct netdev_tc_txq tc;
struct gnet_stats_queue qstats = {0};
struct gnet_stats_basic_sync bstats;
u32 qlen = 0;
int i;
+ tc.combined = READ_ONCE(dev->tc_to_txq[cl & TC_BITMASK].combined);
+
gnet_stats_basic_sync_init(&bstats);
for (i = tc.offset; i < tc.offset + tc.count; i++) {
diff --git a/net/sched/sch_mqprio_lib.c b/net/sched/sch_mqprio_lib.c
index b3a5572c167b719f96e2947fa4267f1f93077814..b60e130c70781479eed0ea5a02d01197f03f4895 100644
--- a/net/sched/sch_mqprio_lib.c
+++ b/net/sched/sch_mqprio_lib.c
@@ -108,8 +108,11 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt
memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map));
for (tc = 0; tc < num_tc; tc++) {
- qopt->count[tc] = dev->tc_to_txq[tc].count;
- qopt->offset[tc] = dev->tc_to_txq[tc].offset;
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(dev->tc_to_txq[tc].combined);
+ qopt->count[tc] = res.count;
+ qopt->offset[tc] = res.offset;
}
}
EXPORT_SYMBOL_GPL(mqprio_qopt_reconstruct);
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 299234a5f0fe60589ef9594a8e685bb8cf751e2d..7d5fe93a4c12434cc1c35594ea6de0e88ac67260 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -762,12 +762,13 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq,
static void taprio_next_tc_txq(struct net_device *dev, int tc, int *txq)
{
- int offset = dev->tc_to_txq[tc].offset;
- int count = dev->tc_to_txq[tc].count;
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(dev->tc_to_txq[tc].combined);
(*txq)++;
- if (*txq == offset + count)
- *txq = offset;
+ if (*txq == res.offset + res.count)
+ *txq = res.offset;
}
/* Prioritize higher traffic classes, and select among TXQs belonging to the
@@ -1441,15 +1442,14 @@ static u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask)
u32 i, queue_mask = 0;
for (i = 0; i < dev->num_tc; i++) {
- u32 offset, count;
+ struct netdev_tc_txq res;
if (!(tc_mask & BIT(i)))
continue;
- offset = dev->tc_to_txq[i].offset;
- count = dev->tc_to_txq[i].count;
+ res.combined = READ_ONCE(dev->tc_to_txq[i].combined);
- queue_mask |= GENMASK(offset + count - 1, offset);
+ queue_mask |= GENMASK(res.offset + res.count - 1, res.offset);
}
return queue_mask;
@@ -1802,10 +1802,14 @@ static int taprio_mqprio_cmp(const struct net_device *dev,
if (!mqprio || mqprio->num_tc != dev->num_tc)
return -1;
- for (i = 0; i < mqprio->num_tc; i++)
- if (dev->tc_to_txq[i].count != mqprio->count[i] ||
- dev->tc_to_txq[i].offset != mqprio->offset[i])
+ for (i = 0; i < mqprio->num_tc; i++) {
+ struct netdev_tc_txq res;
+
+ res.combined = READ_ONCE(dev->tc_to_txq[i].combined);
+ if (res.count != mqprio->count[i] ||
+ res.offset != mqprio->offset[i])
return -1;
+ }
for (i = 0; i <= TC_BITMASK; i++)
if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i])
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net-next 2/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc
2026-08-12 8:54 [PATCH net-next 0/3] net: prevent lockless data races in net_device TC structures Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 1/3] net: prevent torn reads in netdev_tc_txq Eric Dumazet
@ 2026-08-12 8:54 ` Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 3/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map Eric Dumazet
2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-08-12 8:54 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet
Several fast-path and control-path lockless readers access dev->num_tc
(e.g., skb_tx_hash(), netdev_txq_to_tc(), netdev_get_num_tc(), and
qdisc/driver lookups) while concurrent writers update dev->num_tc
during TC setup, device reset, or channel configuration.
Add READ_ONCE() and WRITE_ONCE() annotations to prevent compiler
reordering and load/store tearing when accessing dev->num_tc.
Update inline helpers in netdevice.h (netdev_get_num_tc(),
netdev_set_prio_tc_map(), and netdev_get_sb_channel()) as well as
writers and lockless readers in core networking code and drivers.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
.../net/ethernet/chelsio/cxgb4/cxgb4_main.c | 2 +-
.../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 12 +++++----
drivers/net/ethernet/intel/igc/igc_tsn.c | 2 +-
.../net/ethernet/mellanox/mlx5/core/en_main.c | 2 +-
drivers/net/ethernet/sfc/falcon/net_driver.h | 2 +-
drivers/net/ethernet/sfc/falcon/tx.c | 8 +++---
drivers/net/ethernet/sfc/siena/tx.c | 4 +--
drivers/net/ethernet/ti/cpsw_priv.c | 2 +-
include/linux/netdevice.h | 8 +++---
net/core/dev.c | 25 ++++++++++---------
net/core/net-sysfs.c | 2 +-
net/sched/sch_taprio.c | 7 +++---
12 files changed, 40 insertions(+), 36 deletions(-)
diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c
index 9e2c2fa16d7a50ed91667a9e8173262317e0184b..1ced6df6eac8c219220e5834476921739307be0a 100644
--- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c
+++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c
@@ -1163,7 +1163,7 @@ static u16 cxgb_select_queue(struct net_device *dev, struct sk_buff *skb,
}
#endif /* CONFIG_CHELSIO_T4_DCB */
- if (dev->num_tc) {
+ if (netdev_get_num_tc(dev)) {
struct port_info *pi = netdev2pinfo(dev);
u8 ver, proto;
diff --git a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
index 764d2a09668f566c937979752ccde8b1eaee6248..6f1046c9cc5157f51c4f2c6d0245591c7cdc788f 100644
--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
+++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
@@ -1403,10 +1403,10 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb,
struct dpaa2_eth_fq *fq;
struct netdev_queue *nq;
struct dpaa2_fd *fd;
+ int err, i, num_tc;
u16 queue_mapping;
void *swa = NULL;
u8 prio = 0;
- int err, i;
u32 fd_len;
percpu_stats = this_cpu_ptr(priv->percpu_stats);
@@ -1468,12 +1468,14 @@ static netdev_tx_t __dpaa2_eth_tx(struct sk_buff *skb,
*/
queue_mapping = skb_get_queue_mapping(skb);
- if (net_dev->num_tc) {
+ num_tc = netdev_get_num_tc(net_dev);
+
+ if (num_tc) {
prio = netdev_txq_to_tc(net_dev, queue_mapping);
/* Hardware interprets priority level 0 as being the highest,
* so we need to do a reverse mapping to the netdev tc index
*/
- prio = net_dev->num_tc - prio - 1;
+ prio = num_tc - prio - 1;
/* We have only one FQ array entry for all Tx hardware queues
* with the same flow id (but different priority levels)
*/
@@ -2913,7 +2915,7 @@ static int update_xps(struct dpaa2_eth_priv *priv)
return -ENOMEM;
num_queues = dpaa2_eth_queue_count(priv);
- netdev_queues = (net_dev->num_tc ? : 1) * num_queues;
+ netdev_queues = (netdev_get_num_tc(net_dev) ? : 1) * num_queues;
/* The first <num_queues> entries in priv->fq array are Tx/Tx conf
* queues, so only process those
@@ -2946,7 +2948,7 @@ static int dpaa2_eth_setup_mqprio(struct net_device *net_dev,
num_queues = dpaa2_eth_queue_count(priv);
num_tc = mqprio->num_tc;
- if (num_tc == net_dev->num_tc)
+ if (num_tc == netdev_get_num_tc(net_dev))
return 0;
if (num_tc > dpaa2_eth_tc_count(priv)) {
diff --git a/drivers/net/ethernet/intel/igc/igc_tsn.c b/drivers/net/ethernet/intel/igc/igc_tsn.c
index 0c08650d3bb2d95f63529e9a950d6a00cd7d6eca..d23a45a34fa3c9d95af1639e44155df28038bf46 100644
--- a/drivers/net/ethernet/intel/igc/igc_tsn.c
+++ b/drivers/net/ethernet/intel/igc/igc_tsn.c
@@ -182,7 +182,7 @@ static u32 igc_fpe_map_preempt_tc_to_queue(const struct igc_adapter *adapter,
struct net_device *dev = adapter->netdev;
u32 i, queue = 0;
- for (i = 0; i < dev->num_tc; i++) {
+ for (i = 0; i < netdev_get_num_tc(dev); i++) {
struct netdev_tc_txq res;
u32 offset, count;
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
index 8a877891e690d4250ac0e610553d65a686a3ed4d..fc110a7d16e8da994a0847dab868bf1da402d64e 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c
@@ -3245,7 +3245,7 @@ static int mlx5e_update_tc_and_tx_queues(struct mlx5e_priv *priv)
int i;
old_num_txqs = netdev->real_num_tx_queues;
- old_ntc = netdev->num_tc ? : 1;
+ old_ntc = netdev_get_num_tc(netdev) ? : 1;
for (i = 0; i < ARRAY_SIZE(old_tc_to_txq); i++)
old_tc_to_txq[i].combined = READ_ONCE(netdev->tc_to_txq[i].combined);
diff --git a/drivers/net/ethernet/sfc/falcon/net_driver.h b/drivers/net/ethernet/sfc/falcon/net_driver.h
index 7ab0db44720da7c4cd8e8190ad100698ad32ccee..63016bbae115b9d141e540c779528243c2fec2e2 100644
--- a/drivers/net/ethernet/sfc/falcon/net_driver.h
+++ b/drivers/net/ethernet/sfc/falcon/net_driver.h
@@ -1208,7 +1208,7 @@ ef4_channel_get_tx_queue(struct ef4_channel *channel, unsigned type)
static inline bool ef4_tx_queue_used(struct ef4_tx_queue *tx_queue)
{
- return !(tx_queue->efx->net_dev->num_tc < 2 &&
+ return !(netdev_get_num_tc(tx_queue->efx->net_dev) < 2 &&
tx_queue->queue & EF4_TXQ_TYPE_HIGHPRI);
}
diff --git a/drivers/net/ethernet/sfc/falcon/tx.c b/drivers/net/ethernet/sfc/falcon/tx.c
index e4d47d26a87a09ada5c1b85da099ff9147a25fe6..2103b6fdf9683daca84246bcb9d218827029fccb 100644
--- a/drivers/net/ethernet/sfc/falcon/tx.c
+++ b/drivers/net/ethernet/sfc/falcon/tx.c
@@ -435,7 +435,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS;
- if (num_tc == net_dev->num_tc)
+ if (num_tc == netdev_get_num_tc(net_dev))
return 0;
for (tc = 0; tc < num_tc; tc++) {
@@ -447,7 +447,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined);
}
- if (num_tc > net_dev->num_tc) {
+ if (num_tc > netdev_get_num_tc(net_dev)) {
/* Initialise high-priority queues as necessary */
ef4_for_each_channel(channel, efx) {
ef4_for_each_possible_channel_tx_queue(tx_queue,
@@ -466,7 +466,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
}
} else {
/* Reduce number of classes before number of queues */
- net_dev->num_tc = num_tc;
+ WRITE_ONCE(net_dev->num_tc, num_tc);
}
rc = netif_set_real_num_tx_queues(net_dev,
@@ -481,7 +481,7 @@ int ef4_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
* it to ef4_fini_channels().
*/
- net_dev->num_tc = num_tc;
+ WRITE_ONCE(net_dev->num_tc, num_tc);
return 0;
}
diff --git a/drivers/net/ethernet/sfc/siena/tx.c b/drivers/net/ethernet/sfc/siena/tx.c
index 1ce98f8fdaf81cffc0ff7d25e4cf3bb1f12acae3..67c77d67d98439f54bc5c1020c77461b6b97570f 100644
--- a/drivers/net/ethernet/sfc/siena/tx.c
+++ b/drivers/net/ethernet/sfc/siena/tx.c
@@ -376,7 +376,7 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
mqprio->hw = TC_MQPRIO_HW_OFFLOAD_TCS;
- if (num_tc == net_dev->num_tc)
+ if (num_tc == netdev_get_num_tc(net_dev))
return 0;
for (tc = 0; tc < num_tc; tc++) {
@@ -388,7 +388,7 @@ int efx_siena_setup_tc(struct net_device *net_dev, enum tc_setup_type type,
WRITE_ONCE(net_dev->tc_to_txq[tc].combined, res.combined);
}
- net_dev->num_tc = num_tc;
+ WRITE_ONCE(net_dev->num_tc, num_tc);
return netif_set_real_num_tx_queues(net_dev,
max_t(int, num_tc, 1) *
diff --git a/drivers/net/ethernet/ti/cpsw_priv.c b/drivers/net/ethernet/ti/cpsw_priv.c
index 1f6f374551cb6e1db589d7bc235446429f13c095..0580a7885d3394cbd3e50d6669f3a4d1f0a361c4 100644
--- a/drivers/net/ethernet/ti/cpsw_priv.c
+++ b/drivers/net/ethernet/ti/cpsw_priv.c
@@ -949,7 +949,7 @@ static int cpsw_set_cbs(struct net_device *ndev,
* limited first and for compliance with CPDMA rate limited channels
* that also used in bacward order. FIFO0 cannot be rate limited.
*/
- fifo = cpsw_tc_to_fifo(tc, ndev->num_tc);
+ fifo = cpsw_tc_to_fifo(tc, netdev_get_num_tc(ndev));
if (!fifo) {
dev_err(priv->dev, "Last tc%d can't be rate limited", tc);
return -EINVAL;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ccb3da375f193251fa177b8dbdb0073e8e906735..db0002bd68f90fa4dd087edabc11549bc8ee0f66 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2677,7 +2677,7 @@ int netdev_get_prio_tc_map(const struct net_device *dev, u32 prio)
static inline
int netdev_set_prio_tc_map(struct net_device *dev, u8 prio, u8 tc)
{
- if (tc >= dev->num_tc)
+ if (tc >= READ_ONCE(dev->num_tc))
return -EINVAL;
dev->prio_tc_map[prio & TC_BITMASK] = tc & TC_BITMASK;
@@ -2690,9 +2690,9 @@ int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset);
int netdev_set_num_tc(struct net_device *dev, u8 num_tc);
static inline
-int netdev_get_num_tc(struct net_device *dev)
+int netdev_get_num_tc(const struct net_device *dev)
{
- return dev->num_tc;
+ return READ_ONCE(dev->num_tc);
}
static inline void net_prefetch(void *p)
@@ -2719,7 +2719,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev,
int netdev_set_sb_channel(struct net_device *dev, u16 channel);
static inline int netdev_get_sb_channel(struct net_device *dev)
{
- return max_t(int, -dev->num_tc, 0);
+ return max_t(int, -READ_ONCE(dev->num_tc), 0);
}
static inline
diff --git a/net/core/dev.c b/net/core/dev.c
index 6fab5f3046f9a64573121796bf4889d8611f49ff..8ffae11d272e979bab5b64f4d912d3d97f738d48 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -2664,7 +2664,7 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq)
/* If TC0 is invalidated disable TC mapping */
if (res.offset + res.count > txq) {
netdev_warn(dev, "Number of in use tx queues changed invalidating tc mappings. Priority traffic classification disabled!\n");
- dev->num_tc = 0;
+ WRITE_ONCE(dev->num_tc, 0);
return;
}
@@ -2683,7 +2683,7 @@ static void netif_setup_tc(struct net_device *dev, unsigned int txq)
int netdev_txq_to_tc(struct net_device *dev, unsigned int txq)
{
- if (dev->num_tc) {
+ if (READ_ONCE(dev->num_tc)) {
struct netdev_tc_txq *tc = &dev->tc_to_txq[0];
int i;
@@ -2885,18 +2885,19 @@ int __netif_set_xps_queue(struct net_device *dev, const unsigned long *mask,
u16 index, enum xps_map_type type)
{
struct xps_dev_maps *dev_maps, *new_dev_maps = NULL, *old_dev_maps = NULL;
+ int maps_sz, num_tc = 1, tc = 0, dev_num_tc;
const unsigned long *online_mask = NULL;
bool active = false, copy = false;
int i, j, tci, numa_node_id = -2;
- int maps_sz, num_tc = 1, tc = 0;
struct xps_map *map, *new_map;
unsigned int nr_ids;
WARN_ON_ONCE(index >= dev->num_tx_queues);
- if (dev->num_tc) {
+ dev_num_tc = READ_ONCE(dev->num_tc);
+ if (dev_num_tc) {
/* Do not allow XPS on subordinate device directly */
- num_tc = dev->num_tc;
+ num_tc = dev_num_tc;
if (num_tc < 0)
return -EINVAL;
@@ -3120,7 +3121,7 @@ void netdev_reset_tc(struct net_device *dev)
netdev_unbind_all_sb_channels(dev);
/* Reset TC configuration of device */
- dev->num_tc = 0;
+ WRITE_ONCE(dev->num_tc, 0);
for (i = 0; i < TC_MAX_QUEUE; i++)
WRITE_ONCE(dev->tc_to_txq[i].combined, 0);
memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map));
@@ -3134,7 +3135,7 @@ int netdev_set_tc_queue(struct net_device *dev, u8 tc, u16 count, u16 offset)
.offset = offset,
};
- if (tc >= dev->num_tc)
+ if (tc >= READ_ONCE(dev->num_tc))
return -EINVAL;
#ifdef CONFIG_XPS
@@ -3155,7 +3156,7 @@ int netdev_set_num_tc(struct net_device *dev, u8 num_tc)
#endif
netdev_unbind_all_sb_channels(dev);
- dev->num_tc = num_tc;
+ WRITE_ONCE(dev->num_tc, num_tc);
return 0;
}
EXPORT_SYMBOL(netdev_set_num_tc);
@@ -3185,7 +3186,7 @@ int netdev_bind_sb_channel_queue(struct net_device *dev,
u8 tc, u16 count, u16 offset)
{
/* Make certain the sb_dev and dev are already configured */
- if (sb_dev->num_tc >= 0 || tc >= dev->num_tc)
+ if (READ_ONCE(sb_dev->num_tc) >= 0 || tc >= READ_ONCE(dev->num_tc))
return -EINVAL;
/* We cannot hand out queues we don't have */
@@ -3224,7 +3225,7 @@ int netdev_set_sb_channel(struct net_device *dev, u16 channel)
if (channel > S16_MAX)
return -EINVAL;
- dev->num_tc = -channel;
+ WRITE_ONCE(dev->num_tc, -channel);
return 0;
}
@@ -3253,7 +3254,7 @@ int netif_set_real_num_tx_queues(struct net_device *dev, unsigned int txq)
if (rc)
return rc;
- if (dev->num_tc)
+ if (READ_ONCE(dev->num_tc))
netif_setup_tc(dev, txq);
net_shaper_set_real_num_tx_queues(dev, txq);
@@ -3562,7 +3563,7 @@ static u16 skb_tx_hash(const struct net_device *dev,
u16 qoffset = 0;
u16 qcount = dev->real_num_tx_queues;
- if (dev->num_tc) {
+ if (READ_ONCE(dev->num_tc)) {
u8 tc = netdev_get_prio_tc_map(dev, skb->priority);
struct netdev_tc_txq res;
diff --git a/net/core/net-sysfs.c b/net/core/net-sysfs.c
index 25546deacec8024eb6b05c4a926c1c4c64ccb266..352173df757850af75641501dfa1a91585515a5e 100644
--- a/net/core/net-sysfs.c
+++ b/net/core/net-sysfs.c
@@ -1432,7 +1432,7 @@ static ssize_t traffic_class_show(struct kobject *kobj, struct attribute *attr,
/* If queue belongs to subordinate dev use its TC mapping */
dev = netdev_get_tx_queue(dev, index)->sb_dev ? : dev;
- num_tc = dev->num_tc;
+ num_tc = READ_ONCE(dev->num_tc);
tc = netdev_txq_to_tc(dev, index);
rtnl_unlock();
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 7d5fe93a4c12434cc1c35594ea6de0e88ac67260..18fcb4e78456a2f74fa5d3465175d100a39575b4 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -1185,7 +1185,7 @@ static int taprio_parse_mqprio_opt(struct net_device *dev,
bool allow_overlapping_txqs = TXTIME_ASSIST_IS_ENABLED(taprio_flags);
if (!qopt) {
- if (!dev->num_tc) {
+ if (!netdev_get_num_tc(dev)) {
NL_SET_ERR_MSG(extack, "'mqprio' configuration is necessary");
return -EINVAL;
}
@@ -1439,9 +1439,10 @@ static void taprio_offload_config_changed(struct taprio_sched *q)
static u32 tc_map_to_queue_mask(struct net_device *dev, u32 tc_mask)
{
+ int num_tc = netdev_get_num_tc(dev);
u32 i, queue_mask = 0;
- for (i = 0; i < dev->num_tc; i++) {
+ for (i = 0; i < num_tc; i++) {
struct netdev_tc_txq res;
if (!(tc_mask & BIT(i)))
@@ -1799,7 +1800,7 @@ static int taprio_mqprio_cmp(const struct net_device *dev,
{
int i;
- if (!mqprio || mqprio->num_tc != dev->num_tc)
+ if (!mqprio || mqprio->num_tc != netdev_get_num_tc(dev))
return -1;
for (i = 0; i < mqprio->num_tc; i++) {
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net-next 3/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map
2026-08-12 8:54 [PATCH net-next 0/3] net: prevent lockless data races in net_device TC structures Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 1/3] net: prevent torn reads in netdev_tc_txq Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 2/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc Eric Dumazet
@ 2026-08-12 8:54 ` Eric Dumazet
2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-08-12 8:54 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, eric.dumazet, Eric Dumazet
Concurrent fast-path readers access dev->prio_tc_map (e.g. via
skb_tx_hash(), netdev_get_prio_tc_map(), and qdiscs) while writers
update entries in dev->prio_tc_map or reset/clear the map via
netdev_reset_tc() and netdev_unbind_sb_channel().
Furthermore, memset() in netdev_reset_tc() and
netdev_unbind_sb_channel() provides no guarantee of performing
atomic word/byte stores.
Add READ_ONCE() and WRITE_ONCE() annotations to netdev_get_prio_tc_map()
and netdev_set_prio_tc_map(), replace memset() in dev.c with explicit
WRITE_ONCE() loops, and update direct array accesses in qdiscs to use
netdev_get_prio_tc_map().
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/linux/netdevice.h | 4 ++--
net/core/dev.c | 6 ++++--
net/sched/sch_mqprio_lib.c | 3 ++-
net/sched/sch_taprio.c | 2 +-
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index db0002bd68f90fa4dd087edabc11549bc8ee0f66..de307c01d33eb8835bdbe32118c2fc086e53a51c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2671,7 +2671,7 @@ static inline bool netif_elide_gro(const struct net_device *dev)
static inline
int netdev_get_prio_tc_map(const struct net_device *dev, u32 prio)
{
- return dev->prio_tc_map[prio & TC_BITMASK];
+ return READ_ONCE(dev->prio_tc_map[prio & TC_BITMASK]);
}
static inline
@@ -2680,7 +2680,7 @@ int netdev_set_prio_tc_map(struct net_device *dev, u8 prio, u8 tc)
if (tc >= READ_ONCE(dev->num_tc))
return -EINVAL;
- dev->prio_tc_map[prio & TC_BITMASK] = tc & TC_BITMASK;
+ WRITE_ONCE(dev->prio_tc_map[prio & TC_BITMASK], tc & TC_BITMASK);
return 0;
}
diff --git a/net/core/dev.c b/net/core/dev.c
index 8ffae11d272e979bab5b64f4d912d3d97f738d48..d52285ac01a54fdafa63703f1af134e19d028ffd 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -3124,7 +3124,8 @@ void netdev_reset_tc(struct net_device *dev)
WRITE_ONCE(dev->num_tc, 0);
for (i = 0; i < TC_MAX_QUEUE; i++)
WRITE_ONCE(dev->tc_to_txq[i].combined, 0);
- memset(dev->prio_tc_map, 0, sizeof(dev->prio_tc_map));
+ for (i = 0; i <= TC_BITMASK; i++)
+ WRITE_ONCE(dev->prio_tc_map[i], 0);
}
EXPORT_SYMBOL(netdev_reset_tc);
@@ -3172,7 +3173,8 @@ void netdev_unbind_sb_channel(struct net_device *dev,
#endif
for (i = 0; i < TC_MAX_QUEUE; i++)
WRITE_ONCE(sb_dev->tc_to_txq[i].combined, 0);
- memset(sb_dev->prio_tc_map, 0, sizeof(sb_dev->prio_tc_map));
+ for (i = 0; i <= TC_BITMASK; i++)
+ WRITE_ONCE(sb_dev->prio_tc_map[i], 0);
while (txq-- != &dev->_tx[0]) {
if (txq->sb_dev == sb_dev)
diff --git a/net/sched/sch_mqprio_lib.c b/net/sched/sch_mqprio_lib.c
index b60e130c70781479eed0ea5a02d01197f03f4895..888935e34d4381f85aaf4c97cf9c4bacb93e13f1 100644
--- a/net/sched/sch_mqprio_lib.c
+++ b/net/sched/sch_mqprio_lib.c
@@ -105,7 +105,8 @@ void mqprio_qopt_reconstruct(struct net_device *dev, struct tc_mqprio_qopt *qopt
int tc, num_tc = netdev_get_num_tc(dev);
qopt->num_tc = num_tc;
- memcpy(qopt->prio_tc_map, dev->prio_tc_map, sizeof(qopt->prio_tc_map));
+ for (tc = 0; tc <= TC_BITMASK; tc++)
+ qopt->prio_tc_map[tc] = netdev_get_prio_tc_map(dev, tc);
for (tc = 0; tc < num_tc; tc++) {
struct netdev_tc_txq res;
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 18fcb4e78456a2f74fa5d3465175d100a39575b4..39ac5b97aa3af83fe63ab5ff9173c39700f6bbb0 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -1813,7 +1813,7 @@ static int taprio_mqprio_cmp(const struct net_device *dev,
}
for (i = 0; i <= TC_BITMASK; i++)
- if (dev->prio_tc_map[i] != mqprio->prio_tc_map[i])
+ if (netdev_get_prio_tc_map(dev, i) != mqprio->prio_tc_map[i])
return -1;
return 0;
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-08-12 8:54 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 8:54 [PATCH net-next 0/3] net: prevent lockless data races in net_device TC structures Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 1/3] net: prevent torn reads in netdev_tc_txq Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 2/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->num_tc Eric Dumazet
2026-08-12 8:54 ` [PATCH net-next 3/3] net: add READ_ONCE()/WRITE_ONCE() annotations for dev->prio_tc_map Eric Dumazet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox