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 directed = sum(cnts[2+i*2:4+i*2]) 178 179 ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context:" + str(cnts)) 180 ksft_ge(directed, 20000, f"traffic on context {i}: " + str(cnts)) 181 ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 182 183 if requested_ctx_cnt != ctx_cnt: 184 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 185 186 187def test_rss_context4(cfg): 188 test_rss_context(cfg, 4) 189 190 191def test_rss_context32(cfg): 192 test_rss_context(cfg, 32) 193 194 195def test_rss_context4_create_with_cfg(cfg): 196 test_rss_context(cfg, 4, create_with_cfg=True) 197 198 199def test_rss_context_out_of_order(cfg, ctx_cnt=4): 200 """ 201 Test separating traffic into RSS contexts. 202 Contexts are removed in semi-random order, and steering re-tested 203 to make sure removal doesn't break steering to surviving contexts. 204 Test requires 3 contexts to work. 205 """ 206 207 require_ntuple(cfg) 208 209 requested_ctx_cnt = ctx_cnt 210 211 # Try to allocate more queues when necessary 212 qcnt = len(_get_rx_cnts(cfg)) 213 if qcnt < 2 + 2 * ctx_cnt: 214 try: 215 ksft_pr(f"Increasing queue count {qcnt} -> {2 + 2 * ctx_cnt}") 216 ethtool(f"-L {cfg.ifname} combined {2 + 2 * ctx_cnt}") 217 defer(ethtool, f"-L {cfg.ifname} combined {qcnt}") 218 except: 219 raise KsftSkipEx("Not enough queues for the test") 220 221 ntuple = [] 222 ctx = [] 223 ports = [] 224 225 def remove_ctx(idx): 226 ntuple[idx].exec() 227 ntuple[idx] = None 228 ctx[idx].exec() 229 ctx[idx] = None 230 231 def check_traffic(): 232 for i in range(ctx_cnt): 233 cnts = _get_rx_cnts(cfg) 234 GenerateTraffic(cfg, port=ports[i]).wait_pkts_and_stop(20000) 235 cnts = _get_rx_cnts(cfg, prev=cnts) 236 237 if ctx[i]: 238 directed = sum(cnts[2+i*2:4+i*2]) 239 ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context:" + str(cnts)) 240 ksft_ge(directed, 20000, f"traffic on context {i}: " + str(cnts)) 241 ksft_eq(sum(cnts[2:2+i*2] + cnts[4+i*2:]), 0, "traffic on other contexts: " + str(cnts)) 242 else: 243 ksft_ge(sum(cnts[ :2]), 20000, "traffic on main context:" + str(cnts)) 244 ksft_eq(sum(cnts[2: ]), 0, "traffic on other contexts: " + str(cnts)) 245 246 # Use queues 0 and 1 for normal traffic 247 ethtool(f"-X {cfg.ifname} equal 2") 248 defer(ethtool, f"-X {cfg.ifname} default") 249 250 for i in range(ctx_cnt): 251 ctx_id = ethtool_create(cfg, "-X", f"context new start {2 + i * 2} equal 2") 252 ctx.append(defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete")) 253 254 ports.append(rand_port()) 255 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {ports[i]} context {ctx_id}" 256 ntuple_id = ethtool_create(cfg, "-N", flow) 257 ntuple.append(defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}")) 258 259 check_traffic() 260 261 # Remove middle context 262 remove_ctx(ctx_cnt // 2) 263 check_traffic() 264 265 # Remove first context 266 remove_ctx(0) 267 check_traffic() 268 269 # Remove last context 270 remove_ctx(-1) 271 check_traffic() 272 273 if requested_ctx_cnt != ctx_cnt: 274 raise KsftSkipEx(f"Tested only {ctx_cnt} contexts, wanted {requested_ctx_cnt}") 275 276 277def test_rss_context_overlap(cfg, other_ctx=0): 278 """ 279 Test contexts overlapping with each other. 280 Use 4 queues for the main context, but only queues 2 and 3 for context 1. 281 """ 282 283 require_ntuple(cfg) 284 285 queue_cnt = len(_get_rx_cnts(cfg)) 286 if queue_cnt < 4: 287 try: 288 ksft_pr(f"Increasing queue count {queue_cnt} -> 4") 289 ethtool(f"-L {cfg.ifname} combined 4") 290 defer(ethtool, f"-L {cfg.ifname} combined {queue_cnt}") 291 except: 292 raise KsftSkipEx("Not enough queues for the test") 293 294 if other_ctx == 0: 295 ethtool(f"-X {cfg.ifname} equal 4") 296 defer(ethtool, f"-X {cfg.ifname} default") 297 else: 298 other_ctx = ethtool_create(cfg, "-X", "context new") 299 ethtool(f"-X {cfg.ifname} context {other_ctx} equal 4") 300 defer(ethtool, f"-X {cfg.ifname} context {other_ctx} delete") 301 302 ctx_id = ethtool_create(cfg, "-X", "context new") 303 ethtool(f"-X {cfg.ifname} context {ctx_id} start 2 equal 2") 304 defer(ethtool, f"-X {cfg.ifname} context {ctx_id} delete") 305 306 port = rand_port() 307 if other_ctx: 308 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {other_ctx}" 309 ntuple_id = ethtool_create(cfg, "-N", flow) 310 ntuple = defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 311 312 # Test the main context 313 cnts = _get_rx_cnts(cfg) 314 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 315 cnts = _get_rx_cnts(cfg, prev=cnts) 316 317 ksft_ge(sum(cnts[ :4]), 20000, "traffic on main context: " + str(cnts)) 318 ksft_ge(sum(cnts[ :2]), 7000, "traffic on main context (1/2): " + str(cnts)) 319 ksft_ge(sum(cnts[2:4]), 7000, "traffic on main context (2/2): " + str(cnts)) 320 if other_ctx == 0: 321 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 322 323 # Now create a rule for context 1 and make sure traffic goes to a subset 324 if other_ctx: 325 ntuple.exec() 326 flow = f"flow-type tcp{cfg.addr_ipver} dst-port {port} context {ctx_id}" 327 ntuple_id = ethtool_create(cfg, "-N", flow) 328 defer(ethtool, f"-N {cfg.ifname} delete {ntuple_id}") 329 330 cnts = _get_rx_cnts(cfg) 331 GenerateTraffic(cfg, port=port).wait_pkts_and_stop(20000) 332 cnts = _get_rx_cnts(cfg, prev=cnts) 333 334 directed = sum(cnts[2:4]) 335 ksft_lt(sum(cnts[ :2]), directed / 2, "traffic on main context: " + str(cnts)) 336 ksft_ge(directed, 20000, "traffic on extra context: " + str(cnts)) 337 if other_ctx == 0: 338 ksft_eq(sum(cnts[4: ]), 0, "traffic on other queues: " + str(cnts)) 339 340 341def test_rss_context_overlap2(cfg): 342 test_rss_context_overlap(cfg, True) 343 344 345def main() -> None: 346 with NetDrvEpEnv(__file__, nsim_test=False) as cfg: 347 cfg.netdevnl = NetdevFamily() 348 349 ksft_run([test_rss_key_indir, 350 test_rss_context, test_rss_context4, test_rss_context32, 351 test_rss_context_overlap, test_rss_context_overlap2, 352 test_rss_context_out_of_order, test_rss_context4_create_with_cfg], 353 args=(cfg, )) 354 ksft_exit() 355 356 357if __name__ == "__main__": 358 main() 359