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