#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 {
u16 fc;
u8 tid;
bool multicast;
u16 nssn;
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;
struct {
u16 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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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 = {
.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;
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;
lockdep_assert_wiphy(mld->wiphy);
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;
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;
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;
__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);
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);
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);