DORMANTno replies

[PATCH v2] net/rps: consolidate RPS dispatch into helpers

From: Jemmy Wong <hidden>
Date: 2026-07-11 12:10:18
Also in: lkml
Subsystem: networking drivers, networking [general], the rest · Maintainers: Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

The RPS steering logic in netif_rx_internal(), netif_receive_skb_internal()
and netif_receive_skb_list_internal() was open-coded three times, each with
its own #ifdef CONFIG_RPS block and manual rcu_read_lock()/unlock() pairs.

Factor it into two helpers, netif_rps() for the single-skb path and
netif_rps_list() for the list path, sharing a common __netif_rps() inner
helper that runs the get_rps_cpu()/enqueue_to_backlog() step under RCU.

Both wrappers keep the early static_branch_unlikely(&rps_needed) check
and only enter an RCU read-side critical section when RPS is actually
enabled at runtime, so no extra rcu_read_lock()/unlock() is executed on
the hot netif_rx_internal() path when RPS is compiled in but disabled.

A new internal NET_RX_UNHANDLED sentinel lets a helper report "RPS did
not take this skb" so the caller falls back to the local enqueue /
__netif_receive_skb() path; it never escapes to callers.

No functional change intended.

Signed-off-by: Jemmy Wong <redacted>
---
Changes since v1 [1]:
  - Move rcu_read_lock()/unlock() inside the
    static_branch_unlikely(&rps_needed) check, so no RCU read-side section
    is entered when RPS is compiled in but disabled at runtime. Addresses
    Paolo's concern that netif_rx_internal() is performance critical and
    rcu_read_lock() is not a no-op.
  - Drop the guard(rcu) / scoped_guard(rcu) conversion; the RCU section
    is now scoped inside netif_rps() / netif_rps_list() themselves.
  - Split __netif_rps() out of netif_rps() so that netif_rps_list() can
    reuse the per-skb steering step without re-testing the static branch
    or dropping and reacquiring the RCU lock per skb.
  - netif_rps_list() now returns int; when RPS drains the entire list,
    netif_receive_skb_list_internal() skips the __netif_receive_skb_list()
    fall-through instead of walking an empty list. The result is decided
    directly from list_empty(&undo_list): drop the splice on the drained
    path and only splice back when there is something to fall through.

Verified generated code with objdump on aarch64 with CONFIG_RPS=y,
CONFIG_XPS=y, CONFIG_SMP=y, CONFIG_PREEMPT=y (so rcu_read_lock()
expands to a real preempt_disable(), i.e. not a no-op -- the
specific scenario Paolo asked about [2]):

  scripts/bloat-o-meter net/core/dev.o (before -> after):
    add/remove: 2/2 grow/shrink: 1/2 up/down: 720/-716 (4)
    Function                        old     new   delta
    __netif_rps                       -     660    +660
    netif_receive_skb_list_internal 640     700     +60
    netif_rx_internal               236     200     -36
    netif_receive_skb               204     136     -68
    get_rps_cpu                     608       -    -608
    Total: Before=335841, After=335849, chg +0.00%

  netif_rx_internal fast path (!rps_needed, i.e. static branch not
  taken -- the path Paolo asked about [2]):
    - identical instruction sequence to base, no rcu_read_lock/unlock
    - stack frame 16 bytes smaller (0x50 -> 0x40)
    - static_branch NOP is preserved
  netif_receive_skb_internal: inlined in both builds; caller
    netif_receive_skb shrinks by 68 bytes.
  netif_receive_skb_list_internal: !rps_needed fast path unchanged;
    rps_needed=true path grows +60 bytes for LIST_HEAD(undo_list)
    setup, the explicit list_empty(&undo_list)/list_splice_init()
    branch and its early NET_RX_SUCCESS return.

[1] https://lore.kernel.org/all/20260702152830.39065-1-jemmywong512@gmail.com/ (local)
[2] https://lore.kernel.org/all/e53482b8-4e1d-4a04-929c-f5cb443ba59d@redhat.com/ (local)

 include/linux/netdevice.h |   5 +-
 net/core/dev.c            | 118 +++++++++++++++++++++++---------------
 2 files changed, 74 insertions(+), 49 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..c265b78082e3 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -93,8 +93,9 @@ void netdev_set_default_ethtool_ops(struct net_device *dev,
 void netdev_sw_irq_coalesce_default_on(struct net_device *dev);

 /* Backlog congestion levels */
-#define NET_RX_SUCCESS		0	/* keep 'em coming, baby */
-#define NET_RX_DROP		1	/* packet dropped */
+#define NET_RX_UNHANDLED	-1
+#define NET_RX_SUCCESS		0
+#define NET_RX_DROP		1

 #define MAX_NEST_DEV 8
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e0..1a05ac8fdd50 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -5426,6 +5426,65 @@ static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
 	return NET_RX_DROP;
 }

+#ifdef CONFIG_RPS
+static inline int __netif_rps(struct sk_buff *skb)
+{
+	int cpu;
+	int ret = NET_RX_UNHANDLED;
+	struct rps_dev_flow voidflow, *rflow = &voidflow;
+
+	cpu = get_rps_cpu(skb->dev, skb, &rflow);
+	if (cpu >= 0)
+		ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
+
+	return ret;
+}
+#endif
+
+static inline int netif_rps(struct sk_buff *skb)
+{
+	int ret = NET_RX_UNHANDLED;
+
+#ifdef CONFIG_RPS
+	if (!static_branch_unlikely(&rps_needed))
+		return ret;
+
+	rcu_read_lock();
+	ret = __netif_rps(skb);
+	rcu_read_unlock();
+#endif
+
+	return ret;
+}
+
+static inline int netif_rps_list(struct list_head *head)
+{
+	int ret = NET_RX_UNHANDLED;
+
+#ifdef CONFIG_RPS
+	LIST_HEAD(undo_list);
+	struct sk_buff *skb, *next;
+
+	if (!static_branch_unlikely(&rps_needed))
+		return ret;
+
+	rcu_read_lock();
+	list_for_each_entry_safe(skb, next, head, list) {
+		skb_list_del_init(skb);
+		if (__netif_rps(skb) == NET_RX_UNHANDLED)
+			list_add_tail(&skb->list, &undo_list);
+	}
+	rcu_read_unlock();
+
+	if (list_empty(&undo_list))
+		ret = NET_RX_SUCCESS;
+	else
+		list_splice_init(&undo_list, head);
+#endif
+
+	return ret;
+}
+
 static struct netdev_rx_queue *netif_get_rxqueue(struct sk_buff *skb)
 {
 	struct net_device *dev = skb->dev;
@@ -5695,34 +5754,18 @@ EXPORT_SYMBOL_GPL(do_xdp_generic);

 static int netif_rx_internal(struct sk_buff *skb)
 {
+	unsigned int qtail;
 	int ret;

 	net_timestamp_check(READ_ONCE(net_hotdata.tstamp_prequeue), skb);

 	trace_netif_rx(skb);

-#ifdef CONFIG_RPS
-	if (static_branch_unlikely(&rps_needed)) {
-		struct rps_dev_flow voidflow, *rflow = &voidflow;
-		int cpu;
-
-		rcu_read_lock();
-
-		cpu = get_rps_cpu(skb->dev, skb, &rflow);
-		if (cpu < 0)
-			cpu = smp_processor_id();
-
-		ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
-
-		rcu_read_unlock();
-	} else
-#endif
-	{
-		unsigned int qtail;
+	ret = netif_rps(skb);
+	if (ret != NET_RX_UNHANDLED)
+		return ret;

-		ret = enqueue_to_backlog(skb, smp_processor_id(), &qtail);
-	}
-	return ret;
+	return enqueue_to_backlog(skb, smp_processor_id(), &qtail);
 }

 /**
@@ -6389,19 +6432,11 @@ static int netif_receive_skb_internal(struct sk_buff *skb)
 	if (skb_defer_rx_timestamp(skb))
 		return NET_RX_SUCCESS;

-	rcu_read_lock();
-#ifdef CONFIG_RPS
-	if (static_branch_unlikely(&rps_needed)) {
-		struct rps_dev_flow voidflow, *rflow = &voidflow;
-		int cpu = get_rps_cpu(skb->dev, skb, &rflow);
+	ret = netif_rps(skb);
+	if (ret != NET_RX_UNHANDLED)
+		return ret;

-		if (cpu >= 0) {
-			ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
-			rcu_read_unlock();
-			return ret;
-		}
-	}
-#endif
+	rcu_read_lock();
 	ret = __netif_receive_skb(skb);
 	rcu_read_unlock();
 	return ret;
@@ -6421,21 +6456,10 @@ void netif_receive_skb_list_internal(struct list_head *head)
 	}
 	list_splice_init(&sublist, head);

-	rcu_read_lock();
-#ifdef CONFIG_RPS
-	if (static_branch_unlikely(&rps_needed)) {
-		list_for_each_entry_safe(skb, next, head, list) {
-			struct rps_dev_flow voidflow, *rflow = &voidflow;
-			int cpu = get_rps_cpu(skb->dev, skb, &rflow);
+	if (netif_rps_list(head) == NET_RX_SUCCESS)
+		return;

-			if (cpu >= 0) {
-				/* Will be handled, remove from list */
-				skb_list_del_init(skb);
-				enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
-			}
-		}
-	}
-#endif
+	rcu_read_lock();
 	__netif_receive_skb_list(head);
 	rcu_read_unlock();
 }
--
2.54.0 (Apple Git-157)
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help