| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * KUnit tests for channel mode functions | |
| * | |
| * Copyright (C) 2024-2026 Intel Corporation | |
| */ | |
| #include <net/cfg80211.h> | |
| #include <kunit/test.h> | |
| #include "util.h" | |
| MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); | |
| static const struct determine_chan_mode_case { | |
| const char *desc; | |
| u8 extra_supp_rate; | |
| enum ieee80211_conn_mode conn_mode; | |
| enum ieee80211_conn_mode expected_mode; | |
| bool strict; | |
| u8 userspace_selector; | |
| struct ieee80211_ht_cap ht_capa_mask; | |
| struct ieee80211_vht_cap vht_capa; | |
| struct ieee80211_vht_cap vht_capa_mask; | |
| u8 vht_basic_mcs_1_4_set:1, | |
| vht_basic_mcs_5_8_set:1, | |
| he_basic_mcs_1_4_set:1, | |
| he_basic_mcs_5_8_set:1; | |
| u8 vht_basic_mcs_1_4, vht_basic_mcs_5_8; | |
| u8 he_basic_mcs_1_4, he_basic_mcs_5_8; | |
| u8 eht_mcs7_min_nss; | |
| u16 eht_disabled_subchannels; | |
| u8 eht_bw; | |
| enum ieee80211_conn_bw_limit conn_bw_limit; | |
| enum ieee80211_conn_bw_limit expected_bw_limit; | |
| int error; | |
| } determine_chan_mode_cases[] = { | |
| { | |
| .desc = "Normal case, EHT is working", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_EHT, | |
| }, { | |
| .desc = "Requiring EHT support is fine", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_EHT, | |
| .extra_supp_rate = 0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY, | |
| }, { | |
| .desc = "Lowering the mode limits us", | |
| .conn_mode = IEEE80211_CONN_MODE_VHT, | |
| .expected_mode = IEEE80211_CONN_MODE_VHT, | |
| }, { | |
| .desc = "Requesting a basic rate/selector that we do not support", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .extra_supp_rate = 0x80 | (BSS_MEMBERSHIP_SELECTOR_MIN - 1), | |
| .error = EINVAL, | |
| }, { | |
| .desc = "As before, but userspace says it is taking care of it", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .userspace_selector = BSS_MEMBERSHIP_SELECTOR_MIN - 1, | |
| .extra_supp_rate = 0x80 | (BSS_MEMBERSHIP_SELECTOR_MIN - 1), | |
| .expected_mode = IEEE80211_CONN_MODE_EHT, | |
| }, { | |
| .desc = "Masking out a supported rate in HT capabilities", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_LEGACY, | |
| .ht_capa_mask = { | |
| .mcs.rx_mask[0] = 0xf7, | |
| }, | |
| .strict = true, | |
| }, { | |
| .desc = "Masking out a RX rate in VHT capabilities", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HT, | |
| /* Only one RX stream at MCS 0-7 */ | |
| .vht_capa = { | |
| .supp_mcs.rx_mcs_map = | |
| cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_7), | |
| }, | |
| .vht_capa_mask = { | |
| .supp_mcs.rx_mcs_map = cpu_to_le16(0xffff), | |
| }, | |
| .strict = true, | |
| }, { | |
| .desc = "Masking out a TX rate in VHT capabilities", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HT, | |
| /* Only one TX stream at MCS 0-7 */ | |
| .vht_capa = { | |
| .supp_mcs.tx_mcs_map = | |
| cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_7), | |
| }, | |
| .vht_capa_mask = { | |
| .supp_mcs.tx_mcs_map = cpu_to_le16(0xffff), | |
| }, | |
| .strict = true, | |
| }, { | |
| .desc = "AP has higher VHT requirement than client", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HT, | |
| .vht_basic_mcs_5_8_set = 1, | |
| .vht_basic_mcs_5_8 = 0xFE, /* require 5th stream */ | |
| .strict = true, | |
| }, { | |
| .desc = "all zero VHT basic rates are ignored (many APs broken)", | |
| .conn_mode = IEEE80211_CONN_MODE_VHT, | |
| .expected_mode = IEEE80211_CONN_MODE_VHT, | |
| .vht_basic_mcs_1_4_set = 1, | |
| .vht_basic_mcs_5_8_set = 1, | |
| }, { | |
| .desc = "AP requires 3 HE streams but client only has two", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_VHT, | |
| .he_basic_mcs_1_4 = 0b11001010, | |
| .he_basic_mcs_1_4_set = 1, | |
| }, { | |
| .desc = "all zero HE basic rates are ignored (iPhone workaround)", | |
| .conn_mode = IEEE80211_CONN_MODE_HE, | |
| .expected_mode = IEEE80211_CONN_MODE_HE, | |
| .he_basic_mcs_1_4_set = 1, | |
| .he_basic_mcs_5_8_set = 1, | |
| }, { | |
| .desc = "AP requires too many RX streams with EHT MCS 7", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HE, | |
| .eht_mcs7_min_nss = 0x15, | |
| }, { | |
| .desc = "AP requires too many TX streams with EHT MCS 7", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HE, | |
| .eht_mcs7_min_nss = 0x51, | |
| }, { | |
| .desc = "AP requires too many RX streams with EHT MCS 7 and EHT is required", | |
| .extra_supp_rate = 0x80 | BSS_MEMBERSHIP_SELECTOR_EHT_PHY, | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .eht_mcs7_min_nss = 0x15, | |
| .error = EINVAL, | |
| }, { | |
| .desc = "80 MHz EHT is downgraded to 40 MHz HE due to puncturing", | |
| .conn_mode = IEEE80211_CONN_MODE_EHT, | |
| .expected_mode = IEEE80211_CONN_MODE_HE, | |
| .conn_bw_limit = IEEE80211_CONN_BW_LIMIT_80, | |
| .expected_bw_limit = IEEE80211_CONN_BW_LIMIT_40, | |
| .eht_disabled_subchannels = 0x08, | |
| .eht_bw = IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ, | |
| } | |
| }; | |
| KUNIT_ARRAY_PARAM_DESC(determine_chan_mode, determine_chan_mode_cases, desc) | |
| static void test_determine_chan_mode(struct kunit *test) | |
| { | |
| const struct determine_chan_mode_case *params = test->param_value; | |
| struct t_sdata *t_sdata = T_SDATA(test); | |
| struct ieee80211_conn_settings conn = { | |
| .mode = params->conn_mode, | |
| .bw_limit = params->conn_bw_limit, | |
| }; | |
| struct cfg80211_bss cbss = { | |
| .channel = &t_sdata->band_5ghz.channels[0], | |
| }; | |
| unsigned long userspace_selectors[BITS_TO_LONGS(128)] = {}; | |
| u8 bss_ies[] = { | |
| /* Supported Rates */ | |
| WLAN_EID_SUPP_RATES, 0x08, | |
| 0x82, 0x84, 0x8b, 0x96, 0xc, 0x12, 0x18, 0x24, | |
| /* Extended Supported Rates */ | |
| WLAN_EID_EXT_SUPP_RATES, 0x05, | |
| 0x30, 0x48, 0x60, 0x6c, params->extra_supp_rate, | |
| /* HT Capabilities */ | |
| WLAN_EID_HT_CAPABILITY, 0x1a, | |
| 0x0c, 0x00, 0x1b, 0xff, 0xff, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, | |
| /* HT Information (0xff for 1 stream) */ | |
| WLAN_EID_HT_OPERATION, 0x16, | |
| 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| /* VHT Capabilities */ | |
| WLAN_EID_VHT_CAPABILITY, 0xc, | |
| 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, | |
| 0xff, 0xff, 0x00, 0x00, | |
| /* VHT Operation */ | |
| WLAN_EID_VHT_OPERATION, 0x05, | |
| 0x00, 0x00, 0x00, | |
| params->vht_basic_mcs_1_4_set ? | |
| params->vht_basic_mcs_1_4 : | |
| le16_get_bits(t_sdata->band_5ghz.vht_cap.vht_mcs.rx_mcs_map, 0xff), | |
| params->vht_basic_mcs_5_8_set ? | |
| params->vht_basic_mcs_5_8 : | |
| le16_get_bits(t_sdata->band_5ghz.vht_cap.vht_mcs.rx_mcs_map, 0xff00), | |
| /* HE Capabilities */ | |
| WLAN_EID_EXTENSION, 0x16, WLAN_EID_EXT_HE_CAPABILITY, | |
| 0x01, 0x78, 0xc8, 0x1a, 0x40, 0x00, 0x00, 0xbf, | |
| 0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0xfa, 0xff, 0xfa, 0xff, | |
| /* HE Operation (permit overriding values) */ | |
| WLAN_EID_EXTENSION, 0x07, WLAN_EID_EXT_HE_OPERATION, | |
| 0xf0, 0x3f, 0x00, 0xb0, | |
| params->he_basic_mcs_1_4_set ? params->he_basic_mcs_1_4 : 0xfc, | |
| params->he_basic_mcs_5_8_set ? params->he_basic_mcs_5_8 : 0xff, | |
| /* EHT Capabilities */ | |
| WLAN_EID_EXTENSION, 0x12, WLAN_EID_EXT_EHT_CAPABILITY, | |
| 0x07, 0x00, 0x1c, 0x00, 0x00, 0xfe, 0xff, 0xff, | |
| 0x7f, 0x01, 0x00, 0x88, 0x88, 0x88, 0x00, 0x00, | |
| 0x00, | |
| /* EHT Operation */ | |
| WLAN_EID_EXTENSION, 0x0b, WLAN_EID_EXT_EHT_OPERATION, | |
| 0x03, params->eht_mcs7_min_nss ? params->eht_mcs7_min_nss : 0x11, | |
| 0x00, 0x00, 0x00, params->eht_bw, | |
| params->eht_bw == IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ ? 42 : 36, | |
| 0x00, | |
| u16_get_bits(params->eht_disabled_subchannels, 0xff), | |
| u16_get_bits(params->eht_disabled_subchannels, 0xff00), | |
| }; | |
| struct ieee80211_chan_req chanreq = {}; | |
| struct cfg80211_chan_def ap_chandef = {}; | |
| struct ieee802_11_elems *elems; | |
| /* To force EHT downgrade to HE on punctured 80 MHz downgraded to 40 MHz */ | |
| set_bit(IEEE80211_HW_DISALLOW_PUNCTURING, t_sdata->local.hw.flags); | |
| if (params->strict) | |
| set_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags); | |
| else | |
| clear_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags); | |
| t_sdata->sdata->u.mgd.ht_capa_mask = params->ht_capa_mask; | |
| t_sdata->sdata->u.mgd.vht_capa = params->vht_capa; | |
| t_sdata->sdata->u.mgd.vht_capa_mask = params->vht_capa_mask; | |
| if (params->userspace_selector) | |
| set_bit(params->userspace_selector, userspace_selectors); | |
| rcu_assign_pointer(cbss.ies, | |
| kunit_kzalloc(test, | |
| sizeof(cbss) + sizeof(bss_ies), | |
| GFP_KERNEL)); | |
| KUNIT_ASSERT_NOT_NULL(test, rcu_access_pointer(cbss.ies)); | |
| ((struct cfg80211_bss_ies *)rcu_access_pointer(cbss.ies))->len = sizeof(bss_ies); | |
| memcpy((void *)rcu_access_pointer(cbss.ies)->data, bss_ies, | |
| sizeof(bss_ies)); | |
| rcu_read_lock(); | |
| elems = ieee80211_determine_chan_mode(t_sdata->sdata, &conn, &cbss, | |
| NULL, 0, &chanreq, &ap_chandef, | |
| userspace_selectors); | |
| rcu_read_unlock(); | |
| /* We do not need elems, free them if they are valid. */ | |
| if (!IS_ERR_OR_NULL(elems)) | |
| kfree(elems); | |
| if (params->error) { | |
| KUNIT_ASSERT_TRUE(test, IS_ERR(elems)); | |
| KUNIT_ASSERT_EQ(test, PTR_ERR(elems), -params->error); | |
| } else { | |
| KUNIT_ASSERT_NOT_ERR_OR_NULL(test, elems); | |
| KUNIT_ASSERT_EQ(test, conn.mode, params->expected_mode); | |
| KUNIT_ASSERT_EQ(test, conn.bw_limit, params->expected_bw_limit); | |
| } | |
| } | |
| static struct kunit_case chan_mode_cases[] = { | |
| KUNIT_CASE_PARAM(test_determine_chan_mode, | |
| determine_chan_mode_gen_params), | |
| {} | |
| }; | |
| static struct kunit_suite chan_mode = { | |
| .name = "mac80211-mlme-chan-mode", | |
| .test_cases = chan_mode_cases, | |
| }; | |
| kunit_test_suite(chan_mode); |