blob: 63b88e0e684a9b6dccd0b2324d22c7f8ebfd491a [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include <iterator>
#include <memory>
#include <numeric>
#include <random>
#include <gtest/gtest.h>
#include "third_party/driver-lib/wlan/ieee80211.h"
#include "third_party/iwlwifi/test/mock-function.h"
extern "C" {
#include "third_party/iwlwifi/mvm/mvm.h"
#include "third_party/iwlwifi/mvm/time-event.h"
}
#include "third_party/iwlwifi/platform/ieee80211.h"
#include "third_party/iwlwifi/platform/irq.h"
#include "third_party/iwlwifi/platform/memory.h"
#include "third_party/iwlwifi/platform/stats.h"
#include "third_party/iwlwifi/platform/time.h"
#include "third_party/iwlwifi/test/fake-ucode-test.h"
#include "third_party/iwlwifi/test/mock-trans.h"
#include "third_party/iwlwifi/test/sim-time-event.h"
#include "third_party/iwlwifi/test/single-ap-test.h"
#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
#pragma GCC diagnostic ignored "-Wthread-safety-analysis"
namespace wlan {
namespace testing {
namespace {
// Helper function to create a PHY context for the interface.
static void setup_phy_ctxt(struct iwl_mvm_vif* mvmvif) __TA_NO_THREAD_SAFETY_ANALYSIS {
// Create a PHY context and assign it to mvmvif.
struct ieee80211_channel chan = {
.band = NL80211_BAND_2GHZ,
.ch_num = 6,
.hw_value = 6,
};
struct cfg80211_chan_def chandef = {
.chan = &chan,
.width = NL80211_CHAN_WIDTH_20_NOHT,
};
uint16_t phy_ctxt_id;
struct iwl_mvm* mvm = mvmvif->mvm;
mtx_unlock(&mvm->mutex);
ASSERT_EQ(ZX_OK, iwl_mvm_add_chanctx(mvm, &chandef, &phy_ctxt_id));
mvmvif->phy_ctxt = &(mvm->phy_ctxts[phy_ctxt_id]);
mtx_lock(&mvm->mutex);
}
// An iwl_rx_cmd_buffer instance that cleans up its allocated resources.
class TestRxcb : public iwl_rx_cmd_buffer {
public:
explicit TestRxcb(struct device* dev, void* pkt_data, size_t pkt_len) {
struct iwl_iobuf* io_buf = nullptr;
EXPECT_EQ(ZX_OK,
iwl_iobuf_allocate_contiguous(dev, pkt_len + sizeof(struct iwl_rx_packet), &io_buf));
_iobuf = io_buf;
_offset = 0;
struct iwl_rx_packet* pkt = reinterpret_cast<struct iwl_rx_packet*>(iwl_iobuf_virtual(io_buf));
// Most fields are not cared but initialized with known values.
pkt->len_n_flags = cpu_to_le32(0);
pkt->hdr.cmd = 0;
pkt->hdr.group_id = 0;
pkt->hdr.sequence = 0;
memcpy(pkt->data, pkt_data, pkt_len);
}
~TestRxcb() { iwl_iobuf_release(_iobuf); }
};
struct TestCtx {
wlan_rx_info_t rx_info;
size_t frame_len;
};
class MvmTest : public SingleApTest {
public:
MvmTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_ = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
mvmvif_->mvm = mvm_;
mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
mvm_->mvmvif[0] = mvmvif_;
mvm_->vif_count++;
mtx_lock(&mvm_->mutex);
}
~MvmTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
free(mvmvif_);
mtx_unlock(&mvm_->mutex);
}
protected:
using RecvFn = void (*)(void*, const wlan_rx_packet_t*);
// This function is kind of dirty. It hijacks the wlan_softmac_ifc_protocol_t.recv() so that we
// can save the rx_info passed to MLME. See TearDown() for cleanup logic related to this
// function.
template <typename Context>
void MockRecv(Context* ctx, RecvFn recv) {
mvmvif_->ifc.ctx = ctx;
mvmvif_->ifc.recv = recv;
}
static void MockRecvCallback(void* ctx, const wlan_rx_packet_t* packet) {
TestCtx* test_ctx = reinterpret_cast<TestCtx*>(ctx);
test_ctx->rx_info = packet->info;
test_ctx->frame_len = packet->mac_frame_size;
}
struct iwl_mvm* mvm_;
struct iwl_mvm_vif* mvmvif_;
wlan_softmac_ifc_protocol_ops_t protocol_ops_;
};
TEST_F(MvmTest, GetMvm) { EXPECT_NE(mvm_, nullptr); }
// In this test case, we expect the CCMP is removed.
//
// Before:
//
// Frame Header
// CCMP
// Payload
//
// After:
//
// Frame Header
// Payload
//
TEST_F(MvmTest, testCreatePacket) {
struct wlan_rx_info rx_info = {};
const size_t kMacPayloadLen = 60;
struct {
struct iwl_rx_mpdu_res_start rx_res;
struct ieee80211_frame_header frame;
uint8_t ccmp[fuchsia_wlan_ieee80211_CCMP_HDR_LEN];
uint8_t mac_payload[kMacPayloadLen];
uint32_t rx_pkt_status;
} __packed mpdu = {
.rx_res =
{
.byte_count = kMacPayloadLen,
.assist = 0,
},
.frame = {},
.ccmp =
{
0x01,
0x02,
0x03,
0x04,
0x05,
0x06,
0x07,
0x08,
},
.mac_payload =
{
0xff,
0xfe,
0xfd,
0xfc,
},
.rx_pkt_status = 0x0,
};
TestRxcb mpdu_rxcb(sim_trans_.iwl_trans()->dev, &mpdu, sizeof(mpdu));
size_t org_size =
sizeof(struct ieee80211_frame_header) + fuchsia_wlan_ieee80211_CCMP_HDR_LEN + kMacPayloadLen;
size_t new_size = iwl_mvm_create_packet(
&mpdu.frame, org_size, fuchsia_wlan_ieee80211_CCMP_HDR_LEN, &rx_info, &mpdu_rxcb);
EXPECT_EQ(new_size, org_size - fuchsia_wlan_ieee80211_CCMP_HDR_LEN);
EXPECT_EQ(mpdu.ccmp[0], 0xff); // moved from mpdu.mac_payload[0]
EXPECT_EQ(mpdu.ccmp[3], 0xfc); // moved from mpdu.mac_payload[3]
}
TEST_F(MvmTest, rxMpdu) {
const int kExpChan = 40;
// Simulate the previous PHY_INFO packet
struct iwl_rx_phy_info phy_info = {
.non_cfg_phy_cnt = IWL_RX_INFO_ENERGY_ANT_ABC_IDX + 1,
.phy_flags = cpu_to_le16(0),
.channel = cpu_to_le16(kExpChan),
.non_cfg_phy =
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
[IWL_RX_INFO_ENERGY_ANT_ABC_IDX] = 0x000a28, // RSSI C:n/a B:-10, A:-40
#pragma GCC diagnostic pop
},
.rate_n_flags = cpu_to_le32(0x7), // IWL_RATE_18M_PLCP
};
TestRxcb phy_info_rxcb(sim_trans_.iwl_trans()->dev, &phy_info, sizeof(phy_info));
iwl_mvm_rx_rx_phy_cmd(mvm_, &phy_info_rxcb);
// Now, it comes the MPDU packet.
const size_t kMacPayloadLen = 60;
struct {
struct iwl_rx_mpdu_res_start rx_res;
struct ieee80211_frame_header frame;
uint8_t mac_payload[kMacPayloadLen];
uint32_t rx_pkt_status;
} __packed mpdu = {
.rx_res =
{
.byte_count = kMacPayloadLen,
.assist = 0,
},
.frame = {},
.rx_pkt_status = 0x0,
};
TestRxcb mpdu_rxcb(sim_trans_.iwl_trans()->dev, &mpdu, sizeof(mpdu));
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_CMD_FROM_FW), 0);
TestCtx test_ctx = {};
MockRecv(&test_ctx, MockRecvCallback);
iwl_mvm_rx_rx_mpdu(mvm_, nullptr /* napi */, &mpdu_rxcb);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_CMD_FROM_FW), 1);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_UNICAST_TO_MLME), 1);
EXPECT_EQ(WLAN_RX_INFO_VALID_DATA_RATE,
test_ctx.rx_info.valid_fields & WLAN_RX_INFO_VALID_DATA_RATE);
EXPECT_EQ(TO_HALF_MBPS(18), test_ctx.rx_info.data_rate);
EXPECT_EQ(kExpChan, test_ctx.rx_info.channel.primary);
EXPECT_EQ(WLAN_RX_INFO_VALID_RSSI, test_ctx.rx_info.valid_fields & WLAN_RX_INFO_VALID_RSSI);
EXPECT_EQ(static_cast<int8_t>(-10), test_ctx.rx_info.rssi_dbm);
}
// Basic test for Rx MQ (no padding by FW)
TEST_F(MvmTest, rxMqMpdu) {
const int kExpChan = 11;
// Simulate the previous PHY_INFO packet
struct iwl_rx_phy_info phy_info = {
.non_cfg_phy_cnt = IWL_RX_INFO_ENERGY_ANT_ABC_IDX + 1,
.phy_flags = cpu_to_le16(0),
.channel = cpu_to_le16(kExpChan),
.non_cfg_phy =
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
[IWL_RX_INFO_ENERGY_ANT_ABC_IDX] = 0x000a28, // RSSI C:n/a B:-10, A:-40
#pragma GCC diagnostic pop
},
.rate_n_flags = cpu_to_le32(0x7), // IWL_RATE_18M_PLCP
};
TestRxcb phy_info_rxcb(sim_trans_.iwl_trans()->dev, &phy_info, sizeof(phy_info));
iwl_mvm_rx_rx_phy_cmd(mvm_, &phy_info_rxcb);
// Now, it comes the MPDU packet.
const size_t kMacPayloadLen = 60;
struct {
char mpdu_desc[IWL_RX_DESC_SIZE_V1];
struct ieee80211_frame_header frame;
uint8_t mac_payload[kMacPayloadLen];
} __packed mpdu = {};
struct iwl_rx_mpdu_desc* desc = (struct iwl_rx_mpdu_desc*)mpdu.mpdu_desc;
desc->mpdu_len = kMacPayloadLen + sizeof(mpdu.frame);
desc->v1.channel = kExpChan;
desc->v1.energy_a = 0x7f;
desc->v1.energy_b = 0x28;
desc->status = 0x1007;
desc->v1.rate_n_flags = 0x820a;
mpdu.frame.frame_ctrl = 0x8; // Data frame
TestRxcb mpdu_rxcb(sim_trans_.iwl_trans()->dev, &mpdu, sizeof(mpdu));
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_CMD_FROM_FW), 0);
TestCtx test_ctx = {};
MockRecv(&test_ctx, MockRecvCallback);
iwl_mvm_rx_mpdu_mq(mvm_, nullptr /* napi */, &mpdu_rxcb, 0);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_CMD_FROM_FW), 1);
EXPECT_EQ(iwl_stats_read(IWL_STATS_CNT_UNICAST_TO_MLME), 1);
EXPECT_EQ(desc->mpdu_len, test_ctx.frame_len);
EXPECT_EQ(WLAN_RX_INFO_VALID_DATA_RATE,
test_ctx.rx_info.valid_fields & WLAN_RX_INFO_VALID_DATA_RATE);
EXPECT_EQ(TO_HALF_MBPS(1), test_ctx.rx_info.data_rate);
EXPECT_EQ(kExpChan, test_ctx.rx_info.channel.primary);
EXPECT_EQ(WLAN_RX_INFO_VALID_RSSI, test_ctx.rx_info.valid_fields & WLAN_RX_INFO_VALID_RSSI);
EXPECT_EQ(static_cast<int8_t>(-40), test_ctx.rx_info.rssi_dbm);
}
// Test checks to see frame header padding added by FW is indicated correctly to SME
TEST_F(MvmTest, rxMqMpdu_with_header_padding) {
const int kExpChan = 11;
// Simulate the previous PHY_INFO packet
struct iwl_rx_phy_info phy_info = {
.non_cfg_phy_cnt = IWL_RX_INFO_ENERGY_ANT_ABC_IDX + 1,
.phy_flags = cpu_to_le16(0),
.channel = cpu_to_le16(kExpChan),
.non_cfg_phy =
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc99-designator"
[IWL_RX_INFO_ENERGY_ANT_ABC_IDX] = 0x000a28, // RSSI C:n/a B:-10, A:-40
#pragma GCC diagnostic pop
},
.rate_n_flags = cpu_to_le32(0x7), // IWL_RATE_18M_PLCP
};
TestRxcb phy_info_rxcb(sim_trans_.iwl_trans()->dev, &phy_info, sizeof(phy_info));
iwl_mvm_rx_rx_phy_cmd(mvm_, &phy_info_rxcb);
// Now, it comes the MPDU packet.
const size_t kMacPayloadLen = 60;
struct {
char mpdu_desc[IWL_RX_DESC_SIZE_V1];
// struct ieee80211_frame_header frame;
uint8_t frame_header[28];
uint8_t mac_payload[kMacPayloadLen];
} __packed mpdu = {};
struct iwl_rx_mpdu_desc* desc = (struct iwl_rx_mpdu_desc*)mpdu.mpdu_desc;
struct ieee80211_frame_header* frame_header = (struct ieee80211_frame_header*)mpdu.frame_header;
desc->mpdu_len = kMacPayloadLen + 28;
desc->v1.channel = kExpChan;
desc->v1.energy_a = 0x7f;
desc->v1.energy_b = 0x28;
desc->status = 0x1007;
desc->v1.rate_n_flags = 0x820a;
desc->mac_flags2 = IWL_RX_MPDU_MFLG2_PAD;
frame_header->frame_ctrl = 0x288; // QOS data frame
TestRxcb mpdu_rxcb(sim_trans_.iwl_trans()->dev, &mpdu, sizeof(mpdu));
TestCtx test_ctx = {};
MockRecv(&test_ctx, MockRecvCallback);
iwl_mvm_rx_mpdu_mq(mvm_, nullptr /* napi */, &mpdu_rxcb, 0);
// Expect FRAME_BODY_PADDING_4 is set in rx_flags
EXPECT_EQ(WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4,
test_ctx.rx_info.rx_flags & WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4);
// Received frame length should be the same as actual receive length
EXPECT_EQ(desc->mpdu_len, test_ctx.frame_len);
EXPECT_EQ(TO_HALF_MBPS(1), test_ctx.rx_info.data_rate);
EXPECT_EQ(kExpChan, test_ctx.rx_info.channel.primary);
EXPECT_EQ(WLAN_RX_INFO_VALID_RSSI, test_ctx.rx_info.valid_fields & WLAN_RX_INFO_VALID_RSSI);
EXPECT_EQ(static_cast<int8_t>(-40), test_ctx.rx_info.rssi_dbm);
}
// The antenna index will be toggled after each call.
// Check 'ucode_phy_sku' in test/single-ap-test.cc for the fake antenna setting.
TEST_F(MvmTest, toggleTxAntenna) {
uint8_t ant = 1; // the current antenna 1
iwl_mvm_toggle_tx_ant(mvm_, &ant);
// Since there is only antenna 1 and 0 available, the 'ant' should be updated to 0.
EXPECT_EQ(0, ant);
// Do again.
iwl_mvm_toggle_tx_ant(mvm_, &ant);
// The 'ant' should be toggled to 1.
EXPECT_EQ(1, ant);
}
// Check 'ucode_phy_sku' in test/single-ap-test.cc for the fake antenna setting.
TEST_F(MvmTest, validRxAnt) { EXPECT_EQ(iwl_mvm_get_valid_rx_ant(mvm_), 6); }
TEST_F(MvmTest, scanLmacErrorChecking) {
struct iwl_mvm_scan_params params = {
.n_scan_plans = IWL_MAX_SCHED_SCAN_PLANS + 1,
};
EXPECT_EQ(ZX_ERR_INVALID_ARGS, iwl_mvm_scan_lmac(mvm_, &params));
}
// This test focuses on testing the scan_cmd filling for LMAC passive scan.
TEST_F(MvmTest, scanLmacPassiveCmdFilling) {
ASSERT_NE(nullptr, mvm_->scan_cmd); // scan cmd should have been allocated during init.
struct ieee80211_channel channels[] = {
{
.hw_value = 5,
},
{
.hw_value = 11,
},
{
.hw_value = 36,
},
{
.hw_value = 165,
},
};
struct ieee80211_channel* p_channels[] = {
&channels[0],
&channels[1],
&channels[2],
&channels[3],
};
struct iwl_mvm_scan_params params = {
.type = IWL_SCAN_TYPE_WILD,
.hb_type = IWL_SCAN_TYPE_NOT_SET,
.n_channels = ARRAY_SIZE(channels),
.n_ssids = 0,
.channels = p_channels,
.flags = 0,
.pass_all = true,
.n_match_sets = 0,
.preq =
{
// arbitrary values for memory comparison below
.mac_header =
{
.offset = cpu_to_le16(0x1234),
.len = cpu_to_le16(0x5678),
},
},
.n_scan_plans = 0,
};
EXPECT_EQ(ZX_OK, iwl_mvm_scan_lmac(mvm_, &params));
struct iwl_scan_req_lmac* cmd = reinterpret_cast<struct iwl_scan_req_lmac*>(mvm_->scan_cmd);
EXPECT_EQ(0x036d, le16_to_cpu(cmd->rx_chain_select)); // Refer iwl_mvm_scan_rx_chain() for the
// actual implementation.
EXPECT_EQ(1, le32_to_cpu(cmd->iter_num));
EXPECT_EQ(0, le32_to_cpu(cmd->delay));
EXPECT_EQ(4, cmd->n_channels);
EXPECT_EQ(PHY_BAND_24, le32_to_cpu(cmd->flags));
EXPECT_EQ(1, cmd->schedule[0].iterations);
struct iwl_scan_channel_cfg_lmac* channel_cfg =
reinterpret_cast<struct iwl_scan_channel_cfg_lmac*>(cmd->data);
EXPECT_EQ(5, le16_to_cpu(channel_cfg[0].channel_num));
EXPECT_EQ(165, le16_to_cpu(channel_cfg[3].channel_num));
// preq
uint8_t* preq =
&cmd->data[sizeof(struct iwl_scan_channel_cfg_lmac) * mvm_->fw->ucode_capa.n_scan_channels];
EXPECT_EQ(0x34, preq[0]);
EXPECT_EQ(0x12, preq[1]);
EXPECT_EQ(0x78, preq[2]);
EXPECT_EQ(0x56, preq[3]);
}
///////////////////////////////////////////////////////////////////////////////
// Aggregation tests
//
struct AggregationCommandTest : public MvmTest, public MockTrans {
AggregationCommandTest() {
BIND_TEST(mvm_->trans);
auto* ucode_capa = const_cast<struct iwl_ucode_capabilities*>(&mvm_->fw->ucode_capa);
// ensure we have new Rx api for test
set_bit(IWL_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT, ucode_capa->_capa);
// setup station
mvm_sta_.sta_id = 0;
mvm_sta_.mvmvif = mvmvif_;
// set response packet
bindSendCmd(SendAddStaCmdWrapper);
}
~AggregationCommandTest() {
unbindSendCmd();
mock_send_add_sta_cmd_.VerifyAndClear();
}
zx_status_t CallRxAggWithExpectation(zx_status_t ret, uint8_t tid, uint16_t ssn,
uint16_t buf_size, bool start, uint16_t timeout,
uint8_t resp_status, uint8_t baid, bool baid_valid) {
SetAddStaCmdResponse(resp_status, baid, baid_valid);
mock_send_add_sta_cmd_.ExpectCall(
ret, WIDE_ID(LONG_GROUP, ADD_STA), sizeof(struct iwl_mvm_add_sta_cmd),
mvm_sta_.mac_id_n_color, mvm_sta_.sta_id, STA_MODE_MODIFY, start ? tid : 0, start ? ssn : 0,
start ? buf_size : 0, start ? STA_MODIFY_ADD_BA_TID : STA_MODIFY_REMOVE_BA_TID,
start ? 0 : tid);
return iwl_mvm_sta_rx_agg(mvm_, &sta_, tid, ssn, start, buf_size, timeout);
}
void SetAddStaCmdResponse(uint8_t status, uint8_t baid, bool baid_valid) {
auto* resp_pkt = reinterpret_cast<struct iwl_rx_packet*>(rx_packet_buf_.data());
auto* resp = reinterpret_cast<struct iwl_cmd_response*>(resp_pkt->data);
resp->status = status;
if (baid_valid) {
resp->status |= (IWL_ADD_STA_BAID_VALID_MASK);
resp->status |= ((baid << IWL_ADD_STA_BAID_SHIFT) & IWL_ADD_STA_BAID_MASK);
}
resp_pkt->len_n_flags = sizeof(*resp) + sizeof(resp_pkt->hdr);
}
void CheckBaidForTidIsSetup(uint8_t tid, uint8_t expect_baid, uint16_t ssn, uint16_t buf_size,
uint16_t timeout) {
ASSERT_TRUE(mvm_->rx_ba_sessions > 0);
uint8_t baid = mvm_sta_.tid_to_baid[tid];
ASSERT_EQ(expect_baid, baid);
auto* baid_data = mvm_->baid_map[baid];
ASSERT_NE(nullptr, baid_data);
ASSERT_EQ(expect_baid, baid_data->baid);
ASSERT_EQ(mvm_, baid_data->mvm);
ASSERT_EQ(tid, baid_data->tid);
}
static zx_status_t SendAddStaCmdWrapper(struct iwl_trans* trans, struct iwl_host_cmd* host_cmd) {
auto add_sta_cmd = reinterpret_cast<const struct iwl_mvm_add_sta_cmd*>(host_cmd->data[0]);
auto test = GET_TEST(AggregationCommandTest, trans);
host_cmd->resp_pkt = reinterpret_cast<struct iwl_rx_packet*>(test->rx_packet_buf_.data());
// Simulate the behavior of iwl_pcie_hcmd_complete() because the interrupt is not triggerred in
// the simulated environment.
//
// TODO(b/299301406): remove this code once the simulated interrupt is supported.
sync_completion_signal(&trans->wait_command_queue);
clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
return test->mock_send_add_sta_cmd_.Call(
host_cmd->id, host_cmd->len[0], add_sta_cmd->mac_id_n_color, add_sta_cmd->sta_id,
add_sta_cmd->add_modify, add_sta_cmd->add_immediate_ba_tid,
add_sta_cmd->add_immediate_ba_ssn, add_sta_cmd->rx_ba_window, add_sta_cmd->modify_mask,
add_sta_cmd->remove_immediate_ba_tid);
}
struct iwl_mvm_sta mvm_sta_{};
struct ieee80211_sta sta_{
.drv_priv = &mvm_sta_,
};
static constexpr size_t kBufferSize =
sizeof(struct iwl_rx_packet) + sizeof(struct iwl_cmd_response);
// This buffer contains the response iwl_rx_packet and iwl_cmd_response
std::array<uint8_t, kBufferSize> rx_packet_buf_;
mock_function::MockFunction<zx_status_t, // return value
uint32_t, // host_cmd->id
uint16_t, // host_cmd->len[0]
__le32, // add_sta_cmd->mac_id_n_color
uint8_t, // add_sta_cmd->sta_id
uint8_t, // add_sta_cmd->add_modify
uint8_t, // add_sta_cmd->add_immediate_ba_tid
__le16, // add_sta_cmd->add_immediate_ba_ssn
__le16, // add_sta_cmd->rx_ba_window
uint8_t, // add_sta_cmd->modify_mask
// only used to teardown Rx Ba session
uint8_t // add_sta_cmd->remove_immediate_ba_tid
>
mock_send_add_sta_cmd_;
};
TEST_F(AggregationCommandTest, SetupAndTeardownRxAggregationSucceeds) {
uint8_t tid = 5;
uint16_t ssn = 123;
uint16_t buf_size = 64;
uint8_t baid = 3;
uint16_t timeout = 0;
ASSERT_EQ(ZX_OK, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, true, timeout,
ADD_STA_SUCCESS, baid, true));
ASSERT_EQ(1, mvm_->rx_ba_sessions);
CheckBaidForTidIsSetup(tid, baid, ssn, buf_size, timeout);
ASSERT_EQ(ZX_OK, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, false, timeout,
ADD_STA_SUCCESS, baid, true));
ASSERT_EQ(0, mvm_->rx_ba_sessions);
ASSERT_EQ(nullptr, mvm_->baid_map[baid]);
}
TEST_F(AggregationCommandTest, RejectAfterMaxTid) {
uint16_t ssn = 123;
uint16_t buf_size = 64;
uint16_t timeout = 0;
for (uint8_t tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
uint8_t baid = tid + 1;
ASSERT_EQ(ZX_OK, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, true, timeout,
ADD_STA_SUCCESS, baid, true));
ASSERT_EQ(baid, mvm_->rx_ba_sessions);
CheckBaidForTidIsSetup(tid, baid, ssn, buf_size, timeout);
}
ASSERT_EQ(IWL_MAX_TID_COUNT, mvm_->rx_ba_sessions);
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
iwl_mvm_sta_rx_agg(mvm_, &sta_, IWL_MAX_TID_COUNT, ssn, true, buf_size, timeout));
// Need to teardown all the sessions to keep asan happy
for (uint8_t tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
uint8_t baid = tid + 1;
ASSERT_EQ(ZX_OK, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, false, timeout,
ADD_STA_SUCCESS, baid, true));
ASSERT_EQ(nullptr, mvm_->baid_map[baid]);
}
ASSERT_EQ(0, mvm_->rx_ba_sessions);
}
TEST_F(AggregationCommandTest, RejectAfterFwCmdFails) {
uint8_t tid = 5;
uint16_t ssn = 123;
uint16_t buf_size = 64;
uint8_t baid = 3;
uint16_t timeout = 0;
ASSERT_EQ(ZX_ERR_NO_SPACE, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, true, timeout,
ADD_STA_IMMEDIATE_BA_FAILURE, baid, true));
ASSERT_EQ(ZX_ERR_IO, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, true, timeout,
ADD_STA_MODIFY_NON_EXISTING_STA, baid, true));
// If ADD_STA_SUCCESS is returned, but BAID is not valid
ASSERT_EQ(ZX_ERR_INVALID_ARGS, CallRxAggWithExpectation(ZX_OK, tid, ssn, buf_size, true, timeout,
ADD_STA_SUCCESS, baid, false));
// If actually sending the command fails
ASSERT_EQ(ZX_ERR_IO, CallRxAggWithExpectation(ZX_ERR_IO, tid, ssn, buf_size, true, timeout,
ADD_STA_SUCCESS, baid, true));
}
using MacAddress = std::array<uint8_t, ETH_ALEN>;
constexpr MacAddress kDefaultMacAddr{0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
void CopyMacAddress(uint8_t* dst, const MacAddress& src) {
std::memcpy(dst, src.data(), src.size());
}
// Wrapper packet for managing a wlan_rx_packet_t whose frame buffer points to an
// ieee80211_frame_header with the corresponding iwl_rx_mpdu_desc.
struct WlanRxPacket {
WlanRxPacket() {
// fill payload with random data so that packets are (probably) uniquely identified
std::random_device rd;
std::mt19937 g(rd());
std::generate(bytes_.begin(), bytes_.end(), g);
// setup header
SetFrameTypeAndSubtype(IEEE80211_FRAME_TYPE_DATA, IEEE80211_FRAME_SUBTYPE_QOS);
CopyMacAddress(hdr_->addr1, kDefaultMacAddr);
CopyMacAddress(hdr_->addr2, kDefaultMacAddr);
CopyMacAddress(hdr_->addr3, kDefaultMacAddr);
SetBlockAckId(0);
SetSn(0);
SetNssn(0);
SetTid(0);
}
bool Equals(const wlan_rx_packet_t& other) {
auto* packet = Packet();
return packet->mac_frame_size == other.mac_frame_size &&
memcmp(packet->mac_frame_buffer, other.mac_frame_buffer, packet->mac_frame_size) == 0;
}
wlan_rx_packet_t* Packet() { return &packet_; }
struct iwl_rx_mpdu_desc* Desc() { return &desc_; }
struct ieee80211_frame_header* Header() { return hdr_; }
void SetFrameTypeAndSubtype(enum ieee80211_frame_type frame_type,
enum ieee80211_frame_subtype subtype) {
hdr_->frame_ctrl &= ~(IEEE80211_FRAME_TYPE_MASK | IEEE80211_FRAME_SUBTYPE_MASK);
hdr_->frame_ctrl |= frame_type;
hdr_->frame_ctrl |= subtype;
}
void SetTid(uint8_t tid) {
// based on code from ieee80211.cc
uint8_t* qos_ctl = reinterpret_cast<uint8_t*>(hdr_) + ieee80211_get_qos_ctrl_offset(hdr_);
qos_ctl[0] &= ~0xF;
qos_ctl[0] |= (tid & 0xF);
}
void SetBlockAckId(uint8_t baid) {
desc_.reorder_data &= ~(IWL_RX_MPDU_REORDER_BAID_MASK);
desc_.reorder_data |= (baid << IWL_RX_MPDU_REORDER_BAID_SHIFT) & IWL_RX_MPDU_REORDER_BAID_MASK;
}
void SetSn(uint8_t sn) {
desc_.reorder_data &= ~(IWL_RX_MPDU_REORDER_SN_MASK);
desc_.reorder_data |= (sn << IWL_RX_MPDU_REORDER_SN_SHIFT) & IWL_RX_MPDU_REORDER_SN_MASK;
}
void SetNssn(uint8_t nssn) {
desc_.reorder_data &= ~(IWL_RX_MPDU_REORDER_NSSN_MASK);
desc_.reorder_data |= (nssn & IWL_RX_MPDU_REORDER_NSSN_MASK);
}
struct iwl_rx_mpdu_desc desc_{};
std::array<uint8_t, 256> bytes_{};
struct ieee80211_frame_header* hdr_{
reinterpret_cast<struct ieee80211_frame_header*>(bytes_.data())};
wlan_rx_packet_t packet_{
.mac_frame_buffer = bytes_.data(),
.mac_frame_size = bytes_.size(),
};
};
// The test fixture manages baid_data/mvm/sta, but each test itself
// must manage WlanRxPackets themselves.
struct ReorderTimeoutTest : public MvmTest, public MockTrans {
static constexpr uint16_t kEntriesPerQueue = 64;
ReorderTimeoutTest() {
BIND_TEST(mvm_->trans);
// setup station
mvm_sta_.sta_id = 0;
mvm_sta_.mvmvif = mvmvif_;
CopyMacAddress(mvm_sta_.addr, kDefaultMacAddr);
SetupBaidData();
mvm_->fw_id_to_mac_id[0] = &mvm_sta_;
mvm_->baid_map[0] = baid_data_;
}
~ReorderTimeoutTest() {
// reorder buffer entries are usually freed by the call to iwl_mvm_release_frames
// but not all tests call iwl_mvm_release_frames
iwl_mvm_free_reorder(mvm_, baid_data_);
free(baid_data_);
}
virtual void SetupBaidData() {
uint16_t reorder_buf_size = kEntriesPerQueue * sizeof(baid_data_->entries[0]);
baid_data_ = reinterpret_cast<struct iwl_mvm_baid_data*>(
calloc(1, sizeof(*baid_data_) + (mvm_->trans->num_rx_queues * reorder_buf_size)));
baid_data_->sta_id = 0;
baid_data_->entries_per_queue = kEntriesPerQueue;
baid_data_->mvm = mvm_;
baid_data_->baid = 0;
baid_data_->tid = 0;
iwl_mvm_init_reorder_buffer(mvm_, baid_data_, 0, kEntriesPerQueue);
}
void SetupReorderTimer(struct iwl_mvm_reorder_buffer* reorder_buf, iwl_irq_timer_func cb,
void* data) {
if (reorder_buf->reorder_timer) {
iwl_irq_timer_release_sync(reorder_buf->reorder_timer);
}
iwl_irq_timer_create(mvm_->dev, cb, data, &reorder_buf->reorder_timer);
}
bool Reorder(WlanRxPacket& packet, int queue = 0) {
return iwl_mvm_reorder(mvm_, &sta_, packet.Packet(), &rx_status_, queue, packet.Desc());
}
struct iwl_mvm_reorder_buffer& GetReorderBuf(int queue = 0) {
return baid_data_->reorder_buf[queue];
}
struct iwl_mvm_reorder_buf_entry& GetReorderBufEntry(uint8_t sn, int queue = 0) {
auto* entries = &baid_data_->entries[queue * baid_data_->entries_per_queue];
auto index = sn % GetReorderBuf(queue).buf_size;
return entries[index];
}
wlan_rx_packet_t& GetBufferedPacket(uint8_t sn, int queue = 0) {
return GetReorderBufEntry(sn, queue).e.rx_packet;
}
bool BufferIndexHasPacket(uint8_t sn, int queue = 0) {
return GetReorderBufEntry(sn, queue).e.has_packet;
}
bool BufferIsEmpty(int queue = 0) {
if (GetReorderBuf(queue).num_stored > 0) {
return false;
}
for (uint8_t i = 0; i < kEntriesPerQueue; i++) {
auto& entry = GetReorderBufEntry(i);
if (entry.e.has_packet || entry.e.rx_packet.mac_frame_buffer != NULL) {
return false;
}
}
return true;
}
struct TestCtx {
// Holds copies of received packetes
std::vector<wlan_rx_packet_t> received{};
~TestCtx() {
for (auto packet : received) {
if (packet.mac_frame_buffer) {
free((void*)packet.mac_frame_buffer);
}
}
}
};
static void MockRecvCallback(void* ptr, const wlan_rx_packet_t* pkt) {
TestCtx* ctx = reinterpret_cast<TestCtx*>(ptr);
wlan_rx_packet_t copy;
copy.mac_frame_size = pkt->mac_frame_size;
auto* buffer = malloc(pkt->mac_frame_size);
memcpy(buffer, pkt->mac_frame_buffer, pkt->mac_frame_size);
copy.mac_frame_buffer = reinterpret_cast<const uint8_t*>(buffer);
copy.info = pkt->info;
ctx->received.push_back(copy);
}
struct iwl_mvm_sta mvm_sta_{};
struct ieee80211_sta sta_{
.drv_priv = &mvm_sta_,
};
TestCtx ctx_{};
struct ieee80211_rx_status rx_status_{};
// Initialized separately in the constructor because we also have to allocate room for
// baid_data_->entries manually
struct iwl_mvm_baid_data* baid_data_{nullptr};
};
TEST_F(ReorderTimeoutTest, ReleaseExpiredFrames) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
MockRecv(&ctx_, MockRecvCallback);
uint8_t sn = 1;
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn++);
ASSERT_TRUE(Reorder(packet));
}
ASSERT_EQ(kNumPackets, GetReorderBuf().num_stored);
ASSERT_EQ(ZX_OK, iwl_irq_timer_wait(GetReorderBuf().reorder_timer));
ASSERT_EQ(packets.size(), ctx_.received.size());
for (auto i = 0U; i < packets.size(); i++) {
ASSERT_TRUE(packets[i].Equals(ctx_.received[i]));
}
}
// TODO(fxb/124174); This test is disabled due to flakiness.
// To remove the flaky behavior, we will probably need to refactor tests to use a fake clock
// so that we can have greater control over when the timer function runs.
TEST_F(ReorderTimeoutTest, DISABLED_NoFramesReleasedIfFramesNotExpired) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
MockRecv(&ctx_, MockRecvCallback);
uint8_t sn = 1;
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn++);
ASSERT_TRUE(Reorder(packet));
}
// Stop the timer that gets setup by the Reorder call normally so that we can be sure
// the next time the timer callback runs it's because of iwl_mvm_reorder_timer_expired.
// NOTE: it is possible that enough time passes between the last call to Reorder() and now that
// the timer fires and that this call to iwl_irq_timer_stop() fails.
iwl_irq_timer_stop(GetReorderBuf().reorder_timer);
ASSERT_EQ(kNumPackets, GetReorderBuf().num_stored);
// Explicitly call timer_expired callback early and show that no packets get released
// This will also restart the reorder timer.
iwl_mvm_reorder_timer_expired(&GetReorderBuf());
// NOTE: it is possible that enough time passes between the above line and this check that the
// timer callback runs and the packets are received by ctx_.
ASSERT_TRUE(ctx_.received.empty());
// Then actually wait for the timer and see that frames get released next time
ASSERT_EQ(ZX_OK, iwl_irq_timer_wait(GetReorderBuf().reorder_timer));
ASSERT_EQ(packets.size(), ctx_.received.size());
for (auto i = 0U; i < packets.size(); i++) {
ASSERT_TRUE(packets[i].Equals(ctx_.received[i]));
}
}
struct ReorderTest : public ReorderTimeoutTest {
ReorderTest() {
// override default timeout callback to do nothing
for (auto i = 0; i < mvm_->trans->num_rx_queues; i++) {
auto& reorder_buf = GetReorderBuf(i);
SetupReorderTimer(&reorder_buf, [](void*) {}, nullptr);
}
}
};
TEST_F(ReorderTest, NotBufferedIfInvalidBlockAckId) {
WlanRxPacket packet;
packet.SetBlockAckId(IWL_RX_REORDER_DATA_INVALID_BAID);
ASSERT_FALSE(Reorder(packet));
}
TEST_F(ReorderTest, NotBufferedIfInvalidStation) {
WlanRxPacket packet;
ASSERT_FALSE(iwl_mvm_reorder(mvm_, nullptr, packet.Packet(), &rx_status_, 0, packet.Desc()));
}
TEST_F(ReorderTest, NotBufferedIfStationsDontMatch) {
WlanRxPacket packet;
mvm_sta_.sta_id = baid_data_->sta_id + 1;
ASSERT_FALSE(Reorder(packet));
}
TEST_F(ReorderTest, NotBufferedIfTidsDontMatch) {
WlanRxPacket packet;
packet.SetTid(baid_data_->tid + 1);
ASSERT_FALSE(Reorder(packet));
}
TEST_F(ReorderTest, NotBufferedIfInvalidBaidData) {
WlanRxPacket packet;
mvm_->baid_map[0] = nullptr;
ASSERT_FALSE(Reorder(packet));
}
TEST_F(ReorderTest, BufferPacketsWithDifferentSequenceNumbers) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
ASSERT_FALSE(GetReorderBuf().valid);
uint8_t sn = 1;
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn);
ASSERT_FALSE(BufferIndexHasPacket(sn));
ASSERT_TRUE(Reorder(packet));
ASSERT_TRUE(GetReorderBuf().valid);
ASSERT_TRUE(BufferIndexHasPacket(sn));
ASSERT_TRUE(packet.Equals(GetBufferedPacket(sn)));
++sn;
}
ASSERT_EQ(kNumPackets, GetReorderBuf().num_stored);
}
TEST_F(ReorderTest, DropPacketIfSameSequenceNumber) {
WlanRxPacket packet0;
packet0.SetSn(1);
ASSERT_FALSE(BufferIndexHasPacket(1));
ASSERT_TRUE(Reorder(packet0));
ASSERT_TRUE(BufferIndexHasPacket(1));
ASSERT_TRUE(packet0.Equals(GetBufferedPacket(1)));
WlanRxPacket packet1;
packet1.SetSn(1);
// Returns true if packet is dropped
ASSERT_TRUE(Reorder(packet1));
ASSERT_TRUE(BufferIndexHasPacket(1));
// Check that stored packet is packet0 to show packet1 was dropped
ASSERT_FALSE(packet1.Equals(GetBufferedPacket(1)));
ASSERT_TRUE(packet0.Equals(GetBufferedPacket(1)));
ASSERT_EQ(1, GetReorderBuf().num_stored);
}
TEST_F(ReorderTest, DropPacketIfSequenceNumberBehindBufferHead) {
WlanRxPacket packet;
GetReorderBuf().head_sn = 1;
ASSERT_TRUE(Reorder(packet));
ASSERT_TRUE(BufferIsEmpty());
packet.SetSn(10);
GetReorderBuf().head_sn = 23;
ASSERT_TRUE(Reorder(packet));
ASSERT_TRUE(BufferIsEmpty());
}
TEST_F(ReorderTest, FramesReleasedOnBlockAckRequestUpToNssn) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
// Pass packets to reorder buffer
uint8_t sn = static_cast<uint8_t>(GetReorderBuf().head_sn + 1);
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn++);
ASSERT_TRUE(Reorder(packet));
}
ASSERT_EQ(kNumPackets, GetReorderBuf().num_stored);
MockRecv(&ctx_, MockRecvCallback);
ASSERT_TRUE(ctx_.received.empty());
// Send Block Ack requests to trigger frames to be released
WlanRxPacket bar;
bar.SetFrameTypeAndSubtype(IEEE80211_FRAME_TYPE_CTRL, IEEE80211_FRAME_SUBTYPE_BACK_REQ);
bar.SetSn(sn);
bar.SetNssn(kNumPackets - 10 + 1);
ASSERT_TRUE(Reorder(bar));
ASSERT_EQ(10, GetReorderBuf().num_stored);
bar.SetSn(sn + 1);
bar.SetNssn(kNumPackets + 1);
ASSERT_TRUE(Reorder(bar));
ASSERT_TRUE(BufferIsEmpty());
ASSERT_EQ(ctx_.received.size(), packets.size());
// Check that they're released in the expected order
for (auto i = 0U; i < packets.size(); i++) {
ASSERT_TRUE(packets[i].Equals(ctx_.received[i]));
}
}
TEST_F(ReorderTest, OutOfOrderFramesAreReleasedInOrder) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
// Receive packets in random order
std::vector<uint8_t> receive_order{};
receive_order.resize(packets.size());
std::iota(receive_order.begin(), receive_order.end(), 0);
std::random_device rd;
std::mt19937 g(rd());
std::shuffle(receive_order.begin(), receive_order.end(), g);
for (const uint8_t i : receive_order) {
// Sn needs to be index + 1, because iwl_mvm_reorder() drops packets if it's empty
// and sn == head_sn. So adding 1 avoids that case.
packets[i].SetSn(i + 1);
ASSERT_FALSE(BufferIndexHasPacket(i + 1));
ASSERT_TRUE(Reorder(packets[i]));
ASSERT_TRUE(BufferIndexHasPacket(i + 1));
}
ASSERT_EQ(packets.size(), GetReorderBuf().num_stored);
ASSERT_TRUE(ctx_.received.empty());
MockRecv(&ctx_, MockRecvCallback);
// Release frames by sending BAR
WlanRxPacket bar;
bar.SetFrameTypeAndSubtype(IEEE80211_FRAME_TYPE_CTRL, IEEE80211_FRAME_SUBTYPE_BACK_REQ);
bar.SetSn(kNumPackets + 1);
bar.SetNssn(kNumPackets + 1);
ASSERT_TRUE(Reorder(bar));
ASSERT_TRUE(BufferIsEmpty());
ASSERT_EQ(ctx_.received.size(), packets.size());
// Check that the received order matches the order in packets,
// which is in ascending SN.
for (auto i = 0U; i < packets.size(); i++) {
ASSERT_TRUE(packets[i].Equals(ctx_.received[i]));
}
}
TEST_F(ReorderTest, FramesReleasedOnDataPacketsUpToNssn) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
// Pass packets to reorder buffer
// Starts with 1 so that they don't get passed up immediately.
uint8_t sn = 1;
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn++);
ASSERT_TRUE(Reorder(packet));
}
ASSERT_EQ(packets.size(), GetReorderBuf().num_stored);
MockRecv(&ctx_, MockRecvCallback);
ASSERT_TRUE(ctx_.received.empty());
// Send Block Ack requests to trigger frames to be released
WlanRxPacket data1, data2;
data1.SetSn(sn);
data1.SetNssn(kNumPackets - 10 + 1);
ASSERT_TRUE(Reorder(data1));
// 10 remaining in buffer + 1 for the new data packet
ASSERT_EQ(11, GetReorderBuf().num_stored);
data2.SetSn(sn + 1);
data2.SetNssn(kNumPackets + 1);
ASSERT_TRUE(Reorder(data2));
// Buffer should contain the previous data packet and this one
ASSERT_EQ(2, GetReorderBuf().num_stored);
ASSERT_TRUE(data1.Equals(GetBufferedPacket(sn)));
ASSERT_TRUE(data2.Equals(GetBufferedPacket(sn + 1)));
ASSERT_EQ(ctx_.received.size(), packets.size());
// Check that they're released in the expected order
for (auto i = 0U; i < packets.size(); i++) {
ASSERT_TRUE(packets[i].Equals(ctx_.received[i]));
}
}
TEST_F(ReorderTest, FrameNotBufferedIfBufferEmptyAndSnMatchesHeadSn) {
constexpr uint8_t kNumPackets = 30;
std::vector<WlanRxPacket> packets{kNumPackets};
uint8_t sn = static_cast<uint8_t>(GetReorderBuf().head_sn);
for (WlanRxPacket& packet : packets) {
packet.SetSn(sn++);
// Expected that Reorder will return false so that caller immediately passes it
// to MLME
ASSERT_FALSE(Reorder(packet));
}
ASSERT_TRUE(BufferIsEmpty());
}
TEST_F(ReorderTest, FrameNotBufferedIfBufferEmptyAndSnBehindNssn) {
WlanRxPacket packet;
packet.SetSn(0);
packet.SetNssn(1);
ASSERT_FALSE(Reorder(packet));
ASSERT_EQ(1, GetReorderBuf().head_sn);
ASSERT_TRUE(BufferIsEmpty());
}
TEST_F(ReorderTest, FrameNotBufferedIfReorderDataFlagSet) {
WlanRxPacket packet;
packet.SetSn(1);
packet.Desc()->reorder_data |= IWL_RX_MPDU_REORDER_BA_OLD_SN;
ASSERT_FALSE(Reorder(packet));
ASSERT_TRUE(BufferIsEmpty());
ASSERT_FALSE(GetReorderBuf().valid);
}
TEST_F(ReorderTest, TimeoutCallbackCalled) {
bool callback_called = false;
auto cb = [](void* data) {
bool* called = reinterpret_cast<bool*>(data);
*called = true;
};
SetupReorderTimer(&GetReorderBuf(), cb, &callback_called);
WlanRxPacket packet;
packet.SetSn(1);
ASSERT_TRUE(Reorder(packet));
ASSERT_EQ(ZX_OK, iwl_irq_timer_wait(GetReorderBuf().reorder_timer));
ASSERT_TRUE(callback_called);
}
///////////////////////////////////////////////////////////////////////////////
// Scan Test
//
// LMAC scan currently only supports passive scan.
class LmacScanTest : public MvmTest {
public:
LmacScanTest() {
mvmvif_sta.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_sta.mac_role = WLAN_MAC_ROLE_CLIENT;
// Fake callback registered to capture scan completion responses.
mvmvif_sta.ifc.scan_complete = [](void* ctx, zx_status_t status, uint64_t scan_id) {
// TODO(fxbug.dev/88934): scan_id is always 0
EXPECT_EQ(scan_id, 0);
struct ScanResult* sr = (struct ScanResult*)ctx;
sr->sme_notified = true;
sr->success = (status == ZX_OK ? true : false);
};
scan_result.sme_notified = false;
scan_result.success = false;
mvmvif_sta.ifc.ctx = &scan_result;
trans_ = sim_trans_.iwl_trans();
}
~LmacScanTest() {}
struct iwl_trans* trans_;
struct iwl_mvm_vif mvmvif_sta;
uint8_t channels_to_scan_[4] = {7, 1, 40, 136};
iwl_mvm_scan_req passive_scan_args_{
.channels_list = channels_to_scan_, .channels_count = 4,
// TODO(fxbug.dev/88943): Fill in other fields once support determined.
};
// Structure to capture scan results.
struct ScanResult {
bool sme_notified;
bool success;
} scan_result;
};
// UMAC scan currently supports both passive and active scan.
class UmacScanTest : public FakeUcodeTest, public MockTrans {
public:
static constexpr size_t kChannelNum = 4;
const uint8_t kChannelsToScan_[kChannelNum] = {7, 1, 40, 136};
static constexpr size_t kFakeSsidLen = 6;
const uint8_t kFakeSsid[kFakeSsidLen] = {'F', 'a', 'k', 'e', 'A', 'p'};
static constexpr size_t kFakeIesLen = 6;
const uint8_t kFakeIes[kFakeIesLen] = {'F', 'a', 'k', 'e', 'I', 'E'};
static constexpr size_t kFakeMacHdrLen = 8;
const uint8_t kFakeMacHdr[kFakeMacHdrLen] = {'F', 'a', 'k', 'e', 'H', 'e', 'a', 'd'};
UmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS
: FakeUcodeTest({IWL_UCODE_TLV_CAPA_UMAC_SCAN},
{IWL_UCODE_TLV_API_ADAPTIVE_DWELL, IWL_UCODE_TLV_API_SCAN_EXT_CHAN_VER}) {
mvm_ = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_ = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
mvmvif_->mvm = mvm_;
mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
mvm_->mvmvif[0] = mvmvif_;
mvm_->vif_count++;
// Set the channel filter to the exact channel that we want to conduct scan on to pass the
// filtering.
// TODO(fxbug.dev/95207): Remove the hardcode and read mcc_info from the firmware when
// FakeUcodeTest supports multiple api_flag input.
mvm_->mcc_info.num_ch = kChannelNum;
memcpy(&(mvm_->mcc_info.channels[0]), &kChannelsToScan_[0], 4);
for (uint16_t i = 0; i < kChannelNum; i++) {
mvm_->mcc_info.ch_flags[i] = BIT(0) | BIT(3); // NVM_CHANNEL_VALID and NVM_CHANNEL_ACTIVE
}
mtx_lock(&mvm_->mutex);
// Fake callback registered to capture scan completion responses.
mvmvif_sta_.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_sta_.mac_role = WLAN_MAC_ROLE_CLIENT;
// Fake callback registered to capture scan completion responses.
mvmvif_sta_.ifc.scan_complete = [](void* ctx, zx_status_t status, uint64_t scan_id) {
// TODO(fxbug.dev/88934): scan_id is always 0
EXPECT_EQ(scan_id, 0);
struct ScanResult* sr = (struct ScanResult*)ctx;
sr->sme_notified = true;
sr->success = (status == ZX_OK ? true : false);
};
scan_result_.sme_notified = false;
scan_result_.success = false;
mvmvif_sta_.ifc.ctx = &scan_result_;
active_scan_args_.ssids =
(struct iwl_mvm_ssid*)calloc(active_scan_args_.ssids_count, sizeof(struct iwl_mvm_ssid));
active_scan_args_.ssids->ssid_len = 6;
memcpy(active_scan_args_.ssids->ssid_data, kFakeSsid, 6);
trans_ = sim_trans_.iwl_trans();
}
~UmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
mock_send_umac_scan_cmd_.VerifyAndClear();
free(mvmvif_);
free(active_scan_args_.ssids);
mtx_unlock(&mvm_->mutex);
}
static zx_status_t send_scan_cmd_wrapper(struct iwl_trans* trans, struct iwl_host_cmd* host_cmd) {
auto umac_scan_cmd = reinterpret_cast<const struct iwl_scan_req_umac*>(host_cmd->data[0]);
auto test = GET_TEST(UmacScanTest, trans);
return test->mock_send_umac_scan_cmd_.Call(host_cmd->id, host_cmd->len[0], umac_scan_cmd->uid);
}
struct iwl_mvm_vif mvmvif_sta_;
iwl_mvm_scan_req passive_scan_args_{
.channels_list = kChannelsToScan_, .channels_count = kChannelNum,
// TODO(fxbug.dev/89693): iwlwifi ignores all other fields.
};
iwl_mvm_scan_req active_scan_args_{
.channels_list = kChannelsToScan_,
.channels_count = kChannelNum,
.ssids_count = 1,
.mac_header_buffer = kFakeMacHdr,
.mac_header_size = 5,
.ies_buffer = kFakeIes,
.ies_size = 6,
// TODO(fxbug.dev/89693): iwlwifi ignores all other fields.
};
// Structure to capture scan results.
struct ScanResult {
bool sme_notified;
bool success;
} scan_result_;
protected:
struct iwl_mvm* mvm_;
struct iwl_mvm_vif* mvmvif_;
wlan_softmac_ifc_protocol_ops_t protocol_ops_;
// Expected fields.
mock_function::MockFunction<zx_status_t, // return value
uint32_t, // host_cmd->id
uint16_t, // host_cmd->len[0]
__le32 // umac_scan_cmd->uid
>
mock_send_umac_scan_cmd_;
};
/* Tests for LMAC scan */
// Tests scenario for a successful scan completion.
TEST_F(LmacScanTest, RegPassiveLmacScanSuccess) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result.sme_notified);
ASSERT_EQ(false, scan_result.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
struct iwl_periodic_scan_complete scan_notif{.status = IWL_SCAN_OFFLOAD_COMPLETED};
TestRxcb rxb(sim_trans_.iwl_trans()->dev, &scan_notif, sizeof(scan_notif));
// Call notify complete to simulate scan completion.
iwl_mvm_rx_lmac_scan_complete_notif(mvm_, &rxb);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result.sme_notified);
EXPECT_EQ(true, scan_result.success);
}
// Tests scenario where the scan request aborted / failed.
TEST_F(LmacScanTest, RegPassiveLmacScanAborted) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result.sme_notified);
ASSERT_EQ(false, scan_result.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
// Set scan status to ABORTED so simulate a scan abort.
struct iwl_periodic_scan_complete scan_notif{.status = IWL_SCAN_OFFLOAD_ABORTED};
TestRxcb rxb(sim_trans_.iwl_trans()->dev, &scan_notif, sizeof(scan_notif));
// Call notify complete to simulate scan abort.
iwl_mvm_rx_lmac_scan_complete_notif(mvm_, &rxb);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result.sme_notified);
EXPECT_EQ(false, scan_result.success);
}
/* Tests for UMAC scan */
// Tests scenario for a successful passive scan completion.
TEST_F(UmacScanTest, RegPassiveUmacScanSuccess) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result_.sme_notified);
ASSERT_EQ(false, scan_result_.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
struct iwl_umac_scan_complete scan_notif{.status = IWL_SCAN_OFFLOAD_COMPLETED};
TestRxcb rxb(sim_trans_.iwl_trans()->dev, &scan_notif, sizeof(scan_notif));
// Call notify complete to simulate scan completion.
mtx_unlock(&mvm_->mutex);
iwl_mvm_rx_umac_scan_complete_notif(mvm_, &rxb);
mtx_lock(&mvm_->mutex);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result_.sme_notified);
EXPECT_EQ(true, scan_result_.success);
}
// Tests scenario for a successful active scan completion.
TEST_F(UmacScanTest, RegActiveUmacScanSuccess) __TA_NO_THREAD_SAFETY_ANALYSIS {
// Check some assumptions before running the test.
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result_.sme_notified);
ASSERT_EQ(false, scan_result_.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &active_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_uid_status[0]);
EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
// Verify the scan cmd filling is correct.
struct iwl_scan_req_umac* cmd = reinterpret_cast<struct iwl_scan_req_umac*>(mvm_->scan_cmd);
// IWL_UCODE_TLV_API_ADAPTIVE_DWELL is set when constructing the UmacScanTest class, the
// corresponding data type is v7.
uint8_t* cmd_data = (uint8_t*)&cmd->v7.data;
EXPECT_EQ(4, cmd->v7.channel.count);
// Verify that it's an active scan.
EXPECT_EQ(0, le16_to_cpu(cmd->general_flags) & IWL_UMAC_SCAN_GEN_FLAGS_PASSIVE);
struct iwl_scan_channel_cfg_umac* channel_cfg =
reinterpret_cast<struct iwl_scan_channel_cfg_umac*>(cmd_data);
// Verify ssid_bitmap.
EXPECT_EQ(BIT(0), le32_to_cpu(channel_cfg[0].flags));
EXPECT_EQ(1, le32_to_cpu(channel_cfg[0].v2.iter_count));
EXPECT_EQ(0, le32_to_cpu(channel_cfg[0].v2.iter_interval));
// Verify the first and the last channel number.
EXPECT_EQ(7, le16_to_cpu(channel_cfg[0].v1.channel_num));
EXPECT_EQ(136, le16_to_cpu(channel_cfg[3].v1.channel_num));
struct iwl_scan_req_umac_tail_v2* sec_part_of_cmd_data =
(struct iwl_scan_req_umac_tail_v2*)(cmd_data + sizeof(struct iwl_scan_channel_cfg_umac) *
mvm_->fw->ucode_capa.n_scan_channels);
struct iwl_scan_probe_req* preq = &sec_part_of_cmd_data->preq;
uint8_t* frame_data = (uint8_t*)preq->buf;
// Verify schedule data.
EXPECT_EQ(1, sec_part_of_cmd_data->schedule[0].iter_count);
EXPECT_EQ(0, sec_part_of_cmd_data->schedule[0].interval);
// Verify MAC header.
EXPECT_EQ(0, memcmp(kFakeMacHdr, frame_data + le16_to_cpu(preq->mac_header.offset),
le16_to_cpu(preq->mac_header.len) - 2));
// Verify common IE.
EXPECT_EQ(0, memcmp(kFakeIes, frame_data + le16_to_cpu(preq->common_data.offset),
le16_to_cpu(preq->common_data.len)));
// Verify ssid.
EXPECT_EQ(WLAN_EID_SSID, sec_part_of_cmd_data->direct_scan[0].id);
EXPECT_EQ(6, sec_part_of_cmd_data->direct_scan[0].len);
EXPECT_EQ(0, memcmp(kFakeSsid, sec_part_of_cmd_data->direct_scan[0].ssid, 6));
struct iwl_umac_scan_complete scan_notif{.status = IWL_SCAN_OFFLOAD_COMPLETED};
TestRxcb rxb(sim_trans_.iwl_trans()->dev, &scan_notif, sizeof(scan_notif));
// Call notify complete to simulate scan completion.
mtx_unlock(&mvm_->mutex);
iwl_mvm_rx_umac_scan_complete_notif(mvm_, &rxb);
mtx_lock(&mvm_->mutex);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result_.sme_notified);
EXPECT_EQ(true, scan_result_.success);
}
// Tests scenario where the scan request aborted / failed.
TEST_F(UmacScanTest, RegPassiveUmacScanAborted) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result_.sme_notified);
ASSERT_EQ(false, scan_result_.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
// Set scan status to ABORTED so simulate a scan abort.
struct iwl_umac_scan_complete scan_notif{.status = IWL_SCAN_OFFLOAD_ABORTED};
TestRxcb rxb(sim_trans_.iwl_trans()->dev, &scan_notif, sizeof(scan_notif));
// Call notify complete to simulate scan abort.
mtx_unlock(&mvm_->mutex);
iwl_mvm_rx_umac_scan_complete_notif(mvm_, &rxb);
mtx_lock(&mvm_->mutex);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result_.sme_notified);
EXPECT_EQ(false, scan_result_.success);
}
// Tests explicit abort of Passive Umac scan in progress.
TEST_F(UmacScanTest, RegPassiveUmacAbortScan) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result_.sme_notified);
ASSERT_EQ(false, scan_result_.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta_, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta_, mvm_->scan_vif);
// attempt to stop any ongoing scans.
iwl_mvm_scan_stop(mvm_, IWL_MVM_SCAN_REGULAR, false);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result_.sme_notified);
EXPECT_EQ(false, scan_result_.success);
}
TEST_F(UmacScanTest, FailedScanCmd) __TA_NO_THREAD_SAFETY_ANALYSIS {
// mock function after the testing environment had been set.
BIND_TEST(mvm_->trans);
bindSendCmd(send_scan_cmd_wrapper);
static constexpr size_t uid = 0;
ASSERT_EQ(0, mvm_->scan_uid_status[uid]);
size_t cmd_len = iwl_mvm_scan_size(mvm_);
mock_send_umac_scan_cmd_.ExpectCall(ZX_ERR_INTERNAL, // return value
WIDE_ID(LONG_GROUP, SCAN_REQ_UMAC), // host_cmd->id
cmd_len, // host_cmd->len[0]
uid // umac_scan_cmd->uid
);
ASSERT_EQ(ZX_ERR_INTERNAL, iwl_mvm_reg_scan_start(&mvmvif_sta_, &passive_scan_args_));
ASSERT_EQ(0, mvm_->scan_uid_status[uid]);
unbindSendCmd();
}
/* Tests for both LMAC and UMAC scans */
// Tests condition where scan completion timeouts out due to no response from FW.
TEST_F(LmacScanTest, RegPassiveScanTimeout) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result.sme_notified);
ASSERT_EQ(false, scan_result.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
// Do not call notify complete; instead invoke the timeout callback to simulate a timeout event.
mtx_unlock(&mvm_->mutex);
iwl_mvm_scan_timeout_wk(mvm_);
mtx_lock(&mvm_->mutex);
EXPECT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(true, scan_result.sme_notified);
EXPECT_EQ(false, scan_result.success);
}
// Tests condition where timer is shutdown and there is no response from FW.
TEST_F(LmacScanTest, RegPassiveScanTimerShutdown) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(false, scan_result.sme_notified);
ASSERT_EQ(false, scan_result.success);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
// Do not call notify complete, and do not invoke the timeout callback. This simulates a timer
// shutdown while it is pending.
// Ensure the state is such that no FW response or timeout has happened.
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(false, scan_result.sme_notified);
EXPECT_EQ(false, scan_result.success);
}
// Tests condition where iwl_mvm_mac_stop() is invoked while timer is pending.
TEST_F(LmacScanTest, RegPassiveScanTimerMvmStop) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(nullptr, mvm_->scan_vif);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(&mvmvif_sta, mvm_->scan_vif);
mtx_unlock(&mvm_->mutex);
iwl_mvm_mac_stop(mvm_);
mtx_lock(&mvm_->mutex);
}
// Tests condition where multiple calls to the scan API returns appropriate error.
TEST_F(LmacScanTest, RegPassiveScanParallel) __TA_NO_THREAD_SAFETY_ANALYSIS {
ASSERT_EQ(0, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
ASSERT_EQ(ZX_OK, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
EXPECT_EQ(IWL_MVM_SCAN_REGULAR, mvm_->scan_status & IWL_MVM_SCAN_REGULAR);
EXPECT_EQ(ZX_ERR_SHOULD_WAIT, iwl_mvm_reg_scan_start(&mvmvif_sta, &passive_scan_args_));
}
///////////////////////////////////////////////////////////////////////////////
// Time Event Test
//
class TimeEventTest : public MvmTest {
public:
TimeEventTest() {
// In order to init the mvmvif_->time_event_data.id to TE_MAX.
iwl_mvm_mac_ctxt_init(mvmvif_);
}
};
TEST_F(TimeEventTest, NormalCase) {
// wait_for_notif is true.
ASSERT_EQ(0, list_length(&mvm_->time_event_list));
ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, true));
ASSERT_EQ(1, list_length(&mvm_->time_event_list));
ASSERT_EQ(ZX_OK, iwl_mvm_stop_session_protection(mvmvif_));
ASSERT_EQ(0, list_length(&mvm_->time_event_list));
// wait_for_notif is false.
ASSERT_EQ(0, list_length(&mvm_->time_event_list));
ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, false));
ASSERT_EQ(1, list_length(&mvm_->time_event_list));
ASSERT_EQ(ZX_OK, iwl_mvm_stop_session_protection(mvmvif_));
ASSERT_EQ(0, list_length(&mvm_->time_event_list));
}
TEST_F(TimeEventTest, Notification) {
// Set wait_for_notif to false so that we don't wait for TIME_EVENT_NOTIFICATION.
ASSERT_EQ(ZX_OK, iwl_mvm_protect_session(mvm_, mvmvif_, 1, 2, 3, false));
// On the real device, 'te_data->uid' is populated by response of TIME_EVENT_CMD. However, the
// iwl_mvm_time_event_send_add() uses iwl_wait_notification() to get the value instead of reading
// from the cmd->resp_pkt (see the comment in iwl_mvm_time_event_send_add()).
//
// However, the current test/sim-mvm.cc is hard to implement the wait notification yet (which
// requires multi-threading model). So, the hack is inserting the 'te_data->uid' in the test code.
//
// TODO(fxbug.dev/87974): remove this hack once the wait notification model is supported in the
// testing code.
//
ASSERT_EQ(1, list_length(&mvm_->time_event_list));
auto* te_data = list_peek_head_type(&mvm_->time_event_list, struct iwl_mvm_time_event_data, list);
te_data->uid = kFakeUniqueId;
// Generate a fake TIME_EVENT_NOTIFICATION from the firmware. Note that this notification is
// differnt from the above code, which is the notification for TIME_EVENT_CMD.
//
// We expect the driver will remove the waiting notification from the `time_event_list`.
//
// TODO(fxbug.dev/51671): remove this hack once the test/sim-mvm.cc can support filing another
// notification from one host command.
//
struct iwl_time_event_notif notif = {
.unique_id = kFakeUniqueId,
.action = TE_V2_NOTIF_HOST_EVENT_END,
};
TestRxcb time_event_rxcb(sim_trans_.iwl_trans()->dev, &notif, sizeof(notif));
iwl_mvm_rx_time_event_notif(mvm_, &time_event_rxcb);
ASSERT_EQ(0, list_length(&mvm_->time_event_list));
}
///////////////////////////////////////////////////////////////////////////////
// Binding Test
//
class BindingTest : public MvmTest {
public:
BindingTest() { setup_phy_ctxt(mvmvif_); }
};
TEST_F(BindingTest, CheckArgs) {
// Failed because phy_ctxt is unexpected.
mvmvif_->phy_ctxt = NULL;
ASSERT_EQ(ZX_ERR_BAD_STATE, iwl_mvm_binding_add_vif(mvmvif_));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, iwl_mvm_binding_remove_vif(mvmvif_));
}
TEST_F(BindingTest, NormalCase) {
ASSERT_EQ(ZX_OK, iwl_mvm_binding_add_vif(mvmvif_));
ASSERT_EQ(ZX_OK, iwl_mvm_binding_remove_vif(mvmvif_));
}
///////////////////////////////////////////////////////////////////////////////
// Power Test
//
class PowerTest : public MvmTest {
public:
PowerTest() { setup_phy_ctxt(mvmvif_); }
};
// By default, only one interface is created and its ps_disabled is false. So:
//
// - mvmvif->pm_enabled is true.
// - mvmvif->ps_disabled is false.
// - thus, mvm->ps_disabled is false as well.
//
TEST_F(PowerTest, DefaultCase) {
ASSERT_EQ(ZX_OK, iwl_mvm_power_update_mac(mvm_));
EXPECT_EQ(true, mvmvif_->pm_enabled);
EXPECT_EQ(false, mvmvif_->ps_disabled);
EXPECT_EQ(false, mvm_->ps_disabled);
}
// Disable the PS of interface. We shall see MVM PS is disabled as well.
//
// - mvmvif->pm_enabled is true.
// - mvmvif->ps_disabled is false.
// - thus, mvm->ps_disabled is false as well.
//
TEST_F(PowerTest, PsDisabled) {
mvmvif_->ps_disabled = true;
ASSERT_EQ(ZX_OK, iwl_mvm_power_update_mac(mvm_));
EXPECT_EQ(true, mvmvif_->pm_enabled);
EXPECT_EQ(true, mvmvif_->ps_disabled);
EXPECT_EQ(true, mvm_->ps_disabled);
}
// The input pm_enabled has no effect since it is determined by iwl_mvm_power_update_mac() according
// to the current interface configuraiton.
//
// The expected results are identical to the default case above.
//
TEST_F(PowerTest, PmHasNoEffect) {
mvmvif_->pm_enabled = false;
ASSERT_EQ(ZX_OK, iwl_mvm_power_update_mac(mvm_));
EXPECT_EQ(true, mvmvif_->pm_enabled);
EXPECT_EQ(false, mvmvif_->ps_disabled);
EXPECT_EQ(false, mvm_->ps_disabled);
mvmvif_->pm_enabled = true;
ASSERT_EQ(ZX_OK, iwl_mvm_power_update_mac(mvm_));
EXPECT_EQ(true, mvmvif_->pm_enabled);
EXPECT_EQ(false, mvmvif_->ps_disabled);
EXPECT_EQ(false, mvm_->ps_disabled);
}
///////////////////////////////////////////////////////////////////////////////
// Txq Test
//
class TxqTest : public MvmTest, public MockTrans {
public:
TxqTest()
: sta_{
.sta_id = 0,
.mvmvif = mvmvif_,
.addr = {0x02, 0x03, 0x04, 0x05, 0x06, 0x07},
} {
BIND_TEST(mvm_->trans);
mvm_->fw_id_to_mac_id[0] = &sta_;
for (size_t i = 0; i < std::size(sta_.txq); ++i) {
sta_.txq[i] = reinterpret_cast<struct iwl_mvm_txq*>(calloc(1, sizeof(struct iwl_mvm_txq)));
EXPECT_NE(nullptr, sta_.txq[i]);
}
}
~TxqTest() {
for (size_t i = 0; i < std::size(sta_.txq); ++i) {
free(sta_.txq[i]);
}
mock_tx_.VerifyAndClear();
}
// Expected fields.
mock_function::MockFunction<zx_status_t, // return value
size_t, // packet size
uint16_t, // cmd + group_id
int // txq_id
>
mock_tx_;
static zx_status_t tx_wrapper(struct iwl_trans* trans, struct ieee80211_mac_packet* pkt,
struct iwl_device_tx_cmd* dev_cmd, int txq_id) {
auto test = GET_TEST(TxqTest, trans);
return test->mock_tx_.Call(pkt->header_size + pkt->headroom_used_size + pkt->body_size,
WIDE_ID(dev_cmd->hdr.group_id, dev_cmd->hdr.cmd), txq_id);
}
protected:
struct iwl_mvm_sta sta_;
};
TEST_F(TxqTest, TestAllocManagement) {
// Ensure the internal state is cleared.
ASSERT_EQ(0, sta_.tid_data[IWL_MAX_TID_COUNT].txq_id);
ASSERT_EQ(0, sta_.tfd_queue_msk);
// Keep asking for queue for management packet (TID=MAX).
// Expect txq_id IWL_MVM_DQA_MIN_MGMT_QUEUE is allocated.
auto expected_mask = sta_.tfd_queue_msk;
for (size_t i = 0; i < (IWL_MVM_DQA_MAX_MGMT_QUEUE - IWL_MVM_DQA_MIN_MGMT_QUEUE + 1); ++i) {
int tid = IWL_MAX_TID_COUNT;
ASSERT_EQ(ZX_OK, iwl_mvm_sta_alloc_queue(mvm_, &sta_, IEEE80211_AC_BE, tid));
EXPECT_EQ(i + IWL_MVM_DQA_MIN_MGMT_QUEUE, sta_.tid_data[tid].txq_id);
expected_mask |= BIT(i + IWL_MVM_DQA_MIN_MGMT_QUEUE);
EXPECT_EQ(expected_mask, sta_.tfd_queue_msk);
}
// Request once more. Since there is no queue for management packet, expect data queue.
ASSERT_EQ(ZX_OK, iwl_mvm_sta_alloc_queue(mvm_, &sta_, IEEE80211_AC_BE, IWL_MAX_TID_COUNT));
EXPECT_EQ(IWL_MVM_DQA_MIN_DATA_QUEUE, sta_.tid_data[IWL_MAX_TID_COUNT].txq_id);
expected_mask |= BIT(IWL_MVM_DQA_MIN_DATA_QUEUE);
EXPECT_EQ(expected_mask, sta_.tfd_queue_msk);
}
TEST_F(TxqTest, TestAllocData) {
// Ensure the internal state is cleared.
ASSERT_EQ(0, sta_.tid_data[IWL_MAX_TID_COUNT].txq_id);
ASSERT_EQ(0, sta_.tfd_queue_msk);
// Keep asking for queue for data packet (TID!=MAX).
// Expect txq_id IWL_MVM_DQA_MIN_DATA_QUEUE is allocated.
auto expected_mask = sta_.tfd_queue_msk;
for (size_t i = 0; i < (IWL_MVM_DQA_MAX_DATA_QUEUE - IWL_MVM_DQA_MIN_DATA_QUEUE + 1); ++i) {
int tid = IWL_TID_NON_QOS;
ASSERT_EQ(ZX_OK, iwl_mvm_sta_alloc_queue(mvm_, &sta_, IEEE80211_AC_BE, tid));
EXPECT_EQ(i + IWL_MVM_DQA_MIN_DATA_QUEUE, sta_.tid_data[tid].txq_id);
expected_mask |= BIT(i + IWL_MVM_DQA_MIN_DATA_QUEUE);
EXPECT_EQ(expected_mask, sta_.tfd_queue_msk);
}
// Request once more. Since there is no queue for data packet, expect failure.
// TODO(fxbug.dev/49530): this should be re-written once shared queue is supported.
ASSERT_EQ(ZX_ERR_NO_RESOURCES, iwl_mvm_sta_alloc_queue(mvm_, &sta_, IEEE80211_AC_BE, 0));
}
TEST_F(TxqTest, DataTxCmd) {
ieee80211_mac_packet pkt = {
.body_size = 56, // arbitrary value.
};
iwl_tx_cmd tx_cmd = {
.tx_flags = TX_CMD_FLG_TSF, // arbitary value to ensure the function would keep it.
};
struct ieee80211_tx_info info = {};
iwl_mvm_set_tx_cmd(mvmvif_->mvm, &pkt, &tx_cmd, &info, static_cast<uint8_t>(sta_.sta_id));
// Currently the function doesn't consider the QoS so that those values are just fixed value.
EXPECT_EQ(TX_CMD_FLG_TSF | TX_CMD_FLG_SEQ_CTL | TX_CMD_FLG_BT_DIS | TX_CMD_FLG_ACK,
tx_cmd.tx_flags);
EXPECT_EQ(IWL_MAX_TID_COUNT, tx_cmd.tid_tspec);
EXPECT_EQ(cpu_to_le16(PM_FRAME_MGMT), tx_cmd.pm_frame_timeout);
EXPECT_EQ(cpu_to_le16(static_cast<uint16_t>(pkt.body_size)), tx_cmd.len);
EXPECT_EQ(cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE), tx_cmd.life_time);
EXPECT_EQ(0, tx_cmd.sta_id);
}
TEST_F(TxqTest, DataTxCmdRate) {
iwl_tx_cmd tx_cmd = {};
struct ieee80211_frame_header frame_hdr;
// construct a data frame, and check the rate.
frame_hdr.frame_ctrl |= IEEE80211_FRAME_TYPE_DATA;
sta_.sta_state = IWL_STA_AUTHORIZED;
iwl_mvm_set_tx_cmd_rate(mvmvif_->mvm, &tx_cmd, &frame_hdr);
// Verify tx_cmd rate fields when frame type is data frame when station is authorized, the rate
// should not be set.
EXPECT_EQ(0, tx_cmd.initial_rate_index);
EXPECT_GT(tx_cmd.tx_flags & cpu_to_le32(TX_CMD_FLG_STA_RATE), 0);
EXPECT_EQ(0, tx_cmd.rate_n_flags);
EXPECT_EQ(IWL_RTS_DFAULT_RETRY_LIMIT, tx_cmd.rts_retry_limit);
EXPECT_EQ(IWL_DEFAULT_TX_RETRY, tx_cmd.data_retry_limit);
}
TEST_F(TxqTest, MgmtTxCmdRate) {
iwl_tx_cmd tx_cmd = {};
struct ieee80211_frame_header frame_hdr;
// construct a non-data frame, and check the rate.
frame_hdr.frame_ctrl |= IEEE80211_FRAME_TYPE_MGMT;
iwl_mvm_set_tx_cmd_rate(mvmvif_->mvm, &tx_cmd, &frame_hdr);
// Because the rate which is set to non-data frame in our code is a temporary value, so this line
// might be changed in the future.
EXPECT_EQ(iwl_mvm_mac80211_idx_to_hwrate(IWL_FIRST_OFDM_RATE) |
(BIT(mvm_->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS),
tx_cmd.rate_n_flags);
}
TEST_F(TxqTest, TxPktInvalidInput) {
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt(builder.build());
// Null STA
EXPECT_EQ(ZX_ERR_INVALID_ARGS, iwl_mvm_tx_skb(mvm_, wlan_pkt->mac_pkt(), nullptr));
// invalid STA id.
uint32_t sta_id = sta_.sta_id;
sta_.sta_id = IWL_MVM_INVALID_STA;
EXPECT_EQ(ZX_ERR_INVALID_ARGS, iwl_mvm_tx_skb(mvm_, wlan_pkt->mac_pkt(), &sta_));
sta_.sta_id = sta_id;
// the check in iwl_mvm_tx_pkt_queued() -- after iwl_trans_tx().
{
bindTx(tx_wrapper);
mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), 0);
uint32_t mac_id_n_color = sta_.mac_id_n_color;
sta_.mac_id_n_color = NUM_MAC_INDEX_DRIVER;
EXPECT_EQ(ZX_ERR_INVALID_ARGS, iwl_mvm_tx_skb(mvm_, wlan_pkt->mac_pkt(), &sta_));
sta_.mac_id_n_color = mac_id_n_color; // Restore the changed value.
unbindTx();
}
}
TEST_F(TxqTest, TxPkt) {
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt(builder.build());
bindTx(tx_wrapper);
mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), 0);
EXPECT_EQ(ZX_OK, iwl_mvm_tx_skb(mvmvif_->mvm, wlan_pkt->mac_pkt(), &sta_));
unbindTx();
}
// Check to see Tx params are set correctly based on frame control
TEST_F(TxqTest, TxPktProtected) {
// Send a protected data frame and see that the crypt header is being added
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt(builder.build(0x4188));
EXPECT_EQ(wlan_pkt->mac_pkt()->headroom_used_size, 0);
// Setup a key conf to pretend that this is a secure connection
auto key_conf = reinterpret_cast<ieee80211_key_conf*>(malloc(sizeof(ieee80211_key_conf) + 16));
memset(key_conf, 0, sizeof(*key_conf) + 16);
key_conf->cipher = 4;
key_conf->key_type = 1;
key_conf->keyidx = 0;
key_conf->keylen = 16;
key_conf->rx_seq = 0;
wlan_pkt->mac_pkt()->info.control.hw_key = key_conf;
bindTx(tx_wrapper);
// Expect the packet length to be 8 bytes longer
mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len() + 8, WIDE_ID(0, TX_CMD), 0);
EXPECT_EQ(ZX_OK, iwl_mvm_tx_skb(mvmvif_->mvm, wlan_pkt->mac_pkt(), &sta_));
unbindTx();
// Expect that the headroom size is set to 8
EXPECT_EQ(wlan_pkt->mac_pkt()->headroom_used_size, 8);
free(key_conf);
}
} // namespace
} // namespace testing
} // namespace wlan