[PATCH net 1/2] netpoll: use a raw lock for the deferred transmit queue
flat view
COOLING12d
From: Karl Mehltretter <hidden>
Date: 2026-09-28 06:42:57
Also in:
linux-rt-devel, lkml, stable
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v1 of 2 in this series.
Revisions (2)
- v1 current
- v1
netpoll_send_skb() calls __netpoll_send_skb() with hard interrupts
disabled. When direct transmission cannot complete, the latter queues
the skb with skb_queue_tail(). The sk_buff_head lock may sleep on
PREEMPT_RT:
BUG: sleeping function called from invalid context
in_atomic(): 0, irqs_disabled(): 1, non_block: 0
rt_spin_lock
skb_queue_tail
netpoll_send_skb
The delayed transmit worker has the same problem when it requeues a busy
skb with skb_queue_head() after disabling interrupts.
Add a dedicated raw spinlock and use the unlocked skb queue helpers under
it. Keep raw critical sections limited to queue operations. During
cleanup, splice the queue to a private list before freeing its skbs.
Fixes: b6cd27ed3388 ("netpoll per device txq")
Cc: stable@vger.kernel.org # 6.12+
Assisted-by: LLM
Signed-off-by: Karl Mehltretter <redacted>
---
include/linux/netpoll.h | 1 +
net/core/netpoll.c | 75 +++++++++++++++++++++++++++++++++++++----
2 files changed, 70 insertions(+), 6 deletions(-)
diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h
index 1c6b1eec5efd6..e20e0592e9349 100644
--- a/include/linux/netpoll.h
+++ b/include/linux/netpoll.h@@ -47,6 +47,7 @@ struct netpoll_info { struct semaphore dev_lock; struct sk_buff_head txq; + raw_spinlock_t txq_lock; struct delayed_work tx_work;
diff --git a/net/core/netpoll.c b/net/core/netpoll.c
index fe1e0cda5d6bf..e0cfcb05468e2 100644
--- a/net/core/netpoll.c
+++ b/net/core/netpoll.c@@ -79,6 +79,68 @@ static netdev_tx_t netpoll_start_xmit(struct sk_buff *skb, return status; } +/* + * Transmit paths can access txq with hard IRQs disabled. Use a raw lock + * because the skb queue lock may sleep on PREEMPT_RT. + */ +static bool netpoll_txq_empty(struct netpoll_info *npinfo) +{ + unsigned long flags; + bool empty; + + raw_spin_lock_irqsave(&npinfo->txq_lock, flags); + empty = skb_queue_empty(&npinfo->txq); + raw_spin_unlock_irqrestore(&npinfo->txq_lock, flags); + + return empty; +} + +static struct sk_buff *netpoll_txq_dequeue(struct netpoll_info *npinfo) +{ + unsigned long flags; + struct sk_buff *skb; + + raw_spin_lock_irqsave(&npinfo->txq_lock, flags); + skb = __skb_dequeue(&npinfo->txq); + raw_spin_unlock_irqrestore(&npinfo->txq_lock, flags); + + return skb; +} + +static void netpoll_txq_queue_head(struct netpoll_info *npinfo, + struct sk_buff *skb) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&npinfo->txq_lock, flags); + __skb_queue_head(&npinfo->txq, skb); + raw_spin_unlock_irqrestore(&npinfo->txq_lock, flags); +} + +static void netpoll_txq_queue_tail(struct netpoll_info *npinfo, + struct sk_buff *skb) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&npinfo->txq_lock, flags); + __skb_queue_tail(&npinfo->txq, skb); + raw_spin_unlock_irqrestore(&npinfo->txq_lock, flags); +} + +static void netpoll_txq_purge(struct netpoll_info *npinfo) +{ + struct sk_buff_head purge; + unsigned long flags; + + __skb_queue_head_init(&purge); + + raw_spin_lock_irqsave(&npinfo->txq_lock, flags); + skb_queue_splice_init(&npinfo->txq, &purge); + raw_spin_unlock_irqrestore(&npinfo->txq_lock, flags); + + __skb_queue_purge(&purge); +} + static void queue_process(struct work_struct *work) { struct netpoll_info *npinfo =
@@ -86,7 +148,7 @@ static void queue_process(struct work_struct *work) struct sk_buff *skb; unsigned long flags; - while ((skb = skb_dequeue(&npinfo->txq))) { + while ((skb = netpoll_txq_dequeue(npinfo))) { struct net_device *dev = skb->dev; struct netdev_queue *txq; unsigned int q_index;
@@ -107,7 +169,7 @@ static void queue_process(struct work_struct *work) HARD_TX_LOCK(dev, txq, smp_processor_id()); if (netif_xmit_frozen_or_stopped(txq) || !dev_xmit_complete(netpoll_start_xmit(skb, dev, txq))) { - skb_queue_head(&npinfo->txq, skb); + netpoll_txq_queue_head(npinfo, skb); HARD_TX_UNLOCK(dev, txq); local_irq_restore(flags);
@@ -282,7 +344,7 @@ static netdev_tx_t __netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } /* don't get messages out of order, and no recursion */ - if (skb_queue_len(&npinfo->txq) == 0 && !netpoll_owner_active(dev)) { + if (netpoll_txq_empty(npinfo) && !netpoll_owner_active(dev)) { struct netdev_queue *txq; txq = netdev_core_pick_tx(dev, skb, NULL);
@@ -314,7 +376,7 @@ static netdev_tx_t __netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } if (!dev_xmit_complete(status)) { - skb_queue_tail(&npinfo->txq, skb); + netpoll_txq_queue_tail(npinfo, skb); schedule_delayed_work(&npinfo->tx_work,0); } ret = NETDEV_TX_OK;
@@ -362,7 +424,8 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) } sema_init(&npinfo->dev_lock, 1); - skb_queue_head_init(&npinfo->txq); + __skb_queue_head_init(&npinfo->txq); + raw_spin_lock_init(&npinfo->txq_lock); INIT_DELAYED_WORK(&npinfo->tx_work, queue_process); refcount_set(&npinfo->refcnt, 1);
@@ -397,7 +460,7 @@ static void rcu_cleanup_netpoll_info(struct rcu_head *rcu_head) struct netpoll_info *npinfo = container_of(rcu_head, struct netpoll_info, rcu); - skb_queue_purge(&npinfo->txq); + netpoll_txq_purge(npinfo); kfree(npinfo); }
--
2.53.0