[PATCH net-next v7 6/6] selftests: drv-net: extend so_txtime with hw offload
From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
Date: 2026-08-31 21:32:32
Subsystem:
kernel selftest framework, networking drivers, the rest · Maintainers:
Shuah Khan, Shuah Khan, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
From: Willem de Bruijn <willemb@google.com>
Add two pacing hardware offload variants
1. one that uses FQ to safely offload when within bounds.
2. one that uses pfifo_fast and thus forwards all packets.
Verify that the packets are paced in hardware with new flag '-H'.
Also increase rcvtimeout significantly to reduce flakiness. Especially
for the new beyond_hw_horizon test, which is close to the 100ms limit.
But update recv_verify_empty to take MSG_DONTWAIT. That last empty
check must not delay each testcase by the receive timeout.
Hardware pacing offload can complete packets out of order. So the
reverse_order test is expected to pass with pfifo_fast too.
Do not test ETF, which does not change its dequeue behavior based on
pacing_offload_horizon.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Changes
v5 -> v6
- do not suppress tx failures on expected failure:
all expect the receiver process to signal failure
v3 -> v4
- replace ethtool with rtnetlink APIs
- expect_fail: correctly handle negative test pfifofast beyond_hw_horizon,
also when KSFT_MACHINE_SLOW suppresses timing errors
- commit-msg: clarify that rcvtimeout increase is also needed for
beyond_hw_horizon test
- define the horizon (50ms) once, rather than three times
- leave cfg.require_ipver in place
v2 -> v3
- remove drivers/net/settings timeout change: superseded by recent commit
- add reverse_order comment
v1 -> v2
- re-raise NlError from e (patchwork pylint)
- simplify expect_pass test (patchwork pylint)
---
.../testing/selftests/drivers/net/so_txtime.c | 4 +-
.../selftests/drivers/net/so_txtime.py | 74 ++++++++++++++++++-
2 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index 9ebe9f91227c..5bb15498da30 100644
--- a/tools/testing/selftests/drivers/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c@@ -155,7 +155,7 @@ static void do_recv_verify_empty(int fdr) char rbuf[1]; int ret; - ret = recv(fdr, rbuf, sizeof(rbuf), 0); + ret = recv(fdr, rbuf, sizeof(rbuf), MSG_DONTWAIT); if (ret != -1 || errno != EAGAIN) error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno); }
@@ -380,7 +380,7 @@ static int setup_tx(struct sockaddr *addr, socklen_t alen) static int setup_rx(struct sockaddr *addr, socklen_t alen) { - struct timeval tv = { .tv_usec = 100 * 1000 }; + struct timeval tv = { .tv_usec = 600 * 1000 }; int fd; fd = socket(addr->sa_family, SOCK_DGRAM, 0);
diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py
index a097fae0b335..a6ccaf134199 100755
--- a/tools/testing/selftests/drivers/net/so_txtime.py
+++ b/tools/testing/selftests/drivers/net/so_txtime.py@@ -12,8 +12,9 @@ import time from lib.py import ksft_exit, ksft_run, ksft_variants from lib.py import KsftNamedVariant, KsftSkipEx from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc -from lib.py import CmdExitFailure +from lib.py import CmdExitFailure, RtnlFamily, NlError +_HW_OFFLOAD_HORIZON_MS = 50 def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): """Main function. Run so_txtime as sender and receiver."""
@@ -33,12 +34,41 @@ def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success): expect_fail = not expect_success if slow_machine: expect_success = False + expect_fail = None with bkg(cmd_rx, host=cfg.remote, fail=expect_success, expect_fail=expect_fail, exit_wait=True): cmd(cmd_tx) +def _dev_setup_pacing_offload(cfg): + """Configure pacing-offload-horizon.""" + rtnl = RtnlFamily() + + try: + link = rtnl.getlink({'ifi-index': cfg.ifindex}) + except NlError as e: + raise KsftSkipEx('getlink not supported by device') from e + + if 'pacing-offload-horizon' not in link or \ + 'max-pacing-offload-horizon' not in link: + raise KsftSkipEx('pacing offload horizon not supported by device') + + horizon = _HW_OFFLOAD_HORIZON_MS * 1000_000 + if link['max-pacing-offload-horizon'] < horizon: + raise KsftSkipEx('pacing offload max horizon too small') + + cur_horizon = link['pacing-offload-horizon'] + rtnl.setlink({ + 'ifi-index': cfg.ifindex, + 'pacing-offload-horizon': horizon, + }) + defer(rtnl.setlink, { + 'ifi-index': cfg.ifindex, + 'pacing-offload-horizon': cur_horizon + }) + + def _qdisc_setup(ifname, qdisc, optargs=""): """Replace root qdisc. Restore the original after the test.
@@ -61,6 +91,7 @@ def _test_variants_fq(): ["one_pkt", "a,10", "a,10"], ["in_order", "a,10,b,20", "a,10,b,20"], ["reverse_order", "a,20,b,10", "b,10,a,20"], + ["beyond_hw_horizon", "a,70", "a,70"], ]: name = f"v{ipver}_{testcase[0]}" yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
@@ -74,6 +105,39 @@ def test_so_txtime_fq_mono(cfg, ipver, args_tx, args_rx): test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True) +@ksft_variants(_test_variants_fq()) +def test_so_txtime_fq_mono_hw(cfg, ipver, args_tx, args_rx): + """Run all variants of monotonic fq tests, with offload horizon.""" + cfg.require_ipver(ipver) + cfg.require_nsim(nsim_test=False) + + _dev_setup_pacing_offload(cfg) + try: + _qdisc_setup(cfg.ifname, "fq", f"offload_horizon {_HW_OFFLOAD_HORIZON_MS}ms") + except Exception as e: + raise KsftSkipEx("netdev does not support offload. skipping") from e + + # Expect all tests to use only hw pacing, except beyond_hw_horizon. + # Do not pass -H to that test so that with sw pacing fallback it passes. + hw_only = "-H" if args_tx != "a,70" else "" + test_so_txtime(cfg, "mono", ipver, f"{hw_only} {args_tx}", args_rx, True) + + +@ksft_variants(_test_variants_fq()) +def test_so_txtime_pfifofast_mono_hw(cfg, ipver, args_tx, args_rx): + """Run all variants of monotonic tests, without fq pacing sw backup.""" + cfg.require_ipver(ipver) + cfg.require_nsim(nsim_test=False) + + _dev_setup_pacing_offload(cfg) + _qdisc_setup(cfg.ifname, "pfifo_fast") + + # Expect all tests to pass, except beyond_hw_horizon without sw fallback. + # It will send immediately, failing the receiver arrival bounds check. + expect_pass = not args_tx == "a,70" + test_so_txtime(cfg, "mono", ipver, f"-H {args_tx}", args_rx, expect_pass) + + @ksft_variants(_test_variants_fq()) def test_so_txtime_fq_tai(cfg, ipver, args_tx, args_rx): """Run all variants of fq tests, but pass CLOCK_TAI to test conversion."""
@@ -123,7 +187,13 @@ def main() -> None: """Boilerplate ksft main.""" with NetDrvEpEnv(__file__) as cfg: ksft_run( - [test_so_txtime_fq_mono, test_so_txtime_fq_tai, test_so_txtime_etf], + [ + test_so_txtime_fq_mono, + test_so_txtime_fq_mono_hw, + test_so_txtime_pfifofast_mono_hw, + test_so_txtime_fq_tai, + test_so_txtime_etf, + ], args=(cfg,), ) ksft_exit()
--
2.55.0.897.gb25b4bd76c-goog