Files
linux/tools/testing/selftests/net/nl_netdev.py
Samiullah Khawaja 689883de94 net: stop napi kthreads when THREADED napi is disabled
Once the THREADED napi is disabled, the napi kthread should also be
stopped. Keeping the kthread intact after disabling THREADED napi makes
the PID of this kthread show up in the output of netlink 'napi-get' and
ps -ef output.

The is discussed in the patch below:
https://lore.kernel.org/all/20250502191548.559cc416@kernel.org

NAPI kthread should stop only if,

- There are no pending napi poll scheduled for this thread.
- There are no new napi poll scheduled for this thread while it has
  stopped.
- The ____napi_schedule can correctly fallback to the softirq for napi
  polling.

Since napi_schedule_prep provides mutual exclusion over STATE_SCHED bit,
it is safe to unset the STATE_THREADED when SCHED_THREADED is set or the
SCHED bit is not set. SCHED_THREADED being set means that SCHED is
already set and the kthread owns this napi.

To disable threaded napi, unset STATE_THREADED bit safely if
SCHED_THREADED is set or SCHED is unset. Once STATE_THREADED is unset
safely then wait for the kthread to unset the SCHED_THREADED bit so it
safe to stop the kthread.

Add a new test in nl_netdev to verify this behaviour.

Tested:
 ./tools/testing/selftests/net/nl_netdev.py
 TAP version 13
 1..6
 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.nsim_rxq_reset_down
 # Totals: pass:6 fail:0 xfail:0 xpass:0 skip:0 error:0

Ran neper for 300 seconds and did enable/disable of thread napi in a
loop continuously.

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>
Link: https://patch.msgid.link/20250609173015.3851695-1-skhawaja@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2025-06-10 17:52:03 -07:00

166 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0
import time
from os import system
from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_eq, ksft_ge, ksft_ne, ksft_busy_wait
from lib.py import NetdevFamily, NetdevSimDev, ip
def empty_check(nf) -> None:
devs = nf.dev_get({}, dump=True)
ksft_ge(len(devs), 1)
def lo_check(nf) -> None:
lo_info = nf.dev_get({"ifindex": 1})
ksft_eq(len(lo_info['xdp-features']), 0)
ksft_eq(len(lo_info['xdp-rx-metadata-features']), 0)
def napi_list_check(nf) -> None:
with NetdevSimDev(queue_count=100) 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), 100)
for q in [50, 0, 99]:
for i in range(4):
nsim.dfs_write("queue_reset", f"{q} {i}")
napis = nf.napi_get({'ifindex': nsim.ifindex}, dump=True)
ksft_eq(len(napis), 100,
comment=f"queue count after reset queue {q} mode {i}")
def dev_set_threaded(nf) -> None:
"""
Test that verifies various cases of napi threaded
set and unset at device level using sysfs.
"""
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 threaded
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_ne(napi0.get('pid'), None)
napi1 = nf.napi_get({'id': napi1_id})
ksft_ne(napi1.get('pid'), None)
# unset threaded
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.get('pid'), None)
napi1 = nf.napi_get({'id': napi1_id})
ksft_eq(napi1.get('pid'), None)
def nsim_rxq_reset_down(nf) -> None:
"""
Test that the queue API supports resetting a queue
while the interface is down. We should convert this
test to testing real HW once more devices support
queue API.
"""
with NetdevSimDev(queue_count=4) as nsimdev:
nsim = nsimdev.nsims[0]
ip(f"link set dev {nsim.ifname} down")
for i in [0, 2, 3]:
nsim.dfs_write("queue_reset", f"1 {i}")
def page_pool_check(nf) -> None:
with NetdevSimDev() as nsimdev:
nsim = nsimdev.nsims[0]
def up():
ip(f"link set dev {nsim.ifname} up")
def down():
ip(f"link set dev {nsim.ifname} down")
def get_pp():
pp_list = nf.page_pool_get({}, dump=True)
return [pp for pp in pp_list if pp.get("ifindex") == nsim.ifindex]
# No page pools when down
down()
ksft_eq(len(get_pp()), 0)
# Up, empty page pool appears
up()
pp_list = get_pp()
ksft_ge(len(pp_list), 0)
refs = sum([pp["inflight"] for pp in pp_list])
ksft_eq(refs, 0)
# Down, it disappears, again
down()
pp_list = get_pp()
ksft_eq(len(pp_list), 0)
# Up, allocate a page
up()
nsim.dfs_write("pp_hold", "y")
pp_list = nf.page_pool_get({}, dump=True)
refs = sum([pp["inflight"] for pp in pp_list if pp.get("ifindex") == nsim.ifindex])
ksft_ge(refs, 1)
# Now let's leak a page
down()
pp_list = get_pp()
ksft_eq(len(pp_list), 1)
refs = sum([pp["inflight"] for pp in pp_list])
ksft_eq(refs, 1)
attached = [pp for pp in pp_list if "detach-time" not in pp]
ksft_eq(len(attached), 0)
# New pp can get created, and we'll have two
up()
pp_list = get_pp()
attached = [pp for pp in pp_list if "detach-time" not in pp]
detached = [pp for pp in pp_list if "detach-time" in pp]
ksft_eq(len(attached), 1)
ksft_eq(len(detached), 1)
# Free the old page and the old pp is gone
nsim.dfs_write("pp_hold", "n")
# Freeing check is once a second so we may need to retry
ksft_busy_wait(lambda: len(get_pp()) == 1, deadline=2)
# And down...
down()
ksft_eq(len(get_pp()), 0)
# Last, leave the page hanging for destroy, nothing to check
# we're trying to exercise the orphaning path in the kernel
up()
nsim.dfs_write("pp_hold", "y")
def main() -> None:
nf = NetdevFamily()
ksft_run([empty_check, lo_check, page_pool_check, napi_list_check,
dev_set_threaded, nsim_rxq_reset_down],
args=(nf, ))
ksft_exit()
if __name__ == "__main__":
main()