1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4import datetime 5import random 6from lib.py import ksft_run, ksft_pr, ksft_exit, ksft_eq, ksft_ge, ksft_lt 7from lib.py import NetDrvEpEnv 8from lib.py import NetdevFamily 9from lib.py import KsftSkipEx 10from lib.py import rand_port 11from lib.py import ethtool, ip, defer, GenerateTraffic, CmdExitFailure 12 13 14def _rss_key_str(key): 15 return ":".join(["{:02x}".format(x) for x in key]) 16 17 18def _rss_key_rand(length): 19 return [random.randint(0, 255) for _ in range(length)] 20 21 22def get_rss(cfg, context=0): 23 return ethtool(f"-x {cfg.ifname} context {context}", json=True)[0] 24 25 26def get_drop_err_sum(cfg): 27 stats = ip("-s -s link show dev " + cfg.ifname, json=True)[0] 28 cnt = 0 29 for key in ['errors', 'dropped', 'over_errors', 'fifo_errors', 30 'length_errors', 'crc_errors', 'missed_errors', 31 'frame_errors']: 32 cnt += stats["stats64"]["rx"][key] 33 return cnt, stats["stats64"]["tx"]["carrier_changes"] 34 35 36def ethtool_create(cfg, act, opts): 37 output = ethtool(f"{act} {cfg.ifname} {opts}").stdout 38 # Output will be something like: "New RSS context is 1" or 39 # "Added rule with ID 7", we want the integer from the end 40 return int(output.split()[-1]) 41 42 43def require_ntuple(cfg): 44 features = ethtool(f"-k {cfg.ifname}", json=True)[0] 45 if not features["ntuple-filters"]["active"]: 46 # ntuple is more of a capability than a config knob, don't bother 47 # trying to enable it (until some driver actually needs it). 48 raise KsftSkipEx("Ntuple filters not enabled on the device: " + str(features["ntuple-filters"])) 49 50 51# Get Rx packet counts for all queues, as a simple list of integers 52# if @prev is specified the prev counts will be subtracted 53def _get_rx_cnts(cfg, prev=None): 54 cfg.wait_hw_stats_settle() 55 data = cfg.netdevnl.qstats_get({"ifindex": cfg.ifindex, "scope": ["queue"]}, dump=True) 56 data = [x for x in data if x['queue-type'] == "rx"] 57 max_q = max([x["queue-id"] for x in data]) 58 queue_stats = [0] * (max_q + 1) 59 for q in data: 60 queue_stats[q["queue-id"]] = q["rx-packets"] 61 if prev and q["queue-id"] < len(prev): 62 queue_stats[q["queue-id"]] -= prev[q["queue-id"]] 63 return queue_stats 64 65 66def test_rss_key_indir(cfg): 67 """ 68 Test basics like updating the main RSS key and indirection table. 69 """ 70 if len(_get_rx_cnts(cfg)) < 2: 71 KsftSkipEx("Device has only one queue (or doesn't support queue stats)") 72 73 data = get_rss(cfg) 74 want_keys = ['rss-hash-key', 'rss-hash-function', 'rss-indirection-table'] 75 for k in want_keys: 76 if k not in data: 77 raise KsftFailEx("ethtool results missing key: " + k) 78 if not data[k]: 79 raise KsftFailEx(f"ethtool results empty for '{k}': {data[k]}") 80 81 key_len = len(data['rss-hash-key']) 82 83 # Set the key 84 key = _rss_key_rand(key_len) 85 ethtool(f"-X {cfg.ifname} hkey " + _rss_key_str(key)) 86 87 data = get_rss(cfg) 88 ksft_eq(key, data['rss-hash-key']) 89 90 # Set the indirection table 91 ethtool(f"-X {cfg.ifname} equal 2") 92 data = get_rss(cfg) 93 ksft_eq(0, min(data['rss-indirection-table'])) 94 ksft_eq(1, max(data['rss-indirection-table'])) 95 96 # Check we only get traffic on the first 2 queues 97 cnts = _get_rx_cnts(cfg) 98 GenerateTraffic(cfg).wait_pkts_and_stop(20000) 99 cnts = _get_rx_cnts(cfg, prev=cnts) 100 # 2 queues, 20k packets, must be at least 5k per queue 101 ksft_ge(cnts[0], 5000, "traffic on main context (1/2): " + str(cnts)) 102 ksft_ge(cnts[1], 5000, "traffic on main context (2/2): " + str(cnts)) 103 # The other queues should be unused 104 ksft_eq(sum(cnts[2:]), 0, "traffic on unused queues: " + str(cnts)) 105 106 # Restore, and check traffic gets spread again 107 ethtool(f"-X {cfg.ifname} default") 108 109 cnts = _get_rx_cnts(cfg) 110 GenerateTraffic(cfg).wait_pkts_and_stop(20000) 111 cnts = _get_rx_cnts(cfg, prev=cnts) 112 # First two queues get less traffic than all the rest 113 ksft_lt(sum(cnts[:2]), sum(cnts[2:]), "traffic distributed: " + str(cnts)) 114 115 116def test_rss_context(cfg, ctx_cnt=1, create_with_cfg=None): 117 """ 118 Test separating traffic into RSS contexts. 119 The queues will be allocated 2 for each context: 120 ctx0 ctx1 ctx2 ctx3 121 [0 1] [2 3] [4 5] [6 7] ... 122 """ 123 124 require_ntuple(cfg) 125 126 requested_ctx_cnt = ctx_cnt 127 128 # Try to allocate more queues when necessary 129 qcnt = len(_get_rx_cnts(cfg)) 130 if qcnt < 2 + 2 * ctx_cnt: 131 try: 132 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 133 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 134 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 135 except: 136 raise KsftSkipEx("Not enough queues for the test") 137 138 ports = [] 139 140 # Use queues 0 and 1 for normal traffic 141 ethtool(f"-X {cfg.ifname} equal 2") 142 defer(ethtool, f"-X {cfg.ifname} default") 143 144 for i in range(ctx_cnt): 145 want_cfg = f"start {2 + i * 2} equal 2" 146 create_cfg = want_cfg if create_with_cfg else "" 147 148 try: 149 ctx_id = ethtool_create(cfg, "-X", f"context new {create_cfg}") 150 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 151 except CmdExitFailure: 152 # try to carry on and skip at the end 153 if i == 0: 154 raise 155 ksft_pr(f"Failed to create context {i + 1}, trying to test what we got") 156 ctx_cnt = i 157 break 158 159 if not create_with_cfg: 160 ethtool(f"-X {cfg.ifname} context {ctx_id} {want_cfg}") 161 162 # Sanity check the context we just created 163 data = get_rss(cfg, ctx_id) 164 ksft_eq(min(data['rss-indirection-table']), 2 + i * 2, "Unexpected context cfg: " + str(data)) 165 ksft_eq(max(data['rss-indirection-table']), 2 + i * 2 + 1, "Unexpected context cfg: " + str(data)) 166 167 ports.append(rand_port()) 168 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}" 169 ntuple = ethtool_create(cfg, "-N", flow) 170 defer(ethtool, f"-N {cfg.ifname} delete {ntuple}") 171 172 for i in range(ctx_cnt): 173 cnts = _get_rx_cnts(cfg) 174 GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 175 cnts = _get_rx_cnts(cfg, prev=cnts) 176 177 ksft_lt(sum(cnts[ :2]), 10000, "traffic on main context:" + str(cnts)) 178 ksft_ge(sum(cnts[2+i*2:4+i*2]), 20000, f"traffic on context {i}: " + str(cnts)) 179 ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 180 181 if requested_ctx_cnt != ctx_cnt: 182 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 183 184 185def test_rss_context4(cfg): 186 test_rss_context(cfg, 4) 187 188 189def test_rss_context32(cfg): 190 test_rss_context(cfg, 32) 191 192 193def test_rss_context4_create_with_cfg(cfg): 194 test_rss_context(cfg, 4, create_with_cfg=True) 195 196 197def test_rss_context_out_of_order(cfg, ctx_cnt=4): 198 """ 199 Test separating traffic into RSS contexts. 200 Contexts are removed in semi-random order, and steering re-tested 201 to make sure removal doesn't break steering to surviving contexts. 202 Test requires 3 contexts to work. 203 """ 204 205 require_ntuple(cfg) 206 207 requested_ctx_cnt = ctx_cnt 208 209 # Try to allocate more queues when necessary 210 qcnt = len(_get_rx_cnts(cfg)) 211 if qcnt < 2 + 2 * ctx_cnt: 212 try: 213 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 214 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 215 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 216 except: 217 raise KsftSkipEx("Not enough queues for the test") 218 219 ntuple = [] 220 ctx = [] 221 ports = [] 222 223 def remove_ctx(idx): 224 ntuple[idx].exec() 225 ntuple[idx] = None 226 ctx[idx].exec() 227 ctx[idx] = None 228 229 def check_traffic(): 230 for i in range(ctx_cnt): 231 cnts = _get_rx_cnts(cfg) 232 GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 233 cnts = _get_rx_cnts(cfg, prev=cnts) 234 235 if ctx[i]: 236 ksft_lt(sum(cnts[ :2]), 10000, "traffic on main context:" + str(cnts)) 237 ksft_ge(sum(cnts[2+i*2:4+i*2]), 20000, f"traffic on context {i}: " + str(cnts)) 238 ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 239 else: 240 ksft_ge(sum(cnts[ :2]), 20000, "traffic on main context:" + str(cnts)) 241 ksft_eq(sum(cnts[2: ]), 0, "traffic on other contexts: " + str(cnts)) 242 243 # Use queues 0 and 1 for normal traffic 244 ethtool(f"-X {cfg.ifname} equal 2") 245 defer(ethtool, f"-X {cfg.ifname} default") 246 247 for i in range(ctx_cnt): 248 ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2") 249 ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")) 250 251 ports.append(rand_port()) 252 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}" 253 ntuple_id = ethtool_create(cfg, "-N", flow) 254 ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")) 255 256 check_traffic() 257 258 # Remove middle context 259 remove_ctx(ctx_cnt // 2) 260 check_traffic() 261 262 # Remove first context 263 remove_ctx(0) 264 check_traffic() 265 266 # Remove last context 267 remove_ctx(-1) 268 check_traffic() 269 270 if requested_ctx_cnt != ctx_cnt: 271 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 272 273 274def test_rss_context_overlap(cfg, other_ctx=0): 275 """ 276 Test contexts overlapping with each other. 277 Use 4 queues for the main context, but only queues 2 and 3 for context 1. 278 """ 279 280 require_ntuple(cfg) 281 282 queue_cnt = len(_get_rx_cnts(cfg)) 283 if queue_cnt < 4: 284 try: 285 ksft_pr(f"Increasing queue count {queue_cnt} -> 4") 286 ethtool(f"-L {cfg.ifname} combined 4") 287 defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") 288 except: 289 raise KsftSkipEx("Not enough queues for the test") 290 291 if other_ctx == 0: 292 ethtool(f"-X {cfg.ifname} equal 4") 293 defer(ethtool, f"-X {cfg.ifname} default") 294 else: 295 other_ctx = ethtool_create(cfg, "-X", "context new") 296 ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4") 297 defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete") 298 299 ctx_id = ethtool_create(cfg, "-X", "context new") 300 ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2") 301 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 302 303 port = rand_port() 304 if other_ctx: 305 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {other_ctx}" 306 ntuple_id = ethtool_create(cfg, "-N", flow) 307 ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 308 309 # Test the main context 310 cnts = _get_rx_cnts(cfg) 311 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 312 cnts = _get_rx_cnts(cfg, prev=cnts) 313 314 ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) 315 ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) 316 ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts)) 317 if other_ctx == 0: 318 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 319 320 # Now create a rule for context 1 and make sure traffic goes to a subset 321 if other_ctx: 322 ntuple.exec() 323 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}" 324 ntuple_id = ethtool_create(cfg, "-N", flow) 325 defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 326 327 cnts = _get_rx_cnts(cfg) 328 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 329 cnts = _get_rx_cnts(cfg, prev=cnts) 330 331 ksft_lt(sum(cnts[ :2]), 7000, "traffic on main context: " + str(cnts)) 332 ksft_ge(sum(cnts[2:4]), 20000, "traffic on extra context: " + str(cnts)) 333 if other_ctx == 0: 334 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 335 336 337def test_rss_context_overlap2(cfg): 338 test_rss_context_overlap(cfg, True) 339 340 341def main() -> None: 342 with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 343 cfg.netdevnl = NetdevFamily() 344 345 ksft_run([test_rss_key_indir, 346 test_rss_context, test_rss_context4, test_rss_context32, 347 test_rss_context_overlap, test_rss_context_overlap2, 348 test_rss_context_out_of_order, test_rss_context4_create_with_cfg], 349 args=(cfg, )) 350 ksft_exit() 351 352 353if __name__ == "__main__": 354 main() 355