diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/tests')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/Makefile | 5 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/agg.c | 663 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/hcmd.c | 62 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/link-selection.c | 339 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/link.c | 110 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/module.c | 11 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/rx.c | 353 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/utils.c | 503 | ||||
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/tests/utils.h | 140 | 
9 files changed, 2186 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/tests/Makefile b/sys/contrib/dev/iwlwifi/mld/tests/Makefile new file mode 100644 index 000000000000..36317feb923b --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +iwlmld-tests-y += module.o hcmd.o utils.o link.o rx.o agg.o link-selection.o + +ccflags-y += -I$(src)/../ +obj-$(CONFIG_IWLWIFI_KUNIT_TESTS) += iwlmld-tests.o diff --git a/sys/contrib/dev/iwlwifi/mld/tests/agg.c b/sys/contrib/dev/iwlwifi/mld/tests/agg.c new file mode 100644 index 000000000000..29b0248cec3d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/agg.c @@ -0,0 +1,663 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <kunit/test.h> +#include <kunit/static_stub.h> +#include <kunit/skbuff.h> + +#include "utils.h" +#include "mld.h" +#include "sta.h" +#include "agg.h" +#include "rx.h" + +#define FC_QOS_DATA (IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA) +#define BA_WINDOW_SIZE 64 +#define QUEUE 0 + +static const struct reorder_buffer_case { +	const char *desc; +	struct { +		/* ieee80211_hdr fields */ +		u16 fc; +		u8 tid; +		bool multicast; +		/* iwl_rx_mpdu_desc fields */ +		u16 nssn; +		/* used also for setting hdr::seq_ctrl */ +		u16 sn; +		u8 baid; +		bool amsdu; +		bool last_subframe; +		bool old_sn; +		bool dup; +	} rx_pkt; +	struct { +		bool valid; +		u16 head_sn; +		u8 baid; +		u16 num_entries; +		/* The test prepares the reorder buffer with fake skbs based +		 * on the sequence numbers provided in @entries array. +		 */ +		struct { +			u16 sn; +			/* Set add_subframes > 0 to simulate an A-MSDU by +			 * queueing additional @add_subframes skbs in the +			 * appropriate reorder buffer entry (based on the @sn) +			 */ +			u8 add_subframes; +		} entries[BA_WINDOW_SIZE]; +	} reorder_buf_state; +	struct { +		enum iwl_mld_reorder_result reorder_res; +		u16 head_sn; +		u16 num_stored; +		u16 skb_release_order[BA_WINDOW_SIZE]; +		u16 skb_release_order_count; +	} expected; +} reorder_buffer_cases[] = { +	{ +		.desc = "RX packet with invalid BAID", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.baid = IWL_RX_REORDER_DATA_INVALID_BAID, +		}, +		.reorder_buf_state = { +			.valid = true, +		}, +		.expected = { +			/* Invalid BAID should not be buffered. The frame is +			 * passed to the network stack immediately. +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +			.num_stored = 0, +		}, +	}, +	{ +		.desc = "RX Multicast packet", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.multicast = true, +		}, +		.reorder_buf_state = { +			.valid = true, +		}, +		.expected = { +			/* Multicast packets are not buffered. The packet is +			 * passed to the network stack immediately. +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +			.num_stored = 0, +		}, +	}, +	{ +		.desc = "RX non-QoS data", +		.rx_pkt = { +			.fc = IEEE80211_FTYPE_DATA, +		}, +		.reorder_buf_state = { +			.valid = true, +		}, +		.expected = { +			/* non-QoS data frames do not require reordering. +			 * The packet is passed to the network stack +			 * immediately. +			 */ +		.reorder_res = IWL_MLD_PASS_SKB, +		}, +	}, +	{ +		.desc = "RX old SN packet, reorder buffer is not yet valid", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.old_sn = true, +		}, +		.reorder_buf_state = { +			.valid = false, +		}, +		.expected = { +			/* The buffer is invalid and the RX packet has an old +			 * SN. The packet is passed to the network stack +			 * immediately. +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +		}, +	}, +	{ +		.desc = "RX old SN packet, reorder buffer valid", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.old_sn = true, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +		}, +		.expected = { +			/* The buffer is valid and the RX packet has an old SN. +			 * The packet should be dropped. +			 */ +			.reorder_res = IWL_MLD_DROP_SKB, +			.num_stored = 0, +			.head_sn = 100, +		}, +	}, +	{ +		.desc = "RX duplicate packet", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.dup = true, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +		}, +		.expected = { +			/* Duplicate packets should be dropped */ +			.reorder_res = IWL_MLD_DROP_SKB, +			.num_stored = 0, +			.head_sn = 100, +		}, +	}, +	{ +		.desc = "RX In-order packet, sn < nssn", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 100, +			.nssn = 101, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +		}, +		.expected = { +			/* 1. Reorder buffer is empty. +			 * 2. RX packet SN is in order and less than NSSN. +			 * Packet is released to the network stack immediately +			 * and buffer->head_sn is updated to NSSN. +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +			.num_stored = 0, +			.head_sn = 101, +		}, +	}, +	{ +		.desc = "RX In-order packet, sn == head_sn", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 101, +			.nssn = 100, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 101, +		}, +		.expected = { +			/* 1. Reorder buffer is empty. +			 * 2. RX packet SN is equal to buffer->head_sn. +			 * Packet is released to the network stack immediately +			 * and buffer->head_sn is incremented. +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +			.num_stored = 0, +			.head_sn = 102, +		}, +	}, +	{ +		.desc = "RX In-order packet, IEEE80211_MAX_SN wrap around", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = IEEE80211_MAX_SN, +			.nssn = IEEE80211_MAX_SN - 1, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = IEEE80211_MAX_SN, +		}, +		.expected = { +			/* 1. Reorder buffer is empty. +			 * 2. RX SN == buffer->head_sn == IEEE80211_MAX_SN +			 * Packet is released to the network stack immediately +			 * and buffer->head_sn is incremented correctly (wraps +			 * around to 0). +			 */ +			.reorder_res = IWL_MLD_PASS_SKB, +			.num_stored = 0, +			.head_sn = 0, +		}, +	}, +	{ +		.desc = "RX Out-of-order packet, pending packet in buffer", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 100, +			.nssn = 102, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 1, +			.entries[0].sn = 101, +		}, +		.expected = { +			/* 1. Reorder buffer contains one packet with SN=101. +			 * 2. RX packet SN = buffer->head_sn. +			 * Both packets are released (in order) to the network +			 * stack as there are no gaps. +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 0, +			.head_sn = 102, +			.skb_release_order = {100, 101}, +			.skb_release_order_count = 2, +		}, +	}, +	{ +		.desc = "RX Out-of-order packet, pending packet in buffer (wrap around)", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 0, +			.nssn = 1, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = IEEE80211_MAX_SN - 1, +			.num_entries = 1, +			.entries[0].sn = IEEE80211_MAX_SN, +		}, +		.expected = { +			/* 1. Reorder buffer contains one packet with +			 *    SN=IEEE80211_MAX_SN. +			 * 2. RX Packet SN = 0 (after wrap around) +			 * Both packets are released (in order) to the network +			 * stack as there are no gaps. +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 0, +			.head_sn = 1, +			.skb_release_order = { 4095, 0 }, +			.skb_release_order_count = 2, +		}, +	}, +	{ +		.desc = "RX Out-of-order packet, filling 1/2 holes in buffer, release RX packet", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 100, +			.nssn = 101, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 1, +			.entries[0].sn = 102, +		}, +		.expected = { +			/* 1. Reorder buffer contains one packet with SN=102. +			 * 2. There are 2 holes at SN={100, 101}. +			 * Only the RX packet (SN=100) is released, there is +			 * still a hole at 101. +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 1, +			.head_sn = 101, +			.skb_release_order = {100}, +			.skb_release_order_count = 1, +		}, +	}, +	{ +		.desc = "RX Out-of-order packet, filling 1/2 holes, release 2 packets", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 102, +			.nssn = 103, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 3, +			.entries[0].sn = 101, +			.entries[1].sn = 104, +			.entries[2].sn = 105, +		}, +		.expected = { +			/* 1. Reorder buffer contains three packets. +			 * 2. RX packet fills one of two holes (at SN=102). +			 * Two packets are released (until the next hole at +			 * SN=103). +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 2, +			.head_sn = 103, +			.skb_release_order = {101, 102}, +			.skb_release_order_count = 2, +		}, +	}, +		{ +		.desc = "RX Out-of-order packet, filling 1/1 holes, no packets released", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 102, +			.nssn = 100, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 3, +			.entries[0].sn = 101, +			.entries[1].sn = 103, +			.entries[2].sn = 104, +		}, +		.expected = { +			/* 1. Reorder buffer contains three packets: +			 *    SN={101, 103, 104}. +			 * 2. RX packet fills a hole (SN=102), but NSSN is +			 *    smaller than buffered frames. +			 * No packets can be released yet and buffer->head_sn +			 * is not updated. +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 4, +			.head_sn = 100, +		}, +	}, +	{ +		.desc = "RX In-order A-MSDU, last subframe", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 100, +			.nssn = 101, +			.amsdu = true, +			.last_subframe = true, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 1, +			.entries[0] = { +				.sn = 100, +				.add_subframes = 1, +			}, +		}, +		.expected = { +			/* 1. Reorder buffer contains a 2-sub frames A-MSDU +			 *    at SN=100. +			 * 2. RX packet is the last SN=100 A-MSDU subframe +			 * All packets are released in order (3 x SN=100). +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 0, +			.head_sn = 101, +			.skb_release_order = {100, 100, 100}, +			.skb_release_order_count = 3, +		}, +	}, +	{ +		.desc = "RX In-order A-MSDU, not the last subframe", +		.rx_pkt = { +			.fc = FC_QOS_DATA, +			.sn = 100, +			.nssn = 101, +			.amsdu = true, +			.last_subframe = false, +		}, +		.reorder_buf_state = { +			.valid = true, +			.head_sn = 100, +			.num_entries = 1, +			.entries[0] = { +				.sn = 100, +				.add_subframes = 1, +			}, +		}, +		.expected = { +			/* 1. Reorder buffer contains a 2-sub frames A-MSDU +			 *    at SN=100. +			 * 2. RX packet additional SN=100 A-MSDU subframe, +			 *    but not the last one +			 * No packets are released and head_sn is not updated. +			 */ +			.reorder_res = IWL_MLD_BUFFERED_SKB, +			.num_stored = 3, +			.head_sn = 100, +		}, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(test_reorder_buffer, reorder_buffer_cases, desc); + +static struct sk_buff_head g_released_skbs; +static u16 g_num_released_skbs; + +/* Add released skbs from reorder buffer to a global list; This allows us + * to verify the correct release order of packets after they pass through the + * simulated reorder logic. + */ +static void +fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, +				     struct napi_struct *napi, +				     struct sk_buff *skb, int queue, +				     struct ieee80211_sta *sta) +{ +	__skb_queue_tail(&g_released_skbs, skb); +	g_num_released_skbs++; +} + +static u32 +fake_iwl_mld_fw_sta_id_mask(struct iwl_mld *mld, struct ieee80211_sta *sta) +{ +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld_link_sta *mld_link_sta; +	u8 link_id; +	u32 sta_mask = 0; + +	/* This is the expectation in the real function */ +	lockdep_assert_wiphy(mld->wiphy); + +	/* We can't use for_each_sta_active_link */ +	for_each_mld_link_sta(mld_sta, mld_link_sta, link_id) +		sta_mask |= BIT(mld_link_sta->fw_id); +	return sta_mask; +} + +static struct iwl_rx_mpdu_desc *setup_mpdu_desc(void) +{ +	struct kunit *test = kunit_get_current_test(); +	const struct reorder_buffer_case *param = +		(const void *)(test->param_value); +	struct iwl_rx_mpdu_desc *mpdu_desc; + +	KUNIT_ALLOC_AND_ASSERT(test, mpdu_desc); + +	mpdu_desc->reorder_data |= +		le32_encode_bits(param->rx_pkt.baid, +				 IWL_RX_MPDU_REORDER_BAID_MASK); +	mpdu_desc->reorder_data |= +		le32_encode_bits(param->rx_pkt.sn, +				 IWL_RX_MPDU_REORDER_SN_MASK); +	mpdu_desc->reorder_data |= +		le32_encode_bits(param->rx_pkt.nssn, +				 IWL_RX_MPDU_REORDER_NSSN_MASK); +	if (param->rx_pkt.old_sn) +		mpdu_desc->reorder_data |= +			cpu_to_le32(IWL_RX_MPDU_REORDER_BA_OLD_SN); + +	if (param->rx_pkt.dup) +		mpdu_desc->status |= cpu_to_le32(IWL_RX_MPDU_STATUS_DUPLICATE); + +	if (param->rx_pkt.amsdu) { +		mpdu_desc->mac_flags2 |= IWL_RX_MPDU_MFLG2_AMSDU; +		if (param->rx_pkt.last_subframe) +			mpdu_desc->amsdu_info |= +				IWL_RX_MPDU_AMSDU_LAST_SUBFRAME; +	} + +	return mpdu_desc; +} + +static struct sk_buff *alloc_and_setup_skb(u16 fc, u16 seq_ctrl, u8 tid, +					   bool mcast) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_hdr hdr = { +		.frame_control = cpu_to_le16(fc), +		.seq_ctrl = cpu_to_le16(seq_ctrl), +	}; +	struct sk_buff *skb; + +	skb = kunit_zalloc_skb(test, 128, GFP_KERNEL); +	KUNIT_ASSERT_NOT_NULL(test, skb); + +	if (ieee80211_is_data_qos(hdr.frame_control)) { +		u8 *qc = ieee80211_get_qos_ctl(&hdr); + +		qc[0] = tid & IEEE80211_QOS_CTL_TID_MASK; +	} + +	if (mcast) +		hdr.addr1[0] = 0x1; + +	skb_set_mac_header(skb, skb->len); +	skb_put_data(skb, &hdr, ieee80211_hdrlen(hdr.frame_control)); + +	return skb; +} + +static struct iwl_mld_reorder_buffer * +setup_reorder_buffer(struct iwl_mld_baid_data *baid_data) +{ +	struct kunit *test = kunit_get_current_test(); +	const struct reorder_buffer_case *param = +		(const void *)(test->param_value); +	struct iwl_mld_reorder_buffer *buffer = baid_data->reorder_buf; +	struct iwl_mld_reorder_buf_entry *entries = baid_data->entries; +	struct sk_buff *fake_skb; + +	buffer->valid = param->reorder_buf_state.valid; +	buffer->head_sn = param->reorder_buf_state.head_sn; +	buffer->queue = QUEUE; + +	for (int i = 0; i < baid_data->buf_size; i++) +		__skb_queue_head_init(&entries[i].frames); + +	for (int i = 0; i < param->reorder_buf_state.num_entries; i++) { +		u16 sn = param->reorder_buf_state.entries[i].sn; +		int index = sn % baid_data->buf_size; +		u8 add_subframes = +			param->reorder_buf_state.entries[i].add_subframes; +		/* create 1 skb per entry + additional skbs per num of +		 * requested subframes +		 */ +		u8 num_skbs = 1 + add_subframes; + +		for (int j = 0; j < num_skbs; j++) { +			fake_skb = alloc_and_setup_skb(FC_QOS_DATA, sn, 0, +						       false); +			__skb_queue_tail(&entries[index].frames, fake_skb); +			buffer->num_stored++; +		} +	} + +	return buffer; +} + +static struct iwl_mld_reorder_buffer *setup_ba_data(struct ieee80211_sta *sta) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	const struct reorder_buffer_case *param = +		(const void *)(test->param_value); +	struct iwl_mld_baid_data *baid_data = NULL; +	struct iwl_mld_reorder_buffer *buffer; +	u32 reorder_buf_size = BA_WINDOW_SIZE * sizeof(baid_data->entries[0]); +	u8 baid = param->reorder_buf_state.baid; + +	/* Assuming only 1 RXQ */ +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, baid_data, +				    sizeof(*baid_data) + reorder_buf_size); + +	baid_data->baid = baid; +	baid_data->tid = param->rx_pkt.tid; +	baid_data->buf_size = BA_WINDOW_SIZE; + +	wiphy_lock(mld->wiphy); +	baid_data->sta_mask = iwl_mld_fw_sta_id_mask(mld, sta); +	wiphy_unlock(mld->wiphy); + +	baid_data->entries_per_queue = BA_WINDOW_SIZE; + +	buffer = setup_reorder_buffer(baid_data); + +	KUNIT_EXPECT_NULL(test, rcu_access_pointer(mld->fw_id_to_ba[baid])); +	rcu_assign_pointer(mld->fw_id_to_ba[baid], baid_data); + +	return buffer; +} + +static void test_reorder_buffer(struct kunit *test) +{ +	struct iwl_mld *mld = test->priv; +	const struct reorder_buffer_case *param = +		(const void *)(test->param_value); +	struct iwl_rx_mpdu_desc *mpdu_desc; +	struct ieee80211_vif *vif; +	struct ieee80211_sta *sta; +	struct sk_buff *skb; +	struct iwl_mld_reorder_buffer *buffer; +	enum iwl_mld_reorder_result reorder_res; +	u16 skb_release_order_count = param->expected.skb_release_order_count; +	u16 skb_idx = 0; + +	/* Init globals and activate stubs */ +	__skb_queue_head_init(&g_released_skbs); +	g_num_released_skbs = 0; +	kunit_activate_static_stub(test, iwl_mld_fw_sta_id_mask, +				   fake_iwl_mld_fw_sta_id_mask); +	kunit_activate_static_stub(test, iwl_mld_pass_packet_to_mac80211, +				   fake_iwl_mld_pass_packet_to_mac80211); + +	vif = iwlmld_kunit_add_vif(false, NL80211_IFTYPE_STATION); +	sta = iwlmld_kunit_setup_sta(vif, IEEE80211_STA_AUTHORIZED, -1); + +	/* Prepare skb, mpdu_desc, BA data and the reorder buffer */ +	skb = alloc_and_setup_skb(param->rx_pkt.fc, param->rx_pkt.sn, +				  param->rx_pkt.tid, param->rx_pkt.multicast); +	buffer = setup_ba_data(sta); +	mpdu_desc = setup_mpdu_desc(); + +	rcu_read_lock(); +	reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc); +	rcu_read_unlock(); + +	KUNIT_ASSERT_EQ(test, reorder_res, param->expected.reorder_res); +	KUNIT_ASSERT_EQ(test, buffer->num_stored, param->expected.num_stored); +	KUNIT_ASSERT_EQ(test, buffer->head_sn, param->expected.head_sn); + +	/* Verify skbs release order */ +	KUNIT_ASSERT_EQ(test, skb_release_order_count, g_num_released_skbs); +	while ((skb = __skb_dequeue(&g_released_skbs))) { +		struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb); + +		KUNIT_ASSERT_EQ(test, le16_to_cpu(hdr->seq_ctrl), +				param->expected.skb_release_order[skb_idx]); +		skb_idx++; +	} +	KUNIT_ASSERT_EQ(test, skb_idx, skb_release_order_count); +} + +static struct kunit_case reorder_buffer_test_cases[] = { +	KUNIT_CASE_PARAM(test_reorder_buffer, test_reorder_buffer_gen_params), +	{}, +}; + +static struct kunit_suite reorder_buffer = { +	.name = "iwlmld-reorder-buffer", +	.test_cases = reorder_buffer_test_cases, +	.init = iwlmld_kunit_test_init, +}; + +kunit_test_suite(reorder_buffer); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/hcmd.c b/sys/contrib/dev/iwlwifi/mld/tests/hcmd.c new file mode 100644 index 000000000000..0e3b9417dd63 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/hcmd.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <kunit/test.h> + +#include <iwl-trans.h> +#include "mld.h" + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +static void test_hcmd_names_sorted(struct kunit *test) +{ +	int i; + +	for (i = 0; i < global_iwl_mld_goups_size; i++) { +		const struct iwl_hcmd_arr *arr = &iwl_mld_groups[i]; +		int j; + +		if (!arr->arr) +			continue; +		for (j = 0; j < arr->size - 1; j++) +			KUNIT_EXPECT_LE(test, arr->arr[j].cmd_id, +					arr->arr[j + 1].cmd_id); +	} +} + +static void test_hcmd_names_for_rx(struct kunit *test) +{ +	static struct iwl_trans t = { +		.conf.command_groups = iwl_mld_groups, +	}; + +	t.conf.command_groups_size = global_iwl_mld_goups_size; + +	for (unsigned int i = 0; i < iwl_mld_rx_handlers_num; i++) { +		const struct iwl_rx_handler *rxh; +		const char *name; + +		rxh = &iwl_mld_rx_handlers[i]; + +		name = iwl_get_cmd_string(&t, rxh->cmd_id); +		KUNIT_EXPECT_NOT_NULL(test, name); +		KUNIT_EXPECT_NE_MSG(test, strcmp(name, "UNKNOWN"), 0, +				    "ID 0x%04x is UNKNOWN", rxh->cmd_id); +	} +} + +static struct kunit_case hcmd_names_cases[] = { +	KUNIT_CASE(test_hcmd_names_sorted), +	KUNIT_CASE(test_hcmd_names_for_rx), +	{}, +}; + +static struct kunit_suite hcmd_names = { +	.name = "iwlmld-hcmd-names", +	.test_cases = hcmd_names_cases, +}; + +kunit_test_suite(hcmd_names); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/link-selection.c b/sys/contrib/dev/iwlwifi/mld/tests/link-selection.c new file mode 100644 index 000000000000..766c24db3613 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/link-selection.c @@ -0,0 +1,339 @@ +// 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); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/link.c b/sys/contrib/dev/iwlwifi/mld/tests/link.c new file mode 100644 index 000000000000..69a0d67858bf --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/link.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <kunit/static_stub.h> + +#include "utils.h" +#include "mld.h" +#include "link.h" +#include "iface.h" +#include "fw/api/mac-cfg.h" + +static const struct missed_beacon_test_case { +	const char *desc; +	struct { +		struct iwl_missed_beacons_notif notif; +		bool emlsr; +	} input; +	struct { +		bool disconnected; +		bool emlsr; +	} output; +} missed_beacon_cases[] = { +	{ +		.desc = "no EMLSR, no disconnect", +		.input.notif = { +			.consec_missed_beacons = cpu_to_le32(4), +		}, +	}, +	{ +		.desc = "no EMLSR, no beacon loss since Rx, no disconnect", +		.input.notif = { +			.consec_missed_beacons = cpu_to_le32(20), +		}, +	}, +	{ +		.desc = "no EMLSR, beacon loss since Rx, disconnect", +		.input.notif = { +			.consec_missed_beacons = cpu_to_le32(20), +			.consec_missed_beacons_since_last_rx = +				cpu_to_le32(10), +		}, +		.output.disconnected = true, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(test_missed_beacon, missed_beacon_cases, desc); + +static void fake_ieee80211_connection_loss(struct ieee80211_vif *vif) +{ +	vif->cfg.assoc = false; +} + +static void test_missed_beacon(struct kunit *test) +{ +	struct iwl_mld *mld = test->priv; +	struct iwl_missed_beacons_notif *notif; +	const struct missed_beacon_test_case *test_param = +		(const void *)(test->param_value); +	struct ieee80211_vif *vif; +	struct iwl_rx_packet *pkt; +	struct iwl_mld_kunit_link link1 = { +		.id = 0, +		.chandef = &chandef_6ghz_160mhz, +	}; +	struct iwl_mld_kunit_link link2 = { +		.id = 1, +		.chandef = &chandef_5ghz_80mhz, +	}; + +	kunit_activate_static_stub(test, ieee80211_connection_loss, +				   fake_ieee80211_connection_loss); +	pkt = iwl_mld_kunit_create_pkt(test_param->input.notif); +	notif = (void *)pkt->data; + +	if (test_param->input.emlsr) { +		vif = iwlmld_kunit_assoc_emlsr(&link1, &link2); +	} else { +		struct iwl_mld_vif *mld_vif; + +		vif = iwlmld_kunit_setup_non_mlo_assoc(&link1); +		mld_vif = iwl_mld_vif_from_mac80211(vif); +		notif->link_id = cpu_to_le32(mld_vif->deflink.fw_id); +	} + +	wiphy_lock(mld->wiphy); + +	iwl_mld_handle_missed_beacon_notif(mld, pkt); + +	wiphy_unlock(mld->wiphy); + +	KUNIT_ASSERT_NE(test, vif->cfg.assoc, test_param->output.disconnected); + +	/* TODO: add test cases for esr and check */ +} + +static struct kunit_case link_cases[] = { +	KUNIT_CASE_PARAM(test_missed_beacon, test_missed_beacon_gen_params), +	{}, +}; + +static struct kunit_suite link = { +	.name = "iwlmld-link", +	.test_cases = link_cases, +	.init = iwlmld_kunit_test_init, +}; + +kunit_test_suite(link); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/module.c b/sys/contrib/dev/iwlwifi/mld/tests/module.c new file mode 100644 index 000000000000..5d9818587b23 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/module.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * This is just module boilerplate for the iwlmld kunit module. + * + * Copyright (C) 2024 Intel Corporation + */ +#include <linux/module.h> + +MODULE_IMPORT_NS("IWLWIFI"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("kunit tests for iwlmld"); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/rx.c b/sys/contrib/dev/iwlwifi/mld/tests/rx.c new file mode 100644 index 000000000000..20cb4e03ab41 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/rx.c @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024 Intel Corporation + */ +#include <kunit/test.h> +#include "utils.h" +#include "iwl-trans.h" +#include "mld.h" +#include "sta.h" + +static const struct is_dup_case { +	const char *desc; +	struct { +		/* ieee80211_hdr fields */ +		__le16 fc; +		__le16 seq; +		u8 tid; +		bool multicast; +		/* iwl_rx_mpdu_desc fields */ +		bool is_amsdu; +		u8 sub_frame_idx; +	} rx_pkt; +	struct { +		__le16 last_seq; +		u8 last_sub_frame_idx; +		u8 tid; +	} dup_data_state; +	struct { +		bool is_dup; +		u32 rx_status_flag; +	} result; +} is_dup_cases[] = { +	{ +		.desc = "Control frame", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_CTL), +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = 0, +		} +	}, +	{ +		.desc = "Null func frame", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_NULLFUNC), +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = 0, +		} +	}, +	{ +		.desc = "Multicast data", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA), +			.multicast = true, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = 0, +		} +	}, +	{ +		.desc = "QoS null func frame", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_NULLFUNC), +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = 0, +		} +	}, +	{ +		.desc = "QoS data new sequence", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x101), +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "QoS data same sequence, no retry", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x100), +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "QoS data same sequence, has retry", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA | +					    IEEE80211_FCTL_RETRY), +			.seq = __cpu_to_le16(0x100), +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = true, +			.rx_status_flag = 0, +		}, +	}, +	{ +		.desc = "QoS data invalid tid", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x100), +			.tid = IWL_MAX_TID_COUNT + 1, +		}, +		.result = { +			.is_dup = true, +			.rx_status_flag = 0, +		}, +	}, +	{ +		.desc = "non-QoS data, same sequence, same tid, no retry", +		.rx_pkt = { +			/* Driver will use tid = IWL_MAX_TID_COUNT */ +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA), +			.seq = __cpu_to_le16(0x100), +		}, +		.dup_data_state = { +			.tid = IWL_MAX_TID_COUNT, +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "non-QoS data, same sequence, same tid, has retry", +		.rx_pkt = { +			/* Driver will use tid = IWL_MAX_TID_COUNT */ +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_FCTL_RETRY), +			.seq = __cpu_to_le16(0x100), +		}, +		.dup_data_state = { +			.tid = IWL_MAX_TID_COUNT, +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = true, +			.rx_status_flag = 0, +		}, +	}, +	{ +		.desc = "non-QoS data, same sequence on different tid's", +		.rx_pkt = { +			/* Driver will use tid = IWL_MAX_TID_COUNT */ +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA), +			.seq = __cpu_to_le16(0x100), +		}, +		.dup_data_state = { +			.tid = 7, +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "A-MSDU new subframe, allow same PN", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x100), +			.is_amsdu = true, +			.sub_frame_idx = 1, +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_ALLOW_SAME_PN | +					  RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "A-MSDU subframe with smaller idx, disallow same PN", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x100), +			.is_amsdu = true, +			.sub_frame_idx = 1, +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 2, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "A-MSDU same subframe, no retry, disallow same PN", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA), +			.seq = __cpu_to_le16(0x100), +			.is_amsdu = true, +			.sub_frame_idx = 0, +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = false, +			.rx_status_flag = RX_FLAG_DUP_VALIDATED, +		}, +	}, +	{ +		.desc = "A-MSDU same subframe, has retry", +		.rx_pkt = { +			.fc = __cpu_to_le16(IEEE80211_FTYPE_DATA | +					    IEEE80211_STYPE_QOS_DATA | +					    IEEE80211_FCTL_RETRY), +			.seq = __cpu_to_le16(0x100), +			.is_amsdu = true, +			.sub_frame_idx = 0, +		}, +		.dup_data_state = { +			.last_seq = __cpu_to_le16(0x100), +			.last_sub_frame_idx = 0, +		}, +		.result = { +			.is_dup = true, +			.rx_status_flag = 0, +		}, +	}, +}; + +KUNIT_ARRAY_PARAM_DESC(test_is_dup, is_dup_cases, desc); + +static void +setup_dup_data_state(struct ieee80211_sta *sta) +{ +	struct kunit *test = kunit_get_current_test(); +	const struct is_dup_case *param = (const void *)(test->param_value); +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	u8 tid = param->dup_data_state.tid; +	struct iwl_mld_rxq_dup_data *dup_data; + +	/* Allocate dup_data only for 1 queue */ +	KUNIT_ALLOC_AND_ASSERT(test, dup_data); + +	/* Initialize dup data, see iwl_mld_alloc_dup_data */ +	memset(dup_data->last_seq, 0xff, sizeof(dup_data->last_seq)); + +	dup_data->last_seq[tid] = param->dup_data_state.last_seq; +	dup_data->last_sub_frame_idx[tid] = +		param->dup_data_state.last_sub_frame_idx; + +	mld_sta->dup_data = dup_data; +} + +static void setup_rx_pkt(const struct is_dup_case *param, +			 struct ieee80211_hdr *hdr, +			 struct iwl_rx_mpdu_desc *mpdu_desc) +{ +	u8 tid = param->rx_pkt.tid; + +	/* Set "new rx packet" header */ +	hdr->frame_control = param->rx_pkt.fc; +	hdr->seq_ctrl = param->rx_pkt.seq; + +	if (ieee80211_is_data_qos(hdr->frame_control)) { +		u8 *qc = ieee80211_get_qos_ctl(hdr); + +		qc[0] = tid & IEEE80211_QOS_CTL_TID_MASK; +	} + +	if (param->rx_pkt.multicast) +		hdr->addr1[0] = 0x1; + +	/* Set mpdu_desc */ +	mpdu_desc->amsdu_info = param->rx_pkt.sub_frame_idx & +				IWL_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; +	if (param->rx_pkt.is_amsdu) +		mpdu_desc->mac_flags2 |= IWL_RX_MPDU_MFLG2_AMSDU; +} + +static void test_is_dup(struct kunit *test) +{ +	const struct is_dup_case *param = (const void *)(test->param_value); +	struct iwl_mld *mld = test->priv; +	struct iwl_rx_mpdu_desc mpdu_desc = { }; +	struct ieee80211_rx_status rx_status = { }; +	struct ieee80211_vif *vif; +	struct ieee80211_sta *sta; +	struct ieee80211_hdr hdr; + +	vif = iwlmld_kunit_add_vif(false, NL80211_IFTYPE_STATION); +	sta = iwlmld_kunit_setup_sta(vif, IEEE80211_STA_AUTHORIZED, -1); + +	/* Prepare test case state */ +	setup_dup_data_state(sta); +	setup_rx_pkt(param, &hdr, &mpdu_desc); + +	KUNIT_EXPECT_EQ(test, +			iwl_mld_is_dup(mld, sta, &hdr, &mpdu_desc, &rx_status, +				       0), /* assuming only 1 queue */ +			param->result.is_dup); +	KUNIT_EXPECT_EQ(test, rx_status.flag, param->result.rx_status_flag); +} + +static struct kunit_case is_dup_test_cases[] = { +	KUNIT_CASE_PARAM(test_is_dup, test_is_dup_gen_params), +	{}, +}; + +static struct kunit_suite is_dup = { +	.name = "iwlmld-rx-is-dup", +	.test_cases = is_dup_test_cases, +	.init = iwlmld_kunit_test_init, +}; + +kunit_test_suite(is_dup); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/utils.c b/sys/contrib/dev/iwlwifi/mld/tests/utils.c new file mode 100644 index 000000000000..26cf27be762d --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/utils.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * KUnit tests for channel helper functions + * + * Copyright (C) 2024-2025 Intel Corporation + */ +#include <kunit/test.h> +#include <kunit/test-bug.h> + +#include "utils.h" + +#include <linux/device.h> + +#include "fw/api/scan.h" +#include "fw/api/mac-cfg.h" +#include "iwl-trans.h" +#include "mld.h" +#include "iface.h" +#include "link.h" +#include "phy.h" +#include "sta.h" + +int iwlmld_kunit_test_init(struct kunit *test) +{ +	struct iwl_mld *mld; +	struct iwl_trans *trans; +	const struct iwl_rf_cfg *cfg; +	struct iwl_fw *fw; +	struct ieee80211_hw *hw; + +	KUNIT_ALLOC_AND_ASSERT(test, trans); +	KUNIT_ALLOC_AND_ASSERT(test, trans->dev); +	KUNIT_ALLOC_AND_ASSERT(test, cfg); +	KUNIT_ALLOC_AND_ASSERT(test, fw); +	KUNIT_ALLOC_AND_ASSERT(test, hw); +	KUNIT_ALLOC_AND_ASSERT(test, hw->wiphy); + +	mutex_init(&hw->wiphy->mtx); + +	/* Allocate and initialize the mld structure */ +	KUNIT_ALLOC_AND_ASSERT(test, mld); +	iwl_construct_mld(mld, trans, cfg, fw, hw, NULL); + +	fw->ucode_capa.num_stations = IWL_STATION_COUNT_MAX; +	fw->ucode_capa.num_links = IWL_FW_MAX_LINK_ID + 1; + +	mld->fwrt.trans = trans; +	mld->fwrt.fw = fw; +	mld->fwrt.dev = trans->dev; + +	/* TODO: add priv_size to hw allocation and setup hw->priv to enable +	 * testing mac80211 callbacks +	 */ + +	KUNIT_ALLOC_AND_ASSERT(test, mld->nvm_data); +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, mld->scan.cmd, +				    sizeof(struct iwl_scan_req_umac_v17)); +	mld->scan.cmd_size = sizeof(struct iwl_scan_req_umac_v17); + +	/* This is not the state at the end of the regular opmode_start, +	 * but it is more common to need it. Explicitly undo this if needed. +	 */ +	mld->trans->state = IWL_TRANS_FW_ALIVE; +	mld->fw_status.running = true; + +	/* Avoid passing mld struct around */ +	test->priv = mld; +	return 0; +} + +IWL_MLD_ALLOC_FN(link, bss_conf) + +static void iwlmld_kunit_init_link(struct ieee80211_vif *vif, +				   struct ieee80211_bss_conf *link, +				   struct iwl_mld_link *mld_link, int link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	int ret; + +	/* setup mac80211 link */ +	rcu_assign_pointer(vif->link_conf[link_id], link); +	link->link_id = link_id; +	link->vif = vif; +	link->beacon_int = 100; +	link->dtim_period = 3; +	link->qos = true; + +	/* and mld_link */ +	ret = iwl_mld_allocate_link_fw_id(mld, &mld_link->fw_id, link); +	KUNIT_ASSERT_EQ(test, ret, 0); +	rcu_assign_pointer(mld_vif->link[link_id], mld_link); +	rcu_assign_pointer(vif->link_conf[link_id], link); +} + +IWL_MLD_ALLOC_FN(vif, vif) + +/* Helper function to add and initialize a VIF for KUnit tests */ +struct ieee80211_vif *iwlmld_kunit_add_vif(bool mlo, enum nl80211_iftype type) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct ieee80211_vif *vif; +	struct iwl_mld_vif *mld_vif; +	int ret; + +	/* TODO: support more types */ +	KUNIT_ASSERT_EQ(test, type, NL80211_IFTYPE_STATION); + +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, vif, +				    sizeof(*vif) + sizeof(*mld_vif)); + +	vif->type = type; +	mld_vif = iwl_mld_vif_from_mac80211(vif); +	mld_vif->mld = mld; + +	ret = iwl_mld_allocate_vif_fw_id(mld, &mld_vif->fw_id, vif); +	KUNIT_ASSERT_EQ(test, ret, 0); + +	/* TODO: revisit (task=EHT) */ +	if (mlo) +		return vif; + +	/* Initialize the default link */ +	iwlmld_kunit_init_link(vif, &vif->bss_conf, &mld_vif->deflink, 0); + +	return vif; +} + +/* Use only for MLO vif */ +struct ieee80211_bss_conf * +iwlmld_kunit_add_link(struct ieee80211_vif *vif, int link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_bss_conf *link; +	struct iwl_mld_link *mld_link; + +	KUNIT_ALLOC_AND_ASSERT(test, link); +	KUNIT_ALLOC_AND_ASSERT(test, mld_link); + +	iwlmld_kunit_init_link(vif, link, mld_link, link_id); +	vif->valid_links |= BIT(link_id); + +	return link; +} + +struct ieee80211_chanctx_conf * +iwlmld_kunit_add_chanctx(const struct cfg80211_chan_def *def) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct ieee80211_chanctx_conf *ctx; +	struct iwl_mld_phy *phy; +	int fw_id; + +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, ctx, sizeof(*ctx) + sizeof(*phy)); + +	/* Setup the chanctx conf */ +	ctx->def = *def; +	ctx->min_def = *def; +	ctx->ap = *def; + +	/* and the iwl_mld_phy */ +	phy = iwl_mld_phy_from_mac80211(ctx); + +	fw_id = iwl_mld_allocate_fw_phy_id(mld); +	KUNIT_ASSERT_GE(test, fw_id, 0); + +	phy->fw_id = fw_id; +	phy->mld = mld; +	phy->chandef = *def; + +	return ctx; +} + +void iwlmld_kunit_assign_chanctx_to_link(struct ieee80211_vif *vif, +					 struct ieee80211_bss_conf *link, +					 struct ieee80211_chanctx_conf *ctx) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct iwl_mld_link *mld_link; + +	KUNIT_EXPECT_NULL(test, rcu_access_pointer(link->chanctx_conf)); +	rcu_assign_pointer(link->chanctx_conf, ctx); + +	lockdep_assert_wiphy(mld->wiphy); + +	mld_link = iwl_mld_link_from_mac80211(link); + +	KUNIT_EXPECT_NULL(test, rcu_access_pointer(mld_link->chan_ctx)); +	KUNIT_EXPECT_FALSE(test, mld_link->active); + +	rcu_assign_pointer(mld_link->chan_ctx, ctx); +	mld_link->active = true; + +	if (ieee80211_vif_is_mld(vif)) +		vif->active_links |= BIT(link->link_id); +} + +IWL_MLD_ALLOC_FN(link_sta, link_sta) + +static void iwlmld_kunit_add_link_sta(struct ieee80211_sta *sta, +				      struct ieee80211_link_sta *link_sta, +				      struct iwl_mld_link_sta *mld_link_sta, +				      u8 link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld *mld = test->priv; +	u8 fw_id; +	int ret; + +	/* initialize mac80211's link_sta */ +	link_sta->link_id = link_id; +	rcu_assign_pointer(sta->link[link_id], link_sta); + +	link_sta->sta = sta; + +	/* and the iwl_mld_link_sta */ +	ret = iwl_mld_allocate_link_sta_fw_id(mld, &fw_id, link_sta); +	KUNIT_ASSERT_EQ(test, ret, 0); +	mld_link_sta->fw_id = fw_id; + +	rcu_assign_pointer(mld_sta->link[link_id], mld_link_sta); +} + +static struct ieee80211_link_sta * +iwlmld_kunit_alloc_link_sta(struct ieee80211_sta *sta, int link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_link_sta *link_sta; +	struct iwl_mld_link_sta *mld_link_sta; + +	/* Only valid for MLO */ +	KUNIT_ASSERT_TRUE(test, sta->valid_links); + +	KUNIT_ALLOC_AND_ASSERT(test, link_sta); +	KUNIT_ALLOC_AND_ASSERT(test, mld_link_sta); + +	iwlmld_kunit_add_link_sta(sta, link_sta, mld_link_sta, link_id); + +	sta->valid_links |= BIT(link_id); + +	return link_sta; +} + +/* Allocate and initialize a STA with the first link_sta */ +static struct ieee80211_sta * +iwlmld_kunit_add_sta(struct ieee80211_vif *vif, int link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_sta *sta; +	struct iwl_mld_sta *mld_sta; + +	/* Allocate memory for ieee80211_sta with embedded iwl_mld_sta */ +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, sta, sizeof(*sta) + sizeof(*mld_sta)); + +	/* TODO: allocate and initialize the TXQs ? */ + +	mld_sta = iwl_mld_sta_from_mac80211(sta); +	mld_sta->vif = vif; +	mld_sta->mld = test->priv; + +	/* TODO: adjust for internal stations */ +	mld_sta->sta_type = STATION_TYPE_PEER; + +	if (link_id >= 0) { +		iwlmld_kunit_add_link_sta(sta, &sta->deflink, +					  &mld_sta->deflink, link_id); +		sta->valid_links = BIT(link_id); +	} else { +		iwlmld_kunit_add_link_sta(sta, &sta->deflink, +					  &mld_sta->deflink, 0); +	} +	return sta; +} + +/* Move s STA to a state */ +static void iwlmld_kunit_move_sta_state(struct ieee80211_vif *vif, +					struct ieee80211_sta *sta, +					enum ieee80211_sta_state state) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld_sta *mld_sta; +	struct iwl_mld_vif *mld_vif; + +	/* The sta will be removed automatically at the end of the test */ +	KUNIT_ASSERT_NE(test, state, IEEE80211_STA_NOTEXIST); + +	mld_sta = iwl_mld_sta_from_mac80211(sta); +	mld_sta->sta_state = state; + +	mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif); +	mld_vif->authorized = state == IEEE80211_STA_AUTHORIZED; + +	if (vif->type == NL80211_IFTYPE_STATION && !sta->tdls) +		mld_vif->ap_sta = sta; +} + +struct ieee80211_sta *iwlmld_kunit_setup_sta(struct ieee80211_vif *vif, +					     enum ieee80211_sta_state state, +					     int link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_sta *sta; + +	/* The sta will be removed automatically at the end of the test */ +	KUNIT_ASSERT_NE(test, state, IEEE80211_STA_NOTEXIST); + +	/* First - allocate and init the STA */ +	sta = iwlmld_kunit_add_sta(vif, link_id); + +	/* Now move it all the way to the wanted state */ +	for (enum ieee80211_sta_state _state = IEEE80211_STA_NONE; +	     _state <= state; _state++) +		iwlmld_kunit_move_sta_state(vif, sta, state); + +	return sta; +} + +static void iwlmld_kunit_set_vif_associated(struct ieee80211_vif *vif) +{ +	/* TODO: setup chanreq */ +	/* TODO setup capabilities */ + +	vif->cfg.assoc = 1; +} + +static struct ieee80211_vif * +iwlmld_kunit_setup_assoc(bool mlo, struct iwl_mld_kunit_link *assoc_link) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct ieee80211_vif *vif; +	struct ieee80211_bss_conf *link; +	struct ieee80211_chanctx_conf *chan_ctx; + +	KUNIT_ASSERT_TRUE(test, mlo || assoc_link->id == 0); + +	vif = iwlmld_kunit_add_vif(mlo, NL80211_IFTYPE_STATION); + +	if (mlo) +		link = iwlmld_kunit_add_link(vif, assoc_link->id); +	else +		link = &vif->bss_conf; + +	chan_ctx = iwlmld_kunit_add_chanctx(assoc_link->chandef); + +	wiphy_lock(mld->wiphy); +	iwlmld_kunit_assign_chanctx_to_link(vif, link, chan_ctx); +	wiphy_unlock(mld->wiphy); + +	/* The AP sta will now be pointer to by mld_vif->ap_sta */ +	iwlmld_kunit_setup_sta(vif, IEEE80211_STA_AUTHORIZED, assoc_link->id); + +	iwlmld_kunit_set_vif_associated(vif); + +	return vif; +} + +struct ieee80211_vif * +iwlmld_kunit_setup_mlo_assoc(u16 valid_links, +			     struct iwl_mld_kunit_link *assoc_link) +{ +	struct kunit *test = kunit_get_current_test(); +	struct ieee80211_vif *vif; + +	KUNIT_ASSERT_TRUE(test, +			  hweight16(valid_links) == 1 || +			  hweight16(valid_links) == 2); +	KUNIT_ASSERT_TRUE(test, valid_links & BIT(assoc_link->id)); + +	vif = iwlmld_kunit_setup_assoc(true, assoc_link); + +	/* Add the other link, if applicable */ +	if (hweight16(valid_links) > 1) { +		u8 other_link_id = ffs(valid_links & ~BIT(assoc_link->id)) - 1; + +		iwlmld_kunit_add_link(vif, other_link_id); +	} + +	return vif; +} + +struct ieee80211_vif * +iwlmld_kunit_setup_non_mlo_assoc(struct iwl_mld_kunit_link *assoc_link) +{ +	return iwlmld_kunit_setup_assoc(false, assoc_link); +} + +struct iwl_rx_packet * +_iwl_mld_kunit_create_pkt(const void *notif, size_t notif_sz) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_rx_packet *pkt; + +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, pkt, sizeof(pkt) + notif_sz); + +	memcpy(pkt->data, notif, notif_sz); +	pkt->len_n_flags = cpu_to_le32(sizeof(pkt->hdr) + notif_sz); + +	return pkt; +} + +struct ieee80211_vif *iwlmld_kunit_assoc_emlsr(struct iwl_mld_kunit_link *link1, +					       struct iwl_mld_kunit_link *link2) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct ieee80211_vif *vif; +	struct ieee80211_bss_conf *link; +	struct ieee80211_chanctx_conf *chan_ctx; +	struct ieee80211_sta *sta; +	struct iwl_mld_vif *mld_vif; +	u16 valid_links = BIT(link1->id) | BIT(link2->id); + +	KUNIT_ASSERT_TRUE(test, hweight16(valid_links) == 2); + +	vif = iwlmld_kunit_setup_mlo_assoc(valid_links, link1); +	mld_vif = iwl_mld_vif_from_mac80211(vif); + +	/* Activate second link */ +	wiphy_lock(mld->wiphy); + +	link = wiphy_dereference(mld->wiphy, vif->link_conf[link2->id]); +	KUNIT_EXPECT_NOT_NULL(test, link); + +	chan_ctx = iwlmld_kunit_add_chanctx(link2->chandef); +	iwlmld_kunit_assign_chanctx_to_link(vif, link, chan_ctx); + +	wiphy_unlock(mld->wiphy); + +	/* And other link sta */ +	sta = mld_vif->ap_sta; +	KUNIT_EXPECT_NOT_NULL(test, sta); + +	iwlmld_kunit_alloc_link_sta(sta, link2->id); + +	return vif; +} + +struct element *iwlmld_kunit_gen_element(u8 id, const void *data, size_t len) +{ +	struct kunit *test = kunit_get_current_test(); +	struct element *elem; + +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, elem, sizeof(*elem) + len); + +	elem->id = id; +	elem->datalen = len; +	memcpy(elem->data, data, len); + +	return elem; +} + +struct iwl_mld_phy *iwlmld_kunit_get_phy_of_link(struct ieee80211_vif *vif, +						 u8 link_id) +{ +	struct kunit *test = kunit_get_current_test(); +	struct iwl_mld *mld = test->priv; +	struct ieee80211_chanctx_conf *chanctx; +	struct ieee80211_bss_conf *link = +		wiphy_dereference(mld->wiphy, vif->link_conf[link_id]); + +	KUNIT_EXPECT_NOT_NULL(test, link); + +	chanctx = wiphy_dereference(mld->wiphy, link->chanctx_conf); +	KUNIT_EXPECT_NOT_NULL(test, chanctx); + +	return iwl_mld_phy_from_mac80211(chanctx); +} + +static const struct chandef_case { +	const char *desc; +	const struct cfg80211_chan_def *chandef; +} chandef_cases[] = { +#define CHANDEF(c, ...) { .desc = "chandef " #c " valid", .chandef = &c, }, +	CHANDEF_LIST +#undef CHANDEF +}; + +KUNIT_ARRAY_PARAM_DESC(chandef, chandef_cases, desc); + +static void test_iwl_mld_chandef_valid(struct kunit *test) +{ +	const struct chandef_case *params = test->param_value; + +	KUNIT_EXPECT_EQ(test, true, cfg80211_chandef_valid(params->chandef)); +} + +static struct kunit_case chandef_test_cases[] = { +	KUNIT_CASE_PARAM(test_iwl_mld_chandef_valid, chandef_gen_params), +	{} +}; + +static struct kunit_suite chandef_tests = { +	.name = "iwlmld_valid_test_chandefs", +	.test_cases = chandef_test_cases, +}; + +kunit_test_suite(chandef_tests); diff --git a/sys/contrib/dev/iwlwifi/mld/tests/utils.h b/sys/contrib/dev/iwlwifi/mld/tests/utils.h new file mode 100644 index 000000000000..edf8eef4e81a --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/tests/utils.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* + * Copyright (C) 2024-2025 Intel Corporation + */ + +#ifndef __iwl_mld_kunit_utils_h__ +#define __iwl_mld_kunit_utils_h__ + +#include <net/mac80211.h> +#include <kunit/test-bug.h> + +struct iwl_mld; + +int iwlmld_kunit_test_init(struct kunit *test); + +struct iwl_mld_kunit_link { +	const struct cfg80211_chan_def *chandef; +	u8 id; +}; + +enum nl80211_iftype; + +struct ieee80211_vif *iwlmld_kunit_add_vif(bool mlo, enum nl80211_iftype type); + +struct ieee80211_bss_conf * +iwlmld_kunit_add_link(struct ieee80211_vif *vif, int link_id); + +#define KUNIT_ALLOC_AND_ASSERT_SIZE(test, ptr, size)			\ +do {									\ +	(ptr) = kunit_kzalloc((test), (size), GFP_KERNEL);		\ +	KUNIT_ASSERT_NOT_NULL((test), (ptr));				\ +} while (0) + +#define KUNIT_ALLOC_AND_ASSERT(test, ptr)				\ +	KUNIT_ALLOC_AND_ASSERT_SIZE(test, ptr, sizeof(*(ptr))) + +#define CHANNEL(_name, _band, _freq)				\ +static struct ieee80211_channel _name = {			\ +	.band = (_band),					\ +	.center_freq = (_freq),					\ +	.hw_value = (_freq),					\ +} + +CHANNEL(chan_2ghz, NL80211_BAND_2GHZ, 2412); +CHANNEL(chan_2ghz_11, NL80211_BAND_2GHZ, 2462); +CHANNEL(chan_5ghz, NL80211_BAND_5GHZ, 5200); +CHANNEL(chan_5ghz_120, NL80211_BAND_5GHZ, 5600); +CHANNEL(chan_6ghz, NL80211_BAND_6GHZ, 6115); +CHANNEL(chan_6ghz_221, NL80211_BAND_6GHZ, 7055); +/* Feel free to add more */ +#undef CHANNEL + +#define CHANDEF_LIST \ +	CHANDEF(chandef_2ghz_20mhz, chan_2ghz, 2412,		\ +		NL80211_CHAN_WIDTH_20)				\ +	CHANDEF(chandef_2ghz_40mhz, chan_2ghz, 2422,		\ +		NL80211_CHAN_WIDTH_40)				\ +	CHANDEF(chandef_2ghz_11_20mhz, chan_2ghz_11, 2462,	\ +		NL80211_CHAN_WIDTH_20)				\ +	CHANDEF(chandef_5ghz_20mhz, chan_5ghz, 5200,		\ +		NL80211_CHAN_WIDTH_20)				\ +	CHANDEF(chandef_5ghz_40mhz, chan_5ghz, 5210,		\ +		NL80211_CHAN_WIDTH_40)				\ +	CHANDEF(chandef_5ghz_80mhz, chan_5ghz, 5210,		\ +		NL80211_CHAN_WIDTH_80)				\ +	CHANDEF(chandef_5ghz_160mhz, chan_5ghz, 5250,		\ +		NL80211_CHAN_WIDTH_160)				\ +	CHANDEF(chandef_5ghz_120_40mhz, chan_5ghz_120, 5610,	\ +		NL80211_CHAN_WIDTH_40)				\ +	CHANDEF(chandef_6ghz_20mhz, chan_6ghz, 6115,		\ +		NL80211_CHAN_WIDTH_20)				\ +	CHANDEF(chandef_6ghz_40mhz, chan_6ghz, 6125,		\ +		NL80211_CHAN_WIDTH_40)				\ +	CHANDEF(chandef_6ghz_80mhz, chan_6ghz, 6145,		\ +		NL80211_CHAN_WIDTH_80)				\ +	CHANDEF(chandef_6ghz_160mhz, chan_6ghz, 6185,		\ +		NL80211_CHAN_WIDTH_160)				\ +	CHANDEF(chandef_6ghz_320mhz, chan_6ghz, 6105,		\ +		NL80211_CHAN_WIDTH_320)				\ +	CHANDEF(chandef_6ghz_221_160mhz, chan_6ghz_221, 6985,	\ +		NL80211_CHAN_WIDTH_160)				\ +	/* Feel free to add more */ + +#define CHANDEF(_name, _channel, _freq1, _width)		\ +__maybe_unused static const struct cfg80211_chan_def _name = {	\ +	.chan = &(_channel),					\ +	.center_freq1 = (_freq1),				\ +	.width = (_width),					\ +}; +CHANDEF_LIST +#undef CHANDEF + +struct ieee80211_chanctx_conf * +iwlmld_kunit_add_chanctx(const struct cfg80211_chan_def *def); + +void iwlmld_kunit_assign_chanctx_to_link(struct ieee80211_vif *vif, +					 struct ieee80211_bss_conf *link, +					 struct ieee80211_chanctx_conf *ctx); + +/* Allocate a sta, initialize it and move it to the wanted state */ +struct ieee80211_sta *iwlmld_kunit_setup_sta(struct ieee80211_vif *vif, +					     enum ieee80211_sta_state state, +					     int link_id); + +struct ieee80211_vif * +iwlmld_kunit_setup_mlo_assoc(u16 valid_links, +			     struct iwl_mld_kunit_link *assoc_link); + +struct ieee80211_vif * +iwlmld_kunit_setup_non_mlo_assoc(struct iwl_mld_kunit_link *assoc_link); + +struct iwl_rx_packet * +_iwl_mld_kunit_create_pkt(const void *notif, size_t notif_sz); + +#define iwl_mld_kunit_create_pkt(_notif)	\ +	_iwl_mld_kunit_create_pkt(&(_notif), sizeof(_notif)) + +struct ieee80211_vif * +iwlmld_kunit_assoc_emlsr(struct iwl_mld_kunit_link *link1, +			 struct iwl_mld_kunit_link *link2); + +struct element *iwlmld_kunit_gen_element(u8 id, const void *data, size_t len); + +/** + * iwlmld_kunit_get_phy_of_link - Get the phy of a link + * + * @vif: The vif to get the phy from. + * @link_id: The id of the link to get the phy for. + * + * given a vif and link id, return the phy pointer of that link. + * This assumes that the link exists, and that it had a chanctx + * assigned. + * If this is not the case, the test will fail. + * + * Return: phy pointer. + */ +struct iwl_mld_phy *iwlmld_kunit_get_phy_of_link(struct ieee80211_vif *vif, +						 u8 link_id); + +#endif /* __iwl_mld_kunit_utils_h__ */ | 
