root/drivers/net/wireless/intel/iwlwifi/mld/tests/link-selection.c
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * KUnit tests for link selection functions
 *
 * Copyright (C) 2025 Intel Corporation
 */
#include <kunit/static_stub.h>

#include "utils.h"
#include "mld.h"
#include "link.h"
#include "iface.h"
#include "phy.h"
#include "mlo.h"

static const struct link_grading_test_case {
        const char *desc;
        struct {
                struct {
                        u8 link_id;
                        const struct cfg80211_chan_def *chandef;
                        bool active;
                        s32 signal;
                        bool has_chan_util_elem;
                        u8 chan_util; /* 0-255 , used only if has_chan_util_elem is true */
                        u8 chan_load_by_us; /* 0-100, used only if active is true */;
                } link;
        } input;
        unsigned int expected_grade;
} link_grading_cases[] = {
        {
                .desc = "channel util of 128 (50%)",
                .input.link = {
                        .link_id = 0,
                        .chandef = &chandef_2ghz_20mhz,
                        .active = false,
                        .has_chan_util_elem = true,
                        .chan_util = 128,
                },
                .expected_grade = 86,
        },
        {
                .desc = "channel util of 180 (70%)",
                .input.link = {
                        .link_id = 0,
                        .chandef = &chandef_2ghz_20mhz,
                        .active = false,
                        .has_chan_util_elem = true,
                        .chan_util = 180,
                },
                .expected_grade = 51,
        },
        {
                .desc = "channel util of 180 (70%), channel load by us of 10%",
                .input.link = {
                        .link_id = 0,
                        .chandef = &chandef_2ghz_20mhz,
                        .has_chan_util_elem = true,
                        .chan_util = 180,
                        .active = true,
                        .chan_load_by_us = 10,
                },
                .expected_grade = 67,
        },
                {
                .desc = "no channel util element",
                .input.link = {
                        .link_id = 0,
                        .chandef = &chandef_2ghz_20mhz,
                        .active = true,
                },
                .expected_grade = 120,
        },
};

KUNIT_ARRAY_PARAM_DESC(link_grading, link_grading_cases, desc);

static void setup_link(struct ieee80211_bss_conf *link)
{
        struct kunit *test = kunit_get_current_test();
        struct iwl_mld *mld = test->priv;
        const struct link_grading_test_case *test_param =
                (const void *)(test->param_value);

        KUNIT_ALLOC_AND_ASSERT(test, link->bss);

        link->bss->signal = DBM_TO_MBM(test_param->input.link.signal);

        link->chanreq.oper = *test_param->input.link.chandef;

        if (test_param->input.link.has_chan_util_elem) {
                struct cfg80211_bss_ies *ies;
                struct ieee80211_bss_load_elem bss_load = {
                        .channel_util = test_param->input.link.chan_util,
                };
                struct element *elem =
                        iwlmld_kunit_gen_element(WLAN_EID_QBSS_LOAD,
                                                 &bss_load,
                                                 sizeof(bss_load));
                unsigned int elem_len = sizeof(*elem) + sizeof(bss_load);

                KUNIT_ALLOC_AND_ASSERT_SIZE(test, ies, sizeof(*ies) + elem_len);
                memcpy(ies->data, elem, elem_len);
                ies->len = elem_len;
                rcu_assign_pointer(link->bss->beacon_ies, ies);
                rcu_assign_pointer(link->bss->ies, ies);
        }

        if (test_param->input.link.active) {
                struct ieee80211_chanctx_conf *chan_ctx =
                        wiphy_dereference(mld->wiphy, link->chanctx_conf);
                struct iwl_mld_phy *phy;

                KUNIT_ASSERT_NOT_NULL(test, chan_ctx);

                phy = iwl_mld_phy_from_mac80211(chan_ctx);

                phy->channel_load_by_us = test_param->input.link.chan_load_by_us;
        }
}

static void test_link_grading(struct kunit *test)
{
        struct iwl_mld *mld = test->priv;
        const struct link_grading_test_case *test_param =
                (const void *)(test->param_value);
        struct ieee80211_vif *vif;
        struct ieee80211_bss_conf *link;
        unsigned int actual_grade;
        /* Extract test case parameters */
        u8 link_id = test_param->input.link.link_id;
        bool active = test_param->input.link.active;
        u16 valid_links;
        struct iwl_mld_kunit_link assoc_link = {
                .chandef = test_param->input.link.chandef,
        };

        /* If the link is not active, use a different link as the assoc link */
        if (active) {
                assoc_link.id = link_id;
                valid_links = BIT(link_id);
        } else {
                assoc_link.id = BIT(ffz(BIT(link_id)));
                valid_links = BIT(assoc_link.id) | BIT(link_id);
        }

        vif = iwlmld_kunit_setup_mlo_assoc(valid_links, &assoc_link);

        wiphy_lock(mld->wiphy);
        link = wiphy_dereference(mld->wiphy, vif->link_conf[link_id]);
        KUNIT_ASSERT_NOT_NULL(test, link);

        setup_link(link);

        actual_grade = iwl_mld_get_link_grade(mld, link);
        wiphy_unlock(mld->wiphy);

        /* Assert that the returned grade matches the expected grade */
        KUNIT_EXPECT_EQ(test, actual_grade, test_param->expected_grade);
}

static struct kunit_case link_selection_cases[] = {
        KUNIT_CASE_PARAM(test_link_grading, link_grading_gen_params),
        {},
};

static struct kunit_suite link_selection = {
        .name = "iwlmld-link-selection-tests",
        .test_cases = link_selection_cases,
        .init = iwlmld_kunit_test_init,
};

kunit_test_suite(link_selection);

static const struct link_pair_case {
        const char *desc;
        const struct cfg80211_chan_def *chandef_a, *chandef_b;
        bool low_latency_vif;
        u32 chan_load_not_by_us;
        bool primary_link_active;
        u32 expected_result;
} link_pair_cases[] = {
        {
                .desc = "Unequal bandwidth, primary link inactive, EMLSR not allowed",
                .low_latency_vif = false,
                .primary_link_active = false,
                .chandef_a = &chandef_5ghz_40mhz,
                .chandef_b = &chandef_6ghz_20mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD,
        },
        {
                .desc = "Equal bandwidths, sufficient channel load, EMLSR allowed",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 11,
                .chandef_a = &chandef_5ghz_40mhz,
                .chandef_b = &chandef_6ghz_40mhz,
                .expected_result = 0,
        },
        {
                .desc = "Equal bandwidths, insufficient channel load, EMLSR not allowed",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 6,
                .chandef_a = &chandef_5ghz_80mhz,
                .chandef_b = &chandef_6ghz_80mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD,
        },
        {
                .desc = "Low latency VIF, sufficient channel load, EMLSR allowed",
                .low_latency_vif = true,
                .primary_link_active = true,
                .chan_load_not_by_us = 6,
                .chandef_a = &chandef_5ghz_160mhz,
                .chandef_b = &chandef_6ghz_160mhz,
                .expected_result = 0,
        },
        {
                .desc = "Different bandwidths (2x ratio), primary link load permits EMLSR",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 30,
                .chandef_a = &chandef_5ghz_40mhz,
                .chandef_b = &chandef_6ghz_20mhz,
                .expected_result = 0,
        },
        {
                .desc = "Different bandwidths (4x ratio), primary link load permits EMLSR",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 45,
                .chandef_a = &chandef_5ghz_80mhz,
                .chandef_b = &chandef_6ghz_20mhz,
                .expected_result = 0,
        },
        {
                .desc = "Different bandwidths (16x ratio), primary link load insufficient",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 45,
                .chandef_a = &chandef_6ghz_320mhz,
                .chandef_b = &chandef_5ghz_20mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_CHAN_LOAD,
        },
        {
                .desc = "Same band not allowed (2.4 GHz)",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 30,
                .chandef_a = &chandef_2ghz_20mhz,
                .chandef_b = &chandef_2ghz_11_20mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND,
        },
        {
                .desc = "Same band not allowed (5 GHz)",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 30,
                .chandef_a = &chandef_5ghz_40mhz,
                .chandef_b = &chandef_5ghz_40mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND,
        },
        {
                .desc = "Same band allowed (5 GHz separated)",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 30,
                .chandef_a = &chandef_5ghz_40mhz,
                .chandef_b = &chandef_5ghz_120_40mhz,
                .expected_result = 0,
        },
        {
                .desc = "Same band not allowed (6 GHz)",
                .low_latency_vif = false,
                .primary_link_active = true,
                .chan_load_not_by_us = 30,
                .chandef_a = &chandef_6ghz_160mhz,
                .chandef_b = &chandef_6ghz_221_160mhz,
                .expected_result = IWL_MLD_EMLSR_EXIT_EQUAL_BAND,
        },
};

KUNIT_ARRAY_PARAM_DESC(link_pair, link_pair_cases, desc);

static void test_iwl_mld_link_pair_allows_emlsr(struct kunit *test)
{
        const struct link_pair_case *params = test->param_value;
        struct iwl_mld *mld = test->priv;
        struct ieee80211_vif *vif;
        /* link A is the primary and link B is the secondary */
        struct iwl_mld_link_sel_data a = {
                .chandef = params->chandef_a,
                .link_id = 4,
        };
        struct iwl_mld_link_sel_data b = {
                .chandef = params->chandef_b,
                .link_id = 5,
        };
        struct iwl_mld_kunit_link assoc_link = {
                .chandef = params->primary_link_active ? a.chandef : b.chandef,
                .id = params->primary_link_active ? a.link_id : b.link_id,
        };
        u32 result;

        vif = iwlmld_kunit_setup_mlo_assoc(BIT(a.link_id) | BIT(b.link_id),
                                           &assoc_link);

        if (params->low_latency_vif)
                iwl_mld_vif_from_mac80211(vif)->low_latency_causes = 1;

        wiphy_lock(mld->wiphy);

        /* Simulate channel load */
        if (params->primary_link_active) {
                struct iwl_mld_phy *phy =
                        iwlmld_kunit_get_phy_of_link(vif, a.link_id);

                phy->avg_channel_load_not_by_us = params->chan_load_not_by_us;
        }

        result = iwl_mld_emlsr_pair_state(vif, &a, &b);

        wiphy_unlock(mld->wiphy);

        KUNIT_EXPECT_EQ(test, result, params->expected_result);
}

static struct kunit_case link_pair_criteria_test_cases[] = {
        KUNIT_CASE_PARAM(test_iwl_mld_link_pair_allows_emlsr, link_pair_gen_params),
        {}
};

static struct kunit_suite link_pair_criteria_tests = {
        .name = "iwlmld_link_pair_allows_emlsr",
        .test_cases = link_pair_criteria_test_cases,
        .init = iwlmld_kunit_test_init,
};

kunit_test_suite(link_pair_criteria_tests);