mirror of
https://github.com/torvalds/linux
synced 2026-07-25 03:30:47 +09:00
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>
255 lines
7.9 KiB
Python
Executable File
255 lines
7.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 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
|
|
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_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
|
|
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 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, napi_set_threaded, nsim_rxq_reset_down],
|
|
args=(nf, ))
|
|
ksft_exit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|