xref: /linux/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py (revision 91ec2035134982b98fab0609a9fd8480e8217dc1)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4"""
5Devlink Rate Cross-eswitch Scheduling Test Suite
6==================================================
7
8Control-plane tests for cross-eswitch TX scheduling via devlink-rate.
9Validates that VFs from different PFs on the same chip can share
10rate groups using the cross-device parent-dev attribute.
11
12Preconditions:
13- NETIF points to a bond device with exactly two interfaces.
14- the interfaces must be two PFs from different devices sharing the same chip.
15- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG:
16  - devlink dev eswitch set $DEV1 mode switchdev
17  - devlink dev eswitch set $DEV2 mode switchdev
18  - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime
19  - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime
20- test cases will be skipped if:
21  - the number of interfaces in the bond device is != 2.
22  - the kernel doesn't support devlink rates.
23  - the devlink API doesn't support cross-device parents (ENODEV).
24  - cross-esw rate scheduling returns EOPNOTSUPP.
25"""
26
27import errno
28import glob
29import os
30import time
31
32from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit
33from lib.py import KsftSkipEx, KsftFailEx
34from lib.py import NetDrvEnv, DevlinkFamily
35from lib.py import NlError
36from lib.py import cmd, defer, ip, tool
37
38
39# --- Discovery and setup ---
40
41
42def get_bond_slaves(bond_ifname):
43    """Returns sorted list of slave netdev names for a bond."""
44    pattern = f"/sys/class/net/{bond_ifname}/lower_*"
45    lowers = glob.glob(pattern)
46    if not lowers:
47        raise KsftSkipEx(f"No bond slaves for {bond_ifname}")
48    slaves = []
49    for path in sorted(lowers):
50        name = os.path.basename(path)
51        if name.startswith("lower_"):
52            name = name[len("lower_"):]
53        slaves.append(name)
54    return slaves
55
56
57def discover_pfs(cfg):
58    """Discovers both PFs from bond slaves."""
59    slaves = get_bond_slaves(cfg.ifname)
60    if len(slaves) != 2:
61        raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}")
62
63    pf0, pf1 = slaves[0], slaves[1]
64    ksft_pr(f"PF0: {pf0} PF1: {pf1}")
65    return pf0, pf1
66
67
68def get_pci_addr(ifname):
69    """Resolves PCI address for a network interface."""
70    return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device"))
71
72
73def get_vf_port_index(pf_pci):
74    """Finds devlink port-index for vf0 under pf_pci."""
75    ports = tool("devlink", "port show", json=True)["port"]
76    for port_name, props in ports.items():
77        if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0:
78            return int(port_name.split("/")[-1])
79    raise KsftSkipEx(f"VF port not found for {pf_pci}")
80
81
82def cleanup_esw(pf):
83    """Removes VFs if created by tests."""
84    cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False)
85
86
87def setup_esw(pf):
88    """Creates 1 VF on 'pf'."""
89    path = f"/sys/class/net/{pf}/device/sriov_numvfs"
90    cmd(f"echo 0 > {path}", shell=True)
91    cmd(f"echo 1 > {path}", shell=True)
92    defer(cleanup_esw, pf)
93    time.sleep(2)
94
95    vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net"
96    entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else []
97    if not entries:
98        raise KsftSkipEx(f"VF not found for {pf}")
99    ip(f"link set dev {entries[0]} up")
100
101    pf_pci = get_pci_addr(pf)
102    vf_idx = get_vf_port_index(pf_pci)
103    ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})")
104    return pf_pci, vf_idx
105
106
107# --- Rate operation helpers ---
108
109
110def rate_new(devnl, dev_pci, node_name, **kwargs):
111    """Creates rate node."""
112    params = {
113        "bus-name": "pci",
114        "dev-name": dev_pci,
115        "rate-node-name": node_name,
116    }
117    params.update(kwargs)
118    try:
119        devnl.rate_new(params)
120    except NlError as e:
121        if e.error == errno.EOPNOTSUPP:
122            raise KsftSkipEx("rate_new not supported") from e
123        raise KsftFailEx("rate_new failed") from e
124
125
126def rate_get(devnl, dev_pci, node_name):
127    """Gets rate node."""
128    params = {
129        "bus-name": "pci",
130        "dev-name": dev_pci,
131        "rate-node-name": node_name,
132    }
133    return devnl.rate_get(params)
134
135
136def rate_get_leaf(devnl, dev_pci, port_index):
137    """Gets rate leaf (VF)."""
138    params = {
139        "bus-name": "pci",
140        "dev-name": dev_pci,
141        "port-index": port_index,
142    }
143    return devnl.rate_get(params)
144
145
146def rate_del(devnl, dev_pci, node_name):
147    """Deletes rate node."""
148    devnl.rate_del({
149        "bus-name": "pci",
150        "dev-name": dev_pci,
151        "rate-node-name": node_name,
152    })
153
154
155def rate_set_leaf(devnl, dev_pci, port_index, **kwargs):
156    """Sets rate attributes on a leaf (VF)."""
157    params = {
158        "bus-name": "pci",
159        "dev-name": dev_pci,
160        "port-index": port_index,
161    }
162    params.update(kwargs)
163    try:
164        devnl.rate_set(params)
165    except NlError as e:
166        if e.error == errno.EOPNOTSUPP:
167            raise KsftSkipEx("rate_set not supported") from e
168        raise KsftFailEx("rate_set failed") from e
169
170
171def rate_set_leaf_parent(devnl, dev_pci, port_index,
172                         parent_name, parent_dev_pci=None):
173    """Sets a leaf's parent, optionally cross-esw."""
174    params = {
175        "bus-name": "pci",
176        "dev-name": dev_pci,
177        "port-index": port_index,
178        "rate-parent-node-name": parent_name,
179    }
180    if parent_dev_pci:
181        params["parent-dev"] = {
182            "bus-name": "pci",
183            "dev-name": parent_dev_pci,
184        }
185    try:
186        devnl.rate_set(params)
187    except NlError as e:
188        if e.error == errno.EOPNOTSUPP:
189            raise KsftSkipEx("rate_set not supported") from e
190        if parent_dev_pci and e.error == errno.ENODEV:
191            raise KsftSkipEx("Cross-esw scheduling not supported") from e
192        raise KsftFailEx("rate_set failed") from e
193
194
195def rate_clear_leaf_parent(devnl, dev_pci, port_index):
196    """Clears a leaf's parent."""
197    rate_set_leaf_parent(devnl, dev_pci, port_index, "")
198
199
200def rate_set_node(devnl, dev_pci, node_name, **kwargs):
201    """Sets rate attributes on a node."""
202    params = {
203        "bus-name": "pci",
204        "dev-name": dev_pci,
205        "rate-node-name": node_name,
206    }
207    params.update(kwargs)
208    devnl.rate_set(params)
209
210
211# --- Test cases ---
212
213
214def test_same_esw_parent(cfg):
215    """Assigns PF0's VF to PF0's group (same esw baseline)."""
216    pf0, _ = discover_pfs(cfg)
217    pf0_pci, vf0_idx = setup_esw(pf0)
218
219    rate_new(cfg.devnl, pf0_pci, "group0")
220    defer(rate_del, cfg.devnl, pf0_pci, "group0")
221    ksft_pr("rate-new succeeded")
222
223    rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0")
224    defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
225
226    ksft_pr("Same-esw parent assignment succeeded")
227
228
229def test_cross_esw_parent(cfg):
230    """Sets cross-esw parent, then clear it."""
231    pf0, pf1 = discover_pfs(cfg)
232    pf0_pci, _ = setup_esw(pf0)
233    pf1_pci, vf1_idx = setup_esw(pf1)
234
235    rate_new(cfg.devnl, pf0_pci, "group1")
236    defer(rate_del, cfg.devnl, pf0_pci, "group1")
237    ksft_pr("rate-new succeeded")
238
239    rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
240                         "group1", parent_dev_pci=pf0_pci)
241    defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
242
243    ksft_pr("Cross-esw parent set and clear succeeded")
244
245
246def test_tx_rates_on_cross_esw(cfg):
247    """Sets tx_max on group and tx_share on leaves in a cross-esw setup."""
248    pf0, pf1 = discover_pfs(cfg)
249    pf0_pci, vf0_idx = setup_esw(pf0)
250    pf1_pci, vf1_idx = setup_esw(pf1)
251
252    rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000})
253    defer(rate_del, cfg.devnl, pf0_pci, "group2")
254    ksft_pr("rate-new succeeded")
255
256    rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx,
257                         "group2", parent_dev_pci=pf0_pci)
258    defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx)
259    ksft_pr("set parent cross-esw succeeded")
260
261    rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2")
262    defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx)
263    ksft_pr("set parent same esw succeeded")
264
265    rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000})
266    rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx)
267    ksft_eq(rate["rate-tx-share"], 1000000)
268    rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000})
269    rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx)
270    ksft_eq(rate["rate-tx-share"], 2000000)
271    rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000})
272    rate = rate_get(cfg.devnl, pf0_pci, "group2")
273    ksft_eq(rate["rate-tx-max"], 250000000)
274
275    ksft_pr("tx_max and tx_share set on cross-esw group")
276
277
278def main() -> None:
279    """Main function."""
280
281    with NetDrvEnv(__file__, nsim_test=False) as cfg:
282        cfg.devnl = DevlinkFamily()
283
284        ksft_run(
285            cases=[
286                test_same_esw_parent,
287                test_cross_esw_parent,
288                test_tx_rates_on_cross_esw,
289            ],
290            args=(cfg,),
291        )
292    ksft_exit()
293
294
295if __name__ == "__main__":
296    main()
297