blob: d017baad819ebc12b0e855f29c2666e29a236c47 [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 <iterator>
#include <memory>
extern "C" {
#include "third_party/iwlwifi/mvm/mvm.h"
#include "third_party/iwlwifi/mvm/time-event.h"
}
#include "third_party/iwlwifi/platform/ieee80211_include.h"
#include "third_party/iwlwifi/platform/memory.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"
#include "third_party/iwlwifi/test/mock-function.h"
#include "third_party/iwlwifi/test/test.h"
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.
wlan_channel_t chandef = {
// any arbitrary values
.primary = 6,
};
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_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;
mvmvif_->ifc.ops = reinterpret_cast<wlan_softmac_ifc_protocol_ops_t*>(
calloc(1, sizeof(wlan_softmac_ifc_protocol_ops_t)));
mvm_->mvmvif[0] = mvmvif_;
mvm_->vif_count++;
mtx_lock(&mvm_->mutex);
}
~MvmTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
free(mvmvif_->ifc.ops);
free(mvmvif_);
mtx_unlock(&mvm_->mutex);
}
protected:
// 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.
void MockRecv(TestCtx* ctx) {
// TODO(fxbug.dev/43218): replace rxq->napi with interface instance so that we can map to
// mvmvif.
mvmvif_->ifc.ctx = ctx; // 'ctx' was used as 'wlan_softmac_ifc_protocol_t*', but we override it
// with 'TestCtx*'.
mvmvif_->ifc.ops->recv = [](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_;
};
TEST_F(MvmTest, GetMvm) { EXPECT_NE(mvm_, nullptr); }
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));
TestCtx test_ctx = {};
MockRecv(&test_ctx);
iwl_mvm_rx_rx_mpdu(mvm_, nullptr /* napi */, &mpdu_rxcb);
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));
TestCtx test_ctx = {};
MockRecv(&test_ctx);
iwl_mvm_rx_mpdu_mq(mvm_, nullptr /* napi */, &mpdu_rxcb, 0);
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);
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.
TEST_F(MvmTest, scanLmacNormal) {
ASSERT_NE(nullptr, mvm_->scan_cmd); // scan cmd should have been allocated during init.
struct iwl_mvm_scan_params params = {
.type = IWL_SCAN_TYPE_WILD,
.hb_type = IWL_SCAN_TYPE_NOT_SET,
.n_channels = 4,
.channels =
{
5,
11,
36,
165,
},
.n_ssids = 0,
.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]);
}
///////////////////////////////////////////////////////////////////////////////
// Scan Test
//
class PassiveScanTest : public MvmTest {
public:
PassiveScanTest() {
// Fake callback registered to capture scan completion responses.
ops.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);
};
mvmvif_sta.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_sta.mac_role = WLAN_MAC_ROLE_CLIENT;
mvmvif_sta.ifc.ops = &ops;
mvmvif_sta.ifc.ctx = &scan_result;
trans_ = sim_trans_.iwl_trans();
}
~PassiveScanTest() {}
struct iwl_trans* trans_;
wlan_softmac_ifc_protocol_ops_t ops;
struct iwl_mvm_vif mvmvif_sta;
uint8_t channels_to_scan_[4] = {7, 1, 40, 136};
wlan_softmac_passive_scan_args_t 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;
};
class PassiveUmacScanTest : public FakeUcodeTest {
public:
PassiveUmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS
: FakeUcodeTest(0, BIT(IWL_UCODE_TLV_CAPA_UMAC_SCAN), 0, 0) {
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;
mvmvif_->ifc.ops = reinterpret_cast<wlan_softmac_ifc_protocol_ops_t*>(
calloc(1, sizeof(wlan_softmac_ifc_protocol_ops_t)));
mvm_->mvmvif[0] = mvmvif_;
mvm_->vif_count++;
mtx_lock(&mvm_->mutex);
// Fake callback registered to capture scan completion responses.
ops_.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);
};
mvmvif_sta_.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_sta_.mac_role = WLAN_MAC_ROLE_CLIENT;
mvmvif_sta_.ifc.ops = &ops_;
mvmvif_sta_.ifc.ctx = &scan_result_;
trans_ = sim_trans_.iwl_trans();
}
~PassiveUmacScanTest() __TA_NO_THREAD_SAFETY_ANALYSIS {
free(mvmvif_->ifc.ops);
free(mvmvif_);
mtx_unlock(&mvm_->mutex);
}
struct iwl_trans* trans_;
wlan_softmac_ifc_protocol_ops_t ops_;
struct iwl_mvm_vif mvmvif_sta_;
uint8_t channels_to_scan_[4] = {7, 1, 40, 136};
wlan_softmac_passive_scan_args_t passive_scan_args_{
.channels_list = channels_to_scan_, .channels_count = 4,
// 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_;
};
/* Tests for LMAC scan */
// Tests scenario for a successful scan completion.
TEST_F(PassiveScanTest, 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_passive(&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.
mtx_unlock(&mvm_->mutex);
iwl_mvm_rx_lmac_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(PassiveScanTest, 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_passive(&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.
mtx_unlock(&mvm_->mutex);
iwl_mvm_rx_lmac_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 for UMAC scan */
// Tests scenario for a successful scan completion.
TEST_F(PassiveUmacScanTest, 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_passive(&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 where the scan request aborted / failed.
TEST_F(PassiveUmacScanTest, 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_passive(&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 for both LMAC and UMAC scans */
// Tests condition where scan completion timeouts out due to no response from FW.
TEST_F(PassiveScanTest, 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_passive(&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(PassiveScanTest, 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_passive(&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(PassiveScanTest, 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_passive(&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(PassiveScanTest, 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_passive(&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_passive(&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]);
}
}
// 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,
const struct iwl_device_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.
};
iwl_mvm_set_tx_cmd(mvmvif_->mvm, &pkt, &tx_cmd, 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