1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 /* 3 * KUnit tests for link selection functions 4 * 5 * Copyright (C) 2025-2026 Intel Corporation 6 */ 7 #include <kunit/static_stub.h> 8 9 #include "utils.h" 10 #include "mld.h" 11 #include "link.h" 12 #include "iface.h" 13 #include "phy.h" 14 #include "mlo.h" 15 16 struct link_grading_input { 17 u8 link_id; 18 const struct cfg80211_chan_def *chandef; 19 bool active; 20 s32 signal; 21 bool has_chan_util_elem; 22 u8 chan_util; 23 u8 chan_load_by_us; 24 s8 dup_beacon_adj; 25 s8 psd_eirp_adj; 26 u16 punctured; 27 }; 28 29 static const struct link_grading_test_case { 30 const char *desc; 31 struct link_grading_input link; 32 unsigned int expected_grade; 33 } link_grading_cases[] = { 34 /* Per-bandwidth grading table tests */ 35 { 36 .desc = "20 MHz grading table: -75 dBm", 37 .link = { 38 .link_id = 0, 39 .chandef = &chandef_2ghz_20mhz, 40 .active = false, 41 .signal = -75, 42 }, 43 /* 137 * 0.7 (default 2.4GHz channel load 30%) */ 44 .expected_grade = 96, 45 }, 46 { 47 .desc = "20 MHz with channel util 128 (50%): -70 dBm", 48 .link = { 49 .link_id = 0, 50 .chandef = &chandef_2ghz_20mhz, 51 .active = false, 52 .signal = -70, 53 .has_chan_util_elem = true, 54 .chan_util = 128, 55 }, 56 .expected_grade = 86, 57 }, 58 { 59 .desc = "20 MHz with channel util 180 (70%): -70 dBm", 60 .link = { 61 .link_id = 0, 62 .chandef = &chandef_2ghz_20mhz, 63 .active = false, 64 .signal = -70, 65 .has_chan_util_elem = true, 66 .chan_util = 180, 67 }, 68 .expected_grade = 51, 69 }, 70 { 71 .desc = "20 MHz active link with chan load by us 10%: -70 dBm", 72 .link = { 73 .link_id = 0, 74 .chandef = &chandef_2ghz_20mhz, 75 .active = true, 76 .signal = -70, 77 .has_chan_util_elem = true, 78 .chan_util = 180, 79 .chan_load_by_us = 10, 80 }, 81 .expected_grade = 67, 82 }, 83 { 84 .desc = "40 MHz grading table: -80 dBm", 85 .link = { 86 .link_id = 0, 87 .chandef = &chandef_5ghz_40mhz, 88 .active = false, 89 .signal = -80, 90 }, 91 /* 206 * 0.85 (default 5GHz channel load 15%) */ 92 .expected_grade = 175, 93 }, 94 { 95 .desc = "80 MHz grading table: -70 dBm", 96 .link = { 97 .link_id = 0, 98 .chandef = &chandef_5ghz_80mhz, 99 .active = false, 100 .signal = -70, 101 }, 102 /* 548 * 0.85 (default 5GHz channel load 15%) */ 103 .expected_grade = 466, 104 }, 105 { 106 .desc = "160 MHz grading table: -65 dBm", 107 .link = { 108 .link_id = 0, 109 .chandef = &chandef_5ghz_160mhz, 110 .active = false, 111 .signal = -65, 112 }, 113 /* 1240 * 0.85 (default 5GHz channel load 15%) */ 114 .expected_grade = 1055, 115 }, 116 { 117 .desc = "320 MHz grading table: -60 dBm", 118 .link = { 119 .link_id = 0, 120 .chandef = &chandef_6ghz_320mhz, 121 .active = false, 122 .signal = -60, 123 }, 124 /* 3680 at -56 dBm (-60 + 4 dBm 6 GHz) */ 125 .expected_grade = 3680, 126 }, 127 /* 6 GHz RSSI adjustment integration tests */ 128 { 129 .desc = "6 GHz 160 MHz with fixed +4 dBm adjustment", 130 .link = { 131 .link_id = 0, 132 .chandef = &chandef_6ghz_160mhz, 133 .active = false, 134 .signal = -69, 135 }, 136 /* -69 + 4 dBm = -65, grade 1240 */ 137 .expected_grade = 1240, 138 }, 139 { 140 .desc = "6 GHz 80 MHz with fixed +4 dBm adjustment", 141 .link = { 142 .link_id = 0, 143 .chandef = &chandef_6ghz_80mhz, 144 .active = false, 145 .signal = -74, 146 }, 147 /* -74 + 4 dBm = -70, grade 548 */ 148 .expected_grade = 548, 149 }, 150 { 151 .desc = "6 GHz 40 MHz with fixed +4 dBm adjustment", 152 .link = { 153 .link_id = 0, 154 .chandef = &chandef_6ghz_40mhz, 155 .active = false, 156 .signal = -84, 157 }, 158 /* -84 + 4 dBm = -80, grade 206 */ 159 .expected_grade = 206, 160 }, 161 { 162 .desc = "6 GHz 20 MHz with fixed +4 dBm adjustment", 163 .link = { 164 .link_id = 0, 165 .chandef = &chandef_6ghz_20mhz, 166 .active = false, 167 .signal = -79, 168 }, 169 .expected_grade = 137, 170 }, 171 /* Duplicated beacon RSSI adjustment tests */ 172 { 173 .desc = "6 GHz 40 MHz dup beacon: -81 dBm + 3 dBm = -78 dBm", 174 .link = { 175 .link_id = 0, 176 .chandef = &chandef_6ghz_40mhz, 177 .active = false, 178 .signal = -81, 179 .dup_beacon_adj = 3, 180 }, 181 .expected_grade = 206, 182 }, 183 { 184 .desc = "6 GHz 80 MHz dup beacon: -73 dBm + 6 dBm = -67 dBm", 185 .link = { 186 .link_id = 0, 187 .chandef = &chandef_6ghz_80mhz, 188 .active = false, 189 .signal = -73, 190 .dup_beacon_adj = 6, 191 }, 192 .expected_grade = 620, 193 }, 194 { 195 .desc = "6 GHz 160 MHz dup beacon: -74 dBm + 9 dBm = -65 dBm", 196 .link = { 197 .link_id = 0, 198 .chandef = &chandef_6ghz_160mhz, 199 .active = false, 200 .signal = -74, 201 .dup_beacon_adj = 9, 202 }, 203 .expected_grade = 1240, 204 }, 205 { 206 .desc = "6 GHz 320 MHz dup beacon: -72 dBm + 12 dBm = -60 dBm", 207 .link = { 208 .link_id = 0, 209 .chandef = &chandef_6ghz_320mhz, 210 .active = false, 211 .signal = -72, 212 .dup_beacon_adj = 12, 213 }, 214 .expected_grade = 3296, 215 }, 216 /* PSD/EIRP RSSI adjustment tests */ 217 { 218 .desc = "6 GHz 80 MHz PSD/EIRP: -77 dBm + 3 dBm = -74 dBm", 219 .link = { 220 .link_id = 0, 221 .chandef = &chandef_6ghz_80mhz, 222 .active = false, 223 .signal = -77, 224 .psd_eirp_adj = 3, 225 }, 226 /* -77 + 3 dBm = -74, grade 412; fallback +4: -73 -> 548 */ 227 .expected_grade = 412, 228 }, 229 { 230 .desc = "6 GHz 160 MHz PSD/EIRP: -70 dBm + 3 dBm = -67 dBm", 231 .link = { 232 .link_id = 0, 233 .chandef = &chandef_6ghz_160mhz, 234 .active = false, 235 .signal = -70, 236 .psd_eirp_adj = 3, 237 }, 238 /* -70 + 3 dBm = -67, grade 1096; fallback +4: -66 -> 1240 */ 239 .expected_grade = 1096, 240 }, 241 /* Puncturing penalty tests */ 242 { 243 .desc = "80 MHz with 20 MHz punctured: 3 active subchannels", 244 .link = { 245 .link_id = 0, 246 .chandef = &chandef_5ghz_80mhz, 247 .active = false, 248 .signal = -70, 249 .punctured = 0x2, 250 }, 251 /* 548 * 0.85 (5GHz load) * 3/4 (puncturing) */ 252 .expected_grade = 349, 253 }, 254 { 255 .desc = "160 MHz with 40 MHz punctured: 6 active subchannels", 256 .link = { 257 .link_id = 0, 258 .chandef = &chandef_5ghz_160mhz, 259 .active = false, 260 .signal = -65, 261 .punctured = 0xC, 262 }, 263 /* 1240 * 0.85 (5GHz load) * 6/8 (puncturing) */ 264 .expected_grade = 791, 265 }, 266 }; 267 268 KUNIT_ARRAY_PARAM_DESC(link_grading, link_grading_cases, desc); 269 270 static s8 fake_dup_beacon_rssi_adjust(struct iwl_mld *mld, 271 struct ieee80211_bss_conf *link_conf) 272 { 273 const struct link_grading_test_case *params = 274 kunit_get_current_test()->param_value; 275 276 return params->link.dup_beacon_adj; 277 } 278 279 static s8 fake_psd_eirp_rssi_adjust(struct ieee80211_bss_conf *link_conf) 280 { 281 const struct link_grading_test_case *params = 282 kunit_get_current_test()->param_value; 283 284 return params->link.psd_eirp_adj; 285 } 286 287 static void setup_link(struct ieee80211_bss_conf *link) 288 { 289 struct kunit *test = kunit_get_current_test(); 290 struct iwl_mld *mld = test->priv; 291 const struct link_grading_test_case *test_param = 292 (const void *)(test->param_value); 293 294 KUNIT_ALLOC_AND_ASSERT(test, link->bss); 295 296 link->bss->signal = DBM_TO_MBM(test_param->link.signal); 297 298 link->chanreq.oper = *test_param->link.chandef; 299 300 if (test_param->link.has_chan_util_elem) { 301 struct cfg80211_bss_ies *ies; 302 struct ieee80211_bss_load_elem bss_load = { 303 .channel_util = test_param->link.chan_util, 304 }; 305 struct element *elem = 306 iwlmld_kunit_gen_element(WLAN_EID_QBSS_LOAD, 307 &bss_load, 308 sizeof(bss_load)); 309 unsigned int elem_len = sizeof(*elem) + sizeof(bss_load); 310 311 KUNIT_ALLOC_AND_ASSERT_SIZE(test, ies, sizeof(*ies) + elem_len); 312 memcpy(ies->data, elem, elem_len); 313 ies->len = elem_len; 314 rcu_assign_pointer(link->bss->beacon_ies, ies); 315 rcu_assign_pointer(link->bss->ies, ies); 316 } 317 318 if (test_param->link.punctured) 319 link->chanreq.oper.punctured = test_param->link.punctured; 320 321 if (test_param->link.active) { 322 struct ieee80211_chanctx_conf *chan_ctx = 323 wiphy_dereference(mld->wiphy, link->chanctx_conf); 324 struct iwl_mld_phy *phy; 325 326 KUNIT_ASSERT_NOT_NULL(test, chan_ctx); 327 328 phy = iwl_mld_phy_from_mac80211(chan_ctx); 329 330 phy->channel_load_by_us = test_param->link.chan_load_by_us; 331 } 332 } 333 334 static void test_link_grading(struct kunit *test) 335 { 336 struct iwl_mld *mld = test->priv; 337 const struct link_grading_test_case *test_param = 338 (const void *)(test->param_value); 339 struct ieee80211_vif *vif; 340 struct ieee80211_bss_conf *link; 341 unsigned int actual_grade; 342 u8 link_id = test_param->link.link_id; 343 bool active = test_param->link.active; 344 u16 valid_links; 345 struct iwl_mld_kunit_link assoc_link = { 346 .chandef = test_param->link.chandef, 347 }; 348 349 /* If the link is not active, use a different link as the assoc link */ 350 if (active) { 351 assoc_link.id = link_id; 352 valid_links = BIT(link_id); 353 } else { 354 assoc_link.id = BIT(ffz(BIT(link_id))); 355 valid_links = BIT(assoc_link.id) | BIT(link_id); 356 } 357 358 vif = iwlmld_kunit_setup_mlo_assoc(valid_links, &assoc_link); 359 360 kunit_activate_static_stub(test, iwl_mld_get_dup_beacon_rssi_adjust, 361 fake_dup_beacon_rssi_adjust); 362 kunit_activate_static_stub(test, iwl_mld_get_psd_eirp_rssi_adjust, 363 fake_psd_eirp_rssi_adjust); 364 365 wiphy_lock(mld->wiphy); 366 link = wiphy_dereference(mld->wiphy, vif->link_conf[link_id]); 367 KUNIT_ASSERT_NOT_NULL(test, link); 368 369 setup_link(link); 370 371 actual_grade = iwl_mld_get_link_grade(mld, link); 372 wiphy_unlock(mld->wiphy); 373 374 /* Assert that the returned grade matches the expected grade */ 375 KUNIT_EXPECT_EQ(test, actual_grade, test_param->expected_grade); 376 } 377 378 static struct kunit_case link_selection_cases[] = { 379 KUNIT_CASE_PARAM(test_link_grading, link_grading_gen_params), 380 {}, 381 }; 382 383 static struct kunit_suite link_selection = { 384 .name = "iwlmld-link-selection-tests", 385 .test_cases = link_selection_cases, 386 .init = iwlmld_kunit_test_init, 387 }; 388 389 kunit_test_suite(link_selection); 390 391 static const struct link_pair_case { 392 const char *desc; 393 const struct cfg80211_chan_def *chandef_a, *chandef_b; 394 bool low_latency_vif; 395 u32 chan_load_not_by_us; 396 bool primary_link_active; 397 u32 expected_result; 398 } link_pair_cases[] = { 399 { 400 .desc = "Unequal bandwidth, primary link inactive, EMLSR not allowed", 401 .low_latency_vif = false, 402 .primary_link_active = false, 403 .chandef_a = &chandef_5ghz_40mhz, 404 .chandef_b = &chandef_6ghz_20mhz, 405 .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD, 406 }, 407 { 408 .desc = "Equal bandwidths, sufficient channel load, EMLSR allowed", 409 .low_latency_vif = false, 410 .primary_link_active = true, 411 .chan_load_not_by_us = 11, 412 .chandef_a = &chandef_5ghz_40mhz, 413 .chandef_b = &chandef_6ghz_40mhz, 414 .expected_result = 0, 415 }, 416 { 417 .desc = "Equal bandwidths, insufficient channel load, EMLSR not allowed", 418 .low_latency_vif = false, 419 .primary_link_active = true, 420 .chan_load_not_by_us = 6, 421 .chandef_a = &chandef_5ghz_80mhz, 422 .chandef_b = &chandef_6ghz_80mhz, 423 .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD, 424 }, 425 { 426 .desc = "Low latency VIF, sufficient channel load, EMLSR allowed", 427 .low_latency_vif = true, 428 .primary_link_active = true, 429 .chan_load_not_by_us = 6, 430 .chandef_a = &chandef_5ghz_160mhz, 431 .chandef_b = &chandef_6ghz_160mhz, 432 .expected_result = 0, 433 }, 434 { 435 .desc = "Different bandwidths (2x ratio), primary link load permits EMLSR", 436 .low_latency_vif = false, 437 .primary_link_active = true, 438 .chan_load_not_by_us = 30, 439 .chandef_a = &chandef_5ghz_40mhz, 440 .chandef_b = &chandef_6ghz_20mhz, 441 .expected_result = 0, 442 }, 443 { 444 .desc = "Different bandwidths (4x ratio), primary link load permits EMLSR", 445 .low_latency_vif = false, 446 .primary_link_active = true, 447 .chan_load_not_by_us = 45, 448 .chandef_a = &chandef_5ghz_80mhz, 449 .chandef_b = &chandef_6ghz_20mhz, 450 .expected_result = 0, 451 }, 452 { 453 .desc = "Different bandwidths (16x ratio), primary link load insufficient", 454 .low_latency_vif = false, 455 .primary_link_active = true, 456 .chan_load_not_by_us = 45, 457 .chandef_a = &chandef_6ghz_320mhz, 458 .chandef_b = &chandef_5ghz_20mhz, 459 .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD, 460 }, 461 { 462 .desc = "Same band not allowed (2.4 GHz)", 463 .low_latency_vif = false, 464 .primary_link_active = true, 465 .chan_load_not_by_us = 30, 466 .chandef_a = &chandef_2ghz_20mhz, 467 .chandef_b = &chandef_2ghz_11_20mhz, 468 .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND, 469 }, 470 { 471 .desc = "Same band not allowed (5 GHz)", 472 .low_latency_vif = false, 473 .primary_link_active = true, 474 .chan_load_not_by_us = 30, 475 .chandef_a = &chandef_5ghz_40mhz, 476 .chandef_b = &chandef_5ghz_40mhz, 477 .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND, 478 }, 479 { 480 .desc = "Same band allowed (5 GHz separated)", 481 .low_latency_vif = false, 482 .primary_link_active = true, 483 .chan_load_not_by_us = 30, 484 .chandef_a = &chandef_5ghz_40mhz, 485 .chandef_b = &chandef_5ghz_120_40mhz, 486 .expected_result = 0, 487 }, 488 { 489 .desc = "Same band not allowed (6 GHz)", 490 .low_latency_vif = false, 491 .primary_link_active = true, 492 .chan_load_not_by_us = 30, 493 .chandef_a = &chandef_6ghz_160mhz, 494 .chandef_b = &chandef_6ghz_221_160mhz, 495 .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND, 496 }, 497 }; 498 499 KUNIT_ARRAY_PARAM_DESC(link_pair, link_pair_cases, desc); 500 501 static void test_iwl_mld_link_pair_allows_emlsr(struct kunit *test) 502 { 503 const struct link_pair_case *params = test->param_value; 504 struct iwl_mld *mld = test->priv; 505 struct ieee80211_vif *vif; 506 /* link A is the primary and link B is the secondary */ 507 struct iwl_mld_link_sel_data a = { 508 .chandef = params->chandef_a, 509 .link_id = 4, 510 }; 511 struct iwl_mld_link_sel_data b = { 512 .chandef = params->chandef_b, 513 .link_id = 5, 514 }; 515 struct iwl_mld_kunit_link assoc_link = { 516 .chandef = params->primary_link_active ? a.chandef : b.chandef, 517 .id = params->primary_link_active ? a.link_id : b.link_id, 518 }; 519 u32 result; 520 521 vif = iwlmld_kunit_setup_mlo_assoc(BIT(a.link_id) | BIT(b.link_id), 522 &assoc_link); 523 524 if (params->low_latency_vif) 525 iwl_mld_vif_from_mac80211(vif)->low_latency_causes = 1; 526 527 wiphy_lock(mld->wiphy); 528 529 /* Simulate channel load */ 530 if (params->primary_link_active) { 531 struct iwl_mld_phy *phy = 532 iwlmld_kunit_get_phy_of_link(vif, a.link_id); 533 534 phy->avg_channel_load_not_by_us = params->chan_load_not_by_us; 535 } 536 537 result = iwl_mld_emlsr_pair_state(vif, &a, &b); 538 539 wiphy_unlock(mld->wiphy); 540 541 KUNIT_EXPECT_EQ(test, result, params->expected_result); 542 } 543 544 static struct kunit_case link_pair_criteria_test_cases[] = { 545 KUNIT_CASE_PARAM(test_iwl_mld_link_pair_allows_emlsr, link_pair_gen_params), 546 {} 547 }; 548 549 static struct kunit_suite link_pair_criteria_tests = { 550 .name = "iwlmld_link_pair_allows_emlsr", 551 .test_cases = link_pair_criteria_test_cases, 552 .init = iwlmld_kunit_test_init, 553 }; 554 555 kunit_test_suite(link_pair_criteria_tests); 556