]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
Add support to set NAPI threaded for individual NAPI
authorSamiullah Khawaja <skhawaja@google.com>
Thu, 10 Jul 2025 21:12:03 +0000 (21:12 +0000)
committerJakub Kicinski <kuba@kernel.org>
Tue, 15 Jul 2025 01:02:37 +0000 (18:02 -0700)
A net device has a threaded sysctl that can be used to enable threaded
NAPI polling on all of the NAPI contexts under that device. Allow
enabling threaded NAPI polling at individual NAPI level using netlink.

Extend the netlink operation `napi-set` and allow setting the threaded
attribute of a NAPI. This will enable the threaded polling on a NAPI
context.

Add a test in `nl_netdev.py` that verifies various cases of threaded
NAPI being set at NAPI and at device level.

Tested
 ./tools/testing/selftests/net/nl_netdev.py
 TAP version 13
 1..7
 ok 1 nl_netdev.empty_check
 ok 2 nl_netdev.lo_check
 ok 3 nl_netdev.page_pool_check
 ok 4 nl_netdev.napi_list_check
 ok 5 nl_netdev.dev_set_threaded
 ok 6 nl_netdev.napi_set_threaded
 ok 7 nl_netdev.nsim_rxq_reset_down
 # Totals: pass:7 fail:0 xfail:0 xpass:0 skip:0 error:0

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20250710211203.3979655-1-skhawaja@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/netdev.yaml
Documentation/networking/napi.rst
include/linux/netdevice.h
include/uapi/linux/netdev.h
net/core/dev.c
net/core/dev.h
net/core/netdev-genl-gen.c
net/core/netdev-genl.c
tools/include/uapi/linux/netdev.h
tools/testing/selftests/net/nl_netdev.py

index ce4cfec821004387878b0489facef1aedf6e7bf0..85d0ea6ac4266283ba5ad35ae199c9a3b1eb6fd3 100644 (file)
@@ -283,6 +283,14 @@ attribute-sets:
         doc: The timeout, in nanoseconds, of how long to suspend irq
              processing, if event polling finds events
         type: uint
+      -
+        name: threaded
+        doc: Whether the NAPI is configured to operate in threaded polling
+             mode. If this is set to 1 then the NAPI context operates in
+             threaded polling mode.
+        type: uint
+        checks:
+          max: 1
   -
     name: xsk-info
     attributes: []
@@ -694,6 +702,7 @@ operations:
             - defer-hard-irqs
             - gro-flush-timeout
             - irq-suspend-timeout
+            - threaded
       dump:
         request:
           attributes:
@@ -746,6 +755,7 @@ operations:
             - defer-hard-irqs
             - gro-flush-timeout
             - irq-suspend-timeout
+            - threaded
     -
       name: bind-tx
       doc: Bind dmabuf to netdev for TX
index d0e3953cae6ab11f9f5bbc5aa1d6a49245e3e9b3..a15754adb041738f1a71978484dbde990ac8b6ec 100644 (file)
@@ -444,7 +444,14 @@ dependent). The NAPI instance IDs will be assigned in the opposite
 order than the process IDs of the kernel threads.
 
 Threaded NAPI is controlled by writing 0/1 to the ``threaded`` file in
-netdev's sysfs directory.
+netdev's sysfs directory. It can also be enabled for a specific NAPI using
+netlink interface.
+
+For example, using the script:
+
+.. code-block:: bash
+
+  $ ynl --family netdev --do napi-set --json='{"id": 66, "threaded": 1}'
 
 .. rubric:: Footnotes
 
index ec23cee5245d770f2900f12c02b3a77592216435..e49d8c98d284bd8f8b0494d1d612c0694de511a1 100644 (file)
@@ -369,6 +369,7 @@ struct napi_config {
        u64 irq_suspend_timeout;
        u32 defer_hard_irqs;
        cpumask_t affinity_mask;
+       bool threaded;
        unsigned int napi_id;
 };
 
index 7eb9571786b83ed0b1e33bebff8a96cf60afd60b..1f3719a9a0eba24231b28b368d8a544ca00627d2 100644 (file)
@@ -134,6 +134,7 @@ enum {
        NETDEV_A_NAPI_DEFER_HARD_IRQS,
        NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT,
        NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT,
+       NETDEV_A_NAPI_THREADED,
 
        __NETDEV_A_NAPI_MAX,
        NETDEV_A_NAPI_MAX = (__NETDEV_A_NAPI_MAX - 1)
index 19ddc3e6990a7d8d1ba5396b375efcee87d01fb5..621a639aeba1b7431c4676c3aea8724976006aae 100644 (file)
@@ -6961,6 +6961,31 @@ static void napi_stop_kthread(struct napi_struct *napi)
        napi->thread = NULL;
 }
 
+int napi_set_threaded(struct napi_struct *napi, bool threaded)
+{
+       if (threaded) {
+               if (!napi->thread) {
+                       int err = napi_kthread_create(napi);
+
+                       if (err)
+                               return err;
+               }
+       }
+
+       if (napi->config)
+               napi->config->threaded = threaded;
+
+       if (!threaded && napi->thread) {
+               napi_stop_kthread(napi);
+       } else {
+               /* Make sure kthread is created before THREADED bit is set. */
+               smp_mb__before_atomic();
+               assign_bit(NAPI_STATE_THREADED, &napi->state, threaded);
+       }
+
+       return 0;
+}
+
 int dev_set_threaded(struct net_device *dev, bool threaded)
 {
        struct napi_struct *napi;
@@ -6968,9 +6993,6 @@ int dev_set_threaded(struct net_device *dev, bool threaded)
 
        netdev_assert_locked_or_invisible(dev);
 
-       if (dev->threaded == threaded)
-               return 0;
-
        if (threaded) {
                list_for_each_entry(napi, &dev->napi_list, dev_list) {
                        if (!napi->thread) {
@@ -7221,6 +7243,8 @@ static void napi_restore_config(struct napi_struct *n)
                napi_hash_add(n);
                n->config->napi_id = n->napi_id;
        }
+
+       WARN_ON_ONCE(napi_set_threaded(n, n->config->threaded));
 }
 
 static void napi_save_config(struct napi_struct *n)
index e93f36b7ddf369e99dc4b6eee011e5e998ed0837..a603387fb566829d7d083cb2eb819332014e7b80 100644 (file)
@@ -315,6 +315,13 @@ static inline void napi_set_irq_suspend_timeout(struct napi_struct *n,
        WRITE_ONCE(n->irq_suspend_timeout, timeout);
 }
 
+static inline bool napi_get_threaded(struct napi_struct *n)
+{
+       return test_bit(NAPI_STATE_THREADED, &n->state);
+}
+
+int napi_set_threaded(struct napi_struct *n, bool threaded);
+
 int rps_cpumask_housekeeping(struct cpumask *mask);
 
 #if defined(CONFIG_DEBUG_NET) && defined(CONFIG_BPF_SYSCALL)
index 4fc44587f4936c64ff41ee95f42d3e49a1159807..0994bd68a7e639043c4fac4aefe47ba16812be6c 100644 (file)
@@ -92,11 +92,12 @@ static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1]
 };
 
 /* NETDEV_CMD_NAPI_SET - do */
-static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT + 1] = {
+static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_THREADED + 1] = {
        [NETDEV_A_NAPI_ID] = { .type = NLA_U32, },
        [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_UINT, 1),
 };
 
 /* NETDEV_CMD_BIND_TX - do */
@@ -193,7 +194,7 @@ static const struct genl_split_ops netdev_nl_ops[] = {
                .cmd            = NETDEV_CMD_NAPI_SET,
                .doit           = netdev_nl_napi_set_doit,
                .policy         = netdev_napi_set_nl_policy,
-               .maxattr        = NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT,
+               .maxattr        = NETDEV_A_NAPI_THREADED,
                .flags          = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
        },
        {
index 2afa7b2141aa6a3b7f630d1c5f0e4f6093cced62..5875df372415e66af6cac922a09e7153104f8198 100644 (file)
@@ -184,6 +184,10 @@ netdev_nl_napi_fill_one(struct sk_buff *rsp, struct napi_struct *napi,
        if (napi->irq >= 0 && nla_put_u32(rsp, NETDEV_A_NAPI_IRQ, napi->irq))
                goto nla_put_failure;
 
+       if (nla_put_uint(rsp, NETDEV_A_NAPI_THREADED,
+                        napi_get_threaded(napi)))
+               goto nla_put_failure;
+
        if (napi->thread) {
                pid = task_pid_nr(napi->thread);
                if (nla_put_u32(rsp, NETDEV_A_NAPI_PID, pid))
@@ -322,8 +326,18 @@ netdev_nl_napi_set_config(struct napi_struct *napi, struct genl_info *info)
 {
        u64 irq_suspend_timeout = 0;
        u64 gro_flush_timeout = 0;
+       u8 threaded = 0;
        u32 defer = 0;
 
+       if (info->attrs[NETDEV_A_NAPI_THREADED]) {
+               int ret;
+
+               threaded = nla_get_uint(info->attrs[NETDEV_A_NAPI_THREADED]);
+               ret = napi_set_threaded(napi, !!threaded);
+               if (ret)
+                       return ret;
+       }
+
        if (info->attrs[NETDEV_A_NAPI_DEFER_HARD_IRQS]) {
                defer = nla_get_u32(info->attrs[NETDEV_A_NAPI_DEFER_HARD_IRQS]);
                napi_set_defer_hard_irqs(napi, defer);
index 7eb9571786b83ed0b1e33bebff8a96cf60afd60b..1f3719a9a0eba24231b28b368d8a544ca00627d2 100644 (file)
@@ -134,6 +134,7 @@ enum {
        NETDEV_A_NAPI_DEFER_HARD_IRQS,
        NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT,
        NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT,
+       NETDEV_A_NAPI_THREADED,
 
        __NETDEV_A_NAPI_MAX,
        NETDEV_A_NAPI_MAX = (__NETDEV_A_NAPI_MAX - 1)
index c9109627a7418df75a9caaab0c77f5d597bcc5ac..c8ffade79a5201a35f47e1382dcfed7dc602e515 100755 (executable)
@@ -35,6 +35,91 @@ def napi_list_check(nf) -> None:
                 ksft_eq(len(napis), 100,
                         comment=f"queue count after reset queue {q} mode {i}")
 
+def napi_set_threaded(nf) -> None:
+    """
+    Test that verifies various cases of napi threaded
+    set and unset at napi and device level.
+    """
+    with NetdevSimDev(queue_count=2) as nsimdev:
+        nsim = nsimdev.nsims[0]
+
+        ip(f"link set dev {nsim.ifname} up")
+
+        napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
+        ksft_eq(len(napis), 2)
+
+        napi0_id = napis[0]['id']
+        napi1_id = napis[1]['id']
+
+        # set napi threaded and verify
+        nf.napi_set({'id': napi0_id, 'threaded': 1})
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 1)
+        ksft_ne(napi0.get('pid'), None)
+
+        # check it is not set for napi1
+        napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 0)
+        ksft_eq(napi1.get('pid'), None)
+
+        ip(f"link set dev {nsim.ifname} down")
+        ip(f"link set dev {nsim.ifname} up")
+
+        # verify if napi threaded is still set
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 1)
+        ksft_ne(napi0.get('pid'), None)
+
+        # check it is still not set for napi1
+        napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 0)
+        ksft_eq(napi1.get('pid'), None)
+
+        # unset napi threaded and verify
+        nf.napi_set({'id': napi0_id, 'threaded': 0})
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 0)
+        ksft_eq(napi0.get('pid'), None)
+
+        # set threaded at device level
+        system(f"echo 1 > /sys/class/net/{nsim.ifname}/threaded")
+
+        # check napi threaded is set for both napis
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 1)
+        ksft_ne(napi0.get('pid'), None)
+        napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 1)
+        ksft_ne(napi1.get('pid'), None)
+
+        # unset threaded at device level
+        system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded")
+
+        # check napi threaded is unset for both napis
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 0)
+        ksft_eq(napi0.get('pid'), None)
+        napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 0)
+        ksft_eq(napi1.get('pid'), None)
+
+        # set napi threaded for napi0
+        nf.napi_set({'id': napi0_id, 'threaded': 1})
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 1)
+        ksft_ne(napi0.get('pid'), None)
+
+        # unset threaded at device level
+        system(f"echo 0 > /sys/class/net/{nsim.ifname}/threaded")
+
+        # check napi threaded is unset for both napis
+        napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 0)
+        ksft_eq(napi0.get('pid'), None)
+        napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 0)
+        ksft_eq(napi1.get('pid'), None)
+
 def dev_set_threaded(nf) -> None:
     """
     Test that verifies various cases of napi threaded
@@ -56,8 +141,10 @@ def dev_set_threaded(nf) -> None:
 
         # check napi threaded is set for both napis
         napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 1)
         ksft_ne(napi0.get('pid'), None)
         napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 1)
         ksft_ne(napi1.get('pid'), None)
 
         # unset threaded
@@ -65,8 +152,10 @@ def dev_set_threaded(nf) -> None:
 
         # check napi threaded is unset for both napis
         napi0 = nf.napi_get({'id': napi0_id})
+        ksft_eq(napi0['threaded'], 0)
         ksft_eq(napi0.get('pid'), None)
         napi1 = nf.napi_get({'id': napi1_id})
+        ksft_eq(napi1['threaded'], 0)
         ksft_eq(napi1.get('pid'), None)
 
 def nsim_rxq_reset_down(nf) -> None:
@@ -156,7 +245,7 @@ def page_pool_check(nf) -> None:
 def main() -> None:
     nf = NetdevFamily()
     ksft_run([empty_check, lo_check, page_pool_check, napi_list_check,
-              dev_set_threaded, nsim_rxq_reset_down],
+              dev_set_threaded, napi_set_threaded, nsim_rxq_reset_down],
              args=(nf, ))
     ksft_exit()