blob: 9fd15fa83fddece6b06f5b949bb5481f53060317 [file] [log] [blame]
// Copyright 2022 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 "third_party/iwlwifi/platform/wlan-softmac-device.h"
#include <fidl/fuchsia.wlan.ieee80211/cpp/wire_types.h>
#include <fuchsia/hardware/wlan/associnfo/cpp/banjo.h>
#include <fuchsia/hardware/wlan/softmac/cpp/banjo.h>
#include <fuchsia/wlan/ieee80211/c/fidl.h>
#include <lib/mock-function/mock-function.h>
#include <lib/zx/channel.h>
#include <stdlib.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <iterator>
#include <list>
#include <memory>
#include <utility>
#include <zxtest/zxtest.h>
extern "C" {
#include "third_party/iwlwifi/iwl-nvm-parse.h"
#include "third_party/iwlwifi/mvm/mvm.h"
} // extern "C"
#include "third_party/iwlwifi/platform/mvm-mlme.h"
#include "third_party/iwlwifi/platform/scoped_utils.h"
#include "third_party/iwlwifi/test/mock-trans.h"
#include "third_party/iwlwifi/test/single-ap-test.h"
#include "third_party/iwlwifi/test/wlan-pkt-builder.h"
namespace wlan::testing {
namespace {
constexpr size_t kListenInterval = 100;
constexpr uint8_t kInvalidBandIdFillByte = 0xa5;
constexpr wlan_info_band_t kInvalidBandId = 0xa5a5a5a5;
constexpr zx_handle_t kDummyMlmeChannel = 73939133; // An arbitrary value not ZX_HANDLE_INVALID
using recv_cb_t = mock_function::MockFunction<void, void*, const wlan_rx_packet_t*>;
// Short-cut to access the iwl_cfg80211_rates[] structure and convert it to 802.11 rate.
//
// Args:
// index: the index of iwl_cfg80211_rates[].
//
// Returns:
// the 802.11 rate.
//
static unsigned expected_rate(size_t index) {
return cfg_rates_to_80211(iwl_cfg80211_rates[index]);
}
// The wrapper used by wlan_softmac_ifc_t.recv() to call mock-up.
void recv_wrapper(void* cookie, const wlan_rx_packet_t* packet) {
auto recv = reinterpret_cast<recv_cb_t*>(cookie);
recv->Call(cookie, packet);
}
class WlanSoftmacDeviceTest : public SingleApTest {
public:
WlanSoftmacDeviceTest() {
mvmvif_.reset(reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif))));
mvmvif_->mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
mvmvif_->mlme_channel = kDummyMlmeChannel;
mvmvif_->mac_role = WLAN_MAC_ROLE_CLIENT;
mvmvif_->bss_conf = {.beacon_int = kListenInterval};
device_ = std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(nullptr, sim_trans_.iwl_trans(),
0, mvmvif_.get());
}
~WlanSoftmacDeviceTest() override {}
protected:
wlan::iwlwifi::unique_free_ptr<struct iwl_mvm_vif> mvmvif_;
std::unique_ptr<::wlan::iwlwifi::WlanSoftmacDevice> device_;
};
//////////////////////////////////// Helper Functions /////////////////////////////////////////////
TEST_F(WlanSoftmacDeviceTest, ComposeBandList) {
struct iwl_nvm_data nvm_data;
wlan_info_band_t bands[WLAN_INFO_BAND_COUNT];
// nothing enabled
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
EXPECT_EQ(0, compose_band_list(&nvm_data, bands));
EXPECT_EQ(kInvalidBandId, bands[0]);
EXPECT_EQ(kInvalidBandId, bands[1]);
// 2.4GHz only
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_24ghz_enable = true;
EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, bands[0]);
EXPECT_EQ(kInvalidBandId, bands[1]);
// 5GHz only
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_52ghz_enable = true;
EXPECT_EQ(1, compose_band_list(&nvm_data, bands));
EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, bands[0]);
EXPECT_EQ(kInvalidBandId, bands[1]);
// both bands enabled
memset(&nvm_data, 0, sizeof(nvm_data));
memset(bands, kInvalidBandIdFillByte, sizeof(bands));
nvm_data.sku_cap_band_24ghz_enable = true;
nvm_data.sku_cap_band_52ghz_enable = true;
EXPECT_EQ(2, compose_band_list(&nvm_data, bands));
EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, bands[0]);
EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, bands[1]);
}
TEST_F(WlanSoftmacDeviceTest, FillBandInfos) {
// The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
WLAN_INFO_BAND_TWO_GHZ,
WLAN_INFO_BAND_FIVE_GHZ,
};
wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, std::size(bands),
band_infos);
// 2.4Ghz
wlan_info_band_info_t* exp_band_info = &band_infos[0];
EXPECT_EQ(WLAN_INFO_BAND_TWO_GHZ, exp_band_info->band);
EXPECT_EQ(true, exp_band_info->ht_supported);
EXPECT_EQ(expected_rate(0), exp_band_info->rates[0]); // 1Mbps
EXPECT_EQ(expected_rate(11), exp_band_info->rates[11]); // 54Mbps
EXPECT_EQ(2407, exp_band_info->supported_channels.base_freq);
EXPECT_EQ(1, exp_band_info->supported_channels.channels[0]);
EXPECT_EQ(13, exp_band_info->supported_channels.channels[12]);
// 5GHz
exp_band_info = &band_infos[1];
EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, exp_band_info->band);
EXPECT_EQ(true, exp_band_info->ht_supported);
EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]); // 6Mbps
EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]); // 54Mbps
EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
}
TEST_F(WlanSoftmacDeviceTest, FillBandInfosOnly5GHz) {
// The default 'nvm_data' is loaded from test/sim-default-nvm.cc.
wlan_info_band_t bands[WLAN_INFO_BAND_COUNT] = {
WLAN_INFO_BAND_FIVE_GHZ,
0,
};
wlan_info_band_info_t band_infos[WLAN_INFO_BAND_COUNT] = {};
fill_band_infos(iwl_trans_get_mvm(sim_trans_.iwl_trans())->nvm_data, bands, 1, band_infos);
// 5GHz
wlan_info_band_info_t* exp_band_info = &band_infos[0];
EXPECT_EQ(WLAN_INFO_BAND_FIVE_GHZ, exp_band_info->band);
EXPECT_EQ(true, exp_band_info->ht_supported);
EXPECT_EQ(expected_rate(4), exp_band_info->rates[0]); // 6Mbps
EXPECT_EQ(expected_rate(11), exp_band_info->rates[7]); // 54Mbps
EXPECT_EQ(5000, exp_band_info->supported_channels.base_freq);
EXPECT_EQ(36, exp_band_info->supported_channels.channels[0]);
EXPECT_EQ(165, exp_band_info->supported_channels.channels[24]);
// index 1 should be empty.
exp_band_info = &band_infos[1];
EXPECT_EQ(false, exp_band_info->ht_supported);
EXPECT_EQ(0x00, exp_band_info->rates[0]);
EXPECT_EQ(0x00, exp_band_info->rates[7]);
EXPECT_EQ(0, exp_band_info->supported_channels.channels[0]);
}
TEST_F(WlanSoftmacDeviceTest, Query) {
// Test input null pointers
uint32_t options = 0;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQuery(options, nullptr));
wlan_softmac_info_t info = {};
ASSERT_EQ(ZX_OK, device_->WlanSoftmacQuery(options, &info));
EXPECT_EQ(WLAN_MAC_ROLE_CLIENT, info.mac_role);
//
// The below code assumes the test/sim-default-nvm.cc contains 2 bands.
//
// .bands[0]: WLAN_INFO_BAND_TWO_GHZ
// .bands[1]: WLAN_INFO_BAND_FIVE_GHZ
//
ASSERT_EQ(2, info.bands_count);
EXPECT_EQ(expected_rate(0), info.bands[0].rates[0]); // 1 Mbps
EXPECT_EQ(expected_rate(7), info.bands[0].rates[7]); // 18 Mbps
EXPECT_EQ(expected_rate(11), info.bands[0].rates[11]); // 54 Mbps
EXPECT_EQ(expected_rate(4), info.bands[1].rates[0]); // 6 Mbps
EXPECT_EQ(165, info.bands[1].supported_channels.channels[24]);
}
TEST_F(WlanSoftmacDeviceTest, MacStart) {
// Test input null pointers
wlan_softmac_ifc_protocol_ops_t proto_ops = {
.recv = recv_wrapper,
};
wlan_softmac_ifc_protocol_t ifc = {.ops = &proto_ops};
zx::channel mlme_channel;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacStart(nullptr, &mlme_channel));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacStart(&ifc, nullptr));
// Test callback function
recv_cb_t mock_recv; // To mock up the wlan_softmac_ifc_t.recv().
mlme_channel = zx::channel(static_cast<zx_handle_t>(0xF000));
ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc, &mlme_channel));
// Expect the above line would copy the 'ifc'. Then set expectation below and fire test.
mock_recv.ExpectCall(&mock_recv, nullptr);
mvmvif_->ifc.ops->recv(&mock_recv, nullptr);
mock_recv.VerifyAndClear();
}
TEST_F(WlanSoftmacDeviceTest, MacStartSmeChannel) {
// The normal case. A channel will be transferred to MLME.
constexpr zx_handle_t kChannelOne = static_cast<zx_handle_t>(0xF001);
constexpr zx_handle_t kChannelTwo = static_cast<zx_handle_t>(0xF002);
mvmvif_->mlme_channel = kChannelOne;
wlan_softmac_ifc_protocol_ops_t proto_ops = {
.recv = recv_wrapper,
};
wlan_softmac_ifc_protocol_t ifc = {.ops = &proto_ops};
zx::channel mlme_channel(kChannelTwo);
ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc, &mlme_channel));
ASSERT_EQ(mlme_channel.get(), kChannelOne); // The channel handle is returned.
ASSERT_EQ(mvmvif_->mlme_channel, ZX_HANDLE_INVALID); // Driver no longer holds the ownership.
// Since the driver no longer owns the handle, the start should fail.
ASSERT_EQ(ZX_ERR_ALREADY_BOUND, device_->WlanSoftmacStart(&ifc, &mlme_channel));
}
TEST_F(WlanSoftmacDeviceTest, Release) {
// Create a channel. Let this test case holds one end while driver holds the other end.
char dummy[1];
zx_handle_t case_end;
ASSERT_EQ(zx_channel_create(0 /* option */, &case_end, &mvmvif_->mlme_channel), ZX_OK);
ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0), ZX_OK);
// Call release and the sme channel should be closed so that we will get a peer-close error while
// trying to write any data to it.
mvmvif_.release();
device_.release()->DdkRelease();
ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0),
ZX_ERR_PEER_CLOSED);
}
// The class for WLAN device MAC testing.
//
class MacInterfaceTest : public WlanSoftmacDeviceTest, public MockTrans {
public:
MacInterfaceTest() : ifc_{ .ops = &proto_ops_, } , proto_ops_{ .recv = recv_wrapper, } {
zx_handle_t wlanphy_impl_channel = mvmvif_->mlme_channel;
zx::channel mlme_channel;
ASSERT_EQ(ZX_OK, device_->WlanSoftmacStart(&ifc_, &mlme_channel));
ASSERT_EQ(wlanphy_impl_channel, mlme_channel);
// Add the interface to MVM instance.
mvmvif_->mvm->mvmvif[0] = mvmvif_.get();
}
~MacInterfaceTest() {
VerifyExpectation(); // Ensure all expectations had been met.
// Restore the original callback for other test cases not using the mock.
ResetSendCmdFunc();
// Stop the MAC to free resources we allocated.
// This must be called after we verify the expected commands and restore the mock command
// callback so that the stop command doesn't mess up the test case expectation.
device_->WlanSoftmacStop();
VerifyStaHasBeenRemoved();
}
// Used in MockCommand constructor to indicate if the command needs to be either
//
// - returned immediately (with a status code), or
// - passed to the sim_mvm.c.
//
enum SimMvmBehavior {
kSimMvmReturnWithStatus,
kSimMvmBypassToSimMvm,
};
// A flexible mock-up of firmware command for testing code. Testing code can decide to either call
// the simulated firmware or return the status code immediately.
//
// cmd_id: the command ID. Sometimes composed with WIDE_ID() macro.
// behavior: determine what this mockup command is to do.
// status: the status code to return when behavior is 'kSimMvmReturnWithStatus'.
//
class MockCommand {
public:
MockCommand(uint32_t cmd_id, SimMvmBehavior behavior, zx_status_t status)
: cmd_id_(cmd_id), behavior_(behavior), status_(status) {}
MockCommand(uint32_t cmd_id) : MockCommand(cmd_id, kSimMvmBypassToSimMvm, ZX_OK) {}
~MockCommand() {}
uint32_t cmd_id_;
SimMvmBehavior behavior_;
zx_status_t status_;
};
typedef std::list<MockCommand> expected_cmd_id_list;
typedef zx_status_t (*fp_send_cmd)(struct iwl_trans* trans, struct iwl_host_cmd* cmd);
// Public for MockSendCmd().
expected_cmd_id_list expected_cmd_ids;
fp_send_cmd original_send_cmd;
protected:
zx_status_t SetChannel(const wlan_channel_t* channel) {
uint32_t option = 0;
return device_->WlanSoftmacSetChannel(option, channel);
}
zx_status_t ConfigureBss(const bss_config_t* config) {
uint32_t option = 0;
return device_->WlanSoftmacConfigureBss(option, config);
}
zx_status_t ConfigureAssoc(const wlan_assoc_ctx_t* config) {
uint32_t option = 0;
return device_->WlanSoftmacConfigureAssoc(option, config);
}
zx_status_t ClearAssoc() {
uint32_t option = 0;
uint8_t
peer_addr[::fuchsia_wlan_ieee80211::wire::kMacAddrLen]; // Not used since all info were
// saved in mvmvif_sta_ already.
return device_->WlanSoftmacClearAssoc(option, peer_addr);
}
zx_status_t SetKey(const wlan_key_config_t* key_config) {
uint32_t option = 0;
IWL_INFO(nullptr, "Calling set_key");
return device_->WlanSoftmacSetKey(option, key_config);
}
// The following functions are for mocking up the firmware commands.
//
// The mock function will return the special error ZX_ERR_INTERNAL when the expectation
// is not expected.
// Set the expected commands sending to the firmware.
//
// Args:
// cmd_ids: list of expected commands. Will be matched in order.
//
void ExpectSendCmd(const expected_cmd_id_list& cmd_ids) {
expected_cmd_ids = cmd_ids;
// Re-define the 'dev' field in the 'struct iwl_trans' to a test instance of this class.
sim_trans_.iwl_trans()->dev = reinterpret_cast<struct device*>(this);
// Setup the mock function for send command.
original_send_cmd = sim_trans_.iwl_trans()->ops->send_cmd;
sim_trans_.iwl_trans()->ops->send_cmd = MockSendCmd;
}
// Reset the send command function to the original one, so that the test case would stop checking
// commands one by one.
void ResetSendCmdFunc() {
if (original_send_cmd) {
IWL_INFO(nullptr, "Reseting send_cmd.");
sim_trans_.iwl_trans()->ops->send_cmd = original_send_cmd;
} else {
IWL_WARN(nullptr, "No original send_cmd found.");
}
}
static zx_status_t MockSendCmd(struct iwl_trans* trans, struct iwl_host_cmd* cmd) {
MacInterfaceTest* this_ = reinterpret_cast<MacInterfaceTest*>(trans->dev);
// remove the first one and match.
expected_cmd_id_list& expected = this_->expected_cmd_ids;
ZX_ASSERT_MSG(!expected.empty(),
"A command (0x%04x) is going to send, but no command is expected.\n", cmd->id);
// check the command ID.
auto exp = expected.front();
ZX_ASSERT_MSG(exp.cmd_id_ == cmd->id,
"The command doesn't match! Expect: 0x%04x, actual: 0x%04x.\n", exp.cmd_id_,
cmd->id);
expected.pop_front();
if (exp.behavior_ == kSimMvmBypassToSimMvm) {
return this_->original_send_cmd(trans, cmd);
} else {
return exp.status_;
}
}
void VerifyExpectation() {
for (expected_cmd_id_list::iterator it = expected_cmd_ids.begin(); it != expected_cmd_ids.end();
it++) {
printf(" ==> 0x%04x\n", it->cmd_id_);
}
ASSERT_TRUE(expected_cmd_ids.empty(), "The expected command set is not empty.");
mock_tx_.VerifyAndClear();
}
void VerifyStaHasBeenRemoved() {
auto mvm = mvmvif_->mvm;
for (size_t i = 0; i < std::size(mvm->fw_id_to_mac_id); i++) {
struct iwl_mvm_sta* mvm_sta = mvm->fw_id_to_mac_id[i];
ASSERT_EQ(nullptr, mvm_sta);
}
ASSERT_EQ(0, mvm->vif_count);
}
// Mock function for Tx.
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(MacInterfaceTest, 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);
}
wlan_softmac_ifc_protocol_t ifc_;
wlan_softmac_ifc_protocol_ops_t proto_ops_;
static constexpr bss_config_t kBssConfig = {
.bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
.bss_type = BSS_TYPE_INFRASTRUCTURE,
.remote = true,
};
// Assoc context without HT related data.
static constexpr wlan_assoc_ctx_t kAssocCtx = {
.listen_interval = kListenInterval,
};
// Assoc context with HT related data. (The values below comes from real data in manual test)
static constexpr wlan_assoc_ctx_t kHtAssocCtx = {
.listen_interval = kListenInterval,
.channel =
{
.primary = 157,
.cbw = CHANNEL_BANDWIDTH_CBW80,
},
.rates_cnt = 8,
.rates =
{
140,
18,
152,
36,
176,
72,
96,
108,
},
.has_ht_cap = true,
.ht_cap =
{
.supported_mcs_set =
{
.bytes =
{
255,
255,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
},
},
},
};
};
// Test the set_channel().
//
TEST_F(MacInterfaceTest, TestSetChannel) {
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
}));
mvmvif_->csa_bcn_pending = true; // Expect to be clear because this is client role.
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
EXPECT_EQ(false, mvmvif_->csa_bcn_pending);
}
// Test call set_channel() multiple times.
//
TEST_F(MacInterfaceTest, TestMultipleSetChannel) {
ExpectSendCmd(expected_cmd_id_list({
// for the first SetChannel()
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
// for the second SetChannel()
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)), // for remove_chanctx
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
MockCommand(WIDE_ID(LONG_GROUP, BINDING_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_PM_POWER_TABLE)),
}));
for (size_t i = 0; i < 2; i++) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
}
}
// Test the unsupported MAC role.
//
TEST_F(MacInterfaceTest, TestSetChannelWithUnsupportedRole) {
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for add_chanctx
MockCommand(WIDE_ID(LONG_GROUP, PHY_CONTEXT_CMD)), // for change_chanctx
}));
mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, SetChannel(&kChannel));
}
// Tests calling SetChannel()/ConfigureBss() again without ConfigureAssoc()/ClearAssoc()
TEST_F(MacInterfaceTest, DuplicateSetChannel) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
struct iwl_mvm_phy_ctxt* phy_ctxt = mvmvif_->phy_ctxt;
ASSERT_NE(nullptr, phy_ctxt);
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
// Call SetChannel() again. This should return the same phy context but ConfigureBss()
// should setup a new STA.
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
struct iwl_mvm_phy_ctxt* new_phy_ctxt = mvmvif_->phy_ctxt;
ASSERT_NE(nullptr, new_phy_ctxt);
ASSERT_EQ(phy_ctxt, new_phy_ctxt);
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
struct iwl_mvm_sta* new_mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
// Now Associate and disassociate - this should release and reset the phy ctxt.
ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
ASSERT_EQ(IWL_STA_AUTHORIZED, new_mvm_sta->sta_state);
ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
}
// Test ConfigureBss()
//
TEST_F(MacInterfaceTest, TestConfigureBss) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, SCD_QUEUE_CFG)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
}));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
// Ensure the BSSID was copied into mvmvif
ASSERT_EQ(memcmp(mvmvif_->bss_conf.bssid, kBssConfig.bssid, ETH_ALEN), 0);
ASSERT_EQ(memcmp(mvmvif_->bssid, kBssConfig.bssid, ETH_ALEN), 0);
}
// Test duplicate BSS config.
//
TEST_F(MacInterfaceTest, DuplicateConfigureBss) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
ASSERT_EQ(ZX_ERR_ALREADY_BOUND, ConfigureBss(&kBssConfig));
}
// Test unsupported bss_type.
//
TEST_F(MacInterfaceTest, UnsupportedBssType) {
static constexpr bss_config_t kUnsupportedBssConfig = {
.bssid = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06},
.bss_type = BSS_TYPE_INDEPENDENT,
.remote = true,
};
ASSERT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kUnsupportedBssConfig));
}
// Test failed ADD_STA command.
//
TEST_F(MacInterfaceTest, TestFailedAddSta) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD), kSimMvmReturnWithStatus,
ZX_ERR_BUFFER_TOO_SMALL /* an arbitrary error */),
}));
ASSERT_EQ(ZX_ERR_BUFFER_TOO_SMALL, ConfigureBss(&kBssConfig));
}
// Test exception handling in driver.
//
TEST_F(MacInterfaceTest, TestExceptionHandling) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
// Test the beacon interval checking.
mvmvif_->bss_conf.beacon_int = 0;
EXPECT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kBssConfig));
mvmvif_->bss_conf.beacon_int = 16; // which just passes the check.
// Test the phy_ctxt checking.
auto backup_phy_ctxt = mvmvif_->phy_ctxt;
mvmvif_->phy_ctxt = nullptr;
EXPECT_EQ(ZX_ERR_BAD_STATE, ConfigureBss(&kBssConfig));
mvmvif_->phy_ctxt = backup_phy_ctxt;
// Test the case we run out of slots for STA.
std::list<std::unique_ptr<::wlan::iwlwifi::WlanSoftmacDevice>> devices;
for (size_t i = 0; i < IWL_MVM_STATION_COUNT; i++) {
// Pretend the STA is not assigned so that we can add it again.
devices.emplace_back(std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(
nullptr, sim_trans_.iwl_trans(), 0, mvmvif_.get()));
std::swap(device_, devices.back());
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
}
// However, the last one should fail because we run out of all slots in fw_id_to_mac_id[].
devices.emplace_back(std::make_unique<::wlan::iwlwifi::WlanSoftmacDevice>(
nullptr, sim_trans_.iwl_trans(), 0, mvmvif_.get()));
std::swap(device_, devices.back());
ASSERT_EQ(ZX_ERR_NO_RESOURCES, ConfigureBss(&kBssConfig));
}
// The test is used to test the typical procedure to connect to an open network.
//
TEST_F(MacInterfaceTest, AssociateToOpenNetwork) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
}
// Back to back calls of ClearAssoc().
TEST_F(MacInterfaceTest, ClearAssocAfterClearAssoc) {
ASSERT_NE(ZX_OK, ClearAssoc());
ASSERT_NE(ZX_OK, ClearAssoc());
}
// ClearAssoc() should cleanup when called without Assoc
TEST_F(MacInterfaceTest, ClearAssocAfterNoAssoc) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Call ClearAssoc() again to check if it is handled correctly.
ASSERT_NE(ZX_OK, ClearAssoc());
}
// ClearAssoc() should cleanup when called after a failed Assoc
TEST_F(MacInterfaceTest, ClearAssocAfterFailedAssoc) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
// Fail the association by forcing some relevant internal state.
auto orig = mvmvif_->uploaded;
mvmvif_->uploaded = false;
ASSERT_EQ(ZX_ERR_IO, ConfigureAssoc(&kAssocCtx));
mvmvif_->uploaded = orig;
// ClearAssoc will clean up the failed association.
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Call ClearAssoc() again to check if it is handled correctly.
ASSERT_NE(ZX_OK, ClearAssoc());
}
// This test case is to verify ConfigureAssoc() with HT wlan_assoc_ctx_t input can successfully
// trigger LQ_CMD with correct data.
TEST_F(MacInterfaceTest, AssocWithHtConfig) {
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
ExpectSendCmd(expected_cmd_id_list({
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, LQ_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, MAC_CONTEXT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
MockCommand(WIDE_ID(LONG_GROUP, MCAST_FILTER_CMD)),
}));
// Extract LQ_CMD data.
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
struct iwl_lq_cmd* lq_cmd = &mvm_sta->lq_sta.rs_drv.lq;
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ConfigureAssoc(&kHtAssocCtx));
// Verify the values in LQ_CMD API structure.
EXPECT_EQ(lq_cmd->sta_id, 0);
EXPECT_EQ(lq_cmd->reduced_tpc, 0);
EXPECT_EQ(lq_cmd->flags, 0);
EXPECT_EQ(lq_cmd->mimo_delim, 0);
EXPECT_EQ(lq_cmd->single_stream_ant_msk, 1);
EXPECT_EQ(lq_cmd->dual_stream_ant_msk, 3);
EXPECT_EQ(lq_cmd->initial_rate_index[0], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[1], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[2], 0);
EXPECT_EQ(lq_cmd->initial_rate_index[3], 0);
EXPECT_EQ(lq_cmd->agg_time_limit, 0x0fa0);
EXPECT_EQ(lq_cmd->agg_disable_start_th, 3);
EXPECT_EQ(lq_cmd->agg_frame_cnt_limit, 1);
EXPECT_EQ(lq_cmd->reserved2, 0);
// Verify rate_n_flags in the table.
EXPECT_EQ(lq_cmd->rs_table[0], 0x4103);
// The value of RS_MNG_RETRY_TABLE_INITIAL_RATE_NUM is 3.
EXPECT_EQ(lq_cmd->rs_table[3], 0x4102);
EXPECT_EQ(lq_cmd->ss_params, 0);
// Stop checking following commands one by one.
ResetSendCmdFunc();
// Clean up the association states.
ASSERT_EQ(ZX_OK, ClearAssoc());
}
// Check to ensure keys are set during assoc and deleted after disassoc
// for now use open network
TEST_F(MacInterfaceTest, SetKeysTest) {
constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
struct iwl_mvm_sta* mvm_sta = mvmvif_->mvm->fw_id_to_mac_id[mvmvif_->ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_->mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
ASSERT_EQ(IWL_STA_AUTHORIZED, mvm_sta->sta_state);
ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_->bss_conf.listen_interval);
char keybuf[sizeof(wlan_key_config_t) + 16] = {};
wlan_key_config_t* key_config = new (keybuf) wlan_key_config_t();
// Set an arbitrary pairwise key.
key_config->cipher_type = 4;
key_config->key_type = 1;
key_config->key_idx = 0;
key_config->key_len = 16;
memcpy(key_config->cipher_oui, kIeeeOui, 3);
ASSERT_EQ(ZX_OK, SetKey(key_config));
// Expect bit 0 to be set.
ASSERT_EQ(*mvm->fw_key_table, 0x1);
// Set an arbitrary group key.
key_config->key_type = 2;
key_config->key_idx = 1;
ASSERT_EQ(ZX_OK, SetKey(key_config));
// Expect bit 1 to be set as well.
ASSERT_EQ(*mvm->fw_key_table, 0x3);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_->phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_->ap_sta_id);
ASSERT_EQ(list_length(&mvm->time_event_list), 0);
// Both the keys should have been deleted.
ASSERT_EQ(*mvm->fw_key_table, 0x0);
}
// Check that we can sucessfully set some key configurations required for supported functionality.
TEST_F(MacInterfaceTest, SetKeysSupportConfigs) {
constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC};
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
ASSERT_EQ(ZX_OK, ConfigureBss(&kBssConfig));
ASSERT_EQ(ZX_OK, ConfigureAssoc(&kAssocCtx));
ASSERT_EQ(true, mvmvif_->bss_conf.assoc);
char keybuf[sizeof(wlan_key_config_t) + 16] = {};
wlan_key_config_t* key_config = new (keybuf) wlan_key_config_t();
key_config->key_len = 16;
memcpy(key_config->cipher_oui, kIeeeOui, 3);
// Default cipher configuration for WPA2/3 PTK. This is data frame protection, required for
// WPA2/3.
key_config->cipher_type = fuchsia_wlan_ieee80211_CipherSuiteType_CCMP_128;
key_config->key_type = WLAN_KEY_TYPE_PAIRWISE;
key_config->key_idx = 0;
ASSERT_EQ(ZX_OK, SetKey(key_config));
// Default cipher configuration for WPA2/3 IGTK. This is management frame protection, optional
// for WPA2 and required for WPA3.
key_config->cipher_type = fuchsia_wlan_ieee80211_CipherSuiteType_BIP_CMAC_128;
key_config->key_type = WLAN_KEY_TYPE_IGTK;
key_config->key_idx = 1;
ASSERT_EQ(ZX_OK, SetKey(key_config));
ASSERT_EQ(ZX_OK, ClearAssoc());
}
TEST_F(MacInterfaceTest, TxPktTooLong) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
BIND_TEST(sim_trans_.iwl_trans());
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
wlan_pkt->wlan_pkt()->mac_frame_size = WLAN_MSDU_MAX_LEN + 1;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
unbindTx();
}
TEST_F(MacInterfaceTest, TxPktNotSupportedRole) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
BIND_TEST(sim_trans_.iwl_trans());
// Set to an unsupported role.
mvmvif_->mac_role = WLAN_MAC_ROLE_AP;
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
unbindTx();
}
// To test if a packet can be sent out.
TEST_F(MacInterfaceTest, TxPkt) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
BIND_TEST(sim_trans_.iwl_trans());
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
mock_tx_.ExpectCall(ZX_OK, wlan_pkt->len(), WIDE_ID(0, TX_CMD), IWL_MVM_DQA_MIN_MGMT_QUEUE);
ASSERT_EQ(ZX_OK, device_->WlanSoftmacQueueTx(0, wlan_pkt->wlan_pkt()));
unbindTx();
}
} // namespace
} // namespace wlan::testing