| /****************************************************************************** |
| * |
| * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved. |
| * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH |
| * Copyright(c) 2018 Intel Corporation |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name Intel Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| *****************************************************************************/ |
| |
| #include <fuchsia/hardware/wlan/info/c/banjo.h> |
| |
| #include <fuchsia/hardware/wlanphyinfo/c/banjo.h> |
| |
| #include "third_party/iwlwifi/mvm/fw-api.h" |
| #include "third_party/iwlwifi/mvm/mvm.h" |
| |
| // A channel setting used as default value. In some cases, we need an arbitrary value for channel. |
| // For example, during initializing a PHY context in firmware, we need a (whatever) value to add |
| // an entry in firmware. This value usually will be changed later. |
| const wlan_channel_t default_channel = { |
| .primary = 1, |
| .cbw = CHANNEL_BANDWIDTH_CBW20, |
| }; |
| |
| // Converts channel number to band type. |
| // |
| // Args: |
| // chan_num: starts from 1. |
| // |
| // Returns: |
| // the band ID. |
| wlan_info_band_t iwl_mvm_get_channel_band(uint8_t chan_num) { |
| return chan_num < 14 ? WLAN_INFO_BAND_2GHZ : WLAN_INFO_BAND_5GHZ; |
| } |
| |
| /* Maps the driver specific channel width definition to the fw values */ |
| uint8_t iwl_mvm_get_channel_width(const wlan_channel_t* chandef) { |
| switch (chandef->cbw) { |
| case CHANNEL_BANDWIDTH_CBW20: |
| return PHY_VHT_CHANNEL_MODE20; |
| case CHANNEL_BANDWIDTH_CBW40: |
| case CHANNEL_BANDWIDTH_CBW40BELOW: // fall-thru |
| return PHY_VHT_CHANNEL_MODE40; |
| case CHANNEL_BANDWIDTH_CBW80: |
| return PHY_VHT_CHANNEL_MODE80; |
| case CHANNEL_BANDWIDTH_CBW160: |
| return PHY_VHT_CHANNEL_MODE160; |
| default: |
| WARN(1, "Invalid channel width=%u", chandef->width); |
| return PHY_VHT_CHANNEL_MODE20; |
| } |
| } |
| |
| /* |
| * Maps the driver specific control channel position (relative to the center |
| * freq) definitions to the the fw values. |
| * |
| * Here is the channel list: https://en.wikipedia.org/wiki/List_of_WLAN_channels |
| */ |
| uint8_t iwl_mvm_get_ctrl_pos(const wlan_channel_t* chandef) { |
| uint8_t primary = chandef->primary; |
| channel_bandwidth_t cbw = chandef->cbw; |
| uint8_t base; |
| |
| if (chandef->cbw == CHANNEL_BANDWIDTH_CBW20) { |
| // 20Mhz always uses the default value. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| |
| // TODO(fxbug.dev/29830): move out the center freq calculation into a shared lib. |
| |
| if (36 <= primary && primary <= 64) { |
| base = 36; |
| } else if (100 <= primary && primary <= 128) { |
| base = 100; |
| } else if (132 <= primary && primary <= 144) { |
| if (cbw == CHANNEL_BANDWIDTH_CBW160) { // This group doesn't support 160MHz primary |
| // channels. Use default value. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| base = 132; |
| } else if (149 <= primary && primary <= 161) { |
| if (cbw == CHANNEL_BANDWIDTH_CBW160) { // This group doesn't support 160MHz primary |
| // channels. Use default value. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| base = 149; |
| } else { |
| // 2.4GHz band or invalid channel index. Use default value. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| |
| uint8_t offset_to_base = primary - base; |
| uint8_t mask; // used to mask the chan_index. |
| // # of 1's means the bandwidth. |
| bool has_bit2 = offset_to_base & 0x4; // for HT40+/- checking |
| switch (chandef->cbw) { |
| case CHANNEL_BANDWIDTH_CBW40: // The secondary channel is above the primary. |
| mask = 0x7; // Keep 3 bits. |
| if (has_bit2) { // Channel 40, 48, 56 ... doesn't allow HT40+. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| break; |
| case CHANNEL_BANDWIDTH_CBW40BELOW: // The secondary channel is below the primary. |
| mask = 0x7; // Keep 3 bits. |
| if (!has_bit2) { // Channel 36, 44, 52 ... doesn't allow HT40-. |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| break; |
| case CHANNEL_BANDWIDTH_CBW80: |
| mask = 0xf; // Keep 4 bits. |
| break; |
| case CHANNEL_BANDWIDTH_CBW160: |
| mask = 0x1f; // Keep 5 bits. |
| break; |
| /* |
| * The FW is expected to check the control channel position only |
| * when in HT/VHT and the channel width is not 20MHz. Return |
| * this value as the default one. |
| */ |
| default: |
| IWL_WARN(chandef, "Invalid channel bandwidth (primary=%u cbw=%u)\n", chandef->primary, |
| chandef->cbw); |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| |
| // Now, calculate offset from the primary channel index to the center. |
| // |
| // Take primary channel 48 @80MHz channel as example: |
| // |
| // primary | freq | | mask=0xf | half |
| // channel |offset| chan num | (CBW80) |bandwidth=8 (40MHz) |
| // ============================================================================== |
| // |
| // 36 --------------------> 0 base ^ ^ |
| // base index | | |
| // 40 20 base + 4 | | |
| // -------------- | -------- | <----------- center freq/index |
| // 44 40 base + 8 | ^ v | 10Mhz |
| // | | |
| // 48 --------------------> 60 base + 12 | v --- This is offset_to_center. |
| // offset_to_base = 12 | |
| // 80 v |
| // |
| // We can get: |
| // |
| // offset_to_base = 48 - 36 # = 12 indexes |
| // mask = 0xf # 80MHz |
| // half_bandwidth = 8 # 40MHz |
| // center_index = 8 - 2 = 6 # 30MHz (offset to the base index) |
| // offset_to_center = 12 - 6 = 6 # 60MHz - 30MHz = +30MHz |
| // |
| uint8_t half_bandwith = (mask + 1) / 2; // # of channel indexes within the half bandwidth. |
| uint8_t center_index = half_bandwith - 2; // 2 indexes = 2 * 5MHz = 10Mhz. |
| int offset_to_center = (offset_to_base & mask) - center_index; |
| |
| const int mhz_per_index = 5; // 5 MHz for each channel index. |
| switch (offset_to_center * mhz_per_index) { |
| case -70: |
| return PHY_VHT_CTRL_POS_4_BELOW; |
| case -50: |
| return PHY_VHT_CTRL_POS_3_BELOW; |
| case -30: |
| return PHY_VHT_CTRL_POS_2_BELOW; |
| case -10: |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| case 10: |
| return PHY_VHT_CTRL_POS_1_ABOVE; |
| case 30: |
| return PHY_VHT_CTRL_POS_2_ABOVE; |
| case 50: |
| return PHY_VHT_CTRL_POS_3_ABOVE; |
| case 70: |
| return PHY_VHT_CTRL_POS_4_ABOVE; |
| default: |
| IWL_WARN(chandef, "Invalid channel definition (primary=%u cbw=%u)\n", chandef->primary, |
| chandef->cbw); |
| return PHY_VHT_CTRL_POS_1_BELOW; |
| } |
| } |
| |
| /* |
| * Construct the generic fields of the PHY context command |
| */ |
| static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt* ctxt, struct iwl_phy_context_cmd* cmd, |
| uint32_t action, uint32_t apply_time) { |
| memset(cmd, 0, sizeof(struct iwl_phy_context_cmd)); |
| |
| cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, ctxt->color)); |
| cmd->action = cpu_to_le32(action); |
| cmd->apply_time = cpu_to_le32(apply_time); |
| } |
| |
| /* |
| * Add the phy configuration to the PHY context command |
| */ |
| static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm* mvm, struct iwl_phy_context_cmd* cmd, |
| const wlan_channel_t* chandef, uint8_t chains_static, |
| uint8_t chains_dynamic) { |
| uint8_t active_cnt, idle_cnt; |
| |
| /* Set the channel info data */ |
| cmd->ci.band = |
| iwl_mvm_get_channel_band(chandef->primary) == WLAN_INFO_BAND_2GHZ ? PHY_BAND_24 : PHY_BAND_5; |
| |
| cmd->ci.channel = chandef->primary; |
| cmd->ci.width = iwl_mvm_get_channel_width(chandef); |
| cmd->ci.ctrl_pos = iwl_mvm_get_ctrl_pos(chandef); |
| |
| /* Set rx the chains */ |
| idle_cnt = chains_static; |
| active_cnt = chains_dynamic; |
| |
| #if 0 // NEEDS_PORTING |
| /* In scenarios where we only ever use a single-stream rates, |
| * i.e. legacy 11b/g/a associations, single-stream APs or even |
| * static SMPS, enable both chains to get diversity, improving |
| * the case where we're far enough from the AP that attenuation |
| * between the two antennas is sufficiently different to impact |
| * performance. |
| */ |
| if (active_cnt == 1 && iwl_mvm_rx_diversity_allowed(mvm)) { |
| idle_cnt = 2; |
| active_cnt = 2; |
| } |
| #endif // NEEDS_PORTING |
| |
| cmd->rxchain_info = cpu_to_le32(iwl_mvm_get_valid_rx_ant(mvm) << PHY_RX_CHAIN_VALID_POS); |
| cmd->rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); |
| cmd->rxchain_info |= cpu_to_le32(active_cnt << PHY_RX_CHAIN_MIMO_CNT_POS); |
| #ifdef CPTCFG_IWLWIFI_DEBUGFS |
| if (unlikely(mvm->dbgfs_rx_phyinfo)) { |
| cmd->rxchain_info = cpu_to_le32(mvm->dbgfs_rx_phyinfo); |
| } |
| #endif |
| |
| cmd->txchain_info = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); |
| } |
| |
| /* |
| * Send a command to apply the current phy configuration. The command is send |
| * only if something in the configuration changed: in case that this is the |
| * first time that the phy configuration is applied or in case that the phy |
| * configuration changed from the previous apply. |
| */ |
| static zx_status_t iwl_mvm_phy_ctxt_apply(struct iwl_mvm* mvm, struct iwl_mvm_phy_ctxt* ctxt, |
| const wlan_channel_t* chandef, uint8_t chains_static, |
| uint8_t chains_dynamic, uint32_t action, |
| uint32_t apply_time) { |
| struct iwl_phy_context_cmd cmd; |
| zx_status_t ret; |
| |
| /* Set the command header fields */ |
| iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action, apply_time); |
| |
| /* Set the command data */ |
| iwl_mvm_phy_ctxt_cmd_data(mvm, &cmd, chandef, chains_static, chains_dynamic); |
| |
| ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, 0, sizeof(struct iwl_phy_context_cmd), &cmd); |
| if (ret != ZX_OK) { |
| IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); |
| } |
| return ret; |
| } |
| |
| /* |
| * Send a command to add a PHY context based on the current HW configuration. |
| */ |
| zx_status_t iwl_mvm_phy_ctxt_add(struct iwl_mvm* mvm, struct iwl_mvm_phy_ctxt* ctxt, |
| wlan_channel_t* chandef, uint8_t chains_static, |
| uint8_t chains_dynamic) { |
| WARN_ON(!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && ctxt->ref); |
| iwl_assert_lock_held(&mvm->mutex); |
| |
| #ifdef CPTCFG_IWLWIFI_FRQ_MGR |
| ctxt->fm_tx_power_limit = IWL_DEFAULT_MAX_TX_POWER; |
| #endif |
| |
| return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, chains_static, chains_dynamic, |
| FW_CTXT_ACTION_ADD, 0); |
| } |
| |
| /* |
| * Update the number of references to the given PHY context. This is valid only |
| * in case the PHY context was already created, i.e., its reference count > 0. |
| */ |
| void iwl_mvm_phy_ctxt_ref(struct iwl_mvm* mvm, struct iwl_mvm_phy_ctxt* ctxt) { |
| iwl_assert_lock_held(&mvm->mutex); |
| ctxt->ref++; |
| } |
| |
| /* |
| * Send a command to modify the PHY context based on the current HW |
| * configuration. Note that the function does not check that the configuration |
| * changed. |
| */ |
| zx_status_t iwl_mvm_phy_ctxt_changed(struct iwl_mvm* mvm, struct iwl_mvm_phy_ctxt* ctxt, |
| const wlan_channel_t* chandef, uint8_t chains_static, |
| uint8_t chains_dynamic) { |
| enum iwl_ctxt_action action = FW_CTXT_ACTION_MODIFY; |
| |
| iwl_assert_lock_held(&mvm->mutex); |
| |
| if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && |
| iwl_mvm_get_channel_band(chandef->primary) != |
| iwl_mvm_get_channel_band(ctxt->chandef.primary)) { |
| zx_status_t ret; |
| |
| /* ... remove it here ...*/ |
| ret = iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, chains_static, chains_dynamic, |
| FW_CTXT_ACTION_REMOVE, 0); |
| if (ret != ZX_OK) { |
| return ret; |
| } |
| |
| /* ... and proceed to add it again */ |
| action = FW_CTXT_ACTION_ADD; |
| } |
| |
| ctxt->chandef = *chandef; |
| return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, chains_static, chains_dynamic, action, 0); |
| } |
| |
| zx_status_t iwl_mvm_phy_ctxt_unref(struct iwl_mvm* mvm, struct iwl_mvm_phy_ctxt* ctxt) { |
| iwl_assert_lock_held(&mvm->mutex); |
| |
| if (!ctxt) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (ctxt->ref == 0) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| ctxt->ref--; |
| |
| /* |
| * Move unused phy's to a default channel. When the phy is moved the, |
| * fw will cleanup immediate quiet bit if it was previously set, |
| * otherwise we might not be able to reuse this phy. |
| */ |
| if (ctxt->ref == 0) { |
| // TODO(45353): support MIMO Rx. |
| iwl_mvm_phy_ctxt_changed(mvm, ctxt, &default_channel, 1, 1); |
| } |
| |
| return ZX_OK; |
| } |
| |
| #if 0 // NEEDS_PORTING |
| static void iwl_mvm_binding_iterator(void* _data, uint8_t* mac, struct ieee80211_vif* vif) { |
| unsigned long* data = _data; |
| struct iwl_mvm_vif* mvmvif = iwl_mvm_vif_from_mac80211(vif); |
| |
| if (!mvmvif->phy_ctxt) { |
| return; |
| } |
| |
| if (vif->type == NL80211_IFTYPE_STATION || vif->type == NL80211_IFTYPE_AP) { |
| __set_bit(mvmvif->phy_ctxt->id, data); |
| } |
| } |
| |
| int iwl_mvm_phy_ctx_count(struct iwl_mvm* mvm) { |
| unsigned long phy_ctxt_counter = 0; |
| |
| ieee80211_iterate_active_interfaces_atomic(mvm->hw, IEEE80211_IFACE_ITER_NORMAL, |
| iwl_mvm_binding_iterator, &phy_ctxt_counter); |
| |
| return hweight8(phy_ctxt_counter); |
| } |
| #endif // NEEDS_PORTING |