xref: /linux/net/mac80211/tests/chan-mode.c (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * KUnit tests for channel mode functions
4  *
5  * Copyright (C) 2024-2025 Intel Corporation
6  */
7 #include <net/cfg80211.h>
8 #include <kunit/test.h>
9 
10 #include "util.h"
11 
12 MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
13 
14 static const struct determine_chan_mode_case {
15 	const char *desc;
16 	u8 extra_supp_rate;
17 	enum ieee80211_conn_mode conn_mode;
18 	enum ieee80211_conn_mode expected_mode;
19 	bool strict;
20 	u8 userspace_selector;
21 	struct ieee80211_ht_cap ht_capa_mask;
22 	struct ieee80211_vht_cap vht_capa;
23 	struct ieee80211_vht_cap vht_capa_mask;
24 	u8 vht_basic_mcs_1_4_set:1,
25 	   vht_basic_mcs_5_8_set:1,
26 	   he_basic_mcs_1_4_set:1,
27 	   he_basic_mcs_5_8_set:1;
28 	u8 vht_basic_mcs_1_4, vht_basic_mcs_5_8;
29 	u8 he_basic_mcs_1_4, he_basic_mcs_5_8;
30 	u8 eht_mcs7_min_nss;
31 	u16 eht_disabled_subchannels;
32 	u8 eht_bw;
33 	enum ieee80211_conn_bw_limit conn_bw_limit;
34 	enum ieee80211_conn_bw_limit expected_bw_limit;
35 	int error;
36 } determine_chan_mode_cases[] = {
37 	{
38 		.desc = "Normal case, EHT is working",
39 		.conn_mode = IEEE80211_CONN_MODE_EHT,
40 		.expected_mode = IEEE80211_CONN_MODE_EHT,
41 	}, {
42 		.desc = "Requiring EHT support is fine",
43 		.conn_mode = IEEE80211_CONN_MODE_EHT,
44 		.expected_mode = IEEE80211_CONN_MODE_EHT,
45 		.extra_supp_rate = 0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY,
46 	}, {
47 		.desc = "Lowering the mode limits us",
48 		.conn_mode = IEEE80211_CONN_MODE_VHT,
49 		.expected_mode = IEEE80211_CONN_MODE_VHT,
50 	}, {
51 		.desc = "Requesting a basic rate/selector that we do not support",
52 		.conn_mode = IEEE80211_CONN_MODE_EHT,
53 		.extra_supp_rate = 0x80 | (BSS_MEMBERSHIP_SELECTOR_MIN - 1),
54 		.error = EINVAL,
55 	}, {
56 		.desc = "As before, but userspace says it is taking care of it",
57 		.conn_mode = IEEE80211_CONN_MODE_EHT,
58 		.userspace_selector = BSS_MEMBERSHIP_SELECTOR_MIN - 1,
59 		.extra_supp_rate = 0x80 | (BSS_MEMBERSHIP_SELECTOR_MIN - 1),
60 		.expected_mode = IEEE80211_CONN_MODE_EHT,
61 	}, {
62 		.desc = "Masking out a supported rate in HT capabilities",
63 		.conn_mode = IEEE80211_CONN_MODE_EHT,
64 		.expected_mode = IEEE80211_CONN_MODE_LEGACY,
65 		.ht_capa_mask = {
66 			.mcs.rx_mask[0] = 0xf7,
67 		},
68 	}, {
69 		.desc = "Masking out a RX rate in VHT capabilities",
70 		.conn_mode = IEEE80211_CONN_MODE_EHT,
71 		.expected_mode = IEEE80211_CONN_MODE_HT,
72 		/* Only one RX stream at MCS 0-7 */
73 		.vht_capa = {
74 			.supp_mcs.rx_mcs_map =
75 				cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_7),
76 		},
77 		.vht_capa_mask = {
78 			.supp_mcs.rx_mcs_map = cpu_to_le16(0xffff),
79 		},
80 		.strict = true,
81 	}, {
82 		.desc = "Masking out a TX rate in VHT capabilities",
83 		.conn_mode = IEEE80211_CONN_MODE_EHT,
84 		.expected_mode = IEEE80211_CONN_MODE_HT,
85 		/* Only one TX stream at MCS 0-7 */
86 		.vht_capa = {
87 			.supp_mcs.tx_mcs_map =
88 				cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_7),
89 		},
90 		.vht_capa_mask = {
91 			.supp_mcs.tx_mcs_map = cpu_to_le16(0xffff),
92 		},
93 		.strict = true,
94 	}, {
95 		.desc = "AP has higher VHT requirement than client",
96 		.conn_mode = IEEE80211_CONN_MODE_EHT,
97 		.expected_mode = IEEE80211_CONN_MODE_HT,
98 		.vht_basic_mcs_5_8_set = 1,
99 		.vht_basic_mcs_5_8 = 0xFE, /* require 5th stream */
100 		.strict = true,
101 	}, {
102 		.desc = "all zero VHT basic rates are ignored (many APs broken)",
103 		.conn_mode = IEEE80211_CONN_MODE_VHT,
104 		.expected_mode = IEEE80211_CONN_MODE_VHT,
105 		.vht_basic_mcs_1_4_set = 1,
106 		.vht_basic_mcs_5_8_set = 1,
107 	}, {
108 		.desc = "AP requires 3 HE streams but client only has two",
109 		.conn_mode = IEEE80211_CONN_MODE_EHT,
110 		.expected_mode = IEEE80211_CONN_MODE_VHT,
111 		.he_basic_mcs_1_4 = 0b11001010,
112 		.he_basic_mcs_1_4_set = 1,
113 	}, {
114 		.desc = "all zero HE basic rates are ignored (iPhone workaround)",
115 		.conn_mode = IEEE80211_CONN_MODE_HE,
116 		.expected_mode = IEEE80211_CONN_MODE_HE,
117 		.he_basic_mcs_1_4_set = 1,
118 		.he_basic_mcs_5_8_set = 1,
119 	}, {
120 		.desc = "AP requires too many RX streams with EHT MCS 7",
121 		.conn_mode = IEEE80211_CONN_MODE_EHT,
122 		.expected_mode = IEEE80211_CONN_MODE_HE,
123 		.eht_mcs7_min_nss = 0x15,
124 	}, {
125 		.desc = "AP requires too many TX streams with EHT MCS 7",
126 		.conn_mode = IEEE80211_CONN_MODE_EHT,
127 		.expected_mode = IEEE80211_CONN_MODE_HE,
128 		.eht_mcs7_min_nss = 0x51,
129 	}, {
130 		.desc = "AP requires too many RX streams with EHT MCS 7 and EHT is required",
131 		.extra_supp_rate = 0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY,
132 		.conn_mode = IEEE80211_CONN_MODE_EHT,
133 		.eht_mcs7_min_nss = 0x15,
134 		.error = EINVAL,
135 	}, {
136 		.desc = "80 MHz EHT is downgraded to 40 MHz HE due to puncturing",
137 		.conn_mode = IEEE80211_CONN_MODE_EHT,
138 		.expected_mode = IEEE80211_CONN_MODE_HE,
139 		.conn_bw_limit = IEEE80211_CONN_BW_LIMIT_80,
140 		.expected_bw_limit = IEEE80211_CONN_BW_LIMIT_40,
141 		.eht_disabled_subchannels = 0x08,
142 		.eht_bw = IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ,
143 	}
144 };
145 KUNIT_ARRAY_PARAM_DESC(determine_chan_mode, determine_chan_mode_cases, desc)
146 
147 static void test_determine_chan_mode(struct kunit *test)
148 {
149 	const struct determine_chan_mode_case *params = test->param_value;
150 	struct t_sdata *t_sdata = T_SDATA(test);
151 	struct ieee80211_conn_settings conn = {
152 		.mode = params->conn_mode,
153 		.bw_limit = params->conn_bw_limit,
154 	};
155 	struct cfg80211_bss cbss = {
156 		.channel = &t_sdata->band_5ghz.channels[0],
157 	};
158 	unsigned long userspace_selectors[BITS_TO_LONGS(128)] = {};
159 	u8 bss_ies[] = {
160 		/* Supported Rates */
161 		WLAN_EID_SUPP_RATES, 0x08,
162 		0x82, 0x84, 0x8b, 0x96, 0xc, 0x12, 0x18, 0x24,
163 		/* Extended Supported Rates */
164 		WLAN_EID_EXT_SUPP_RATES, 0x05,
165 		0x30, 0x48, 0x60, 0x6c, params->extra_supp_rate,
166 		/* HT Capabilities */
167 		WLAN_EID_HT_CAPABILITY, 0x1a,
168 		0x0c, 0x00, 0x1b, 0xff, 0xff, 0x00, 0x00, 0x00,
169 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
170 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
171 		0x00, 0x00,
172 		/* HT Information (0xff for 1 stream) */
173 		WLAN_EID_HT_OPERATION, 0x16,
174 		0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
175 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
176 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
177 		/* VHT Capabilities */
178 		WLAN_EID_VHT_CAPABILITY, 0xc,
179 		0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
180 		0xff, 0xff, 0x00, 0x00,
181 		/* VHT Operation */
182 		WLAN_EID_VHT_OPERATION, 0x05,
183 		0x00, 0x00, 0x00,
184 		params->vht_basic_mcs_1_4_set ?
185 			params->vht_basic_mcs_1_4 :
186 			le16_get_bits(t_sdata->band_5ghz.vht_cap.vht_mcs.rx_mcs_map, 0xff),
187 		params->vht_basic_mcs_5_8_set ?
188 			params->vht_basic_mcs_5_8 :
189 			le16_get_bits(t_sdata->band_5ghz.vht_cap.vht_mcs.rx_mcs_map, 0xff00),
190 		/* HE Capabilities */
191 		WLAN_EID_EXTENSION, 0x16, WLAN_EID_EXT_HE_CAPABILITY,
192 		0x01, 0x78, 0xc8, 0x1a, 0x40, 0x00, 0x00, 0xbf,
193 		0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
194 		0x00, 0xfa, 0xff, 0xfa, 0xff,
195 		/* HE Operation (permit overriding values) */
196 		WLAN_EID_EXTENSION, 0x07, WLAN_EID_EXT_HE_OPERATION,
197 		0xf0, 0x3f, 0x00, 0xb0,
198 		params->he_basic_mcs_1_4_set ? params->he_basic_mcs_1_4 : 0xfc,
199 		params->he_basic_mcs_5_8_set ? params->he_basic_mcs_5_8 : 0xff,
200 		/* EHT Capabilities */
201 		WLAN_EID_EXTENSION, 0x12, WLAN_EID_EXT_EHT_CAPABILITY,
202 		0x07, 0x00, 0x1c, 0x00, 0x00, 0xfe, 0xff, 0xff,
203 		0x7f, 0x01, 0x00, 0x88, 0x88, 0x88, 0x00, 0x00,
204 		0x00,
205 		/* EHT Operation */
206 		WLAN_EID_EXTENSION, 0x0b, WLAN_EID_EXT_EHT_OPERATION,
207 		0x03, params->eht_mcs7_min_nss ? params->eht_mcs7_min_nss : 0x11,
208 		0x00, 0x00, 0x00, params->eht_bw,
209 		params->eht_bw == IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ ? 42 : 36,
210 		0x00,
211 		u16_get_bits(params->eht_disabled_subchannels, 0xff),
212 		u16_get_bits(params->eht_disabled_subchannels, 0xff00),
213 	};
214 	struct ieee80211_chan_req chanreq = {};
215 	struct cfg80211_chan_def ap_chandef = {};
216 	struct ieee802_11_elems *elems;
217 
218 	/* To force EHT downgrade to HE on punctured 80 MHz downgraded to 40 MHz */
219 	set_bit(IEEE80211_HW_DISALLOW_PUNCTURING, t_sdata->local.hw.flags);
220 
221 	if (params->strict)
222 		set_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags);
223 	else
224 		clear_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags);
225 
226 	t_sdata->sdata->u.mgd.ht_capa_mask = params->ht_capa_mask;
227 	t_sdata->sdata->u.mgd.vht_capa = params->vht_capa;
228 	t_sdata->sdata->u.mgd.vht_capa_mask = params->vht_capa_mask;
229 
230 	if (params->userspace_selector)
231 		set_bit(params->userspace_selector, userspace_selectors);
232 
233 	rcu_assign_pointer(cbss.ies,
234 			   kunit_kzalloc(test,
235 					 sizeof(cbss) + sizeof(bss_ies),
236 					 GFP_KERNEL));
237 	KUNIT_ASSERT_NOT_NULL(test, rcu_access_pointer(cbss.ies));
238 	((struct cfg80211_bss_ies *)rcu_access_pointer(cbss.ies))->len = sizeof(bss_ies);
239 
240 	memcpy((void *)rcu_access_pointer(cbss.ies)->data, bss_ies,
241 	       sizeof(bss_ies));
242 
243 	rcu_read_lock();
244 	elems = ieee80211_determine_chan_mode(t_sdata->sdata, &conn, &cbss,
245 					      0, &chanreq, &ap_chandef,
246 					      userspace_selectors);
247 	rcu_read_unlock();
248 
249 	/* We do not need elems, free them if they are valid. */
250 	if (!IS_ERR_OR_NULL(elems))
251 		kfree(elems);
252 
253 	if (params->error) {
254 		KUNIT_ASSERT_TRUE(test, IS_ERR(elems));
255 		KUNIT_ASSERT_EQ(test, PTR_ERR(elems), -params->error);
256 	} else {
257 		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, elems);
258 		KUNIT_ASSERT_EQ(test, conn.mode, params->expected_mode);
259 		KUNIT_ASSERT_EQ(test, conn.bw_limit, params->expected_bw_limit);
260 	}
261 }
262 
263 static struct kunit_case chan_mode_cases[] = {
264 	KUNIT_CASE_PARAM(test_determine_chan_mode,
265 			 determine_chan_mode_gen_params),
266 	{}
267 };
268 
269 static struct kunit_suite chan_mode = {
270 	.name = "mac80211-mlme-chan-mode",
271 	.test_cases = chan_mode_cases,
272 };
273 
274 kunit_test_suite(chan_mode);
275