blob: f2cbffa8cb7bc1f77328a109aa954153dc7458da [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.
// To test PHY and MAC device callback functions.
#include <fuchsia/wlan/common/cpp/banjo.h>
#include <fuchsia/wlan/ieee80211/c/banjo.h>
#include <fuchsia/wlan/internal/cpp/banjo.h>
#include <lib/mock-function/mock-function.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <list>
#include <zxtest/zxtest.h>
extern "C" {
#include "src/iwlwifi/mvm/mvm.h"
}
#include "src/iwlwifi/platform/ieee80211.h"
#include "src/iwlwifi/platform/mvm-mlme.h"
#include "src/iwlwifi/platform/wlanphy-impl-device.h"
#include "src/iwlwifi/test/mock-trans.h"
#include "src/iwlwifi/test/single-ap-test.h"
#include "src/iwlwifi/test/wlan-pkt-builder.h"
namespace wlan::testing {
namespace {
static constexpr size_t kListenInterval = 100;
typedef mock_function::MockFunction<void, void*, uint32_t, const void*, size_t,
const wlan_rx_info_t*>
recv_cb_t;
// The wrapper used by wlanmac_ifc_t.recv() to call mock-up.
void recv_wrapper(void* cookie, uint32_t flags, const uint8_t* data, size_t length,
const wlan_rx_info_t* info) {
auto recv = reinterpret_cast<recv_cb_t*>(cookie);
recv->Call(cookie, flags, data, length, info);
}
class WlanDeviceTest : public SingleApTest {
public:
WlanDeviceTest()
: mvmvif_sta_{
.mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans()),
.mac_role = WLAN_INFO_MAC_ROLE_CLIENT,
.bss_conf =
{
.beacon_int = kListenInterval,
},
} {
device_ = sim_trans_.sim_device();
}
~WlanDeviceTest() {}
protected:
static constexpr zx_handle_t mlme_channel_ =
73939133; // An arbitrary value not ZX_HANDLE_INVALID
static constexpr uint8_t kInvalidBandIdFillByte = 0xa5;
static constexpr wlan_info_band_t kInvalidBandId = 0xa5a5a5a5;
struct iwl_mvm_vif mvmvif_sta_; // The mvm_vif settings for station role.
wlan::iwlwifi::WlanphyImplDevice* device_;
};
//////////////////////////////////// Helper Functions /////////////////////////////////////////////
TEST_F(WlanDeviceTest, 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_2GHZ, 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_5GHZ, 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_2GHZ, bands[0]);
EXPECT_EQ(WLAN_INFO_BAND_5GHZ, bands[1]);
}
// 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]);
}
TEST_F(WlanDeviceTest, 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_2GHZ,
WLAN_INFO_BAND_5GHZ,
};
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, ARRAY_SIZE(bands),
band_infos);
// 2.4Ghz
wlan_info_band_info_t* exp_band_info = &band_infos[0];
EXPECT_EQ(WLAN_INFO_BAND_2GHZ, 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_5GHZ, 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(WlanDeviceTest, 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_5GHZ,
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_5GHZ, 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]);
}
///////////////////////////////////// MAC //////////////////////////////////////////////
TEST_F(WlanDeviceTest, MacQuery) {
// Test input null pointers
uint32_t options = 0;
void* whatever = &options;
ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.query(nullptr, options, nullptr));
ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.query(whatever, options, nullptr));
ASSERT_EQ(ZX_ERR_INVALID_ARGS,
wlanmac_ops.query(nullptr, options, reinterpret_cast<wlanmac_info*>(whatever)));
wlanmac_info_t info = {};
ASSERT_EQ(ZX_OK, wlanmac_ops.query(&mvmvif_sta_, options, &info));
EXPECT_EQ(WLAN_INFO_MAC_ROLE_CLIENT, info.mac_role);
//
// The below code assumes the test/sim-default-nvm.cc contains 2 bands.
//
// .bands[0]: WLAN_INFO_BAND_2GHZ
// .bands[1]: WLAN_INFO_BAND_5GHZ
//
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(WlanDeviceTest, MacStart) {
// Test input null pointers
wlanmac_ifc_protocol_ops_t proto_ops = {
.recv = recv_wrapper,
};
wlanmac_ifc_protocol_t ifc = {.ops = &proto_ops};
zx_handle_t mlme_channel;
ASSERT_EQ(wlanmac_ops.start(nullptr, &ifc, &mlme_channel), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, nullptr, &mlme_channel), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, nullptr), ZX_ERR_INVALID_ARGS);
// Test callback function
recv_cb_t mock_recv; // To mock up the wlanmac_ifc_t.recv().
mvmvif_sta_.mlme_channel = mlme_channel_;
ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_OK);
// Expect the above line would copy the 'ifc'. Then set expectation below and fire test.
mock_recv.ExpectCall(&mock_recv, 0, nullptr, 0, nullptr);
mvmvif_sta_.ifc.ops->recv(&mock_recv, 0, nullptr, 0, nullptr);
mock_recv.VerifyAndClear();
}
TEST_F(WlanDeviceTest, MacStartSmeChannel) {
// The normal case. A channel will be transferred to MLME.
constexpr zx_handle_t from_devmgr = mlme_channel_;
mvmvif_sta_.mlme_channel = from_devmgr;
wlanmac_ifc_protocol_ops_t proto_ops = {
.recv = recv_wrapper,
};
wlanmac_ifc_protocol_t ifc = {.ops = &proto_ops};
zx_handle_t mlme_channel;
ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_OK);
ASSERT_EQ(mlme_channel, from_devmgr); // The channel handle is returned.
ASSERT_EQ(mvmvif_sta_.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(wlanmac_ops.start(&mvmvif_sta_, &ifc, &mlme_channel), ZX_ERR_ALREADY_BOUND);
}
TEST_F(WlanDeviceTest, MacRelease) {
// Allocate an instance so that we can free that in mac_release().
struct iwl_mvm_vif* mvmvif =
reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
// 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.
device_mac_ops.release(mvmvif);
ASSERT_EQ(zx_channel_write(case_end, 0 /* option */, dummy, sizeof(dummy), nullptr, 0),
ZX_ERR_PEER_CLOSED);
}
///////////////////////////////////// PHY //////////////////////////////////////////////
TEST_F(WlanDeviceTest, PhyQuery) {
wlanphy_impl_info_t info = {};
// Test input null pointers
ASSERT_EQ(ZX_ERR_INVALID_ARGS, device_->WlanphyImplQuery(nullptr));
ASSERT_EQ(ZX_OK, device_->WlanphyImplQuery(&info));
// Normal case
ASSERT_EQ(ZX_OK, device_->WlanphyImplQuery(&info));
EXPECT_EQ(WLAN_INFO_MAC_ROLE_CLIENT, info.supported_mac_roles);
}
TEST_F(WlanDeviceTest, PhyPartialCreateCleanup) {
wlanphy_impl_create_iface_req_t req = {
.role = WLAN_INFO_MAC_ROLE_CLIENT,
.mlme_channel = mlme_channel_,
};
uint16_t iface_id;
struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
// Test input null pointers
ASSERT_OK(phy_create_iface(iwl_trans, &req, &iface_id));
// Ensure mvmvif got created and indexed.
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
ASSERT_NOT_NULL(mvm->mvmvif[iface_id]);
// Ensure partial create failure removes it from the index.
phy_create_iface_undo(iwl_trans, iface_id);
ASSERT_NULL(mvm->mvmvif[iface_id]);
}
TEST_F(WlanDeviceTest, PhyCreateDestroySingleInterface) {
wlanphy_impl_create_iface_req_t req = {
.role = WLAN_INFO_MAC_ROLE_CLIENT,
.mlme_channel = mlme_channel_,
};
uint16_t iface_id;
// Test input null pointers
ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, &iface_id), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, nullptr), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(device_->WlanphyImplCreateIface(nullptr, nullptr), ZX_ERR_INVALID_ARGS);
// Test invalid inputs
ASSERT_EQ(device_->WlanphyImplDestroyIface(MAX_NUM_MVMVIF), ZX_ERR_INVALID_ARGS);
ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND); // hasn't been added yet.
// To verify the internal state of MVM driver.
struct iwl_mvm* mvm = iwl_trans_get_mvm(sim_trans_.iwl_trans());
// Add interface
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 0); // the first interface should have id 0.
struct iwl_mvm_vif* mvmvif = mvm->mvmvif[iface_id];
ASSERT_NE(mvmvif, nullptr);
ASSERT_EQ(mvmvif->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
// Count includes phy device in addition to the newly created mac device.
ASSERT_EQ(fake_parent_->descendant_count(), 2);
device_->zxdev()->GetLatestChild()->InitOp();
// Remove interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[iface_id], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 1);
}
TEST_F(WlanDeviceTest, PhyCreateDestroyMultipleInterfaces) {
wlanphy_impl_create_iface_req_t req = {
.role = WLAN_INFO_MAC_ROLE_CLIENT,
.mlme_channel = mlme_channel_,
};
uint16_t iface_id;
struct iwl_trans* iwl_trans = sim_trans_.iwl_trans();
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans); // To verify the internal state of MVM driver
// Add 1st interface
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 0);
ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
ASSERT_EQ(fake_parent_->descendant_count(), 2);
device_->zxdev()->GetLatestChild()->InitOp();
// Add 2nd interface
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 1);
ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
ASSERT_EQ(fake_parent_->descendant_count(), 3);
device_->zxdev()->GetLatestChild()->InitOp();
// Add 3rd interface
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 2);
ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
ASSERT_EQ(fake_parent_->descendant_count(), 4);
device_->zxdev()->GetLatestChild()->InitOp();
// Remove the 2nd interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[1], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 3);
// Add a new interface and it should be the 2nd one.
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 1);
ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
ASSERT_EQ(fake_parent_->descendant_count(), 4);
device_->zxdev()->GetLatestChild()->InitOp();
// Add 4th interface
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_OK);
ASSERT_EQ(iface_id, 3);
ASSERT_NE(mvm->mvmvif[iface_id], nullptr);
ASSERT_EQ(mvm->mvmvif[iface_id]->mac_role, WLAN_INFO_MAC_ROLE_CLIENT);
ASSERT_EQ(fake_parent_->descendant_count(), 5);
device_->zxdev()->GetLatestChild()->InitOp();
// Add 5th interface and it should fail
ASSERT_EQ(device_->WlanphyImplCreateIface(&req, &iface_id), ZX_ERR_NO_RESOURCES);
ASSERT_EQ(fake_parent_->descendant_count(), 5);
// Remove the 2nd interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(1), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[1], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 4);
// Remove the 3rd interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(2), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[2], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 3);
// Remove the 4th interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(3), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[3], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 2);
// Remove the 1st interface
ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_OK);
mock_ddk::ReleaseFlaggedDevices(fake_parent_.get());
ASSERT_EQ(mvm->mvmvif[0], nullptr);
ASSERT_EQ(fake_parent_->descendant_count(), 1);
// Remove the 1st interface again and it should fail.
ASSERT_EQ(device_->WlanphyImplDestroyIface(0), ZX_ERR_NOT_FOUND);
ASSERT_EQ(fake_parent_->descendant_count(), 1);
}
// The class for WLAN device MAC testing.
//
class MacInterfaceTest : public WlanDeviceTest, public MockTrans {
public:
MacInterfaceTest() : ifc_{ .ops = &proto_ops_, } , proto_ops_{ .recv = recv_wrapper, } {
mvmvif_sta_.mlme_channel = mlme_channel_;
zx_handle_t mlme_channel;
ASSERT_EQ(wlanmac_ops.start(&mvmvif_sta_, &ifc_, &mlme_channel), ZX_OK);
// Add the interface to MVM instance.
mvmvif_sta_.mvm->mvmvif[0] = &mvmvif_sta_;
}
~MacInterfaceTest() {
VerifyExpectation(); // Ensure all expectations had been met.
// Restore the original callback for other test cases not using the mock.
if (original_send_cmd) {
sim_trans_.iwl_trans()->ops->send_cmd = original_send_cmd;
}
// 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.
wlanmac_ops.stop(&mvmvif_sta_);
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 wlanmac_ops.set_channel(&mvmvif_sta_, option, channel);
}
zx_status_t ConfigureBss(const bss_config_t* config) {
uint32_t option = 0;
return wlanmac_ops.configure_bss(&mvmvif_sta_, option, config);
}
zx_status_t ConfigureAssoc(const wlan_assoc_ctx_t* config) {
uint32_t option = 0;
return wlanmac_ops.configure_assoc(&mvmvif_sta_, option, config);
}
zx_status_t ClearAssoc() {
uint32_t option = 0;
uint8_t peer_addr[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]; // Not used since all info were
// saved in mvmvif_sta_ already.
return wlanmac_ops.clear_assoc(&mvmvif_sta_, 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 wlanmac_ops.set_key(&mvmvif_sta_, 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;
}
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_sta_.mvm;
for (size_t i = 0; i < ARRAY_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);
}
wlanmac_ifc_protocol_t ifc_;
wlanmac_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,
};
static constexpr wlan_assoc_ctx_t kAssocCtx = {
.listen_interval = kListenInterval,
};
};
// 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_sta_.csa_bcn_pending = true; // Expect to be clear because this is client role.
ASSERT_EQ(ZX_OK, SetChannel(&kChannel));
EXPECT_EQ(false, mvmvif_sta_.csa_bcn_pending);
}
// 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_sta_.mac_role = WLAN_INFO_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_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
struct iwl_mvm_phy_ctxt* phy_ctxt = mvmvif_sta_.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_sta_.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_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.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_sta_.bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.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, ADD_STA)),
MockCommand(WIDE_ID(LONG_GROUP, TIME_EVENT_CMD)),
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_sta_.bss_conf.bssid, kBssConfig.bssid, ETH_ALEN), 0);
ASSERT_EQ(memcmp(mvmvif_sta_.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_EXISTS, 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_sta_.bss_conf.beacon_int = 0;
EXPECT_EQ(ZX_ERR_INVALID_ARGS, ConfigureBss(&kBssConfig));
mvmvif_sta_.bss_conf.beacon_int = 16; // which just passes the check.
// Test the phy_ctxt checking.
auto backup_phy_ctxt = mvmvif_sta_.phy_ctxt;
mvmvif_sta_.phy_ctxt = nullptr;
EXPECT_EQ(ZX_ERR_BAD_STATE, ConfigureBss(&kBssConfig));
mvmvif_sta_.phy_ctxt = backup_phy_ctxt;
// Test the case we run out of slots for STA.
//
// In the constructor of the test, mvmvif_sta_ had been added once. So we would expect the
// following (IWL_MVM_STATION_COUNT - 1) adding would be successful as well.
//
for (size_t i = 0; i < IWL_MVM_STATION_COUNT - 1; i++) {
// Pretent the STA is not assigned so that we can add it again.
mvmvif_sta_.ap_sta_id = IWL_MVM_INVALID_STA;
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[].
mvmvif_sta_.ap_sta_id = IWL_MVM_INVALID_STA;
ASSERT_EQ(ZX_OK, 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_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_sta_.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_sta_.bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.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_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_sta_.mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.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());
}
TEST_F(MacInterfaceTest, AssociateToOpenNetworkNullStation) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
// Replace the STA pointer with NULL and expect the association will fail.
auto org = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = nullptr;
ASSERT_EQ(ZX_ERR_BAD_STATE, ConfigureAssoc(&kAssocCtx));
// Expect error while disassociating a non-existing association.
ASSERT_EQ(ZX_ERR_BAD_STATE, ClearAssoc());
// We have to recover the pointer so that the MAC stop function can recycle the memory.
mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = org;
}
TEST_F(MacInterfaceTest, ClearAssocAfterFailedAssoc) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
struct iwl_mvm* mvm = mvmvif_sta_.mvm;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
// Replace the STA pointer with NULL and expect the association will fail.
auto org = mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = nullptr;
ASSERT_EQ(ZX_ERR_BAD_STATE, ConfigureAssoc(&kAssocCtx));
// Now put back the original STA pointer so ClearAssoc runs and also
// to recycle allocated memory
mvmvif_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id] = org;
ASSERT_GT(list_length(&mvm->time_event_list), 0);
// Expect error while disassociating a non-existing association.
ASSERT_EQ(ZX_OK, ClearAssoc());
ASSERT_EQ(nullptr, mvmvif_sta_.phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.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());
}
// 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_sta_.mvm->fw_id_to_mac_id[mvmvif_sta_.ap_sta_id];
ASSERT_EQ(IWL_STA_NONE, mvm_sta->sta_state);
struct iwl_mvm* mvm = mvmvif_sta_.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_sta_.bss_conf.assoc);
ASSERT_EQ(kListenInterval, mvmvif_sta_.bss_conf.listen_interval);
char keybuf[sizeof(wlan_key_config_t) + 16];
wlan_key_config_t* key_config = (wlan_key_config_t*)keybuf;
// 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((const wlan_key_config_t*)key_config));
// Expect bit 0 to be set.
ASSERT_EQ(*mvm->fw_key_table, 0x1);
// GROUP KEY
key_config->key_type = 2;
key_config->key_idx = 1;
ASSERT_EQ(ZX_OK, SetKey((const wlan_key_config_t*)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_sta_.phy_ctxt);
ASSERT_EQ(IWL_MVM_INVALID_STA, mvmvif_sta_.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);
}
TEST_F(MacInterfaceTest, TxPktNotSupportedRole) {
SetChannel(&kChannel);
ConfigureBss(&kBssConfig);
BIND_TEST(sim_trans_.iwl_trans());
// Set to an unsupported role.
mvmvif_sta_.mac_role = WLAN_INFO_MAC_ROLE_AP;
bindTx(tx_wrapper);
WlanPktBuilder builder;
std::shared_ptr<WlanPktBuilder::WlanPkt> wlan_pkt = builder.build();
ASSERT_EQ(ZX_ERR_INVALID_ARGS, wlanmac_ops.queue_tx(&mvmvif_sta_, 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, wlanmac_ops.queue_tx(&mvmvif_sta_, 0, wlan_pkt->wlan_pkt()));
unbindTx();
}
} // namespace
} // namespace wlan::testing