First patch here tries to auto-disable building the iouring sample. Our CI will still run the iouring test(s), of course, but it looks like the liburing updates aren't very quick in distroes and having to hack around it when developing unrelated tests is a bit annoying.
Remaining 4 patches iron out running the Toeplitz hash test against real NICs. I tested mlx5, bnxt and fbnic, they all pass now. I switched to using YNL directly in the C code, can't see a reason to get the info in Python and pass it to C via argv. The old code likely did this because it predates YNL.
Jakub Kicinski (5): selftests: hw-net: auto-disable building the iouring C code selftests: hw-net: toeplitz: make sure NICs have pure Toeplitz configured selftests: hw-net: toeplitz: read the RSS key directly from C selftests: hw-net: toeplitz: read indirection table from the device selftests: hw-net: toeplitz: give the test up to 4 seconds
.../testing/selftests/drivers/net/hw/Makefile | 23 ++++++- .../selftests/drivers/net/hw/toeplitz.c | 65 ++++++++++++++++++- .../selftests/drivers/net/hw/toeplitz.py | 28 ++++---- 3 files changed, 98 insertions(+), 18 deletions(-)
Looks like the liburing is not updated by distros very aggressively. Presumably because a lot of packages depend on it. I just updated to Fedora 43 and it's still on liburing 2.9. The test is 9mo old, at this stage I think this warrants handling the build failure more gracefully.
Detect if iouring is recent enough and if not print a warning and exclude the C prog from build. The Python test will just fail since the binary won't exist. But it removes the major annoyance of having to update liburing from sources when developing other tests.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/hw/Makefile | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 1760238e9d4f..949aeeeb357d 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -1,7 +1,20 @@ # SPDX-License-Identifier: GPL-2.0+ OR MIT
+# Check if io_uring supports zero-copy receive +HAS_IOURING_ZCRX := $(shell \ + echo -e '#include <liburing.h>\n' \ + 'void *func = (void *)io_uring_register_ifq;\n' \ + 'int main() {return 0;}' | \ + $(CC) -luring -x c - -o /dev/null 2>&1 && echo y) + +ifeq ($(HAS_IOURING_ZCRX),y) +COND_GEN_FILES += iou-zcrx +else +$(warning excluding iouring tests, liburing not installed or too old) +endif + TEST_GEN_FILES := \ - iou-zcrx \ + $(COND_GEN_FILES) \ toeplitz \ # end of TEST_GEN_FILES
@@ -58,4 +71,6 @@ include ../../../net/ynl.mk
include ../../../net/bpf.mk
+ifeq ($(HAS_IOURING_ZCRX),y) $(OUTPUT)/iou-zcrx: LDLIBS += -luring +endif
Make sure that the NIC under test is configured for pure Toeplitz hashing, and no input key transform (no symmetric hashing).
Signed-off-by: Jakub Kicinski kuba@kernel.org --- .../selftests/drivers/net/hw/toeplitz.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index 9019a8c1ff62..642a5cc385b6 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -17,6 +17,9 @@ from lib.py import cmd, bkg, rand_port, defer from lib.py import ksft_in from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
+# "define" for the ID of the Toeplitz hash function +ETH_RSS_HASH_TOP = 1 +
def _check_rps_and_rfs_not_configured(cfg): """Verify that RPS is not already configured.""" @@ -34,16 +37,6 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")
-def _get_rss_key(cfg): - """ - Read the RSS key from the device. - Return a string in the traditional %02x:%02x:%02x:.. format. - """ - - rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) - return ':'.join(f'{b:02x}' for b in rss["hkey"]) - - def _get_cpu_for_irq(irq): with open(f"/proc/irq/{irq}/smp_affinity_list", "r", encoding="utf-8") as fp: @@ -153,8 +146,22 @@ from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx # Check that rxhash is enabled ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)
+ rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) + # Make sure NIC is configured to use Toeplitz hash, and no key xfrm. + if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'): + cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex}, + "hfunc": ETH_RSS_HASH_TOP, + "input-xfrm": {}}) + defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex}, + "hfunc": rss.get('hfunc'), + "input-xfrm": rss.get('input-xfrm', {}) + }) + # Refresh in case changing hfunc changes things. + rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) + + key = ':'.join(f'{b:02x}' for b in rss["hkey"]) + port = rand_port(socket.SOCK_DGRAM) - key = _get_rss_key(cfg)
toeplitz_path = cfg.test_dir / "toeplitz" rx_cmd = [
Now that we have YNL support for RSS accessing the RSS info from C is very easy. Instead of passing the RSS key from Python do it directly in the C code.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- .../testing/selftests/drivers/net/hw/Makefile | 6 ++- .../selftests/drivers/net/hw/toeplitz.c | 41 ++++++++++++++++++- .../selftests/drivers/net/hw/toeplitz.py | 5 --- 3 files changed, 44 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 949aeeeb357d..7c819fdfa107 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -15,7 +15,6 @@ endif
TEST_GEN_FILES := \ $(COND_GEN_FILES) \ - toeplitz \ # end of TEST_GEN_FILES
TEST_PROGS = \ @@ -55,7 +54,10 @@ TEST_INCLUDES := \ #
# YNL files, must be before "include ..lib.mk" -YNL_GEN_FILES := ncdevmem +YNL_GEN_FILES := \ + ncdevmem \ + toeplitz \ +# end of YNL_GEN_FILES TEST_GEN_FILES += $(YNL_GEN_FILES) TEST_GEN_FILES += $(patsubst %.c,%.o,$(wildcard *.bpf.c))
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.c b/tools/testing/selftests/drivers/net/hw/toeplitz.c index afc5f910b006..7420a4e201cc 100644 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.c +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.c @@ -52,6 +52,9 @@ #include <sys/types.h> #include <unistd.h>
+#include <ynl.h> +#include "ethtool-user.h" + #include "../../../kselftest.h" #include "../../../net/lib/ksft.h"
@@ -483,6 +486,42 @@ static void parse_rps_bitmap(const char *arg) rps_silo_to_cpu[cfg_num_rps_cpus++] = i; }
+static void read_rss_dev_info_ynl(void) +{ + struct ethtool_rss_get_req *req; + struct ethtool_rss_get_rsp *rsp; + struct ynl_sock *ys; + + ys = ynl_sock_create(&ynl_ethtool_family, NULL); + if (!ys) + error(1, errno, "ynl_sock_create failed"); + + req = ethtool_rss_get_req_alloc(); + if (!req) + error(1, errno, "ethtool_rss_get_req_alloc failed"); + + ethtool_rss_get_req_set_header_dev_name(req, cfg_ifname); + + rsp = ethtool_rss_get(ys, req); + if (!rsp) + error(1, ys->err.code, "YNL: %s", ys->err.msg); + + if (!rsp->_len.hkey) + error(1, 0, "RSS key not available for %s", cfg_ifname); + + if (rsp->_len.hkey < TOEPLITZ_KEY_MIN_LEN || + rsp->_len.hkey > TOEPLITZ_KEY_MAX_LEN) + error(1, 0, "RSS key length %u out of bounds [%u, %u]", + rsp->_len.hkey, TOEPLITZ_KEY_MIN_LEN, + TOEPLITZ_KEY_MAX_LEN); + + memcpy(toeplitz_key, rsp->hkey, rsp->_len.hkey); + + ethtool_rss_get_rsp_free(rsp); + ethtool_rss_get_req_free(req); + ynl_sock_destroy(ys); +} + static void parse_opts(int argc, char **argv) { static struct option long_options[] = { @@ -551,7 +590,7 @@ static void parse_opts(int argc, char **argv) }
if (!have_toeplitz) - error(1, 0, "Must supply rss key ('-k')"); + read_rss_dev_info_ynl();
num_cpus = get_nprocs(); if (num_cpus > RSS_MAX_CPUS) diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index 642a5cc385b6..945c58d23310 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -156,10 +156,6 @@ ETH_RSS_HASH_TOP = 1 "hfunc": rss.get('hfunc'), "input-xfrm": rss.get('input-xfrm', {}) }) - # Refresh in case changing hfunc changes things. - rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}}) - - key = ':'.join(f'{b:02x}' for b in rss["hkey"])
port = rand_port(socket.SOCK_DGRAM)
@@ -170,7 +166,6 @@ ETH_RSS_HASH_TOP = 1 proto_flag, "-d", str(port), "-i", cfg.ifname, - "-k", key, "-T", "1000", "-s", "-v"
Replace the simple modulo math with the real indirection table read from the device. This makes the tests pass for mlx5 and bnxt NICs.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- .../selftests/drivers/net/hw/toeplitz.c | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.c b/tools/testing/selftests/drivers/net/hw/toeplitz.c index 7420a4e201cc..a4d04438c313 100644 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.c +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.c @@ -68,6 +68,7 @@ #define FOUR_TUPLE_MAX_LEN ((sizeof(struct in6_addr) * 2) + (sizeof(uint16_t) * 2))
#define RSS_MAX_CPUS (1 << 16) /* real constraint is PACKET_FANOUT_MAX */ +#define RSS_MAX_INDIR (1 << 16)
#define RPS_MAX_CPUS 16UL /* must be a power of 2 */
@@ -105,6 +106,8 @@ struct ring_state { static unsigned int rx_irq_cpus[RSS_MAX_CPUS]; /* map from rxq to cpu */ static int rps_silo_to_cpu[RPS_MAX_CPUS]; static unsigned char toeplitz_key[TOEPLITZ_KEY_MAX_LEN]; +static unsigned int rss_indir_tbl[RSS_MAX_INDIR]; +static unsigned int rss_indir_tbl_size; static struct ring_state rings[RSS_MAX_CPUS];
static inline uint32_t toeplitz(const unsigned char *four_tuple, @@ -133,7 +136,12 @@ static inline uint32_t toeplitz(const unsigned char *four_tuple, /* Compare computed cpu with arrival cpu from packet_fanout_cpu */ static void verify_rss(uint32_t rx_hash, int cpu) { - int queue = rx_hash % cfg_num_queues; + int queue; + + if (rss_indir_tbl_size) + queue = rss_indir_tbl[rx_hash % rss_indir_tbl_size]; + else + queue = rx_hash % cfg_num_queues;
log_verbose(" rxq %d (cpu %d)", queue, rx_irq_cpus[queue]); if (rx_irq_cpus[queue] != cpu) { @@ -517,6 +525,20 @@ static void read_rss_dev_info_ynl(void)
memcpy(toeplitz_key, rsp->hkey, rsp->_len.hkey);
+ if (rsp->_count.indir > RSS_MAX_INDIR) + error(1, 0, "RSS indirection table too large (%u > %u)", + rsp->_count.indir, RSS_MAX_INDIR); + + /* If indir table not available we'll fallback to simple modulo math */ + if (rsp->_count.indir) { + memcpy(rss_indir_tbl, rsp->indir, + rsp->_count.indir * sizeof(rss_indir_tbl[0])); + rss_indir_tbl_size = rsp->_count.indir; + + log_verbose("RSS indirection table size: %u\n", + rss_indir_tbl_size); + } + ethtool_rss_get_rsp_free(rsp); ethtool_rss_get_req_free(req); ynl_sock_destroy(ys);
Increase the receiver timeout. When running between machines in different geographic regions the test needs more than a second to SSH across and send the frames.
The bkg() command that runs the receiver defaults to 5 sec timeout, so using 4 sec sounds like a reasonable value for the receiver itself.
Signed-off-by: Jakub Kicinski kuba@kernel.org --- tools/testing/selftests/drivers/net/hw/toeplitz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/drivers/net/hw/toeplitz.py b/tools/testing/selftests/drivers/net/hw/toeplitz.py index 945c58d23310..d2db5ee9e358 100755 --- a/tools/testing/selftests/drivers/net/hw/toeplitz.py +++ b/tools/testing/selftests/drivers/net/hw/toeplitz.py @@ -166,7 +166,7 @@ ETH_RSS_HASH_TOP = 1 proto_flag, "-d", str(port), "-i", cfg.ifname, - "-T", "1000", + "-T", "4000", "-s", "-v" ]
linux-kselftest-mirror@lists.linaro.org