xref: /linux/tools/testing/selftests/drivers/net/hw/toeplitz.py (revision 84318277d6334c6981ab326d4acc87c6a6ddc9b8)
19cf9aa77SJakub Kicinski#!/usr/bin/env python3
29cf9aa77SJakub Kicinski# SPDX-License-Identifier: GPL-2.0
39cf9aa77SJakub Kicinski
49cf9aa77SJakub Kicinski"""
59cf9aa77SJakub KicinskiToeplitz Rx hashing test:
69cf9aa77SJakub Kicinski - rxhash (the hash value calculation itself);
79cf9aa77SJakub Kicinski - RSS mapping from rxhash to rx queue;
89cf9aa77SJakub Kicinski - RPS mapping from rxhash to cpu.
99cf9aa77SJakub Kicinski"""
109cf9aa77SJakub Kicinski
119cf9aa77SJakub Kicinskiimport glob
129cf9aa77SJakub Kicinskiimport os
139cf9aa77SJakub Kicinskiimport socket
149cf9aa77SJakub Kicinskifrom lib.py import ksft_run, ksft_exit, ksft_pr
159cf9aa77SJakub Kicinskifrom lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily
169cf9aa77SJakub Kicinskifrom lib.py import cmd, bkg, rand_port, defer
179cf9aa77SJakub Kicinskifrom lib.py import ksft_in
189cf9aa77SJakub Kicinskifrom lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
199cf9aa77SJakub Kicinski
2027c512afSJakub Kicinski# "define" for the ID of the Toeplitz hash function
2127c512afSJakub KicinskiETH_RSS_HASH_TOP = 1
2227c512afSJakub Kicinski
239cf9aa77SJakub Kicinski
249cf9aa77SJakub Kicinskidef _check_rps_and_rfs_not_configured(cfg):
259cf9aa77SJakub Kicinski    """Verify that RPS is not already configured."""
269cf9aa77SJakub Kicinski
279cf9aa77SJakub Kicinski    for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
289cf9aa77SJakub Kicinski        with open(rps_file, "r", encoding="utf-8") as fp:
299cf9aa77SJakub Kicinski            val = fp.read().strip()
309cf9aa77SJakub Kicinski            if set(val) - {"0", ","}:
319cf9aa77SJakub Kicinski                raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}")
329cf9aa77SJakub Kicinski
339cf9aa77SJakub Kicinski    rfs_file = "/proc/sys/net/core/rps_sock_flow_entries"
349cf9aa77SJakub Kicinski    with open(rfs_file, "r", encoding="utf-8") as fp:
359cf9aa77SJakub Kicinski        val = fp.read().strip()
369cf9aa77SJakub Kicinski        if val != "0":
379cf9aa77SJakub Kicinski            raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")
389cf9aa77SJakub Kicinski
399cf9aa77SJakub Kicinski
409cf9aa77SJakub Kicinskidef _get_cpu_for_irq(irq):
419cf9aa77SJakub Kicinski    with open(f"/proc/irq/{irq}/smp_affinity_list", "r",
429cf9aa77SJakub Kicinski              encoding="utf-8") as fp:
439cf9aa77SJakub Kicinski        data = fp.read().strip()
449cf9aa77SJakub Kicinski        if "," in data or "-" in data:
459cf9aa77SJakub Kicinski            raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}")
469cf9aa77SJakub Kicinski        return int(data)
479cf9aa77SJakub Kicinski
489cf9aa77SJakub Kicinski
499cf9aa77SJakub Kicinskidef _get_irq_cpus(cfg):
509cf9aa77SJakub Kicinski    """
519cf9aa77SJakub Kicinski    Read the list of IRQs for the device Rx queues.
529cf9aa77SJakub Kicinski    """
539cf9aa77SJakub Kicinski    queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True)
549cf9aa77SJakub Kicinski    napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True)
559cf9aa77SJakub Kicinski
569cf9aa77SJakub Kicinski    # Remap into ID-based dicts
579cf9aa77SJakub Kicinski    napis = {n["id"]: n for n in napis}
589cf9aa77SJakub Kicinski    queues = {f"{q['type']}{q['id']}": q for q in queues}
599cf9aa77SJakub Kicinski
609cf9aa77SJakub Kicinski    cpus = []
619cf9aa77SJakub Kicinski    for rx in range(9999):
629cf9aa77SJakub Kicinski        name = f"rx{rx}"
639cf9aa77SJakub Kicinski        if name not in queues:
649cf9aa77SJakub Kicinski            break
659cf9aa77SJakub Kicinski        cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"]))
669cf9aa77SJakub Kicinski
679cf9aa77SJakub Kicinski    return cpus
689cf9aa77SJakub Kicinski
699cf9aa77SJakub Kicinski
709cf9aa77SJakub Kicinskidef _get_unused_cpus(cfg, count=2):
719cf9aa77SJakub Kicinski    """
729cf9aa77SJakub Kicinski    Get CPUs that are not used by Rx queues.
739cf9aa77SJakub Kicinski    Returns a list of at least 'count' CPU numbers.
749cf9aa77SJakub Kicinski    """
759cf9aa77SJakub Kicinski
769cf9aa77SJakub Kicinski    # Get CPUs used by Rx queues
779cf9aa77SJakub Kicinski    rx_cpus = set(_get_irq_cpus(cfg))
789cf9aa77SJakub Kicinski
799cf9aa77SJakub Kicinski    # Get total number of CPUs
809cf9aa77SJakub Kicinski    num_cpus = os.cpu_count()
819cf9aa77SJakub Kicinski
829cf9aa77SJakub Kicinski    # Find unused CPUs
839cf9aa77SJakub Kicinski    unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus]
849cf9aa77SJakub Kicinski
859cf9aa77SJakub Kicinski    if len(unused_cpus) < count:
869cf9aa77SJakub Kicinski        raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}")
879cf9aa77SJakub Kicinski
889cf9aa77SJakub Kicinski    return unused_cpus[:count]
899cf9aa77SJakub Kicinski
909cf9aa77SJakub Kicinski
919cf9aa77SJakub Kicinskidef _configure_rps(cfg, rps_cpus):
929cf9aa77SJakub Kicinski    """Configure RPS for all Rx queues."""
939cf9aa77SJakub Kicinski
949cf9aa77SJakub Kicinski    mask = 0
959cf9aa77SJakub Kicinski    for cpu in rps_cpus:
969cf9aa77SJakub Kicinski        mask |= (1 << cpu)
979cf9aa77SJakub Kicinski    mask = hex(mask)[2:]
989cf9aa77SJakub Kicinski
999cf9aa77SJakub Kicinski    # Set RPS bitmap for all rx queues
1009cf9aa77SJakub Kicinski    for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
1019cf9aa77SJakub Kicinski        with open(rps_file, "w", encoding="utf-8") as fp:
1029cf9aa77SJakub Kicinski            fp.write(mask)
1039cf9aa77SJakub Kicinski
1049cf9aa77SJakub Kicinski    return mask
1059cf9aa77SJakub Kicinski
1069cf9aa77SJakub Kicinski
1079cf9aa77SJakub Kicinskidef _send_traffic(cfg, proto_flag, ipver, port):
1089cf9aa77SJakub Kicinski    """Send 20 packets of requested type."""
1099cf9aa77SJakub Kicinski
1109cf9aa77SJakub Kicinski    # Determine protocol and IP version for socat
1119cf9aa77SJakub Kicinski    if proto_flag == "-u":
1129cf9aa77SJakub Kicinski        proto = "UDP"
1139cf9aa77SJakub Kicinski    else:
1149cf9aa77SJakub Kicinski        proto = "TCP"
1159cf9aa77SJakub Kicinski
1169cf9aa77SJakub Kicinski    baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"]
1179cf9aa77SJakub Kicinski
1189cf9aa77SJakub Kicinski    # Run socat in a loop to send traffic periodically
1199cf9aa77SJakub Kicinski    # Use sh -c with a loop similar to toeplitz_client.sh
1209cf9aa77SJakub Kicinski    socat_cmd = f"""
1219cf9aa77SJakub Kicinski    for i in `seq 20`; do
1229cf9aa77SJakub Kicinski        echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port};
1239cf9aa77SJakub Kicinski        sleep 0.001;
1249cf9aa77SJakub Kicinski    done
1259cf9aa77SJakub Kicinski    """
1269cf9aa77SJakub Kicinski
1279cf9aa77SJakub Kicinski    cmd(socat_cmd, shell=True, host=cfg.remote)
1289cf9aa77SJakub Kicinski
1299cf9aa77SJakub Kicinski
1309cf9aa77SJakub Kicinskidef _test_variants():
1319cf9aa77SJakub Kicinski    for grp in ["", "rss", "rps"]:
1329cf9aa77SJakub Kicinski        for l4 in ["tcp", "udp"]:
1339cf9aa77SJakub Kicinski            for l3 in ["4", "6"]:
1349cf9aa77SJakub Kicinski                name = f"{l4}_ipv{l3}"
1359cf9aa77SJakub Kicinski                if grp:
1369cf9aa77SJakub Kicinski                    name = f"{grp}_{name}"
1379cf9aa77SJakub Kicinski                yield KsftNamedVariant(name, "-" + l4[0], l3, grp)
1389cf9aa77SJakub Kicinski
1399cf9aa77SJakub Kicinski
1409cf9aa77SJakub Kicinski@ksft_variants(_test_variants())
1419cf9aa77SJakub Kicinskidef test(cfg, proto_flag, ipver, grp):
1429cf9aa77SJakub Kicinski    """Run a single toeplitz test."""
1439cf9aa77SJakub Kicinski
1449cf9aa77SJakub Kicinski    cfg.require_ipver(ipver)
1459cf9aa77SJakub Kicinski
1469cf9aa77SJakub Kicinski    # Check that rxhash is enabled
1479cf9aa77SJakub Kicinski    ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)
1489cf9aa77SJakub Kicinski
14927c512afSJakub Kicinski    rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
15027c512afSJakub Kicinski    # Make sure NIC is configured to use Toeplitz hash, and no key xfrm.
15127c512afSJakub Kicinski    if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'):
15227c512afSJakub Kicinski        cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
15327c512afSJakub Kicinski                           "hfunc": ETH_RSS_HASH_TOP,
15427c512afSJakub Kicinski                           "input-xfrm": {}})
15527c512afSJakub Kicinski        defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},
15627c512afSJakub Kicinski                                  "hfunc": rss.get('hfunc'),
15727c512afSJakub Kicinski                                  "input-xfrm": rss.get('input-xfrm', {})
15827c512afSJakub Kicinski                                  })
15927c512afSJakub Kicinski
1609cf9aa77SJakub Kicinski    port = rand_port(socket.SOCK_DGRAM)
1619cf9aa77SJakub Kicinski
1629cf9aa77SJakub Kicinski    toeplitz_path = cfg.test_dir / "toeplitz"
1639cf9aa77SJakub Kicinski    rx_cmd = [
1649cf9aa77SJakub Kicinski        str(toeplitz_path),
1659cf9aa77SJakub Kicinski        "-" + ipver,
1669cf9aa77SJakub Kicinski        proto_flag,
1679cf9aa77SJakub Kicinski        "-d", str(port),
1689cf9aa77SJakub Kicinski        "-i", cfg.ifname,
169*5aadc155SJakub Kicinski        "-T", "4000",
1709cf9aa77SJakub Kicinski        "-s",
1719cf9aa77SJakub Kicinski        "-v"
1729cf9aa77SJakub Kicinski    ]
1739cf9aa77SJakub Kicinski
1749cf9aa77SJakub Kicinski    if grp:
1759cf9aa77SJakub Kicinski        _check_rps_and_rfs_not_configured(cfg)
1769cf9aa77SJakub Kicinski    if grp == "rss":
1779cf9aa77SJakub Kicinski        irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])
1789cf9aa77SJakub Kicinski        rx_cmd += ["-C", irq_cpus]
1799cf9aa77SJakub Kicinski        ksft_pr(f"RSS using CPUs: {irq_cpus}")
1809cf9aa77SJakub Kicinski    elif grp == "rps":
1819cf9aa77SJakub Kicinski        # Get CPUs not used by Rx queues and configure them for RPS
1829cf9aa77SJakub Kicinski        rps_cpus = _get_unused_cpus(cfg, count=2)
1839cf9aa77SJakub Kicinski        rps_mask = _configure_rps(cfg, rps_cpus)
1849cf9aa77SJakub Kicinski        defer(_configure_rps, cfg, [])
1859cf9aa77SJakub Kicinski        rx_cmd += ["-r", rps_mask]
1869cf9aa77SJakub Kicinski        ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}")
1879cf9aa77SJakub Kicinski
1889cf9aa77SJakub Kicinski    # Run rx in background, it will exit once it has seen enough packets
1899cf9aa77SJakub Kicinski    with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc:
1909cf9aa77SJakub Kicinski        while rx_proc.proc.poll() is None:
1919cf9aa77SJakub Kicinski            _send_traffic(cfg, proto_flag, ipver, port)
1929cf9aa77SJakub Kicinski
1939cf9aa77SJakub Kicinski    # Check rx result
1949cf9aa77SJakub Kicinski    ksft_pr("Receiver output:")
1959cf9aa77SJakub Kicinski    ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))
1969cf9aa77SJakub Kicinski    if rx_proc.stderr:
1979cf9aa77SJakub Kicinski        ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))
1989cf9aa77SJakub Kicinski
1999cf9aa77SJakub Kicinski
2009cf9aa77SJakub Kicinskidef main() -> None:
2019cf9aa77SJakub Kicinski    """Ksft boilerplate main."""
2029cf9aa77SJakub Kicinski
2039cf9aa77SJakub Kicinski    with NetDrvEpEnv(__file__) as cfg:
2049cf9aa77SJakub Kicinski        cfg.ethnl = EthtoolFamily()
2059cf9aa77SJakub Kicinski        cfg.netnl = NetdevFamily()
2069cf9aa77SJakub Kicinski        ksft_run(cases=[test], args=(cfg,))
2079cf9aa77SJakub Kicinski    ksft_exit()
2089cf9aa77SJakub Kicinski
2099cf9aa77SJakub Kicinski
2109cf9aa77SJakub Kicinskiif __name__ == "__main__":
2119cf9aa77SJakub Kicinski    main()
212