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