xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c (revision 1a9239bb4253f9076b5b4b2a1a4e8d7defd77a95)
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