netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
@ 2025-09-11 21:28 Samiullah Khawaja
  2025-09-11 21:29 ` [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling Samiullah Khawaja
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: Samiullah Khawaja @ 2025-09-11 21:28 UTC (permalink / raw)
  To: Jakub Kicinski, David S . Miller , Eric Dumazet, Paolo Abeni,
	almasrymina, willemb
  Cc: Joe Damato, mkarsten, netdev, skhawaja

Extend the already existing support of threaded napi poll to do continuous
busy polling.

This is used for doing continuous polling of napi to fetch descriptors
from backing RX/TX queues for low latency applications. Allow enabling
of threaded busypoll using netlink so this can be enabled on a set of
dedicated napis for low latency applications.

Once enabled user can fetch the PID of the kthread doing NAPI polling
and set affinity, priority and scheduler for it depending on the
low-latency requirements.

Extend the netlink interface to allow enabling/disabling threaded
busypolling at individual napi level.

We use this for our AF_XDP based hard low-latency usecase with usecs
level latency requirement. For our usecase we want low jitter and stable
latency at P99.

Following is an analysis and comparison of available (and compatible)
busy poll interfaces for a low latency usecase with stable P99. This can
be suitable for applications that want very low latency at the expense
of cpu usage and efficiency.

Already existing APIs (SO_BUSYPOLL and epoll) allow busy polling a NAPI
backing a socket, but the missing piece is a mechanism to busy poll a
NAPI instance in a dedicated thread while ignoring available events or
packets, regardless of the userspace API. Most existing mechanisms are
designed to work in a pattern where you poll until new packets or events
are received, after which userspace is expected to handle them.

As a result, one has to hack together a solution using a mechanism
intended to receive packets or events, not to simply NAPI poll. NAPI
threaded busy polling, on the other hand, provides this capability
natively, independent of any userspace API. This makes it really easy to
setup and manage.

For analysis we use an AF_XDP based benchmarking tool `xsk_rr`. The
description of the tool and how it tries to simulate the real workload
is following,

- It sends UDP packets between 2 machines.
- The client machine sends packets at a fixed frequency. To maintain the
  frequency of the packet being sent, we use open-loop sampling. That is
  the packets are sent in a separate thread.
- The server replies to the packet inline by reading the pkt from the
  recv ring and replies using the tx ring.
- To simulate the application processing time, we use a configurable
  delay in usecs on the client side after a reply is received from the
  server.

The xsk_rr tool is posted separately as an RFC for tools/testing/selftest.

We use this tool with following napi polling configurations,

- Interrupts only
- SO_BUSYPOLL (inline in the same thread where the client receives the
  packet).
- SO_BUSYPOLL (separate thread and separate core)
- Threaded NAPI busypoll

System is configured using following script in all 4 cases,

```
echo 0 | sudo tee /sys/class/net/eth0/threaded
echo 0 | sudo tee /proc/sys/kernel/timer_migration
echo off | sudo tee  /sys/devices/system/cpu/smt/control

sudo ethtool -L eth0 rx 1 tx 1
sudo ethtool -G eth0 rx 1024

echo 0 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
echo 0 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus

 # pin IRQs on CPU 2
IRQS="$(gawk '/eth0-(TxRx-)?1/ {match($1, /([0-9]+)/, arr); \
				print arr[0]}' < /proc/interrupts)"
for irq in "${IRQS}"; \
	do echo 2 | sudo tee /proc/irq/$irq/smp_affinity_list; done

echo -1 | sudo tee /proc/sys/kernel/sched_rt_runtime_us

for i in /sys/devices/virtual/workqueue/*/cpumask; \
			do echo $i; echo 1,2,3,4,5,6 > $i; done

if [[ -z "$1" ]]; then
  echo 400 | sudo tee /proc/sys/net/core/busy_read
  echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
  echo 15000   | sudo tee /sys/class/net/eth0/gro_flush_timeout
fi

sudo ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 0 tx-usecs 0

if [[ "$1" == "enable_threaded" ]]; then
  echo 0 | sudo tee /proc/sys/net/core/busy_poll
  echo 0 | sudo tee /proc/sys/net/core/busy_read
  echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
  echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
  NAPI_ID=$(ynl --family netdev --output-json --do queue-get \
    --json '{"ifindex": '${IFINDEX}', "id": '0', "type": "rx"}' | jq '."napi-id"')

  ynl --family netdev --json '{"id": "'${NAPI_ID}'", "threaded": "busy-poll-enabled"}'

  NAPI_T=$(ynl --family netdev --output-json --do napi-get \
    --json '{"id": "'$NAPI_ID'"}' | jq '."pid"')

  sudo chrt -f  -p 50 $NAPI_T

  # pin threaded poll thread to CPU 2
  sudo taskset -pc 2 $NAPI_T
fi

if [[ "$1" == "enable_interrupt" ]]; then
  echo 0 | sudo tee /proc/sys/net/core/busy_read
  echo 0 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
  echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
fi
```

To enable various configurations, script can be run as following,

- Interrupt Only
  ```
  <script> enable_interrupt
  ```

- SO_BUSYPOLL (no arguments to script)
  ```
  <script>
  ```

- NAPI threaded busypoll
  ```
  <script> enable_threaded
  ```

If using idpf, the script needs to be run again after launching the
workload just to make sure that the configurations are not reverted. As
idpf reverts some configurations on software reset when AF_XDP program
is attached.

Once configured, the workload is run with various configurations using
following commands. Set period (1/frequency) and delay in usecs to
produce results for packet frequency and application processing delay.

 ## Interrupt Only and SO_BUSYPOLL (inline)

- Server
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 -h -v
```

- Client
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v
```

 ## SO_BUSYPOLL(done in separate core using recvfrom)

Argument -t spawns a seprate thread and continuously calls recvfrom.

- Server
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
	-h -v -t
```

- Client
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -t
```

 ## NAPI Threaded Busy Poll

Argument -n skips the recvfrom call as there is no recv kick needed.

- Server
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
	-h -v -n
```

- Client
```
sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -n
```

| Experiment | interrupts | SO_BUSYPOLL | SO_BUSYPOLL(separate) | NAPI threaded |
|---|---|---|---|---|
| 12 Kpkt/s + 0us delay | | | | |
|  | p5: 12700 | p5: 12900 | p5: 13300 | p5: 12800 |
|  | p50: 13100 | p50: 13600 | p50: 14100 | p50: 13000 |
|  | p95: 13200 | p95: 13800 | p95: 14400 | p95: 13000 |
|  | p99: 13200 | p99: 13800 | p99: 14400 | p99: 13000 |
| 32 Kpkt/s + 30us delay | | | | |
|  | p5: 19900 | p5: 16600 | p5: 13100 | p5: 12800 |
|  | p50: 21100 | p50: 17000 | p50: 13700 | p50: 13000 |
|  | p95: 21200 | p95: 17100 | p95: 14000 | p95: 13000 |
|  | p99: 21200 | p99: 17100 | p99: 14000 | p99: 13000 |
| 125 Kpkt/s + 6us delay | | | | |
|  | p5: 14600 | p5: 17100 | p5: 13300 | p5: 12900 |
|  | p50: 15400 | p50: 17400 | p50: 13800 | p50: 13100 |
|  | p95: 15600 | p95: 17600 | p95: 14000 | p95: 13100 |
|  | p99: 15600 | p99: 17600 | p99: 14000 | p99: 13100 |
| 12 Kpkt/s + 78us delay | | | | |
|  | p5: 14100 | p5: 16700 | p5: 13200 | p5: 12600 |
|  | p50: 14300 | p50: 17100 | p50: 13900 | p50: 12800 |
|  | p95: 14300 | p95: 17200 | p95: 14200 | p95: 12800 |
|  | p99: 14300 | p99: 17200 | p99: 14200 | p99: 12800 |
| 25 Kpkt/s + 38us delay | | | | |
|  | p5: 19900 | p5: 16600 | p5: 13000 | p5: 12700 |
|  | p50: 21000 | p50: 17100 | p50: 13800 | p50: 12900 |
|  | p95: 21100 | p95: 17100 | p95: 14100 | p95: 12900 |
|  | p99: 21100 | p99: 17100 | p99: 14100 | p99: 12900 |

 ## Observations

- Here without application processing all the approaches give the same
  latency within 1usecs range and NAPI threaded gives minimum latency.
- With application processing the latency increases by 3-4usecs when
  doing inline polling.
- Using a dedicated core to drive napi polling keeps the latency same
  even with application processing. This is observed both in userspace
  and threaded napi (in kernel).
- Using napi threaded polling in kernel gives lower latency by
  1-2usecs as compared to userspace driven polling in separate core.
- Even on a dedicated core, SO_BUSYPOLL adds around 1-2usecs of latency.
  This is because it doesn't continuously busy poll until events are
  ready. Instead, it returns after polling only once, requiring the
  process to re-invoke the syscall for each poll, which requires a new
  enter/leave kernel cycle and the setup/teardown of the busy poll for
  every single poll attempt.
- With application processing userspace will get the packet from recv
  ring and spend some time doing application processing and then do napi
  polling. While application processing is happening a dedicated core
  doing napi polling can pull the packet of the NAPI RX queue and
  populate the AF_XDP recv ring. This means that when the application
  thread is done with application processing it has new packets ready to
  recv and process in recv ring.
- Napi threaded busy polling in the kernel with a dedicated core gives
  the consistent P5-P99 latency.

Following histogram is generated to measure the time spent in recvfrom
while using inline thread with SO_BUSYPOLL. The histogram is generated
using the following bpftrace command. In this experiment there are 32K
packets per second and the application processing delay is 30usecs. This
is to measure whether there is significant time spent pulling packets
from the descriptor queue that it will affect the overall latency if
done inline.

```
bpftrace -e '
        kprobe:xsk_recvmsg {
                @start[tid] = nsecs;
        }
        kretprobe:xsk_recvmsg {
                if (@start[tid]) {
                        $sample = (nsecs - @start[tid]);
                        @xsk_recvfrom_hist = hist($sample);
                        delete(@start[tid]);
                }
        }
        END { clear(@start);}'
```

Here in case of inline busypolling around 35 percent of calls are taking
1-2usecs and around 50 percent are taking 0.5-2usecs.

@xsk_recvfrom_hist:
[128, 256)         24073 |@@@@@@@@@@@@@@@@@@@@@@                              |
[256, 512)         55633 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[512, 1K)          20974 |@@@@@@@@@@@@@@@@@@@                                 |
[1K, 2K)           34234 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                     |
[2K, 4K)            3266 |@@@                                                 |
[4K, 8K)              19 |                                                    |

v9:
 - Unset NAPI_STATE_THREADED_BUSY_POLL when stopping napi kthread to
   prevent network disruption as reported by Martin Karsten.
 - Updated napi threaded busy poll enable instructions to use netlink
   instead of sysfs. This is because the sysfs mechanism to enable napi
   threaded busy poll is removed.

v8:
 - Fixed selftest build error.
 - Removed support of enabling napi busy poll at device level.
 - Updated documentation to reflect removal of busy poll at device
   level.
 - Added paragraph in the cover letter mentioning that napi threaded
   busy polling allows busy polling a NAPI natively independent of API.
 - Added paragraph in the cover letter explaining why SO_BUSYPOLL is
   still not enough when run in a dedicated core.

v7:
 - Rebased.

v6:
 - Moved threaded in struct netdevice up to fill the cacheline hole.
 - Changed dev_set_threaded to dev_set_threaded_hint and removed the
   second argument that was always set to true by all the drivers.
   Exported only dev_set_threaded_hint and made dev_set_threaded core
   only function. This change is done in a separate commit.
 - Updated documentation comment for threaded in struct netdevice.
 - gro_flush_helper renamed to gro_flush_normal and moved to gro.h. Also
   used it in kernel/bpf/cpumap.c
 - Updated documentation to explicitly state that the NAPI threaded busy
   polling would keep the CPU core busy at 100% usage.
 - Updated documentation and commit messages.

v5:
 - Updated experiment data with 'SO_PREFER_BUSY_POLL' usage as
   suggested.
 - Sent 'Add support to set napi threaded for individual napi'
   separately. This series depends on top of that patch.
   https://lore.kernel.org/netdev/20250423201413.1564527-1-skhawaja@google.com/
 - Added a separate patch to use enum for napi threaded state. Updated
   the nl_netdev python test.
 - Using "write all" semantics when napi settings set at device level.
   This aligns with already existing behaviour for other settings.
 - Fix comments to make them kdoc compatible.
 - Updated Documentation/networking/net_cachelines/net_device.rst
 - Updated the missed gro_flush modification in napi_complete_done

v4:
 - Using AF_XDP based benchmark for experiments.
 - Re-enable dev level napi threaded busypoll after soft reset.

v3:
 - Fixed calls to dev_set_threaded in drivers

v2:
 - Add documentation in napi.rst.
 - Provide experiment data and usecase details.
 - Update busy_poller selftest to include napi threaded poll testcase.
 - Define threaded mode enum in netlink interface.
 - Included NAPI threaded state in napi config to save/restore.

Samiullah Khawaja (2):
  Extend napi threaded polling to allow kthread based busy polling
  selftests: Add napi threaded busy poll test in `busy_poller`

 Documentation/netlink/specs/netdev.yaml       |  5 +-
 Documentation/networking/napi.rst             | 62 +++++++++++++++-
 include/linux/netdevice.h                     | 11 ++-
 include/uapi/linux/netdev.h                   |  1 +
 net/core/dev.c                                | 74 ++++++++++++++++---
 net/core/dev.h                                |  3 +
 net/core/netdev-genl-gen.c                    |  2 +-
 tools/include/uapi/linux/netdev.h             |  1 +
 tools/testing/selftests/net/busy_poll_test.sh | 25 ++++++-
 tools/testing/selftests/net/busy_poller.c     | 14 +++-
 10 files changed, 179 insertions(+), 19 deletions(-)


base-commit: 3b4296f5893d3a4e19edfc3800cb79381095e55f
-- 
2.51.0.384.g4c02a37b29-goog


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling
  2025-09-11 21:28 [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Samiullah Khawaja
@ 2025-09-11 21:29 ` Samiullah Khawaja
  2025-09-13  1:46   ` Jakub Kicinski
  2025-09-11 21:29 ` [PATCH net-next v9 2/2] selftests: Add napi threaded busy poll test in `busy_poller` Samiullah Khawaja
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 11+ messages in thread
From: Samiullah Khawaja @ 2025-09-11 21:29 UTC (permalink / raw)
  To: Jakub Kicinski, David S . Miller , Eric Dumazet, Paolo Abeni,
	almasrymina, willemb
  Cc: Joe Damato, mkarsten, netdev, skhawaja

Add a new state to napi state enum:

- NAPI_STATE_THREADED_BUSY_POLL
  Threaded busy poll is enabled/running for this napi.

Following changes are introduced in the napi scheduling and state logic:

- When threaded busy poll is enabled through netlink it also enables
  NAPI_STATE_THREADED so a kthread is created per napi. It also sets
  NAPI_STATE_THREADED_BUSY_POLL bit on each napi to indicate that it is
  going to busy poll the napi.

- When napi is scheduled with NAPI_STATE_SCHED_THREADED and associated
  kthread is woken up, the kthread owns the context. If
  NAPI_STATE_THREADED_BUSY_POLL and NAPI_STATE_SCHED_THREADED both are
  set then it means that kthread can busy poll.

- To keep busy polling and to avoid scheduling of the interrupts, the
  napi_complete_done returns false when both NAPI_STATE_SCHED_THREADED
  and NAPI_STATE_THREADED_BUSY_POLL flags are set. Also
  napi_complete_done returns early to avoid the
  NAPI_STATE_SCHED_THREADED being unset.

- If at any point NAPI_STATE_THREADED_BUSY_POLL is unset, the
  napi_complete_done will run and unset the NAPI_STATE_SCHED_THREADED
  bit also. This will make the associated kthread go to sleep as per
  existing logic.

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>

---

This patch requires a fix that was sent separately:
https://lore.kernel.org/all/20250910203716.1016546-1-skhawaja@google.com/

---

 Documentation/netlink/specs/netdev.yaml |  5 +-
 Documentation/networking/napi.rst       | 62 ++++++++++++++++++++-
 include/linux/netdevice.h               | 11 +++-
 include/uapi/linux/netdev.h             |  1 +
 net/core/dev.c                          | 74 +++++++++++++++++++++----
 net/core/dev.h                          |  3 +
 net/core/netdev-genl-gen.c              |  2 +-
 tools/include/uapi/linux/netdev.h       |  1 +
 8 files changed, 144 insertions(+), 15 deletions(-)

diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml
index c035dc0f64fd..ce28e8708a87 100644
--- a/Documentation/netlink/specs/netdev.yaml
+++ b/Documentation/netlink/specs/netdev.yaml
@@ -88,7 +88,7 @@ definitions:
   -
     name: napi-threaded
     type: enum
-    entries: [disabled, enabled]
+    entries: [disabled, enabled, busy-poll-enabled]
 
 attribute-sets:
   -
@@ -291,7 +291,8 @@ attribute-sets:
         name: threaded
         doc: Whether the NAPI is configured to operate in threaded polling
              mode. If this is set to enabled then the NAPI context operates
-             in threaded polling mode.
+             in threaded polling mode. If this is set to busy-poll-enabled
+             then the NAPI kthread also does busypolling.
         type: u32
         enum: napi-threaded
   -
diff --git a/Documentation/networking/napi.rst b/Documentation/networking/napi.rst
index 7dd60366f4ff..c6b9c4cb9cae 100644
--- a/Documentation/networking/napi.rst
+++ b/Documentation/networking/napi.rst
@@ -263,7 +263,9 @@ are not well known).
 Busy polling is enabled by either setting ``SO_BUSY_POLL`` on
 selected sockets or using the global ``net.core.busy_poll`` and
 ``net.core.busy_read`` sysctls. An io_uring API for NAPI busy polling
-also exists.
+also exists. Threaded polling of NAPI also has a mode to busy poll for
+packets (:ref:`threaded busy polling<threaded_busy_poll>`) using the same
+thread that is used for NAPI processing.
 
 epoll-based busy polling
 ------------------------
@@ -426,6 +428,64 @@ Therefore, setting ``gro_flush_timeout`` and ``napi_defer_hard_irqs`` is
 the recommended usage, because otherwise setting ``irq-suspend-timeout``
 might not have any discernible effect.
 
+.. _threaded_busy_poll:
+
+Threaded NAPI busy polling
+--------------------------
+
+Threaded NAPI allows processing of packets from each NAPI in a kthread in
+kernel. Threaded NAPI busy polling extends this and adds support to do
+continuous busy polling of this NAPI. This can be used to enable busy polling
+independent of userspace application or the API (epoll, io_uring, raw sockets)
+being used in userspace to process the packets.
+
+It can be enabled for each NAPI using netlink interface.
+
+For example, using following script:
+
+.. code-block:: bash
+
+  $ ynl --family netdev --do napi-set \
+            --json='{"id": 66, "threaded": "busy-poll-enabled"}'
+
+
+Enabling it for each NAPI allows finer control to enable busy pollling for
+only a set of NIC queues which will get traffic with low latency requirements.
+
+Depending on application requirement, user might want to set affinity of the
+kthread that is busy polling each NAPI. User might also want to set priority
+and the scheduler of the thread depending on the latency requirements.
+
+For a hard low-latency application, user might want to dedicate the full core
+for the NAPI polling so the NIC queue descriptors are picked up from the queue
+as soon as they appear. Once enabled, the NAPI thread will poll the NIC queues
+continuously without sleeping. This will keep the CPU core busy with 100%
+usage. For more relaxed low-latency requirement, user might want to share the
+core with other threads by setting thread affinity and priority.
+
+Once threaded busy polling is enabled for a NAPI, PID of the kthread can be
+fetched using netlink interface so the affinity, priority and scheduler
+configuration can be done.
+
+For example, following script can be used to fetch the pid:
+
+.. code-block:: bash
+
+  $ ynl --family netdev --do napi-get --json='{"id": 66}'
+
+This will output something like following, the pid `258` is the PID of the
+kthread that is polling this NAPI.
+
+.. code-block:: bash
+
+  $ {'defer-hard-irqs': 0,
+     'gro-flush-timeout': 0,
+     'id': 66,
+     'ifindex': 2,
+     'irq-suspend-timeout': 0,
+     'pid': 258,
+     'threaded': 'busy-poll-enabled'}
+
 .. _threaded:
 
 Threaded NAPI
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index f3a3b761abfb..a88f6596aef7 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -427,6 +427,8 @@ enum {
 	NAPI_STATE_THREADED,		/* The poll is performed inside its own thread*/
 	NAPI_STATE_SCHED_THREADED,	/* Napi is currently scheduled in threaded mode */
 	NAPI_STATE_HAS_NOTIFIER,	/* Napi has an IRQ notifier */
+	NAPI_STATE_THREADED_BUSY_POLL,	/* The threaded napi poller will busy poll */
+	NAPI_STATE_SCHED_THREADED_BUSY_POLL,  /* The threaded napi poller is busy polling */
 };
 
 enum {
@@ -441,8 +443,14 @@ enum {
 	NAPIF_STATE_THREADED		= BIT(NAPI_STATE_THREADED),
 	NAPIF_STATE_SCHED_THREADED	= BIT(NAPI_STATE_SCHED_THREADED),
 	NAPIF_STATE_HAS_NOTIFIER	= BIT(NAPI_STATE_HAS_NOTIFIER),
+	NAPIF_STATE_THREADED_BUSY_POLL	= BIT(NAPI_STATE_THREADED_BUSY_POLL),
+	NAPIF_STATE_SCHED_THREADED_BUSY_POLL  =
+			BIT(NAPI_STATE_SCHED_THREADED_BUSY_POLL),
 };
 
+#define NAPIF_STATE_THREADED_BUSY_POLL_MASK \
+	(NAPIF_STATE_THREADED | NAPIF_STATE_THREADED_BUSY_POLL)
+
 enum gro_result {
 	GRO_MERGED,
 	GRO_MERGED_FREE,
@@ -1873,7 +1881,8 @@ enum netdev_reg_state {
  * 	@addr_len:		Hardware address length
  *	@upper_level:		Maximum depth level of upper devices.
  *	@lower_level:		Maximum depth level of lower devices.
- *	@threaded:		napi threaded state.
+ *	@threaded:		napi threaded mode is disabled, enabled or
+ *				enabled with busy polling.
  *	@neigh_priv_len:	Used in neigh_alloc()
  * 	@dev_id:		Used to differentiate devices that share
  * 				the same link layer address
diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h
index 48eb49aa03d4..8163afb15377 100644
--- a/include/uapi/linux/netdev.h
+++ b/include/uapi/linux/netdev.h
@@ -80,6 +80,7 @@ enum netdev_qstats_scope {
 enum netdev_napi_threaded {
 	NETDEV_NAPI_THREADED_DISABLED,
 	NETDEV_NAPI_THREADED_ENABLED,
+	NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED,
 };
 
 enum {
diff --git a/net/core/dev.c b/net/core/dev.c
index 1d1650d9ecff..55359059b1d5 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -78,6 +78,7 @@
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/sched/isolation.h>
+#include <linux/sched/types.h>
 #include <linux/sched/mm.h>
 #include <linux/smpboot.h>
 #include <linux/mutex.h>
@@ -6619,7 +6620,8 @@ bool napi_complete_done(struct napi_struct *n, int work_done)
 	 *    the guarantee we will be called later.
 	 */
 	if (unlikely(n->state & (NAPIF_STATE_NPSVC |
-				 NAPIF_STATE_IN_BUSY_POLL)))
+				 NAPIF_STATE_IN_BUSY_POLL |
+				 NAPIF_STATE_SCHED_THREADED_BUSY_POLL)))
 		return false;
 
 	if (work_done) {
@@ -7000,7 +7002,8 @@ static void napi_stop_kthread(struct napi_struct *napi)
 		 */
 		if ((val & NAPIF_STATE_SCHED_THREADED) ||
 		    !(val & NAPIF_STATE_SCHED)) {
-			new = val & (~NAPIF_STATE_THREADED);
+			new = val & (~(NAPIF_STATE_THREADED |
+				       NAPIF_STATE_THREADED_BUSY_POLL));
 		} else {
 			msleep(20);
 			continue;
@@ -7024,6 +7027,19 @@ static void napi_stop_kthread(struct napi_struct *napi)
 	napi->thread = NULL;
 }
 
+static void napi_set_threaded_state(struct napi_struct *napi,
+				    enum netdev_napi_threaded threaded)
+{
+	unsigned long val;
+
+	val = 0;
+	if (threaded == NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED)
+		val |= NAPIF_STATE_THREADED_BUSY_POLL;
+	if (threaded)
+		val |= NAPIF_STATE_THREADED;
+	set_mask_bits(&napi->state, NAPIF_STATE_THREADED_BUSY_POLL_MASK, val);
+}
+
 int napi_set_threaded(struct napi_struct *napi,
 		      enum netdev_napi_threaded threaded)
 {
@@ -7050,7 +7066,7 @@ int napi_set_threaded(struct napi_struct *napi,
 	} else {
 		/* Make sure kthread is created before THREADED bit is set. */
 		smp_mb__before_atomic();
-		assign_bit(NAPI_STATE_THREADED, &napi->state, threaded);
+		napi_set_threaded_state(napi, threaded);
 	}
 
 	return 0;
@@ -7442,7 +7458,9 @@ void napi_disable_locked(struct napi_struct *n)
 		}
 
 		new = val | NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC;
-		new &= ~(NAPIF_STATE_THREADED | NAPIF_STATE_PREFER_BUSY_POLL);
+		new &= ~(NAPIF_STATE_THREADED
+			 | NAPIF_STATE_THREADED_BUSY_POLL
+			 | NAPIF_STATE_PREFER_BUSY_POLL);
 	} while (!try_cmpxchg(&n->state, &val, new));
 
 	hrtimer_cancel(&n->timer);
@@ -7486,7 +7504,7 @@ void napi_enable_locked(struct napi_struct *n)
 
 		new = val & ~(NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC);
 		if (n->dev->threaded && n->thread)
-			new |= NAPIF_STATE_THREADED;
+			napi_set_threaded_state(n, n->dev->threaded);
 	} while (!try_cmpxchg(&n->state, &val, new));
 }
 EXPORT_SYMBOL(napi_enable_locked);
@@ -7654,7 +7672,7 @@ static int napi_thread_wait(struct napi_struct *napi)
 	return -1;
 }
 
-static void napi_threaded_poll_loop(struct napi_struct *napi)
+static void napi_threaded_poll_loop(struct napi_struct *napi, bool busy_poll)
 {
 	struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
 	struct softnet_data *sd;
@@ -7683,22 +7701,58 @@ static void napi_threaded_poll_loop(struct napi_struct *napi)
 		}
 		skb_defer_free_flush(sd);
 		bpf_net_ctx_clear(bpf_net_ctx);
+
+		/* Flush too old packets. If HZ < 1000, flush all packets */
+		if (busy_poll)
+			gro_flush_normal(&napi->gro, HZ >= 1000);
 		local_bh_enable();
 
-		if (!repoll)
+		/* If busy polling then do not break here because we need to
+		 * call cond_resched and rcu_softirq_qs_periodic to prevent
+		 * watchdog warnings.
+		 */
+		if (!repoll && !busy_poll)
 			break;
 
 		rcu_softirq_qs_periodic(last_qs);
 		cond_resched();
+
+		if (!repoll)
+			break;
 	}
 }
 
 static int napi_threaded_poll(void *data)
 {
 	struct napi_struct *napi = data;
+	bool busy_poll_sched;
+	unsigned long val;
+	bool busy_poll;
+
+	while (!napi_thread_wait(napi)) {
+		/* Once woken up, this means that we are scheduled as threaded
+		 * napi and this thread owns the napi context, if busy poll
+		 * state is set then busy poll this napi.
+		 */
+		val = READ_ONCE(napi->state);
+		busy_poll = val & NAPIF_STATE_THREADED_BUSY_POLL;
+		busy_poll_sched = val & NAPIF_STATE_SCHED_THREADED_BUSY_POLL;
+
+		/* Do not busy poll if napi is disabled. */
+		if (unlikely(val & NAPIF_STATE_DISABLE))
+			busy_poll = false;
+
+		if (busy_poll != busy_poll_sched) {
+			val = busy_poll ?
+					NAPIF_STATE_SCHED_THREADED_BUSY_POLL :
+					0;
+			set_mask_bits(&napi->state,
+				      NAPIF_STATE_SCHED_THREADED_BUSY_POLL,
+				      val);
+		}
 
-	while (!napi_thread_wait(napi))
-		napi_threaded_poll_loop(napi);
+		napi_threaded_poll_loop(napi, busy_poll);
+	}
 
 	return 0;
 }
@@ -12890,7 +12944,7 @@ static void run_backlog_napi(unsigned int cpu)
 {
 	struct softnet_data *sd = per_cpu_ptr(&softnet_data, cpu);
 
-	napi_threaded_poll_loop(&sd->backlog);
+	napi_threaded_poll_loop(&sd->backlog, false);
 }
 
 static void backlog_napi_setup(unsigned int cpu)
diff --git a/net/core/dev.h b/net/core/dev.h
index d6b08d435479..d6cfe7105903 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -317,6 +317,9 @@ static inline void napi_set_irq_suspend_timeout(struct napi_struct *n,
 
 static inline enum netdev_napi_threaded napi_get_threaded(struct napi_struct *n)
 {
+	if (test_bit(NAPI_STATE_THREADED_BUSY_POLL, &n->state))
+		return NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED;
+
 	if (test_bit(NAPI_STATE_THREADED, &n->state))
 		return NETDEV_NAPI_THREADED_ENABLED;
 
diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c
index e9a2a6f26cb7..ff20435c45d2 100644
--- a/net/core/netdev-genl-gen.c
+++ b/net/core/netdev-genl-gen.c
@@ -97,7 +97,7 @@ static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_THREADED
 	[NETDEV_A_NAPI_DEFER_HARD_IRQS] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_napi_defer_hard_irqs_range),
 	[NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT] = { .type = NLA_UINT, },
 	[NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT] = { .type = NLA_UINT, },
-	[NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_U32, 1),
+	[NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_U32, 2),
 };
 
 /* NETDEV_CMD_BIND_TX - do */
diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h
index 48eb49aa03d4..8163afb15377 100644
--- a/tools/include/uapi/linux/netdev.h
+++ b/tools/include/uapi/linux/netdev.h
@@ -80,6 +80,7 @@ enum netdev_qstats_scope {
 enum netdev_napi_threaded {
 	NETDEV_NAPI_THREADED_DISABLED,
 	NETDEV_NAPI_THREADED_ENABLED,
+	NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED,
 };
 
 enum {
-- 
2.51.0.384.g4c02a37b29-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH net-next v9 2/2] selftests: Add napi threaded busy poll test in `busy_poller`
  2025-09-11 21:28 [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Samiullah Khawaja
  2025-09-11 21:29 ` [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling Samiullah Khawaja
@ 2025-09-11 21:29 ` Samiullah Khawaja
  2025-09-12  8:08 ` [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Martin Karsten
  2025-09-12 20:22 ` Stanislav Fomichev
  3 siblings, 0 replies; 11+ messages in thread
From: Samiullah Khawaja @ 2025-09-11 21:29 UTC (permalink / raw)
  To: Jakub Kicinski, David S . Miller , Eric Dumazet, Paolo Abeni,
	almasrymina, willemb
  Cc: Joe Damato, mkarsten, netdev, skhawaja

Add testcase to run busy poll test with threaded napi busy poll enabled.

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>

---
 tools/testing/selftests/net/busy_poll_test.sh | 25 ++++++++++++++++++-
 tools/testing/selftests/net/busy_poller.c     | 14 ++++++++---
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/net/busy_poll_test.sh b/tools/testing/selftests/net/busy_poll_test.sh
index 7d2d40812074..ab230df1057e 100755
--- a/tools/testing/selftests/net/busy_poll_test.sh
+++ b/tools/testing/selftests/net/busy_poll_test.sh
@@ -27,6 +27,9 @@ NAPI_DEFER_HARD_IRQS=100
 GRO_FLUSH_TIMEOUT=50000
 SUSPEND_TIMEOUT=20000000
 
+# NAPI threaded busy poll config
+NAPI_THREADED_POLL=2
+
 setup_ns()
 {
 	set -e
@@ -62,6 +65,9 @@ cleanup_ns()
 test_busypoll()
 {
 	suspend_value=${1:-0}
+	napi_threaded_value=${2:-0}
+	prefer_busy_poll_value=${3:-$PREFER_BUSY_POLL}
+
 	tmp_file=$(mktemp)
 	out_file=$(mktemp)
 
@@ -73,10 +79,11 @@ test_busypoll()
 					     -b${SERVER_IP}        \
 					     -m${MAX_EVENTS}       \
 					     -u${BUSY_POLL_USECS}  \
-					     -P${PREFER_BUSY_POLL} \
+					     -P${prefer_busy_poll_value} \
 					     -g${BUSY_POLL_BUDGET} \
 					     -i${NSIM_SV_IFIDX}    \
 					     -s${suspend_value}    \
+					     -t${napi_threaded_value} \
 					     -o${out_file}&
 
 	wait_local_port_listen nssv ${SERVER_PORT} tcp
@@ -109,6 +116,15 @@ test_busypoll_with_suspend()
 	return $?
 }
 
+test_busypoll_with_napi_threaded()
+{
+	# Only enable napi threaded poll. Set suspend timeout and prefer busy
+	# poll to 0.
+	test_busypoll 0 ${NAPI_THREADED_POLL} 0
+
+	return $?
+}
+
 ###
 ### Code start
 ###
@@ -154,6 +170,13 @@ if [ $? -ne 0 ]; then
 	exit 1
 fi
 
+test_busypoll_with_napi_threaded
+if [ $? -ne 0 ]; then
+	echo "test_busypoll_with_napi_threaded failed"
+	cleanup_ns
+	exit 1
+fi
+
 echo "$NSIM_SV_FD:$NSIM_SV_IFIDX" > $NSIM_DEV_SYS_UNLINK
 
 echo $NSIM_CL_ID > $NSIM_DEV_SYS_DEL
diff --git a/tools/testing/selftests/net/busy_poller.c b/tools/testing/selftests/net/busy_poller.c
index 04c7ff577bb8..46401d5e01be 100644
--- a/tools/testing/selftests/net/busy_poller.c
+++ b/tools/testing/selftests/net/busy_poller.c
@@ -65,15 +65,16 @@ static uint32_t cfg_busy_poll_usecs;
 static uint16_t cfg_busy_poll_budget;
 static uint8_t cfg_prefer_busy_poll;
 
-/* IRQ params */
+/* NAPI params */
 static uint32_t cfg_defer_hard_irqs;
 static uint64_t cfg_gro_flush_timeout;
 static uint64_t cfg_irq_suspend_timeout;
+static enum netdev_napi_threaded cfg_napi_threaded_poll = NETDEV_NAPI_THREADED_DISABLED;
 
 static void usage(const char *filepath)
 {
 	error(1, 0,
-	      "Usage: %s -p<port> -b<addr> -m<max_events> -u<busy_poll_usecs> -P<prefer_busy_poll> -g<busy_poll_budget> -o<outfile> -d<defer_hard_irqs> -r<gro_flush_timeout> -s<irq_suspend_timeout> -i<ifindex>",
+	      "Usage: %s -p<port> -b<addr> -m<max_events> -u<busy_poll_usecs> -P<prefer_busy_poll> -g<busy_poll_budget> -o<outfile> -d<defer_hard_irqs> -r<gro_flush_timeout> -s<irq_suspend_timeout> -t<napi_threaded_poll> -i<ifindex>",
 	      filepath);
 }
 
@@ -86,7 +87,7 @@ static void parse_opts(int argc, char **argv)
 	if (argc <= 1)
 		usage(argv[0]);
 
-	while ((c = getopt(argc, argv, "p:m:b:u:P:g:o:d:r:s:i:")) != -1) {
+	while ((c = getopt(argc, argv, "p:m:b:u:P:g:o:d:r:s:i:t:")) != -1) {
 		/* most options take integer values, except o and b, so reduce
 		 * code duplication a bit for the common case by calling
 		 * strtoull here and leave bounds checking and casting per
@@ -168,6 +169,12 @@ static void parse_opts(int argc, char **argv)
 
 			cfg_ifindex = (int)tmp;
 			break;
+		case 't':
+			if (tmp == ULLONG_MAX || tmp > 2)
+				error(1, ERANGE, "napi threaded poll value must be 0-2");
+
+			cfg_napi_threaded_poll = (enum netdev_napi_threaded)tmp;
+			break;
 		}
 	}
 
@@ -246,6 +253,7 @@ static void setup_queue(void)
 						  cfg_gro_flush_timeout);
 	netdev_napi_set_req_set_irq_suspend_timeout(set_req,
 						    cfg_irq_suspend_timeout);
+	netdev_napi_set_req_set_threaded(set_req, cfg_napi_threaded_poll);
 
 	if (netdev_napi_set(ys, set_req))
 		error(1, 0, "can't set NAPI params: %s\n", yerr.msg);
-- 
2.51.0.384.g4c02a37b29-goog


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-11 21:28 [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Samiullah Khawaja
  2025-09-11 21:29 ` [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling Samiullah Khawaja
  2025-09-11 21:29 ` [PATCH net-next v9 2/2] selftests: Add napi threaded busy poll test in `busy_poller` Samiullah Khawaja
@ 2025-09-12  8:08 ` Martin Karsten
  2025-09-12 16:22   ` Willem de Bruijn
  2025-09-13  2:07   ` Jakub Kicinski
  2025-09-12 20:22 ` Stanislav Fomichev
  3 siblings, 2 replies; 11+ messages in thread
From: Martin Karsten @ 2025-09-12  8:08 UTC (permalink / raw)
  To: Samiullah Khawaja, Jakub Kicinski, David S . Miller, Eric Dumazet,
	Paolo Abeni, almasrymina, willemb
  Cc: Joe Damato, netdev

On 2025-09-11 17:28, Samiullah Khawaja wrote:
> Extend the already existing support of threaded napi poll to do continuous
> busy polling.
> 
> This is used for doing continuous polling of napi to fetch descriptors
> from backing RX/TX queues for low latency applications. Allow enabling
> of threaded busypoll using netlink so this can be enabled on a set of
> dedicated napis for low latency applications.
> 
> Once enabled user can fetch the PID of the kthread doing NAPI polling
> and set affinity, priority and scheduler for it depending on the
> low-latency requirements.
> 
> Extend the netlink interface to allow enabling/disabling threaded
> busypolling at individual napi level.
> 
> We use this for our AF_XDP based hard low-latency usecase with usecs
> level latency requirement. For our usecase we want low jitter and stable
> latency at P99.
> 
> Following is an analysis and comparison of available (and compatible)
> busy poll interfaces for a low latency usecase with stable P99. This can
> be suitable for applications that want very low latency at the expense
> of cpu usage and efficiency.
> 
> Already existing APIs (SO_BUSYPOLL and epoll) allow busy polling a NAPI
> backing a socket, but the missing piece is a mechanism to busy poll a
> NAPI instance in a dedicated thread while ignoring available events or
> packets, regardless of the userspace API. Most existing mechanisms are
> designed to work in a pattern where you poll until new packets or events
> are received, after which userspace is expected to handle them.
> 
> As a result, one has to hack together a solution using a mechanism
> intended to receive packets or events, not to simply NAPI poll. NAPI
> threaded busy polling, on the other hand, provides this capability
> natively, independent of any userspace API. This makes it really easy to
> setup and manage.
> 
> For analysis we use an AF_XDP based benchmarking tool `xsk_rr`. The
> description of the tool and how it tries to simulate the real workload
> is following,
> 
> - It sends UDP packets between 2 machines.
> - The client machine sends packets at a fixed frequency. To maintain the
>    frequency of the packet being sent, we use open-loop sampling. That is
>    the packets are sent in a separate thread.
> - The server replies to the packet inline by reading the pkt from the
>    recv ring and replies using the tx ring.
> - To simulate the application processing time, we use a configurable
>    delay in usecs on the client side after a reply is received from the
>    server.
> 
> The xsk_rr tool is posted separately as an RFC for tools/testing/selftest.
> 
> We use this tool with following napi polling configurations,
> 
> - Interrupts only
> - SO_BUSYPOLL (inline in the same thread where the client receives the
>    packet).
> - SO_BUSYPOLL (separate thread and separate core)
> - Threaded NAPI busypoll
> 
> System is configured using following script in all 4 cases,
> 
> ```
> echo 0 | sudo tee /sys/class/net/eth0/threaded
> echo 0 | sudo tee /proc/sys/kernel/timer_migration
> echo off | sudo tee  /sys/devices/system/cpu/smt/control
> 
> sudo ethtool -L eth0 rx 1 tx 1
> sudo ethtool -G eth0 rx 1024
> 
> echo 0 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
> echo 0 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
> 
>   # pin IRQs on CPU 2
> IRQS="$(gawk '/eth0-(TxRx-)?1/ {match($1, /([0-9]+)/, arr); \
> 				print arr[0]}' < /proc/interrupts)"
> for irq in "${IRQS}"; \
> 	do echo 2 | sudo tee /proc/irq/$irq/smp_affinity_list; done
> 
> echo -1 | sudo tee /proc/sys/kernel/sched_rt_runtime_us
> 
> for i in /sys/devices/virtual/workqueue/*/cpumask; \
> 			do echo $i; echo 1,2,3,4,5,6 > $i; done
> 
> if [[ -z "$1" ]]; then
>    echo 400 | sudo tee /proc/sys/net/core/busy_read
>    echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>    echo 15000   | sudo tee /sys/class/net/eth0/gro_flush_timeout
> fi
> 
> sudo ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 0 tx-usecs 0
> 
> if [[ "$1" == "enable_threaded" ]]; then
>    echo 0 | sudo tee /proc/sys/net/core/busy_poll
>    echo 0 | sudo tee /proc/sys/net/core/busy_read
>    echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>    echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
>    NAPI_ID=$(ynl --family netdev --output-json --do queue-get \
>      --json '{"ifindex": '${IFINDEX}', "id": '0', "type": "rx"}' | jq '."napi-id"')
> 
>    ynl --family netdev --json '{"id": "'${NAPI_ID}'", "threaded": "busy-poll-enabled"}'
> 
>    NAPI_T=$(ynl --family netdev --output-json --do napi-get \
>      --json '{"id": "'$NAPI_ID'"}' | jq '."pid"')
> 
>    sudo chrt -f  -p 50 $NAPI_T
> 
>    # pin threaded poll thread to CPU 2
>    sudo taskset -pc 2 $NAPI_T
> fi
> 
> if [[ "$1" == "enable_interrupt" ]]; then
>    echo 0 | sudo tee /proc/sys/net/core/busy_read
>    echo 0 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>    echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
> fi
> ```
> 
> To enable various configurations, script can be run as following,
> 
> - Interrupt Only
>    ```
>    <script> enable_interrupt
>    ```
> 
> - SO_BUSYPOLL (no arguments to script)
>    ```
>    <script>
>    ```
> 
> - NAPI threaded busypoll
>    ```
>    <script> enable_threaded
>    ```
> 
> If using idpf, the script needs to be run again after launching the
> workload just to make sure that the configurations are not reverted. As
> idpf reverts some configurations on software reset when AF_XDP program
> is attached.
> 
> Once configured, the workload is run with various configurations using
> following commands. Set period (1/frequency) and delay in usecs to
> produce results for packet frequency and application processing delay.
> 
>   ## Interrupt Only and SO_BUSYPOLL (inline)
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 -h -v
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v
> ```
> 
>   ## SO_BUSYPOLL(done in separate core using recvfrom)
> 
> Argument -t spawns a seprate thread and continuously calls recvfrom.
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> 	-h -v -t
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -t
> ```
> 
>   ## NAPI Threaded Busy Poll
> 
> Argument -n skips the recvfrom call as there is no recv kick needed.
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> 	-h -v -n
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -n
> ```
> 
> | Experiment | interrupts | SO_BUSYPOLL | SO_BUSYPOLL(separate) | NAPI threaded |
> |---|---|---|---|---|
> | 12 Kpkt/s + 0us delay | | | | |
> |  | p5: 12700 | p5: 12900 | p5: 13300 | p5: 12800 |
> |  | p50: 13100 | p50: 13600 | p50: 14100 | p50: 13000 |
> |  | p95: 13200 | p95: 13800 | p95: 14400 | p95: 13000 |
> |  | p99: 13200 | p99: 13800 | p99: 14400 | p99: 13000 |
> | 32 Kpkt/s + 30us delay | | | | |
> |  | p5: 19900 | p5: 16600 | p5: 13100 | p5: 12800 |
> |  | p50: 21100 | p50: 17000 | p50: 13700 | p50: 13000 |
> |  | p95: 21200 | p95: 17100 | p95: 14000 | p95: 13000 |
> |  | p99: 21200 | p99: 17100 | p99: 14000 | p99: 13000 |
> | 125 Kpkt/s + 6us delay | | | | |
> |  | p5: 14600 | p5: 17100 | p5: 13300 | p5: 12900 |
> |  | p50: 15400 | p50: 17400 | p50: 13800 | p50: 13100 |
> |  | p95: 15600 | p95: 17600 | p95: 14000 | p95: 13100 |
> |  | p99: 15600 | p99: 17600 | p99: 14000 | p99: 13100 |
> | 12 Kpkt/s + 78us delay | | | | |
> |  | p5: 14100 | p5: 16700 | p5: 13200 | p5: 12600 |
> |  | p50: 14300 | p50: 17100 | p50: 13900 | p50: 12800 |
> |  | p95: 14300 | p95: 17200 | p95: 14200 | p95: 12800 |
> |  | p99: 14300 | p99: 17200 | p99: 14200 | p99: 12800 |
> | 25 Kpkt/s + 38us delay | | | | |
> |  | p5: 19900 | p5: 16600 | p5: 13000 | p5: 12700 |
> |  | p50: 21000 | p50: 17100 | p50: 13800 | p50: 12900 |
> |  | p95: 21100 | p95: 17100 | p95: 14100 | p95: 12900 |
> |  | p99: 21100 | p99: 17100 | p99: 14100 | p99: 12900 |
> 
>   ## Observations
> 
> - Here without application processing all the approaches give the same
>    latency within 1usecs range and NAPI threaded gives minimum latency.
> - With application processing the latency increases by 3-4usecs when
>    doing inline polling.
> - Using a dedicated core to drive napi polling keeps the latency same
>    even with application processing. This is observed both in userspace
>    and threaded napi (in kernel).
> - Using napi threaded polling in kernel gives lower latency by
>    1-2usecs as compared to userspace driven polling in separate core.
> - Even on a dedicated core, SO_BUSYPOLL adds around 1-2usecs of latency.
>    This is because it doesn't continuously busy poll until events are
>    ready. Instead, it returns after polling only once, requiring the
>    process to re-invoke the syscall for each poll, which requires a new
>    enter/leave kernel cycle and the setup/teardown of the busy poll for
>    every single poll attempt.
> - With application processing userspace will get the packet from recv
>    ring and spend some time doing application processing and then do napi
>    polling. While application processing is happening a dedicated core
>    doing napi polling can pull the packet of the NAPI RX queue and
>    populate the AF_XDP recv ring. This means that when the application
>    thread is done with application processing it has new packets ready to
>    recv and process in recv ring.
> - Napi threaded busy polling in the kernel with a dedicated core gives
>    the consistent P5-P99 latency.

We have been through this several times, but I want to repeat & 
summarize my concerns:

While the proposed mechanism has some (limited) merit in specific 
circumstances, it also has negative side effects in others. I believe 
the cover letter for such a new feature should provide a balanced 
description of pros and cons, along with a reproducible and sufficiently 
comprehensive evaluation. Otherwise I worry that developers & 
practitioners will look at this mechanism in the future and be tempted 
to use it in scenarios where it does not help, but only wastes 
resources. It does not seem hard to do this and also provide experiment 
setup, scripts, and benchmarks in a coherent repo that is easy to use, 
instead of the current piecewise presentation.

The xsk_rr tool represents a specific (niche) case that is likely 
relevant, but a comprehensive evaluation would also include mainstream 
communication patterns using an existing benchmarking tool. While 
resource usage is claimed to not be a concern in this particular use 
case, it might be quite relevant in other use cases and as such, should 
be documented.

Further to the above, the most convincing results presented here are 
cherry-picked and only use parameter combinations that represent an 
unrealistic scenario of near-100% load, instead of showing a range of 
parameter combinations. In fact, even the conceptually easiest case of 0 
delay / 0 period is omitted.

Best,
Martin


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-12  8:08 ` [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Martin Karsten
@ 2025-09-12 16:22   ` Willem de Bruijn
  2025-09-13  2:07   ` Jakub Kicinski
  1 sibling, 0 replies; 11+ messages in thread
From: Willem de Bruijn @ 2025-09-12 16:22 UTC (permalink / raw)
  To: Martin Karsten, Samiullah Khawaja, Jakub Kicinski,
	David S . Miller, Eric Dumazet, Paolo Abeni, almasrymina, willemb
  Cc: Joe Damato, netdev

Martin Karsten wrote:
> On 2025-09-11 17:28, Samiullah Khawaja wrote:
> > Extend the already existing support of threaded napi poll to do continuous
> > busy polling.
> > 
> > This is used for doing continuous polling of napi to fetch descriptors
> > from backing RX/TX queues for low latency applications. Allow enabling
> > of threaded busypoll using netlink so this can be enabled on a set of
> > dedicated napis for low latency applications.
> > 
> > Once enabled user can fetch the PID of the kthread doing NAPI polling
> > and set affinity, priority and scheduler for it depending on the
> > low-latency requirements.
> > 
> > Extend the netlink interface to allow enabling/disabling threaded
> > busypolling at individual napi level.
> > 
> > We use this for our AF_XDP based hard low-latency usecase with usecs
> > level latency requirement. For our usecase we want low jitter and stable
> > latency at P99.
> > 
> > Following is an analysis and comparison of available (and compatible)
> > busy poll interfaces for a low latency usecase with stable P99. This can
> > be suitable for applications that want very low latency at the expense
> > of cpu usage and efficiency.
> > 
> > Already existing APIs (SO_BUSYPOLL and epoll) allow busy polling a NAPI
> > backing a socket, but the missing piece is a mechanism to busy poll a
> > NAPI instance in a dedicated thread while ignoring available events or
> > packets, regardless of the userspace API. Most existing mechanisms are
> > designed to work in a pattern where you poll until new packets or events
> > are received, after which userspace is expected to handle them.
> > 
> > As a result, one has to hack together a solution using a mechanism
> > intended to receive packets or events, not to simply NAPI poll. NAPI
> > threaded busy polling, on the other hand, provides this capability
> > natively, independent of any userspace API. This makes it really easy to
> > setup and manage.
> > 
> > For analysis we use an AF_XDP based benchmarking tool `xsk_rr`. The
> > description of the tool and how it tries to simulate the real workload
> > is following,
> > 
> > - It sends UDP packets between 2 machines.
> > - The client machine sends packets at a fixed frequency. To maintain the
> >    frequency of the packet being sent, we use open-loop sampling. That is
> >    the packets are sent in a separate thread.
> > - The server replies to the packet inline by reading the pkt from the
> >    recv ring and replies using the tx ring.
> > - To simulate the application processing time, we use a configurable
> >    delay in usecs on the client side after a reply is received from the
> >    server.
> > 
> > The xsk_rr tool is posted separately as an RFC for tools/testing/selftest.
> > 
> > We use this tool with following napi polling configurations,
> > 
> > - Interrupts only
> > - SO_BUSYPOLL (inline in the same thread where the client receives the
> >    packet).
> > - SO_BUSYPOLL (separate thread and separate core)
> > - Threaded NAPI busypoll
> > 
> > System is configured using following script in all 4 cases,
> > 
> > ```
> > echo 0 | sudo tee /sys/class/net/eth0/threaded
> > echo 0 | sudo tee /proc/sys/kernel/timer_migration
> > echo off | sudo tee  /sys/devices/system/cpu/smt/control
> > 
> > sudo ethtool -L eth0 rx 1 tx 1
> > sudo ethtool -G eth0 rx 1024
> > 
> > echo 0 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
> > echo 0 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
> > 
> >   # pin IRQs on CPU 2
> > IRQS="$(gawk '/eth0-(TxRx-)?1/ {match($1, /([0-9]+)/, arr); \
> > 				print arr[0]}' < /proc/interrupts)"
> > for irq in "${IRQS}"; \
> > 	do echo 2 | sudo tee /proc/irq/$irq/smp_affinity_list; done
> > 
> > echo -1 | sudo tee /proc/sys/kernel/sched_rt_runtime_us
> > 
> > for i in /sys/devices/virtual/workqueue/*/cpumask; \
> > 			do echo $i; echo 1,2,3,4,5,6 > $i; done
> > 
> > if [[ -z "$1" ]]; then
> >    echo 400 | sudo tee /proc/sys/net/core/busy_read
> >    echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
> >    echo 15000   | sudo tee /sys/class/net/eth0/gro_flush_timeout
> > fi
> > 
> > sudo ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 0 tx-usecs 0
> > 
> > if [[ "$1" == "enable_threaded" ]]; then
> >    echo 0 | sudo tee /proc/sys/net/core/busy_poll
> >    echo 0 | sudo tee /proc/sys/net/core/busy_read
> >    echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
> >    echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
> >    NAPI_ID=$(ynl --family netdev --output-json --do queue-get \
> >      --json '{"ifindex": '${IFINDEX}', "id": '0', "type": "rx"}' | jq '."napi-id"')
> > 
> >    ynl --family netdev --json '{"id": "'${NAPI_ID}'", "threaded": "busy-poll-enabled"}'
> > 
> >    NAPI_T=$(ynl --family netdev --output-json --do napi-get \
> >      --json '{"id": "'$NAPI_ID'"}' | jq '."pid"')
> > 
> >    sudo chrt -f  -p 50 $NAPI_T
> > 
> >    # pin threaded poll thread to CPU 2
> >    sudo taskset -pc 2 $NAPI_T
> > fi
> > 
> > if [[ "$1" == "enable_interrupt" ]]; then
> >    echo 0 | sudo tee /proc/sys/net/core/busy_read
> >    echo 0 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
> >    echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
> > fi
> > ```
> > 
> > To enable various configurations, script can be run as following,
> > 
> > - Interrupt Only
> >    ```
> >    <script> enable_interrupt
> >    ```
> > 
> > - SO_BUSYPOLL (no arguments to script)
> >    ```
> >    <script>
> >    ```
> > 
> > - NAPI threaded busypoll
> >    ```
> >    <script> enable_threaded
> >    ```
> > 
> > If using idpf, the script needs to be run again after launching the
> > workload just to make sure that the configurations are not reverted. As
> > idpf reverts some configurations on software reset when AF_XDP program
> > is attached.
> > 
> > Once configured, the workload is run with various configurations using
> > following commands. Set period (1/frequency) and delay in usecs to
> > produce results for packet frequency and application processing delay.
> > 
> >   ## Interrupt Only and SO_BUSYPOLL (inline)
> > 
> > - Server
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 -h -v
> > ```
> > 
> > - Client
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> > 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v
> > ```
> > 
> >   ## SO_BUSYPOLL(done in separate core using recvfrom)
> > 
> > Argument -t spawns a seprate thread and continuously calls recvfrom.
> > 
> > - Server
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> > 	-h -v -t
> > ```
> > 
> > - Client
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> > 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -t
> > ```
> > 
> >   ## NAPI Threaded Busy Poll
> > 
> > Argument -n skips the recvfrom call as there is no recv kick needed.
> > 
> > - Server
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> > 	-h -v -n
> > ```
> > 
> > - Client
> > ```
> > sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> > 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> > 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -n
> > ```
> > 
> > | Experiment | interrupts | SO_BUSYPOLL | SO_BUSYPOLL(separate) | NAPI threaded |
> > |---|---|---|---|---|
> > | 12 Kpkt/s + 0us delay | | | | |
> > |  | p5: 12700 | p5: 12900 | p5: 13300 | p5: 12800 |
> > |  | p50: 13100 | p50: 13600 | p50: 14100 | p50: 13000 |
> > |  | p95: 13200 | p95: 13800 | p95: 14400 | p95: 13000 |
> > |  | p99: 13200 | p99: 13800 | p99: 14400 | p99: 13000 |
> > | 32 Kpkt/s + 30us delay | | | | |
> > |  | p5: 19900 | p5: 16600 | p5: 13100 | p5: 12800 |
> > |  | p50: 21100 | p50: 17000 | p50: 13700 | p50: 13000 |
> > |  | p95: 21200 | p95: 17100 | p95: 14000 | p95: 13000 |
> > |  | p99: 21200 | p99: 17100 | p99: 14000 | p99: 13000 |
> > | 125 Kpkt/s + 6us delay | | | | |
> > |  | p5: 14600 | p5: 17100 | p5: 13300 | p5: 12900 |
> > |  | p50: 15400 | p50: 17400 | p50: 13800 | p50: 13100 |
> > |  | p95: 15600 | p95: 17600 | p95: 14000 | p95: 13100 |
> > |  | p99: 15600 | p99: 17600 | p99: 14000 | p99: 13100 |
> > | 12 Kpkt/s + 78us delay | | | | |
> > |  | p5: 14100 | p5: 16700 | p5: 13200 | p5: 12600 |
> > |  | p50: 14300 | p50: 17100 | p50: 13900 | p50: 12800 |
> > |  | p95: 14300 | p95: 17200 | p95: 14200 | p95: 12800 |
> > |  | p99: 14300 | p99: 17200 | p99: 14200 | p99: 12800 |
> > | 25 Kpkt/s + 38us delay | | | | |
> > |  | p5: 19900 | p5: 16600 | p5: 13000 | p5: 12700 |
> > |  | p50: 21000 | p50: 17100 | p50: 13800 | p50: 12900 |
> > |  | p95: 21100 | p95: 17100 | p95: 14100 | p95: 12900 |
> > |  | p99: 21100 | p99: 17100 | p99: 14100 | p99: 12900 |
> > 
> >   ## Observations
> > 
> > - Here without application processing all the approaches give the same
> >    latency within 1usecs range and NAPI threaded gives minimum latency.
> > - With application processing the latency increases by 3-4usecs when
> >    doing inline polling.
> > - Using a dedicated core to drive napi polling keeps the latency same
> >    even with application processing. This is observed both in userspace
> >    and threaded napi (in kernel).
> > - Using napi threaded polling in kernel gives lower latency by
> >    1-2usecs as compared to userspace driven polling in separate core.
> > - Even on a dedicated core, SO_BUSYPOLL adds around 1-2usecs of latency.
> >    This is because it doesn't continuously busy poll until events are
> >    ready. Instead, it returns after polling only once, requiring the
> >    process to re-invoke the syscall for each poll, which requires a new
> >    enter/leave kernel cycle and the setup/teardown of the busy poll for
> >    every single poll attempt.
> > - With application processing userspace will get the packet from recv
> >    ring and spend some time doing application processing and then do napi
> >    polling. While application processing is happening a dedicated core
> >    doing napi polling can pull the packet of the NAPI RX queue and
> >    populate the AF_XDP recv ring. This means that when the application
> >    thread is done with application processing it has new packets ready to
> >    recv and process in recv ring.
> > - Napi threaded busy polling in the kernel with a dedicated core gives
> >    the consistent P5-P99 latency.
> 
> We have been through this several times, but I want to repeat & 
> summarize my concerns:
> 
> While the proposed mechanism has some (limited) merit in specific 
> circumstances, it also has negative side effects in others. I believe 
> the cover letter for such a new feature should provide a balanced 
> description of pros and cons, along with a reproducible and sufficiently 
> comprehensive evaluation. Otherwise I worry that developers & 
> practitioners will look at this mechanism in the future and be tempted 
> to use it in scenarios where it does not help, but only wastes 
> resources. It does not seem hard to do this 

Your feedback has been heard.

This warning/limitation was intended to be covered in the
Documentation, but on rereading that can be made stronger.
I think the Documentation will be more valuable to users for this than
the cover letter. We'll revise.

Side-note: removing the device-wide enablement also greatly reduces
the odds that uniformed users blindly enable this because they think
busypolling is a strict good.

> and also provide experiment 
> setup, scripts, and benchmarks in a coherent repo that is easy to use, 
> instead of the current piecewise presentation.
 
With xsk_rr the series shares a reproducible benchmark, with detailed
setup instructions in the cover letter. This is not an academic
publication. That is a level of measurement of new kernel features
exceeding most. So here I disagree.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-11 21:28 [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Samiullah Khawaja
                   ` (2 preceding siblings ...)
  2025-09-12  8:08 ` [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Martin Karsten
@ 2025-09-12 20:22 ` Stanislav Fomichev
  3 siblings, 0 replies; 11+ messages in thread
From: Stanislav Fomichev @ 2025-09-12 20:22 UTC (permalink / raw)
  To: Samiullah Khawaja
  Cc: Jakub Kicinski, David S . Miller , Eric Dumazet, Paolo Abeni,
	almasrymina, willemb, Joe Damato, mkarsten, netdev

On 09/11, Samiullah Khawaja wrote:
> Extend the already existing support of threaded napi poll to do continuous
> busy polling.
> 
> This is used for doing continuous polling of napi to fetch descriptors
> from backing RX/TX queues for low latency applications. Allow enabling
> of threaded busypoll using netlink so this can be enabled on a set of
> dedicated napis for low latency applications.
> 
> Once enabled user can fetch the PID of the kthread doing NAPI polling
> and set affinity, priority and scheduler for it depending on the
> low-latency requirements.
> 
> Extend the netlink interface to allow enabling/disabling threaded
> busypolling at individual napi level.
> 
> We use this for our AF_XDP based hard low-latency usecase with usecs
> level latency requirement. For our usecase we want low jitter and stable
> latency at P99.
> 
> Following is an analysis and comparison of available (and compatible)
> busy poll interfaces for a low latency usecase with stable P99. This can
> be suitable for applications that want very low latency at the expense
> of cpu usage and efficiency.
> 
> Already existing APIs (SO_BUSYPOLL and epoll) allow busy polling a NAPI
> backing a socket, but the missing piece is a mechanism to busy poll a
> NAPI instance in a dedicated thread while ignoring available events or
> packets, regardless of the userspace API. Most existing mechanisms are
> designed to work in a pattern where you poll until new packets or events
> are received, after which userspace is expected to handle them.
> 
> As a result, one has to hack together a solution using a mechanism
> intended to receive packets or events, not to simply NAPI poll. NAPI
> threaded busy polling, on the other hand, provides this capability
> natively, independent of any userspace API. This makes it really easy to
> setup and manage.
> 
> For analysis we use an AF_XDP based benchmarking tool `xsk_rr`. The
> description of the tool and how it tries to simulate the real workload
> is following,
> 
> - It sends UDP packets between 2 machines.
> - The client machine sends packets at a fixed frequency. To maintain the
>   frequency of the packet being sent, we use open-loop sampling. That is
>   the packets are sent in a separate thread.
> - The server replies to the packet inline by reading the pkt from the
>   recv ring and replies using the tx ring.
> - To simulate the application processing time, we use a configurable
>   delay in usecs on the client side after a reply is received from the
>   server.
> 
> The xsk_rr tool is posted separately as an RFC for tools/testing/selftest.
> 
> We use this tool with following napi polling configurations,
> 
> - Interrupts only
> - SO_BUSYPOLL (inline in the same thread where the client receives the
>   packet).
> - SO_BUSYPOLL (separate thread and separate core)
> - Threaded NAPI busypoll
> 
> System is configured using following script in all 4 cases,
> 
> ```
> echo 0 | sudo tee /sys/class/net/eth0/threaded
> echo 0 | sudo tee /proc/sys/kernel/timer_migration
> echo off | sudo tee  /sys/devices/system/cpu/smt/control
> 
> sudo ethtool -L eth0 rx 1 tx 1
> sudo ethtool -G eth0 rx 1024
> 
> echo 0 | sudo tee /proc/sys/net/core/rps_sock_flow_entries
> echo 0 | sudo tee /sys/class/net/eth0/queues/rx-0/rps_cpus
> 
>  # pin IRQs on CPU 2
> IRQS="$(gawk '/eth0-(TxRx-)?1/ {match($1, /([0-9]+)/, arr); \
> 				print arr[0]}' < /proc/interrupts)"
> for irq in "${IRQS}"; \
> 	do echo 2 | sudo tee /proc/irq/$irq/smp_affinity_list; done
> 
> echo -1 | sudo tee /proc/sys/kernel/sched_rt_runtime_us
> 
> for i in /sys/devices/virtual/workqueue/*/cpumask; \
> 			do echo $i; echo 1,2,3,4,5,6 > $i; done
> 
> if [[ -z "$1" ]]; then
>   echo 400 | sudo tee /proc/sys/net/core/busy_read
>   echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>   echo 15000   | sudo tee /sys/class/net/eth0/gro_flush_timeout
> fi
> 
> sudo ethtool -C eth0 adaptive-rx off adaptive-tx off rx-usecs 0 tx-usecs 0
> 
> if [[ "$1" == "enable_threaded" ]]; then
>   echo 0 | sudo tee /proc/sys/net/core/busy_poll
>   echo 0 | sudo tee /proc/sys/net/core/busy_read
>   echo 100 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>   echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
>   NAPI_ID=$(ynl --family netdev --output-json --do queue-get \
>     --json '{"ifindex": '${IFINDEX}', "id": '0', "type": "rx"}' | jq '."napi-id"')
> 
>   ynl --family netdev --json '{"id": "'${NAPI_ID}'", "threaded": "busy-poll-enabled"}'
> 
>   NAPI_T=$(ynl --family netdev --output-json --do napi-get \
>     --json '{"id": "'$NAPI_ID'"}' | jq '."pid"')
> 
>   sudo chrt -f  -p 50 $NAPI_T
> 
>   # pin threaded poll thread to CPU 2
>   sudo taskset -pc 2 $NAPI_T
> fi
> 
> if [[ "$1" == "enable_interrupt" ]]; then
>   echo 0 | sudo tee /proc/sys/net/core/busy_read
>   echo 0 | sudo tee /sys/class/net/eth0/napi_defer_hard_irqs
>   echo 15000 | sudo tee /sys/class/net/eth0/gro_flush_timeout
> fi
> ```
> 
> To enable various configurations, script can be run as following,
> 
> - Interrupt Only
>   ```
>   <script> enable_interrupt
>   ```
> 
> - SO_BUSYPOLL (no arguments to script)
>   ```
>   <script>
>   ```
> 
> - NAPI threaded busypoll
>   ```
>   <script> enable_threaded
>   ```
> 
> If using idpf, the script needs to be run again after launching the
> workload just to make sure that the configurations are not reverted. As
> idpf reverts some configurations on software reset when AF_XDP program
> is attached.
> 
> Once configured, the workload is run with various configurations using
> following commands. Set period (1/frequency) and delay in usecs to
> produce results for packet frequency and application processing delay.
> 
>  ## Interrupt Only and SO_BUSYPOLL (inline)
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 -h -v
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v
> ```
> 
>  ## SO_BUSYPOLL(done in separate core using recvfrom)
> 
> Argument -t spawns a seprate thread and continuously calls recvfrom.
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> 	-h -v -t
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -t
> ```
> 
>  ## NAPI Threaded Busy Poll
> 
> Argument -n skips the recvfrom call as there is no recv kick needed.
> 
> - Server
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-D <IP-dest> -S <IP-src> -M <MAC-dst> -m <MAC-src> -p 54321 \
> 	-h -v -n
> ```
> 
> - Client
> ```
> sudo chrt -f 50 taskset -c 3-5 ./xsk_rr -o 0 -B 400 -i eth0 -4 \
> 	-S <IP-src> -D <IP-dest> -m <MAC-src> -M <MAC-dst> -p 54321 \
> 	-P <Period-usecs> -d <Delay-usecs>  -T -l 1 -v -n
> ```
> 
> | Experiment | interrupts | SO_BUSYPOLL | SO_BUSYPOLL(separate) | NAPI threaded |
> |---|---|---|---|---|
> | 12 Kpkt/s + 0us delay | | | | |
> |  | p5: 12700 | p5: 12900 | p5: 13300 | p5: 12800 |
> |  | p50: 13100 | p50: 13600 | p50: 14100 | p50: 13000 |
> |  | p95: 13200 | p95: 13800 | p95: 14400 | p95: 13000 |
> |  | p99: 13200 | p99: 13800 | p99: 14400 | p99: 13000 |
> | 32 Kpkt/s + 30us delay | | | | |
> |  | p5: 19900 | p5: 16600 | p5: 13100 | p5: 12800 |
> |  | p50: 21100 | p50: 17000 | p50: 13700 | p50: 13000 |
> |  | p95: 21200 | p95: 17100 | p95: 14000 | p95: 13000 |
> |  | p99: 21200 | p99: 17100 | p99: 14000 | p99: 13000 |
> | 125 Kpkt/s + 6us delay | | | | |
> |  | p5: 14600 | p5: 17100 | p5: 13300 | p5: 12900 |
> |  | p50: 15400 | p50: 17400 | p50: 13800 | p50: 13100 |
> |  | p95: 15600 | p95: 17600 | p95: 14000 | p95: 13100 |
> |  | p99: 15600 | p99: 17600 | p99: 14000 | p99: 13100 |
> | 12 Kpkt/s + 78us delay | | | | |
> |  | p5: 14100 | p5: 16700 | p5: 13200 | p5: 12600 |
> |  | p50: 14300 | p50: 17100 | p50: 13900 | p50: 12800 |
> |  | p95: 14300 | p95: 17200 | p95: 14200 | p95: 12800 |
> |  | p99: 14300 | p99: 17200 | p99: 14200 | p99: 12800 |
> | 25 Kpkt/s + 38us delay | | | | |
> |  | p5: 19900 | p5: 16600 | p5: 13000 | p5: 12700 |
> |  | p50: 21000 | p50: 17100 | p50: 13800 | p50: 12900 |
> |  | p95: 21100 | p95: 17100 | p95: 14100 | p95: 12900 |
> |  | p99: 21100 | p99: 17100 | p99: 14100 | p99: 12900 |
> 
>  ## Observations
> 
> - Here without application processing all the approaches give the same
>   latency within 1usecs range and NAPI threaded gives minimum latency.
> - With application processing the latency increases by 3-4usecs when
>   doing inline polling.
> - Using a dedicated core to drive napi polling keeps the latency same
>   even with application processing. This is observed both in userspace
>   and threaded napi (in kernel).
> - Using napi threaded polling in kernel gives lower latency by
>   1-2usecs as compared to userspace driven polling in separate core.
> - Even on a dedicated core, SO_BUSYPOLL adds around 1-2usecs of latency.
>   This is because it doesn't continuously busy poll until events are
>   ready. Instead, it returns after polling only once, requiring the
>   process to re-invoke the syscall for each poll, which requires a new
>   enter/leave kernel cycle and the setup/teardown of the busy poll for
>   every single poll attempt.
> - With application processing userspace will get the packet from recv
>   ring and spend some time doing application processing and then do napi
>   polling. While application processing is happening a dedicated core
>   doing napi polling can pull the packet of the NAPI RX queue and
>   populate the AF_XDP recv ring. This means that when the application
>   thread is done with application processing it has new packets ready to
>   recv and process in recv ring.
> - Napi threaded busy polling in the kernel with a dedicated core gives
>   the consistent P5-P99 latency.
> 
> Following histogram is generated to measure the time spent in recvfrom
> while using inline thread with SO_BUSYPOLL. The histogram is generated
> using the following bpftrace command. In this experiment there are 32K
> packets per second and the application processing delay is 30usecs. This
> is to measure whether there is significant time spent pulling packets
> from the descriptor queue that it will affect the overall latency if
> done inline.
> 
> ```
> bpftrace -e '
>         kprobe:xsk_recvmsg {
>                 @start[tid] = nsecs;
>         }
>         kretprobe:xsk_recvmsg {
>                 if (@start[tid]) {
>                         $sample = (nsecs - @start[tid]);
>                         @xsk_recvfrom_hist = hist($sample);
>                         delete(@start[tid]);
>                 }
>         }
>         END { clear(@start);}'
> ```
> 
> Here in case of inline busypolling around 35 percent of calls are taking
> 1-2usecs and around 50 percent are taking 0.5-2usecs.
> 
> @xsk_recvfrom_hist:
> [128, 256)         24073 |@@@@@@@@@@@@@@@@@@@@@@                              |
> [256, 512)         55633 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
> [512, 1K)          20974 |@@@@@@@@@@@@@@@@@@@                                 |
> [1K, 2K)           34234 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                     |
> [2K, 4K)            3266 |@@@                                                 |
> [4K, 8K)              19 |                                                    |
> 
> v9:
>  - Unset NAPI_STATE_THREADED_BUSY_POLL when stopping napi kthread to
>    prevent network disruption as reported by Martin Karsten.
>  - Updated napi threaded busy poll enable instructions to use netlink
>    instead of sysfs. This is because the sysfs mechanism to enable napi
>    threaded busy poll is removed.

Acked-by: Stanislav Fomichev <sdf@fomichev.me>

On a high level I think at this point there is enough evidence in the
cover letter to explain why kthread mode is useful (vs other busy
polling options from userspace). But up to the maintainers on whether
the added complexity is justified.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling
  2025-09-11 21:29 ` [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling Samiullah Khawaja
@ 2025-09-13  1:46   ` Jakub Kicinski
  2025-09-15 16:24     ` Samiullah Khawaja
  0 siblings, 1 reply; 11+ messages in thread
From: Jakub Kicinski @ 2025-09-13  1:46 UTC (permalink / raw)
  To: Samiullah Khawaja
  Cc: David S . Miller , Eric Dumazet, Paolo Abeni, almasrymina,
	willemb, Joe Damato, mkarsten, netdev

On Thu, 11 Sep 2025 21:29:00 +0000 Samiullah Khawaja wrote:
> Add a new state to napi state enum:
> 
> - NAPI_STATE_THREADED_BUSY_POLL
>   Threaded busy poll is enabled/running for this napi.
> 
> Following changes are introduced in the napi scheduling and state logic:
> 
> - When threaded busy poll is enabled through netlink it also enables
>   NAPI_STATE_THREADED so a kthread is created per napi. It also sets
>   NAPI_STATE_THREADED_BUSY_POLL bit on each napi to indicate that it is
>   going to busy poll the napi.
> 
> - When napi is scheduled with NAPI_STATE_SCHED_THREADED and associated
>   kthread is woken up, the kthread owns the context. If
>   NAPI_STATE_THREADED_BUSY_POLL and NAPI_STATE_SCHED_THREADED both are
>   set then it means that kthread can busy poll.
> 
> - To keep busy polling and to avoid scheduling of the interrupts, the
>   napi_complete_done returns false when both NAPI_STATE_SCHED_THREADED
>   and NAPI_STATE_THREADED_BUSY_POLL flags are set. Also
>   napi_complete_done returns early to avoid the
>   NAPI_STATE_SCHED_THREADED being unset.
> 
> - If at any point NAPI_STATE_THREADED_BUSY_POLL is unset, the
>   napi_complete_done will run and unset the NAPI_STATE_SCHED_THREADED
>   bit also. This will make the associated kthread go to sleep as per
>   existing logic.
> 
> Signed-off-by: Samiullah Khawaja <skhawaja@google.com>

I think you need to spend some time trying to make this code more..
elegant. 

> diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml
> index c035dc0f64fd..ce28e8708a87 100644
> --- a/Documentation/netlink/specs/netdev.yaml
> +++ b/Documentation/netlink/specs/netdev.yaml
> @@ -88,7 +88,7 @@ definitions:
>    -
>      name: napi-threaded
>      type: enum
> -    entries: [disabled, enabled]
> +    entries: [disabled, enabled, busy-poll-enabled]

drop the -enabled

>  attribute-sets:
>    -
> @@ -291,7 +291,8 @@ attribute-sets:
>          name: threaded
>          doc: Whether the NAPI is configured to operate in threaded polling
>               mode. If this is set to enabled then the NAPI context operates
> -             in threaded polling mode.
> +             in threaded polling mode. If this is set to busy-poll-enabled
> +             then the NAPI kthread also does busypolling.

I don't think busypolling is a word? I mean, I don't think English
combines words like this.

> +Threaded NAPI busy polling
> +--------------------------

Please feed the documentation into a grammar checker. A bunch of
articles seems to be missing.

> +Threaded NAPI allows processing of packets from each NAPI in a kthread in
> +kernel. Threaded NAPI busy polling extends this and adds support to do
> +continuous busy polling of this NAPI. This can be used to enable busy polling
> +independent of userspace application or the API (epoll, io_uring, raw sockets)
> +being used in userspace to process the packets.
> +
> +It can be enabled for each NAPI using netlink interface.

Netlink, capital letter

> +For example, using following script:
> +
> +.. code-block:: bash
> +
> +  $ ynl --family netdev --do napi-set \
> +            --json='{"id": 66, "threaded": "busy-poll-enabled"}'
> +
> +
> +Enabling it for each NAPI allows finer control to enable busy pollling for

pollling -> polling

> +only a set of NIC queues which will get traffic with low latency requirements.

A bit of a non-sequitur. Sounds like you just cut off the device-wide
config here.

> +Depending on application requirement, user might want to set affinity of the
> +kthread that is busy polling each NAPI. User might also want to set priority
> +and the scheduler of the thread depending on the latency requirements.
> +
> +For a hard low-latency application, user might want to dedicate the full core
> +for the NAPI polling so the NIC queue descriptors are picked up from the queue
> +as soon as they appear. Once enabled, the NAPI thread will poll the NIC queues
> +continuously without sleeping. This will keep the CPU core busy with 100%
> +usage. For more relaxed low-latency requirement, user might want to share the
> +core with other threads by setting thread affinity and priority.

Is there such a thing a priority in the Linux scheduler? Being more
specific would be useful. I think this code is useful for forwarding
or AF_XDP but normal socket applications would struggle to use this
mode.

>  Threaded NAPI
> diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> index f3a3b761abfb..a88f6596aef7 100644
> --- a/include/linux/netdevice.h
> +++ b/include/linux/netdevice.h
> @@ -427,6 +427,8 @@ enum {
>  	NAPI_STATE_THREADED,		/* The poll is performed inside its own thread*/
>  	NAPI_STATE_SCHED_THREADED,	/* Napi is currently scheduled in threaded mode */
>  	NAPI_STATE_HAS_NOTIFIER,	/* Napi has an IRQ notifier */
> +	NAPI_STATE_THREADED_BUSY_POLL,	/* The threaded napi poller will busy poll */
> +	NAPI_STATE_SCHED_THREADED_BUSY_POLL,  /* The threaded napi poller is busy polling */

I don't get why you need 2 bits to implement this feature.

> @@ -1873,7 +1881,8 @@ enum netdev_reg_state {
>   * 	@addr_len:		Hardware address length
>   *	@upper_level:		Maximum depth level of upper devices.
>   *	@lower_level:		Maximum depth level of lower devices.
> - *	@threaded:		napi threaded state.
> + *	@threaded:		napi threaded mode is disabled, enabled or
> + *				enabled with busy polling.

And you are still updating the device level kdoc.

>   *	@neigh_priv_len:	Used in neigh_alloc()
>   * 	@dev_id:		Used to differentiate devices that share
>   * 				the same link layer address

> @@ -78,6 +78,7 @@
>  #include <linux/slab.h>
>  #include <linux/sched.h>
>  #include <linux/sched/isolation.h>
> +#include <linux/sched/types.h>

Leftover from experiments with setting scheduler params in the core?
Or dare I say Google prod kernel?

>  #include <linux/sched/mm.h>
>  #include <linux/smpboot.h>
>  #include <linux/mutex.h>
> @@ -6619,7 +6620,8 @@ bool napi_complete_done(struct napi_struct *n, int work_done)
>  	 *    the guarantee we will be called later.
>  	 */
>  	if (unlikely(n->state & (NAPIF_STATE_NPSVC |
> -				 NAPIF_STATE_IN_BUSY_POLL)))
> +				 NAPIF_STATE_IN_BUSY_POLL |
> +				 NAPIF_STATE_SCHED_THREADED_BUSY_POLL)))

Why not just set the IN_BUSY_POLL when the thread starts polling?
What's the significance of the distinction?

>  		return false;
>  
>  	if (work_done) {

> +static void napi_set_threaded_state(struct napi_struct *napi,
> +				    enum netdev_napi_threaded threaded)
> +{
> +	unsigned long val;
> +
> +	val = 0;
> +	if (threaded == NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED)
> +		val |= NAPIF_STATE_THREADED_BUSY_POLL;
> +	if (threaded)
> +		val |= NAPIF_STATE_THREADED;

this reads odd, set threaded first then the sub-option

> +	set_mask_bits(&napi->state, NAPIF_STATE_THREADED_BUSY_POLL_MASK, val);

Does this actually have to be atomic? I don't think so.

> +}
> +
>  int napi_set_threaded(struct napi_struct *napi,
>  		      enum netdev_napi_threaded threaded)
>  {
> @@ -7050,7 +7066,7 @@ int napi_set_threaded(struct napi_struct *napi,
>  	} else {
>  		/* Make sure kthread is created before THREADED bit is set. */
>  		smp_mb__before_atomic();
> -		assign_bit(NAPI_STATE_THREADED, &napi->state, threaded);
> +		napi_set_threaded_state(napi, threaded);
>  	}
>  
>  	return 0;
> @@ -7442,7 +7458,9 @@ void napi_disable_locked(struct napi_struct *n)
>  		}
>  
>  		new = val | NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC;
> -		new &= ~(NAPIF_STATE_THREADED | NAPIF_STATE_PREFER_BUSY_POLL);
> +		new &= ~(NAPIF_STATE_THREADED
> +			 | NAPIF_STATE_THREADED_BUSY_POLL
> +			 | NAPIF_STATE_PREFER_BUSY_POLL);

kernel coding style has | at the end of the line.

>  	} while (!try_cmpxchg(&n->state, &val, new));
>  
>  	hrtimer_cancel(&n->timer);
> @@ -7486,7 +7504,7 @@ void napi_enable_locked(struct napi_struct *n)
>  
>  		new = val & ~(NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC);
>  		if (n->dev->threaded && n->thread)
> -			new |= NAPIF_STATE_THREADED;
> +			napi_set_threaded_state(n, n->dev->threaded);
>  	} while (!try_cmpxchg(&n->state, &val, new));
>  }
>  EXPORT_SYMBOL(napi_enable_locked);
> @@ -7654,7 +7672,7 @@ static int napi_thread_wait(struct napi_struct *napi)
>  	return -1;
>  }
>  
> -static void napi_threaded_poll_loop(struct napi_struct *napi)
> +static void napi_threaded_poll_loop(struct napi_struct *napi, bool busy_poll)
>  {
>  	struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
>  	struct softnet_data *sd;
> @@ -7683,22 +7701,58 @@ static void napi_threaded_poll_loop(struct napi_struct *napi)
>  		}
>  		skb_defer_free_flush(sd);
>  		bpf_net_ctx_clear(bpf_net_ctx);
> +
> +		/* Flush too old packets. If HZ < 1000, flush all packets */

Probably better to say something about the condition than just copy 
the comment third time.

> +		if (busy_poll)
> +			gro_flush_normal(&napi->gro, HZ >= 1000);
>  		local_bh_enable();
>  
> -		if (!repoll)
> +		/* If busy polling then do not break here because we need to
> +		 * call cond_resched and rcu_softirq_qs_periodic to prevent
> +		 * watchdog warnings.
> +		 */
> +		if (!repoll && !busy_poll)
>  			break;
>  
>  		rcu_softirq_qs_periodic(last_qs);
>  		cond_resched();
> +
> +		if (!repoll)
> +			break;
>  	}
>  }
>  
>  static int napi_threaded_poll(void *data)
>  {
>  	struct napi_struct *napi = data;
> +	bool busy_poll_sched;
> +	unsigned long val;
> +	bool busy_poll;
> +
> +	while (!napi_thread_wait(napi)) {
> +		/* Once woken up, this means that we are scheduled as threaded
> +		 * napi and this thread owns the napi context, if busy poll

please capitalize NAPI in all comments and docs

> +		 * state is set then busy poll this napi.
> +		 */
> +		val = READ_ONCE(napi->state);
> +		busy_poll = val & NAPIF_STATE_THREADED_BUSY_POLL;
> +		busy_poll_sched = val & NAPIF_STATE_SCHED_THREADED_BUSY_POLL;
> +
> +		/* Do not busy poll if napi is disabled. */

It's not disabled, disable is pending

> +		if (unlikely(val & NAPIF_STATE_DISABLE))

> +			busy_poll = false;
> +
> +		if (busy_poll != busy_poll_sched) {
> +			val = busy_poll ?
> +					NAPIF_STATE_SCHED_THREADED_BUSY_POLL :
> +					0;
> +			set_mask_bits(&napi->state,
> +				      NAPIF_STATE_SCHED_THREADED_BUSY_POLL,
> +				      val);

why set single bit with set_mask_bits() ?

Please improve, IIRC it took quite a lot of reviewer effort to get 
the thread stopping into shape, similar level of effort will not be
afforded again.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-12  8:08 ` [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Martin Karsten
  2025-09-12 16:22   ` Willem de Bruijn
@ 2025-09-13  2:07   ` Jakub Kicinski
  2025-09-13 16:03     ` Martin Karsten
  1 sibling, 1 reply; 11+ messages in thread
From: Jakub Kicinski @ 2025-09-13  2:07 UTC (permalink / raw)
  To: Martin Karsten
  Cc: Samiullah Khawaja, David S . Miller, Eric Dumazet, Paolo Abeni,
	almasrymina, willemb, Joe Damato, netdev

On Fri, 12 Sep 2025 04:08:21 -0400 Martin Karsten wrote:
> The xsk_rr tool represents a specific (niche) case that is likely 
> relevant, but a comprehensive evaluation would also include mainstream 
> communication patterns using an existing benchmarking tool. While 
> resource usage is claimed to not be a concern in this particular use 
> case, it might be quite relevant in other use cases and as such, should 
> be documented.

Thanks a lot for working on this.

Were you able to replicate the results? Would you be willing to perhaps
sketch out a summary of your findings that we could use as the cover
letter / addition to the docs? 

I agree with you that the use cases for this will be very narrow (as
are the use cases for threaded NAPI in the first place, don't get me
started). For forwarding use cases, however, this may be the only
option for busy polling, unfortunately :(

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-13  2:07   ` Jakub Kicinski
@ 2025-09-13 16:03     ` Martin Karsten
  2025-09-15 16:26       ` Samiullah Khawaja
  0 siblings, 1 reply; 11+ messages in thread
From: Martin Karsten @ 2025-09-13 16:03 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Samiullah Khawaja, David S . Miller, Eric Dumazet, Paolo Abeni,
	almasrymina, willemb, Joe Damato, netdev

On 2025-09-12 22:07, Jakub Kicinski wrote:
> On Fri, 12 Sep 2025 04:08:21 -0400 Martin Karsten wrote:
>> The xsk_rr tool represents a specific (niche) case that is likely
>> relevant, but a comprehensive evaluation would also include mainstream
>> communication patterns using an existing benchmarking tool. While
>> resource usage is claimed to not be a concern in this particular use
>> case, it might be quite relevant in other use cases and as such, should
>> be documented.
> 
> Thanks a lot for working on this.
> 
> Were you able to replicate the results? Would you be willing to perhaps
> sketch out a summary of your findings that we could use as the cover
> letter / addition to the docs?
> 
> I agree with you that the use cases for this will be very narrow (as
> are the use cases for threaded NAPI in the first place, don't get me
> started). For forwarding use cases, however, this may be the only
> option for busy polling, unfortunately :(

Yes, to the extent possible (different, old hardware) I am seeing 
similar results for similar test cases.

In terms of summary, I would like to see a qualifying statement added 
prominently to the cover letter, such as:

Note well that threaded napi busy-polling has not been shown to yield 
efficiency or throughput benefits. In contrast, dedicating an entire 
core to busy-polling one NAPI (NIC queue) is rather inefficient. 
However, in certain specific use cases, this mechanism results in lower 
packet processing latency. The experimental testing reported here only 
covers those use cases and does not present a comprehensive evaluation 
of threaded napi busy-polling.

Thanks,
Martin


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling
  2025-09-13  1:46   ` Jakub Kicinski
@ 2025-09-15 16:24     ` Samiullah Khawaja
  0 siblings, 0 replies; 11+ messages in thread
From: Samiullah Khawaja @ 2025-09-15 16:24 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: David S . Miller, Eric Dumazet, Paolo Abeni, almasrymina, willemb,
	Joe Damato, mkarsten, netdev

On Fri, Sep 12, 2025 at 6:46 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Thu, 11 Sep 2025 21:29:00 +0000 Samiullah Khawaja wrote:
> > Add a new state to napi state enum:
> >
> > - NAPI_STATE_THREADED_BUSY_POLL
> >   Threaded busy poll is enabled/running for this napi.
> >
> > Following changes are introduced in the napi scheduling and state logic:
> >
> > - When threaded busy poll is enabled through netlink it also enables
> >   NAPI_STATE_THREADED so a kthread is created per napi. It also sets
> >   NAPI_STATE_THREADED_BUSY_POLL bit on each napi to indicate that it is
> >   going to busy poll the napi.
> >
> > - When napi is scheduled with NAPI_STATE_SCHED_THREADED and associated
> >   kthread is woken up, the kthread owns the context. If
> >   NAPI_STATE_THREADED_BUSY_POLL and NAPI_STATE_SCHED_THREADED both are
> >   set then it means that kthread can busy poll.
> >
> > - To keep busy polling and to avoid scheduling of the interrupts, the
> >   napi_complete_done returns false when both NAPI_STATE_SCHED_THREADED
> >   and NAPI_STATE_THREADED_BUSY_POLL flags are set. Also
> >   napi_complete_done returns early to avoid the
> >   NAPI_STATE_SCHED_THREADED being unset.
> >
> > - If at any point NAPI_STATE_THREADED_BUSY_POLL is unset, the
> >   napi_complete_done will run and unset the NAPI_STATE_SCHED_THREADED
> >   bit also. This will make the associated kthread go to sleep as per
> >   existing logic.
> >
> > Signed-off-by: Samiullah Khawaja <skhawaja@google.com>
>
> I think you need to spend some time trying to make this code more..
> elegant.
Thanks for the review and the feedback. I will work on it for the next revision.
>
> > diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml
> > index c035dc0f64fd..ce28e8708a87 100644
> > --- a/Documentation/netlink/specs/netdev.yaml
> > +++ b/Documentation/netlink/specs/netdev.yaml
> > @@ -88,7 +88,7 @@ definitions:
> >    -
> >      name: napi-threaded
> >      type: enum
> > -    entries: [disabled, enabled]
> > +    entries: [disabled, enabled, busy-poll-enabled]
>
> drop the -enabled
Agreed
>
> >  attribute-sets:
> >    -
> > @@ -291,7 +291,8 @@ attribute-sets:
> >          name: threaded
> >          doc: Whether the NAPI is configured to operate in threaded polling
> >               mode. If this is set to enabled then the NAPI context operates
> > -             in threaded polling mode.
> > +             in threaded polling mode. If this is set to busy-poll-enabled
> > +             then the NAPI kthread also does busypolling.
>
> I don't think busypolling is a word? I mean, I don't think English
> combines words like this.
>
> > +Threaded NAPI busy polling
> > +--------------------------
>
> Please feed the documentation into a grammar checker. A bunch of
> articles seems to be missing.
>
> > +Threaded NAPI allows processing of packets from each NAPI in a kthread in
> > +kernel. Threaded NAPI busy polling extends this and adds support to do
> > +continuous busy polling of this NAPI. This can be used to enable busy polling
> > +independent of userspace application or the API (epoll, io_uring, raw sockets)
> > +being used in userspace to process the packets.
> > +
> > +It can be enabled for each NAPI using netlink interface.
>
> Netlink, capital letter
>
> > +For example, using following script:
> > +
> > +.. code-block:: bash
> > +
> > +  $ ynl --family netdev --do napi-set \
> > +            --json='{"id": 66, "threaded": "busy-poll-enabled"}'
> > +
> > +
> > +Enabling it for each NAPI allows finer control to enable busy pollling for
>
> pollling -> polling
>
> > +only a set of NIC queues which will get traffic with low latency requirements.
>
> A bit of a non-sequitur. Sounds like you just cut off the device-wide
> config here.
Will reword this.
>
> > +Depending on application requirement, user might want to set affinity of the
> > +kthread that is busy polling each NAPI. User might also want to set priority
> > +and the scheduler of the thread depending on the latency requirements.
> > +
> > +For a hard low-latency application, user might want to dedicate the full core
> > +for the NAPI polling so the NIC queue descriptors are picked up from the queue
> > +as soon as they appear. Once enabled, the NAPI thread will poll the NIC queues
> > +continuously without sleeping. This will keep the CPU core busy with 100%
> > +usage. For more relaxed low-latency requirement, user might want to share the
> > +core with other threads by setting thread affinity and priority.
>
> Is there such a thing a priority in the Linux scheduler? Being more
> specific would be useful. I think this code is useful for forwarding
> or AF_XDP but normal socket applications would struggle to use this
> mode.
Will reword this.
>
> >  Threaded NAPI
> > diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
> > index f3a3b761abfb..a88f6596aef7 100644
> > --- a/include/linux/netdevice.h
> > +++ b/include/linux/netdevice.h
> > @@ -427,6 +427,8 @@ enum {
> >       NAPI_STATE_THREADED,            /* The poll is performed inside its own thread*/
> >       NAPI_STATE_SCHED_THREADED,      /* Napi is currently scheduled in threaded mode */
> >       NAPI_STATE_HAS_NOTIFIER,        /* Napi has an IRQ notifier */
> > +     NAPI_STATE_THREADED_BUSY_POLL,  /* The threaded napi poller will busy poll */
> > +     NAPI_STATE_SCHED_THREADED_BUSY_POLL,  /* The threaded napi poller is busy polling */
>
> I don't get why you need 2 bits to implement this feature.
I will reuse the IN_BUSY_POLL bit as you suggested below.
>
> > @@ -1873,7 +1881,8 @@ enum netdev_reg_state {
> >   *   @addr_len:              Hardware address length
> >   *   @upper_level:           Maximum depth level of upper devices.
> >   *   @lower_level:           Maximum depth level of lower devices.
> > - *   @threaded:              napi threaded state.
> > + *   @threaded:              napi threaded mode is disabled, enabled or
> > + *                           enabled with busy polling.
>
> And you are still updating the device level kdoc.
>
> >   *   @neigh_priv_len:        Used in neigh_alloc()
> >   *   @dev_id:                Used to differentiate devices that share
> >   *                           the same link layer address
>
> > @@ -78,6 +78,7 @@
> >  #include <linux/slab.h>
> >  #include <linux/sched.h>
> >  #include <linux/sched/isolation.h>
> > +#include <linux/sched/types.h>
>
> Leftover from experiments with setting scheduler params in the core?
> Or dare I say Google prod kernel?
>
> >  #include <linux/sched/mm.h>
> >  #include <linux/smpboot.h>
> >  #include <linux/mutex.h>
> > @@ -6619,7 +6620,8 @@ bool napi_complete_done(struct napi_struct *n, int work_done)
> >        *    the guarantee we will be called later.
> >        */
> >       if (unlikely(n->state & (NAPIF_STATE_NPSVC |
> > -                              NAPIF_STATE_IN_BUSY_POLL)))
> > +                              NAPIF_STATE_IN_BUSY_POLL |
> > +                              NAPIF_STATE_SCHED_THREADED_BUSY_POLL)))
>
> Why not just set the IN_BUSY_POLL when the thread starts polling?
> What's the significance of the distinction?
Agreed. I think I can reuse the IN_BUSY_POLL bit.
>
> >               return false;
> >
> >       if (work_done) {
>
> > +static void napi_set_threaded_state(struct napi_struct *napi,
> > +                                 enum netdev_napi_threaded threaded)
> > +{
> > +     unsigned long val;
> > +
> > +     val = 0;
> > +     if (threaded == NETDEV_NAPI_THREADED_BUSY_POLL_ENABLED)
> > +             val |= NAPIF_STATE_THREADED_BUSY_POLL;
> > +     if (threaded)
> > +             val |= NAPIF_STATE_THREADED;
>
> this reads odd, set threaded first then the sub-option
>
> > +     set_mask_bits(&napi->state, NAPIF_STATE_THREADED_BUSY_POLL_MASK, val);
>
> Does this actually have to be atomic? I don't think so.
Agreed.
>
> > +}
> > +
> >  int napi_set_threaded(struct napi_struct *napi,
> >                     enum netdev_napi_threaded threaded)
> >  {
> > @@ -7050,7 +7066,7 @@ int napi_set_threaded(struct napi_struct *napi,
> >       } else {
> >               /* Make sure kthread is created before THREADED bit is set. */
> >               smp_mb__before_atomic();
> > -             assign_bit(NAPI_STATE_THREADED, &napi->state, threaded);
> > +             napi_set_threaded_state(napi, threaded);
> >       }
> >
> >       return 0;
> > @@ -7442,7 +7458,9 @@ void napi_disable_locked(struct napi_struct *n)
> >               }
> >
> >               new = val | NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC;
> > -             new &= ~(NAPIF_STATE_THREADED | NAPIF_STATE_PREFER_BUSY_POLL);
> > +             new &= ~(NAPIF_STATE_THREADED
> > +                      | NAPIF_STATE_THREADED_BUSY_POLL
> > +                      | NAPIF_STATE_PREFER_BUSY_POLL);
>
> kernel coding style has | at the end of the line.
>
> >       } while (!try_cmpxchg(&n->state, &val, new));
> >
> >       hrtimer_cancel(&n->timer);
> > @@ -7486,7 +7504,7 @@ void napi_enable_locked(struct napi_struct *n)
> >
> >               new = val & ~(NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC);
> >               if (n->dev->threaded && n->thread)
> > -                     new |= NAPIF_STATE_THREADED;
> > +                     napi_set_threaded_state(n, n->dev->threaded);
> >       } while (!try_cmpxchg(&n->state, &val, new));
> >  }
> >  EXPORT_SYMBOL(napi_enable_locked);
> > @@ -7654,7 +7672,7 @@ static int napi_thread_wait(struct napi_struct *napi)
> >       return -1;
> >  }
> >
> > -static void napi_threaded_poll_loop(struct napi_struct *napi)
> > +static void napi_threaded_poll_loop(struct napi_struct *napi, bool busy_poll)
> >  {
> >       struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx;
> >       struct softnet_data *sd;
> > @@ -7683,22 +7701,58 @@ static void napi_threaded_poll_loop(struct napi_struct *napi)
> >               }
> >               skb_defer_free_flush(sd);
> >               bpf_net_ctx_clear(bpf_net_ctx);
> > +
> > +             /* Flush too old packets. If HZ < 1000, flush all packets */
>
> Probably better to say something about the condition than just copy
> the comment third time.
>
> > +             if (busy_poll)
> > +                     gro_flush_normal(&napi->gro, HZ >= 1000);
> >               local_bh_enable();
> >
> > -             if (!repoll)
> > +             /* If busy polling then do not break here because we need to
> > +              * call cond_resched and rcu_softirq_qs_periodic to prevent
> > +              * watchdog warnings.
> > +              */
> > +             if (!repoll && !busy_poll)
> >                       break;
> >
> >               rcu_softirq_qs_periodic(last_qs);
> >               cond_resched();
> > +
> > +             if (!repoll)
> > +                     break;
> >       }
> >  }
> >
> >  static int napi_threaded_poll(void *data)
> >  {
> >       struct napi_struct *napi = data;
> > +     bool busy_poll_sched;
> > +     unsigned long val;
> > +     bool busy_poll;
> > +
> > +     while (!napi_thread_wait(napi)) {
> > +             /* Once woken up, this means that we are scheduled as threaded
> > +              * napi and this thread owns the napi context, if busy poll
>
> please capitalize NAPI in all comments and docs
>
> > +              * state is set then busy poll this napi.
> > +              */
> > +             val = READ_ONCE(napi->state);
> > +             busy_poll = val & NAPIF_STATE_THREADED_BUSY_POLL;
> > +             busy_poll_sched = val & NAPIF_STATE_SCHED_THREADED_BUSY_POLL;
> > +
> > +             /* Do not busy poll if napi is disabled. */
>
> It's not disabled, disable is pending
>
> > +             if (unlikely(val & NAPIF_STATE_DISABLE))
>
> > +                     busy_poll = false;
> > +
> > +             if (busy_poll != busy_poll_sched) {
> > +                     val = busy_poll ?
> > +                                     NAPIF_STATE_SCHED_THREADED_BUSY_POLL :
> > +                                     0;
> > +                     set_mask_bits(&napi->state,
> > +                                   NAPIF_STATE_SCHED_THREADED_BUSY_POLL,
> > +                                   val);
>
> why set single bit with set_mask_bits() ?
>
> Please improve, IIRC it took quite a lot of reviewer effort to get
> the thread stopping into shape, similar level of effort will not be
> afforded again.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH net-next v9 0/2] Add support to do threaded napi busy poll
  2025-09-13 16:03     ` Martin Karsten
@ 2025-09-15 16:26       ` Samiullah Khawaja
  0 siblings, 0 replies; 11+ messages in thread
From: Samiullah Khawaja @ 2025-09-15 16:26 UTC (permalink / raw)
  To: Martin Karsten
  Cc: Jakub Kicinski, David S . Miller, Eric Dumazet, Paolo Abeni,
	almasrymina, willemb, Joe Damato, netdev

On Sat, Sep 13, 2025 at 9:03 AM Martin Karsten <mkarsten@uwaterloo.ca> wrote:
>
> On 2025-09-12 22:07, Jakub Kicinski wrote:
> > On Fri, 12 Sep 2025 04:08:21 -0400 Martin Karsten wrote:
> >> The xsk_rr tool represents a specific (niche) case that is likely
> >> relevant, but a comprehensive evaluation would also include mainstream
> >> communication patterns using an existing benchmarking tool. While
> >> resource usage is claimed to not be a concern in this particular use
> >> case, it might be quite relevant in other use cases and as such, should
> >> be documented.
> >
> > Thanks a lot for working on this.
> >
> > Were you able to replicate the results? Would you be willing to perhaps
> > sketch out a summary of your findings that we could use as the cover
> > letter / addition to the docs?
> >
> > I agree with you that the use cases for this will be very narrow (as
> > are the use cases for threaded NAPI in the first place, don't get me
> > started). For forwarding use cases, however, this may be the only
> > option for busy polling, unfortunately :(
>
> Yes, to the extent possible (different, old hardware) I am seeing
> similar results for similar test cases.
Thanks for reproducing the results.
>
> In terms of summary, I would like to see a qualifying statement added
> prominently to the cover letter, such as:
>
> Note well that threaded napi busy-polling has not been shown to yield
> efficiency or throughput benefits. In contrast, dedicating an entire
> core to busy-polling one NAPI (NIC queue) is rather inefficient.
> However, in certain specific use cases, this mechanism results in lower
> packet processing latency. The experimental testing reported here only
> covers those use cases and does not present a comprehensive evaluation
> of threaded napi busy-polling.
Sounds good. I will add this to the cover letter in next revision.
>
> Thanks,
> Martin
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2025-09-15 16:27 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-11 21:28 [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Samiullah Khawaja
2025-09-11 21:29 ` [PATCH net-next v9 1/2] Extend napi threaded polling to allow kthread based busy polling Samiullah Khawaja
2025-09-13  1:46   ` Jakub Kicinski
2025-09-15 16:24     ` Samiullah Khawaja
2025-09-11 21:29 ` [PATCH net-next v9 2/2] selftests: Add napi threaded busy poll test in `busy_poller` Samiullah Khawaja
2025-09-12  8:08 ` [PATCH net-next v9 0/2] Add support to do threaded napi busy poll Martin Karsten
2025-09-12 16:22   ` Willem de Bruijn
2025-09-13  2:07   ` Jakub Kicinski
2025-09-13 16:03     ` Martin Karsten
2025-09-15 16:26       ` Samiullah Khawaja
2025-09-12 20:22 ` Stanislav Fomichev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).