1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3 * KUnit tests for link selection functions
4 *
5 * Copyright (C) 2025 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 static const struct link_grading_test_case {
17 const char *desc;
18 struct {
19 struct {
20 u8 link_id;
21 const struct cfg80211_chan_def *chandef;
22 bool active;
23 s32 signal;
24 bool has_chan_util_elem;
25 u8 chan_util; /* 0-255 , used only if has_chan_util_elem is true */
26 u8 chan_load_by_us; /* 0-100, used only if active is true */;
27 } link;
28 } input;
29 unsigned int expected_grade;
30 } link_grading_cases[] = {
31 {
32 .desc = "channel util of 128 (50%)",
33 .input.link = {
34 .link_id = 0,
35 .chandef = &chandef_2ghz,
36 .active = false,
37 .has_chan_util_elem = true,
38 .chan_util = 128,
39 },
40 .expected_grade = 86,
41 },
42 {
43 .desc = "channel util of 180 (70%)",
44 .input.link = {
45 .link_id = 0,
46 .chandef = &chandef_2ghz,
47 .active = false,
48 .has_chan_util_elem = true,
49 .chan_util = 180,
50 },
51 .expected_grade = 51,
52 },
53 {
54 .desc = "channel util of 180 (70%), channel load by us of 10%",
55 .input.link = {
56 .link_id = 0,
57 .chandef = &chandef_2ghz,
58 .has_chan_util_elem = true,
59 .chan_util = 180,
60 .active = true,
61 .chan_load_by_us = 10,
62 },
63 .expected_grade = 67,
64 },
65 {
66 .desc = "no channel util element",
67 .input.link = {
68 .link_id = 0,
69 .chandef = &chandef_2ghz,
70 .active = true,
71 },
72 .expected_grade = 120,
73 },
74 };
75
76 KUNIT_ARRAY_PARAM_DESC(link_grading, link_grading_cases, desc);
77
setup_link(struct ieee80211_bss_conf * link)78 static void setup_link(struct ieee80211_bss_conf *link)
79 {
80 struct kunit *test = kunit_get_current_test();
81 struct iwl_mld *mld = test->priv;
82 const struct link_grading_test_case *test_param =
83 (const void *)(test->param_value);
84
85 KUNIT_ALLOC_AND_ASSERT(test, link->bss);
86
87 link->bss->signal = DBM_TO_MBM(test_param->input.link.signal);
88
89 link->chanreq.oper = *test_param->input.link.chandef;
90
91 if (test_param->input.link.has_chan_util_elem) {
92 struct cfg80211_bss_ies *ies;
93 struct ieee80211_bss_load_elem bss_load = {
94 .channel_util = test_param->input.link.chan_util,
95 };
96 struct element *elem =
97 iwlmld_kunit_gen_element(WLAN_EID_QBSS_LOAD,
98 &bss_load,
99 sizeof(bss_load));
100 unsigned int elem_len = sizeof(*elem) + sizeof(bss_load);
101
102 KUNIT_ALLOC_AND_ASSERT_SIZE(test, ies, sizeof(*ies) + elem_len);
103 memcpy(ies->data, elem, elem_len);
104 ies->len = elem_len;
105 rcu_assign_pointer(link->bss->beacon_ies, ies);
106 rcu_assign_pointer(link->bss->ies, ies);
107 }
108
109 if (test_param->input.link.active) {
110 struct ieee80211_chanctx_conf *chan_ctx =
111 wiphy_dereference(mld->wiphy, link->chanctx_conf);
112 struct iwl_mld_phy *phy;
113
114 KUNIT_ASSERT_NOT_NULL(test, chan_ctx);
115
116 phy = iwl_mld_phy_from_mac80211(chan_ctx);
117
118 phy->channel_load_by_us = test_param->input.link.chan_load_by_us;
119 }
120 }
121
test_link_grading(struct kunit * test)122 static void test_link_grading(struct kunit *test)
123 {
124 struct iwl_mld *mld = test->priv;
125 const struct link_grading_test_case *test_param =
126 (const void *)(test->param_value);
127 struct ieee80211_vif *vif;
128 struct ieee80211_bss_conf *link;
129 unsigned int actual_grade;
130 /* Extract test case parameters */
131 u8 link_id = test_param->input.link.link_id;
132 bool active = test_param->input.link.active;
133 u16 valid_links;
134 struct iwl_mld_kunit_link assoc_link = {
135 .band = test_param->input.link.chandef->chan->band,
136 };
137
138 /* If the link is not active, use a different link as the assoc link */
139 if (active) {
140 assoc_link.id = link_id;
141 valid_links = BIT(link_id);
142 } else {
143 assoc_link.id = BIT(ffz(BIT(link_id)));
144 valid_links = BIT(assoc_link.id) | BIT(link_id);
145 }
146
147 vif = iwlmld_kunit_setup_mlo_assoc(valid_links, &assoc_link);
148
149 wiphy_lock(mld->wiphy);
150 link = wiphy_dereference(mld->wiphy, vif->link_conf[link_id]);
151 KUNIT_ASSERT_NOT_NULL(test, link);
152
153 setup_link(link);
154
155 actual_grade = iwl_mld_get_link_grade(mld, link);
156 wiphy_unlock(mld->wiphy);
157
158 /* Assert that the returned grade matches the expected grade */
159 KUNIT_EXPECT_EQ(test, actual_grade, test_param->expected_grade);
160 }
161
162 static struct kunit_case link_selection_cases[] = {
163 KUNIT_CASE_PARAM(test_link_grading, link_grading_gen_params),
164 {},
165 };
166
167 static struct kunit_suite link_selection = {
168 .name = "iwlmld-link-selection-tests",
169 .test_cases = link_selection_cases,
170 .init = iwlmld_kunit_test_init,
171 };
172
173 kunit_test_suite(link_selection);
174
175 static const struct channel_load_case {
176 const char *desc;
177 bool low_latency_vif;
178 u32 chan_load_not_by_us;
179 enum nl80211_chan_width bw_a;
180 enum nl80211_chan_width bw_b;
181 bool primary_link_active;
182 bool expected_result;
183 } channel_load_cases[] = {
184 {
185 .desc = "Unequal bandwidth, primary link inactive, EMLSR not allowed",
186 .low_latency_vif = false,
187 .primary_link_active = false,
188 .bw_a = NL80211_CHAN_WIDTH_40,
189 .bw_b = NL80211_CHAN_WIDTH_20,
190 .expected_result = false,
191 },
192 {
193 .desc = "Equal bandwidths, sufficient channel load, EMLSR allowed",
194 .low_latency_vif = false,
195 .primary_link_active = true,
196 .chan_load_not_by_us = 11,
197 .bw_a = NL80211_CHAN_WIDTH_40,
198 .bw_b = NL80211_CHAN_WIDTH_40,
199 .expected_result = true,
200 },
201 {
202 .desc = "Equal bandwidths, insufficient channel load, EMLSR not allowed",
203 .low_latency_vif = false,
204 .primary_link_active = true,
205 .chan_load_not_by_us = 6,
206 .bw_a = NL80211_CHAN_WIDTH_80,
207 .bw_b = NL80211_CHAN_WIDTH_80,
208 .expected_result = false,
209 },
210 {
211 .desc = "Low latency VIF, sufficient channel load, EMLSR allowed",
212 .low_latency_vif = true,
213 .primary_link_active = true,
214 .chan_load_not_by_us = 6,
215 .bw_a = NL80211_CHAN_WIDTH_160,
216 .bw_b = NL80211_CHAN_WIDTH_160,
217 .expected_result = true,
218 },
219 {
220 .desc = "Different bandwidths (2x ratio), primary link load permits EMLSR",
221 .low_latency_vif = false,
222 .primary_link_active = true,
223 .chan_load_not_by_us = 30,
224 .bw_a = NL80211_CHAN_WIDTH_40,
225 .bw_b = NL80211_CHAN_WIDTH_20,
226 .expected_result = true,
227 },
228 {
229 .desc = "Different bandwidths (4x ratio), primary link load permits EMLSR",
230 .low_latency_vif = false,
231 .primary_link_active = true,
232 .chan_load_not_by_us = 45,
233 .bw_a = NL80211_CHAN_WIDTH_80,
234 .bw_b = NL80211_CHAN_WIDTH_20,
235 .expected_result = true,
236 },
237 {
238 .desc = "Different bandwidths (16x ratio), primary link load insufficient",
239 .low_latency_vif = false,
240 .primary_link_active = true,
241 .chan_load_not_by_us = 45,
242 .bw_a = NL80211_CHAN_WIDTH_320,
243 .bw_b = NL80211_CHAN_WIDTH_20,
244 .expected_result = false,
245 },
246 };
247
248 KUNIT_ARRAY_PARAM_DESC(channel_load, channel_load_cases, desc);
249
test_iwl_mld_channel_load_allows_emlsr(struct kunit * test)250 static void test_iwl_mld_channel_load_allows_emlsr(struct kunit *test)
251 {
252 const struct channel_load_case *params = test->param_value;
253 struct iwl_mld *mld = test->priv;
254 struct ieee80211_vif *vif;
255 struct cfg80211_chan_def chandef_a, chandef_b;
256 struct iwl_mld_link_sel_data a = {.chandef = &chandef_a,
257 .link_id = 4};
258 struct iwl_mld_link_sel_data b = {.chandef = &chandef_b,
259 .link_id = 5};
260 struct iwl_mld_kunit_link assoc_link = {
261 .id = params->primary_link_active ? a.link_id : b.link_id,
262 .bandwidth = params->primary_link_active ? params->bw_a : params->bw_b,
263 };
264 bool result;
265
266 vif = iwlmld_kunit_setup_mlo_assoc(BIT(a.link_id) | BIT(b.link_id),
267 &assoc_link);
268
269 chandef_a.width = params->bw_a;
270 chandef_b.width = params->bw_b;
271
272 if (params->low_latency_vif)
273 iwl_mld_vif_from_mac80211(vif)->low_latency_causes = 1;
274
275 wiphy_lock(mld->wiphy);
276
277 /* Simulate channel load */
278 if (params->primary_link_active) {
279 struct iwl_mld_phy *phy =
280 iwlmld_kunit_get_phy_of_link(vif, a.link_id);
281
282 phy->avg_channel_load_not_by_us = params->chan_load_not_by_us;
283 }
284
285 result = iwl_mld_channel_load_allows_emlsr(mld, vif, &a, &b);
286
287 wiphy_unlock(mld->wiphy);
288
289 KUNIT_EXPECT_EQ(test, result, params->expected_result);
290 }
291
292 static struct kunit_case channel_load_criteria_test_cases[] = {
293 KUNIT_CASE_PARAM(test_iwl_mld_channel_load_allows_emlsr, channel_load_gen_params),
294 {}
295 };
296
297 static struct kunit_suite channel_load_criteria_tests = {
298 .name = "iwlmld_channel_load_allows_emlsr",
299 .test_cases = channel_load_criteria_test_cases,
300 .init = iwlmld_kunit_test_init,
301 };
302
303 kunit_test_suite(channel_load_criteria_tests);
304