Merge branch 'iwlwifi-fixes' into iwlwifi-next
[firefly-linux-kernel-4.4.55.git] / drivers / net / wireless / iwlwifi / mvm / mac80211.c
index 0f986d7840b5398b031d6e3177106689027316b5..302c8cc50f25a695c40ce59555f8a05bf58cae2d 100644 (file)
@@ -86,6 +86,7 @@
 #include "iwl-fw-error-dump.h"
 #include "iwl-prph.h"
 #include "iwl-csr.h"
+#include "iwl-nvm-parse.h"
 
 static const struct ieee80211_iface_limit iwl_mvm_limits[] = {
        {
@@ -301,6 +302,109 @@ static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm)
        }
 }
 
+struct ieee80211_regdomain *iwl_mvm_get_regdomain(struct wiphy *wiphy,
+                                                 const char *alpha2,
+                                                 enum iwl_mcc_source src_id,
+                                                 bool *changed)
+{
+       struct ieee80211_regdomain *regd = NULL;
+       struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       struct iwl_mcc_update_resp *resp;
+
+       IWL_DEBUG_LAR(mvm, "Getting regdomain data for %s from FW\n", alpha2);
+
+       lockdep_assert_held(&mvm->mutex);
+
+       resp = iwl_mvm_update_mcc(mvm, alpha2, src_id);
+       if (IS_ERR_OR_NULL(resp)) {
+               IWL_DEBUG_LAR(mvm, "Could not get update from FW %d\n",
+                             PTR_RET(resp));
+               goto out;
+       }
+
+       if (changed)
+               *changed = (resp->status == MCC_RESP_NEW_CHAN_PROFILE);
+
+       regd = iwl_parse_nvm_mcc_info(mvm->trans->dev, mvm->cfg,
+                                     __le32_to_cpu(resp->n_channels),
+                                     resp->channels,
+                                     __le16_to_cpu(resp->mcc));
+       /* Store the return source id */
+       src_id = resp->source_id;
+       kfree(resp);
+       if (IS_ERR_OR_NULL(regd)) {
+               IWL_DEBUG_LAR(mvm, "Could not get parse update from FW %d\n",
+                             PTR_RET(regd));
+               goto out;
+       }
+
+       IWL_DEBUG_LAR(mvm, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n",
+                     regd->alpha2, regd->alpha2[0], regd->alpha2[1], src_id);
+       mvm->lar_regdom_set = true;
+       mvm->mcc_src = src_id;
+
+out:
+       return regd;
+}
+
+void iwl_mvm_update_changed_regdom(struct iwl_mvm *mvm)
+{
+       bool changed;
+       struct ieee80211_regdomain *regd;
+
+       if (!iwl_mvm_is_lar_supported(mvm))
+               return;
+
+       regd = iwl_mvm_get_current_regdomain(mvm, &changed);
+       if (!IS_ERR_OR_NULL(regd)) {
+               /* only update the regulatory core if changed */
+               if (changed)
+                       regulatory_set_wiphy_regd(mvm->hw->wiphy, regd);
+
+               kfree(regd);
+       }
+}
+
+struct ieee80211_regdomain *iwl_mvm_get_current_regdomain(struct iwl_mvm *mvm,
+                                                         bool *changed)
+{
+       return iwl_mvm_get_regdomain(mvm->hw->wiphy, "ZZ",
+                                    iwl_mvm_is_wifi_mcc_supported(mvm) ?
+                                    MCC_SOURCE_GET_CURRENT :
+                                    MCC_SOURCE_OLD_FW, changed);
+}
+
+int iwl_mvm_init_fw_regd(struct iwl_mvm *mvm)
+{
+       enum iwl_mcc_source used_src;
+       struct ieee80211_regdomain *regd;
+       const struct ieee80211_regdomain *r =
+                       rtnl_dereference(mvm->hw->wiphy->regd);
+
+       if (!r)
+               return 0;
+
+       /* save the last source in case we overwrite it below */
+       used_src = mvm->mcc_src;
+       if (iwl_mvm_is_wifi_mcc_supported(mvm)) {
+               /* Notify the firmware we support wifi location updates */
+               regd = iwl_mvm_get_current_regdomain(mvm, NULL);
+               if (!IS_ERR_OR_NULL(regd))
+                       kfree(regd);
+       }
+
+       /* Now set our last stored MCC and source */
+       regd = iwl_mvm_get_regdomain(mvm->hw->wiphy, r->alpha2, used_src, NULL);
+       if (IS_ERR_OR_NULL(regd))
+               return -EIO;
+
+       regulatory_set_wiphy_regd(mvm->hw->wiphy, regd);
+       kfree(regd);
+
+       return 0;
+}
+
 int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 {
        struct ieee80211_hw *hw = mvm->hw;
@@ -356,8 +460,12 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
                BIT(NL80211_IFTYPE_ADHOC);
 
        hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN;
-       hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG |
-                                      REGULATORY_DISABLE_BEACON_HINTS;
+       hw->wiphy->regulatory_flags |= REGULATORY_ENABLE_RELAX_NO_IR;
+       if (iwl_mvm_is_lar_supported(mvm))
+               hw->wiphy->regulatory_flags |= REGULATORY_WIPHY_SELF_MANAGED;
+       else
+               hw->wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG |
+                                              REGULATORY_DISABLE_BEACON_HINTS;
 
        if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_GO_UAPSD)
                hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD;
@@ -402,7 +510,10 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
                hw->wiphy->bands[IEEE80211_BAND_5GHZ] =
                        &mvm->nvm_data->bands[IEEE80211_BAND_5GHZ];
 
-               if (mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_BEAMFORMER)
+               if ((mvm->fw->ucode_capa.capa[0] &
+                    IWL_UCODE_TLV_CAPA_BEAMFORMER) &&
+                   (mvm->fw->ucode_capa.api[0] &
+                    IWL_UCODE_TLV_API_LQ_SS_PARAMS))
                        hw->wiphy->bands[IEEE80211_BAND_5GHZ]->vht_cap.cap |=
                                IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
        }
@@ -886,12 +997,23 @@ static void iwl_mvm_dump_fifos(struct iwl_mvm *mvm,
        iwl_trans_release_nic_access(mvm->trans, &flags);
 }
 
+void iwl_mvm_free_fw_dump_desc(struct iwl_mvm *mvm)
+{
+       if (mvm->fw_dump_desc == &iwl_mvm_dump_desc_assert ||
+           !mvm->fw_dump_desc)
+               return;
+
+       kfree(mvm->fw_dump_desc);
+       mvm->fw_dump_desc = NULL;
+}
+
 void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
 {
        struct iwl_fw_error_dump_file *dump_file;
        struct iwl_fw_error_dump_data *dump_data;
        struct iwl_fw_error_dump_info *dump_info;
        struct iwl_fw_error_dump_mem *dump_mem;
+       struct iwl_fw_error_dump_trigger_desc *dump_trig;
        struct iwl_mvm_dump_ptrs *fw_error_dump;
        u32 sram_len, sram_ofs;
        u32 file_len, fifo_data_len = 0;
@@ -961,6 +1083,10 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
                   fifo_data_len +
                   sizeof(*dump_info);
 
+       if (mvm->fw_dump_desc)
+               file_len += sizeof(*dump_data) + sizeof(*dump_trig) +
+                           mvm->fw_dump_desc->len;
+
        /* Make room for the SMEM, if it exists */
        if (smem_len)
                file_len += sizeof(*dump_data) + sizeof(*dump_mem) + smem_len;
@@ -972,6 +1098,7 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
        dump_file = vzalloc(file_len);
        if (!dump_file) {
                kfree(fw_error_dump);
+               iwl_mvm_free_fw_dump_desc(mvm);
                return;
        }
 
@@ -1000,6 +1127,19 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
        if (test_bit(STATUS_FW_ERROR, &mvm->trans->status))
                iwl_mvm_dump_fifos(mvm, &dump_data);
 
+       if (mvm->fw_dump_desc) {
+               dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_ERROR_INFO);
+               dump_data->len = cpu_to_le32(sizeof(*dump_trig) +
+                                            mvm->fw_dump_desc->len);
+               dump_trig = (void *)dump_data->data;
+               memcpy(dump_trig, &mvm->fw_dump_desc->trig_desc,
+                      sizeof(*dump_trig) + mvm->fw_dump_desc->len);
+
+               /* now we can free this copy */
+               iwl_mvm_free_fw_dump_desc(mvm);
+               dump_data = iwl_fw_error_next_data(dump_data);
+       }
+
        dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM);
        dump_data->len = cpu_to_le32(sram_len + sizeof(*dump_mem));
        dump_mem = (void *)dump_data->data;
@@ -1038,16 +1178,26 @@ void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
 
        dev_coredumpm(mvm->trans->dev, THIS_MODULE, fw_error_dump, 0,
                      GFP_KERNEL, iwl_mvm_read_coredump, iwl_mvm_free_coredump);
+
+       clear_bit(IWL_MVM_STATUS_DUMPING_FW_LOG, &mvm->status);
 }
 
+struct iwl_mvm_dump_desc iwl_mvm_dump_desc_assert = {
+       .trig_desc = {
+               .type = cpu_to_le32(FW_DBG_TRIGGER_FW_ASSERT),
+       },
+};
+
 static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
 {
        /* clear the D3 reconfig, we only need it to avoid dumping a
         * firmware coredump on reconfiguration, we shouldn't do that
         * on D3->D0 transition
         */
-       if (!test_and_clear_bit(IWL_MVM_STATUS_D3_RECONFIG, &mvm->status))
+       if (!test_and_clear_bit(IWL_MVM_STATUS_D3_RECONFIG, &mvm->status)) {
+               mvm->fw_dump_desc = &iwl_mvm_dump_desc_assert;
                iwl_mvm_fw_error_dump(mvm);
+       }
 
        /* cleanup all stale references (scan, roc), but keep the
         * ucode_down ref until reconfig is complete
@@ -1088,6 +1238,7 @@ static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
 
        mvm->vif_count = 0;
        mvm->rx_ba_sessions = 0;
+       mvm->fw_dbg_conf = FW_DBG_INVALID;
 
        /* keep statistics ticking */
        iwl_mvm_accu_radio_stats(mvm);
@@ -1150,7 +1301,7 @@ static void iwl_mvm_restart_complete(struct iwl_mvm *mvm)
 
        clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
        iwl_mvm_d0i3_enable_tx(mvm, NULL);
-       ret = iwl_mvm_update_quotas(mvm, NULL);
+       ret = iwl_mvm_update_quotas(mvm, false, NULL);
        if (ret)
                IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n",
                        ret);
@@ -1257,7 +1408,8 @@ static void iwl_mvm_mac_stop(struct ieee80211_hw *hw)
 
        flush_work(&mvm->d0i3_exit_work);
        flush_work(&mvm->async_handlers_wk);
-       flush_work(&mvm->fw_error_dump_wk);
+       cancel_delayed_work_sync(&mvm->fw_dump_wk);
+       iwl_mvm_free_fw_dump_desc(mvm);
 
        mutex_lock(&mvm->mutex);
        __iwl_mvm_mac_stop(mvm);
@@ -1305,6 +1457,8 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
        struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
        int ret;
 
+       mvmvif->mvm = mvm;
+
        /*
         * make sure D0i3 exit is completed, otherwise a target access
         * during tx queue configuration could be done when still in
@@ -1322,6 +1476,11 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
 
        mutex_lock(&mvm->mutex);
 
+       /* make sure that beacon statistics don't go backwards with FW reset */
+       if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
+               mvmvif->beacon_stats.accu_num_beacons +=
+                       mvmvif->beacon_stats.num_beacons;
+
        /* Allocate resources for the MAC context, and add it to the fw  */
        ret = iwl_mvm_mac_ctxt_init(mvm, vif);
        if (ret)
@@ -1815,8 +1974,13 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
 
        if (changes & BSS_CHANGED_ASSOC) {
                if (bss_conf->assoc) {
+                       /* clear statistics to get clean beacon counter */
+                       iwl_mvm_request_statistics(mvm, true);
+                       memset(&mvmvif->beacon_stats, 0,
+                              sizeof(mvmvif->beacon_stats));
+
                        /* add quota for this interface */
-                       ret = iwl_mvm_update_quotas(mvm, NULL);
+                       ret = iwl_mvm_update_quotas(mvm, true, NULL);
                        if (ret) {
                                IWL_ERR(mvm, "failed to update quotas\n");
                                return;
@@ -1868,7 +2032,7 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
                                mvm->d0i3_ap_sta_id = IWL_MVM_STATION_COUNT;
                        mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT;
                        /* remove quota for this interface */
-                       ret = iwl_mvm_update_quotas(mvm, NULL);
+                       ret = iwl_mvm_update_quotas(mvm, false, NULL);
                        if (ret)
                                IWL_ERR(mvm, "failed to update quotas\n");
 
@@ -1987,7 +2151,7 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw,
        /* power updated needs to be done before quotas */
        iwl_mvm_power_update_mac(mvm);
 
-       ret = iwl_mvm_update_quotas(mvm, NULL);
+       ret = iwl_mvm_update_quotas(mvm, false, NULL);
        if (ret)
                goto out_quota_failed;
 
@@ -2053,7 +2217,7 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw,
        if (vif->p2p && mvm->p2p_device_vif)
                iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL);
 
-       iwl_mvm_update_quotas(mvm, NULL);
+       iwl_mvm_update_quotas(mvm, false, NULL);
        iwl_mvm_send_rm_bcast_sta(mvm, vif);
        iwl_mvm_binding_remove_vif(mvm, vif);
 
@@ -2192,6 +2356,12 @@ static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw,
 
        mutex_lock(&mvm->mutex);
 
+       if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) {
+               IWL_ERR(mvm, "scan while LAR regdomain is not set\n");
+               ret = -EBUSY;
+               goto out;
+       }
+
        if (mvm->scan_status != IWL_MVM_SCAN_NONE) {
                ret = -EBUSY;
                goto out;
@@ -2218,7 +2388,19 @@ static void iwl_mvm_mac_cancel_hw_scan(struct ieee80211_hw *hw,
 
        mutex_lock(&mvm->mutex);
 
-       iwl_mvm_cancel_scan(mvm);
+       /* Due to a race condition, it's possible that mac80211 asks
+        * us to stop a hw_scan when it's already stopped.  This can
+        * happen, for instance, if we stopped the scan ourselves,
+        * called ieee80211_scan_completed() and the userspace called
+        * cancel scan scan before ieee80211_scan_work() could run.
+        * To handle that, simply return if the scan is not running.
+       */
+       /* FIXME: for now, we ignore this race for UMAC scans, since
+        * they don't set the scan_status.
+        */
+       if ((mvm->scan_status == IWL_MVM_SCAN_OS) ||
+           (mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN))
+               iwl_mvm_cancel_scan(mvm);
 
        mutex_unlock(&mvm->mutex);
 }
@@ -2260,25 +2442,35 @@ static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw,
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
+       unsigned long txqs = 0, tids = 0;
        int tid;
 
+       spin_lock_bh(&mvmsta->lock);
+       for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
+               struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
+
+               if (tid_data->state != IWL_AGG_ON &&
+                   tid_data->state != IWL_EMPTYING_HW_QUEUE_DELBA)
+                       continue;
+
+               __set_bit(tid_data->txq_id, &txqs);
+
+               if (iwl_mvm_tid_queued(tid_data) == 0)
+                       continue;
+
+               __set_bit(tid, &tids);
+       }
+
        switch (cmd) {
        case STA_NOTIFY_SLEEP:
                if (atomic_read(&mvm->pending_frames[mvmsta->sta_id]) > 0)
                        ieee80211_sta_block_awake(hw, sta, true);
-               spin_lock_bh(&mvmsta->lock);
-               for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) {
-                       struct iwl_mvm_tid_data *tid_data;
 
-                       tid_data = &mvmsta->tid_data[tid];
-                       if (tid_data->state != IWL_AGG_ON &&
-                           tid_data->state != IWL_EMPTYING_HW_QUEUE_DELBA)
-                               continue;
-                       if (iwl_mvm_tid_queued(tid_data) == 0)
-                               continue;
+               for_each_set_bit(tid, &tids, IWL_MAX_TID_COUNT)
                        ieee80211_sta_set_buffered(sta, tid, true);
-               }
-               spin_unlock_bh(&mvmsta->lock);
+
+               if (txqs)
+                       iwl_trans_freeze_txq_timer(mvm->trans, txqs, true);
                /*
                 * The fw updates the STA to be asleep. Tx packets on the Tx
                 * queues to this station will not be transmitted. The fw will
@@ -2288,11 +2480,15 @@ static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw,
        case STA_NOTIFY_AWAKE:
                if (WARN_ON(mvmsta->sta_id == IWL_MVM_STATION_COUNT))
                        break;
+
+               if (txqs)
+                       iwl_trans_freeze_txq_timer(mvm->trans, txqs, false);
                iwl_mvm_sta_modify_ps_wake(mvm, sta);
                break;
        default:
                break;
        }
+       spin_unlock_bh(&mvmsta->lock);
 }
 
 static void iwl_mvm_sta_pre_rcu_remove(struct ieee80211_hw *hw,
@@ -2530,6 +2726,12 @@ static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw,
 
        mutex_lock(&mvm->mutex);
 
+       if (iwl_mvm_is_lar_supported(mvm) && !mvm->lar_regdom_set) {
+               IWL_ERR(mvm, "sched-scan while LAR regdomain is not set\n");
+               ret = -EBUSY;
+               goto out;
+       }
+
        if (!vif->bss_conf.idle) {
                ret = -EBUSY;
                goto out;
@@ -2556,12 +2758,29 @@ static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw,
        int ret;
 
        mutex_lock(&mvm->mutex);
+
+       /* Due to a race condition, it's possible that mac80211 asks
+        * us to stop a sched_scan when it's already stopped.  This
+        * can happen, for instance, if we stopped the scan ourselves,
+        * called ieee80211_sched_scan_stopped() and the userspace called
+        * stop sched scan scan before ieee80211_sched_scan_stopped_work()
+        * could run.  To handle this, simply return if the scan is
+        * not running.
+       */
+       /* FIXME: for now, we ignore this race for UMAC scans, since
+        * they don't set the scan_status.
+        */
+       if (mvm->scan_status != IWL_MVM_SCAN_SCHED &&
+           !(mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
+               mutex_unlock(&mvm->mutex);
+               return 0;
+       }
+
        ret = iwl_mvm_scan_offload_stop(mvm, false);
        mutex_unlock(&mvm->mutex);
        iwl_mvm_wait_for_async_handlers(mvm);
 
        return ret;
-
 }
 
 static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw,
@@ -3074,14 +3293,14 @@ static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm,
         */
        if (vif->type == NL80211_IFTYPE_MONITOR) {
                mvmvif->monitor_active = true;
-               ret = iwl_mvm_update_quotas(mvm, NULL);
+               ret = iwl_mvm_update_quotas(mvm, false, NULL);
                if (ret)
                        goto out_remove_binding;
        }
 
        /* Handle binding during CSA */
        if (vif->type == NL80211_IFTYPE_AP) {
-               iwl_mvm_update_quotas(mvm, NULL);
+               iwl_mvm_update_quotas(mvm, false, NULL);
                iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
        }
 
@@ -3105,7 +3324,7 @@ static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm,
 
                iwl_mvm_unref(mvm, IWL_MVM_REF_PROTECT_CSA);
 
-               iwl_mvm_update_quotas(mvm, NULL);
+               iwl_mvm_update_quotas(mvm, false, NULL);
        }
 
        goto out;
@@ -3178,7 +3397,7 @@ static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm,
                break;
        }
 
-       iwl_mvm_update_quotas(mvm, disabled_vif);
+       iwl_mvm_update_quotas(mvm, false, disabled_vif);
        iwl_mvm_binding_remove_vif(mvm, vif);
 
 out:
@@ -3370,7 +3589,7 @@ static int __iwl_mvm_mac_testmode_cmd(struct iwl_mvm *mvm,
                mvm->noa_duration = noa_duration;
                mvm->noa_vif = vif;
 
-               return iwl_mvm_update_quotas(mvm, NULL);
+               return iwl_mvm_update_quotas(mvm, false, NULL);
        case IWL_MVM_TM_CMD_SET_BEACON_FILTER:
                /* must be associated client vif - ignore authorized */
                if (!vif || vif->type != NL80211_IFTYPE_STATION ||
@@ -3430,6 +3649,9 @@ static int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,
        IWL_DEBUG_MAC80211(mvm, "pre CSA to freq %d\n",
                           chsw->chandef.center_freq1);
 
+       iwl_fw_dbg_trigger_simple_stop(mvm, vif, FW_DBG_TRIGGER_CHANNEL_SWITCH,
+                                      NULL, 0);
+
        switch (vif->type) {
        case NL80211_IFTYPE_AP:
                csa_vif =
@@ -3597,7 +3819,7 @@ static int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx,
        mutex_lock(&mvm->mutex);
 
        if (mvm->ucode_loaded) {
-               ret = iwl_mvm_request_statistics(mvm);
+               ret = iwl_mvm_request_statistics(mvm, false);
                if (ret)
                        goto out;
        }
@@ -3627,6 +3849,46 @@ static int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx,
        return ret;
 }
 
+static void iwl_mvm_mac_sta_statistics(struct ieee80211_hw *hw,
+                                      struct ieee80211_vif *vif,
+                                      struct ieee80211_sta *sta,
+                                      struct station_info *sinfo)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
+
+       if (!(mvm->fw->ucode_capa.capa[0] &
+                               IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS))
+               return;
+
+       /* if beacon filtering isn't on mac80211 does it anyway */
+       if (!(vif->driver_flags & IEEE80211_VIF_BEACON_FILTER))
+               return;
+
+       if (!vif->bss_conf.assoc)
+               return;
+
+       mutex_lock(&mvm->mutex);
+
+       if (mvmvif->ap_sta_id != mvmsta->sta_id)
+               goto unlock;
+
+       if (iwl_mvm_request_statistics(mvm, false))
+               goto unlock;
+
+       sinfo->rx_beacon = mvmvif->beacon_stats.num_beacons +
+                          mvmvif->beacon_stats.accu_num_beacons;
+       sinfo->filled |= BIT(NL80211_STA_INFO_BEACON_RX);
+       if (mvmvif->beacon_stats.avg_signal) {
+               /* firmware only reports a value after RXing a few beacons */
+               sinfo->rx_beacon_signal_avg = mvmvif->beacon_stats.avg_signal;
+               sinfo->filled |= BIT(NL80211_STA_INFO_BEACON_SIGNAL_AVG);
+       }
+ unlock:
+       mutex_unlock(&mvm->mutex);
+}
+
 const struct ieee80211_ops iwl_mvm_hw_ops = {
        .tx = iwl_mvm_mac_tx,
        .ampdu_action = iwl_mvm_mac_ampdu_action,
@@ -3694,4 +3956,5 @@ const struct ieee80211_ops iwl_mvm_hw_ops = {
        .set_default_unicast_key = iwl_mvm_set_default_unicast_key,
 #endif
        .get_survey = iwl_mvm_mac_get_survey,
+       .sta_statistics = iwl_mvm_mac_sta_statistics,
 };