blob: b795cab467959d86706822854b9c7a0f8b2fcfdf [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.
// The place holder for the driver code to interact with the MLME.
//
// devmgr
// |
// v
// MLME === channel === SME
// |
// v
// +-------------------+
// | mvm-mlme.cc |
// +-------------------+
// | PHY ops | MAC ops |
// +-------------------+
// | |
// v v
// mvm/mac80211.c
//
// Note that the '*ctx' in this file may refer to:
//
// - 'struct iwl_trans*' for PHY ops.
// - 'struct iwl_mvm_vif*' for MAC ops.
//
//
// Sme_channel
//
// The steps below briefly describe how the 'mlme_channel' is used and transferred. In short,
// the goal is to let SME and MLME have a channel to communicate with each other.
//
// + After the devmgr (the device manager in wlanstack) detects a PHY device, the devmgr first
// creates an SME instance in order to handle the MAC operation later. Then the devmgr
// establishes a channel and passes one end to the SME instance.
//
// + The devmgr requests the PHY device to create a MAC interface. In the request, the other end
// of channel is passed to the driver.
//
// + The driver's phy_create_iface() gets called, and saves the 'mlme_channel' handle in the newly
// created MAC context.
//
// + Once the MAC device is added, its mac_start() will be called. Then it will transfer the
// 'mlme_channel' handle back to the MLME.
//
// + Now, both sides of channel (SME and MLME) can talk now.
//
#include "third_party/iwlwifi/platform/mvm-mlme.h"
#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h>
#include <fuchsia/hardware/wlan/softmac/c/banjo.h>
#include <fuchsia/wlan/internal/c/banjo.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <stdio.h>
#include <string.h>
#include <zircon/status.h>
#include <algorithm>
#include <iterator>
#include "third_party/iwlwifi/platform/ieee80211_include.h"
extern "C" {
#include "third_party/iwlwifi/iwl-debug.h"
#include "third_party/iwlwifi/mvm/mvm.h"
#include "third_party/iwlwifi/mvm/sta.h"
#include "third_party/iwlwifi/mvm/time-event.h"
} // extern "C"
#include "third_party/iwlwifi/platform/ieee80211_include.h"
#include "third_party/iwlwifi/platform/rcu.h"
#include "third_party/iwlwifi/platform/scoped_utils.h"
//////////////////////////////////// Helper Functions ////////////////////////////////////////////
//
// Given a NVM data structure, and return the list of bands.
//
// Returns:
// size_t: # of bands enabled in the NVM data.
// bands[]: contains the list of enabled bands.
//
size_t compose_band_list(const struct iwl_nvm_data* nvm_data,
wlan_info_band_t bands[WLAN_INFO_BAND_COUNT]) {
size_t bands_count = 0;
if (nvm_data->sku_cap_band_24ghz_enable) {
bands[bands_count++] = WLAN_INFO_BAND_TWO_GHZ;
}
if (nvm_data->sku_cap_band_52ghz_enable) {
bands[bands_count++] = WLAN_INFO_BAND_FIVE_GHZ;
}
ZX_ASSERT(bands_count <= WLAN_INFO_BAND_COUNT);
return bands_count;
}
//
// Given a NVM data, copy the band and channel info into the 'wlan_info_band_info_t' structure.
//
// - 'bands_count' is the number of bands in 'bands[]'.
// - 'band_infos[]' must have at least bands_count for this function to write.
//
void fill_band_infos(const struct iwl_nvm_data* nvm_data, const wlan_info_band_t* bands,
size_t bands_count, wlan_info_band_info_t* band_infos) {
ZX_ASSERT(bands_count <= std::size(nvm_data->bands));
for (size_t band_idx = 0; band_idx < bands_count; ++band_idx) {
wlan_info_band_t band_id = bands[band_idx];
const struct ieee80211_supported_band* sband = &nvm_data->bands[band_id]; // source
wlan_info_band_info_t* band_info = &band_infos[band_idx]; // destination
band_info->band = band_id;
band_info->ht_supported = sband->ht_cap.ht_supported;
band_info->ht_caps.ht_capability_info = sband->ht_cap.cap;
band_info->ht_caps.ampdu_params =
(sband->ht_cap.ampdu_factor << IEEE80211_AMPDU_RX_LEN_SHIFT) | // (64K - 1) bytes
(sband->ht_cap.ampdu_density << IEEE80211_AMPDU_DENSITY_SHIFT); // 8 us
memcpy(&band_info->ht_caps.supported_mcs_set, &sband->ht_cap.mcs,
sizeof(struct ieee80211_mcs_info));
// TODO(36684): band_info->vht_caps =
ZX_ASSERT(sband->n_bitrates <= static_cast<int>(std::size(band_info->rates)));
for (int rate_idx = 0; rate_idx < sband->n_bitrates; ++rate_idx) {
band_info->rates[rate_idx] = cfg_rates_to_80211(sband->bitrates[rate_idx]);
}
// Fill the channel list of this band.
wlan_info_channel_list_t* ch_list = &band_info->supported_channels;
switch (band_info->band) {
case WLAN_INFO_BAND_TWO_GHZ:
ch_list->base_freq = 2407;
break;
case WLAN_INFO_BAND_FIVE_GHZ:
ch_list->base_freq = 5000;
break;
default:
ZX_ASSERT(0); // Unknown band ID.
break;
}
ZX_ASSERT(sband->n_channels <= static_cast<int>(std::size(ch_list->channels)));
for (int ch_idx = 0; ch_idx < sband->n_channels; ++ch_idx) {
ch_list->channels[ch_idx] = sband->channels[ch_idx].ch_num;
}
}
}
///////////////////////////////////// MAC //////////////////////////////////////////////
zx_status_t mac_query(void* ctx, uint32_t options, wlan_softmac_info_t* info) {
const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
if (!ctx || !info) {
return ZX_ERR_INVALID_ARGS;
}
memset(info, 0, sizeof(*info));
ZX_ASSERT(mvmvif->mvm);
ZX_ASSERT(mvmvif->mvm->nvm_data);
struct iwl_nvm_data* nvm_data = mvmvif->mvm->nvm_data;
memcpy(info->sta_addr, nvm_data->hw_addr, sizeof(info->sta_addr));
info->mac_role = mvmvif->mac_role;
// TODO(43517): Better handling of driver features bits/flags
info->driver_features = WLAN_INFO_DRIVER_FEATURE_SCAN_OFFLOAD | WLAN_INFO_DRIVER_FEATURE_MFP;
info->supported_phys = WLAN_INFO_PHY_TYPE_DSSS | WLAN_INFO_PHY_TYPE_HR | WLAN_INFO_PHY_TYPE_OFDM |
WLAN_INFO_PHY_TYPE_HT;
info->caps = WLAN_INFO_HARDWARE_CAPABILITY_SHORT_PREAMBLE |
WLAN_INFO_HARDWARE_CAPABILITY_SPECTRUM_MGMT |
WLAN_INFO_HARDWARE_CAPABILITY_SHORT_SLOT_TIME;
// Determine how many bands this adapter supports.
wlan_info_band_t bands[WLAN_INFO_BAND_COUNT];
info->bands_count = compose_band_list(nvm_data, bands);
fill_band_infos(nvm_data, bands, info->bands_count, info->bands);
return ZX_OK;
}
zx_status_t mac_start(void* ctx, const wlan_softmac_ifc_protocol_t* ifc,
zx_handle_t* out_mlme_channel) {
const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
if (!ctx || !ifc || !out_mlme_channel) {
return ZX_ERR_INVALID_ARGS;
}
// Clear the output result first.
*out_mlme_channel = ZX_HANDLE_INVALID;
// The SME channel assigned in phy_create_iface() is gone.
if (mvmvif->mlme_channel == ZX_HANDLE_INVALID) {
IWL_ERR(mvmvif, "Invalid SME channel. The interface might have been bound already.\n");
return ZX_ERR_ALREADY_BOUND;
}
// Transfer the handle to MLME. Also invalid the copy we hold to indicate that this interface has
// been bound.
*out_mlme_channel = mvmvif->mlme_channel;
mvmvif->mlme_channel = ZX_HANDLE_INVALID;
mvmvif->ifc = *ifc;
zx_status_t ret = iwl_mvm_mac_add_interface(mvmvif);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot add MAC interface: %s\n", zx_status_get_string(ret));
return ret;
}
return ret;
}
void mac_stop(struct iwl_mvm_vif* mvmvif) {
zx_status_t ret = ZX_OK;
if (mvmvif->phy_ctxt) {
ret = iwl_mvm_remove_chanctx(mvmvif->mvm, mvmvif->phy_ctxt->id);
if (ret != ZX_OK) {
IWL_WARN(mvmvif, "Cannot remove chanctx: %s\n", zx_status_get_string(ret));
}
}
ret = iwl_mvm_mac_remove_interface(mvmvif);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot remove MAC interface: %s\n", zx_status_get_string(ret));
}
}
// This function will ensure the mvmvif->phy_ctxt is valid (either get a free one from pool
// or use the assigned one).
//
static zx_status_t mac_ensure_phyctxt_valid(struct iwl_mvm_vif* mvmvif) {
if (!mvmvif->phy_ctxt) {
// Add PHY context with default value.
uint16_t phy_ctxt_id;
zx_status_t ret = iwl_mvm_add_chanctx(mvmvif->mvm, &default_channel, &phy_ctxt_id);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot add channel context: %s\n", zx_status_get_string(ret));
return ret;
}
mvmvif->phy_ctxt = &mvmvif->mvm->phy_ctxts[phy_ctxt_id];
}
return ZX_OK;
}
static zx_status_t remove_chanctx(struct iwl_mvm_vif* mvmvif) {
zx_status_t ret;
// mvmvif->phy_ctxt will be cleared up in iwl_mvm_unassign_vif_chanctx(). So back up the phy
// context ID and the chandef pointer for later use.
auto phy_ctxt_id = mvmvif->phy_ctxt->id;
auto chandef = &mvmvif->phy_ctxt->chandef;
// Unbinding MAC and PHY contexts.
ret = iwl_mvm_unassign_vif_chanctx(mvmvif);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot unassign VIF channel context: %s\n", zx_status_get_string(ret));
goto out;
}
ret = iwl_mvm_remove_chanctx(mvmvif->mvm, phy_ctxt_id);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot remove channel context: %s\n", zx_status_get_string(ret));
goto out;
}
// Clear the chandef in mvm->phy_ctxts[] (was pointed by mvmvif->phy_ctxt->chandef) to indicate
// this phy_ctxt is unused.
memset(chandef, 0, sizeof(*chandef));
out:
return ret;
}
// This is called right after SSID scan. The MLME tells this function the channel to tune in.
// This function configures the PHY context and binds the MAC to that PHY context.
zx_status_t mac_set_channel(struct iwl_mvm_vif* mvmvif, uint32_t options,
const wlan_channel_t* channel) {
zx_status_t ret;
IWL_INFO(mvmvif, "mac_set_channel(primary:%d, bandwidth:'%s', secondary:%d)\n", channel->primary,
channel->cbw == CHANNEL_BANDWIDTH_CBW20 ? "20"
: channel->cbw == CHANNEL_BANDWIDTH_CBW40 ? "40"
: channel->cbw == CHANNEL_BANDWIDTH_CBW40BELOW ? "40-"
: channel->cbw == CHANNEL_BANDWIDTH_CBW80 ? "80"
: channel->cbw == CHANNEL_BANDWIDTH_CBW160 ? "160"
: channel->cbw == CHANNEL_BANDWIDTH_CBW80P80 ? "80+80"
: "unknown",
channel->secondary80);
if (mvmvif->phy_ctxt && mvmvif->phy_ctxt->chandef.primary != 0) {
// The PHY context is set (the RF is on a particular channel). Remove it first. Below code
// will allocate a new one.
ret = remove_chanctx(mvmvif);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot reset PHY context: %s\n", zx_status_get_string(ret));
return ret;
}
}
// Before we do anything, ensure the PHY context had been assigned to the mvmvif.
ret = mac_ensure_phyctxt_valid(mvmvif);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot get an available chanctx: %s\n", zx_status_get_string(ret));
return ret;
}
// Save the info.
mvmvif->phy_ctxt->chandef = *channel;
ret = iwl_mvm_change_chanctx(mvmvif->mvm, mvmvif->phy_ctxt->id, channel);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot change chanctx: %s\n", zx_status_get_string(ret));
return ret;
}
ret = iwl_mvm_assign_vif_chanctx(mvmvif, channel);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "Cannot assign VIF chanctx: %s\n", zx_status_get_string(ret));
return ret;
}
return ret;
}
// This is called after mac_set_channel(). The MAC (mvmvif) will be configured as a CLIENT role.
zx_status_t mac_configure_bss(struct iwl_mvm_vif* mvmvif, uint32_t options,
const bss_config_t* config) {
zx_status_t ret = ZX_OK;
IWL_INFO(mvmvif, "mac_configure_bss(bssid=%02x:%02x:%02x:%02x:%02x:%02x, type=%d, remote=%d)\n",
config->bssid[0], config->bssid[1], config->bssid[2], config->bssid[3], config->bssid[4],
config->bssid[5], config->bss_type, config->remote);
if (config->bss_type != BSS_TYPE_INFRASTRUCTURE) {
IWL_ERR(mvmvif, "invalid bss_type requested: %d\n", config->bss_type);
return ZX_ERR_INVALID_ARGS;
}
{
// Copy the BSSID info.
auto lock = std::lock_guard(mvmvif->mvm->mutex);
memcpy(mvmvif->bss_conf.bssid, config->bssid, ETH_ALEN);
memcpy(mvmvif->bssid, config->bssid, ETH_ALEN);
// Simulates the behavior of iwl_mvm_bss_info_changed_station().
ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, mvmvif->bssid);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot set BSSID: %s\n", zx_status_get_string(ret));
return ret;
}
}
// Ask the firmware to pay attention for beacon.
// Note that this would add TIME_EVENT as well.
iwl_mvm_mac_mgd_prepare_tx(mvmvif->mvm, mvmvif, IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS);
return ZX_OK;
}
// This function is to revert what mac_configure_bss() does.
zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif) {
zx_status_t ret = ZX_OK;
{
// To simulate the behavior that iwl_mvm_bss_info_changed_station() would do for disassocitaion.
auto lock = std::lock_guard(mvmvif->mvm->mutex);
memset(mvmvif->bss_conf.bssid, 0, ETH_ALEN);
memset(mvmvif->bssid, 0, ETH_ALEN);
// This will take the cleared BSSID from bss_conf and update the firmware.
ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
if (ret != ZX_OK) {
IWL_ERR(mvm, "failed to update MAC (clear after unassoc)\n");
return ret;
}
}
return ZX_OK;
}
zx_status_t mac_enable_beaconing(void* ctx, uint32_t options, const wlan_bcn_config_t* bcn_cfg) {
IWL_ERR(ctx, "%s() needs porting ... see fxbug.dev/36742\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t mac_configure_beacon(void* ctx, uint32_t options,
const wlan_tx_packet_t* packet_template) {
IWL_ERR(ctx, "%s() needs porting ... see fxbug.dev/36742\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
// Set the association result to the firmware.
//
// The current mac context is set by mac_configure_bss() with default values.
// TODO(fxbug.dev/36684): supports VHT (802.11ac)
//
zx_status_t mac_configure_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
const wlan_assoc_ctx_t* assoc_ctx) {
zx_status_t ret = ZX_OK;
IWL_INFO(ctx, "Associating ...\n");
// TODO(fxbug.dev/86715): this RCU-unprotected access is safe as deletions from the map are
// RCU-synchronized from API calls to mac_stop() in this same thread.
struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id];
if (!mvm_sta) {
IWL_ERR(mvmvif, "sta info is not set before association.\n");
ret = ZX_ERR_BAD_STATE;
return ret;
}
// Save band info into interface struct for future usage.
mvmvif->phy_ctxt->band = iwl_mvm_get_channel_band(assoc_ctx->channel.primary);
mvm_sta->bw = assoc_ctx->channel.cbw;
// Record the intersection of AP and station supported rate to mvm_sta.
ZX_ASSERT(assoc_ctx->rates_cnt <= sizeof(mvm_sta->supp_rates));
memcpy(mvm_sta->supp_rates, assoc_ctx->rates, assoc_ctx->rates_cnt);
// Copy HT related fields from wlan_assoc_ctx_t.
mvm_sta->support_ht = assoc_ctx->has_ht_cap;
if (assoc_ctx->has_ht_cap) {
memcpy(&mvm_sta->ht_cap, &assoc_ctx->ht_cap, sizeof(struct ieee80211_ht_capabilities));
}
// Change the station states step by step.
ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_AUTH);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot set state from NONE to AUTH: %s\n", zx_status_get_string(ret));
return ret;
}
ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTH, IWL_STA_ASSOC);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot set state from AUTH to ASSOC: %s\n", zx_status_get_string(ret));
return ret;
}
ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_ASSOC, IWL_STA_AUTHORIZED);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot set state from ASSOC to AUTHORIZED: %s\n", zx_status_get_string(ret));
return ret;
}
// Tell firmware to pass multicast packets to driver.
iwl_mvm_configure_filter(mvmvif->mvm);
{
auto lock = std::lock_guard(mvmvif->mvm->mutex);
// Update the MAC context in the firmware.
mvmvif->bss_conf.assoc = true;
mvmvif->bss_conf.listen_interval = assoc_ctx->listen_interval;
ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot update MAC context in the firmware: %s\n", zx_status_get_string(ret));
return ret;
}
ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
return ret;
}
}
// Tell firmware to pass multicast packets to driver.
iwl_mvm_configure_filter(mvmvif->mvm);
// TODO(43218): support multiple interfaces. Need to port iwl_mvm_update_quotas() in mvm/quota.c.
// TODO(56093): support low latency in struct iwl_time_quota_data.
return ZX_OK;
}
zx_status_t mac_clear_assoc(struct iwl_mvm_vif* mvmvif, uint32_t options,
const uint8_t peer_addr[fuchsia_wlan_ieee80211::wire::kMacAddrLen]) {
IWL_INFO(ctx, "Disassociating ...\n");
zx_status_t ret = ZX_OK;
{
auto lock = std::lock_guard(mvmvif->mvm->mutex);
// Remove Time event (in case assoc failed)
ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data);
if (ret != ZX_OK) {
IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret));
}
}
ret = mac_unconfigure_bss(mvmvif);
if (ret != ZX_OK) {
return ret;
}
return remove_chanctx(mvmvif);
}
zx_status_t mac_start_passive_scan(void* ctx,
const wlan_softmac_passive_scan_args_t* passive_scan_args,
uint64_t* out_scan_id) {
const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
return iwl_mvm_mac_hw_scan_passive(mvmvif, passive_scan_args, out_scan_id);
}
zx_status_t mac_start_active_scan(void* ctx,
const wlan_softmac_active_scan_args_t* active_scan_args,
uint64_t* out_scan_id) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t mac_init(void* ctx, struct iwl_trans* drvdata, zx_device_t* zxdev, uint16_t idx) {
zx_status_t status = phy_start_iface(drvdata, zxdev, idx);
if (status != ZX_OK) {
// Freeing of resources allocated in phy_create_iface() will happen in mac_release().
IWL_ERR(this, "%s() failed phy start: %s\n", __func__, zx_status_get_string(status));
}
return status;
}
void mac_unbind(void* ctx) {
const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
if (!mvmvif->zxdev) {
IWL_ERR(nullptr, "mac_unbind(): no zxdev\n");
return;
}
mvmvif->zxdev = nullptr;
}
void mac_release(void* ctx) {
const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx);
// Close the SME channel if it is NOT transferred to MLME yet.
if (mvmvif->mlme_channel != ZX_HANDLE_INVALID) {
zx_handle_close(mvmvif->mlme_channel);
mvmvif->mlme_channel = ZX_HANDLE_INVALID;
}
free(mvmvif);
}
///////////////////////////////////// PHY //////////////////////////////////////////////
zx_status_t phy_get_supported_mac_roles(
void* ctx,
wlan_mac_role_t out_supported_mac_roles_list[fuchsia_wlan_common_MAX_SUPPORTED_MAC_ROLES],
uint8_t* out_supported_mac_roles_count) {
const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
if (nullptr == mvm || nullptr == out_supported_mac_roles_list ||
nullptr == out_supported_mac_roles_count) {
return ZX_ERR_INVALID_ARGS;
}
struct iwl_nvm_data* nvm_data = mvm->nvm_data;
ZX_ASSERT(nvm_data);
// TODO(fxbug.dev/36677): supports AP role
out_supported_mac_roles_list[0] = WLAN_MAC_ROLE_CLIENT;
*out_supported_mac_roles_count = 1;
return ZX_OK;
}
// This function is working with a PHY context ('ctx') to create a MAC interface.
zx_status_t phy_create_iface(void* ctx, const wlanphy_impl_create_iface_req_t* req,
uint16_t* out_iface_id) {
const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
struct iwl_mvm_vif* mvmvif = nullptr;
zx_status_t ret = ZX_OK;
if (!req) {
IWL_ERR(mvm, "req is not given\n");
return ZX_ERR_INVALID_ARGS;
}
if (req->mlme_channel == ZX_HANDLE_INVALID) {
IWL_ERR(mvm, "the given sme channel is invalid\n");
return ZX_ERR_INVALID_ARGS;
}
if (!mvm) {
IWL_ERR(mvm, "cannot obtain MVM from ctx=%p while creating interface\n", ctx);
return ZX_ERR_INVALID_ARGS;
}
if (!out_iface_id) {
IWL_ERR(mvm, "out_iface_id pointer is not given\n");
return ZX_ERR_INVALID_ARGS;
}
auto lock = std::lock_guard(mvm->mutex);
// Find the first empty mvmvif slot.
int idx;
ret = iwl_mvm_find_free_mvmvif_slot(mvm, &idx);
if (ret != ZX_OK) {
IWL_ERR(mvm, "cannot find an empty slot for new MAC interface\n");
return ret;
}
// Allocate a MAC context. This will be initialized once iwl_mvm_mac_add_interface() is called.
// Note that once the 'mvmvif' is saved in the device ctx by device_add() below, it will live
// as long as the device instance, and will be freed in mac_release().
mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(calloc(1, sizeof(struct iwl_mvm_vif)));
if (!mvmvif) {
ret = ZX_ERR_NO_MEMORY;
return ret;
}
// Set default values into the mvmvif
mvmvif->bss_conf.beacon_int = 100;
mvmvif->bss_conf.dtim_period = 3;
mvmvif->mvm = mvm;
mvmvif->mac_role = req->role;
mvmvif->mlme_channel = req->mlme_channel;
ret = iwl_mvm_bind_mvmvif(mvm, idx, mvmvif);
if (ret != ZX_OK) {
IWL_ERR(ctx, "Cannot assign the new mvmvif to MVM: %s\n", zx_status_get_string(ret));
// The allocated mvmvif instance will be freed at mac_release().
return ret;
}
*out_iface_id = idx;
return ZX_OK;
}
// If there are failures post phy_create_iface() and before phy_start_iface()
// is successful, then this is the API to undo phy_create_iface().
void phy_create_iface_undo(struct iwl_trans* iwl_trans, uint16_t idx) {
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
struct iwl_mvm_vif* mvmvif = nullptr;
{
// Unbind and free the mvmvif interface.
auto lock = std::lock_guard(mvm->mutex);
mvmvif = mvm->mvmvif[idx];
iwl_mvm_unbind_mvmvif(mvm, idx);
}
free(mvmvif);
}
zx_status_t phy_start_iface(void* ctx, zx_device_t* zxdev, uint16_t idx) {
const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
zx_status_t ret = ZX_OK;
if (idx >= MAX_NUM_MVMVIF) {
IWL_ERR(mvm, "Interface index is too large (%d). expect less than %d\n", idx, MAX_NUM_MVMVIF);
return ZX_ERR_INVALID_ARGS;
}
auto lock = std::lock_guard(mvm->mutex);
struct iwl_mvm_vif* mvmvif = mvm->mvmvif[idx];
mvmvif->zxdev = zxdev;
// Only start FW MVM for the first device. The 'vif_count' will be increased in
// iwl_mvm_mac_add_interface().
if (mvm->vif_count == 0) {
ret = __iwl_mvm_mac_start(mvm);
if (ret != ZX_OK) {
IWL_ERR(ctx, "Cannot start MVM MAC: %s\n", zx_status_get_string(ret));
// If we fail to start the FW MVM, we shall unbind the mvmvif from the mvm. For the mvmvif
// instance, it will be released in mac_release().
// TODO: It does not look clean to have unbind happen here.
iwl_mvm_unbind_mvmvif(mvm, idx);
return ret;
}
// Once MVM is started, copy the MAC address to mvmvif.
struct iwl_nvm_data* nvm_data = mvmvif->mvm->nvm_data;
memcpy(mvmvif->addr, nvm_data->hw_addr, ETH_ALEN);
}
return ZX_OK;
}
// This function is working with a PHY context ('ctx') to delete a MAC interface ('id').
// The 'id' is the value assigned by phy_create_iface().
zx_status_t phy_destroy_iface(void* ctx, uint16_t id) {
const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx);
struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans);
struct iwl_mvm_vif* mvmvif = nullptr;
if (!mvm) {
IWL_ERR(mvm, "cannot obtain MVM from ctx=%p while destroying interface (%d)\n", ctx, id);
return ZX_ERR_INVALID_ARGS;
}
{
auto lock = std::lock_guard(mvm->mutex);
if (id >= MAX_NUM_MVMVIF) {
IWL_ERR(mvm, "the interface id (%d) is invalid\n", id);
return ZX_ERR_INVALID_ARGS;
}
mvmvif = mvm->mvmvif[id];
if (!mvmvif) {
IWL_ERR(mvm, "the interface id (%d) has no MAC context\n", id);
return ZX_ERR_NOT_FOUND;
}
// Unlink the 'mvmvif' from the 'mvm'. The zxdev will be removed in mac_unbind(),
// and the memory of 'mvmvif' will be freed in mac_release().
iwl_mvm_unbind_mvmvif(mvm, id);
// the last MAC interface. stop the MVM to save power. 'vif_count' had been decreased in
// iwl_mvm_mac_remove_interface().
if (mvm->vif_count == 0) {
__iwl_mvm_mac_stop(mvm);
}
}
device_async_remove(mvmvif->zxdev);
return ZX_OK;
}
zx_status_t phy_set_country(void* ctx, const wlanphy_country_t* country) {
IWL_ERR(ctx, "%s() needs porting ...\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t phy_get_country(void* ctx, wlanphy_country_t* out_country) {
if (out_country == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
IWL_ERR(ctx, "%s() needs porting ...\n", __func__);
return ZX_ERR_NOT_SUPPORTED;
}