]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/net/wireless/iwlwifi/mvm/mac80211.c
iwlwifi: mvm: Refactor and fix max probe len computation
[karo-tx-linux.git] / drivers / net / wireless / iwlwifi / mvm / mac80211.c
index 98556d03c1edabf8e521998d4eec0f4a41a89fdf..c7a73c68bdabddcb2eb2536256e0a7736be06dc1 100644 (file)
@@ -6,6 +6,7 @@
  * GPL LICENSE SUMMARY
  *
  * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of version 2 of the GNU General Public License as
@@ -31,6 +32,7 @@
  * BSD LICENSE
  *
  * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
+ * Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -80,6 +82,8 @@
 #include "fw-api-scan.h"
 #include "iwl-phy-db.h"
 #include "testmode.h"
+#include "iwl-fw-error-dump.h"
+#include "iwl-prph.h"
 
 static const struct ieee80211_iface_limit iwl_mvm_limits[] = {
        {
@@ -209,7 +213,9 @@ void iwl_mvm_ref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
                return;
 
        IWL_DEBUG_RPM(mvm, "Take mvm reference - type %d\n", ref_type);
-       WARN_ON(test_and_set_bit(ref_type, mvm->ref_bitmap));
+       spin_lock_bh(&mvm->refs_lock);
+       mvm->refs[ref_type]++;
+       spin_unlock_bh(&mvm->refs_lock);
        iwl_trans_ref(mvm->trans);
 }
 
@@ -219,26 +225,47 @@ void iwl_mvm_unref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
                return;
 
        IWL_DEBUG_RPM(mvm, "Leave mvm reference - type %d\n", ref_type);
-       WARN_ON(!test_and_clear_bit(ref_type, mvm->ref_bitmap));
+       spin_lock_bh(&mvm->refs_lock);
+       WARN_ON(!mvm->refs[ref_type]--);
+       spin_unlock_bh(&mvm->refs_lock);
        iwl_trans_unref(mvm->trans);
 }
 
-static void
-iwl_mvm_unref_all_except(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref)
+static void iwl_mvm_unref_all_except(struct iwl_mvm *mvm,
+                                    enum iwl_mvm_ref_type except_ref)
 {
-       int i;
+       int i, j;
 
        if (!iwl_mvm_is_d0i3_supported(mvm))
                return;
 
-       for_each_set_bit(i, mvm->ref_bitmap, IWL_MVM_REF_COUNT) {
-               if (ref == i)
+       spin_lock_bh(&mvm->refs_lock);
+       for (i = 0; i < IWL_MVM_REF_COUNT; i++) {
+               if (except_ref == i || !mvm->refs[i])
                        continue;
 
-               IWL_DEBUG_RPM(mvm, "Cleanup: remove mvm ref type %d\n", i);
-               clear_bit(i, mvm->ref_bitmap);
-               iwl_trans_unref(mvm->trans);
+               IWL_DEBUG_RPM(mvm, "Cleanup: remove mvm ref type %d (%d)\n",
+                             i, mvm->refs[i]);
+               for (j = 0; j < mvm->refs[i]; j++)
+                       iwl_trans_unref(mvm->trans);
+               mvm->refs[i] = 0;
+       }
+       spin_unlock_bh(&mvm->refs_lock);
+}
+
+int iwl_mvm_ref_sync(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
+{
+       iwl_mvm_ref(mvm, ref_type);
+
+       if (!wait_event_timeout(mvm->d0i3_exit_waitq,
+                               !test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status),
+                               HZ)) {
+               WARN_ON_ONCE(1);
+               iwl_mvm_unref(mvm, ref_type);
+               return -EIO;
        }
+
+       return 0;
 }
 
 static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm)
@@ -252,14 +279,6 @@ static void iwl_mvm_reset_phy_ctxts(struct iwl_mvm *mvm)
        }
 }
 
-static int iwl_mvm_max_scan_ie_len(struct iwl_mvm *mvm)
-{
-       /* we create the 802.11 header and SSID element */
-       if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_NO_BASIC_SSID)
-               return mvm->fw->ucode_capa.max_probe_length - 24 - 2;
-       return mvm->fw->ucode_capa.max_probe_length - 24 - 34;
-}
-
 int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 {
        struct ieee80211_hw *hw = mvm->hw;
@@ -276,8 +295,8 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
                    IEEE80211_HW_AMPDU_AGGREGATION |
                    IEEE80211_HW_TIMING_BEACON_ONLY |
                    IEEE80211_HW_CONNECTION_MONITOR |
-                   IEEE80211_HW_SUPPORTS_DYNAMIC_SMPS |
-                   IEEE80211_HW_SUPPORTS_STATIC_SMPS;
+                   IEEE80211_HW_CHANCTX_STA_CSA |
+                   IEEE80211_HW_SUPPORTS_CLONED_SKBS;
 
        hw->queues = mvm->first_agg_queue;
        hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE;
@@ -299,10 +318,13 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
            IWL_UCODE_API(mvm->fw->ucode_ver) >= 9 &&
            !iwlwifi_mod_params.uapsd_disable) {
                hw->flags |= IEEE80211_HW_SUPPORTS_UAPSD;
-               hw->uapsd_queues = IWL_UAPSD_AC_INFO;
+               hw->uapsd_queues = IWL_MVM_UAPSD_QUEUES;
                hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP;
        }
 
+       if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)
+               hw->flags |= IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS;
+
        hw->sta_data_size = sizeof(struct iwl_mvm_sta);
        hw->vif_data_size = sizeof(struct iwl_mvm_vif);
        hw->chanctx_data_size = sizeof(u16);
@@ -349,7 +371,7 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 
        iwl_mvm_reset_phy_ctxts(mvm);
 
-       hw->wiphy->max_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm);
+       hw->wiphy->max_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm, false);
 
        hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX;
 
@@ -367,14 +389,36 @@ int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
        else
                hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT;
 
-       hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
-       hw->wiphy->max_sched_scan_ssids = PROBE_OPTION_MAX;
-       hw->wiphy->max_match_sets = IWL_SCAN_MAX_PROFILES;
-       /* we create the 802.11 header and zero length SSID IE. */
-       hw->wiphy->max_sched_scan_ie_len = SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2;
+       if (IWL_UCODE_API(mvm->fw->ucode_ver) >= 10) {
+               hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_SCHED_SCAN;
+               hw->wiphy->max_sched_scan_ssids = PROBE_OPTION_MAX;
+               hw->wiphy->max_match_sets = IWL_SCAN_MAX_PROFILES;
+               /* we create the 802.11 header and zero length SSID IE. */
+               hw->wiphy->max_sched_scan_ie_len =
+                       SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2;
+       }
 
        hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN |
-                              NL80211_FEATURE_P2P_GO_OPPPS;
+                              NL80211_FEATURE_LOW_PRIORITY_SCAN |
+                              NL80211_FEATURE_P2P_GO_OPPPS |
+                              NL80211_FEATURE_DYNAMIC_SMPS |
+                              NL80211_FEATURE_STATIC_SMPS;
+
+       if (mvm->fw->ucode_capa.capa[0] &
+           IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT)
+               hw->wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION;
+       if (mvm->fw->ucode_capa.capa[0] &
+           IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT)
+               hw->wiphy->features |= NL80211_FEATURE_QUIET;
+
+       if (mvm->fw->ucode_capa.capa[0] &
+           IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT)
+               hw->wiphy->features |=
+                       NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES;
+
+       if (mvm->fw->ucode_capa.capa[0] &
+           IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT)
+               hw->wiphy->features |= NL80211_FEATURE_WFA_TPC_IE_IN_PROBES;
 
        mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD;
 
@@ -549,9 +593,6 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
        case IEEE80211_AMPDU_TX_STOP_FLUSH:
        case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
        case IEEE80211_AMPDU_TX_OPERATIONAL:
-               iwl_mvm_ref(mvm, IWL_MVM_REF_TX_AGG);
-               tx_agg_ref = true;
-
                /*
                 * for tx start, wait synchronously until D0i3 exit to
                 * get the correct sequence number for the tid.
@@ -560,12 +601,11 @@ static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
                 * by the trans layer (unlike commands), so wait for
                 * d0i3 exit in these cases as well.
                 */
-               if (!wait_event_timeout(mvm->d0i3_exit_waitq,
-                         !test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status), HZ)) {
-                       WARN_ON_ONCE(1);
-                       iwl_mvm_unref(mvm, IWL_MVM_REF_TX_AGG);
-                       return -EIO;
-               }
+               ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_TX_AGG);
+               if (ret)
+                       return ret;
+
+               tx_agg_ref = true;
                break;
        default:
                break;
@@ -635,22 +675,118 @@ static void iwl_mvm_cleanup_iterator(void *data, u8 *mac,
        spin_unlock_bh(&mvm->time_event_lock);
 
        mvmvif->phy_ctxt = NULL;
+       memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data));
 }
 
-static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
-{
 #ifdef CONFIG_IWLWIFI_DEBUGFS
+void iwl_mvm_fw_error_dump(struct iwl_mvm *mvm)
+{
        static char *env[] = { "DRIVER=iwlwifi", "EVENT=error_dump", NULL };
+       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_mvm_dump_ptrs *fw_error_dump;
+       const struct fw_img *img;
+       u32 sram_len, sram_ofs;
+       u32 file_len, rxf_len;
+       unsigned long flags;
+       int reg_val;
 
-       iwl_mvm_fw_error_dump(mvm);
+       lockdep_assert_held(&mvm->mutex);
+
+       if (mvm->fw_error_dump)
+               return;
+
+       fw_error_dump = kzalloc(sizeof(*mvm->fw_error_dump), GFP_KERNEL);
+       if (!fw_error_dump)
+               return;
+
+       img = &mvm->fw->img[mvm->cur_ucode];
+       sram_ofs = img->sec[IWL_UCODE_SECTION_DATA].offset;
+       sram_len = img->sec[IWL_UCODE_SECTION_DATA].len;
+
+       /* reading buffer size */
+       reg_val = iwl_trans_read_prph(mvm->trans, RXF_SIZE_ADDR);
+       rxf_len = (reg_val & RXF_SIZE_BYTE_CNT_MSK) >> RXF_SIZE_BYTE_CND_POS;
+
+       /* the register holds the value divided by 128 */
+       rxf_len = rxf_len << 7;
+
+       file_len = sizeof(*dump_file) +
+                  sizeof(*dump_data) * 3 +
+                  sram_len +
+                  rxf_len +
+                  sizeof(*dump_info);
+
+       dump_file = vzalloc(file_len);
+       if (!dump_file) {
+               kfree(fw_error_dump);
+               return;
+       }
+
+       fw_error_dump->op_mode_ptr = dump_file;
+
+       dump_file->barker = cpu_to_le32(IWL_FW_ERROR_DUMP_BARKER);
+       dump_data = (void *)dump_file->data;
+
+       dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_DEV_FW_INFO);
+       dump_data->len = cpu_to_le32(sizeof(*dump_info));
+       dump_info = (void *) dump_data->data;
+       dump_info->device_family =
+               mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000 ?
+                       cpu_to_le32(IWL_FW_ERROR_DUMP_FAMILY_7) :
+                       cpu_to_le32(IWL_FW_ERROR_DUMP_FAMILY_8);
+       memcpy(dump_info->fw_human_readable, mvm->fw->human_readable,
+              sizeof(dump_info->fw_human_readable));
+       strncpy(dump_info->dev_human_readable, mvm->cfg->name,
+               sizeof(dump_info->dev_human_readable));
+       strncpy(dump_info->bus_human_readable, mvm->dev->bus->name,
+               sizeof(dump_info->bus_human_readable));
+
+       dump_data = iwl_fw_error_next_data(dump_data);
+       dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF);
+       dump_data->len = cpu_to_le32(rxf_len);
+
+       if (iwl_trans_grab_nic_access(mvm->trans, false, &flags)) {
+               u32 *rxf = (void *)dump_data->data;
+               int i;
+
+               for (i = 0; i < (rxf_len / sizeof(u32)); i++) {
+                       iwl_trans_write_prph(mvm->trans,
+                                            RXF_LD_FENCE_OFFSET_ADDR,
+                                            i * sizeof(u32));
+                       rxf[i] = iwl_trans_read_prph(mvm->trans,
+                                                    RXF_FIFO_RD_FENCE_ADDR);
+               }
+               iwl_trans_release_nic_access(mvm->trans, &flags);
+       }
+
+       dump_data = iwl_fw_error_next_data(dump_data);
+       dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_SRAM);
+       dump_data->len = cpu_to_le32(sram_len);
+       iwl_trans_read_mem_bytes(mvm->trans, sram_ofs, dump_data->data,
+                                sram_len);
+
+       fw_error_dump->trans_ptr = iwl_trans_dump_data(mvm->trans);
+       fw_error_dump->op_mode_len = file_len;
+       if (fw_error_dump->trans_ptr)
+               file_len += fw_error_dump->trans_ptr->len;
+       dump_file->file_len = cpu_to_le32(file_len);
+       mvm->fw_error_dump = fw_error_dump;
 
        /* notify the userspace about the error we had */
        kobject_uevent_env(&mvm->hw->wiphy->dev.kobj, KOBJ_CHANGE, env);
+}
 #endif
 
+static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
+{
+       iwl_mvm_fw_error_dump(mvm);
+
        iwl_trans_stop_device(mvm->trans);
 
        mvm->scan_status = IWL_MVM_SCAN_NONE;
+       mvm->ps_disabled = false;
 
        /* just in case one was running */
        ieee80211_remain_on_channel_expired(mvm->hw);
@@ -665,6 +801,12 @@ static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
        iwl_mvm_reset_phy_ctxts(mvm);
        memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table));
        memset(mvm->sta_drained, 0, sizeof(mvm->sta_drained));
+       memset(&mvm->last_bt_notif, 0, sizeof(mvm->last_bt_notif));
+       memset(&mvm->last_bt_notif_old, 0, sizeof(mvm->last_bt_notif_old));
+       memset(&mvm->last_bt_ci_cmd, 0, sizeof(mvm->last_bt_ci_cmd));
+       memset(&mvm->last_bt_ci_cmd_old, 0, sizeof(mvm->last_bt_ci_cmd_old));
+       memset(&mvm->bt_ack_kill_msk, 0, sizeof(mvm->bt_ack_kill_msk));
+       memset(&mvm->bt_cts_kill_msk, 0, sizeof(mvm->bt_cts_kill_msk));
 
        ieee80211_wake_queues(mvm->hw);
 
@@ -672,22 +814,44 @@ static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
         * ucode_down ref until reconfig is complete */
        iwl_mvm_unref_all_except(mvm, IWL_MVM_REF_UCODE_DOWN);
 
+       /* clear any stale d0i3 state */
+       clear_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status);
+
        mvm->vif_count = 0;
        mvm->rx_ba_sessions = 0;
 }
 
-static int iwl_mvm_mac_start(struct ieee80211_hw *hw)
+int __iwl_mvm_mac_start(struct iwl_mvm *mvm)
 {
-       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        int ret;
 
-       mutex_lock(&mvm->mutex);
+       lockdep_assert_held(&mvm->mutex);
 
        /* Clean up some internal and mac80211 state on restart */
        if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
                iwl_mvm_restart_cleanup(mvm);
 
        ret = iwl_mvm_up(mvm);
+
+       if (ret && test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
+               /* Something went wrong - we need to finish some cleanup
+                * that normally iwl_mvm_mac_restart_complete() below
+                * would do.
+                */
+               clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
+               iwl_mvm_d0i3_enable_tx(mvm, NULL);
+       }
+
+       return ret;
+}
+
+static int iwl_mvm_mac_start(struct ieee80211_hw *hw)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       int ret;
+
+       mutex_lock(&mvm->mutex);
+       ret = __iwl_mvm_mac_start(mvm);
        mutex_unlock(&mvm->mutex);
 
        return ret;
@@ -713,14 +877,9 @@ static void iwl_mvm_mac_restart_complete(struct ieee80211_hw *hw)
        mutex_unlock(&mvm->mutex);
 }
 
-static void iwl_mvm_mac_stop(struct ieee80211_hw *hw)
+void __iwl_mvm_mac_stop(struct iwl_mvm *mvm)
 {
-       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
-
-       flush_work(&mvm->d0i3_exit_work);
-       flush_work(&mvm->async_handlers_wk);
-
-       mutex_lock(&mvm->mutex);
+       lockdep_assert_held(&mvm->mutex);
 
        /* disallow low power states when the FW is down */
        iwl_mvm_ref(mvm, IWL_MVM_REF_UCODE_DOWN);
@@ -739,8 +898,21 @@ static void iwl_mvm_mac_stop(struct ieee80211_hw *hw)
        /* async_handlers_list is empty and will stay empty: HW is stopped */
 
        /* the fw is stopped, the aux sta is dead: clean up driver state */
-       iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta);
+       iwl_mvm_del_aux_sta(mvm);
+
+       mvm->ucode_loaded = false;
+}
+
+static void iwl_mvm_mac_stop(struct ieee80211_hw *hw)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+
+       flush_work(&mvm->d0i3_exit_work);
+       flush_work(&mvm->async_handlers_wk);
+       flush_work(&mvm->fw_error_dump_wk);
 
+       mutex_lock(&mvm->mutex);
+       __iwl_mvm_mac_stop(mvm);
        mutex_unlock(&mvm->mutex);
 
        /*
@@ -785,6 +957,15 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
        struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
        int ret;
 
+       /*
+        * make sure D0i3 exit is completed, otherwise a target access
+        * during tx queue configuration could be done when still in
+        * D0i3 state.
+        */
+       ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_ADD_IF);
+       if (ret)
+               return ret;
+
        /*
         * Not much to do here. The stack will not allow interface
         * types or combinations that we didn't advertise, so we
@@ -815,10 +996,7 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
         */
        if (vif->type == NL80211_IFTYPE_AP ||
            vif->type == NL80211_IFTYPE_ADHOC) {
-               u32 qmask = iwl_mvm_mac_get_queues_mask(mvm, vif);
-               ret = iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta,
-                                              qmask,
-                                              ieee80211_vif_type_p2p(vif));
+               ret = iwl_mvm_alloc_bcast_sta(mvm, vif);
                if (ret) {
                        IWL_ERR(mvm, "Failed to allocate bcast sta\n");
                        goto out_release;
@@ -866,7 +1044,7 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
                if (ret)
                        goto out_unref_phy;
 
-               ret = iwl_mvm_add_bcast_sta(mvm, vif, &mvmvif->bcast_sta);
+               ret = iwl_mvm_add_bcast_sta(mvm, vif);
                if (ret)
                        goto out_unbind;
 
@@ -899,20 +1077,15 @@ static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
  out_unlock:
        mutex_unlock(&mvm->mutex);
 
+       iwl_mvm_unref(mvm, IWL_MVM_REF_ADD_IF);
+
        return ret;
 }
 
 static void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm,
                                        struct ieee80211_vif *vif)
 {
-       u32 tfd_msk = 0, ac;
-
-       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
-               if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE)
-                       tfd_msk |= BIT(vif->hw_queue[ac]);
-
-       if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE)
-               tfd_msk |= BIT(vif->cab_queue);
+       u32 tfd_msk = iwl_mvm_mac_get_queues_mask(mvm, vif);
 
        if (tfd_msk) {
                mutex_lock(&mvm->mutex);
@@ -968,13 +1141,13 @@ static void iwl_mvm_mac_remove_interface(struct ieee80211_hw *hw,
                        mvm->noa_duration = 0;
                }
 #endif
-               iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta);
+               iwl_mvm_dealloc_bcast_sta(mvm, vif);
                goto out_release;
        }
 
        if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {
                mvm->p2p_device_vif = NULL;
-               iwl_mvm_rm_bcast_sta(mvm, &mvmvif->bcast_sta);
+               iwl_mvm_rm_bcast_sta(mvm, vif);
                iwl_mvm_binding_remove_vif(mvm, vif);
                iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt);
                mvmvif->phy_ctxt = NULL;
@@ -1048,14 +1221,15 @@ static u64 iwl_mvm_prepare_multicast(struct ieee80211_hw *hw,
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        struct iwl_mcast_filter_cmd *cmd;
        struct netdev_hw_addr *addr;
-       int addr_count = netdev_hw_addr_list_count(mc_list);
-       bool pass_all = false;
+       int addr_count;
+       bool pass_all;
        int len;
 
-       if (addr_count > MAX_MCAST_FILTERING_ADDRESSES) {
-               pass_all = true;
+       addr_count = netdev_hw_addr_list_count(mc_list);
+       pass_all = addr_count > MAX_MCAST_FILTERING_ADDRESSES ||
+                  IWL_MVM_FW_MCAST_FILTER_PASS_ALL;
+       if (pass_all)
                addr_count = 0;
-       }
 
        len = roundup(sizeof(*cmd) + addr_count * ETH_ALEN, 4);
        cmd = kzalloc(len, GFP_ATOMIC);
@@ -1271,14 +1445,27 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
        if (changes & BSS_CHANGED_ASSOC && bss_conf->assoc)
                iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif);
 
-       ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false);
+       /*
+        * If we're not associated yet, take the (new) BSSID before associating
+        * so the firmware knows. If we're already associated, then use the old
+        * BSSID here, and we'll send a cleared one later in the CHANGED_ASSOC
+        * branch for disassociation below.
+        */
+       if (changes & BSS_CHANGED_BSSID && !mvmvif->associated)
+               memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN);
+
+       ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, mvmvif->bssid);
        if (ret)
                IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr);
 
+       /* after sending it once, adopt mac80211 data */
+       memcpy(mvmvif->bssid, bss_conf->bssid, ETH_ALEN);
+       mvmvif->associated = bss_conf->assoc;
+
        if (changes & BSS_CHANGED_ASSOC) {
                if (bss_conf->assoc) {
                        /* add quota for this interface */
-                       ret = iwl_mvm_update_quotas(mvm, vif);
+                       ret = iwl_mvm_update_quotas(mvm, NULL);
                        if (ret) {
                                IWL_ERR(mvm, "failed to update quotas\n");
                                return;
@@ -1302,13 +1489,17 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
                                 */
                                u32 dur = (11 * vif->bss_conf.beacon_int) / 10;
                                iwl_mvm_protect_session(mvm, vif, dur, dur,
-                                                       5 * dur);
+                                                       5 * dur, false);
                        }
 
                        iwl_mvm_sf_update(mvm, vif, false);
                        iwl_mvm_power_vif_assoc(mvm, vif);
-                       if (vif->p2p)
+                       if (vif->p2p) {
                                iwl_mvm_ref(mvm, IWL_MVM_REF_P2P_CLIENT);
+                               iwl_mvm_update_smps(mvm, vif,
+                                                   IWL_MVM_SMPS_REQ_PROT,
+                                                   IEEE80211_SMPS_DYNAMIC);
+                       }
                } else if (mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) {
                        /*
                         * If update fails - SF might be running in associated
@@ -1332,6 +1523,13 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
 
                        if (vif->p2p)
                                iwl_mvm_unref(mvm, IWL_MVM_REF_P2P_CLIENT);
+
+                       /* this will take the cleared BSSID from bss_conf */
+                       ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
+                       if (ret)
+                               IWL_ERR(mvm,
+                                       "failed to update MAC %pM (clear after unassoc)\n",
+                                       vif->addr);
                }
 
                iwl_mvm_recalc_multicast(mvm);
@@ -1350,14 +1548,19 @@ static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
                 */
                iwl_mvm_remove_time_event(mvm, mvmvif,
                                          &mvmvif->time_event_data);
+       }
+
+       if (changes & BSS_CHANGED_BEACON_INFO) {
                iwl_mvm_sf_update(mvm, vif, false);
                WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0));
-       } else if (changes & (BSS_CHANGED_PS | BSS_CHANGED_P2P_PS |
-                             BSS_CHANGED_QOS)) {
+       }
+
+       if (changes & (BSS_CHANGED_PS | BSS_CHANGED_P2P_PS | BSS_CHANGED_QOS)) {
                ret = iwl_mvm_power_update_mac(mvm);
                if (ret)
                        IWL_ERR(mvm, "failed to update power mode\n");
        }
+
        if (changes & BSS_CHANGED_TXPOWER) {
                IWL_DEBUG_CALIB(mvm, "Changing TX Power to %d\n",
                                bss_conf->txpower);
@@ -1389,6 +1592,14 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw,
        struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
        int ret;
 
+       /*
+        * iwl_mvm_mac_ctxt_add() might read directly from the device
+        * (the system time), so make sure it is available.
+        */
+       ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_START_AP);
+       if (ret)
+               return ret;
+
        mutex_lock(&mvm->mutex);
 
        /* Send the beacon template */
@@ -1415,7 +1626,7 @@ static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw,
 
        /* Send the bcast station. At this stage the TBTT and DTIM time events
         * are added and applied to the scheduler */
-       ret = iwl_mvm_send_bcast_sta(mvm, vif, &mvmvif->bcast_sta);
+       ret = iwl_mvm_send_add_bcast_sta(mvm, vif);
        if (ret)
                goto out_unbind;
 
@@ -1425,31 +1636,36 @@ 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, vif);
+       ret = iwl_mvm_update_quotas(mvm, NULL);
        if (ret)
                goto out_quota_failed;
 
        /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */
        if (vif->p2p && mvm->p2p_device_vif)
-               iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false);
+               iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL);
 
        iwl_mvm_ref(mvm, IWL_MVM_REF_AP_IBSS);
 
        iwl_mvm_bt_coex_vif_change(mvm);
 
+       /* we don't support TDLS during DCM */
+       if (iwl_mvm_phy_ctx_count(mvm) > 1)
+               iwl_mvm_teardown_tdls_peers(mvm);
+
        mutex_unlock(&mvm->mutex);
        return 0;
 
 out_quota_failed:
        iwl_mvm_power_update_mac(mvm);
        mvmvif->ap_ibss_active = false;
-       iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta);
+       iwl_mvm_send_rm_bcast_sta(mvm, vif);
 out_unbind:
        iwl_mvm_binding_remove_vif(mvm, vif);
 out_remove:
        iwl_mvm_mac_ctxt_remove(mvm, vif);
 out_unlock:
        mutex_unlock(&mvm->mutex);
+       iwl_mvm_unref(mvm, IWL_MVM_REF_START_AP);
        return ret;
 }
 
@@ -1463,7 +1679,20 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw,
 
        mutex_lock(&mvm->mutex);
 
+       /* Handle AP stop while in CSA */
+       if (rcu_access_pointer(mvm->csa_vif) == vif) {
+               iwl_mvm_remove_time_event(mvm, mvmvif,
+                                         &mvmvif->time_event_data);
+               RCU_INIT_POINTER(mvm->csa_vif, NULL);
+       }
+
+       if (rcu_access_pointer(mvm->csa_tx_blocked_vif) == vif) {
+               RCU_INIT_POINTER(mvm->csa_tx_blocked_vif, NULL);
+               mvm->csa_tx_block_bcn_timeout = 0;
+       }
+
        mvmvif->ap_ibss_active = false;
+       mvm->ap_last_beacon_gp2 = 0;
 
        iwl_mvm_bt_coex_vif_change(mvm);
 
@@ -1471,10 +1700,10 @@ static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw,
 
        /* Need to update the P2P Device MAC (only GO, IBSS is single vif) */
        if (vif->p2p && mvm->p2p_device_vif)
-               iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false);
+               iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif, false, NULL);
 
        iwl_mvm_update_quotas(mvm, NULL);
-       iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta);
+       iwl_mvm_send_rm_bcast_sta(mvm, vif);
        iwl_mvm_binding_remove_vif(mvm, vif);
 
        iwl_mvm_power_update_mac(mvm);
@@ -1497,8 +1726,8 @@ iwl_mvm_bss_info_changed_ap_ibss(struct iwl_mvm *mvm,
                return;
 
        if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT |
-                      BSS_CHANGED_BANDWIDTH) &&
-           iwl_mvm_mac_ctxt_changed(mvm, vif, false))
+                      BSS_CHANGED_BANDWIDTH | BSS_CHANGED_QOS) &&
+           iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL))
                IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr);
 
        /* Need to send a new beacon template to the FW */
@@ -1514,10 +1743,18 @@ static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw,
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 
+       /*
+        * iwl_mvm_bss_info_changed_station() might call
+        * iwl_mvm_protect_session(), which reads directly from
+        * the device (the system time), so make sure it is available.
+        */
+       if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_BSS_CHANGED))
+               return;
+
        mutex_lock(&mvm->mutex);
 
        if (changes & BSS_CHANGED_IDLE && !bss_conf->idle)
-               iwl_mvm_sched_scan_stop(mvm, true);
+               iwl_mvm_scan_offload_stop(mvm, true);
 
        switch (vif->type) {
        case NL80211_IFTYPE_STATION:
@@ -1533,44 +1770,84 @@ static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw,
        }
 
        mutex_unlock(&mvm->mutex);
+       iwl_mvm_unref(mvm, IWL_MVM_REF_BSS_CHANGED);
 }
 
+static int iwl_mvm_cancel_scan_wait_notif(struct iwl_mvm *mvm,
+                                         enum iwl_scan_status scan_type)
+{
+       int ret;
+       bool wait_for_handlers = false;
+
+       mutex_lock(&mvm->mutex);
+
+       if (mvm->scan_status != scan_type) {
+               ret = 0;
+               /* make sure there are no pending notifications */
+               wait_for_handlers = true;
+               goto out;
+       }
+
+       switch (scan_type) {
+       case IWL_MVM_SCAN_SCHED:
+               ret = iwl_mvm_scan_offload_stop(mvm, true);
+               break;
+       case IWL_MVM_SCAN_OS:
+               ret = iwl_mvm_cancel_scan(mvm);
+               break;
+       case IWL_MVM_SCAN_NONE:
+       default:
+               WARN_ON_ONCE(1);
+               ret = -EINVAL;
+               break;
+       }
+       if (ret)
+               goto out;
+
+       wait_for_handlers = true;
+out:
+       mutex_unlock(&mvm->mutex);
+
+       /* make sure we consume the completion notification */
+       if (wait_for_handlers)
+               iwl_mvm_wait_for_async_handlers(mvm);
+
+       return ret;
+}
 static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw,
                               struct ieee80211_vif *vif,
-                              struct cfg80211_scan_request *req)
+                              struct ieee80211_scan_request *hw_req)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       struct cfg80211_scan_request *req = &hw_req->req;
        int ret;
 
-       if (req->n_channels == 0 || req->n_channels > MAX_NUM_SCAN_CHANNELS)
+       if (req->n_channels == 0 ||
+           req->n_channels > mvm->fw->ucode_capa.n_scan_channels)
                return -EINVAL;
 
+       ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_SCHED);
+       if (ret)
+               return ret;
+
        mutex_lock(&mvm->mutex);
 
-       switch (mvm->scan_status) {
-       case IWL_MVM_SCAN_SCHED:
-               ret = iwl_mvm_sched_scan_stop(mvm, true);
-               if (ret) {
-                       ret = -EBUSY;
-                       goto out;
-               }
-               break;
-       case IWL_MVM_SCAN_NONE:
-               break;
-       default:
+       if (mvm->scan_status != IWL_MVM_SCAN_NONE) {
                ret = -EBUSY;
                goto out;
        }
 
        iwl_mvm_ref(mvm, IWL_MVM_REF_SCAN);
 
-       ret = iwl_mvm_scan_request(mvm, vif, req);
+       if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)
+               ret = iwl_mvm_unified_scan_lmac(mvm, vif, hw_req);
+       else
+               ret = iwl_mvm_scan_request(mvm, vif, req);
+
        if (ret)
                iwl_mvm_unref(mvm, IWL_MVM_REF_SCAN);
 out:
        mutex_unlock(&mvm->mutex);
-       /* make sure to flush the Rx handler before the next scan arrives */
-       iwl_mvm_wait_for_async_handlers(mvm);
        return ret;
 }
 
@@ -1718,7 +1995,20 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw,
                        ret = -EINVAL;
                        goto out_unlock;
                }
+
+               if (sta->tdls &&
+                   (vif->p2p ||
+                    iwl_mvm_tdls_sta_count(mvm, NULL) ==
+                                               IWL_MVM_TDLS_STA_COUNT ||
+                    iwl_mvm_phy_ctx_count(mvm) > 1)) {
+                       IWL_DEBUG_MAC80211(mvm, "refusing TDLS sta\n");
+                       ret = -EBUSY;
+                       goto out_unlock;
+               }
+
                ret = iwl_mvm_add_sta(mvm, vif, sta);
+               if (sta->tdls && ret == 0)
+                       iwl_mvm_recalc_tdls_state(mvm, vif, true);
        } else if (old_state == IEEE80211_STA_NONE &&
                   new_state == IEEE80211_STA_AUTH) {
                /*
@@ -1736,6 +2026,11 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw,
                                             true);
        } else if (old_state == IEEE80211_STA_ASSOC &&
                   new_state == IEEE80211_STA_AUTHORIZED) {
+
+               /* we don't support TDLS during DCM */
+               if (iwl_mvm_phy_ctx_count(mvm) > 1)
+                       iwl_mvm_teardown_tdls_peers(mvm);
+
                /* enable beacon filtering */
                WARN_ON(iwl_mvm_enable_beacon_filter(mvm, vif, 0));
                ret = 0;
@@ -1753,6 +2048,8 @@ static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw,
        } else if (old_state == IEEE80211_STA_NONE &&
                   new_state == IEEE80211_STA_NOTEXIST) {
                ret = iwl_mvm_rm_sta(mvm, vif, sta);
+               if (sta->tdls)
+                       iwl_mvm_recalc_tdls_state(mvm, vif, false);
        } else {
                ret = -EIO;
        }
@@ -1799,7 +2096,7 @@ static int iwl_mvm_mac_conf_tx(struct ieee80211_hw *hw,
                int ret;
 
                mutex_lock(&mvm->mutex);
-               ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false);
+               ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
                mutex_unlock(&mvm->mutex);
                return ret;
        }
@@ -1818,70 +2115,74 @@ static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw,
        if (WARN_ON_ONCE(vif->bss_conf.assoc))
                return;
 
+       /*
+        * iwl_mvm_protect_session() reads directly from the device
+        * (the system time), so make sure it is available.
+        */
+       if (iwl_mvm_ref_sync(mvm, IWL_MVM_REF_PREPARE_TX))
+               return;
+
        mutex_lock(&mvm->mutex);
        /* Try really hard to protect the session and hear a beacon */
-       iwl_mvm_protect_session(mvm, vif, duration, min_duration, 500);
+       iwl_mvm_protect_session(mvm, vif, duration, min_duration, 500, false);
        mutex_unlock(&mvm->mutex);
+
+       iwl_mvm_unref(mvm, IWL_MVM_REF_PREPARE_TX);
 }
 
 static int iwl_mvm_mac_sched_scan_start(struct ieee80211_hw *hw,
                                        struct ieee80211_vif *vif,
                                        struct cfg80211_sched_scan_request *req,
-                                       struct ieee80211_sched_scan_ies *ies)
+                                       struct ieee80211_scan_ies *ies)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        int ret;
 
-       mutex_lock(&mvm->mutex);
+       ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_OS);
+       if (ret)
+               return ret;
 
-       if (!iwl_mvm_is_idle(mvm)) {
-               ret = -EBUSY;
-               goto out;
-       }
+       mutex_lock(&mvm->mutex);
 
-       switch (mvm->scan_status) {
-       case IWL_MVM_SCAN_OS:
-               IWL_DEBUG_SCAN(mvm, "Stopping previous scan for sched_scan\n");
-               ret = iwl_mvm_cancel_scan(mvm);
-               if (ret) {
+       /* Newest FW fixes sched scan while connected on another interface */
+       if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN) {
+               if (!vif->bss_conf.idle) {
                        ret = -EBUSY;
                        goto out;
                }
+       } else if (!iwl_mvm_is_idle(mvm)) {
+               ret = -EBUSY;
+               goto out;
+       }
 
-               /*
-                * iwl_mvm_rx_scan_complete() will be called soon but will
-                * not reset the scan status as it won't be IWL_MVM_SCAN_OS
-                * any more since we queue the next scan immediately (below).
-                * We make sure it is called before the next scan starts by
-                * flushing the async-handlers work.
-                */
-               break;
-       case IWL_MVM_SCAN_NONE:
-               break;
-       default:
+       if (mvm->scan_status != IWL_MVM_SCAN_NONE) {
                ret = -EBUSY;
                goto out;
        }
 
        mvm->scan_status = IWL_MVM_SCAN_SCHED;
 
-       ret = iwl_mvm_config_sched_scan(mvm, vif, req, ies);
-       if (ret)
-               goto err;
+       if (!(mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)) {
+               ret = iwl_mvm_config_sched_scan(mvm, vif, req, ies);
+               if (ret)
+                       goto err;
+       }
 
        ret = iwl_mvm_config_sched_scan_profiles(mvm, req);
        if (ret)
                goto err;
 
-       ret = iwl_mvm_sched_scan_start(mvm, req);
+       if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)
+               ret = iwl_mvm_unified_sched_scan_lmac(mvm, vif, req, ies);
+       else
+               ret = iwl_mvm_sched_scan_start(mvm, req);
+
        if (!ret)
                goto out;
 err:
        mvm->scan_status = IWL_MVM_SCAN_NONE;
 out:
        mutex_unlock(&mvm->mutex);
-       /* make sure to flush the Rx handler before the next scan arrives */
-       iwl_mvm_wait_for_async_handlers(mvm);
        return ret;
 }
 
@@ -1892,7 +2193,7 @@ static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw,
        int ret;
 
        mutex_lock(&mvm->mutex);
-       ret = iwl_mvm_sched_scan_stop(mvm, false);
+       ret = iwl_mvm_scan_offload_stop(mvm, false);
        mutex_unlock(&mvm->mutex);
        iwl_mvm_wait_for_async_handlers(mvm);
 
@@ -2001,6 +2302,119 @@ static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw,
 }
 
 
+static bool iwl_mvm_rx_aux_roc(struct iwl_notif_wait_data *notif_wait,
+                              struct iwl_rx_packet *pkt, void *data)
+{
+       struct iwl_mvm *mvm =
+               container_of(notif_wait, struct iwl_mvm, notif_wait);
+       struct iwl_hs20_roc_res *resp;
+       int resp_len = iwl_rx_packet_payload_len(pkt);
+       struct iwl_mvm_time_event_data *te_data = data;
+
+       if (WARN_ON(pkt->hdr.cmd != HOT_SPOT_CMD))
+               return true;
+
+       if (WARN_ON_ONCE(resp_len != sizeof(*resp))) {
+               IWL_ERR(mvm, "Invalid HOT_SPOT_CMD response\n");
+               return true;
+       }
+
+       resp = (void *)pkt->data;
+
+       IWL_DEBUG_TE(mvm,
+                    "Aux ROC: Recieved response from ucode: status=%d uid=%d\n",
+                    resp->status, resp->event_unique_id);
+
+       te_data->uid = le32_to_cpu(resp->event_unique_id);
+       IWL_DEBUG_TE(mvm, "TIME_EVENT_CMD response - UID = 0x%x\n",
+                    te_data->uid);
+
+       spin_lock_bh(&mvm->time_event_lock);
+       list_add_tail(&te_data->list, &mvm->aux_roc_te_list);
+       spin_unlock_bh(&mvm->time_event_lock);
+
+       return true;
+}
+
+#define AUX_ROC_MAX_DELAY_ON_CHANNEL 5000
+static int iwl_mvm_send_aux_roc_cmd(struct iwl_mvm *mvm,
+                                   struct ieee80211_channel *channel,
+                                   struct ieee80211_vif *vif,
+                                   int duration)
+{
+       int res, time_reg = DEVICE_SYSTEM_TIME_REG;
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       struct iwl_mvm_time_event_data *te_data = &mvmvif->hs_time_event_data;
+       static const u8 time_event_response[] = { HOT_SPOT_CMD };
+       struct iwl_notification_wait wait_time_event;
+       struct iwl_hs20_roc_req aux_roc_req = {
+               .action = cpu_to_le32(FW_CTXT_ACTION_ADD),
+               .id_and_color =
+                       cpu_to_le32(FW_CMD_ID_AND_COLOR(MAC_INDEX_AUX, 0)),
+               .sta_id_and_color = cpu_to_le32(mvm->aux_sta.sta_id),
+               /* Set the channel info data */
+               .channel_info.band = (channel->band == IEEE80211_BAND_2GHZ) ?
+                       PHY_BAND_24 : PHY_BAND_5,
+               .channel_info.channel = channel->hw_value,
+               .channel_info.width = PHY_VHT_CHANNEL_MODE20,
+               /* Set the time and duration */
+               .apply_time = cpu_to_le32(iwl_read_prph(mvm->trans, time_reg)),
+               .apply_time_max_delay =
+                       cpu_to_le32(MSEC_TO_TU(AUX_ROC_MAX_DELAY_ON_CHANNEL)),
+               .duration = cpu_to_le32(MSEC_TO_TU(duration)),
+        };
+
+       /* Set the node address */
+       memcpy(aux_roc_req.node_addr, vif->addr, ETH_ALEN);
+
+       te_data->vif = vif;
+       te_data->duration = duration;
+       te_data->id = HOT_SPOT_CMD;
+
+       lockdep_assert_held(&mvm->mutex);
+
+       spin_lock_bh(&mvm->time_event_lock);
+       list_add_tail(&te_data->list, &mvm->time_event_list);
+       spin_unlock_bh(&mvm->time_event_lock);
+
+       /*
+        * Use a notification wait, which really just processes the
+        * command response and doesn't wait for anything, in order
+        * to be able to process the response and get the UID inside
+        * the RX path. Using CMD_WANT_SKB doesn't work because it
+        * stores the buffer and then wakes up this thread, by which
+        * time another notification (that the time event started)
+        * might already be processed unsuccessfully.
+        */
+       iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event,
+                                  time_event_response,
+                                  ARRAY_SIZE(time_event_response),
+                                  iwl_mvm_rx_aux_roc, te_data);
+
+       res = iwl_mvm_send_cmd_pdu(mvm, HOT_SPOT_CMD, 0, sizeof(aux_roc_req),
+                                  &aux_roc_req);
+
+       if (res) {
+               IWL_ERR(mvm, "Couldn't send HOT_SPOT_CMD: %d\n", res);
+               iwl_remove_notification(&mvm->notif_wait, &wait_time_event);
+               goto out_clear_te;
+       }
+
+       /* No need to wait for anything, so just pass 1 (0 isn't valid) */
+       res = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1);
+       /* should never fail */
+       WARN_ON_ONCE(res);
+
+       if (res) {
+ out_clear_te:
+               spin_lock_bh(&mvm->time_event_lock);
+               iwl_mvm_te_clear_data(mvm, te_data);
+               spin_unlock_bh(&mvm->time_event_lock);
+       }
+
+       return res;
+}
+
 static int iwl_mvm_roc(struct ieee80211_hw *hw,
                       struct ieee80211_vif *vif,
                       struct ieee80211_channel *channel,
@@ -2016,8 +2430,17 @@ static int iwl_mvm_roc(struct ieee80211_hw *hw,
        IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value,
                           duration, type);
 
-       if (vif->type != NL80211_IFTYPE_P2P_DEVICE) {
-               IWL_ERR(mvm, "vif isn't a P2P_DEVICE: %d\n", vif->type);
+       switch (vif->type) {
+       case NL80211_IFTYPE_STATION:
+               /* Use aux roc framework (HS20) */
+               ret = iwl_mvm_send_aux_roc_cmd(mvm, channel,
+                                              vif, duration);
+               return ret;
+       case NL80211_IFTYPE_P2P_DEVICE:
+               /* handle below */
+               break;
+       default:
+               IWL_ERR(mvm, "vif isn't P2P_DEVICE: %d\n", vif->type);
                return -EINVAL;
        }
 
@@ -2126,17 +2549,17 @@ static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw)
        return 0;
 }
 
-static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw,
-                              struct ieee80211_chanctx_conf *ctx)
+static int __iwl_mvm_add_chanctx(struct iwl_mvm *mvm,
+                                struct ieee80211_chanctx_conf *ctx)
 {
-       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        u16 *phy_ctxt_id = (u16 *)ctx->drv_priv;
        struct iwl_mvm_phy_ctxt *phy_ctxt;
        int ret;
 
+       lockdep_assert_held(&mvm->mutex);
+
        IWL_DEBUG_MAC80211(mvm, "Add channel context\n");
 
-       mutex_lock(&mvm->mutex);
        phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm);
        if (!phy_ctxt) {
                ret = -ENOSPC;
@@ -2154,19 +2577,40 @@ static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw,
        iwl_mvm_phy_ctxt_ref(mvm, phy_ctxt);
        *phy_ctxt_id = phy_ctxt->id;
 out:
+       return ret;
+}
+
+static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw,
+                              struct ieee80211_chanctx_conf *ctx)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       int ret;
+
+       mutex_lock(&mvm->mutex);
+       ret = __iwl_mvm_add_chanctx(mvm, ctx);
        mutex_unlock(&mvm->mutex);
+
        return ret;
 }
 
+static void __iwl_mvm_remove_chanctx(struct iwl_mvm *mvm,
+                                    struct ieee80211_chanctx_conf *ctx)
+{
+       u16 *phy_ctxt_id = (u16 *)ctx->drv_priv;
+       struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id];
+
+       lockdep_assert_held(&mvm->mutex);
+
+       iwl_mvm_phy_ctxt_unref(mvm, phy_ctxt);
+}
+
 static void iwl_mvm_remove_chanctx(struct ieee80211_hw *hw,
                                   struct ieee80211_chanctx_conf *ctx)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
-       u16 *phy_ctxt_id = (u16 *)ctx->drv_priv;
-       struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id];
 
        mutex_lock(&mvm->mutex);
-       iwl_mvm_phy_ctxt_unref(mvm, phy_ctxt);
+       __iwl_mvm_remove_chanctx(mvm, ctx);
        mutex_unlock(&mvm->mutex);
 }
 
@@ -2195,17 +2639,17 @@ static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw,
        mutex_unlock(&mvm->mutex);
 }
 
-static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw,
-                                     struct ieee80211_vif *vif,
-                                     struct ieee80211_chanctx_conf *ctx)
+static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm,
+                                       struct ieee80211_vif *vif,
+                                       struct ieee80211_chanctx_conf *ctx,
+                                       bool switching_chanctx)
 {
-       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        u16 *phy_ctxt_id = (u16 *)ctx->drv_priv;
        struct iwl_mvm_phy_ctxt *phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id];
        struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
        int ret;
 
-       mutex_lock(&mvm->mutex);
+       lockdep_assert_held(&mvm->mutex);
 
        mvmvif->phy_ctxt = phy_ctxt;
 
@@ -2222,18 +2666,21 @@ static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw,
                 * (in bss_info_changed), similarly for IBSS.
                 */
                ret = 0;
-               goto out_unlock;
+               goto out;
        case NL80211_IFTYPE_STATION:
+               break;
        case NL80211_IFTYPE_MONITOR:
+               /* always disable PS when a monitor interface is active */
+               mvmvif->ps_disabled = true;
                break;
        default:
                ret = -EINVAL;
-               goto out_unlock;
+               goto out;
        }
 
        ret = iwl_mvm_binding_add_vif(mvm, vif);
        if (ret)
-               goto out_unlock;
+               goto out;
 
        /*
         * Power state must be updated before quotas,
@@ -2247,67 +2694,189 @@ static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw,
         */
        if (vif->type == NL80211_IFTYPE_MONITOR) {
                mvmvif->monitor_active = true;
-               ret = iwl_mvm_update_quotas(mvm, vif);
+               ret = iwl_mvm_update_quotas(mvm, NULL);
                if (ret)
                        goto out_remove_binding;
        }
 
        /* Handle binding during CSA */
-       if (vif->type == NL80211_IFTYPE_AP) {
-               iwl_mvm_update_quotas(mvm, vif);
-               iwl_mvm_mac_ctxt_changed(mvm, vif, false);
+       if ((vif->type == NL80211_IFTYPE_AP) ||
+           (switching_chanctx && (vif->type == NL80211_IFTYPE_STATION))) {
+               iwl_mvm_update_quotas(mvm, NULL);
+               iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
        }
 
-       goto out_unlock;
+       if (vif->csa_active && vif->type == NL80211_IFTYPE_STATION) {
+               struct iwl_mvm_sta *mvmsta;
+
+               mvmsta = iwl_mvm_sta_from_staid_protected(mvm,
+                                                         mvmvif->ap_sta_id);
+
+               if (WARN_ON(!mvmsta))
+                       goto out;
+
+               /* TODO: only re-enable after the first beacon */
+               iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false);
+       }
+
+       goto out;
 
- out_remove_binding:
+out_remove_binding:
        iwl_mvm_binding_remove_vif(mvm, vif);
        iwl_mvm_power_update_mac(mvm);
- out_unlock:
-       mutex_unlock(&mvm->mutex);
+out:
        if (ret)
                mvmvif->phy_ctxt = NULL;
        return ret;
 }
-
-static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw,
-                                        struct ieee80211_vif *vif,
-                                        struct ieee80211_chanctx_conf *ctx)
+static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif *vif,
+                                     struct ieee80211_chanctx_conf *ctx)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
-       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       int ret;
 
        mutex_lock(&mvm->mutex);
+       ret = __iwl_mvm_assign_vif_chanctx(mvm, vif, ctx, false);
+       mutex_unlock(&mvm->mutex);
+
+       return ret;
+}
+
+static void __iwl_mvm_unassign_vif_chanctx(struct iwl_mvm *mvm,
+                                          struct ieee80211_vif *vif,
+                                          struct ieee80211_chanctx_conf *ctx,
+                                          bool switching_chanctx)
+{
+       struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
+       struct ieee80211_vif *disabled_vif = NULL;
+       struct iwl_mvm_sta *mvmsta;
+
+       lockdep_assert_held(&mvm->mutex);
 
        iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data);
 
        switch (vif->type) {
        case NL80211_IFTYPE_ADHOC:
-               goto out_unlock;
+               goto out;
        case NL80211_IFTYPE_MONITOR:
                mvmvif->monitor_active = false;
-               iwl_mvm_update_quotas(mvm, NULL);
+               mvmvif->ps_disabled = false;
                break;
        case NL80211_IFTYPE_AP:
                /* This part is triggered only during CSA */
                if (!vif->csa_active || !mvmvif->ap_ibss_active)
-                       goto out_unlock;
+                       goto out;
+
+               /* Set CS bit on all the stations */
+               iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true);
+
+               /* Save blocked iface, the timeout is set on the next beacon */
+               rcu_assign_pointer(mvm->csa_tx_blocked_vif, vif);
 
                mvmvif->ap_ibss_active = false;
-               iwl_mvm_update_quotas(mvm, NULL);
-               /*TODO: bt_coex notification here? */
+               break;
+       case NL80211_IFTYPE_STATION:
+               if (!switching_chanctx)
+                       break;
+
+               disabled_vif = vif;
+
+               mvmsta = iwl_mvm_sta_from_staid_protected(mvm,
+                                                         mvmvif->ap_sta_id);
+
+               if (!WARN_ON(!mvmsta))
+                       iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, true);
+
+               iwl_mvm_mac_ctxt_changed(mvm, vif, true, NULL);
+               break;
        default:
                break;
        }
 
+       iwl_mvm_update_quotas(mvm, disabled_vif);
        iwl_mvm_binding_remove_vif(mvm, vif);
 
-out_unlock:
+out:
        mvmvif->phy_ctxt = NULL;
        iwl_mvm_power_update_mac(mvm);
+}
+
+static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw,
+                                        struct ieee80211_vif *vif,
+                                        struct ieee80211_chanctx_conf *ctx)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+
+       mutex_lock(&mvm->mutex);
+       __iwl_mvm_unassign_vif_chanctx(mvm, vif, ctx, false);
        mutex_unlock(&mvm->mutex);
 }
 
+static int iwl_mvm_switch_vif_chanctx(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif_chanctx_switch *vifs,
+                                     int n_vifs,
+                                     enum ieee80211_chanctx_switch_mode mode)
+{
+       struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       int ret;
+
+       /* we only support SWAP_CONTEXTS and with a single-vif right now */
+       if (mode != CHANCTX_SWMODE_SWAP_CONTEXTS || n_vifs > 1)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&mvm->mutex);
+       __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true);
+       __iwl_mvm_remove_chanctx(mvm, vifs[0].old_ctx);
+
+       ret = __iwl_mvm_add_chanctx(mvm, vifs[0].new_ctx);
+       if (ret) {
+               IWL_ERR(mvm, "failed to add new_ctx during channel switch\n");
+               goto out_reassign;
+       }
+
+       ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].new_ctx,
+                                          true);
+       if (ret) {
+               IWL_ERR(mvm,
+                       "failed to assign new_ctx during channel switch\n");
+               goto out_remove;
+       }
+
+       /* we don't support TDLS during DCM - can be caused by channel switch */
+       if (iwl_mvm_phy_ctx_count(mvm) > 1)
+               iwl_mvm_teardown_tdls_peers(mvm);
+
+       goto out;
+
+out_remove:
+       __iwl_mvm_remove_chanctx(mvm, vifs[0].new_ctx);
+
+out_reassign:
+       ret = __iwl_mvm_add_chanctx(mvm, vifs[0].old_ctx);
+       if (ret) {
+               IWL_ERR(mvm, "failed to add old_ctx back after failure.\n");
+               goto out_restart;
+       }
+
+       ret = __iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx,
+                                          true);
+       if (ret) {
+               IWL_ERR(mvm, "failed to reassign old_ctx after failure.\n");
+               goto out_restart;
+       }
+
+       goto out;
+
+out_restart:
+       /* things keep failing, better restart the hw */
+       iwl_mvm_nic_restart(mvm, false);
+
+out:
+       mutex_unlock(&mvm->mutex);
+       return ret;
+}
+
 static int iwl_mvm_set_tim(struct ieee80211_hw *hw,
                           struct ieee80211_sta *sta,
                           bool set)
@@ -2395,15 +2964,19 @@ static void iwl_mvm_channel_switch_beacon(struct ieee80211_hw *hw,
                                          struct cfg80211_chan_def *chandef)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       struct ieee80211_vif *csa_vif;
 
        mutex_lock(&mvm->mutex);
-       if (WARN(mvm->csa_vif && mvm->csa_vif->csa_active,
+
+       csa_vif = rcu_dereference_protected(mvm->csa_vif,
+                                           lockdep_is_held(&mvm->mutex));
+       if (WARN(csa_vif && csa_vif->csa_active,
                 "Another CSA is already in progress"))
                goto out_unlock;
 
        IWL_DEBUG_MAC80211(mvm, "CSA started to freq %d\n",
                           chandef->center_freq1);
-       mvm->csa_vif = vif;
+       rcu_assign_pointer(mvm->csa_vif, vif);
 
 out_unlock:
        mutex_unlock(&mvm->mutex);
@@ -2460,6 +3033,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = {
        .sta_rc_update = iwl_mvm_sta_rc_update,
        .conf_tx = iwl_mvm_mac_conf_tx,
        .mgd_prepare_tx = iwl_mvm_mac_mgd_prepare_tx,
+       .mgd_protect_tdls_discover = iwl_mvm_mac_mgd_protect_tdls_discover,
        .flush = iwl_mvm_mac_flush,
        .sched_scan_start = iwl_mvm_mac_sched_scan_start,
        .sched_scan_stop = iwl_mvm_mac_sched_scan_stop,
@@ -2472,6 +3046,7 @@ const struct ieee80211_ops iwl_mvm_hw_ops = {
        .change_chanctx = iwl_mvm_change_chanctx,
        .assign_vif_chanctx = iwl_mvm_assign_vif_chanctx,
        .unassign_vif_chanctx = iwl_mvm_unassign_vif_chanctx,
+       .switch_vif_chanctx = iwl_mvm_switch_vif_chanctx,
 
        .start_ap = iwl_mvm_start_ap_ibss,
        .stop_ap = iwl_mvm_stop_ap_ibss,