root/net/mac80211/tests/chan-mode.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * KUnit tests for channel mode functions
 *
 * Copyright (C) 2024-2025 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,
                },
        }, {
                .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,
                                              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);