xref: /linux/tools/testing/selftests/drivers/net/hw/rss_api.py (revision c3e91403103974e8f40dc170f384c0b707fe93e4)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4"""
5API level tests for RSS (mostly Netlink vs IOCTL).
6"""
7
8import glob
9from lib.py import ksft_run, ksft_exit, ksft_eq, ksft_is, ksft_ne
10from lib.py import KsftSkipEx, KsftFailEx
11from lib.py import defer, ethtool
12from lib.py import EthtoolFamily
13from lib.py import NetDrvEnv
14
15
16def _require_2qs(cfg):
17    qcnt = len(glob.glob(f"/sys/class/net/{cfg.ifname}/queues/rx-*"))
18    if qcnt < 2:
19        raise KsftSkipEx(f"Local has only {qcnt} queues")
20    return qcnt
21
22
23def _ethtool_create(cfg, act, opts):
24    output = ethtool(f"{act} {cfg.ifname} {opts}").stdout
25    # Output will be something like: "New RSS context is 1" or
26    # "Added rule with ID 7", we want the integer from the end
27    return int(output.split()[-1])
28
29
30def _ethtool_get_cfg(cfg, fl_type, to_nl=False):
31    descr = ethtool(f"-n {cfg.ifname} rx-flow-hash {fl_type}").stdout
32
33    if to_nl:
34        converter = {
35            "IP SA": "ip-src",
36            "IP DA": "ip-dst",
37            "L4 bytes 0 & 1 [TCP/UDP src port]": "l4-b-0-1",
38            "L4 bytes 2 & 3 [TCP/UDP dst port]": "l4-b-2-3",
39        }
40
41        ret = set()
42    else:
43        converter = {
44            "IP SA": "s",
45            "IP DA": "d",
46            "L3 proto": "t",
47            "L4 bytes 0 & 1 [TCP/UDP src port]": "f",
48            "L4 bytes 2 & 3 [TCP/UDP dst port]": "n",
49        }
50
51        ret = ""
52
53    for line in descr.split("\n")[1:-2]:
54        # if this raises we probably need to add more keys to converter above
55        if to_nl:
56            ret.add(converter[line])
57        else:
58            ret += converter[line]
59    return ret
60
61
62def test_rxfh_indir_ntf(cfg):
63    """
64    Check that Netlink notifications are generated when RSS indirection
65    table was modified.
66    """
67    _require_2qs(cfg)
68
69    ethnl = EthtoolFamily()
70    ethnl.ntf_subscribe("monitor")
71
72    ethtool(f"--disable-netlink -X {cfg.ifname} weight 0 1")
73    reset = defer(ethtool, f"-X {cfg.ifname} default")
74
75    ntf = next(ethnl.poll_ntf(duration=0.2), None)
76    if ntf is None:
77        raise KsftFailEx("No notification received")
78    ksft_eq(ntf["name"], "rss-ntf")
79    ksft_eq(set(ntf["msg"]["indir"]), {1})
80
81    reset.exec()
82    ntf = next(ethnl.poll_ntf(duration=0.2), None)
83    if ntf is None:
84        raise KsftFailEx("No notification received after reset")
85    ksft_eq(ntf["name"], "rss-ntf")
86    ksft_is(ntf["msg"].get("context"), None)
87    ksft_ne(set(ntf["msg"]["indir"]), {1})
88
89
90def test_rxfh_indir_ctx_ntf(cfg):
91    """
92    Check that Netlink notifications are generated when RSS indirection
93    table was modified on an additional RSS context.
94    """
95    _require_2qs(cfg)
96
97    ctx_id = _ethtool_create(cfg, "-X", "context new")
98    defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")
99
100    ethnl = EthtoolFamily()
101    ethnl.ntf_subscribe("monitor")
102
103    ethtool(f"--disable-netlink -X {cfg.ifname} context {ctx_id} weight 0 1")
104
105    ntf = next(ethnl.poll_ntf(duration=0.2), None)
106    if ntf is None:
107        raise KsftFailEx("No notification received")
108    ksft_eq(ntf["name"], "rss-ntf")
109    ksft_eq(ntf["msg"].get("context"), ctx_id)
110    ksft_eq(set(ntf["msg"]["indir"]), {1})
111
112
113def test_rxfh_fields(cfg):
114    """
115    Test reading Rx Flow Hash over Netlink.
116    """
117
118    flow_types = ["tcp4", "tcp6", "udp4", "udp6"]
119    ethnl = EthtoolFamily()
120
121    cfg_nl = ethnl.rss_get({"header": {"dev-index": cfg.ifindex}})
122    for fl_type in flow_types:
123        one = _ethtool_get_cfg(cfg, fl_type, to_nl=True)
124        ksft_eq(one, cfg_nl["flow-hash"][fl_type],
125                comment="Config for " + fl_type)
126
127
128def main() -> None:
129    """ Ksft boiler plate main """
130
131    with NetDrvEnv(__file__, nsim_test=False) as cfg:
132        ksft_run(globs=globals(), case_pfx={"test_"}, args=(cfg, ))
133    ksft_exit()
134
135
136if __name__ == "__main__":
137    main()
138