| // SPDX-License-Identifier: ISC |
| /* |
| * Copyright (c) 2005-2011 Atheros Communications Inc. |
| * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. |
| * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include "mac.h" |
| |
| #include <net/cfg80211.h> |
| #include <net/mac80211.h> |
| #include <linux/etherdevice.h> |
| #include <linux/acpi.h> |
| #include <linux/of.h> |
| #include <linux/bitfield.h> |
| |
| #include "hif.h" |
| #include "core.h" |
| #include "debug.h" |
| #include "wmi.h" |
| #include "htt.h" |
| #include "txrx.h" |
| #include "testmode.h" |
| #include "wmi-tlv.h" |
| #include "wmi-ops.h" |
| #include "wow.h" |
| |
| /*********/ |
| /* Rates */ |
| /*********/ |
| |
| static struct ieee80211_rate ath10k_rates[] = { |
| { .bitrate = 10, |
| .hw_value = ATH10K_HW_RATE_CCK_LP_1M }, |
| { .bitrate = 20, |
| .hw_value = ATH10K_HW_RATE_CCK_LP_2M, |
| .hw_value_short = ATH10K_HW_RATE_CCK_SP_2M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 55, |
| .hw_value = ATH10K_HW_RATE_CCK_LP_5_5M, |
| .hw_value_short = ATH10K_HW_RATE_CCK_SP_5_5M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 110, |
| .hw_value = ATH10K_HW_RATE_CCK_LP_11M, |
| .hw_value_short = ATH10K_HW_RATE_CCK_SP_11M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| |
| { .bitrate = 60, .hw_value = ATH10K_HW_RATE_OFDM_6M }, |
| { .bitrate = 90, .hw_value = ATH10K_HW_RATE_OFDM_9M }, |
| { .bitrate = 120, .hw_value = ATH10K_HW_RATE_OFDM_12M }, |
| { .bitrate = 180, .hw_value = ATH10K_HW_RATE_OFDM_18M }, |
| { .bitrate = 240, .hw_value = ATH10K_HW_RATE_OFDM_24M }, |
| { .bitrate = 360, .hw_value = ATH10K_HW_RATE_OFDM_36M }, |
| { .bitrate = 480, .hw_value = ATH10K_HW_RATE_OFDM_48M }, |
| { .bitrate = 540, .hw_value = ATH10K_HW_RATE_OFDM_54M }, |
| }; |
| |
| static struct ieee80211_rate ath10k_rates_rev2[] = { |
| { .bitrate = 10, |
| .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_1M }, |
| { .bitrate = 20, |
| .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_2M, |
| .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_2M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 55, |
| .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_5_5M, |
| .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_5_5M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 110, |
| .hw_value = ATH10K_HW_RATE_REV2_CCK_LP_11M, |
| .hw_value_short = ATH10K_HW_RATE_REV2_CCK_SP_11M, |
| .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| |
| { .bitrate = 60, .hw_value = ATH10K_HW_RATE_OFDM_6M }, |
| { .bitrate = 90, .hw_value = ATH10K_HW_RATE_OFDM_9M }, |
| { .bitrate = 120, .hw_value = ATH10K_HW_RATE_OFDM_12M }, |
| { .bitrate = 180, .hw_value = ATH10K_HW_RATE_OFDM_18M }, |
| { .bitrate = 240, .hw_value = ATH10K_HW_RATE_OFDM_24M }, |
| { .bitrate = 360, .hw_value = ATH10K_HW_RATE_OFDM_36M }, |
| { .bitrate = 480, .hw_value = ATH10K_HW_RATE_OFDM_48M }, |
| { .bitrate = 540, .hw_value = ATH10K_HW_RATE_OFDM_54M }, |
| }; |
| |
| static const struct cfg80211_sar_freq_ranges ath10k_sar_freq_ranges[] = { |
| {.start_freq = 2402, .end_freq = 2494 }, |
| {.start_freq = 5170, .end_freq = 5875 }, |
| }; |
| |
| static const struct cfg80211_sar_capa ath10k_sar_capa = { |
| .type = NL80211_SAR_TYPE_POWER, |
| .num_freq_ranges = (ARRAY_SIZE(ath10k_sar_freq_ranges)), |
| .freq_ranges = &ath10k_sar_freq_ranges[0], |
| }; |
| |
| #define ATH10K_MAC_FIRST_OFDM_RATE_IDX 4 |
| |
| #define ath10k_a_rates (ath10k_rates + ATH10K_MAC_FIRST_OFDM_RATE_IDX) |
| #define ath10k_a_rates_size (ARRAY_SIZE(ath10k_rates) - \ |
| ATH10K_MAC_FIRST_OFDM_RATE_IDX) |
| #define ath10k_g_rates (ath10k_rates + 0) |
| #define ath10k_g_rates_size (ARRAY_SIZE(ath10k_rates)) |
| |
| #define ath10k_g_rates_rev2 (ath10k_rates_rev2 + 0) |
| #define ath10k_g_rates_rev2_size (ARRAY_SIZE(ath10k_rates_rev2)) |
| |
| #define ath10k_wmi_legacy_rates ath10k_rates |
| |
| static bool ath10k_mac_bitrate_is_cck(int bitrate) |
| { |
| switch (bitrate) { |
| case 10: |
| case 20: |
| case 55: |
| case 110: |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static u8 ath10k_mac_bitrate_to_rate(int bitrate) |
| { |
| return DIV_ROUND_UP(bitrate, 5) | |
| (ath10k_mac_bitrate_is_cck(bitrate) ? BIT(7) : 0); |
| } |
| |
| u8 ath10k_mac_hw_rate_to_idx(const struct ieee80211_supported_band *sband, |
| u8 hw_rate, bool cck) |
| { |
| const struct ieee80211_rate *rate; |
| int i; |
| |
| for (i = 0; i < sband->n_bitrates; i++) { |
| rate = &sband->bitrates[i]; |
| |
| if (ath10k_mac_bitrate_is_cck(rate->bitrate) != cck) |
| continue; |
| |
| if (rate->hw_value == hw_rate) |
| return i; |
| else if (rate->flags & IEEE80211_RATE_SHORT_PREAMBLE && |
| rate->hw_value_short == hw_rate) |
| return i; |
| } |
| |
| return 0; |
| } |
| |
| u8 ath10k_mac_bitrate_to_idx(const struct ieee80211_supported_band *sband, |
| u32 bitrate) |
| { |
| int i; |
| |
| for (i = 0; i < sband->n_bitrates; i++) |
| if (sband->bitrates[i].bitrate == bitrate) |
| return i; |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_get_rate_hw_value(int bitrate) |
| { |
| int i; |
| u8 hw_value_prefix = 0; |
| |
| if (ath10k_mac_bitrate_is_cck(bitrate)) |
| hw_value_prefix = WMI_RATE_PREAMBLE_CCK << 6; |
| |
| for (i = 0; i < ARRAY_SIZE(ath10k_rates); i++) { |
| if (ath10k_rates[i].bitrate == bitrate) |
| return hw_value_prefix | ath10k_rates[i].hw_value; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int ath10k_mac_get_max_vht_mcs_map(u16 mcs_map, int nss) |
| { |
| switch ((mcs_map >> (2 * nss)) & 0x3) { |
| case IEEE80211_VHT_MCS_SUPPORT_0_7: return BIT(8) - 1; |
| case IEEE80211_VHT_MCS_SUPPORT_0_8: return BIT(9) - 1; |
| case IEEE80211_VHT_MCS_SUPPORT_0_9: return BIT(10) - 1; |
| } |
| return 0; |
| } |
| |
| static u32 |
| ath10k_mac_max_ht_nss(const u8 ht_mcs_mask[IEEE80211_HT_MCS_MASK_LEN]) |
| { |
| int nss; |
| |
| for (nss = IEEE80211_HT_MCS_MASK_LEN - 1; nss >= 0; nss--) |
| if (ht_mcs_mask[nss]) |
| return nss + 1; |
| |
| return 1; |
| } |
| |
| static u32 |
| ath10k_mac_max_vht_nss(const u16 vht_mcs_mask[NL80211_VHT_NSS_MAX]) |
| { |
| int nss; |
| |
| for (nss = NL80211_VHT_NSS_MAX - 1; nss >= 0; nss--) |
| if (vht_mcs_mask[nss]) |
| return nss + 1; |
| |
| return 1; |
| } |
| |
| int ath10k_mac_ext_resource_config(struct ath10k *ar, u32 val) |
| { |
| enum wmi_host_platform_type platform_type; |
| int ret; |
| |
| if (test_bit(WMI_SERVICE_TX_MODE_DYNAMIC, ar->wmi.svc_map)) |
| platform_type = WMI_HOST_PLATFORM_LOW_PERF; |
| else |
| platform_type = WMI_HOST_PLATFORM_HIGH_PERF; |
| |
| ret = ath10k_wmi_ext_resource_config(ar, platform_type, val); |
| |
| if (ret && ret != -EOPNOTSUPP) { |
| ath10k_warn(ar, "failed to configure ext resource: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /**********/ |
| /* Crypto */ |
| /**********/ |
| |
| static int ath10k_send_key(struct ath10k_vif *arvif, |
| struct ieee80211_key_conf *key, |
| enum set_key_cmd cmd, |
| const u8 *macaddr, u32 flags) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct wmi_vdev_install_key_arg arg = { |
| .vdev_id = arvif->vdev_id, |
| .key_idx = key->keyidx, |
| .key_len = key->keylen, |
| .key_data = key->key, |
| .key_flags = flags, |
| .macaddr = macaddr, |
| }; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| switch (key->cipher) { |
| case WLAN_CIPHER_SUITE_CCMP: |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_CCM]; |
| key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV_MGMT; |
| break; |
| case WLAN_CIPHER_SUITE_TKIP: |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_TKIP]; |
| arg.key_txmic_len = 8; |
| arg.key_rxmic_len = 8; |
| break; |
| case WLAN_CIPHER_SUITE_WEP40: |
| case WLAN_CIPHER_SUITE_WEP104: |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_WEP]; |
| break; |
| case WLAN_CIPHER_SUITE_CCMP_256: |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_CCM]; |
| break; |
| case WLAN_CIPHER_SUITE_GCMP: |
| case WLAN_CIPHER_SUITE_GCMP_256: |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_GCM]; |
| key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV_MGMT; |
| break; |
| case WLAN_CIPHER_SUITE_BIP_GMAC_128: |
| case WLAN_CIPHER_SUITE_BIP_GMAC_256: |
| case WLAN_CIPHER_SUITE_BIP_CMAC_256: |
| case WLAN_CIPHER_SUITE_AES_CMAC: |
| WARN_ON(1); |
| return -EINVAL; |
| default: |
| ath10k_warn(ar, "cipher %d is not supported\n", key->cipher); |
| return -EOPNOTSUPP; |
| } |
| |
| if (test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) |
| key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; |
| |
| if (cmd == DISABLE_KEY) { |
| arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_NONE]; |
| arg.key_data = NULL; |
| } |
| |
| return ath10k_wmi_vdev_install_key(arvif->ar, &arg); |
| } |
| |
| static int ath10k_install_key(struct ath10k_vif *arvif, |
| struct ieee80211_key_conf *key, |
| enum set_key_cmd cmd, |
| const u8 *macaddr, u32 flags) |
| { |
| struct ath10k *ar = arvif->ar; |
| int ret; |
| unsigned long time_left; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| reinit_completion(&ar->install_key_done); |
| |
| if (arvif->nohwcrypt) |
| return 1; |
| |
| ret = ath10k_send_key(arvif, key, cmd, macaddr, flags); |
| if (ret) |
| return ret; |
| |
| time_left = wait_for_completion_timeout(&ar->install_key_done, 3 * HZ); |
| if (time_left == 0) |
| return -ETIMEDOUT; |
| |
| return 0; |
| } |
| |
| static int ath10k_install_peer_wep_keys(struct ath10k_vif *arvif, |
| const u8 *addr) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ath10k_peer *peer; |
| int ret; |
| int i; |
| u32 flags; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (WARN_ON(arvif->vif->type != NL80211_IFTYPE_AP && |
| arvif->vif->type != NL80211_IFTYPE_ADHOC && |
| arvif->vif->type != NL80211_IFTYPE_MESH_POINT)) |
| return -EINVAL; |
| |
| spin_lock_bh(&ar->data_lock); |
| peer = ath10k_peer_find(ar, arvif->vdev_id, addr); |
| spin_unlock_bh(&ar->data_lock); |
| |
| if (!peer) |
| return -ENOENT; |
| |
| for (i = 0; i < ARRAY_SIZE(arvif->wep_keys); i++) { |
| if (arvif->wep_keys[i] == NULL) |
| continue; |
| |
| switch (arvif->vif->type) { |
| case NL80211_IFTYPE_AP: |
| flags = WMI_KEY_PAIRWISE; |
| |
| if (arvif->def_wep_key_idx == i) |
| flags |= WMI_KEY_TX_USAGE; |
| |
| ret = ath10k_install_key(arvif, arvif->wep_keys[i], |
| SET_KEY, addr, flags); |
| if (ret < 0) |
| return ret; |
| break; |
| case NL80211_IFTYPE_ADHOC: |
| ret = ath10k_install_key(arvif, arvif->wep_keys[i], |
| SET_KEY, addr, |
| WMI_KEY_PAIRWISE); |
| if (ret < 0) |
| return ret; |
| |
| ret = ath10k_install_key(arvif, arvif->wep_keys[i], |
| SET_KEY, addr, WMI_KEY_GROUP); |
| if (ret < 0) |
| return ret; |
| break; |
| default: |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| spin_lock_bh(&ar->data_lock); |
| peer->keys[i] = arvif->wep_keys[i]; |
| spin_unlock_bh(&ar->data_lock); |
| } |
| |
| /* In some cases (notably with static WEP IBSS with multiple keys) |
| * multicast Tx becomes broken. Both pairwise and groupwise keys are |
| * installed already. Using WMI_KEY_TX_USAGE in different combinations |
| * didn't seem help. Using def_keyid vdev parameter seems to be |
| * effective so use that. |
| * |
| * FIXME: Revisit. Perhaps this can be done in a less hacky way. |
| */ |
| if (arvif->vif->type != NL80211_IFTYPE_ADHOC) |
| return 0; |
| |
| if (arvif->def_wep_key_idx == -1) |
| return 0; |
| |
| ret = ath10k_wmi_vdev_set_param(arvif->ar, |
| arvif->vdev_id, |
| arvif->ar->wmi.vdev_param->def_keyid, |
| arvif->def_wep_key_idx); |
| if (ret) { |
| ath10k_warn(ar, "failed to re-set def wpa key idxon vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_clear_peer_keys(struct ath10k_vif *arvif, |
| const u8 *addr) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ath10k_peer *peer; |
| int first_errno = 0; |
| int ret; |
| int i; |
| u32 flags = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| spin_lock_bh(&ar->data_lock); |
| peer = ath10k_peer_find(ar, arvif->vdev_id, addr); |
| spin_unlock_bh(&ar->data_lock); |
| |
| if (!peer) |
| return -ENOENT; |
| |
| for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { |
| if (peer->keys[i] == NULL) |
| continue; |
| |
| /* key flags are not required to delete the key */ |
| ret = ath10k_install_key(arvif, peer->keys[i], |
| DISABLE_KEY, addr, flags); |
| if (ret < 0 && first_errno == 0) |
| first_errno = ret; |
| |
| if (ret < 0) |
| ath10k_warn(ar, "failed to remove peer wep key %d: %d\n", |
| i, ret); |
| |
| spin_lock_bh(&ar->data_lock); |
| peer->keys[i] = NULL; |
| spin_unlock_bh(&ar->data_lock); |
| } |
| |
| return first_errno; |
| } |
| |
| bool ath10k_mac_is_peer_wep_key_set(struct ath10k *ar, const u8 *addr, |
| u8 keyidx) |
| { |
| struct ath10k_peer *peer; |
| int i; |
| |
| lockdep_assert_held(&ar->data_lock); |
| |
| /* We don't know which vdev this peer belongs to, |
| * since WMI doesn't give us that information. |
| * |
| * FIXME: multi-bss needs to be handled. |
| */ |
| peer = ath10k_peer_find(ar, 0, addr); |
| if (!peer) |
| return false; |
| |
| for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { |
| if (peer->keys[i] && peer->keys[i]->keyidx == keyidx) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int ath10k_clear_vdev_key(struct ath10k_vif *arvif, |
| struct ieee80211_key_conf *key) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ath10k_peer *peer; |
| u8 addr[ETH_ALEN]; |
| int first_errno = 0; |
| int ret; |
| int i; |
| u32 flags = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| for (;;) { |
| /* since ath10k_install_key we can't hold data_lock all the |
| * time, so we try to remove the keys incrementally |
| */ |
| spin_lock_bh(&ar->data_lock); |
| i = 0; |
| list_for_each_entry(peer, &ar->peers, list) { |
| for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { |
| if (peer->keys[i] == key) { |
| ether_addr_copy(addr, peer->addr); |
| peer->keys[i] = NULL; |
| break; |
| } |
| } |
| |
| if (i < ARRAY_SIZE(peer->keys)) |
| break; |
| } |
| spin_unlock_bh(&ar->data_lock); |
| |
| if (i == ARRAY_SIZE(peer->keys)) |
| break; |
| /* key flags are not required to delete the key */ |
| ret = ath10k_install_key(arvif, key, DISABLE_KEY, addr, flags); |
| if (ret < 0 && first_errno == 0) |
| first_errno = ret; |
| |
| if (ret) |
| ath10k_warn(ar, "failed to remove key for %pM: %d\n", |
| addr, ret); |
| } |
| |
| return first_errno; |
| } |
| |
| static int ath10k_mac_vif_update_wep_key(struct ath10k_vif *arvif, |
| struct ieee80211_key_conf *key) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ath10k_peer *peer; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| list_for_each_entry(peer, &ar->peers, list) { |
| if (ether_addr_equal(peer->addr, arvif->vif->addr)) |
| continue; |
| |
| if (ether_addr_equal(peer->addr, arvif->bssid)) |
| continue; |
| |
| if (peer->keys[key->keyidx] == key) |
| continue; |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vif vdev %i update key %i needs update\n", |
| arvif->vdev_id, key->keyidx); |
| |
| ret = ath10k_install_peer_wep_keys(arvif, peer->addr); |
| if (ret) { |
| ath10k_warn(ar, "failed to update wep keys on vdev %i for peer %pM: %d\n", |
| arvif->vdev_id, peer->addr, ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /*********************/ |
| /* General utilities */ |
| /*********************/ |
| |
| static inline enum wmi_phy_mode |
| chan_to_phymode(const struct cfg80211_chan_def *chandef) |
| { |
| enum wmi_phy_mode phymode = MODE_UNKNOWN; |
| |
| switch (chandef->chan->band) { |
| case NL80211_BAND_2GHZ: |
| switch (chandef->width) { |
| case NL80211_CHAN_WIDTH_20_NOHT: |
| if (chandef->chan->flags & IEEE80211_CHAN_NO_OFDM) |
| phymode = MODE_11B; |
| else |
| phymode = MODE_11G; |
| break; |
| case NL80211_CHAN_WIDTH_20: |
| phymode = MODE_11NG_HT20; |
| break; |
| case NL80211_CHAN_WIDTH_40: |
| phymode = MODE_11NG_HT40; |
| break; |
| default: |
| phymode = MODE_UNKNOWN; |
| break; |
| } |
| break; |
| case NL80211_BAND_5GHZ: |
| switch (chandef->width) { |
| case NL80211_CHAN_WIDTH_20_NOHT: |
| phymode = MODE_11A; |
| break; |
| case NL80211_CHAN_WIDTH_20: |
| phymode = MODE_11NA_HT20; |
| break; |
| case NL80211_CHAN_WIDTH_40: |
| phymode = MODE_11NA_HT40; |
| break; |
| case NL80211_CHAN_WIDTH_80: |
| phymode = MODE_11AC_VHT80; |
| break; |
| case NL80211_CHAN_WIDTH_160: |
| phymode = MODE_11AC_VHT160; |
| break; |
| case NL80211_CHAN_WIDTH_80P80: |
| phymode = MODE_11AC_VHT80_80; |
| break; |
| default: |
| phymode = MODE_UNKNOWN; |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| WARN_ON(phymode == MODE_UNKNOWN); |
| return phymode; |
| } |
| |
| static u8 ath10k_parse_mpdudensity(u8 mpdudensity) |
| { |
| /* |
| * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing": |
| * 0 for no restriction |
| * 1 for 1/4 us |
| * 2 for 1/2 us |
| * 3 for 1 us |
| * 4 for 2 us |
| * 5 for 4 us |
| * 6 for 8 us |
| * 7 for 16 us |
| */ |
| switch (mpdudensity) { |
| case 0: |
| return 0; |
| case 1: |
| case 2: |
| case 3: |
| /* Our lower layer calculations limit our precision to |
| * 1 microsecond |
| */ |
| return 1; |
| case 4: |
| return 2; |
| case 5: |
| return 4; |
| case 6: |
| return 8; |
| case 7: |
| return 16; |
| default: |
| return 0; |
| } |
| } |
| |
| int ath10k_mac_vif_chan(struct ieee80211_vif *vif, |
| struct cfg80211_chan_def *def) |
| { |
| struct ieee80211_chanctx_conf *conf; |
| |
| rcu_read_lock(); |
| conf = rcu_dereference(vif->chanctx_conf); |
| if (!conf) { |
| rcu_read_unlock(); |
| return -ENOENT; |
| } |
| |
| *def = conf->def; |
| rcu_read_unlock(); |
| |
| return 0; |
| } |
| |
| static void ath10k_mac_num_chanctxs_iter(struct ieee80211_hw *hw, |
| struct ieee80211_chanctx_conf *conf, |
| void *data) |
| { |
| int *num = data; |
| |
| (*num)++; |
| } |
| |
| static int ath10k_mac_num_chanctxs(struct ath10k *ar) |
| { |
| int num = 0; |
| |
| ieee80211_iter_chan_contexts_atomic(ar->hw, |
| ath10k_mac_num_chanctxs_iter, |
| &num); |
| |
| return num; |
| } |
| |
| static void |
| ath10k_mac_get_any_chandef_iter(struct ieee80211_hw *hw, |
| struct ieee80211_chanctx_conf *conf, |
| void *data) |
| { |
| struct cfg80211_chan_def **def = data; |
| |
| *def = &conf->def; |
| } |
| |
| static void ath10k_wait_for_peer_delete_done(struct ath10k *ar, u32 vdev_id, |
| const u8 *addr) |
| { |
| unsigned long time_left; |
| int ret; |
| |
| if (test_bit(WMI_SERVICE_SYNC_DELETE_CMDS, ar->wmi.svc_map)) { |
| ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr); |
| if (ret) { |
| ath10k_warn(ar, "failed wait for peer deleted"); |
| return; |
| } |
| |
| time_left = wait_for_completion_timeout(&ar->peer_delete_done, |
| 5 * HZ); |
| if (!time_left) |
| ath10k_warn(ar, "Timeout in receiving peer delete response\n"); |
| } |
| } |
| |
| static int ath10k_peer_create(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| u32 vdev_id, |
| const u8 *addr, |
| enum wmi_peer_type peer_type) |
| { |
| struct ath10k_vif *arvif; |
| struct ath10k_peer *peer; |
| int num_peers = 0; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| num_peers = ar->num_peers; |
| |
| /* Each vdev consumes a peer entry as well */ |
| list_for_each_entry(arvif, &ar->arvifs, list) |
| num_peers++; |
| |
| if (num_peers >= ar->max_num_peers) |
| return -ENOBUFS; |
| |
| ret = ath10k_wmi_peer_create(ar, vdev_id, addr, peer_type); |
| if (ret) { |
| ath10k_warn(ar, "failed to create wmi peer %pM on vdev %i: %i\n", |
| addr, vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_wait_for_peer_created(ar, vdev_id, addr); |
| if (ret) { |
| ath10k_warn(ar, "failed to wait for created wmi peer %pM on vdev %i: %i\n", |
| addr, vdev_id, ret); |
| return ret; |
| } |
| |
| spin_lock_bh(&ar->data_lock); |
| |
| peer = ath10k_peer_find(ar, vdev_id, addr); |
| if (!peer) { |
| spin_unlock_bh(&ar->data_lock); |
| ath10k_warn(ar, "failed to find peer %pM on vdev %i after creation\n", |
| addr, vdev_id); |
| ath10k_wait_for_peer_delete_done(ar, vdev_id, addr); |
| return -ENOENT; |
| } |
| |
| peer->vif = vif; |
| peer->sta = sta; |
| |
| spin_unlock_bh(&ar->data_lock); |
| |
| ar->num_peers++; |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_set_kickout(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 param; |
| int ret; |
| |
| param = ar->wmi.pdev_param->sta_kickout_th; |
| ret = ath10k_wmi_pdev_set_param(ar, param, |
| ATH10K_KICKOUT_THRESHOLD); |
| if (ret) { |
| ath10k_warn(ar, "failed to set kickout threshold on vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| param = ar->wmi.vdev_param->ap_keepalive_min_idle_inactive_time_secs; |
| ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param, |
| ATH10K_KEEPALIVE_MIN_IDLE); |
| if (ret) { |
| ath10k_warn(ar, "failed to set keepalive minimum idle time on vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| param = ar->wmi.vdev_param->ap_keepalive_max_idle_inactive_time_secs; |
| ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param, |
| ATH10K_KEEPALIVE_MAX_IDLE); |
| if (ret) { |
| ath10k_warn(ar, "failed to set keepalive maximum idle time on vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| param = ar->wmi.vdev_param->ap_keepalive_max_unresponsive_time_secs; |
| ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param, |
| ATH10K_KEEPALIVE_MAX_UNRESPONSIVE); |
| if (ret) { |
| ath10k_warn(ar, "failed to set keepalive maximum unresponsive time on vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_set_rts(struct ath10k_vif *arvif, u32 value) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 vdev_param; |
| |
| vdev_param = ar->wmi.vdev_param->rts_threshold; |
| return ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, vdev_param, value); |
| } |
| |
| static int ath10k_peer_delete(struct ath10k *ar, u32 vdev_id, const u8 *addr) |
| { |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ret = ath10k_wmi_peer_delete(ar, vdev_id, addr); |
| if (ret) |
| return ret; |
| |
| ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr); |
| if (ret) |
| return ret; |
| |
| if (test_bit(WMI_SERVICE_SYNC_DELETE_CMDS, ar->wmi.svc_map)) { |
| unsigned long time_left; |
| |
| time_left = wait_for_completion_timeout |
| (&ar->peer_delete_done, 5 * HZ); |
| |
| if (!time_left) { |
| ath10k_warn(ar, "Timeout in receiving peer delete response\n"); |
| return -ETIMEDOUT; |
| } |
| } |
| |
| ar->num_peers--; |
| |
| return 0; |
| } |
| |
| static void ath10k_peer_cleanup(struct ath10k *ar, u32 vdev_id) |
| { |
| struct ath10k_peer *peer, *tmp; |
| int peer_id; |
| int i; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| spin_lock_bh(&ar->data_lock); |
| list_for_each_entry_safe(peer, tmp, &ar->peers, list) { |
| if (peer->vdev_id != vdev_id) |
| continue; |
| |
| ath10k_warn(ar, "removing stale peer %pM from vdev_id %d\n", |
| peer->addr, vdev_id); |
| |
| for_each_set_bit(peer_id, peer->peer_ids, |
| ATH10K_MAX_NUM_PEER_IDS) { |
| ar->peer_map[peer_id] = NULL; |
| } |
| |
| /* Double check that peer is properly un-referenced from |
| * the peer_map |
| */ |
| for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) { |
| if (ar->peer_map[i] == peer) { |
| ath10k_warn(ar, "removing stale peer_map entry for %pM (ptr %pK idx %d)\n", |
| peer->addr, peer, i); |
| ar->peer_map[i] = NULL; |
| } |
| } |
| |
| list_del(&peer->list); |
| kfree(peer); |
| ar->num_peers--; |
| } |
| spin_unlock_bh(&ar->data_lock); |
| } |
| |
| static void ath10k_peer_cleanup_all(struct ath10k *ar) |
| { |
| struct ath10k_peer *peer, *tmp; |
| int i; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| spin_lock_bh(&ar->data_lock); |
| list_for_each_entry_safe(peer, tmp, &ar->peers, list) { |
| list_del(&peer->list); |
| kfree(peer); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) |
| ar->peer_map[i] = NULL; |
| |
| spin_unlock_bh(&ar->data_lock); |
| |
| ar->num_peers = 0; |
| ar->num_stations = 0; |
| } |
| |
| static int ath10k_mac_tdls_peer_update(struct ath10k *ar, u32 vdev_id, |
| struct ieee80211_sta *sta, |
| enum wmi_tdls_peer_state state) |
| { |
| int ret; |
| struct wmi_tdls_peer_update_cmd_arg arg = {}; |
| struct wmi_tdls_peer_capab_arg cap = {}; |
| struct wmi_channel_arg chan_arg = {}; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| arg.vdev_id = vdev_id; |
| arg.peer_state = state; |
| ether_addr_copy(arg.addr, sta->addr); |
| |
| cap.peer_max_sp = sta->max_sp; |
| cap.peer_uapsd_queues = sta->uapsd_queues; |
| |
| if (state == WMI_TDLS_PEER_STATE_CONNECTED && |
| !sta->tdls_initiator) |
| cap.is_peer_responder = 1; |
| |
| ret = ath10k_wmi_tdls_peer_update(ar, &arg, &cap, &chan_arg); |
| if (ret) { |
| ath10k_warn(ar, "failed to update tdls peer %pM on vdev %i: %i\n", |
| arg.addr, vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /************************/ |
| /* Interface management */ |
| /************************/ |
| |
| void ath10k_mac_vif_beacon_free(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| |
| lockdep_assert_held(&ar->data_lock); |
| |
| if (!arvif->beacon) |
| return; |
| |
| if (!arvif->beacon_buf) |
| dma_unmap_single(ar->dev, ATH10K_SKB_CB(arvif->beacon)->paddr, |
| arvif->beacon->len, DMA_TO_DEVICE); |
| |
| if (WARN_ON(arvif->beacon_state != ATH10K_BEACON_SCHEDULED && |
| arvif->beacon_state != ATH10K_BEACON_SENT)) |
| return; |
| |
| dev_kfree_skb_any(arvif->beacon); |
| |
| arvif->beacon = NULL; |
| arvif->beacon_state = ATH10K_BEACON_SCHEDULED; |
| } |
| |
| static void ath10k_mac_vif_beacon_cleanup(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| |
| lockdep_assert_held(&ar->data_lock); |
| |
| ath10k_mac_vif_beacon_free(arvif); |
| |
| if (arvif->beacon_buf) { |
| if (ar->bus_param.dev_type == ATH10K_DEV_TYPE_HL) |
| kfree(arvif->beacon_buf); |
| else |
| dma_free_coherent(ar->dev, IEEE80211_MAX_FRAME_LEN, |
| arvif->beacon_buf, |
| arvif->beacon_paddr); |
| arvif->beacon_buf = NULL; |
| } |
| } |
| |
| static inline int ath10k_vdev_setup_sync(struct ath10k *ar) |
| { |
| unsigned long time_left; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) |
| return -ESHUTDOWN; |
| |
| time_left = wait_for_completion_timeout(&ar->vdev_setup_done, |
| ATH10K_VDEV_SETUP_TIMEOUT_HZ); |
| if (time_left == 0) |
| return -ETIMEDOUT; |
| |
| return ar->last_wmi_vdev_start_status; |
| } |
| |
| static int ath10k_monitor_vdev_start(struct ath10k *ar, int vdev_id) |
| { |
| struct cfg80211_chan_def *chandef = NULL; |
| struct ieee80211_channel *channel = NULL; |
| struct wmi_vdev_start_request_arg arg = {}; |
| int ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ieee80211_iter_chan_contexts_atomic(ar->hw, |
| ath10k_mac_get_any_chandef_iter, |
| &chandef); |
| if (WARN_ON_ONCE(!chandef)) |
| return -ENOENT; |
| |
| channel = chandef->chan; |
| |
| arg.vdev_id = vdev_id; |
| arg.channel.freq = channel->center_freq; |
| arg.channel.band_center_freq1 = chandef->center_freq1; |
| arg.channel.band_center_freq2 = chandef->center_freq2; |
| |
| /* TODO setup this dynamically, what in case we |
| * don't have any vifs? |
| */ |
| arg.channel.mode = chan_to_phymode(chandef); |
| arg.channel.chan_radar = |
| !!(channel->flags & IEEE80211_CHAN_RADAR); |
| |
| arg.channel.min_power = 0; |
| arg.channel.max_power = channel->max_power * 2; |
| arg.channel.max_reg_power = channel->max_reg_power * 2; |
| arg.channel.max_antenna_gain = channel->max_antenna_gain; |
| |
| reinit_completion(&ar->vdev_setup_done); |
| reinit_completion(&ar->vdev_delete_done); |
| |
| ret = ath10k_wmi_vdev_start(ar, &arg); |
| if (ret) { |
| ath10k_warn(ar, "failed to request monitor vdev %i start: %d\n", |
| vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_vdev_setup_sync(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to synchronize setup for monitor vdev %i start: %d\n", |
| vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_wmi_vdev_up(ar, vdev_id, 0, ar->mac_addr); |
| if (ret) { |
| ath10k_warn(ar, "failed to put up monitor vdev %i: %d\n", |
| vdev_id, ret); |
| goto vdev_stop; |
| } |
| |
| ar->monitor_vdev_id = vdev_id; |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor vdev %i started\n", |
| ar->monitor_vdev_id); |
| return 0; |
| |
| vdev_stop: |
| ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id); |
| if (ret) |
| ath10k_warn(ar, "failed to stop monitor vdev %i after start failure: %d\n", |
| ar->monitor_vdev_id, ret); |
| |
| return ret; |
| } |
| |
| static int ath10k_monitor_vdev_stop(struct ath10k *ar) |
| { |
| int ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ret = ath10k_wmi_vdev_down(ar, ar->monitor_vdev_id); |
| if (ret) |
| ath10k_warn(ar, "failed to put down monitor vdev %i: %d\n", |
| ar->monitor_vdev_id, ret); |
| |
| reinit_completion(&ar->vdev_setup_done); |
| reinit_completion(&ar->vdev_delete_done); |
| |
| ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id); |
| if (ret) |
| ath10k_warn(ar, "failed to request monitor vdev %i stop: %d\n", |
| ar->monitor_vdev_id, ret); |
| |
| ret = ath10k_vdev_setup_sync(ar); |
| if (ret) |
| ath10k_warn(ar, "failed to synchronize monitor vdev %i stop: %d\n", |
| ar->monitor_vdev_id, ret); |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor vdev %i stopped\n", |
| ar->monitor_vdev_id); |
| return ret; |
| } |
| |
| static int ath10k_monitor_vdev_create(struct ath10k *ar) |
| { |
| int bit, ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (ar->free_vdev_map == 0) { |
| ath10k_warn(ar, "failed to find free vdev id for monitor vdev\n"); |
| return -ENOMEM; |
| } |
| |
| bit = __ffs64(ar->free_vdev_map); |
| |
| ar->monitor_vdev_id = bit; |
| |
| ret = ath10k_wmi_vdev_create(ar, ar->monitor_vdev_id, |
| WMI_VDEV_TYPE_MONITOR, |
| 0, ar->mac_addr); |
| if (ret) { |
| ath10k_warn(ar, "failed to request monitor vdev %i creation: %d\n", |
| ar->monitor_vdev_id, ret); |
| return ret; |
| } |
| |
| ar->free_vdev_map &= ~(1LL << ar->monitor_vdev_id); |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor vdev %d created\n", |
| ar->monitor_vdev_id); |
| |
| return 0; |
| } |
| |
| static int ath10k_monitor_vdev_delete(struct ath10k *ar) |
| { |
| int ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ret = ath10k_wmi_vdev_delete(ar, ar->monitor_vdev_id); |
| if (ret) { |
| ath10k_warn(ar, "failed to request wmi monitor vdev %i removal: %d\n", |
| ar->monitor_vdev_id, ret); |
| return ret; |
| } |
| |
| ar->free_vdev_map |= 1LL << ar->monitor_vdev_id; |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor vdev %d deleted\n", |
| ar->monitor_vdev_id); |
| return ret; |
| } |
| |
| static int ath10k_monitor_start(struct ath10k *ar) |
| { |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ret = ath10k_monitor_vdev_create(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to create monitor vdev: %d\n", ret); |
| return ret; |
| } |
| |
| ret = ath10k_monitor_vdev_start(ar, ar->monitor_vdev_id); |
| if (ret) { |
| ath10k_warn(ar, "failed to start monitor vdev: %d\n", ret); |
| ath10k_monitor_vdev_delete(ar); |
| return ret; |
| } |
| |
| ar->monitor_started = true; |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor started\n"); |
| |
| return 0; |
| } |
| |
| static int ath10k_monitor_stop(struct ath10k *ar) |
| { |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ret = ath10k_monitor_vdev_stop(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to stop monitor vdev: %d\n", ret); |
| return ret; |
| } |
| |
| ret = ath10k_monitor_vdev_delete(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to delete monitor vdev: %d\n", ret); |
| return ret; |
| } |
| |
| ar->monitor_started = false; |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor stopped\n"); |
| |
| return 0; |
| } |
| |
| static bool ath10k_mac_monitor_vdev_is_needed(struct ath10k *ar) |
| { |
| int num_ctx; |
| |
| /* At least one chanctx is required to derive a channel to start |
| * monitor vdev on. |
| */ |
| num_ctx = ath10k_mac_num_chanctxs(ar); |
| if (num_ctx == 0) |
| return false; |
| |
| /* If there's already an existing special monitor interface then don't |
| * bother creating another monitor vdev. |
| */ |
| if (ar->monitor_arvif) |
| return false; |
| |
| return ar->monitor || |
| (!test_bit(ATH10K_FW_FEATURE_ALLOWS_MESH_BCAST, |
| ar->running_fw->fw_file.fw_features) && |
| (ar->filter_flags & FIF_OTHER_BSS)) || |
| test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags); |
| } |
| |
| static bool ath10k_mac_monitor_vdev_is_allowed(struct ath10k *ar) |
| { |
| int num_ctx; |
| |
| num_ctx = ath10k_mac_num_chanctxs(ar); |
| |
| /* FIXME: Current interface combinations and cfg80211/mac80211 code |
| * shouldn't allow this but make sure to prevent handling the following |
| * case anyway since multi-channel DFS hasn't been tested at all. |
| */ |
| if (test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags) && num_ctx > 1) |
| return false; |
| |
| return true; |
| } |
| |
| static int ath10k_monitor_recalc(struct ath10k *ar) |
| { |
| bool needed; |
| bool allowed; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| needed = ath10k_mac_monitor_vdev_is_needed(ar); |
| allowed = ath10k_mac_monitor_vdev_is_allowed(ar); |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, |
| "mac monitor recalc started? %d needed? %d allowed? %d\n", |
| ar->monitor_started, needed, allowed); |
| |
| if (WARN_ON(needed && !allowed)) { |
| if (ar->monitor_started) { |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac monitor stopping disallowed monitor\n"); |
| |
| ret = ath10k_monitor_stop(ar); |
| if (ret) |
| ath10k_warn(ar, "failed to stop disallowed monitor: %d\n", |
| ret); |
| /* not serious */ |
| } |
| |
| return -EPERM; |
| } |
| |
| if (needed == ar->monitor_started) |
| return 0; |
| |
| if (needed) |
| return ath10k_monitor_start(ar); |
| else |
| return ath10k_monitor_stop(ar); |
| } |
| |
| static bool ath10k_mac_can_set_cts_prot(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (!arvif->is_started) { |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "defer cts setup, vdev is not ready yet\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int ath10k_mac_set_cts_prot(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 vdev_param; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| vdev_param = ar->wmi.vdev_param->protection_mode; |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %d cts_protection %d\n", |
| arvif->vdev_id, arvif->use_cts_prot); |
| |
| return ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, vdev_param, |
| arvif->use_cts_prot ? 1 : 0); |
| } |
| |
| static int ath10k_recalc_rtscts_prot(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 vdev_param, rts_cts = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| vdev_param = ar->wmi.vdev_param->enable_rtscts; |
| |
| rts_cts |= SM(WMI_RTSCTS_ENABLED, WMI_RTSCTS_SET); |
| |
| if (arvif->num_legacy_stations > 0) |
| rts_cts |= SM(WMI_RTSCTS_ACROSS_SW_RETRIES, |
| WMI_RTSCTS_PROFILE); |
| else |
| rts_cts |= SM(WMI_RTSCTS_FOR_SECOND_RATESERIES, |
| WMI_RTSCTS_PROFILE); |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %d recalc rts/cts prot %d\n", |
| arvif->vdev_id, rts_cts); |
| |
| return ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, vdev_param, |
| rts_cts); |
| } |
| |
| static int ath10k_start_cac(struct ath10k *ar) |
| { |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| set_bit(ATH10K_CAC_RUNNING, &ar->dev_flags); |
| |
| ret = ath10k_monitor_recalc(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to start monitor (cac): %d\n", ret); |
| clear_bit(ATH10K_CAC_RUNNING, &ar->dev_flags); |
| return ret; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac cac start monitor vdev %d\n", |
| ar->monitor_vdev_id); |
| |
| return 0; |
| } |
| |
| static int ath10k_stop_cac(struct ath10k *ar) |
| { |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| /* CAC is not running - do nothing */ |
| if (!test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags)) |
| return 0; |
| |
| clear_bit(ATH10K_CAC_RUNNING, &ar->dev_flags); |
| ath10k_monitor_stop(ar); |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac cac finished\n"); |
| |
| return 0; |
| } |
| |
| static void ath10k_mac_has_radar_iter(struct ieee80211_hw *hw, |
| struct ieee80211_chanctx_conf *conf, |
| void *data) |
| { |
| bool *ret = data; |
| |
| if (!*ret && conf->radar_enabled) |
| *ret = true; |
| } |
| |
| static bool ath10k_mac_has_radar_enabled(struct ath10k *ar) |
| { |
| bool has_radar = false; |
| |
| ieee80211_iter_chan_contexts_atomic(ar->hw, |
| ath10k_mac_has_radar_iter, |
| &has_radar); |
| |
| return has_radar; |
| } |
| |
| static void ath10k_recalc_radar_detection(struct ath10k *ar) |
| { |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ath10k_stop_cac(ar); |
| |
| if (!ath10k_mac_has_radar_enabled(ar)) |
| return; |
| |
| if (ar->num_started_vdevs > 0) |
| return; |
| |
| ret = ath10k_start_cac(ar); |
| if (ret) { |
| /* |
| * Not possible to start CAC on current channel so starting |
| * radiation is not allowed, make this channel DFS_UNAVAILABLE |
| * by indicating that radar was detected. |
| */ |
| ath10k_warn(ar, "failed to start CAC: %d\n", ret); |
| ieee80211_radar_detected(ar->hw); |
| } |
| } |
| |
| static int ath10k_vdev_stop(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| reinit_completion(&ar->vdev_setup_done); |
| reinit_completion(&ar->vdev_delete_done); |
| |
| ret = ath10k_wmi_vdev_stop(ar, arvif->vdev_id); |
| if (ret) { |
| ath10k_warn(ar, "failed to stop WMI vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_vdev_setup_sync(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to synchronize setup for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| WARN_ON(ar->num_started_vdevs == 0); |
| |
| if (ar->num_started_vdevs != 0) { |
| ar->num_started_vdevs--; |
| ath10k_recalc_radar_detection(ar); |
| } |
| |
| return ret; |
| } |
| |
| static int ath10k_vdev_start_restart(struct ath10k_vif *arvif, |
| const struct cfg80211_chan_def *chandef, |
| bool restart) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct wmi_vdev_start_request_arg arg = {}; |
| int ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| reinit_completion(&ar->vdev_setup_done); |
| reinit_completion(&ar->vdev_delete_done); |
| |
| arg.vdev_id = arvif->vdev_id; |
| arg.dtim_period = arvif->dtim_period; |
| arg.bcn_intval = arvif->beacon_interval; |
| |
| arg.channel.freq = chandef->chan->center_freq; |
| arg.channel.band_center_freq1 = chandef->center_freq1; |
| arg.channel.band_center_freq2 = chandef->center_freq2; |
| arg.channel.mode = chan_to_phymode(chandef); |
| |
| arg.channel.min_power = 0; |
| arg.channel.max_power = chandef->chan->max_power * 2; |
| arg.channel.max_reg_power = chandef->chan->max_reg_power * 2; |
| arg.channel.max_antenna_gain = chandef->chan->max_antenna_gain; |
| |
| if (arvif->vdev_type == WMI_VDEV_TYPE_AP) { |
| arg.ssid = arvif->u.ap.ssid; |
| arg.ssid_len = arvif->u.ap.ssid_len; |
| arg.hidden_ssid = arvif->u.ap.hidden_ssid; |
| |
| /* For now allow DFS for AP mode */ |
| arg.channel.chan_radar = |
| !!(chandef->chan->flags & IEEE80211_CHAN_RADAR); |
| } else if (arvif->vdev_type == WMI_VDEV_TYPE_IBSS) { |
| arg.ssid = arvif->vif->bss_conf.ssid; |
| arg.ssid_len = arvif->vif->bss_conf.ssid_len; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, |
| "mac vdev %d start center_freq %d phymode %s\n", |
| arg.vdev_id, arg.channel.freq, |
| ath10k_wmi_phymode_str(arg.channel.mode)); |
| |
| if (restart) |
| ret = ath10k_wmi_vdev_restart(ar, &arg); |
| else |
| ret = ath10k_wmi_vdev_start(ar, &arg); |
| |
| if (ret) { |
| ath10k_warn(ar, "failed to start WMI vdev %i: %d\n", |
| arg.vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_vdev_setup_sync(ar); |
| if (ret) { |
| ath10k_warn(ar, |
| "failed to synchronize setup for vdev %i restart %d: %d\n", |
| arg.vdev_id, restart, ret); |
| return ret; |
| } |
| |
| ar->num_started_vdevs++; |
| ath10k_recalc_radar_detection(ar); |
| |
| return ret; |
| } |
| |
| static int ath10k_vdev_start(struct ath10k_vif *arvif, |
| const struct cfg80211_chan_def *def) |
| { |
| return ath10k_vdev_start_restart(arvif, def, false); |
| } |
| |
| static int ath10k_vdev_restart(struct ath10k_vif *arvif, |
| const struct cfg80211_chan_def *def) |
| { |
| return ath10k_vdev_start_restart(arvif, def, true); |
| } |
| |
| static int ath10k_mac_setup_bcn_p2p_ie(struct ath10k_vif *arvif, |
| struct sk_buff *bcn) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_mgmt *mgmt; |
| const u8 *p2p_ie; |
| int ret; |
| |
| if (arvif->vif->type != NL80211_IFTYPE_AP || !arvif->vif->p2p) |
| return 0; |
| |
| mgmt = (void *)bcn->data; |
| p2p_ie = cfg80211_find_vendor_ie(WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, |
| mgmt->u.beacon.variable, |
| bcn->len - (mgmt->u.beacon.variable - |
| bcn->data)); |
| if (!p2p_ie) |
| return -ENOENT; |
| |
| ret = ath10k_wmi_p2p_go_bcn_ie(ar, arvif->vdev_id, p2p_ie); |
| if (ret) { |
| ath10k_warn(ar, "failed to submit p2p go bcn ie for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_remove_vendor_ie(struct sk_buff *skb, unsigned int oui, |
| u8 oui_type, size_t ie_offset) |
| { |
| size_t len; |
| const u8 *next; |
| const u8 *end; |
| u8 *ie; |
| |
| if (WARN_ON(skb->len < ie_offset)) |
| return -EINVAL; |
| |
| ie = (u8 *)cfg80211_find_vendor_ie(oui, oui_type, |
| skb->data + ie_offset, |
| skb->len - ie_offset); |
| if (!ie) |
| return -ENOENT; |
| |
| len = ie[1] + 2; |
| end = skb->data + skb->len; |
| next = ie + len; |
| |
| if (WARN_ON(next > end)) |
| return -EINVAL; |
| |
| memmove(ie, next, end - next); |
| skb_trim(skb, skb->len - len); |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_setup_bcn_tmpl(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_hw *hw = ar->hw; |
| struct ieee80211_vif *vif = arvif->vif; |
| struct ieee80211_mutable_offsets offs = {}; |
| struct sk_buff *bcn; |
| int ret; |
| |
| if (!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map)) |
| return 0; |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP && |
| arvif->vdev_type != WMI_VDEV_TYPE_IBSS) |
| return 0; |
| |
| bcn = ieee80211_beacon_get_template(hw, vif, &offs); |
| if (!bcn) { |
| ath10k_warn(ar, "failed to get beacon template from mac80211\n"); |
| return -EPERM; |
| } |
| |
| ret = ath10k_mac_setup_bcn_p2p_ie(arvif, bcn); |
| if (ret) { |
| ath10k_warn(ar, "failed to setup p2p go bcn ie: %d\n", ret); |
| kfree_skb(bcn); |
| return ret; |
| } |
| |
| /* P2P IE is inserted by firmware automatically (as configured above) |
| * so remove it from the base beacon template to avoid duplicate P2P |
| * IEs in beacon frames. |
| */ |
| ath10k_mac_remove_vendor_ie(bcn, WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P, |
| offsetof(struct ieee80211_mgmt, |
| u.beacon.variable)); |
| |
| ret = ath10k_wmi_bcn_tmpl(ar, arvif->vdev_id, offs.tim_offset, bcn, 0, |
| 0, NULL, 0); |
| kfree_skb(bcn); |
| |
| if (ret) { |
| ath10k_warn(ar, "failed to submit beacon template command: %d\n", |
| ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_setup_prb_tmpl(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_hw *hw = ar->hw; |
| struct ieee80211_vif *vif = arvif->vif; |
| struct sk_buff *prb; |
| int ret; |
| |
| if (!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map)) |
| return 0; |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP) |
| return 0; |
| |
| /* For mesh, probe response and beacon share the same template */ |
| if (ieee80211_vif_is_mesh(vif)) |
| return 0; |
| |
| prb = ieee80211_proberesp_get(hw, vif); |
| if (!prb) { |
| ath10k_warn(ar, "failed to get probe resp template from mac80211\n"); |
| return -EPERM; |
| } |
| |
| ret = ath10k_wmi_prb_tmpl(ar, arvif->vdev_id, prb); |
| kfree_skb(prb); |
| |
| if (ret) { |
| ath10k_warn(ar, "failed to submit probe resp template command: %d\n", |
| ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_vif_fix_hidden_ssid(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct cfg80211_chan_def def; |
| int ret; |
| |
| /* When originally vdev is started during assign_vif_chanctx() some |
| * information is missing, notably SSID. Firmware revisions with beacon |
| * offloading require the SSID to be provided during vdev (re)start to |
| * handle hidden SSID properly. |
| * |
| * Vdev restart must be done after vdev has been both started and |
| * upped. Otherwise some firmware revisions (at least 10.2) fail to |
| * deliver vdev restart response event causing timeouts during vdev |
| * syncing in ath10k. |
| * |
| * Note: The vdev down/up and template reinstallation could be skipped |
| * since only wmi-tlv firmware are known to have beacon offload and |
| * wmi-tlv doesn't seem to misbehave like 10.2 wrt vdev restart |
| * response delivery. It's probably more robust to keep it as is. |
| */ |
| if (!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map)) |
| return 0; |
| |
| if (WARN_ON(!arvif->is_started)) |
| return -EINVAL; |
| |
| if (WARN_ON(!arvif->is_up)) |
| return -EINVAL; |
| |
| if (WARN_ON(ath10k_mac_vif_chan(arvif->vif, &def))) |
| return -EINVAL; |
| |
| ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); |
| if (ret) { |
| ath10k_warn(ar, "failed to bring down ap vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| /* Vdev down reset beacon & presp templates. Reinstall them. Otherwise |
| * firmware will crash upon vdev up. |
| */ |
| |
| ret = ath10k_mac_setup_bcn_tmpl(arvif); |
| if (ret) { |
| ath10k_warn(ar, "failed to update beacon template: %d\n", ret); |
| return ret; |
| } |
| |
| ret = ath10k_mac_setup_prb_tmpl(arvif); |
| if (ret) { |
| ath10k_warn(ar, "failed to update presp template: %d\n", ret); |
| return ret; |
| } |
| |
| ret = ath10k_vdev_restart(arvif, &def); |
| if (ret) { |
| ath10k_warn(ar, "failed to restart ap vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_wmi_vdev_up(arvif->ar, arvif->vdev_id, arvif->aid, |
| arvif->bssid); |
| if (ret) { |
| ath10k_warn(ar, "failed to bring up ap vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void ath10k_control_beaconing(struct ath10k_vif *arvif, |
| struct ieee80211_bss_conf *info) |
| { |
| struct ath10k *ar = arvif->ar; |
| int ret = 0; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (!info->enable_beacon) { |
| ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); |
| if (ret) |
| ath10k_warn(ar, "failed to down vdev_id %i: %d\n", |
| arvif->vdev_id, ret); |
| |
| arvif->is_up = false; |
| |
| spin_lock_bh(&arvif->ar->data_lock); |
| ath10k_mac_vif_beacon_free(arvif); |
| spin_unlock_bh(&arvif->ar->data_lock); |
| |
| return; |
| } |
| |
| arvif->tx_seq_no = 0x1000; |
| |
| arvif->aid = 0; |
| ether_addr_copy(arvif->bssid, info->bssid); |
| |
| ret = ath10k_wmi_vdev_up(arvif->ar, arvif->vdev_id, arvif->aid, |
| arvif->bssid); |
| if (ret) { |
| ath10k_warn(ar, "failed to bring up vdev %d: %i\n", |
| arvif->vdev_id, ret); |
| return; |
| } |
| |
| arvif->is_up = true; |
| |
| ret = ath10k_mac_vif_fix_hidden_ssid(arvif); |
| if (ret) { |
| ath10k_warn(ar, "failed to fix hidden ssid for vdev %i, expect trouble: %d\n", |
| arvif->vdev_id, ret); |
| return; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %d up\n", arvif->vdev_id); |
| } |
| |
| static void ath10k_control_ibss(struct ath10k_vif *arvif, |
| struct ieee80211_bss_conf *info, |
| const u8 self_peer[ETH_ALEN]) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 vdev_param; |
| int ret = 0; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (!info->ibss_joined) { |
| if (is_zero_ether_addr(arvif->bssid)) |
| return; |
| |
| eth_zero_addr(arvif->bssid); |
| |
| return; |
| } |
| |
| vdev_param = arvif->ar->wmi.vdev_param->atim_window; |
| ret = ath10k_wmi_vdev_set_param(arvif->ar, arvif->vdev_id, vdev_param, |
| ATH10K_DEFAULT_ATIM); |
| if (ret) |
| ath10k_warn(ar, "failed to set IBSS ATIM for vdev %d: %d\n", |
| arvif->vdev_id, ret); |
| } |
| |
| static int ath10k_mac_vif_recalc_ps_wake_threshold(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 param; |
| u32 value; |
| int ret; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (arvif->u.sta.uapsd) |
| value = WMI_STA_PS_TX_WAKE_THRESHOLD_NEVER; |
| else |
| value = WMI_STA_PS_TX_WAKE_THRESHOLD_ALWAYS; |
| |
| param = WMI_STA_PS_PARAM_TX_WAKE_THRESHOLD; |
| ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id, param, value); |
| if (ret) { |
| ath10k_warn(ar, "failed to submit ps wake threshold %u on vdev %i: %d\n", |
| value, arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_vif_recalc_ps_poll_count(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| u32 param; |
| u32 value; |
| int ret; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (arvif->u.sta.uapsd) |
| value = WMI_STA_PS_PSPOLL_COUNT_UAPSD; |
| else |
| value = WMI_STA_PS_PSPOLL_COUNT_NO_MAX; |
| |
| param = WMI_STA_PS_PARAM_PSPOLL_COUNT; |
| ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id, |
| param, value); |
| if (ret) { |
| ath10k_warn(ar, "failed to submit ps poll count %u on vdev %i: %d\n", |
| value, arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_num_vifs_started(struct ath10k *ar) |
| { |
| struct ath10k_vif *arvif; |
| int num = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| list_for_each_entry(arvif, &ar->arvifs, list) |
| if (arvif->is_started) |
| num++; |
| |
| return num; |
| } |
| |
| static int ath10k_mac_vif_setup_ps(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_vif *vif = arvif->vif; |
| struct ieee80211_conf *conf = &ar->hw->conf; |
| enum wmi_sta_powersave_param param; |
| enum wmi_sta_ps_mode psmode; |
| int ret; |
| int ps_timeout; |
| bool enable_ps; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (arvif->vif->type != NL80211_IFTYPE_STATION) |
| return 0; |
| |
| enable_ps = arvif->ps; |
| |
| if (enable_ps && ath10k_mac_num_vifs_started(ar) > 1 && |
| !test_bit(ATH10K_FW_FEATURE_MULTI_VIF_PS_SUPPORT, |
| ar->running_fw->fw_file.fw_features)) { |
| ath10k_warn(ar, "refusing to enable ps on vdev %i: not supported by fw\n", |
| arvif->vdev_id); |
| enable_ps = false; |
| } |
| |
| if (!arvif->is_started) { |
| /* mac80211 can update vif powersave state while disconnected. |
| * Firmware doesn't behave nicely and consumes more power than |
| * necessary if PS is disabled on a non-started vdev. Hence |
| * force-enable PS for non-running vdevs. |
| */ |
| psmode = WMI_STA_PS_MODE_ENABLED; |
| } else if (enable_ps) { |
| psmode = WMI_STA_PS_MODE_ENABLED; |
| param = WMI_STA_PS_PARAM_INACTIVITY_TIME; |
| |
| ps_timeout = conf->dynamic_ps_timeout; |
| if (ps_timeout == 0) { |
| /* Firmware doesn't like 0 */ |
| ps_timeout = ieee80211_tu_to_usec( |
| vif->bss_conf.beacon_int) / 1000; |
| } |
| |
| ret = ath10k_wmi_set_sta_ps_param(ar, arvif->vdev_id, param, |
| ps_timeout); |
| if (ret) { |
| ath10k_warn(ar, "failed to set inactivity time for vdev %d: %i\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| } else { |
| psmode = WMI_STA_PS_MODE_DISABLED; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %d psmode %s\n", |
| arvif->vdev_id, psmode ? "enable" : "disable"); |
| |
| ret = ath10k_wmi_set_psmode(ar, arvif->vdev_id, psmode); |
| if (ret) { |
| ath10k_warn(ar, "failed to set PS Mode %d for vdev %d: %d\n", |
| psmode, arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_vif_disable_keepalive(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct wmi_sta_keepalive_arg arg = {}; |
| int ret; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_STA) |
| return 0; |
| |
| if (!test_bit(WMI_SERVICE_STA_KEEP_ALIVE, ar->wmi.svc_map)) |
| return 0; |
| |
| /* Some firmware revisions have a bug and ignore the `enabled` field. |
| * Instead use the interval to disable the keepalive. |
| */ |
| arg.vdev_id = arvif->vdev_id; |
| arg.enabled = 1; |
| arg.method = WMI_STA_KEEPALIVE_METHOD_NULL_FRAME; |
| arg.interval = WMI_STA_KEEPALIVE_INTERVAL_DISABLE; |
| |
| ret = ath10k_wmi_sta_keepalive(ar, &arg); |
| if (ret) { |
| ath10k_warn(ar, "failed to submit keepalive on vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static void ath10k_mac_vif_ap_csa_count_down(struct ath10k_vif *arvif) |
| { |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_vif *vif = arvif->vif; |
| int ret; |
| |
| lockdep_assert_held(&arvif->ar->conf_mutex); |
| |
| if (WARN_ON(!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map))) |
| return; |
| |
| if (arvif->vdev_type != WMI_VDEV_TYPE_AP) |
| return; |
| |
| if (!vif->csa_active) |
| return; |
| |
| if (!arvif->is_up) |
| return; |
| |
| if (!ieee80211_beacon_cntdwn_is_complete(vif)) { |
| ieee80211_beacon_update_cntdwn(vif); |
| |
| ret = ath10k_mac_setup_bcn_tmpl(arvif); |
| if (ret) |
| ath10k_warn(ar, "failed to update bcn tmpl during csa: %d\n", |
| ret); |
| |
| ret = ath10k_mac_setup_prb_tmpl(arvif); |
| if (ret) |
| ath10k_warn(ar, "failed to update prb tmpl during csa: %d\n", |
| ret); |
| } else { |
| ieee80211_csa_finish(vif); |
| } |
| } |
| |
| static void ath10k_mac_vif_ap_csa_work(struct work_struct *work) |
| { |
| struct ath10k_vif *arvif = container_of(work, struct ath10k_vif, |
| ap_csa_work); |
| struct ath10k *ar = arvif->ar; |
| |
| mutex_lock(&ar->conf_mutex); |
| ath10k_mac_vif_ap_csa_count_down(arvif); |
| mutex_unlock(&ar->conf_mutex); |
| } |
| |
| static void ath10k_mac_handle_beacon_iter(void *data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| struct sk_buff *skb = data; |
| struct ieee80211_mgmt *mgmt = (void *)skb->data; |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| |
| if (vif->type != NL80211_IFTYPE_STATION) |
| return; |
| |
| if (!ether_addr_equal(mgmt->bssid, vif->bss_conf.bssid)) |
| return; |
| |
| cancel_delayed_work(&arvif->connection_loss_work); |
| } |
| |
| void ath10k_mac_handle_beacon(struct ath10k *ar, struct sk_buff *skb) |
| { |
| ieee80211_iterate_active_interfaces_atomic(ar->hw, |
| ATH10K_ITER_NORMAL_FLAGS, |
| ath10k_mac_handle_beacon_iter, |
| skb); |
| } |
| |
| static void ath10k_mac_handle_beacon_miss_iter(void *data, u8 *mac, |
| struct ieee80211_vif *vif) |
| { |
| u32 *vdev_id = data; |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct ath10k *ar = arvif->ar; |
| struct ieee80211_hw *hw = ar->hw; |
| |
| if (arvif->vdev_id != *vdev_id) |
| return; |
| |
| if (!arvif->is_up) |
| return; |
| |
| ieee80211_beacon_loss(vif); |
| |
| /* Firmware doesn't report beacon loss events repeatedly. If AP probe |
| * (done by mac80211) succeeds but beacons do not resume then it |
| * doesn't make sense to continue operation. Queue connection loss work |
| * which can be cancelled when beacon is received. |
| */ |
| ieee80211_queue_delayed_work(hw, &arvif->connection_loss_work, |
| ATH10K_CONNECTION_LOSS_HZ); |
| } |
| |
| void ath10k_mac_handle_beacon_miss(struct ath10k *ar, u32 vdev_id) |
| { |
| ieee80211_iterate_active_interfaces_atomic(ar->hw, |
| ATH10K_ITER_NORMAL_FLAGS, |
| ath10k_mac_handle_beacon_miss_iter, |
| &vdev_id); |
| } |
| |
| static void ath10k_mac_vif_sta_connection_loss_work(struct work_struct *work) |
| { |
| struct ath10k_vif *arvif = container_of(work, struct ath10k_vif, |
| connection_loss_work.work); |
| struct ieee80211_vif *vif = arvif->vif; |
| |
| if (!arvif->is_up) |
| return; |
| |
| ieee80211_connection_loss(vif); |
| } |
| |
| /**********************/ |
| /* Station management */ |
| /**********************/ |
| |
| static u32 ath10k_peer_assoc_h_listen_intval(struct ath10k *ar, |
| struct ieee80211_vif *vif) |
| { |
| /* Some firmware revisions have unstable STA powersave when listen |
| * interval is set too high (e.g. 5). The symptoms are firmware doesn't |
| * generate NullFunc frames properly even if buffered frames have been |
| * indicated in Beacon TIM. Firmware would seldom wake up to pull |
| * buffered frames. Often pinging the device from AP would simply fail. |
| * |
| * As a workaround set it to 1. |
| */ |
| if (vif->type == NL80211_IFTYPE_STATION) |
| return 1; |
| |
| return ar->hw->conf.listen_interval; |
| } |
| |
| static void ath10k_peer_assoc_h_basic(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| u32 aid; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (vif->type == NL80211_IFTYPE_STATION) |
| aid = vif->bss_conf.aid; |
| else |
| aid = sta->aid; |
| |
| ether_addr_copy(arg->addr, sta->addr); |
| arg->vdev_id = arvif->vdev_id; |
| arg->peer_aid = aid; |
| arg->peer_flags |= arvif->ar->wmi.peer_flags->auth; |
| arg->peer_listen_intval = ath10k_peer_assoc_h_listen_intval(ar, vif); |
| arg->peer_num_spatial_streams = 1; |
| arg->peer_caps = vif->bss_conf.assoc_capability; |
| } |
| |
| static void ath10k_peer_assoc_h_crypto(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| struct ieee80211_bss_conf *info = &vif->bss_conf; |
| struct cfg80211_chan_def def; |
| struct cfg80211_bss *bss; |
| const u8 *rsnie = NULL; |
| const u8 *wpaie = NULL; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (WARN_ON(ath10k_mac_vif_chan(vif, &def))) |
| return; |
| |
| bss = cfg80211_get_bss(ar->hw->wiphy, def.chan, info->bssid, |
| info->ssid_len ? info->ssid : NULL, info->ssid_len, |
| IEEE80211_BSS_TYPE_ANY, IEEE80211_PRIVACY_ANY); |
| if (bss) { |
| const struct cfg80211_bss_ies *ies; |
| |
| rcu_read_lock(); |
| rsnie = ieee80211_bss_get_ie(bss, WLAN_EID_RSN); |
| |
| ies = rcu_dereference(bss->ies); |
| |
| wpaie = cfg80211_find_vendor_ie(WLAN_OUI_MICROSOFT, |
| WLAN_OUI_TYPE_MICROSOFT_WPA, |
| ies->data, |
| ies->len); |
| rcu_read_unlock(); |
| cfg80211_put_bss(ar->hw->wiphy, bss); |
| } |
| |
| /* FIXME: base on RSN IE/WPA IE is a correct idea? */ |
| if (rsnie || wpaie) { |
| ath10k_dbg(ar, ATH10K_DBG_WMI, "%s: rsn ie found\n", __func__); |
| arg->peer_flags |= ar->wmi.peer_flags->need_ptk_4_way; |
| } |
| |
| if (wpaie) { |
| ath10k_dbg(ar, ATH10K_DBG_WMI, "%s: wpa ie found\n", __func__); |
| arg->peer_flags |= ar->wmi.peer_flags->need_gtk_2_way; |
| } |
| |
| if (sta->mfp && |
| test_bit(ATH10K_FW_FEATURE_MFP_SUPPORT, |
| ar->running_fw->fw_file.fw_features)) { |
| arg->peer_flags |= ar->wmi.peer_flags->pmf; |
| } |
| } |
| |
| static void ath10k_peer_assoc_h_rates(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct wmi_rate_set_arg *rateset = &arg->peer_legacy_rates; |
| struct cfg80211_chan_def def; |
| const struct ieee80211_supported_band *sband; |
| const struct ieee80211_rate *rates; |
| enum nl80211_band band; |
| u32 ratemask; |
| u8 rate; |
| int i; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (WARN_ON(ath10k_mac_vif_chan(vif, &def))) |
| return; |
| |
| band = def.chan->band; |
| sband = ar->hw->wiphy->bands[band]; |
| ratemask = sta->supp_rates[band]; |
| ratemask &= arvif->bitrate_mask.control[band].legacy; |
| rates = sband->bitrates; |
| |
| rateset->num_rates = 0; |
| |
| for (i = 0; i < 32; i++, ratemask >>= 1, rates++) { |
| if (!(ratemask & 1)) |
| continue; |
| |
| rate = ath10k_mac_bitrate_to_rate(rates->bitrate); |
| rateset->rates[rateset->num_rates] = rate; |
| rateset->num_rates++; |
| } |
| } |
| |
| static bool |
| ath10k_peer_assoc_h_ht_masked(const u8 ht_mcs_mask[IEEE80211_HT_MCS_MASK_LEN]) |
| { |
| int nss; |
| |
| for (nss = 0; nss < IEEE80211_HT_MCS_MASK_LEN; nss++) |
| if (ht_mcs_mask[nss]) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| ath10k_peer_assoc_h_vht_masked(const u16 vht_mcs_mask[NL80211_VHT_NSS_MAX]) |
| { |
| int nss; |
| |
| for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) |
| if (vht_mcs_mask[nss]) |
| return false; |
| |
| return true; |
| } |
| |
| static void ath10k_peer_assoc_h_ht(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| const struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct cfg80211_chan_def def; |
| enum nl80211_band band; |
| const u8 *ht_mcs_mask; |
| const u16 *vht_mcs_mask; |
| int i, n; |
| u8 max_nss; |
| u32 stbc; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (WARN_ON(ath10k_mac_vif_chan(vif, &def))) |
| return; |
| |
| if (!ht_cap->ht_supported) |
| return; |
| |
| band = def.chan->band; |
| ht_mcs_mask = arvif->bitrate_mask.control[band].ht_mcs; |
| vht_mcs_mask = arvif->bitrate_mask.control[band].vht_mcs; |
| |
| if (ath10k_peer_assoc_h_ht_masked(ht_mcs_mask) && |
| ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) |
| return; |
| |
| arg->peer_flags |= ar->wmi.peer_flags->ht; |
| arg->peer_max_mpdu = (1 << (IEEE80211_HT_MAX_AMPDU_FACTOR + |
| ht_cap->ampdu_factor)) - 1; |
| |
| arg->peer_mpdu_density = |
| ath10k_parse_mpdudensity(ht_cap->ampdu_density); |
| |
| arg->peer_ht_caps = ht_cap->cap; |
| arg->peer_rate_caps |= WMI_RC_HT_FLAG; |
| |
| if (ht_cap->cap & IEEE80211_HT_CAP_LDPC_CODING) |
| arg->peer_flags |= ar->wmi.peer_flags->ldbc; |
| |
| if (sta->bandwidth >= IEEE80211_STA_RX_BW_40) { |
| arg->peer_flags |= ar->wmi.peer_flags->bw40; |
| arg->peer_rate_caps |= WMI_RC_CW40_FLAG; |
| } |
| |
| if (arvif->bitrate_mask.control[band].gi != NL80211_TXRATE_FORCE_LGI) { |
| if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) |
| arg->peer_rate_caps |= WMI_RC_SGI_FLAG; |
| |
| if (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) |
| arg->peer_rate_caps |= WMI_RC_SGI_FLAG; |
| } |
| |
| if (ht_cap->cap & IEEE80211_HT_CAP_TX_STBC) { |
| arg->peer_rate_caps |= WMI_RC_TX_STBC_FLAG; |
| arg->peer_flags |= ar->wmi.peer_flags->stbc; |
| } |
| |
| if (ht_cap->cap & IEEE80211_HT_CAP_RX_STBC) { |
| stbc = ht_cap->cap & IEEE80211_HT_CAP_RX_STBC; |
| stbc = stbc >> IEEE80211_HT_CAP_RX_STBC_SHIFT; |
| stbc = stbc << WMI_RC_RX_STBC_FLAG_S; |
| arg->peer_rate_caps |= stbc; |
| arg->peer_flags |= ar->wmi.peer_flags->stbc; |
| } |
| |
| if (ht_cap->mcs.rx_mask[1] && ht_cap->mcs.rx_mask[2]) |
| arg->peer_rate_caps |= WMI_RC_TS_FLAG; |
| else if (ht_cap->mcs.rx_mask[1]) |
| arg->peer_rate_caps |= WMI_RC_DS_FLAG; |
| |
| for (i = 0, n = 0, max_nss = 0; i < IEEE80211_HT_MCS_MASK_LEN * 8; i++) |
| if ((ht_cap->mcs.rx_mask[i / 8] & BIT(i % 8)) && |
| (ht_mcs_mask[i / 8] & BIT(i % 8))) { |
| max_nss = (i / 8) + 1; |
| arg->peer_ht_rates.rates[n++] = i; |
| } |
| |
| /* |
| * This is a workaround for HT-enabled STAs which break the spec |
| * and have no HT capabilities RX mask (no HT RX MCS map). |
| * |
| * As per spec, in section 20.3.5 Modulation and coding scheme (MCS), |
| * MCS 0 through 7 are mandatory in 20MHz with 800 ns GI at all STAs. |
| * |
| * Firmware asserts if such situation occurs. |
| */ |
| if (n == 0) { |
| arg->peer_ht_rates.num_rates = 8; |
| for (i = 0; i < arg->peer_ht_rates.num_rates; i++) |
| arg->peer_ht_rates.rates[i] = i; |
| } else { |
| arg->peer_ht_rates.num_rates = n; |
| arg->peer_num_spatial_streams = min(sta->rx_nss, max_nss); |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac ht peer %pM mcs cnt %d nss %d\n", |
| arg->addr, |
| arg->peer_ht_rates.num_rates, |
| arg->peer_num_spatial_streams); |
| } |
| |
| static int ath10k_peer_assoc_qos_ap(struct ath10k *ar, |
| struct ath10k_vif *arvif, |
| struct ieee80211_sta *sta) |
| { |
| u32 uapsd = 0; |
| u32 max_sp = 0; |
| int ret = 0; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| if (sta->wme && sta->uapsd_queues) { |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac uapsd_queues 0x%x max_sp %d\n", |
| sta->uapsd_queues, sta->max_sp); |
| |
| if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) |
| uapsd |= WMI_AP_PS_UAPSD_AC3_DELIVERY_EN | |
| WMI_AP_PS_UAPSD_AC3_TRIGGER_EN; |
| if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI) |
| uapsd |= WMI_AP_PS_UAPSD_AC2_DELIVERY_EN | |
| WMI_AP_PS_UAPSD_AC2_TRIGGER_EN; |
| if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK) |
| uapsd |= WMI_AP_PS_UAPSD_AC1_DELIVERY_EN | |
| WMI_AP_PS_UAPSD_AC1_TRIGGER_EN; |
| if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE) |
| uapsd |= WMI_AP_PS_UAPSD_AC0_DELIVERY_EN | |
| WMI_AP_PS_UAPSD_AC0_TRIGGER_EN; |
| |
| if (sta->max_sp < MAX_WMI_AP_PS_PEER_PARAM_MAX_SP) |
| max_sp = sta->max_sp; |
| |
| ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id, |
| sta->addr, |
| WMI_AP_PS_PEER_PARAM_UAPSD, |
| uapsd); |
| if (ret) { |
| ath10k_warn(ar, "failed to set ap ps peer param uapsd for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id, |
| sta->addr, |
| WMI_AP_PS_PEER_PARAM_MAX_SP, |
| max_sp); |
| if (ret) { |
| ath10k_warn(ar, "failed to set ap ps peer param max sp for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| |
| /* TODO setup this based on STA listen interval and |
| * beacon interval. Currently we don't know |
| * sta->listen_interval - mac80211 patch required. |
| * Currently use 10 seconds |
| */ |
| ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id, sta->addr, |
| WMI_AP_PS_PEER_PARAM_AGEOUT_TIME, |
| 10); |
| if (ret) { |
| ath10k_warn(ar, "failed to set ap ps peer param ageout time for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static u16 |
| ath10k_peer_assoc_h_vht_limit(u16 tx_mcs_set, |
| const u16 vht_mcs_limit[NL80211_VHT_NSS_MAX]) |
| { |
| int idx_limit; |
| int nss; |
| u16 mcs_map; |
| u16 mcs; |
| |
| for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) { |
| mcs_map = ath10k_mac_get_max_vht_mcs_map(tx_mcs_set, nss) & |
| vht_mcs_limit[nss]; |
| |
| if (mcs_map) |
| idx_limit = fls(mcs_map) - 1; |
| else |
| idx_limit = -1; |
| |
| switch (idx_limit) { |
| case 0: |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| case 6: |
| default: |
| /* see ath10k_mac_can_set_bitrate_mask() */ |
| WARN_ON(1); |
| fallthrough; |
| case -1: |
| mcs = IEEE80211_VHT_MCS_NOT_SUPPORTED; |
| break; |
| case 7: |
| mcs = IEEE80211_VHT_MCS_SUPPORT_0_7; |
| break; |
| case 8: |
| mcs = IEEE80211_VHT_MCS_SUPPORT_0_8; |
| break; |
| case 9: |
| mcs = IEEE80211_VHT_MCS_SUPPORT_0_9; |
| break; |
| } |
| |
| tx_mcs_set &= ~(0x3 << (nss * 2)); |
| tx_mcs_set |= mcs << (nss * 2); |
| } |
| |
| return tx_mcs_set; |
| } |
| |
| static u32 get_160mhz_nss_from_maxrate(int rate) |
| { |
| u32 nss; |
| |
| switch (rate) { |
| case 780: |
| nss = 1; |
| break; |
| case 1560: |
| nss = 2; |
| break; |
| case 2106: |
| nss = 3; /* not support MCS9 from spec*/ |
| break; |
| case 3120: |
| nss = 4; |
| break; |
| default: |
| nss = 1; |
| } |
| |
| return nss; |
| } |
| |
| static void ath10k_peer_assoc_h_vht(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| const struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap; |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct ath10k_hw_params *hw = &ar->hw_params; |
| struct cfg80211_chan_def def; |
| enum nl80211_band band; |
| const u16 *vht_mcs_mask; |
| u8 ampdu_factor; |
| u8 max_nss, vht_mcs; |
| int i; |
| |
| if (WARN_ON(ath10k_mac_vif_chan(vif, &def))) |
| return; |
| |
| if (!vht_cap->vht_supported) |
| return; |
| |
| band = def.chan->band; |
| vht_mcs_mask = arvif->bitrate_mask.control[band].vht_mcs; |
| |
| if (ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) |
| return; |
| |
| arg->peer_flags |= ar->wmi.peer_flags->vht; |
| |
| if (def.chan->band == NL80211_BAND_2GHZ) |
| arg->peer_flags |= ar->wmi.peer_flags->vht_2g; |
| |
| arg->peer_vht_caps = vht_cap->cap; |
| |
| ampdu_factor = (vht_cap->cap & |
| IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK) >> |
| IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; |
| |
| /* Workaround: Some Netgear/Linksys 11ac APs set Rx A-MPDU factor to |
| * zero in VHT IE. Using it would result in degraded throughput. |
| * arg->peer_max_mpdu at this point contains HT max_mpdu so keep |
| * it if VHT max_mpdu is smaller. |
| */ |
| arg->peer_max_mpdu = max(arg->peer_max_mpdu, |
| (1U << (IEEE80211_HT_MAX_AMPDU_FACTOR + |
| ampdu_factor)) - 1); |
| |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_80) |
| arg->peer_flags |= ar->wmi.peer_flags->bw80; |
| |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_160) |
| arg->peer_flags |= ar->wmi.peer_flags->bw160; |
| |
| /* Calculate peer NSS capability from VHT capabilities if STA |
| * supports VHT. |
| */ |
| for (i = 0, max_nss = 0, vht_mcs = 0; i < NL80211_VHT_NSS_MAX; i++) { |
| vht_mcs = __le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) >> |
| (2 * i) & 3; |
| |
| if ((vht_mcs != IEEE80211_VHT_MCS_NOT_SUPPORTED) && |
| vht_mcs_mask[i]) |
| max_nss = i + 1; |
| } |
| arg->peer_num_spatial_streams = min(sta->rx_nss, max_nss); |
| arg->peer_vht_rates.rx_max_rate = |
| __le16_to_cpu(vht_cap->vht_mcs.rx_highest); |
| arg->peer_vht_rates.rx_mcs_set = |
| __le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map); |
| arg->peer_vht_rates.tx_max_rate = |
| __le16_to_cpu(vht_cap->vht_mcs.tx_highest); |
| arg->peer_vht_rates.tx_mcs_set = ath10k_peer_assoc_h_vht_limit( |
| __le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map), vht_mcs_mask); |
| |
| /* Configure bandwidth-NSS mapping to FW |
| * for the chip's tx chains setting on 160Mhz bw |
| */ |
| if (arg->peer_phymode == MODE_11AC_VHT160 || |
| arg->peer_phymode == MODE_11AC_VHT80_80) { |
| u32 rx_nss; |
| u32 max_rate; |
| |
| max_rate = arg->peer_vht_rates.rx_max_rate; |
| rx_nss = get_160mhz_nss_from_maxrate(max_rate); |
| |
| if (rx_nss == 0) |
| rx_nss = arg->peer_num_spatial_streams; |
| else |
| rx_nss = min(arg->peer_num_spatial_streams, rx_nss); |
| |
| max_rate = hw->vht160_mcs_tx_highest; |
| rx_nss = min(rx_nss, get_160mhz_nss_from_maxrate(max_rate)); |
| |
| arg->peer_bw_rxnss_override = |
| FIELD_PREP(WMI_PEER_NSS_MAP_ENABLE, 1) | |
| FIELD_PREP(WMI_PEER_NSS_160MHZ_MASK, (rx_nss - 1)); |
| |
| if (arg->peer_phymode == MODE_11AC_VHT80_80) { |
| arg->peer_bw_rxnss_override |= |
| FIELD_PREP(WMI_PEER_NSS_80_80MHZ_MASK, (rx_nss - 1)); |
| } |
| } |
| ath10k_dbg(ar, ATH10K_DBG_MAC, |
| "mac vht peer %pM max_mpdu %d flags 0x%x peer_rx_nss_override 0x%x\n", |
| sta->addr, arg->peer_max_mpdu, |
| arg->peer_flags, arg->peer_bw_rxnss_override); |
| } |
| |
| static void ath10k_peer_assoc_h_qos(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| |
| switch (arvif->vdev_type) { |
| case WMI_VDEV_TYPE_AP: |
| if (sta->wme) |
| arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; |
| |
| if (sta->wme && sta->uapsd_queues) { |
| arg->peer_flags |= arvif->ar->wmi.peer_flags->apsd; |
| arg->peer_rate_caps |= WMI_RC_UAPSD_FLAG; |
| } |
| break; |
| case WMI_VDEV_TYPE_STA: |
| if (sta->wme) |
| arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; |
| break; |
| case WMI_VDEV_TYPE_IBSS: |
| if (sta->wme) |
| arg->peer_flags |= arvif->ar->wmi.peer_flags->qos; |
| break; |
| default: |
| break; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac peer %pM qos %d\n", |
| sta->addr, !!(arg->peer_flags & |
| arvif->ar->wmi.peer_flags->qos)); |
| } |
| |
| static bool ath10k_mac_sta_has_ofdm_only(struct ieee80211_sta *sta) |
| { |
| return sta->supp_rates[NL80211_BAND_2GHZ] >> |
| ATH10K_MAC_FIRST_OFDM_RATE_IDX; |
| } |
| |
| static enum wmi_phy_mode ath10k_mac_get_phymode_vht(struct ath10k *ar, |
| struct ieee80211_sta *sta) |
| { |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_160) { |
| switch (sta->vht_cap.cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) { |
| case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ: |
| return MODE_11AC_VHT160; |
| case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ: |
| return MODE_11AC_VHT80_80; |
| default: |
| /* not sure if this is a valid case? */ |
| return MODE_11AC_VHT160; |
| } |
| } |
| |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_80) |
| return MODE_11AC_VHT80; |
| |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_40) |
| return MODE_11AC_VHT40; |
| |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_20) |
| return MODE_11AC_VHT20; |
| |
| return MODE_UNKNOWN; |
| } |
| |
| static void ath10k_peer_assoc_h_phymode(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct cfg80211_chan_def def; |
| enum nl80211_band band; |
| const u8 *ht_mcs_mask; |
| const u16 *vht_mcs_mask; |
| enum wmi_phy_mode phymode = MODE_UNKNOWN; |
| |
| if (WARN_ON(ath10k_mac_vif_chan(vif, &def))) |
| return; |
| |
| band = def.chan->band; |
| ht_mcs_mask = arvif->bitrate_mask.control[band].ht_mcs; |
| vht_mcs_mask = arvif->bitrate_mask.control[band].vht_mcs; |
| |
| switch (band) { |
| case NL80211_BAND_2GHZ: |
| if (sta->vht_cap.vht_supported && |
| !ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) { |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_40) |
| phymode = MODE_11AC_VHT40; |
| else |
| phymode = MODE_11AC_VHT20; |
| } else if (sta->ht_cap.ht_supported && |
| !ath10k_peer_assoc_h_ht_masked(ht_mcs_mask)) { |
| if (sta->bandwidth == IEEE80211_STA_RX_BW_40) |
| phymode = MODE_11NG_HT40; |
| else |
| phymode = MODE_11NG_HT20; |
| } else if (ath10k_mac_sta_has_ofdm_only(sta)) { |
| phymode = MODE_11G; |
| } else { |
| phymode = MODE_11B; |
| } |
| |
| break; |
| case NL80211_BAND_5GHZ: |
| /* |
| * Check VHT first. |
| */ |
| if (sta->vht_cap.vht_supported && |
| !ath10k_peer_assoc_h_vht_masked(vht_mcs_mask)) { |
| phymode = ath10k_mac_get_phymode_vht(ar, sta); |
| } else if (sta->ht_cap.ht_supported && |
| !ath10k_peer_assoc_h_ht_masked(ht_mcs_mask)) { |
| if (sta->bandwidth >= IEEE80211_STA_RX_BW_40) |
| phymode = MODE_11NA_HT40; |
| else |
| phymode = MODE_11NA_HT20; |
| } else { |
| phymode = MODE_11A; |
| } |
| |
| break; |
| default: |
| break; |
| } |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac peer %pM phymode %s\n", |
| sta->addr, ath10k_wmi_phymode_str(phymode)); |
| |
| arg->peer_phymode = phymode; |
| WARN_ON(phymode == MODE_UNKNOWN); |
| } |
| |
| static int ath10k_peer_assoc_prepare(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta *sta, |
| struct wmi_peer_assoc_complete_arg *arg) |
| { |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| memset(arg, 0, sizeof(*arg)); |
| |
| ath10k_peer_assoc_h_basic(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_crypto(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_rates(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_ht(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_phymode(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_vht(ar, vif, sta, arg); |
| ath10k_peer_assoc_h_qos(ar, vif, sta, arg); |
| |
| return 0; |
| } |
| |
| static const u32 ath10k_smps_map[] = { |
| [WLAN_HT_CAP_SM_PS_STATIC] = WMI_PEER_SMPS_STATIC, |
| [WLAN_HT_CAP_SM_PS_DYNAMIC] = WMI_PEER_SMPS_DYNAMIC, |
| [WLAN_HT_CAP_SM_PS_INVALID] = WMI_PEER_SMPS_PS_NONE, |
| [WLAN_HT_CAP_SM_PS_DISABLED] = WMI_PEER_SMPS_PS_NONE, |
| }; |
| |
| static int ath10k_setup_peer_smps(struct ath10k *ar, struct ath10k_vif *arvif, |
| const u8 *addr, |
| const struct ieee80211_sta_ht_cap *ht_cap) |
| { |
| int smps; |
| |
| if (!ht_cap->ht_supported) |
| return 0; |
| |
| smps = ht_cap->cap & IEEE80211_HT_CAP_SM_PS; |
| smps >>= IEEE80211_HT_CAP_SM_PS_SHIFT; |
| |
| if (smps >= ARRAY_SIZE(ath10k_smps_map)) |
| return -EINVAL; |
| |
| return ath10k_wmi_peer_set_param(ar, arvif->vdev_id, addr, |
| ar->wmi.peer_param->smps_state, |
| ath10k_smps_map[smps]); |
| } |
| |
| static int ath10k_mac_vif_recalc_txbf(struct ath10k *ar, |
| struct ieee80211_vif *vif, |
| struct ieee80211_sta_vht_cap vht_cap) |
| { |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| int ret; |
| u32 param; |
| u32 value; |
| |
| if (ath10k_wmi_get_txbf_conf_scheme(ar) != WMI_TXBF_CONF_AFTER_ASSOC) |
| return 0; |
| |
| if (!(ar->vht_cap_info & |
| (IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | |
| IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE | |
| IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | |
| IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE))) |
| return 0; |
| |
| param = ar->wmi.vdev_param->txbf; |
| value = 0; |
| |
| if (WARN_ON(param == WMI_VDEV_PARAM_UNSUPPORTED)) |
| return 0; |
| |
| /* The following logic is correct. If a remote STA advertises support |
| * for being a beamformer then we should enable us being a beamformee. |
| */ |
| |
| if (ar->vht_cap_info & |
| (IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE | |
| IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE)) { |
| if (vht_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE) |
| value |= WMI_VDEV_PARAM_TXBF_SU_TX_BFEE; |
| |
| if (vht_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE) |
| value |= WMI_VDEV_PARAM_TXBF_MU_TX_BFEE; |
| } |
| |
| if (ar->vht_cap_info & |
| (IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | |
| IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE)) { |
| if (vht_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE) |
| value |= WMI_VDEV_PARAM_TXBF_SU_TX_BFER; |
| |
| if (vht_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) |
| value |= WMI_VDEV_PARAM_TXBF_MU_TX_BFER; |
| } |
| |
| if (value & WMI_VDEV_PARAM_TXBF_MU_TX_BFEE) |
| value |= WMI_VDEV_PARAM_TXBF_SU_TX_BFEE; |
| |
| if (value & WMI_VDEV_PARAM_TXBF_MU_TX_BFER) |
| value |= WMI_VDEV_PARAM_TXBF_SU_TX_BFER; |
| |
| ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param, value); |
| if (ret) { |
| ath10k_warn(ar, "failed to submit vdev param txbf 0x%x: %d\n", |
| value, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static bool ath10k_mac_is_connected(struct ath10k *ar) |
| { |
| struct ath10k_vif *arvif; |
| |
| list_for_each_entry(arvif, &ar->arvifs, list) { |
| if (arvif->is_up && arvif->vdev_type == WMI_VDEV_TYPE_STA) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int ath10k_mac_txpower_setup(struct ath10k *ar, int txpower) |
| { |
| int ret; |
| u32 param; |
| int tx_power_2g, tx_power_5g; |
| bool connected; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| /* ath10k internally uses unit of 0.5 dBm so multiply by 2 */ |
| tx_power_2g = txpower * 2; |
| tx_power_5g = txpower * 2; |
| |
| connected = ath10k_mac_is_connected(ar); |
| |
| if (connected && ar->tx_power_2g_limit) |
| if (tx_power_2g > ar->tx_power_2g_limit) |
| tx_power_2g = ar->tx_power_2g_limit; |
| |
| if (connected && ar->tx_power_5g_limit) |
| if (tx_power_5g > ar->tx_power_5g_limit) |
| tx_power_5g = ar->tx_power_5g_limit; |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac txpower 2g: %d, 5g: %d\n", |
| tx_power_2g, tx_power_5g); |
| |
| param = ar->wmi.pdev_param->txpower_limit2g; |
| ret = ath10k_wmi_pdev_set_param(ar, param, tx_power_2g); |
| if (ret) { |
| ath10k_warn(ar, "failed to set 2g txpower %d: %d\n", |
| tx_power_2g, ret); |
| return ret; |
| } |
| |
| param = ar->wmi.pdev_param->txpower_limit5g; |
| ret = ath10k_wmi_pdev_set_param(ar, param, tx_power_5g); |
| if (ret) { |
| ath10k_warn(ar, "failed to set 5g txpower %d: %d\n", |
| tx_power_5g, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_txpower_recalc(struct ath10k *ar) |
| { |
| struct ath10k_vif *arvif; |
| int ret, txpower = -1; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| list_for_each_entry(arvif, &ar->arvifs, list) { |
| /* txpower not initialized yet? */ |
| if (arvif->txpower == INT_MIN) |
| continue; |
| |
| if (txpower == -1) |
| txpower = arvif->txpower; |
| else |
| txpower = min(txpower, arvif->txpower); |
| } |
| |
| if (txpower == -1) |
| return 0; |
| |
| ret = ath10k_mac_txpower_setup(ar, txpower); |
| if (ret) { |
| ath10k_warn(ar, "failed to setup tx power %d: %d\n", |
| txpower, ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int ath10k_mac_set_sar_power(struct ath10k *ar) |
| { |
| if (!ar->hw_params.dynamic_sar_support) |
| return -EOPNOTSUPP; |
| |
| if (!ath10k_mac_is_connected(ar)) |
| return 0; |
| |
| /* if connected, then arvif->txpower must be valid */ |
| return ath10k_mac_txpower_recalc(ar); |
| } |
| |
| static int ath10k_mac_set_sar_specs(struct ieee80211_hw *hw, |
| const struct cfg80211_sar_specs *sar) |
| { |
| const struct cfg80211_sar_sub_specs *sub_specs; |
| struct ath10k *ar = hw->priv; |
| u32 i; |
| int ret; |
| |
| mutex_lock(&ar->conf_mutex); |
| |
| if (!ar->hw_params.dynamic_sar_support) { |
| ret = -EOPNOTSUPP; |
| goto err; |
| } |
| |
| if (!sar || sar->type != NL80211_SAR_TYPE_POWER || |
| sar->num_sub_specs == 0) { |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| sub_specs = sar->sub_specs; |
| |
| /* 0dbm is not a practical value for ath10k, so use 0 |
| * as no SAR limitation on it. |
| */ |
| ar->tx_power_2g_limit = 0; |
| ar->tx_power_5g_limit = 0; |
| |
| /* note the power is in 0.25dbm unit, while ath10k uses |
| * 0.5dbm unit. |
| */ |
| for (i = 0; i < sar->num_sub_specs; i++) { |
| if (sub_specs->freq_range_index == 0) |
| ar->tx_power_2g_limit = sub_specs->power / 2; |
| else if (sub_specs->freq_range_index == 1) |
| ar->tx_power_5g_limit = sub_specs->power / 2; |
| |
| sub_specs++; |
| } |
| |
| ret = ath10k_mac_set_sar_power(ar); |
| if (ret) { |
| ath10k_warn(ar, "failed to set sar power: %d", ret); |
| goto err; |
| } |
| |
| err: |
| mutex_unlock(&ar->conf_mutex); |
| return ret; |
| } |
| |
| /* can be called only in mac80211 callbacks due to `key_count` usage */ |
| static void ath10k_bss_assoc(struct ieee80211_hw *hw, |
| struct ieee80211_vif *vif, |
| struct ieee80211_bss_conf *bss_conf) |
| { |
| struct ath10k *ar = hw->priv; |
| struct ath10k_vif *arvif = (void *)vif->drv_priv; |
| struct ieee80211_sta_ht_cap ht_cap; |
| struct ieee80211_sta_vht_cap vht_cap; |
| struct wmi_peer_assoc_complete_arg peer_arg; |
| struct ieee80211_sta *ap_sta; |
| int ret; |
| |
| lockdep_assert_held(&ar->conf_mutex); |
| |
| ath10k_dbg(ar, ATH10K_DBG_MAC, "mac vdev %i assoc bssid %pM aid %d\n", |
| arvif->vdev_id, arvif->bssid, arvif->aid); |
| |
| rcu_read_lock(); |
| |
| ap_sta = ieee80211_find_sta(vif, bss_conf->bssid); |
| if (!ap_sta) { |
| ath10k_warn(ar, "failed to find station entry for bss %pM vdev %i\n", |
| bss_conf->bssid, arvif->vdev_id); |
| rcu_read_unlock(); |
| return; |
| } |
| |
| /* ap_sta must be accessed only within rcu section which must be left |
| * before calling ath10k_setup_peer_smps() which might sleep. |
| */ |
| ht_cap = ap_sta->ht_cap; |
| vht_cap = ap_sta->vht_cap; |
| |
| ret = ath10k_peer_assoc_prepare(ar, vif, ap_sta, &peer_arg); |
| if (ret) { |
| ath10k_warn(ar, "failed to prepare peer assoc for %pM vdev %i: %d\n", |
| bss_conf->bssid, arvif->vdev_id, ret); |
| rcu_read_unlock(); |
| return; |
| } |
| |
| rcu_read_unlock(); |
| |
| ret = ath10k_wmi_peer_assoc(ar, &peer_arg); |
| if (ret) { |
| ath10k_warn(ar, "failed to run peer assoc for %pM vdev %i: %d\n", |
| bss_conf->bssid, arvif->vdev_id, ret); |
| return; |
| } |
| |
| ret = ath10k_setup_peer_smps(ar, arvif, bss_conf->bssid, &ht_cap); |
| if (ret) { |
| ath10k_warn(ar, "failed to setup peer SMPS for vdev %i: %d\n", |
| arvif->vdev_id, ret); |
| return; |
| } |
| |
| ret = ath10k_mac_vif_recalc_txbf(ar, vif, vht_cap); |
| if (ret) {
|