Merge remote-tracking branch 'iwlwifi-fixes/master' into iwlwifi-next
authorEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Tue, 25 Nov 2014 05:53:57 +0000 (07:53 +0200)
committerEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Tue, 25 Nov 2014 05:53:57 +0000 (07:53 +0200)
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Conflicts:
drivers/net/wireless/iwlwifi/iwl-fw.h

1  2 
drivers/net/wireless/iwlwifi/iwl-fw.h
drivers/net/wireless/iwlwifi/mvm/mac80211.c

@@@ -145,30 -145,26 +145,32 @@@ enum iwl_ucode_tlv_api 
  /**
   * enum iwl_ucode_tlv_capa - ucode capabilities
   * @IWL_UCODE_TLV_CAPA_D0I3_SUPPORT: supports D0i3
 + * @IWL_UCODE_TLV_CAPA_UMAC_SCAN: supports UMAC scan.
 + * @IWL_UCODE_TLV_CAPA_TDLS_SUPPORT: support basic TDLS functionality
   * @IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT: supports insertion of current
   *    tx power value into TPC Report action frame and Link Measurement Report
   *    action frame
 - * @IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT: supports adding DS params
 - *    element in probe requests.
 + * @IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT: supports updating current
 + *    channel in DS parameter set element in probe requests.
   * @IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT: supports adding TPC Report IE in
   *    probe requests.
   * @IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT: supports Quiet Period requests
   * @IWL_UCODE_TLV_CAPA_DQA_SUPPORT: supports dynamic queue allocation (DQA),
   *    which also implies support for the scheduler configuration command
 + * @IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH: supports TDLS channel switching
+  * @IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT: supports Hot Spot Command
   */
  enum iwl_ucode_tlv_capa {
        IWL_UCODE_TLV_CAPA_D0I3_SUPPORT                 = BIT(0),
 +      IWL_UCODE_TLV_CAPA_UMAC_SCAN                    = BIT(2),
 +      IWL_UCODE_TLV_CAPA_TDLS_SUPPORT                 = BIT(6),
        IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT    = BIT(8),
        IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT      = BIT(9),
        IWL_UCODE_TLV_CAPA_WFA_TPC_REP_IE_SUPPORT       = BIT(10),
        IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT         = BIT(11),
        IWL_UCODE_TLV_CAPA_DQA_SUPPORT                  = BIT(12),
 +      IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH          = BIT(13),
+       IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT              = BIT(18),
  };
  
  /* The default calibrate table size if not specified by firmware file */
@@@ -233,6 -229,7 +235,6 @@@ struct fw_desc 
  
  struct fw_img {
        struct fw_desc sec[IWL_UCODE_SECTION_MAX];
 -      bool is_secure;
        bool is_dual_cpus;
  };
  
@@@ -69,7 -69,6 +69,7 @@@
  #include <linux/etherdevice.h>
  #include <linux/ip.h>
  #include <linux/if_arp.h>
 +#include <linux/devcoredump.h>
  #include <net/mac80211.h>
  #include <net/ieee80211_radiotap.h>
  #include <net/tcp.h>
@@@ -254,26 -253,6 +254,26 @@@ static void iwl_mvm_unref_all_except(st
        spin_unlock_bh(&mvm->refs_lock);
  }
  
 +bool iwl_mvm_ref_taken(struct iwl_mvm *mvm)
 +{
 +      int i;
 +      bool taken = false;
 +
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return true;
 +
 +      spin_lock_bh(&mvm->refs_lock);
 +      for (i = 0; i < IWL_MVM_REF_COUNT; i++) {
 +              if (mvm->refs[i]) {
 +                      taken = true;
 +                      break;
 +              }
 +      }
 +      spin_unlock_bh(&mvm->refs_lock);
 +
 +      return taken;
 +}
 +
  int iwl_mvm_ref_sync(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
  {
        iwl_mvm_ref(mvm, ref_type);
@@@ -323,8 -302,7 +323,8 @@@ int iwl_mvm_mac_setup_register(struct i
        hw->offchannel_tx_hw_queue = IWL_MVM_OFFCHANNEL_QUEUE;
        hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FEC |
                                    IEEE80211_RADIOTAP_MCS_HAVE_STBC;
 -      hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC;
 +      hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC |
 +              IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED;
        hw->rate_control_algorithm = "iwl-mvm-rs";
  
        /*
                hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP;
        }
  
 -      if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)
 +      if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN ||
 +          mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN) {
                hw->flags |= IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS;
 +              hw->wiphy->features |=
 +                      NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR |
 +                      NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR;
 +      }
  
        hw->sta_data_size = sizeof(struct iwl_mvm_sta);
        hw->vif_data_size = sizeof(struct iwl_mvm_vif);
                               NL80211_FEATURE_LOW_PRIORITY_SCAN |
                               NL80211_FEATURE_P2P_GO_OPPPS |
                               NL80211_FEATURE_DYNAMIC_SMPS |
 -                             NL80211_FEATURE_STATIC_SMPS;
 +                             NL80211_FEATURE_STATIC_SMPS |
 +                             NL80211_FEATURE_SUPPORTS_WMM_ADMISSION;
  
        if (mvm->fw->ucode_capa.capa[0] &
            IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT)
                mvm->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT |
                                    WIPHY_WOWLAN_DISCONNECT |
                                    WIPHY_WOWLAN_EAP_IDENTITY_REQ |
 -                                  WIPHY_WOWLAN_RFKILL_RELEASE;
 +                                  WIPHY_WOWLAN_RFKILL_RELEASE |
 +                                  WIPHY_WOWLAN_NET_DETECT;
                if (!iwlwifi_mod_params.sw_crypto)
                        mvm->wowlan.flags |= WIPHY_WOWLAN_SUPPORTS_GTK_REKEY |
                                             WIPHY_WOWLAN_GTK_REKEY_FAILURE |
                mvm->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS;
                mvm->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN;
                mvm->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN;
 +              mvm->wowlan.max_nd_match_sets = IWL_SCAN_MAX_PROFILES;
                mvm->wowlan.tcp = &iwl_mvm_wowlan_tcp_support;
                hw->wiphy->wowlan = &mvm->wowlan;
        }
        if (ret)
                return ret;
  
 +      if (mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_TDLS_SUPPORT) {
 +              IWL_DEBUG_TDLS(mvm, "TDLS supported\n");
 +              hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
 +      }
 +
 +      if (mvm->fw->ucode_capa.capa[0] &
 +          IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH) {
 +              IWL_DEBUG_TDLS(mvm, "TDLS channel switch supported\n");
 +              hw->wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
 +      }
 +
        ret = ieee80211_register_hw(mvm->hw);
        if (ret)
                iwl_mvm_leds_exit(mvm);
@@@ -720,51 -679,10 +720,51 @@@ static void iwl_mvm_cleanup_iterator(vo
        memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data));
  }
  
 -#ifdef CONFIG_IWLWIFI_DEBUGFS
 +static ssize_t iwl_mvm_read_coredump(char *buffer, loff_t offset, size_t count,
 +                                   const void *data, size_t datalen)
 +{
 +      const struct iwl_mvm_dump_ptrs *dump_ptrs = data;
 +      ssize_t bytes_read;
 +      ssize_t bytes_read_trans;
 +
 +      if (offset < dump_ptrs->op_mode_len) {
 +              bytes_read = min_t(ssize_t, count,
 +                                 dump_ptrs->op_mode_len - offset);
 +              memcpy(buffer, (u8 *)dump_ptrs->op_mode_ptr + offset,
 +                     bytes_read);
 +              offset += bytes_read;
 +              count -= bytes_read;
 +
 +              if (count == 0)
 +                      return bytes_read;
 +      } else {
 +              bytes_read = 0;
 +      }
 +
 +      if (!dump_ptrs->trans_ptr)
 +              return bytes_read;
 +
 +      offset -= dump_ptrs->op_mode_len;
 +      bytes_read_trans = min_t(ssize_t, count,
 +                               dump_ptrs->trans_ptr->len - offset);
 +      memcpy(buffer + bytes_read,
 +             (u8 *)dump_ptrs->trans_ptr->data + offset,
 +             bytes_read_trans);
 +
 +      return bytes_read + bytes_read_trans;
 +}
 +
 +static void iwl_mvm_free_coredump(const void *data)
 +{
 +      const struct iwl_mvm_dump_ptrs *fw_error_dump = data;
 +
 +      vfree(fw_error_dump->op_mode_ptr);
 +      vfree(fw_error_dump->trans_ptr);
 +      kfree(fw_error_dump);
 +}
 +
  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;
  
        lockdep_assert_held(&mvm->mutex);
  
 -      if (mvm->fw_error_dump)
 -              return;
 -
 -      fw_error_dump = kzalloc(sizeof(*mvm->fw_error_dump), GFP_KERNEL);
 +      fw_error_dump = kzalloc(sizeof(*fw_error_dump), GFP_KERNEL);
        if (!fw_error_dump)
                return;
  
        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);
 +      dev_coredumpm(mvm->trans->dev, THIS_MODULE, fw_error_dump, 0,
 +                    GFP_KERNEL, iwl_mvm_read_coredump, iwl_mvm_free_coredump);
  }
 -#endif
  
  static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm)
  {
 -      iwl_mvm_fw_error_dump(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))
 +              iwl_mvm_fw_error_dump(mvm);
  
        iwl_trans_stop_device(mvm->trans);
  
        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->tfd_drained, 0, sizeof(mvm->tfd_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));
@@@ -942,8 -859,9 +942,8 @@@ static int iwl_mvm_mac_start(struct iee
        return ret;
  }
  
 -static void iwl_mvm_mac_restart_complete(struct ieee80211_hw *hw)
 +static void iwl_mvm_restart_complete(struct iwl_mvm *mvm)
  {
 -      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        int ret;
  
        mutex_lock(&mvm->mutex);
        /* allow transport/FW low power modes */
        iwl_mvm_unref(mvm, IWL_MVM_REF_UCODE_DOWN);
  
 +      /*
 +       * If we have TDLS peers, remove them. We don't know the last seqno/PN
 +       * of packets the FW sent out, so we must reconnect.
 +       */
 +      iwl_mvm_teardown_tdls_peers(mvm);
 +
        mutex_unlock(&mvm->mutex);
  }
  
 +static void iwl_mvm_resume_complete(struct iwl_mvm *mvm)
 +{
 +      bool exit_now;
 +
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return;
 +
 +      mutex_lock(&mvm->d0i3_suspend_mutex);
 +      __clear_bit(D0I3_DEFER_WAKEUP, &mvm->d0i3_suspend_flags);
 +      exit_now = __test_and_clear_bit(D0I3_PENDING_WAKEUP,
 +                                      &mvm->d0i3_suspend_flags);
 +      mutex_unlock(&mvm->d0i3_suspend_mutex);
 +
 +      if (exit_now) {
 +              IWL_DEBUG_RPM(mvm, "Run deferred d0i3 exit\n");
 +              _iwl_mvm_exit_d0i3(mvm);
 +      }
 +}
 +
 +static void
 +iwl_mvm_mac_reconfig_complete(struct ieee80211_hw *hw,
 +                            enum ieee80211_reconfig_type reconfig_type)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      switch (reconfig_type) {
 +      case IEEE80211_RECONFIG_TYPE_RESTART:
 +              iwl_mvm_restart_complete(mvm);
 +              break;
 +      case IEEE80211_RECONFIG_TYPE_SUSPEND:
 +              iwl_mvm_resume_complete(mvm);
 +              break;
 +      }
 +}
 +
  void __iwl_mvm_mac_stop(struct iwl_mvm *mvm)
  {
        lockdep_assert_held(&mvm->mutex);
@@@ -1210,7 -1087,7 +1210,7 @@@ static int iwl_mvm_mac_add_interface(st
  static void iwl_mvm_prepare_mac_removal(struct iwl_mvm *mvm,
                                        struct ieee80211_vif *vif)
  {
 -      u32 tfd_msk = iwl_mvm_mac_get_queues_mask(mvm, vif);
 +      u32 tfd_msk = iwl_mvm_mac_get_queues_mask(vif);
  
        if (tfd_msk) {
                mutex_lock(&mvm->mutex);
@@@ -1506,9 -1383,6 +1506,9 @@@ bool iwl_mvm_bcast_filter_build_cmd(str
                .cmd = cmd,
        };
  
 +      if (IWL_MVM_FW_BCAST_FILTER_PASS_ALL)
 +              return false;
 +
        memset(cmd, 0, sizeof(*cmd));
        cmd->max_bcast_filters = ARRAY_SIZE(cmd->filters);
        cmd->max_macs = ARRAY_SIZE(cmd->macs);
@@@ -1961,11 -1835,9 +1961,11 @@@ static int iwl_mvm_mac_hw_scan(struct i
            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;
 +      if (!(mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
 +              ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_SCHED);
 +              if (ret)
 +                      return ret;
 +      }
  
        mutex_lock(&mvm->mutex);
  
  
        iwl_mvm_ref(mvm, IWL_MVM_REF_SCAN);
  
 -      if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_LMAC_SCAN)
 +      if (mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN)
 +              ret = iwl_mvm_scan_umac(mvm, vif, hw_req);
 +      else 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);
@@@ -2195,15 -2065,6 +2195,15 @@@ static int iwl_mvm_mac_sta_state(struc
   out_unlock:
        mutex_unlock(&mvm->mutex);
  
 +      if (sta->tdls && ret == 0) {
 +              if (old_state == IEEE80211_STA_NOTEXIST &&
 +                  new_state == IEEE80211_STA_NONE)
 +                      ieee80211_reserve_tid(sta, IWL_MVM_TDLS_FW_TID);
 +              else if (old_state == IEEE80211_STA_NONE &&
 +                       new_state == IEEE80211_STA_NOTEXIST)
 +                      ieee80211_unreserve_tid(sta, IWL_MVM_TDLS_FW_TID);
 +      }
 +
        return ret;
  }
  
@@@ -2286,11 -2147,9 +2286,11 @@@ static int iwl_mvm_mac_sched_scan_start
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        int ret;
  
 -      ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_OS);
 -      if (ret)
 -              return ret;
 +      if (!(mvm->fw->ucode_capa.capa[0] & IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
 +              ret = iwl_mvm_cancel_scan_wait_notif(mvm, IWL_MVM_SCAN_OS);
 +              if (ret)
 +                      return ret;
 +      }
  
        mutex_lock(&mvm->mutex);
  
                goto out;
        }
  
 -      mvm->scan_status = IWL_MVM_SCAN_SCHED;
 -
 -      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);
 +      ret = iwl_mvm_scan_offload_start(mvm, vif, req, ies);
        if (ret)
 -              goto err;
 -
 -      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);
 +              mvm->scan_status = IWL_MVM_SCAN_NONE;
  
 -      if (!ret)
 -              goto out;
 -err:
 -      mvm->scan_status = IWL_MVM_SCAN_NONE;
  out:
        mutex_unlock(&mvm->mutex);
        return ret;
@@@ -2331,7 -2207,6 +2331,7 @@@ static int iwl_mvm_mac_sched_scan_stop(
        iwl_mvm_wait_for_async_handlers(mvm);
  
        return ret;
 +
  }
  
  static int iwl_mvm_mac_set_key(struct ieee80211_hw *hw,
                break;
        case WLAN_CIPHER_SUITE_WEP40:
        case WLAN_CIPHER_SUITE_WEP104:
 -              /*
 -               * Support for TX only, at least for now, so accept
 -               * the key and do nothing else. Then mac80211 will
 -               * pass it for TX but we don't have to use it for RX.
 +              /* For non-client mode, only use WEP keys for TX as we probably
 +               * don't have a station yet anyway and would then have to keep
 +               * track of the keys, linking them to each of the clients/peers
 +               * as they appear. For now, don't do that, for performance WEP
 +               * offload doesn't really matter much, but we need it for some
 +               * other offload features in client mode.
                 */
 -              return 0;
 +              if (vif->type != NL80211_IFTYPE_STATION)
 +                      return 0;
 +              break;
        default:
                /* currently FW supports only one optional cipher scheme */
                if (hw->n_cipher_schemes &&
@@@ -2577,9 -2448,15 +2577,15 @@@ static int iwl_mvm_roc(struct ieee80211
  
        switch (vif->type) {
        case NL80211_IFTYPE_STATION:
-               /* Use aux roc framework (HS20) */
-               ret = iwl_mvm_send_aux_roc_cmd(mvm, channel,
-                                              vif, duration);
+               if (mvm->fw->ucode_capa.capa[0] &
+                   IWL_UCODE_TLV_CAPA_HOTSPOT_SUPPORT) {
+                       /* Use aux roc framework (HS20) */
+                       ret = iwl_mvm_send_aux_roc_cmd(mvm, channel,
+                                                      vif, duration);
+                       goto out_unlock;
+               }
+               IWL_ERR(mvm, "hotspot not supported\n");
+               ret = -EINVAL;
                goto out_unlock;
        case NL80211_IFTYPE_P2P_DEVICE:
                /* handle below */
@@@ -2686,7 -2563,7 +2692,7 @@@ static int iwl_mvm_cancel_roc(struct ie
        IWL_DEBUG_MAC80211(mvm, "enter\n");
  
        mutex_lock(&mvm->mutex);
 -      iwl_mvm_stop_p2p_roc(mvm);
 +      iwl_mvm_stop_roc(mvm);
        mutex_unlock(&mvm->mutex);
  
        IWL_DEBUG_MAC80211(mvm, "leave\n");
@@@ -2799,8 -2676,8 +2805,8 @@@ static int __iwl_mvm_assign_vif_chanctx
  
        switch (vif->type) {
        case NL80211_IFTYPE_AP:
 -              /* Unless it's a CSA flow we have nothing to do here */
 -              if (vif->csa_active) {
 +              /* only needed if we're switching chanctx (i.e. during CSA) */
 +              if (switching_chanctx) {
                        mvmvif->ap_ibss_active = true;
                        break;
                }
        }
  
        /* Handle binding during CSA */
 -      if ((vif->type == NL80211_IFTYPE_AP) ||
 -          (switching_chanctx && (vif->type == NL80211_IFTYPE_STATION))) {
 +      if (vif->type == NL80211_IFTYPE_AP) {
                iwl_mvm_update_quotas(mvm, NULL);
                iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
        }
  
 -      if (vif->csa_active && vif->type == NL80211_IFTYPE_STATION) {
 -              struct iwl_mvm_sta *mvmsta;
 +      if (switching_chanctx && vif->type == NL80211_IFTYPE_STATION) {
 +              u32 duration = 2 * vif->bss_conf.beacon_int;
  
 -              mvmsta = iwl_mvm_sta_from_staid_protected(mvm,
 -                                                        mvmvif->ap_sta_id);
 +              /* iwl_mvm_protect_session() reads directly from the
 +               * device (the system time), so make sure it is
 +               * available.
 +               */
 +              ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_PROTECT_CSA);
 +              if (ret)
 +                      goto out_remove_binding;
  
 -              if (WARN_ON(!mvmsta))
 -                      goto out;
 +              /* Protect the session to make sure we hear the first
 +               * beacon on the new channel.
 +               */
 +              iwl_mvm_protect_session(mvm, vif, duration, duration,
 +                                      vif->bss_conf.beacon_int / 2,
 +                                      true);
  
 -              /* TODO: only re-enable after the first beacon */
 -              iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false);
 +              iwl_mvm_unref(mvm, IWL_MVM_REF_PROTECT_CSA);
 +
 +              iwl_mvm_update_quotas(mvm, NULL);
        }
  
        goto out;
@@@ -2903,6 -2771,7 +2909,6 @@@ static void __iwl_mvm_unassign_vif_chan
  {
        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);
  
                break;
        case NL80211_IFTYPE_AP:
                /* This part is triggered only during CSA */
 -              if (!vif->csa_active || !mvmvif->ap_ibss_active)
 +              if (!switching_chanctx || !mvmvif->ap_ibss_active)
                        goto out;
  
 +              mvmvif->csa_countdown = false;
 +
                /* Set CS bit on all the stations */
                iwl_mvm_modify_all_sta_disable_tx(mvm, mvmvif, true);
  
  
                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:
@@@ -2961,12 -2834,18 +2967,12 @@@ static void iwl_mvm_unassign_vif_chanct
        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)
 +static int
 +iwl_mvm_switch_vif_chanctx_swap(struct iwl_mvm *mvm,
 +                              struct ieee80211_vif_chanctx_switch *vifs)
  {
 -      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);
@@@ -2995,51 -2874,15 +3001,51 @@@ 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) {
 +      if (__iwl_mvm_add_chanctx(mvm, vifs[0].old_ctx)) {
                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,
 +      if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx,
 +                                       true)) {
 +              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_switch_vif_chanctx_reassign(struct iwl_mvm *mvm,
 +                                  struct ieee80211_vif_chanctx_switch *vifs)
 +{
 +      int ret;
 +
 +      mutex_lock(&mvm->mutex);
 +      __iwl_mvm_unassign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx, true);
 +
 +      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_reassign;
 +      }
 +
 +      goto out;
 +
 +out_reassign:
 +      if (__iwl_mvm_assign_vif_chanctx(mvm, vifs[0].vif, vifs[0].old_ctx,
 +                                       true)) {
                IWL_ERR(mvm, "failed to reassign old_ctx after failure.\n");
                goto out_restart;
        }
@@@ -3052,34 -2895,6 +3058,34 @@@ out_restart
  
  out:
        mutex_unlock(&mvm->mutex);
 +
 +      return ret;
 +}
 +
 +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 a single-vif right now */
 +      if (n_vifs > 1)
 +              return -EOPNOTSUPP;
 +
 +      switch (mode) {
 +      case CHANCTX_SWMODE_SWAP_CONTEXTS:
 +              ret = iwl_mvm_switch_vif_chanctx_swap(mvm, vifs);
 +              break;
 +      case CHANCTX_SWMODE_REASSIGN_VIF:
 +              ret = iwl_mvm_switch_vif_chanctx_reassign(mvm, vifs);
 +              break;
 +      default:
 +              ret = -EOPNOTSUPP;
 +              break;
 +      }
 +
        return ret;
  }
  
@@@ -3165,134 -2980,27 +3171,134 @@@ static int iwl_mvm_mac_testmode_cmd(str
  }
  #endif
  
 -static void iwl_mvm_channel_switch_beacon(struct ieee80211_hw *hw,
 -                                        struct ieee80211_vif *vif,
 -                                        struct cfg80211_chan_def *chandef)
 +static void iwl_mvm_channel_switch(struct ieee80211_hw *hw,
 +                                 struct ieee80211_vif *vif,
 +                                 struct ieee80211_channel_switch *chsw)
 +{
 +      /* By implementing this operation, we prevent mac80211 from
 +       * starting its own channel switch timer, so that we can call
 +       * ieee80211_chswitch_done() ourselves at the right time
 +       * (which is when the absence time event starts).
 +       */
 +
 +      IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw),
 +                         "dummy channel switch op\n");
 +}
 +
 +static int iwl_mvm_pre_channel_switch(struct ieee80211_hw *hw,
 +                                    struct ieee80211_vif *vif,
 +                                    struct ieee80211_channel_switch *chsw)
  {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        struct ieee80211_vif *csa_vif;
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      u32 apply_time;
 +      int ret;
  
        mutex_lock(&mvm->mutex);
  
 -      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"))
 +      IWL_DEBUG_MAC80211(mvm, "pre CSA to freq %d\n",
 +                         chsw->chandef.center_freq1);
 +
 +      switch (vif->type) {
 +      case NL80211_IFTYPE_AP:
 +              csa_vif =
 +                      rcu_dereference_protected(mvm->csa_vif,
 +                                                lockdep_is_held(&mvm->mutex));
 +              if (WARN_ONCE(csa_vif && csa_vif->csa_active,
 +                            "Another CSA is already in progress")) {
 +                      ret = -EBUSY;
 +                      goto out_unlock;
 +              }
 +
 +              rcu_assign_pointer(mvm->csa_vif, vif);
 +
 +              if (WARN_ONCE(mvmvif->csa_countdown,
 +                            "Previous CSA countdown didn't complete")) {
 +                      ret = -EBUSY;
 +                      goto out_unlock;
 +              }
 +
 +              break;
 +      case NL80211_IFTYPE_STATION:
 +              /* Schedule the time event to a bit before beacon 1,
 +               * to make sure we're in the new channel when the
 +               * GO/AP arrives.
 +               */
 +              apply_time = chsw->device_timestamp +
 +                      ((vif->bss_conf.beacon_int * (chsw->count - 1) -
 +                        IWL_MVM_CHANNEL_SWITCH_TIME_CLIENT) * 1024);
 +
 +              if (chsw->block_tx)
 +                      iwl_mvm_csa_client_absent(mvm, vif);
 +
 +              iwl_mvm_schedule_csa_period(mvm, vif, vif->bss_conf.beacon_int,
 +                                          apply_time);
 +              if (mvmvif->bf_data.bf_enabled) {
 +                      ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0);
 +                      if (ret)
 +                              goto out_unlock;
 +              }
 +
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      mvmvif->ps_disabled = true;
 +
 +      ret = iwl_mvm_power_update_ps(mvm);
 +      if (ret)
                goto out_unlock;
  
 -      IWL_DEBUG_MAC80211(mvm, "CSA started to freq %d\n",
 -                         chandef->center_freq1);
 -      rcu_assign_pointer(mvm->csa_vif, vif);
 +      /* we won't be on this channel any longer */
 +      iwl_mvm_teardown_tdls_peers(mvm);
 +
 +out_unlock:
 +      mutex_unlock(&mvm->mutex);
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_post_channel_switch(struct ieee80211_hw *hw,
 +                                     struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      if (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)) {
 +                      ret = -EIO;
 +                      goto out_unlock;
 +              }
 +
 +              iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, false);
 +
 +              iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +
 +              ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0);
 +              if (ret)
 +                      goto out_unlock;
 +
 +              iwl_mvm_stop_session_protection(mvm, vif);
 +      }
 +
 +      mvmvif->ps_disabled = false;
 +
 +      ret = iwl_mvm_power_update_ps(mvm);
  
  out_unlock:
        mutex_unlock(&mvm->mutex);
 +
 +      return ret;
  }
  
  static void iwl_mvm_mac_flush(struct ieee80211_hw *hw,
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
        struct iwl_mvm_vif *mvmvif;
        struct iwl_mvm_sta *mvmsta;
 +      struct ieee80211_sta *sta;
 +      int i;
 +      u32 msk = 0;
  
        if (!vif || vif->type != NL80211_IFTYPE_STATION)
                return;
  
        mutex_lock(&mvm->mutex);
        mvmvif = iwl_mvm_vif_from_mac80211(vif);
 -      mvmsta = iwl_mvm_sta_from_staid_protected(mvm, mvmvif->ap_sta_id);
  
 -      if (WARN_ON_ONCE(!mvmsta))
 -              goto done;
 +      /* flush the AP-station and all TDLS peers */
 +      for (i = 0; i < IWL_MVM_STATION_COUNT; i++) {
 +              sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i],
 +                                              lockdep_is_held(&mvm->mutex));
 +              if (IS_ERR_OR_NULL(sta))
 +                      continue;
 +
 +              mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +              if (mvmsta->vif != vif)
 +                      continue;
 +
 +              /* make sure only TDLS peers or the AP are flushed */
 +              WARN_ON(i != mvmvif->ap_sta_id && !sta->tdls);
 +
 +              msk |= mvmsta->tfd_queue_msk;
 +      }
  
        if (drop) {
 -              if (iwl_mvm_flush_tx_path(mvm, mvmsta->tfd_queue_msk, true))
 +              if (iwl_mvm_flush_tx_path(mvm, msk, true))
                        IWL_ERR(mvm, "flush request fail\n");
 +              mutex_unlock(&mvm->mutex);
        } else {
 -              iwl_trans_wait_tx_queue_empty(mvm->trans,
 -                                            mvmsta->tfd_queue_msk);
 +              mutex_unlock(&mvm->mutex);
 +
 +              /* this can take a while, and we may need/want other operations
 +               * to succeed while doing this, so do it without the mutex held
 +               */
 +              iwl_trans_wait_tx_queue_empty(mvm->trans, msk);
        }
 -done:
 -      mutex_unlock(&mvm->mutex);
  }
  
  const struct ieee80211_ops iwl_mvm_hw_ops = {
        .tx = iwl_mvm_mac_tx,
        .ampdu_action = iwl_mvm_mac_ampdu_action,
        .start = iwl_mvm_mac_start,
 -      .restart_complete = iwl_mvm_mac_restart_complete,
 +      .reconfig_complete = iwl_mvm_mac_reconfig_complete,
        .stop = iwl_mvm_mac_stop,
        .add_interface = iwl_mvm_mac_add_interface,
        .remove_interface = iwl_mvm_mac_remove_interface,
  
        .set_tim = iwl_mvm_set_tim,
  
 -      .channel_switch_beacon = iwl_mvm_channel_switch_beacon,
 +      .channel_switch = iwl_mvm_channel_switch,
 +      .pre_channel_switch = iwl_mvm_pre_channel_switch,
 +      .post_channel_switch = iwl_mvm_post_channel_switch,
 +
 +      .tdls_channel_switch = iwl_mvm_tdls_channel_switch,
 +      .tdls_cancel_channel_switch = iwl_mvm_tdls_cancel_channel_switch,
 +      .tdls_recv_channel_switch = iwl_mvm_tdls_recv_channel_switch,
  
        CFG80211_TESTMODE_CMD(iwl_mvm_mac_testmode_cmd)