File Index
Files and directories
Sign in
FILE

toeplitz.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/drivers/net/hw/toeplitz.py PYTHON
  1#!/usr/bin/env python3
  2# SPDX-License-Identifier: GPL-2.0
  3
  4"""
  5Toeplitz Rx hashing test:
  6 - rxhash (the hash value calculation itself);
  7 - RSS mapping from rxhash to rx queue;
  8 - RPS mapping from rxhash to cpu.
  9"""
 10
 11import glob
 12import os
 13import socket
 14from lib.py import ksft_run, ksft_exit, ksft_pr
 15from lib.py import NetDrvEpEnv, EthtoolFamily, NetdevFamily
 16from lib.py import cmd, bkg, rand_port, defer
 17from lib.py import ksft_in
 18from lib.py import ksft_variants, KsftNamedVariant, KsftSkipEx, KsftFailEx
 19
 20# "define" for the ID of the Toeplitz hash function
 21ETH_RSS_HASH_TOP = 1
 22
 23
 24def _check_rps_and_rfs_not_configured(cfg):
 25    """Verify that RPS is not already configured."""
 26
 27    for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
 28        with open(rps_file, "r", encoding="utf-8") as fp:
 29            val = fp.read().strip()
 30            if set(val) - {"0", ","}:
 31                raise KsftSkipEx(f"RPS already configured on {rps_file}: {val}")
 32
 33    rfs_file = "/proc/sys/net/core/rps_sock_flow_entries"
 34    with open(rfs_file, "r", encoding="utf-8") as fp:
 35        val = fp.read().strip()
 36        if val != "0":
 37            raise KsftSkipEx(f"RFS already configured {rfs_file}: {val}")
 38
 39
 40def _get_cpu_for_irq(irq):
 41    with open(f"/proc/irq/{irq}/smp_affinity_list", "r",
 42              encoding="utf-8") as fp:
 43        data = fp.read().strip()
 44        if "," in data or "-" in data:
 45            raise KsftFailEx(f"IRQ{irq} is not mapped to a single core: {data}")
 46        return int(data)
 47
 48
 49def _get_irq_cpus(cfg):
 50    """
 51    Read the list of IRQs for the device Rx queues.
 52    """
 53    queues = cfg.netnl.queue_get({"ifindex": cfg.ifindex}, dump=True)
 54    napis = cfg.netnl.napi_get({"ifindex": cfg.ifindex}, dump=True)
 55
 56    # Remap into ID-based dicts
 57    napis = {n["id"]: n for n in napis}
 58    queues = {f"{q['type']}{q['id']}": q for q in queues}
 59
 60    cpus = []
 61    for rx in range(9999):
 62        name = f"rx{rx}"
 63        if name not in queues:
 64            break
 65        cpus.append(_get_cpu_for_irq(napis[queues[name]["napi-id"]]["irq"]))
 66
 67    return cpus
 68
 69
 70def _get_unused_cpus(cfg, count=2):
 71    """
 72    Get CPUs that are not used by Rx queues.
 73    Returns a list of at least 'count' CPU numbers.
 74    """
 75
 76    # Get CPUs used by Rx queues
 77    rx_cpus = set(_get_irq_cpus(cfg))
 78
 79    # Get total number of CPUs
 80    num_cpus = os.cpu_count()
 81
 82    # Find unused CPUs
 83    unused_cpus = [cpu for cpu in range(num_cpus) if cpu not in rx_cpus]
 84
 85    if len(unused_cpus) < count:
 86        raise KsftSkipEx(f"Need at {count} CPUs not used by Rx queues, found {len(unused_cpus)}")
 87
 88    return unused_cpus[:count]
 89
 90
 91def _configure_rps(cfg, rps_cpus):
 92    """Configure RPS for all Rx queues."""
 93
 94    mask = 0
 95    for cpu in rps_cpus:
 96        mask |= (1 << cpu)
 97
 98    mask = hex(mask)
 99
100    # Set RPS bitmap for all rx queues
101    for rps_file in glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*/rps_cpus"):
102        with open(rps_file, "w", encoding="utf-8") as fp:
103            # sysfs expects hex without '0x' prefix, toeplitz.c needs the prefix
104            fp.write(mask[2:])
105
106    return mask
107
108
109def _send_traffic(cfg, proto_flag, ipver, port):
110    """Send 20 packets of requested type."""
111
112    # Determine protocol and IP version for socat
113    if proto_flag == "-u":
114        proto = "UDP"
115    else:
116        proto = "TCP"
117
118    baddr = f"[{cfg.addr_v['6']}]" if ipver == "6" else cfg.addr_v["4"]
119
120    # Run socat in a loop to send traffic periodically
121    # Use sh -c with a loop similar to toeplitz_client.sh
122    socat_cmd = f"""
123    for i in `seq 20`; do
124        echo "msg $i" | socat -{ipver} -t 0.1 - {proto}:{baddr}:{port};
125        sleep 0.001;
126    done
127    """
128
129    cmd(socat_cmd, shell=True, host=cfg.remote)
130
131
132def _test_variants():
133    for grp in ["", "rss", "rps"]:
134        for l4 in ["tcp", "udp"]:
135            for l3 in ["4", "6"]:
136                name = f"{l4}_ipv{l3}"
137                if grp:
138                    name = f"{grp}_{name}"
139                yield KsftNamedVariant(name, "-" + l4[0], l3, grp)
140
141
142@ksft_variants(_test_variants())
143def test(cfg, proto_flag, ipver, grp):
144    """Run a single toeplitz test."""
145
146    cfg.require_ipver(ipver)
147
148    # Check that rxhash is enabled
149    ksft_in("receive-hashing: on", cmd(f"ethtool -k {cfg.ifname}").stdout)
150
151    rss = cfg.ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
152    # Make sure NIC is configured to use Toeplitz hash, and no key xfrm.
153    if rss.get('hfunc') != ETH_RSS_HASH_TOP or rss.get('input-xfrm'):
154        cfg.ethnl.rss_set({"header": {"dev-index": cfg.ifindex},
155                           "hfunc": ETH_RSS_HASH_TOP,
156                           "input-xfrm": {}})
157        defer(cfg.ethnl.rss_set, {"header": {"dev-index": cfg.ifindex},
158                                  "hfunc": rss.get('hfunc'),
159                                  "input-xfrm": rss.get('input-xfrm', {})
160                                  })
161
162    port = rand_port(socket.SOCK_DGRAM)
163
164    toeplitz_path = cfg.test_dir / "toeplitz"
165    rx_cmd = [
166        str(toeplitz_path),
167        "-" + ipver,
168        proto_flag,
169        "-d", str(port),
170        "-i", cfg.ifname,
171        "-T", "4000",
172        "-s",
173        "-v"
174    ]
175
176    if grp:
177        _check_rps_and_rfs_not_configured(cfg)
178    if grp == "rss":
179        irq_cpus = ",".join([str(x) for x in _get_irq_cpus(cfg)])
180        rx_cmd += ["-C", irq_cpus]
181        ksft_pr(f"RSS using CPUs: {irq_cpus}")
182    elif grp == "rps":
183        # Get CPUs not used by Rx queues and configure them for RPS
184        rps_cpus = _get_unused_cpus(cfg, count=2)
185        rps_mask = _configure_rps(cfg, rps_cpus)
186        defer(_configure_rps, cfg, [])
187        rx_cmd += ["-r", rps_mask]
188        ksft_pr(f"RPS using CPUs: {rps_cpus}, mask: {rps_mask}")
189
190    # Run rx in background, it will exit once it has seen enough packets
191    with bkg(" ".join(rx_cmd), ksft_ready=True, exit_wait=True) as rx_proc:
192        while rx_proc.proc.poll() is None:
193            _send_traffic(cfg, proto_flag, ipver, port)
194
195    # Check rx result
196    ksft_pr("Receiver output:")
197    ksft_pr(rx_proc.stdout.strip().replace('\n', '\n# '))
198    if rx_proc.stderr:
199        ksft_pr(rx_proc.stderr.strip().replace('\n', '\n# '))
200
201
202def main() -> None:
203    """Ksft boilerplate main."""
204
205    with NetDrvEpEnv(__file__) as cfg:
206        cfg.ethnl = EthtoolFamily()
207        cfg.netnl = NetdevFamily()
208        ksft_run(cases=[test], args=(cfg,))
209    ksft_exit()
210
211
212if __name__ == "__main__":
213    main()
~/ Read-only file preview.