| // 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 "mvm-mlme.h" |
| |
| //#include <fidl/fuchsia.wlan.ieee80211/cpp/wire.h> |
| #include <fuchsia/hardware/wlan/mac/c/banjo.h> |
| #include <fuchsia/wlan/ieee80211/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 "ieee80211_include.h" |
| |
| extern "C" { |
| #include "third_party/iwlwifi/iwl-debug.h" |
| #include "third_party/iwlwifi/mvm/mvm.h" |
| #include "third_party/iwlwifi/mvm/time-event.h" |
| } // extern "C" |
| |
| #include "ieee80211.h" |
| |
| namespace { |
| |
| // IEEE 802.11-2016 3.2 (c.f. "vendor organizationally unique identifier") |
| constexpr uint8_t kIeeeOui[] = {0x00, 0x0F, 0xAC}; |
| |
| } // namespace |
| |
| //////////////////////////////////// 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_2GHZ; |
| } |
| if (nvm_data->sku_cap_band_52ghz_enable) { |
| bands[bands_count++] = WLAN_INFO_BAND_5GHZ; |
| } |
| 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 <= ARRAY_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 = nvm_data->sku_cap_11n_enable; |
| // TODO(43517): Better handling of driver features bits/flags |
| band_info->ht_caps.ht_capability_info = |
| IEEE80211_HT_CAPS_CHAN_WIDTH | IEEE80211_HT_CAPS_SMPS_DYNAMIC; |
| band_info->ht_caps.ampdu_params = (3 << IEEE80211_AMPDU_RX_LEN_SHIFT) | // (64K - 1) bytes |
| (6 << IEEE80211_AMPDU_DENSITY_SHIFT); // 8 us |
| // TODO(36683): band_info->ht_caps->supported_mcs_set = |
| // TODO(36684): band_info->vht_caps = |
| |
| ZX_ASSERT(sband->n_bitrates <= (int)ARRAY_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_2GHZ: |
| ch_list->base_freq = 2407; |
| break; |
| case WLAN_INFO_BAND_5GHZ: |
| ch_list->base_freq = 5000; |
| break; |
| default: |
| ZX_ASSERT(0); // Unknown band ID. |
| break; |
| } |
| ZX_ASSERT(sband->n_channels <= (int)ARRAY_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; |
| } |
| } |
| } |
| |
| static struct iwl_mvm_sta* alloc_ap_mvm_sta(const uint8_t bssid[]) { |
| auto mvm_sta = reinterpret_cast<struct iwl_mvm_sta*>(calloc(1, sizeof(struct iwl_mvm_sta))); |
| if (!mvm_sta) { |
| return NULL; |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE(mvm_sta->txq); i++) { |
| mvm_sta->txq[i] = reinterpret_cast<struct iwl_mvm_txq*>(calloc(1, sizeof(struct iwl_mvm_txq))); |
| } |
| memcpy(mvm_sta->addr, bssid, ETH_ALEN); |
| mtx_init(&mvm_sta->lock, mtx_plain); |
| |
| return mvm_sta; |
| } |
| |
| static void free_ap_mvm_sta(struct iwl_mvm_sta* mvm_sta) { |
| if (!mvm_sta) { |
| return; |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE(mvm_sta->txq); i++) { |
| free(mvm_sta->txq[i]); |
| } |
| if (mvm_sta->key_conf) { |
| free(mvm_sta->key_conf); |
| } |
| free(mvm_sta); |
| } |
| |
| static void reset_sta_mapping(struct iwl_mvm_vif* mvmvif) { |
| if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { |
| mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id] = NULL; |
| mvmvif->ap_sta_id = IWL_MVM_INVALID_STA; |
| } |
| } |
| |
| ///////////////////////////////////// MAC ////////////////////////////////////////////// |
| |
| zx_status_t mac_query(void* ctx, uint32_t options, wlanmac_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; |
| info->supported_phys = WLAN_INFO_PHY_TYPE_DSSS | WLAN_INFO_PHY_TYPE_CCK | |
| 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 wlanmac_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(void* ctx) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| zx_status_t ret; |
| |
| // Change the sta state linking to the AP. |
| if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { |
| 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 stop.\n"); |
| } else { |
| ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_NOTEXIST); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "Cannot set station state to NOT EXIST: %s\n", zx_status_get_string(ret)); |
| } |
| } |
| } |
| |
| 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)); |
| } |
| } else { |
| IWL_WARN(mvmvif, "PHY context is NULL in %s()\n", __func__); |
| } |
| |
| // Clean up other sta info. |
| for (size_t i = 0; i < ARRAY_SIZE(mvmvif->mvm->fw_id_to_mac_id); i++) { |
| struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[i]; |
| if (mvm_sta) { |
| free_ap_mvm_sta(mvm_sta); |
| mvmvif->mvm->fw_id_to_mac_id[i] = NULL; |
| } |
| } |
| |
| 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)); |
| } |
| } |
| |
| zx_status_t mac_queue_tx(void* ctx, uint32_t options, const wlan_tx_packet_t* tx_packet) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| |
| if (tx_packet->mac_frame_size > WLAN_MSDU_MAX_LEN) { |
| IWL_ERR(mvmvif, "Frame size is to large (%lu). expect less than %lu.\n", |
| tx_packet->mac_frame_size, WLAN_MSDU_MAX_LEN); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| ieee80211_mac_packet packet = {}; |
| packet.common_header = |
| reinterpret_cast<const ieee80211_frame_header*>(tx_packet->mac_frame_buffer); |
| packet.header_size = ieee80211_get_header_len(packet.common_header); |
| if (packet.header_size > tx_packet->mac_frame_size) { |
| IWL_ERR(mvmvif, "TX packet header size %zu too large for data size %zu\n", packet.header_size, |
| tx_packet->mac_frame_size); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| packet.body = tx_packet->mac_frame_buffer + packet.header_size; |
| packet.body_size = tx_packet->mac_frame_size - packet.header_size; |
| |
| mtx_lock(&mvmvif->mvm->mutex); |
| zx_status_t ret = iwl_mvm_mac_tx(mvmvif, &packet); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| |
| return 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 mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta); |
| |
| // 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(void* ctx, uint32_t options, const wlan_channel_t* channel) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| 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->ap_sta_id != IWL_MVM_INVALID_STA) { |
| // We already have PHY context set. It probably was left from the last association attempt |
| // (which was rejected by the AP). Remove it first. |
| IWL_INFO(mvmvif, "We already have PHY context set (ap_sta_id=%d). Removing it.\n", |
| mvmvif->ap_sta_id); |
| |
| // In the normal case, the time event is added in the mac_configure_bss() and removed in the |
| // mac_configure_assoc(). So in the abnormal cases (assoc failed), we need to remove it before |
| // configure_bss(). |
| mtx_lock(&mvmvif->mvm->mutex); |
| ret = iwl_mvm_remove_time_event(mvmvif, &mvmvif->time_event_data); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot remove time event: %s\n", zx_status_get_string(ret)); |
| return ret; |
| } |
| |
| auto mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]; |
| if (mvm_sta) { |
| ret = mac_unconfigure_bss(mvmvif, mvm_sta); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "Cannot unconfigure bss: %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(void* ctx, uint32_t options, const bss_config_t* config) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| 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; |
| } |
| |
| if (mvmvif->ap_sta_id != IWL_MVM_INVALID_STA) { |
| IWL_ERR(mvmvif, "The AP sta ID has been set already. ap_sta_id=%d\n", mvmvif->ap_sta_id); |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| // Note that 'ap_sta_id' is unset and later will be set in iwl_mvm_add_sta(). |
| |
| // Copy the BSSID info. |
| mtx_lock(&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); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set BSSID: %s\n", zx_status_get_string(ret)); |
| return ret; |
| } |
| |
| // Add AP into the STA table in the firmware. |
| struct iwl_mvm_sta* mvm_sta = alloc_ap_mvm_sta(config->bssid); |
| if (!mvm_sta) { |
| IWL_ERR(mvmvif, "cannot allocate MVM STA for AP.\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| // mvm_sta ownership will be transfered to mvm->fw_id_to_mac_id[] in iwl_mvm_add_sta(). It will be |
| // freed at mac_clear_assoc(). |
| // Below is to how the iwl_mvm_mac_sta_state() is called. |
| ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NOTEXIST, IWL_STA_NONE); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set MVM STA state: %s\n", zx_status_get_string(ret)); |
| goto exit; |
| } |
| |
| // 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); |
| |
| // Allocate a Tx queue for this station. |
| mtx_lock(&mvmvif->mvm->mutex); |
| ret = iwl_mvm_sta_alloc_queue(mvmvif->mvm, mvm_sta, IEEE80211_AC_BE, IWL_MAX_TID_COUNT); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot allocate queue for STA: %s\n", zx_status_get_string(ret)); |
| goto exit; |
| } |
| |
| exit: |
| // If it is successful, the ownership has been transferred. If not, free the resource. |
| if (ret != ZX_OK) { |
| free_ap_mvm_sta(mvm_sta); |
| reset_sta_mapping(mvmvif); |
| } |
| return ret; |
| } |
| |
| 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* pkt) { |
| IWL_ERR(ctx, "%s() needs porting ... see fxbug.dev/36742\n", __func__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t mac_set_key(void* ctx, uint32_t options, const wlan_key_config_t* key_config) { |
| zx_status_t status = ZX_OK; |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| iwl_mvm* mvm = mvmvif->mvm; |
| |
| if (mvm->trans->cfg->gen2 || iwl_mvm_has_new_tx_api(mvm)) { |
| // The new firmwares (for starting with the 22000 series) have different packet generation |
| // requirements than mentioned below. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (!std::equal(key_config->cipher_oui, |
| key_config->cipher_oui + std::size(key_config->cipher_oui), kIeeeOui, |
| kIeeeOui + std::size(kIeeeOui))) { |
| // IEEE 802.11-2016 9.4.2.25.2 |
| // The standard ciphers all live in the IEEE space. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| switch (key_config->cipher_type) { |
| case CIPHER_SUITE_TYPE_CCMP_128: |
| // Note: the Linux iwlwifi driver requests IEEE80211_KEY_FLAG_PUT_IV_SPACE from the mac80211 |
| // stack. We will apply equivalent functionality manually to Incoming packets from Fuchsia. |
| break; |
| default: |
| // Additional porting required for other types. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| auto key_conf = reinterpret_cast<iwl_mvm_sta_key_conf*>( |
| malloc(sizeof(iwl_mvm_sta_key_conf) + key_config->key_len)); |
| memset(key_conf, 0, sizeof(*key_conf) + key_config->key_len); |
| key_conf->cipher_type = key_config->cipher_type; |
| key_conf->key_type = key_config->key_type; |
| key_conf->keyidx = key_config->key_idx; |
| key_conf->keylen = key_config->key_len; |
| key_conf->rx_seq = key_config->rsc; |
| memcpy(key_conf->key, key_config->key, key_conf->keylen); |
| |
| struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[mvmvif->ap_sta_id]; |
| if ((status = iwl_mvm_mac_set_key(mvmvif, mvm_sta, key_conf)) != ZX_OK) { |
| free(key_conf); |
| IWL_ERR(mvmvif, "iwl_mvm_mac_set_key() failed: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| if (key_conf->key_type == WLAN_KEY_TYPE_PAIRWISE) { |
| // Save the pairwise key, for use in the TX path. Group keys are receive-only and do not need |
| // to be saved. |
| free(mvm_sta->key_conf); |
| mvm_sta->key_conf = key_conf; |
| } else { |
| free(key_conf); |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Set the association result to the firmware. |
| // |
| // The current mac context is set by mac_configure_bss() with default values. |
| // TODO(fxbug.dev/36683): supports HT (802.11n) |
| // TODO(fxbug.dev/36684): supports VHT (802.11ac) |
| // |
| zx_status_t mac_configure_assoc(void* ctx, uint32_t options, const wlan_assoc_ctx_t* assoc_ctx) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| zx_status_t ret = ZX_OK; |
| |
| IWL_INFO(ctx, "Associating ...\n"); |
| |
| 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; |
| goto out; |
| } |
| |
| // 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)); |
| goto out; |
| } |
| |
| 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)); |
| goto out; |
| } |
| 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)); |
| goto out; |
| } |
| |
| // Tell firmware to pass multicast packets to driver. |
| iwl_mvm_configure_filter(mvmvif->mvm); |
| |
| mtx_lock(&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)); |
| goto unlock; |
| } |
| |
| 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)); |
| goto unlock; |
| } |
| |
| // 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. |
| |
| unlock: |
| mtx_unlock(&mvmvif->mvm->mutex); |
| out: |
| return ret; |
| } |
| |
| zx_status_t mac_clear_assoc(void* ctx, uint32_t options, |
| const uint8_t peer_addr[fuchsia_wlan_ieee80211_MAC_ADDR_LEN]) { |
| IWL_INFO(ctx, "Disassociating ...\n"); |
| |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| zx_status_t ret = ZX_OK; |
| |
| if (mvmvif->ap_sta_id == IWL_MVM_INVALID_STA) { |
| IWL_ERR(mvmif, "sta id has not been set yet"); |
| return ZX_ERR_BAD_STATE; |
| } |
| // iwl_mvm_rm_sta() will reset the ap_sta_id value so that we have to keep it. |
| uint8_t ap_sta_id = mvmvif->ap_sta_id; |
| |
| struct iwl_mvm_sta* mvm_sta = mvmvif->mvm->fw_id_to_mac_id[ap_sta_id]; |
| if (!mvm_sta) { |
| IWL_ERR(mvmvif, "sta info is not set before disassociation.\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // Mark the station is no longer associated. This must be set before iwl_mvm_mac_sta_state(). |
| mvmvif->bss_conf.assoc = false; |
| |
| // Below are to simulate the behavior of iwl_mvm_bss_info_changed_station(). |
| ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTHORIZED, IWL_STA_ASSOC); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set state from AUTHORIZED to ASSOC: %s\n", zx_status_get_string(ret)); |
| goto out; |
| } |
| ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_ASSOC, IWL_STA_AUTH); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set state from ASSOC to AUTH: %s\n", zx_status_get_string(ret)); |
| goto out; |
| } |
| ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_AUTH, IWL_STA_NONE); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set state from AUTH to NONE: %s\n", zx_status_get_string(ret)); |
| goto out; |
| } |
| |
| // Tell firmware to flush all packets in the Tx queue. This must be done before we remove the STA |
| // (in the NONE->NOTEXIST transition). |
| // TODO(79799): understand why we need this. |
| mtx_lock(&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)); |
| } |
| iwl_mvm_flush_sta(mvmvif->mvm, mvm_sta, false, 0); |
| |
| // Update the MAC context in the firmware. |
| mvmvif->bss_conf.assoc = false; |
| ret = iwl_mvm_mac_ctxt_changed(mvmvif, false, NULL); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot update MAC context in the firmware: %s\n", zx_status_get_string(ret)); |
| goto out; |
| } |
| |
| ret = mac_unconfigure_bss(mvmvif, mvm_sta); |
| |
| out: |
| return ret; |
| } |
| |
| // This function is to revert what mac_configure_bss() does. |
| zx_status_t mac_unconfigure_bss(struct iwl_mvm_vif* mvmvif, struct iwl_mvm_sta* mvm_sta) { |
| // mvmvif->phy_ctxt will be cleared up in iwl_mvm_unassign_vif_chanctx(). So backup the phy |
| // context ID for removing. |
| auto phy_ctxt_id = mvmvif->phy_ctxt->id; |
| |
| // The 'ap_sta_id' will be reset in iwl_mvm_rm_sta() (via NONE --> NOTEXIST), back it up. |
| auto ap_sta_id = mvmvif->ap_sta_id; |
| |
| // REMOVE_STA will be issued to remove the station entry in the firmware. |
| zx_status_t ret = iwl_mvm_mac_sta_state(mvmvif, mvm_sta, IWL_STA_NONE, IWL_STA_NOTEXIST); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvmvif, "cannot set state from NONE to NOTEXIST: %s\n", zx_status_get_string(ret)); |
| goto out; |
| } |
| |
| // To simulate the behavior that iwl_mvm_bss_info_changed_station() would do for disassocitaion. |
| mtx_lock(&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); |
| mtx_unlock(&mvmvif->mvm->mutex); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvm, "failed to update MAC (clear after unassoc)\n"); |
| goto out; |
| } |
| |
| // 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; |
| } |
| |
| out: |
| free_ap_mvm_sta(mvm_sta); |
| mvmvif->mvm->fw_id_to_mac_id[ap_sta_id] = NULL; |
| |
| return ret; |
| } |
| |
| zx_status_t mac_start_hw_scan(void* ctx, const wlan_hw_scan_config_t* scan_config) { |
| const auto mvmvif = reinterpret_cast<struct iwl_mvm_vif*>(ctx); |
| |
| if (scan_config->scan_type != WLAN_HW_SCAN_TYPE_PASSIVE) { |
| IWL_ERR(ctx, "Unsupported scan type: %s\n", wlan_hw_scan_type_to_str(scan_config->scan_type)); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return iwl_mvm_mac_hw_scan(mvmvif, scan_config); |
| } |
| |
| 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; |
| } |
| |
| // TODO (fxbug.dev/63618) - to be removed. |
| wlanmac_protocol_ops_t wlanmac_ops = { |
| .query = mac_query, |
| .start = mac_start, |
| .stop = mac_stop, |
| .queue_tx = mac_queue_tx, |
| .set_channel = mac_set_channel, |
| .configure_bss = mac_configure_bss, |
| .enable_beaconing = mac_enable_beaconing, |
| .configure_beacon = mac_configure_beacon, |
| .set_key = mac_set_key, |
| .configure_assoc = mac_configure_assoc, |
| .clear_assoc = mac_clear_assoc, |
| .start_hw_scan = mac_start_hw_scan, |
| }; |
| |
| 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); |
| } |
| |
| // //TODO (fxbug.dev/63618) - to be removed. |
| zx_protocol_device_t device_mac_ops = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = mac_unbind, |
| .release = mac_release, |
| }; |
| |
| ///////////////////////////////////// PHY ////////////////////////////////////////////// |
| |
| zx_status_t phy_query(void* ctx, wlanphy_impl_info_t* info) { |
| const auto iwl_trans = reinterpret_cast<struct iwl_trans*>(ctx); |
| struct iwl_mvm* mvm = iwl_trans_get_mvm(iwl_trans); |
| if (!mvm || !info) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| struct iwl_nvm_data* nvm_data = mvm->nvm_data; |
| ZX_ASSERT(nvm_data); |
| |
| memset(info, 0, sizeof(*info)); |
| |
| // TODO(fxbug.dev/36677): supports AP role |
| info->supported_mac_roles = WLAN_INFO_MAC_ROLE_CLIENT; |
| |
| 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; |
| } |
| |
| mtx_lock(&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"); |
| goto unlock; |
| } |
| |
| // 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; |
| goto unlock; |
| } |
| |
| // 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(). |
| goto unlock; |
| } |
| *out_iface_id = idx; |
| |
| unlock: |
| mtx_unlock(&mvm->mutex); |
| return ret; |
| } |
| |
| // 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); |
| |
| // Unbind and free the mvmvif interface. |
| mtx_lock(&mvm->mutex); |
| struct iwl_mvm_vif* mvmvif = mvm->mvmvif[idx]; |
| iwl_mvm_unbind_mvmvif(mvm, idx); |
| mtx_unlock(&mvm->mutex); |
| |
| 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; |
| |
| mtx_lock(&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); |
| |
| goto unlock; |
| } |
| |
| // 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); |
| } |
| |
| unlock: |
| mtx_unlock(&mvm->mutex); |
| return ret; |
| } |
| |
| // 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; |
| zx_status_t ret = ZX_OK; |
| |
| if (!mvm) { |
| IWL_ERR(mvm, "cannot obtain MVM from ctx=%p while destroying interface (%d)\n", ctx, id); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| mtx_lock(&mvm->mutex); |
| |
| if (id >= MAX_NUM_MVMVIF) { |
| IWL_ERR(mvm, "the interface id (%d) is invalid\n", id); |
| ret = ZX_ERR_INVALID_ARGS; |
| goto unlock; |
| } |
| |
| mvmvif = mvm->mvmvif[id]; |
| if (!mvmvif) { |
| IWL_ERR(mvm, "the interface id (%d) has no MAC context\n", id); |
| ret = ZX_ERR_NOT_FOUND; |
| goto unlock; |
| } |
| |
| // 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); |
| |
| unlock: |
| mtx_unlock(&mvm->mutex); |
| return ret; |
| } |
| |
| 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; |
| } |