Merge remote-tracking branch 'iwlwifi-fixes/master' into next
authorEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Thu, 26 Nov 2015 14:38:24 +0000 (16:38 +0200)
committerEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Thu, 26 Nov 2015 14:38:24 +0000 (16:38 +0200)
1  2 
drivers/net/wireless/intel/iwlwifi/iwl-7000.c
drivers/net/wireless/intel/iwlwifi/iwl-8000.c
drivers/net/wireless/intel/iwlwifi/mvm/d3.c
drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
drivers/net/wireless/intel/iwlwifi/mvm/sta.c
drivers/net/wireless/intel/iwlwifi/mvm/sta.h
drivers/net/wireless/intel/iwlwifi/pcie/drv.c

index 1a73c7a,0000000..bf88ec3
mode 100644,000000..100644
--- /dev/null
@@@ -1,346 -1,0 +1,346 @@@
- #define IWL7260_UCODE_API_MAX 17
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * 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
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +
 +#include <linux/module.h>
 +#include <linux/stringify.h>
 +#include "iwl-config.h"
 +#include "iwl-agn-hw.h"
 +
 +/* Highest firmware API version supported */
++#define IWL7260_UCODE_API_MAX 19
 +
 +/* Oldest version we won't warn about */
 +#define IWL7260_UCODE_API_OK  13
 +
 +/* Lowest firmware API version supported */
 +#define IWL7260_UCODE_API_MIN 13
 +
 +/* NVM versions */
 +#define IWL7260_NVM_VERSION           0x0a1d
 +#define IWL7260_TX_POWER_VERSION      0xffff /* meaningless */
 +#define IWL3160_NVM_VERSION           0x709
 +#define IWL3160_TX_POWER_VERSION      0xffff /* meaningless */
 +#define IWL3165_NVM_VERSION           0x709
 +#define IWL3165_TX_POWER_VERSION      0xffff /* meaningless */
 +#define IWL7265_NVM_VERSION           0x0a1d
 +#define IWL7265_TX_POWER_VERSION      0xffff /* meaningless */
 +#define IWL7265D_NVM_VERSION          0x0c11
 +#define IWL7265_TX_POWER_VERSION      0xffff /* meaningless */
 +
 +/* DCCM offsets and lengths */
 +#define IWL7000_DCCM_OFFSET           0x800000
 +#define IWL7260_DCCM_LEN              0x14000
 +#define IWL3160_DCCM_LEN              0x10000
 +#define IWL7265_DCCM_LEN              0x17A00
 +
 +#define IWL7260_FW_PRE "iwlwifi-7260-"
 +#define IWL7260_MODULE_FIRMWARE(api) IWL7260_FW_PRE __stringify(api) ".ucode"
 +
 +#define IWL3160_FW_PRE "iwlwifi-3160-"
 +#define IWL3160_MODULE_FIRMWARE(api) IWL3160_FW_PRE __stringify(api) ".ucode"
 +
 +#define IWL7265_FW_PRE "iwlwifi-7265-"
 +#define IWL7265_MODULE_FIRMWARE(api) IWL7265_FW_PRE __stringify(api) ".ucode"
 +
 +#define IWL7265D_FW_PRE "iwlwifi-7265D-"
 +#define IWL7265D_MODULE_FIRMWARE(api) IWL7265D_FW_PRE __stringify(api) ".ucode"
 +
 +#define NVM_HW_SECTION_NUM_FAMILY_7000                0
 +
 +static const struct iwl_base_params iwl7000_base_params = {
 +      .eeprom_size = OTP_LOW_IMAGE_SIZE_FAMILY_7000,
 +      .num_of_queues = 31,
 +      .pll_cfg_val = 0,
 +      .shadow_ram_support = true,
 +      .led_compensation = 57,
 +      .wd_timeout = IWL_LONG_WD_TIMEOUT,
 +      .max_event_log_size = 512,
 +      .shadow_reg_enable = true,
 +      .pcie_l1_allowed = true,
 +      .apmg_wake_up_wa = true,
 +};
 +
 +static const struct iwl_tt_params iwl7000_high_temp_tt_params = {
 +      .ct_kill_entry = 118,
 +      .ct_kill_exit = 96,
 +      .ct_kill_duration = 5,
 +      .dynamic_smps_entry = 114,
 +      .dynamic_smps_exit = 110,
 +      .tx_protection_entry = 114,
 +      .tx_protection_exit = 108,
 +      .tx_backoff = {
 +              {.temperature = 112, .backoff = 300},
 +              {.temperature = 113, .backoff = 800},
 +              {.temperature = 114, .backoff = 1500},
 +              {.temperature = 115, .backoff = 3000},
 +              {.temperature = 116, .backoff = 5000},
 +              {.temperature = 117, .backoff = 10000},
 +      },
 +      .support_ct_kill = true,
 +      .support_dynamic_smps = true,
 +      .support_tx_protection = true,
 +      .support_tx_backoff = true,
 +};
 +
 +static const struct iwl_ht_params iwl7000_ht_params = {
 +      .stbc = true,
 +      .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ),
 +};
 +
 +#define IWL_DEVICE_7000                                               \
 +      .ucode_api_max = IWL7260_UCODE_API_MAX,                 \
 +      .ucode_api_ok = IWL7260_UCODE_API_OK,                   \
 +      .ucode_api_min = IWL7260_UCODE_API_MIN,                 \
 +      .device_family = IWL_DEVICE_FAMILY_7000,                \
 +      .max_inst_size = IWL60_RTC_INST_SIZE,                   \
 +      .max_data_size = IWL60_RTC_DATA_SIZE,                   \
 +      .base_params = &iwl7000_base_params,                    \
 +      .led_mode = IWL_LED_RF_STATE,                           \
 +      .nvm_hw_section_num = NVM_HW_SECTION_NUM_FAMILY_7000,   \
 +      .non_shared_ant = ANT_A,                                \
 +      .max_ht_ampdu_exponent = IEEE80211_HT_MAX_AMPDU_64K,    \
 +      .dccm_offset = IWL7000_DCCM_OFFSET
 +
 +const struct iwl_cfg iwl7260_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 7260",
 +      .fw_name_pre = IWL7260_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL7260_NVM_VERSION,
 +      .nvm_calib_ver = IWL7260_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .lp_xtal_workaround = true,
 +      .dccm_len = IWL7260_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7260_2ac_cfg_high_temp = {
 +      .name = "Intel(R) Dual Band Wireless AC 7260",
 +      .fw_name_pre = IWL7260_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL7260_NVM_VERSION,
 +      .nvm_calib_ver = IWL7260_TX_POWER_VERSION,
 +      .high_temp = true,
 +      .host_interrupt_operation_mode = true,
 +      .lp_xtal_workaround = true,
 +      .dccm_len = IWL7260_DCCM_LEN,
 +      .thermal_params = &iwl7000_high_temp_tt_params,
 +};
 +
 +const struct iwl_cfg iwl7260_2n_cfg = {
 +      .name = "Intel(R) Dual Band Wireless N 7260",
 +      .fw_name_pre = IWL7260_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL7260_NVM_VERSION,
 +      .nvm_calib_ver = IWL7260_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .lp_xtal_workaround = true,
 +      .dccm_len = IWL7260_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7260_n_cfg = {
 +      .name = "Intel(R) Wireless N 7260",
 +      .fw_name_pre = IWL7260_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL7260_NVM_VERSION,
 +      .nvm_calib_ver = IWL7260_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .lp_xtal_workaround = true,
 +      .dccm_len = IWL7260_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl3160_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 3160",
 +      .fw_name_pre = IWL3160_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL3160_NVM_VERSION,
 +      .nvm_calib_ver = IWL3160_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .dccm_len = IWL3160_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl3160_2n_cfg = {
 +      .name = "Intel(R) Dual Band Wireless N 3160",
 +      .fw_name_pre = IWL3160_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL3160_NVM_VERSION,
 +      .nvm_calib_ver = IWL3160_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .dccm_len = IWL3160_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl3160_n_cfg = {
 +      .name = "Intel(R) Wireless N 3160",
 +      .fw_name_pre = IWL3160_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL3160_NVM_VERSION,
 +      .nvm_calib_ver = IWL3160_TX_POWER_VERSION,
 +      .host_interrupt_operation_mode = true,
 +      .dccm_len = IWL3160_DCCM_LEN,
 +};
 +
 +static const struct iwl_pwr_tx_backoff iwl7265_pwr_tx_backoffs[] = {
 +      {.pwr = 1600, .backoff = 0},
 +      {.pwr = 1300, .backoff = 467},
 +      {.pwr = 900,  .backoff = 1900},
 +      {.pwr = 800, .backoff = 2630},
 +      {.pwr = 700, .backoff = 3720},
 +      {.pwr = 600, .backoff = 5550},
 +      {.pwr = 500, .backoff = 9350},
 +      {0},
 +};
 +
 +static const struct iwl_ht_params iwl7265_ht_params = {
 +      .stbc = true,
 +      .ldpc = true,
 +      .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ),
 +};
 +
 +const struct iwl_cfg iwl3165_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 3165",
 +      .fw_name_pre = IWL7265D_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7000_ht_params,
 +      .nvm_ver = IWL3165_NVM_VERSION,
 +      .nvm_calib_ver = IWL3165_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 7265",
 +      .fw_name_pre = IWL7265_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265_2n_cfg = {
 +      .name = "Intel(R) Dual Band Wireless N 7265",
 +      .fw_name_pre = IWL7265_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265_n_cfg = {
 +      .name = "Intel(R) Wireless N 7265",
 +      .fw_name_pre = IWL7265_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265d_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 7265",
 +      .fw_name_pre = IWL7265D_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265D_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265d_2n_cfg = {
 +      .name = "Intel(R) Dual Band Wireless N 7265",
 +      .fw_name_pre = IWL7265D_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265D_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +const struct iwl_cfg iwl7265d_n_cfg = {
 +      .name = "Intel(R) Wireless N 7265",
 +      .fw_name_pre = IWL7265D_FW_PRE,
 +      IWL_DEVICE_7000,
 +      .ht_params = &iwl7265_ht_params,
 +      .nvm_ver = IWL7265D_NVM_VERSION,
 +      .nvm_calib_ver = IWL7265_TX_POWER_VERSION,
 +      .pwr_tx_backoffs = iwl7265_pwr_tx_backoffs,
 +      .dccm_len = IWL7265_DCCM_LEN,
 +};
 +
 +MODULE_FIRMWARE(IWL7260_MODULE_FIRMWARE(IWL7260_UCODE_API_OK));
 +MODULE_FIRMWARE(IWL3160_MODULE_FIRMWARE(IWL7260_UCODE_API_OK));
 +MODULE_FIRMWARE(IWL7265_MODULE_FIRMWARE(IWL7260_UCODE_API_OK));
 +MODULE_FIRMWARE(IWL7265D_MODULE_FIRMWARE(IWL7260_UCODE_API_OK));
index 0116e5a,0000000..9bcc0bf
mode 100644,000000..100644
--- /dev/null
@@@ -1,229 -1,0 +1,229 @@@
- #define IWL8000_UCODE_API_MAX 17
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * GPL LICENSE SUMMARY
 + *
 + * Copyright(c) 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2014 - 2015 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * BSD LICENSE
 + *
 + * Copyright(c) 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2014 - 2015 Intel Mobile Communications GmbH
 + * All rights reserved.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +
 +#include <linux/module.h>
 +#include <linux/stringify.h>
 +#include "iwl-config.h"
 +#include "iwl-agn-hw.h"
 +
 +/* Highest firmware API version supported */
++#define IWL8000_UCODE_API_MAX 19
 +
 +/* Oldest version we won't warn about */
 +#define IWL8000_UCODE_API_OK  13
 +
 +/* Lowest firmware API version supported */
 +#define IWL8000_UCODE_API_MIN 13
 +
 +/* NVM versions */
 +#define IWL8000_NVM_VERSION           0x0a1d
 +#define IWL8000_TX_POWER_VERSION      0xffff /* meaningless */
 +
 +/* Memory offsets and lengths */
 +#define IWL8260_DCCM_OFFSET           0x800000
 +#define IWL8260_DCCM_LEN              0x18000
 +#define IWL8260_DCCM2_OFFSET          0x880000
 +#define IWL8260_DCCM2_LEN             0x8000
 +#define IWL8260_SMEM_OFFSET           0x400000
 +#define IWL8260_SMEM_LEN              0x68000
 +
 +#define IWL8000_FW_PRE "iwlwifi-8000"
 +#define IWL8000_MODULE_FIRMWARE(api) \
 +      IWL8000_FW_PRE "-" __stringify(api) ".ucode"
 +
 +#define NVM_HW_SECTION_NUM_FAMILY_8000                10
 +#define DEFAULT_NVM_FILE_FAMILY_8000B         "nvmData-8000B"
 +#define DEFAULT_NVM_FILE_FAMILY_8000C         "nvmData-8000C"
 +
 +/* Max SDIO RX/TX aggregation sizes of the ADDBA request/response */
 +#define MAX_RX_AGG_SIZE_8260_SDIO     21
 +#define MAX_TX_AGG_SIZE_8260_SDIO     40
 +
 +/* Max A-MPDU exponent for HT and VHT */
 +#define MAX_HT_AMPDU_EXPONENT_8260_SDIO       IEEE80211_HT_MAX_AMPDU_32K
 +#define MAX_VHT_AMPDU_EXPONENT_8260_SDIO      IEEE80211_VHT_MAX_AMPDU_32K
 +
 +static const struct iwl_base_params iwl8000_base_params = {
 +      .eeprom_size = OTP_LOW_IMAGE_SIZE_FAMILY_8000,
 +      .num_of_queues = 31,
 +      .pll_cfg_val = 0,
 +      .shadow_ram_support = true,
 +      .led_compensation = 57,
 +      .wd_timeout = IWL_LONG_WD_TIMEOUT,
 +      .max_event_log_size = 512,
 +      .shadow_reg_enable = true,
 +      .pcie_l1_allowed = true,
 +};
 +
 +static const struct iwl_ht_params iwl8000_ht_params = {
 +      .stbc = true,
 +      .ldpc = true,
 +      .ht40_bands = BIT(IEEE80211_BAND_2GHZ) | BIT(IEEE80211_BAND_5GHZ),
 +};
 +
 +static const struct iwl_tt_params iwl8000_tt_params = {
 +      .ct_kill_entry = 115,
 +      .ct_kill_exit = 93,
 +      .ct_kill_duration = 5,
 +      .dynamic_smps_entry = 111,
 +      .dynamic_smps_exit = 107,
 +      .tx_protection_entry = 112,
 +      .tx_protection_exit = 105,
 +      .tx_backoff = {
 +              {.temperature = 110, .backoff = 200},
 +              {.temperature = 111, .backoff = 600},
 +              {.temperature = 112, .backoff = 1200},
 +              {.temperature = 113, .backoff = 2000},
 +              {.temperature = 114, .backoff = 4000},
 +      },
 +      .support_ct_kill = true,
 +      .support_dynamic_smps = true,
 +      .support_tx_protection = true,
 +      .support_tx_backoff = true,
 +};
 +
 +#define IWL_DEVICE_8000                                                       \
 +      .ucode_api_max = IWL8000_UCODE_API_MAX,                         \
 +      .ucode_api_ok = IWL8000_UCODE_API_OK,                           \
 +      .ucode_api_min = IWL8000_UCODE_API_MIN,                         \
 +      .device_family = IWL_DEVICE_FAMILY_8000,                        \
 +      .max_inst_size = IWL60_RTC_INST_SIZE,                           \
 +      .max_data_size = IWL60_RTC_DATA_SIZE,                           \
 +      .base_params = &iwl8000_base_params,                            \
 +      .led_mode = IWL_LED_RF_STATE,                                   \
 +      .nvm_hw_section_num = NVM_HW_SECTION_NUM_FAMILY_8000,           \
 +      .d0i3 = true,                                                   \
 +      .features = NETIF_F_RXCSUM,                                     \
 +      .non_shared_ant = ANT_A,                                        \
 +      .dccm_offset = IWL8260_DCCM_OFFSET,                             \
 +      .dccm_len = IWL8260_DCCM_LEN,                                   \
 +      .dccm2_offset = IWL8260_DCCM2_OFFSET,                           \
 +      .dccm2_len = IWL8260_DCCM2_LEN,                                 \
 +      .smem_offset = IWL8260_SMEM_OFFSET,                             \
 +      .smem_len = IWL8260_SMEM_LEN,                                   \
 +      .default_nvm_file_B_step = DEFAULT_NVM_FILE_FAMILY_8000B,       \
 +      .default_nvm_file_C_step = DEFAULT_NVM_FILE_FAMILY_8000C,       \
 +      .thermal_params = &iwl8000_tt_params,                           \
 +      .apmg_not_supported = true
 +
 +const struct iwl_cfg iwl8260_2n_cfg = {
 +      .name = "Intel(R) Dual Band Wireless N 8260",
 +      .fw_name_pre = IWL8000_FW_PRE,
 +      IWL_DEVICE_8000,
 +      .ht_params = &iwl8000_ht_params,
 +      .nvm_ver = IWL8000_NVM_VERSION,
 +      .nvm_calib_ver = IWL8000_TX_POWER_VERSION,
 +};
 +
 +const struct iwl_cfg iwl8260_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 8260",
 +      .fw_name_pre = IWL8000_FW_PRE,
 +      IWL_DEVICE_8000,
 +      .ht_params = &iwl8000_ht_params,
 +      .nvm_ver = IWL8000_NVM_VERSION,
 +      .nvm_calib_ver = IWL8000_TX_POWER_VERSION,
 +      .max_ht_ampdu_exponent = IEEE80211_HT_MAX_AMPDU_64K,
 +};
 +
 +const struct iwl_cfg iwl4165_2ac_cfg = {
 +      .name = "Intel(R) Dual Band Wireless AC 4165",
 +      .fw_name_pre = IWL8000_FW_PRE,
 +      IWL_DEVICE_8000,
 +      .ht_params = &iwl8000_ht_params,
 +      .nvm_ver = IWL8000_NVM_VERSION,
 +      .nvm_calib_ver = IWL8000_TX_POWER_VERSION,
 +      .max_ht_ampdu_exponent = IEEE80211_HT_MAX_AMPDU_64K,
 +};
 +
 +const struct iwl_cfg iwl8260_2ac_sdio_cfg = {
 +      .name = "Intel(R) Dual Band Wireless-AC 8260",
 +      .fw_name_pre = IWL8000_FW_PRE,
 +      IWL_DEVICE_8000,
 +      .ht_params = &iwl8000_ht_params,
 +      .nvm_ver = IWL8000_NVM_VERSION,
 +      .nvm_calib_ver = IWL8000_TX_POWER_VERSION,
 +      .max_rx_agg_size = MAX_RX_AGG_SIZE_8260_SDIO,
 +      .max_tx_agg_size = MAX_TX_AGG_SIZE_8260_SDIO,
 +      .disable_dummy_notification = true,
 +      .max_ht_ampdu_exponent  = MAX_HT_AMPDU_EXPONENT_8260_SDIO,
 +      .max_vht_ampdu_exponent = MAX_VHT_AMPDU_EXPONENT_8260_SDIO,
 +};
 +
 +const struct iwl_cfg iwl4165_2ac_sdio_cfg = {
 +      .name = "Intel(R) Dual Band Wireless-AC 4165",
 +      .fw_name_pre = IWL8000_FW_PRE,
 +      IWL_DEVICE_8000,
 +      .ht_params = &iwl8000_ht_params,
 +      .nvm_ver = IWL8000_NVM_VERSION,
 +      .nvm_calib_ver = IWL8000_TX_POWER_VERSION,
 +      .max_rx_agg_size = MAX_RX_AGG_SIZE_8260_SDIO,
 +      .max_tx_agg_size = MAX_TX_AGG_SIZE_8260_SDIO,
 +      .bt_shared_single_ant = true,
 +      .disable_dummy_notification = true,
 +      .max_ht_ampdu_exponent  = MAX_HT_AMPDU_EXPONENT_8260_SDIO,
 +      .max_vht_ampdu_exponent = MAX_VHT_AMPDU_EXPONENT_8260_SDIO,
 +};
 +
 +MODULE_FIRMWARE(IWL8000_MODULE_FIRMWARE(IWL8000_UCODE_API_OK));
index 85ae902,0000000..29ae58e
mode 100644,000000..100644
--- /dev/null
@@@ -1,2104 -1,0 +1,2100 @@@
-               key->hw_key_idx = 0;
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * GPL LICENSE SUMMARY
 + *
 + * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * BSD LICENSE
 + *
 + * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
 + * All rights reserved.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +
 +#include <linux/etherdevice.h>
 +#include <linux/ip.h>
 +#include <linux/fs.h>
 +#include <net/cfg80211.h>
 +#include <net/ipv6.h>
 +#include <net/tcp.h>
 +#include <net/addrconf.h>
 +#include "iwl-modparams.h"
 +#include "fw-api.h"
 +#include "mvm.h"
 +
 +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw,
 +                          struct ieee80211_vif *vif,
 +                          struct cfg80211_gtk_rekey_data *data)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      if (iwlwifi_mod_params.sw_crypto)
 +              return;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      memcpy(mvmvif->rekey_data.kek, data->kek, NL80211_KEK_LEN);
 +      memcpy(mvmvif->rekey_data.kck, data->kck, NL80211_KCK_LEN);
 +      mvmvif->rekey_data.replay_ctr =
 +              cpu_to_le64(be64_to_cpup((__be64 *)&data->replay_ctr));
 +      mvmvif->rekey_data.valid = true;
 +
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +#if IS_ENABLED(CONFIG_IPV6)
 +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw,
 +                            struct ieee80211_vif *vif,
 +                            struct inet6_dev *idev)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct inet6_ifaddr *ifa;
 +      int idx = 0;
 +
 +      read_lock_bh(&idev->lock);
 +      list_for_each_entry(ifa, &idev->addr_list, if_list) {
 +              mvmvif->target_ipv6_addrs[idx] = ifa->addr;
 +              idx++;
 +              if (idx >= IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX)
 +                      break;
 +      }
 +      read_unlock_bh(&idev->lock);
 +
 +      mvmvif->num_target_ipv6_addrs = idx;
 +}
 +#endif
 +
 +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw,
 +                                   struct ieee80211_vif *vif, int idx)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      mvmvif->tx_key_idx = idx;
 +}
 +
 +static void iwl_mvm_convert_p1k(u16 *p1k, __le16 *out)
 +{
 +      int i;
 +
 +      for (i = 0; i < IWL_P1K_SIZE; i++)
 +              out[i] = cpu_to_le16(p1k[i]);
 +}
 +
 +struct wowlan_key_data {
 +      struct iwl_wowlan_rsc_tsc_params_cmd *rsc_tsc;
 +      struct iwl_wowlan_tkip_params_cmd *tkip;
 +      bool error, use_rsc_tsc, use_tkip;
 +      int wep_key_idx;
 +};
 +
 +static void iwl_mvm_wowlan_program_keys(struct ieee80211_hw *hw,
 +                                      struct ieee80211_vif *vif,
 +                                      struct ieee80211_sta *sta,
 +                                      struct ieee80211_key_conf *key,
 +                                      void *_data)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct wowlan_key_data *data = _data;
 +      struct aes_sc *aes_sc, *aes_tx_sc = NULL;
 +      struct tkip_sc *tkip_sc, *tkip_tx_sc = NULL;
 +      struct iwl_p1k_cache *rx_p1ks;
 +      u8 *rx_mic_key;
 +      struct ieee80211_key_seq seq;
 +      u32 cur_rx_iv32 = 0;
 +      u16 p1k[IWL_P1K_SIZE];
 +      int ret, i;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      switch (key->cipher) {
 +      case WLAN_CIPHER_SUITE_WEP40:
 +      case WLAN_CIPHER_SUITE_WEP104: { /* hack it for now */
 +              struct {
 +                      struct iwl_mvm_wep_key_cmd wep_key_cmd;
 +                      struct iwl_mvm_wep_key wep_key;
 +              } __packed wkc = {
 +                      .wep_key_cmd.mac_id_n_color =
 +                              cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id,
 +                                                              mvmvif->color)),
 +                      .wep_key_cmd.num_keys = 1,
 +                      /* firmware sets STA_KEY_FLG_WEP_13BYTES */
 +                      .wep_key_cmd.decryption_type = STA_KEY_FLG_WEP,
 +                      .wep_key.key_index = key->keyidx,
 +                      .wep_key.key_size = key->keylen,
 +              };
 +
 +              /*
 +               * This will fail -- the key functions don't set support
 +               * pairwise WEP keys. However, that's better than silently
 +               * failing WoWLAN. Or maybe not?
 +               */
 +              if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE)
 +                      break;
 +
 +              memcpy(&wkc.wep_key.key[3], key->key, key->keylen);
 +              if (key->keyidx == mvmvif->tx_key_idx) {
 +                      /* TX key must be at offset 0 */
 +                      wkc.wep_key.key_offset = 0;
 +              } else {
 +                      /* others start at 1 */
 +                      data->wep_key_idx++;
 +                      wkc.wep_key.key_offset = data->wep_key_idx;
 +              }
 +
 +              ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, 0, sizeof(wkc), &wkc);
 +              data->error = ret != 0;
 +
 +              mvm->ptk_ivlen = key->iv_len;
 +              mvm->ptk_icvlen = key->icv_len;
 +              mvm->gtk_ivlen = key->iv_len;
 +              mvm->gtk_icvlen = key->icv_len;
 +
 +              /* don't upload key again */
 +              goto out_unlock;
 +      }
 +      default:
 +              data->error = true;
 +              goto out_unlock;
 +      case WLAN_CIPHER_SUITE_AES_CMAC:
 +              /*
 +               * Ignore CMAC keys -- the WoWLAN firmware doesn't support them
 +               * but we also shouldn't abort suspend due to that. It does have
 +               * support for the IGTK key renewal, but doesn't really use the
 +               * IGTK for anything. This means we could spuriously wake up or
 +               * be deauthenticated, but that was considered acceptable.
 +               */
 +              goto out_unlock;
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              if (sta) {
 +                      tkip_sc = data->rsc_tsc->all_tsc_rsc.tkip.unicast_rsc;
 +                      tkip_tx_sc = &data->rsc_tsc->all_tsc_rsc.tkip.tsc;
 +
 +                      rx_p1ks = data->tkip->rx_uni;
 +
 +                      ieee80211_get_key_tx_seq(key, &seq);
 +                      tkip_tx_sc->iv16 = cpu_to_le16(seq.tkip.iv16);
 +                      tkip_tx_sc->iv32 = cpu_to_le32(seq.tkip.iv32);
 +
 +                      ieee80211_get_tkip_p1k_iv(key, seq.tkip.iv32, p1k);
 +                      iwl_mvm_convert_p1k(p1k, data->tkip->tx.p1k);
 +
 +                      memcpy(data->tkip->mic_keys.tx,
 +                             &key->key[NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY],
 +                             IWL_MIC_KEY_SIZE);
 +
 +                      rx_mic_key = data->tkip->mic_keys.rx_unicast;
 +              } else {
 +                      tkip_sc =
 +                              data->rsc_tsc->all_tsc_rsc.tkip.multicast_rsc;
 +                      rx_p1ks = data->tkip->rx_multi;
 +                      rx_mic_key = data->tkip->mic_keys.rx_mcast;
 +              }
 +
 +              /*
 +               * For non-QoS this relies on the fact that both the uCode and
 +               * mac80211 use TID 0 (as they need to to avoid replay attacks)
 +               * for checking the IV in the frames.
 +               */
 +              for (i = 0; i < IWL_NUM_RSC; i++) {
 +                      ieee80211_get_key_rx_seq(key, i, &seq);
 +                      tkip_sc[i].iv16 = cpu_to_le16(seq.tkip.iv16);
 +                      tkip_sc[i].iv32 = cpu_to_le32(seq.tkip.iv32);
 +                      /* wrapping isn't allowed, AP must rekey */
 +                      if (seq.tkip.iv32 > cur_rx_iv32)
 +                              cur_rx_iv32 = seq.tkip.iv32;
 +              }
 +
 +              ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid,
 +                                        cur_rx_iv32, p1k);
 +              iwl_mvm_convert_p1k(p1k, rx_p1ks[0].p1k);
 +              ieee80211_get_tkip_rx_p1k(key, vif->bss_conf.bssid,
 +                                        cur_rx_iv32 + 1, p1k);
 +              iwl_mvm_convert_p1k(p1k, rx_p1ks[1].p1k);
 +
 +              memcpy(rx_mic_key,
 +                     &key->key[NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY],
 +                     IWL_MIC_KEY_SIZE);
 +
 +              data->use_tkip = true;
 +              data->use_rsc_tsc = true;
 +              break;
 +      case WLAN_CIPHER_SUITE_CCMP:
 +              if (sta) {
 +                      u64 pn64;
 +
 +                      aes_sc = data->rsc_tsc->all_tsc_rsc.aes.unicast_rsc;
 +                      aes_tx_sc = &data->rsc_tsc->all_tsc_rsc.aes.tsc;
 +
 +                      pn64 = atomic64_read(&key->tx_pn);
 +                      aes_tx_sc->pn = cpu_to_le64(pn64);
 +              } else {
 +                      aes_sc = data->rsc_tsc->all_tsc_rsc.aes.multicast_rsc;
 +              }
 +
 +              /*
 +               * For non-QoS this relies on the fact that both the uCode and
 +               * mac80211 use TID 0 for checking the IV in the frames.
 +               */
 +              for (i = 0; i < IWL_NUM_RSC; i++) {
 +                      u8 *pn = seq.ccmp.pn;
 +
 +                      ieee80211_get_key_rx_seq(key, i, &seq);
 +                      aes_sc[i].pn = cpu_to_le64((u64)pn[5] |
 +                                                 ((u64)pn[4] << 8) |
 +                                                 ((u64)pn[3] << 16) |
 +                                                 ((u64)pn[2] << 24) |
 +                                                 ((u64)pn[1] << 32) |
 +                                                 ((u64)pn[0] << 40));
 +              }
 +              data->use_rsc_tsc = true;
 +              break;
 +      }
 +
 +      /*
 +       * The D3 firmware hardcodes the key offset 0 as the key it uses
 +       * to transmit packets to the AP, i.e. the PTK.
 +       */
 +      if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
-               key->hw_key_idx = 1;
 +              mvm->ptk_ivlen = key->iv_len;
 +              mvm->ptk_icvlen = key->icv_len;
++              ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 0);
 +      } else {
 +              /*
 +               * firmware only supports TSC/RSC for a single key,
 +               * so if there are multiple keep overwriting them
 +               * with new ones -- this relies on mac80211 doing
 +               * list_add_tail().
 +               */
-       ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, true);
 +              mvm->gtk_ivlen = key->iv_len;
 +              mvm->gtk_icvlen = key->icv_len;
++              ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, 1);
 +      }
 +
-       /* We reprogram keys and shouldn't allocate new key indices */
-       memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table));
 +      data->error = ret != 0;
 +out_unlock:
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static int iwl_mvm_send_patterns(struct iwl_mvm *mvm,
 +                               struct cfg80211_wowlan *wowlan)
 +{
 +      struct iwl_wowlan_patterns_cmd *pattern_cmd;
 +      struct iwl_host_cmd cmd = {
 +              .id = WOWLAN_PATTERNS,
 +              .dataflags[0] = IWL_HCMD_DFL_NOCOPY,
 +      };
 +      int i, err;
 +
 +      if (!wowlan->n_patterns)
 +              return 0;
 +
 +      cmd.len[0] = sizeof(*pattern_cmd) +
 +              wowlan->n_patterns * sizeof(struct iwl_wowlan_pattern);
 +
 +      pattern_cmd = kmalloc(cmd.len[0], GFP_KERNEL);
 +      if (!pattern_cmd)
 +              return -ENOMEM;
 +
 +      pattern_cmd->n_patterns = cpu_to_le32(wowlan->n_patterns);
 +
 +      for (i = 0; i < wowlan->n_patterns; i++) {
 +              int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8);
 +
 +              memcpy(&pattern_cmd->patterns[i].mask,
 +                     wowlan->patterns[i].mask, mask_len);
 +              memcpy(&pattern_cmd->patterns[i].pattern,
 +                     wowlan->patterns[i].pattern,
 +                     wowlan->patterns[i].pattern_len);
 +              pattern_cmd->patterns[i].mask_size = mask_len;
 +              pattern_cmd->patterns[i].pattern_size =
 +                      wowlan->patterns[i].pattern_len;
 +      }
 +
 +      cmd.data[0] = pattern_cmd;
 +      err = iwl_mvm_send_cmd(mvm, &cmd);
 +      kfree(pattern_cmd);
 +      return err;
 +}
 +
 +enum iwl_mvm_tcp_packet_type {
 +      MVM_TCP_TX_SYN,
 +      MVM_TCP_RX_SYNACK,
 +      MVM_TCP_TX_DATA,
 +      MVM_TCP_RX_ACK,
 +      MVM_TCP_RX_WAKE,
 +      MVM_TCP_TX_FIN,
 +};
 +
 +static __le16 pseudo_hdr_check(int len, __be32 saddr, __be32 daddr)
 +{
 +      __sum16 check = tcp_v4_check(len, saddr, daddr, 0);
 +      return cpu_to_le16(be16_to_cpu((__force __be16)check));
 +}
 +
 +static void iwl_mvm_build_tcp_packet(struct ieee80211_vif *vif,
 +                                   struct cfg80211_wowlan_tcp *tcp,
 +                                   void *_pkt, u8 *mask,
 +                                   __le16 *pseudo_hdr_csum,
 +                                   enum iwl_mvm_tcp_packet_type ptype)
 +{
 +      struct {
 +              struct ethhdr eth;
 +              struct iphdr ip;
 +              struct tcphdr tcp;
 +              u8 data[];
 +      } __packed *pkt = _pkt;
 +      u16 ip_tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
 +      int i;
 +
 +      pkt->eth.h_proto = cpu_to_be16(ETH_P_IP),
 +      pkt->ip.version = 4;
 +      pkt->ip.ihl = 5;
 +      pkt->ip.protocol = IPPROTO_TCP;
 +
 +      switch (ptype) {
 +      case MVM_TCP_TX_SYN:
 +      case MVM_TCP_TX_DATA:
 +      case MVM_TCP_TX_FIN:
 +              memcpy(pkt->eth.h_dest, tcp->dst_mac, ETH_ALEN);
 +              memcpy(pkt->eth.h_source, vif->addr, ETH_ALEN);
 +              pkt->ip.ttl = 128;
 +              pkt->ip.saddr = tcp->src;
 +              pkt->ip.daddr = tcp->dst;
 +              pkt->tcp.source = cpu_to_be16(tcp->src_port);
 +              pkt->tcp.dest = cpu_to_be16(tcp->dst_port);
 +              /* overwritten for TX SYN later */
 +              pkt->tcp.doff = sizeof(struct tcphdr) / 4;
 +              pkt->tcp.window = cpu_to_be16(65000);
 +              break;
 +      case MVM_TCP_RX_SYNACK:
 +      case MVM_TCP_RX_ACK:
 +      case MVM_TCP_RX_WAKE:
 +              memcpy(pkt->eth.h_dest, vif->addr, ETH_ALEN);
 +              memcpy(pkt->eth.h_source, tcp->dst_mac, ETH_ALEN);
 +              pkt->ip.saddr = tcp->dst;
 +              pkt->ip.daddr = tcp->src;
 +              pkt->tcp.source = cpu_to_be16(tcp->dst_port);
 +              pkt->tcp.dest = cpu_to_be16(tcp->src_port);
 +              break;
 +      default:
 +              WARN_ON(1);
 +              return;
 +      }
 +
 +      switch (ptype) {
 +      case MVM_TCP_TX_SYN:
 +              /* firmware assumes 8 option bytes - 8 NOPs for now */
 +              memset(pkt->data, 0x01, 8);
 +              ip_tot_len += 8;
 +              pkt->tcp.doff = (sizeof(struct tcphdr) + 8) / 4;
 +              pkt->tcp.syn = 1;
 +              break;
 +      case MVM_TCP_TX_DATA:
 +              ip_tot_len += tcp->payload_len;
 +              memcpy(pkt->data, tcp->payload, tcp->payload_len);
 +              pkt->tcp.psh = 1;
 +              pkt->tcp.ack = 1;
 +              break;
 +      case MVM_TCP_TX_FIN:
 +              pkt->tcp.fin = 1;
 +              pkt->tcp.ack = 1;
 +              break;
 +      case MVM_TCP_RX_SYNACK:
 +              pkt->tcp.syn = 1;
 +              pkt->tcp.ack = 1;
 +              break;
 +      case MVM_TCP_RX_ACK:
 +              pkt->tcp.ack = 1;
 +              break;
 +      case MVM_TCP_RX_WAKE:
 +              ip_tot_len += tcp->wake_len;
 +              pkt->tcp.psh = 1;
 +              pkt->tcp.ack = 1;
 +              memcpy(pkt->data, tcp->wake_data, tcp->wake_len);
 +              break;
 +      }
 +
 +      switch (ptype) {
 +      case MVM_TCP_TX_SYN:
 +      case MVM_TCP_TX_DATA:
 +      case MVM_TCP_TX_FIN:
 +              pkt->ip.tot_len = cpu_to_be16(ip_tot_len);
 +              pkt->ip.check = ip_fast_csum(&pkt->ip, pkt->ip.ihl);
 +              break;
 +      case MVM_TCP_RX_WAKE:
 +              for (i = 0; i < DIV_ROUND_UP(tcp->wake_len, 8); i++) {
 +                      u8 tmp = tcp->wake_mask[i];
 +                      mask[i + 6] |= tmp << 6;
 +                      if (i + 1 < DIV_ROUND_UP(tcp->wake_len, 8))
 +                              mask[i + 7] = tmp >> 2;
 +              }
 +              /* fall through for ethernet/IP/TCP headers mask */
 +      case MVM_TCP_RX_SYNACK:
 +      case MVM_TCP_RX_ACK:
 +              mask[0] = 0xff; /* match ethernet */
 +              /*
 +               * match ethernet, ip.version, ip.ihl
 +               * the ip.ihl half byte is really masked out by firmware
 +               */
 +              mask[1] = 0x7f;
 +              mask[2] = 0x80; /* match ip.protocol */
 +              mask[3] = 0xfc; /* match ip.saddr, ip.daddr */
 +              mask[4] = 0x3f; /* match ip.daddr, tcp.source, tcp.dest */
 +              mask[5] = 0x80; /* match tcp flags */
 +              /* leave rest (0 or set for MVM_TCP_RX_WAKE) */
 +              break;
 +      };
 +
 +      *pseudo_hdr_csum = pseudo_hdr_check(ip_tot_len - sizeof(struct iphdr),
 +                                          pkt->ip.saddr, pkt->ip.daddr);
 +}
 +
 +static int iwl_mvm_send_remote_wake_cfg(struct iwl_mvm *mvm,
 +                                      struct ieee80211_vif *vif,
 +                                      struct cfg80211_wowlan_tcp *tcp)
 +{
 +      struct iwl_wowlan_remote_wake_config *cfg;
 +      struct iwl_host_cmd cmd = {
 +              .id = REMOTE_WAKE_CONFIG_CMD,
 +              .len = { sizeof(*cfg), },
 +              .dataflags = { IWL_HCMD_DFL_NOCOPY, },
 +      };
 +      int ret;
 +
 +      if (!tcp)
 +              return 0;
 +
 +      cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
 +      if (!cfg)
 +              return -ENOMEM;
 +      cmd.data[0] = cfg;
 +
 +      cfg->max_syn_retries = 10;
 +      cfg->max_data_retries = 10;
 +      cfg->tcp_syn_ack_timeout = 1; /* seconds */
 +      cfg->tcp_ack_timeout = 1; /* seconds */
 +
 +      /* SYN (TX) */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->syn_tx.data, NULL,
 +              &cfg->syn_tx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_TX_SYN);
 +      cfg->syn_tx.info.tcp_payload_length = 0;
 +
 +      /* SYN/ACK (RX) */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->synack_rx.data, cfg->synack_rx.rx_mask,
 +              &cfg->synack_rx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_RX_SYNACK);
 +      cfg->synack_rx.info.tcp_payload_length = 0;
 +
 +      /* KEEPALIVE/ACK (TX) */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->keepalive_tx.data, NULL,
 +              &cfg->keepalive_tx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_TX_DATA);
 +      cfg->keepalive_tx.info.tcp_payload_length =
 +              cpu_to_le16(tcp->payload_len);
 +      cfg->sequence_number_offset = tcp->payload_seq.offset;
 +      /* length must be 0..4, the field is little endian */
 +      cfg->sequence_number_length = tcp->payload_seq.len;
 +      cfg->initial_sequence_number = cpu_to_le32(tcp->payload_seq.start);
 +      cfg->keepalive_interval = cpu_to_le16(tcp->data_interval);
 +      if (tcp->payload_tok.len) {
 +              cfg->token_offset = tcp->payload_tok.offset;
 +              cfg->token_length = tcp->payload_tok.len;
 +              cfg->num_tokens =
 +                      cpu_to_le16(tcp->tokens_size % tcp->payload_tok.len);
 +              memcpy(cfg->tokens, tcp->payload_tok.token_stream,
 +                     tcp->tokens_size);
 +      } else {
 +              /* set tokens to max value to almost never run out */
 +              cfg->num_tokens = cpu_to_le16(65535);
 +      }
 +
 +      /* ACK (RX) */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->keepalive_ack_rx.data,
 +              cfg->keepalive_ack_rx.rx_mask,
 +              &cfg->keepalive_ack_rx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_RX_ACK);
 +      cfg->keepalive_ack_rx.info.tcp_payload_length = 0;
 +
 +      /* WAKEUP (RX) */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->wake_rx.data, cfg->wake_rx.rx_mask,
 +              &cfg->wake_rx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_RX_WAKE);
 +      cfg->wake_rx.info.tcp_payload_length =
 +              cpu_to_le16(tcp->wake_len);
 +
 +      /* FIN */
 +      iwl_mvm_build_tcp_packet(
 +              vif, tcp, cfg->fin_tx.data, NULL,
 +              &cfg->fin_tx.info.tcp_pseudo_header_checksum,
 +              MVM_TCP_TX_FIN);
 +      cfg->fin_tx.info.tcp_payload_length = 0;
 +
 +      ret = iwl_mvm_send_cmd(mvm, &cmd);
 +      kfree(cfg);
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                              struct ieee80211_sta *ap_sta)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct ieee80211_chanctx_conf *ctx;
 +      u8 chains_static, chains_dynamic;
 +      struct cfg80211_chan_def chandef;
 +      int ret, i;
 +      struct iwl_binding_cmd binding_cmd = {};
 +      struct iwl_time_quota_cmd quota_cmd = {};
 +      u32 status;
 +
 +      /* add back the PHY */
 +      if (WARN_ON(!mvmvif->phy_ctxt))
 +              return -EINVAL;
 +
 +      rcu_read_lock();
 +      ctx = rcu_dereference(vif->chanctx_conf);
 +      if (WARN_ON(!ctx)) {
 +              rcu_read_unlock();
 +              return -EINVAL;
 +      }
 +      chandef = ctx->def;
 +      chains_static = ctx->rx_chains_static;
 +      chains_dynamic = ctx->rx_chains_dynamic;
 +      rcu_read_unlock();
 +
 +      ret = iwl_mvm_phy_ctxt_add(mvm, mvmvif->phy_ctxt, &chandef,
 +                                 chains_static, chains_dynamic);
 +      if (ret)
 +              return ret;
 +
 +      /* add back the MAC */
 +      mvmvif->uploaded = false;
 +
 +      if (WARN_ON(!vif->bss_conf.assoc))
 +              return -EINVAL;
 +
 +      ret = iwl_mvm_mac_ctxt_add(mvm, vif);
 +      if (ret)
 +              return ret;
 +
 +      /* add back binding - XXX refactor? */
 +      binding_cmd.id_and_color =
 +              cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id,
 +                                              mvmvif->phy_ctxt->color));
 +      binding_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD);
 +      binding_cmd.phy =
 +              cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id,
 +                                              mvmvif->phy_ctxt->color));
 +      binding_cmd.macs[0] = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id,
 +                                                            mvmvif->color));
 +      for (i = 1; i < MAX_MACS_IN_BINDING; i++)
 +              binding_cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID);
 +
 +      status = 0;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD,
 +                                        sizeof(binding_cmd), &binding_cmd,
 +                                        &status);
 +      if (ret) {
 +              IWL_ERR(mvm, "Failed to add binding: %d\n", ret);
 +              return ret;
 +      }
 +
 +      if (status) {
 +              IWL_ERR(mvm, "Binding command failed: %u\n", status);
 +              return -EIO;
 +      }
 +
 +      ret = iwl_mvm_sta_send_to_fw(mvm, ap_sta, false);
 +      if (ret)
 +              return ret;
 +      rcu_assign_pointer(mvm->fw_id_to_mac_id[mvmvif->ap_sta_id], ap_sta);
 +
 +      ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +      if (ret)
 +              return ret;
 +
 +      /* and some quota */
 +      quota_cmd.quotas[0].id_and_color =
 +              cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->phy_ctxt->id,
 +                                              mvmvif->phy_ctxt->color));
 +      quota_cmd.quotas[0].quota = cpu_to_le32(IWL_MVM_MAX_QUOTA);
 +      quota_cmd.quotas[0].max_duration = cpu_to_le32(IWL_MVM_MAX_QUOTA);
 +
 +      for (i = 1; i < MAX_BINDINGS; i++)
 +              quota_cmd.quotas[i].id_and_color = cpu_to_le32(FW_CTXT_INVALID);
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, 0,
 +                                 sizeof(quota_cmd), &quota_cmd);
 +      if (ret)
 +              IWL_ERR(mvm, "Failed to send quota: %d\n", ret);
 +
 +      if (iwl_mvm_is_lar_supported(mvm) && iwl_mvm_init_fw_regd(mvm))
 +              IWL_ERR(mvm, "Failed to initialize D3 LAR information\n");
 +
 +      return 0;
 +}
 +
 +static int iwl_mvm_get_last_nonqos_seq(struct iwl_mvm *mvm,
 +                                     struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_nonqos_seq_query_cmd query_cmd = {
 +              .get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_GET),
 +              .mac_id_n_color =
 +                      cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id,
 +                                                      mvmvif->color)),
 +      };
 +      struct iwl_host_cmd cmd = {
 +              .id = NON_QOS_TX_COUNTER_CMD,
 +              .flags = CMD_WANT_SKB,
 +      };
 +      int err;
 +      u32 size;
 +
 +      cmd.data[0] = &query_cmd;
 +      cmd.len[0] = sizeof(query_cmd);
 +
 +      err = iwl_mvm_send_cmd(mvm, &cmd);
 +      if (err)
 +              return err;
 +
 +      size = iwl_rx_packet_payload_len(cmd.resp_pkt);
 +      if (size < sizeof(__le16)) {
 +              err = -EINVAL;
 +      } else {
 +              err = le16_to_cpup((__le16 *)cmd.resp_pkt->data);
 +              /* firmware returns next, not last-used seqno */
 +              err = (u16) (err - 0x10);
 +      }
 +
 +      iwl_free_resp(&cmd);
 +      return err;
 +}
 +
 +void iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_nonqos_seq_query_cmd query_cmd = {
 +              .get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_SET),
 +              .mac_id_n_color =
 +                      cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id,
 +                                                      mvmvif->color)),
 +              .value = cpu_to_le16(mvmvif->seqno),
 +      };
 +
 +      /* return if called during restart, not resume from D3 */
 +      if (!mvmvif->seqno_valid)
 +              return;
 +
 +      mvmvif->seqno_valid = false;
 +
 +      if (iwl_mvm_send_cmd_pdu(mvm, NON_QOS_TX_COUNTER_CMD, 0,
 +                               sizeof(query_cmd), &query_cmd))
 +              IWL_ERR(mvm, "failed to set non-QoS seqno\n");
 +}
 +
 +static int iwl_mvm_switch_to_d3(struct iwl_mvm *mvm)
 +{
 +      iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true);
 +
 +      iwl_trans_stop_device(mvm->trans);
 +
 +      /*
 +       * Set the HW restart bit -- this is mostly true as we're
 +       * going to load new firmware and reprogram that, though
 +       * the reprogramming is going to be manual to avoid adding
 +       * all the MACs that aren't support.
 +       * We don't have to clear up everything though because the
 +       * reprogramming is manual. When we resume, we'll actually
 +       * go through a proper restart sequence again to switch
 +       * back to the runtime firmware image.
 +       */
 +      set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
 +
 +      mvm->ptk_ivlen = 0;
 +      mvm->ptk_icvlen = 0;
 +      mvm->ptk_ivlen = 0;
 +      mvm->ptk_icvlen = 0;
 +
 +      return iwl_mvm_load_d3_fw(mvm);
 +}
 +
 +static int
 +iwl_mvm_get_wowlan_config(struct iwl_mvm *mvm,
 +                        struct cfg80211_wowlan *wowlan,
 +                        struct iwl_wowlan_config_cmd *wowlan_config_cmd,
 +                        struct ieee80211_vif *vif, struct iwl_mvm_vif *mvmvif,
 +                        struct ieee80211_sta *ap_sta)
 +{
 +      int ret;
 +      struct iwl_mvm_sta *mvm_ap_sta = iwl_mvm_sta_from_mac80211(ap_sta);
 +
 +      /* TODO: wowlan_config_cmd->wowlan_ba_teardown_tids */
 +
 +      wowlan_config_cmd->is_11n_connection =
 +                                      ap_sta->ht_cap.ht_supported;
 +
 +      /* Query the last used seqno and set it */
 +      ret = iwl_mvm_get_last_nonqos_seq(mvm, vif);
 +      if (ret < 0)
 +              return ret;
 +
 +      wowlan_config_cmd->non_qos_seq = cpu_to_le16(ret);
 +
 +      iwl_mvm_set_wowlan_qos_seq(mvm_ap_sta, wowlan_config_cmd);
 +
 +      if (wowlan->disconnect)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS |
 +                                  IWL_WOWLAN_WAKEUP_LINK_CHANGE);
 +      if (wowlan->magic_pkt)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET);
 +      if (wowlan->gtk_rekey_failure)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL);
 +      if (wowlan->eap_identity_req)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ);
 +      if (wowlan->four_way_handshake)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE);
 +      if (wowlan->n_patterns)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH);
 +
 +      if (wowlan->rfkill_release)
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);
 +
 +      if (wowlan->tcp) {
 +              /*
 +               * Set the "link change" (really "link lost") flag as well
 +               * since that implies losing the TCP connection.
 +               */
 +              wowlan_config_cmd->wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS |
 +                                  IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE |
 +                                  IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET |
 +                                  IWL_WOWLAN_WAKEUP_LINK_CHANGE);
 +      }
 +
 +      return 0;
 +}
 +
 +static int
 +iwl_mvm_wowlan_config(struct iwl_mvm *mvm,
 +                    struct cfg80211_wowlan *wowlan,
 +                    struct iwl_wowlan_config_cmd *wowlan_config_cmd,
 +                    struct ieee80211_vif *vif, struct iwl_mvm_vif *mvmvif,
 +                    struct ieee80211_sta *ap_sta)
 +{
 +      struct iwl_wowlan_kek_kck_material_cmd kek_kck_cmd = {};
 +      struct iwl_wowlan_tkip_params_cmd tkip_cmd = {};
 +      struct wowlan_key_data key_data = {
 +              .use_rsc_tsc = false,
 +              .tkip = &tkip_cmd,
 +              .use_tkip = false,
 +      };
 +      int ret;
 +
 +      ret = iwl_mvm_switch_to_d3(mvm);
 +      if (ret)
 +              return ret;
 +
 +      ret = iwl_mvm_d3_reprogram(mvm, vif, ap_sta);
 +      if (ret)
 +              return ret;
 +
 +      key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL);
 +      if (!key_data.rsc_tsc)
 +              return -ENOMEM;
 +
 +      if (!iwlwifi_mod_params.sw_crypto) {
 +              /*
 +               * This needs to be unlocked due to lock ordering
 +               * constraints. Since we're in the suspend path
 +               * that isn't really a problem though.
 +               */
 +              mutex_unlock(&mvm->mutex);
 +              ieee80211_iter_keys(mvm->hw, vif,
 +                                  iwl_mvm_wowlan_program_keys,
 +                                  &key_data);
 +              mutex_lock(&mvm->mutex);
 +              if (key_data.error) {
 +                      ret = -EIO;
 +                      goto out;
 +              }
 +
 +              if (key_data.use_rsc_tsc) {
 +                      struct iwl_host_cmd rsc_tsc_cmd = {
 +                              .id = WOWLAN_TSC_RSC_PARAM,
 +                              .data[0] = key_data.rsc_tsc,
 +                              .dataflags[0] = IWL_HCMD_DFL_NOCOPY,
 +                              .len[0] = sizeof(*key_data.rsc_tsc),
 +                      };
 +
 +                      ret = iwl_mvm_send_cmd(mvm, &rsc_tsc_cmd);
 +                      if (ret)
 +                              goto out;
 +              }
 +
 +              if (key_data.use_tkip) {
 +                      ret = iwl_mvm_send_cmd_pdu(mvm,
 +                                                 WOWLAN_TKIP_PARAM,
 +                                                 0, sizeof(tkip_cmd),
 +                                                 &tkip_cmd);
 +                      if (ret)
 +                              goto out;
 +              }
 +
 +              if (mvmvif->rekey_data.valid) {
 +                      memset(&kek_kck_cmd, 0, sizeof(kek_kck_cmd));
 +                      memcpy(kek_kck_cmd.kck, mvmvif->rekey_data.kck,
 +                             NL80211_KCK_LEN);
 +                      kek_kck_cmd.kck_len = cpu_to_le16(NL80211_KCK_LEN);
 +                      memcpy(kek_kck_cmd.kek, mvmvif->rekey_data.kek,
 +                             NL80211_KEK_LEN);
 +                      kek_kck_cmd.kek_len = cpu_to_le16(NL80211_KEK_LEN);
 +                      kek_kck_cmd.replay_ctr = mvmvif->rekey_data.replay_ctr;
 +
 +                      ret = iwl_mvm_send_cmd_pdu(mvm,
 +                                                 WOWLAN_KEK_KCK_MATERIAL, 0,
 +                                                 sizeof(kek_kck_cmd),
 +                                                 &kek_kck_cmd);
 +                      if (ret)
 +                              goto out;
 +              }
 +      }
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0,
 +                                 sizeof(*wowlan_config_cmd),
 +                                 wowlan_config_cmd);
 +      if (ret)
 +              goto out;
 +
 +      ret = iwl_mvm_send_patterns(mvm, wowlan);
 +      if (ret)
 +              goto out;
 +
 +      ret = iwl_mvm_send_proto_offload(mvm, vif, false, 0);
 +      if (ret)
 +              goto out;
 +
 +      ret = iwl_mvm_send_remote_wake_cfg(mvm, vif, wowlan->tcp);
 +
 +out:
 +      kfree(key_data.rsc_tsc);
 +      return ret;
 +}
 +
 +static int
 +iwl_mvm_netdetect_config(struct iwl_mvm *mvm,
 +                       struct cfg80211_wowlan *wowlan,
 +                       struct cfg80211_sched_scan_request *nd_config,
 +                       struct ieee80211_vif *vif)
 +{
 +      struct iwl_wowlan_config_cmd wowlan_config_cmd = {};
 +      int ret;
 +
 +      ret = iwl_mvm_switch_to_d3(mvm);
 +      if (ret)
 +              return ret;
 +
 +      /* rfkill release can be either for wowlan or netdetect */
 +      if (wowlan->rfkill_release)
 +              wowlan_config_cmd.wakeup_filter |=
 +                      cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0,
 +                                 sizeof(wowlan_config_cmd),
 +                                 &wowlan_config_cmd);
 +      if (ret)
 +              return ret;
 +
 +      ret = iwl_mvm_sched_scan_start(mvm, vif, nd_config, &mvm->nd_ies,
 +                                     IWL_MVM_SCAN_NETDETECT);
 +      if (ret)
 +              return ret;
 +
 +      if (WARN_ON(mvm->nd_match_sets || mvm->nd_channels))
 +              return -EBUSY;
 +
 +      /* save the sched scan matchsets... */
 +      if (nd_config->n_match_sets) {
 +              mvm->nd_match_sets = kmemdup(nd_config->match_sets,
 +                                           sizeof(*nd_config->match_sets) *
 +                                           nd_config->n_match_sets,
 +                                           GFP_KERNEL);
 +              if (mvm->nd_match_sets)
 +                      mvm->n_nd_match_sets = nd_config->n_match_sets;
 +      }
 +
 +      /* ...and the sched scan channels for later reporting */
 +      mvm->nd_channels = kmemdup(nd_config->channels,
 +                                 sizeof(*nd_config->channels) *
 +                                 nd_config->n_channels,
 +                                 GFP_KERNEL);
 +      if (mvm->nd_channels)
 +              mvm->n_nd_channels = nd_config->n_channels;
 +
 +      return 0;
 +}
 +
 +static void iwl_mvm_free_nd(struct iwl_mvm *mvm)
 +{
 +      kfree(mvm->nd_match_sets);
 +      mvm->nd_match_sets = NULL;
 +      mvm->n_nd_match_sets = 0;
 +      kfree(mvm->nd_channels);
 +      mvm->nd_channels = NULL;
 +      mvm->n_nd_channels = 0;
 +}
 +
 +static int __iwl_mvm_suspend(struct ieee80211_hw *hw,
 +                           struct cfg80211_wowlan *wowlan,
 +                           bool test)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct ieee80211_vif *vif = NULL;
 +      struct iwl_mvm_vif *mvmvif = NULL;
 +      struct ieee80211_sta *ap_sta = NULL;
 +      struct iwl_d3_manager_config d3_cfg_cmd_data = {
 +              /*
 +               * Program the minimum sleep time to 10 seconds, as many
 +               * platforms have issues processing a wakeup signal while
 +               * still being in the process of suspending.
 +               */
 +              .min_sleep_time = cpu_to_le32(10 * 1000 * 1000),
 +      };
 +      struct iwl_host_cmd d3_cfg_cmd = {
 +              .id = D3_CONFIG_CMD,
 +              .flags = CMD_WANT_SKB,
 +              .data[0] = &d3_cfg_cmd_data,
 +              .len[0] = sizeof(d3_cfg_cmd_data),
 +      };
 +      int ret;
 +      int len __maybe_unused;
 +
 +      if (!wowlan) {
 +              /*
 +               * mac80211 shouldn't get here, but for D3 test
 +               * it doesn't warrant a warning
 +               */
 +              WARN_ON(!test);
 +              return -EINVAL;
 +      }
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      vif = iwl_mvm_get_bss_vif(mvm);
 +      if (IS_ERR_OR_NULL(vif)) {
 +              ret = 1;
 +              goto out_noreset;
 +      }
 +
 +      mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      if (mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) {
 +              /* if we're not associated, this must be netdetect */
 +              if (!wowlan->nd_config && !mvm->nd_config) {
 +                      ret = 1;
 +                      goto out_noreset;
 +              }
 +
 +              ret = iwl_mvm_netdetect_config(
 +                      mvm, wowlan, wowlan->nd_config ?: mvm->nd_config, vif);
 +              if (ret)
 +                      goto out;
 +
 +              mvm->net_detect = true;
 +      } else {
 +              struct iwl_wowlan_config_cmd wowlan_config_cmd = {};
 +
 +              ap_sta = rcu_dereference_protected(
 +                      mvm->fw_id_to_mac_id[mvmvif->ap_sta_id],
 +                      lockdep_is_held(&mvm->mutex));
 +              if (IS_ERR_OR_NULL(ap_sta)) {
 +                      ret = -EINVAL;
 +                      goto out_noreset;
 +              }
 +
 +              ret = iwl_mvm_get_wowlan_config(mvm, wowlan, &wowlan_config_cmd,
 +                                              vif, mvmvif, ap_sta);
 +              if (ret)
 +                      goto out_noreset;
 +              ret = iwl_mvm_wowlan_config(mvm, wowlan, &wowlan_config_cmd,
 +                                          vif, mvmvif, ap_sta);
 +              if (ret)
 +                      goto out;
 +
 +              mvm->net_detect = false;
 +      }
 +
 +      ret = iwl_mvm_power_update_device(mvm);
 +      if (ret)
 +              goto out;
 +
 +      ret = iwl_mvm_power_update_mac(mvm);
 +      if (ret)
 +              goto out;
 +
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +      if (mvm->d3_wake_sysassert)
 +              d3_cfg_cmd_data.wakeup_flags |=
 +                      cpu_to_le32(IWL_WAKEUP_D3_CONFIG_FW_ERROR);
 +#endif
 +
 +      /* must be last -- this switches firmware state */
 +      ret = iwl_mvm_send_cmd(mvm, &d3_cfg_cmd);
 +      if (ret)
 +              goto out;
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +      len = iwl_rx_packet_payload_len(d3_cfg_cmd.resp_pkt);
 +      if (len >= sizeof(u32)) {
 +              mvm->d3_test_pme_ptr =
 +                      le32_to_cpup((__le32 *)d3_cfg_cmd.resp_pkt->data);
 +      }
 +#endif
 +      iwl_free_resp(&d3_cfg_cmd);
 +
 +      clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
 +
 +      iwl_trans_d3_suspend(mvm->trans, test);
 + out:
 +      if (ret < 0) {
 +              iwl_mvm_ref(mvm, IWL_MVM_REF_UCODE_DOWN);
 +              ieee80211_restart_hw(mvm->hw);
 +              iwl_mvm_free_nd(mvm);
 +      }
 + out_noreset:
 +      mutex_unlock(&mvm->mutex);
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_enter_d0i3_sync(struct iwl_mvm *mvm)
 +{
 +      struct iwl_notification_wait wait_d3;
 +      static const u16 d3_notif[] = { D3_CONFIG_CMD };
 +      int ret;
 +
 +      iwl_init_notification_wait(&mvm->notif_wait, &wait_d3,
 +                                 d3_notif, ARRAY_SIZE(d3_notif),
 +                                 NULL, NULL);
 +
 +      ret = iwl_mvm_enter_d0i3(mvm->hw->priv);
 +      if (ret)
 +              goto remove_notif;
 +
 +      ret = iwl_wait_notification(&mvm->notif_wait, &wait_d3, HZ);
 +      WARN_ON_ONCE(ret);
 +      return ret;
 +
 +remove_notif:
 +      iwl_remove_notification(&mvm->notif_wait, &wait_d3);
 +      return ret;
 +}
 +
 +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
 +
 +      /* make sure the d0i3 exit work is not pending */
 +      flush_work(&mvm->d0i3_exit_work);
 +
 +      ret = iwl_trans_suspend(mvm->trans);
 +      if (ret)
 +              return ret;
 +
 +      mvm->trans->wowlan_d0i3 = wowlan->any;
 +      if (mvm->trans->wowlan_d0i3) {
 +              /* 'any' trigger means d0i3 usage */
 +              if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND) {
 +                      ret = iwl_mvm_enter_d0i3_sync(mvm);
 +
 +                      if (ret)
 +                              return ret;
 +              }
 +
 +              mutex_lock(&mvm->d0i3_suspend_mutex);
 +              __set_bit(D0I3_DEFER_WAKEUP, &mvm->d0i3_suspend_flags);
 +              mutex_unlock(&mvm->d0i3_suspend_mutex);
 +
 +              iwl_trans_d3_suspend(mvm->trans, false);
 +
 +              return 0;
 +      }
 +
 +      return __iwl_mvm_suspend(hw, wowlan, false);
 +}
 +
 +/* converted data from the different status responses */
 +struct iwl_wowlan_status_data {
 +      u16 pattern_number;
 +      u16 qos_seq_ctr[8];
 +      u32 wakeup_reasons;
 +      u32 wake_packet_length;
 +      u32 wake_packet_bufsize;
 +      const u8 *wake_packet;
 +};
 +
 +static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm,
 +                                        struct ieee80211_vif *vif,
 +                                        struct iwl_wowlan_status_data *status)
 +{
 +      struct sk_buff *pkt = NULL;
 +      struct cfg80211_wowlan_wakeup wakeup = {
 +              .pattern_idx = -1,
 +      };
 +      struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup;
 +      u32 reasons = status->wakeup_reasons;
 +
 +      if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) {
 +              wakeup_report = NULL;
 +              goto report;
 +      }
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET)
 +              wakeup.magic_pkt = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN)
 +              wakeup.pattern_idx =
 +                      status->pattern_number;
 +
 +      if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON |
 +                     IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH))
 +              wakeup.disconnect = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE)
 +              wakeup.gtk_rekey_failure = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED)
 +              wakeup.rfkill_release = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST)
 +              wakeup.eap_identity_req = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE)
 +              wakeup.four_way_handshake = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS)
 +              wakeup.tcp_connlost = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE)
 +              wakeup.tcp_nomoretokens = true;
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET)
 +              wakeup.tcp_match = true;
 +
 +      if (status->wake_packet_bufsize) {
 +              int pktsize = status->wake_packet_bufsize;
 +              int pktlen = status->wake_packet_length;
 +              const u8 *pktdata = status->wake_packet;
 +              struct ieee80211_hdr *hdr = (void *)pktdata;
 +              int truncated = pktlen - pktsize;
 +
 +              /* this would be a firmware bug */
 +              if (WARN_ON_ONCE(truncated < 0))
 +                      truncated = 0;
 +
 +              if (ieee80211_is_data(hdr->frame_control)) {
 +                      int hdrlen = ieee80211_hdrlen(hdr->frame_control);
 +                      int ivlen = 0, icvlen = 4; /* also FCS */
 +
 +                      pkt = alloc_skb(pktsize, GFP_KERNEL);
 +                      if (!pkt)
 +                              goto report;
 +
 +                      memcpy(skb_put(pkt, hdrlen), pktdata, hdrlen);
 +                      pktdata += hdrlen;
 +                      pktsize -= hdrlen;
 +
 +                      if (ieee80211_has_protected(hdr->frame_control)) {
 +                              /*
 +                               * This is unlocked and using gtk_i(c)vlen,
 +                               * but since everything is under RTNL still
 +                               * that's not really a problem - changing
 +                               * it would be difficult.
 +                               */
 +                              if (is_multicast_ether_addr(hdr->addr1)) {
 +                                      ivlen = mvm->gtk_ivlen;
 +                                      icvlen += mvm->gtk_icvlen;
 +                              } else {
 +                                      ivlen = mvm->ptk_ivlen;
 +                                      icvlen += mvm->ptk_icvlen;
 +                              }
 +                      }
 +
 +                      /* if truncated, FCS/ICV is (partially) gone */
 +                      if (truncated >= icvlen) {
 +                              icvlen = 0;
 +                              truncated -= icvlen;
 +                      } else {
 +                              icvlen -= truncated;
 +                              truncated = 0;
 +                      }
 +
 +                      pktsize -= ivlen + icvlen;
 +                      pktdata += ivlen;
 +
 +                      memcpy(skb_put(pkt, pktsize), pktdata, pktsize);
 +
 +                      if (ieee80211_data_to_8023(pkt, vif->addr, vif->type))
 +                              goto report;
 +                      wakeup.packet = pkt->data;
 +                      wakeup.packet_present_len = pkt->len;
 +                      wakeup.packet_len = pkt->len - truncated;
 +                      wakeup.packet_80211 = false;
 +              } else {
 +                      int fcslen = 4;
 +
 +                      if (truncated >= 4) {
 +                              truncated -= 4;
 +                              fcslen = 0;
 +                      } else {
 +                              fcslen -= truncated;
 +                              truncated = 0;
 +                      }
 +                      pktsize -= fcslen;
 +                      wakeup.packet = status->wake_packet;
 +                      wakeup.packet_present_len = pktsize;
 +                      wakeup.packet_len = pktlen - truncated;
 +                      wakeup.packet_80211 = true;
 +              }
 +      }
 +
 + report:
 +      ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL);
 +      kfree_skb(pkt);
 +}
 +
 +static void iwl_mvm_aes_sc_to_seq(struct aes_sc *sc,
 +                                struct ieee80211_key_seq *seq)
 +{
 +      u64 pn;
 +
 +      pn = le64_to_cpu(sc->pn);
 +      seq->ccmp.pn[0] = pn >> 40;
 +      seq->ccmp.pn[1] = pn >> 32;
 +      seq->ccmp.pn[2] = pn >> 24;
 +      seq->ccmp.pn[3] = pn >> 16;
 +      seq->ccmp.pn[4] = pn >> 8;
 +      seq->ccmp.pn[5] = pn;
 +}
 +
 +static void iwl_mvm_tkip_sc_to_seq(struct tkip_sc *sc,
 +                                 struct ieee80211_key_seq *seq)
 +{
 +      seq->tkip.iv32 = le32_to_cpu(sc->iv32);
 +      seq->tkip.iv16 = le16_to_cpu(sc->iv16);
 +}
 +
 +static void iwl_mvm_set_aes_rx_seq(struct aes_sc *scs,
 +                                 struct ieee80211_key_conf *key)
 +{
 +      int tid;
 +
 +      BUILD_BUG_ON(IWL_NUM_RSC != IEEE80211_NUM_TIDS);
 +
 +      for (tid = 0; tid < IWL_NUM_RSC; tid++) {
 +              struct ieee80211_key_seq seq = {};
 +
 +              iwl_mvm_aes_sc_to_seq(&scs[tid], &seq);
 +              ieee80211_set_key_rx_seq(key, tid, &seq);
 +      }
 +}
 +
 +static void iwl_mvm_set_tkip_rx_seq(struct tkip_sc *scs,
 +                                  struct ieee80211_key_conf *key)
 +{
 +      int tid;
 +
 +      BUILD_BUG_ON(IWL_NUM_RSC != IEEE80211_NUM_TIDS);
 +
 +      for (tid = 0; tid < IWL_NUM_RSC; tid++) {
 +              struct ieee80211_key_seq seq = {};
 +
 +              iwl_mvm_tkip_sc_to_seq(&scs[tid], &seq);
 +              ieee80211_set_key_rx_seq(key, tid, &seq);
 +      }
 +}
 +
 +static void iwl_mvm_set_key_rx_seq(struct ieee80211_key_conf *key,
 +                                 struct iwl_wowlan_status *status)
 +{
 +      union iwl_all_tsc_rsc *rsc = &status->gtk.rsc.all_tsc_rsc;
 +
 +      switch (key->cipher) {
 +      case WLAN_CIPHER_SUITE_CCMP:
 +              iwl_mvm_set_aes_rx_seq(rsc->aes.multicast_rsc, key);
 +              break;
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              iwl_mvm_set_tkip_rx_seq(rsc->tkip.multicast_rsc, key);
 +              break;
 +      default:
 +              WARN_ON(1);
 +      }
 +}
 +
 +struct iwl_mvm_d3_gtk_iter_data {
 +      struct iwl_wowlan_status *status;
 +      void *last_gtk;
 +      u32 cipher;
 +      bool find_phase, unhandled_cipher;
 +      int num_keys;
 +};
 +
 +static void iwl_mvm_d3_update_gtks(struct ieee80211_hw *hw,
 +                                 struct ieee80211_vif *vif,
 +                                 struct ieee80211_sta *sta,
 +                                 struct ieee80211_key_conf *key,
 +                                 void *_data)
 +{
 +      struct iwl_mvm_d3_gtk_iter_data *data = _data;
 +
 +      if (data->unhandled_cipher)
 +              return;
 +
 +      switch (key->cipher) {
 +      case WLAN_CIPHER_SUITE_WEP40:
 +      case WLAN_CIPHER_SUITE_WEP104:
 +              /* ignore WEP completely, nothing to do */
 +              return;
 +      case WLAN_CIPHER_SUITE_CCMP:
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              /* we support these */
 +              break;
 +      default:
 +              /* everything else (even CMAC for MFP) - disconnect from AP */
 +              data->unhandled_cipher = true;
 +              return;
 +      }
 +
 +      data->num_keys++;
 +
 +      /*
 +       * pairwise key - update sequence counters only;
 +       * note that this assumes no TDLS sessions are active
 +       */
 +      if (sta) {
 +              struct ieee80211_key_seq seq = {};
 +              union iwl_all_tsc_rsc *sc = &data->status->gtk.rsc.all_tsc_rsc;
 +
 +              if (data->find_phase)
 +                      return;
 +
 +              switch (key->cipher) {
 +              case WLAN_CIPHER_SUITE_CCMP:
 +                      iwl_mvm_set_aes_rx_seq(sc->aes.unicast_rsc, key);
 +                      atomic64_set(&key->tx_pn, le64_to_cpu(sc->aes.tsc.pn));
 +                      break;
 +              case WLAN_CIPHER_SUITE_TKIP:
 +                      iwl_mvm_tkip_sc_to_seq(&sc->tkip.tsc, &seq);
 +                      iwl_mvm_set_tkip_rx_seq(sc->tkip.unicast_rsc, key);
 +                      ieee80211_set_key_tx_seq(key, &seq);
 +                      break;
 +              }
 +
 +              /* that's it for this key */
 +              return;
 +      }
 +
 +      if (data->find_phase) {
 +              data->last_gtk = key;
 +              data->cipher = key->cipher;
 +              return;
 +      }
 +
 +      if (data->status->num_of_gtk_rekeys)
 +              ieee80211_remove_key(key);
 +      else if (data->last_gtk == key)
 +              iwl_mvm_set_key_rx_seq(key, data->status);
 +}
 +
 +static bool iwl_mvm_setup_connection_keep(struct iwl_mvm *mvm,
 +                                        struct ieee80211_vif *vif,
 +                                        struct iwl_wowlan_status *status)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_d3_gtk_iter_data gtkdata = {
 +              .status = status,
 +      };
 +      u32 disconnection_reasons =
 +              IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON |
 +              IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH;
 +
 +      if (!status || !vif->bss_conf.bssid)
 +              return false;
 +
 +      if (le32_to_cpu(status->wakeup_reasons) & disconnection_reasons)
 +              return false;
 +
 +      /* find last GTK that we used initially, if any */
 +      gtkdata.find_phase = true;
 +      ieee80211_iter_keys(mvm->hw, vif,
 +                          iwl_mvm_d3_update_gtks, &gtkdata);
 +      /* not trying to keep connections with MFP/unhandled ciphers */
 +      if (gtkdata.unhandled_cipher)
 +              return false;
 +      if (!gtkdata.num_keys)
 +              goto out;
 +      if (!gtkdata.last_gtk)
 +              return false;
 +
 +      /*
 +       * invalidate all other GTKs that might still exist and update
 +       * the one that we used
 +       */
 +      gtkdata.find_phase = false;
 +      ieee80211_iter_keys(mvm->hw, vif,
 +                          iwl_mvm_d3_update_gtks, &gtkdata);
 +
 +      if (status->num_of_gtk_rekeys) {
 +              struct ieee80211_key_conf *key;
 +              struct {
 +                      struct ieee80211_key_conf conf;
 +                      u8 key[32];
 +              } conf = {
 +                      .conf.cipher = gtkdata.cipher,
 +                      .conf.keyidx = status->gtk.key_index,
 +              };
 +
 +              switch (gtkdata.cipher) {
 +              case WLAN_CIPHER_SUITE_CCMP:
 +                      conf.conf.keylen = WLAN_KEY_LEN_CCMP;
 +                      memcpy(conf.conf.key, status->gtk.decrypt_key,
 +                             WLAN_KEY_LEN_CCMP);
 +                      break;
 +              case WLAN_CIPHER_SUITE_TKIP:
 +                      conf.conf.keylen = WLAN_KEY_LEN_TKIP;
 +                      memcpy(conf.conf.key, status->gtk.decrypt_key, 16);
 +                      /* leave TX MIC key zeroed, we don't use it anyway */
 +                      memcpy(conf.conf.key +
 +                             NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY,
 +                             status->gtk.tkip_mic_key, 8);
 +                      break;
 +              }
 +
 +              key = ieee80211_gtk_rekey_add(vif, &conf.conf);
 +              if (IS_ERR(key))
 +                      return false;
 +              iwl_mvm_set_key_rx_seq(key, status);
 +      }
 +
 +      if (status->num_of_gtk_rekeys) {
 +              __be64 replay_ctr =
 +                      cpu_to_be64(le64_to_cpu(status->replay_ctr));
 +              ieee80211_gtk_rekey_notify(vif, vif->bss_conf.bssid,
 +                                         (void *)&replay_ctr, GFP_KERNEL);
 +      }
 +
 +out:
 +      mvmvif->seqno_valid = true;
 +      /* +0x10 because the set API expects next-to-use, not last-used */
 +      mvmvif->seqno = le16_to_cpu(status->non_qos_seq_ctr) + 0x10;
 +
 +      return true;
 +}
 +
 +static struct iwl_wowlan_status *
 +iwl_mvm_get_wakeup_status(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      u32 base = mvm->error_event_table;
 +      struct error_table_start {
 +              /* cf. struct iwl_error_event_table */
 +              u32 valid;
 +              u32 error_id;
 +      } err_info;
 +      struct iwl_host_cmd cmd = {
 +              .id = WOWLAN_GET_STATUSES,
 +              .flags = CMD_WANT_SKB,
 +      };
 +      struct iwl_wowlan_status *status, *fw_status;
 +      int ret, len, status_size;
 +
 +      iwl_trans_read_mem_bytes(mvm->trans, base,
 +                               &err_info, sizeof(err_info));
 +
 +      if (err_info.valid) {
 +              IWL_INFO(mvm, "error table is valid (%d) with error (%d)\n",
 +                       err_info.valid, err_info.error_id);
 +              if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) {
 +                      struct cfg80211_wowlan_wakeup wakeup = {
 +                              .rfkill_release = true,
 +                      };
 +                      ieee80211_report_wowlan_wakeup(vif, &wakeup,
 +                                                     GFP_KERNEL);
 +              }
 +              return ERR_PTR(-EIO);
 +      }
 +
 +      /* only for tracing for now */
 +      ret = iwl_mvm_send_cmd_pdu(mvm, OFFLOADS_QUERY_CMD, 0, 0, NULL);
 +      if (ret)
 +              IWL_ERR(mvm, "failed to query offload statistics (%d)\n", ret);
 +
 +      ret = iwl_mvm_send_cmd(mvm, &cmd);
 +      if (ret) {
 +              IWL_ERR(mvm, "failed to query status (%d)\n", ret);
 +              return ERR_PTR(ret);
 +      }
 +
 +      /* RF-kill already asserted again... */
 +      if (!cmd.resp_pkt) {
 +              fw_status = ERR_PTR(-ERFKILL);
 +              goto out_free_resp;
 +      }
 +
 +      status_size = sizeof(*fw_status);
 +
 +      len = iwl_rx_packet_payload_len(cmd.resp_pkt);
 +      if (len < status_size) {
 +              IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
 +              fw_status = ERR_PTR(-EIO);
 +              goto out_free_resp;
 +      }
 +
 +      status = (void *)cmd.resp_pkt->data;
 +      if (len != (status_size +
 +                  ALIGN(le32_to_cpu(status->wake_packet_bufsize), 4))) {
 +              IWL_ERR(mvm, "Invalid WoWLAN status response!\n");
 +              fw_status = ERR_PTR(-EIO);
 +              goto out_free_resp;
 +      }
 +
 +      fw_status = kmemdup(status, len, GFP_KERNEL);
 +
 +out_free_resp:
 +      iwl_free_resp(&cmd);
 +      return fw_status;
 +}
 +
 +/* releases the MVM mutex */
 +static bool iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,
 +                                       struct ieee80211_vif *vif)
 +{
 +      struct iwl_wowlan_status_data status;
 +      struct iwl_wowlan_status *fw_status;
 +      int i;
 +      bool keep;
 +      struct ieee80211_sta *ap_sta;
 +      struct iwl_mvm_sta *mvm_ap_sta;
 +
 +      fw_status = iwl_mvm_get_wakeup_status(mvm, vif);
 +      if (IS_ERR_OR_NULL(fw_status))
 +              goto out_unlock;
 +
 +      status.pattern_number = le16_to_cpu(fw_status->pattern_number);
 +      for (i = 0; i < 8; i++)
 +              status.qos_seq_ctr[i] =
 +                      le16_to_cpu(fw_status->qos_seq_ctr[i]);
 +      status.wakeup_reasons = le32_to_cpu(fw_status->wakeup_reasons);
 +      status.wake_packet_length =
 +              le32_to_cpu(fw_status->wake_packet_length);
 +      status.wake_packet_bufsize =
 +              le32_to_cpu(fw_status->wake_packet_bufsize);
 +      status.wake_packet = fw_status->wake_packet;
 +
 +      /* still at hard-coded place 0 for D3 image */
 +      ap_sta = rcu_dereference_protected(
 +                      mvm->fw_id_to_mac_id[0],
 +                      lockdep_is_held(&mvm->mutex));
 +      if (IS_ERR_OR_NULL(ap_sta))
 +              goto out_free;
 +
 +      mvm_ap_sta = iwl_mvm_sta_from_mac80211(ap_sta);
 +      for (i = 0; i < IWL_MAX_TID_COUNT; i++) {
 +              u16 seq = status.qos_seq_ctr[i];
 +              /* firmware stores last-used value, we store next value */
 +              seq += 0x10;
 +              mvm_ap_sta->tid_data[i].seq_number = seq;
 +      }
 +
 +      /* now we have all the data we need, unlock to avoid mac80211 issues */
 +      mutex_unlock(&mvm->mutex);
 +
 +      iwl_mvm_report_wakeup_reasons(mvm, vif, &status);
 +
 +      keep = iwl_mvm_setup_connection_keep(mvm, vif, fw_status);
 +
 +      kfree(fw_status);
 +      return keep;
 +
 +out_free:
 +      kfree(fw_status);
 +out_unlock:
 +      mutex_unlock(&mvm->mutex);
 +      return false;
 +}
 +
 +struct iwl_mvm_nd_query_results {
 +      u32 matched_profiles;
 +      struct iwl_scan_offload_profile_match matches[IWL_SCAN_MAX_PROFILES];
 +};
 +
 +static int
 +iwl_mvm_netdetect_query_results(struct iwl_mvm *mvm,
 +                              struct iwl_mvm_nd_query_results *results)
 +{
 +      struct iwl_scan_offload_profiles_query *query;
 +      struct iwl_host_cmd cmd = {
 +              .id = SCAN_OFFLOAD_PROFILES_QUERY_CMD,
 +              .flags = CMD_WANT_SKB,
 +      };
 +      int ret, len;
 +
 +      ret = iwl_mvm_send_cmd(mvm, &cmd);
 +      if (ret) {
 +              IWL_ERR(mvm, "failed to query matched profiles (%d)\n", ret);
 +              return ret;
 +      }
 +
 +      /* RF-kill already asserted again... */
 +      if (!cmd.resp_pkt) {
 +              ret = -ERFKILL;
 +              goto out_free_resp;
 +      }
 +
 +      len = iwl_rx_packet_payload_len(cmd.resp_pkt);
 +      if (len < sizeof(*query)) {
 +              IWL_ERR(mvm, "Invalid scan offload profiles query response!\n");
 +              ret = -EIO;
 +              goto out_free_resp;
 +      }
 +
 +      query = (void *)cmd.resp_pkt->data;
 +
 +      results->matched_profiles = le32_to_cpu(query->matched_profiles);
 +      memcpy(results->matches, query->matches, sizeof(results->matches));
 +
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +      mvm->last_netdetect_scans = le32_to_cpu(query->n_scans_done);
 +#endif
 +
 +out_free_resp:
 +      iwl_free_resp(&cmd);
 +      return ret;
 +}
 +
 +static void iwl_mvm_query_netdetect_reasons(struct iwl_mvm *mvm,
 +                                          struct ieee80211_vif *vif)
 +{
 +      struct cfg80211_wowlan_nd_info *net_detect = NULL;
 +      struct cfg80211_wowlan_wakeup wakeup = {
 +              .pattern_idx = -1,
 +      };
 +      struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup;
 +      struct iwl_mvm_nd_query_results query;
 +      struct iwl_wowlan_status *fw_status;
 +      unsigned long matched_profiles;
 +      u32 reasons = 0;
 +      int i, j, n_matches, ret;
 +
 +      fw_status = iwl_mvm_get_wakeup_status(mvm, vif);
 +      if (!IS_ERR_OR_NULL(fw_status)) {
 +              reasons = le32_to_cpu(fw_status->wakeup_reasons);
 +              kfree(fw_status);
 +      }
 +
 +      if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED)
 +              wakeup.rfkill_release = true;
 +
 +      if (reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS)
 +              goto out;
 +
 +      ret = iwl_mvm_netdetect_query_results(mvm, &query);
 +      if (ret || !query.matched_profiles) {
 +              wakeup_report = NULL;
 +              goto out;
 +      }
 +
 +      matched_profiles = query.matched_profiles;
 +      if (mvm->n_nd_match_sets) {
 +              n_matches = hweight_long(matched_profiles);
 +      } else {
 +              IWL_ERR(mvm, "no net detect match information available\n");
 +              n_matches = 0;
 +      }
 +
 +      net_detect = kzalloc(sizeof(*net_detect) +
 +                           (n_matches * sizeof(net_detect->matches[0])),
 +                           GFP_KERNEL);
 +      if (!net_detect || !n_matches)
 +              goto out_report_nd;
 +
 +      for_each_set_bit(i, &matched_profiles, mvm->n_nd_match_sets) {
 +              struct iwl_scan_offload_profile_match *fw_match;
 +              struct cfg80211_wowlan_nd_match *match;
 +              int idx, n_channels = 0;
 +
 +              fw_match = &query.matches[i];
 +
 +              for (j = 0; j < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN; j++)
 +                      n_channels += hweight8(fw_match->matching_channels[j]);
 +
 +              match = kzalloc(sizeof(*match) +
 +                              (n_channels * sizeof(*match->channels)),
 +                              GFP_KERNEL);
 +              if (!match)
 +                      goto out_report_nd;
 +
 +              net_detect->matches[net_detect->n_matches++] = match;
 +
 +              /* We inverted the order of the SSIDs in the scan
 +               * request, so invert the index here.
 +               */
 +              idx = mvm->n_nd_match_sets - i - 1;
 +              match->ssid.ssid_len = mvm->nd_match_sets[idx].ssid.ssid_len;
 +              memcpy(match->ssid.ssid, mvm->nd_match_sets[idx].ssid.ssid,
 +                     match->ssid.ssid_len);
 +
 +              if (mvm->n_nd_channels < n_channels)
 +                      continue;
 +
 +              for (j = 0; j < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN * 8; j++)
 +                      if (fw_match->matching_channels[j / 8] & (BIT(j % 8)))
 +                              match->channels[match->n_channels++] =
 +                                      mvm->nd_channels[j]->center_freq;
 +      }
 +
 +out_report_nd:
 +      wakeup.net_detect = net_detect;
 +out:
 +      iwl_mvm_free_nd(mvm);
 +
 +      mutex_unlock(&mvm->mutex);
 +      ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL);
 +
 +      if (net_detect) {
 +              for (i = 0; i < net_detect->n_matches; i++)
 +                      kfree(net_detect->matches[i]);
 +              kfree(net_detect);
 +      }
 +}
 +
 +static void iwl_mvm_read_d3_sram(struct iwl_mvm *mvm)
 +{
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +      const struct fw_img *img = &mvm->fw->img[IWL_UCODE_WOWLAN];
 +      u32 len = img->sec[IWL_UCODE_SECTION_DATA].len;
 +      u32 offs = img->sec[IWL_UCODE_SECTION_DATA].offset;
 +
 +      if (!mvm->store_d3_resume_sram)
 +              return;
 +
 +      if (!mvm->d3_resume_sram) {
 +              mvm->d3_resume_sram = kzalloc(len, GFP_KERNEL);
 +              if (!mvm->d3_resume_sram)
 +                      return;
 +      }
 +
 +      iwl_trans_read_mem_bytes(mvm->trans, offs, mvm->d3_resume_sram, len);
 +#endif
 +}
 +
 +static void iwl_mvm_d3_disconnect_iter(void *data, u8 *mac,
 +                                     struct ieee80211_vif *vif)
 +{
 +      /* skip the one we keep connection on */
 +      if (data == vif)
 +              return;
 +
 +      if (vif->type == NL80211_IFTYPE_STATION)
 +              ieee80211_resume_disconnect(vif);
 +}
 +
 +static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)
 +{
 +      struct ieee80211_vif *vif = NULL;
 +      int ret;
 +      enum iwl_d3_status d3_status;
 +      bool keep = false;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      /* get the BSS vif pointer again */
 +      vif = iwl_mvm_get_bss_vif(mvm);
 +      if (IS_ERR_OR_NULL(vif))
 +              goto err;
 +
 +      ret = iwl_trans_d3_resume(mvm->trans, &d3_status, test);
 +      if (ret)
 +              goto err;
 +
 +      if (d3_status != IWL_D3_STATUS_ALIVE) {
 +              IWL_INFO(mvm, "Device was reset during suspend\n");
 +              goto err;
 +      }
 +
 +      /* query SRAM first in case we want event logging */
 +      iwl_mvm_read_d3_sram(mvm);
 +
 +      /*
 +       * Query the current location and source from the D3 firmware so we
 +       * can play it back when we re-intiailize the D0 firmware
 +       */
 +      iwl_mvm_update_changed_regdom(mvm);
 +
 +      if (mvm->net_detect) {
 +              iwl_mvm_query_netdetect_reasons(mvm, vif);
 +              /* has unlocked the mutex, so skip that */
 +              goto out;
 +      } else {
 +              keep = iwl_mvm_query_wakeup_reasons(mvm, vif);
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +              if (keep)
 +                      mvm->keep_vif = vif;
 +#endif
 +              /* has unlocked the mutex, so skip that */
 +              goto out_iterate;
 +      }
 +
 +err:
 +      iwl_mvm_free_nd(mvm);
 +      mutex_unlock(&mvm->mutex);
 +
 +out_iterate:
 +      if (!test)
 +              ieee80211_iterate_active_interfaces_rtnl(mvm->hw,
 +                      IEEE80211_IFACE_ITER_NORMAL,
 +                      iwl_mvm_d3_disconnect_iter, keep ? vif : NULL);
 +
 +out:
 +      /* return 1 to reconfigure the device */
 +      set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
 +      set_bit(IWL_MVM_STATUS_D3_RECONFIG, &mvm->status);
 +
 +      /* We always return 1, which causes mac80211 to do a reconfig
 +       * with IEEE80211_RECONFIG_TYPE_RESTART.  This type of
 +       * reconfig calls iwl_mvm_restart_complete(), where we unref
 +       * the IWL_MVM_REF_UCODE_DOWN, so we need to take the
 +       * reference here.
 +       */
 +      iwl_mvm_ref(mvm, IWL_MVM_REF_UCODE_DOWN);
 +      return 1;
 +}
 +
 +static int iwl_mvm_resume_d3(struct iwl_mvm *mvm)
 +{
 +      iwl_trans_resume(mvm->trans);
 +
 +      return __iwl_mvm_resume(mvm, false);
 +}
 +
 +static int iwl_mvm_resume_d0i3(struct iwl_mvm *mvm)
 +{
 +      bool exit_now;
 +      enum iwl_d3_status d3_status;
 +
 +      iwl_trans_d3_resume(mvm->trans, &d3_status, false);
 +
 +      /*
 +       * make sure to clear D0I3_DEFER_WAKEUP before
 +       * calling iwl_trans_resume(), which might wait
 +       * for d0i3 exit completion.
 +       */
 +      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);
 +      }
 +
 +      iwl_trans_resume(mvm->trans);
 +
 +      if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND) {
 +              int ret = iwl_mvm_exit_d0i3(mvm->hw->priv);
 +
 +              if (ret)
 +                      return ret;
 +              /*
 +               * d0i3 exit will be deferred until reconfig_complete.
 +               * make sure there we are out of d0i3.
 +               */
 +      }
 +      return 0;
 +}
 +
 +int iwl_mvm_resume(struct ieee80211_hw *hw)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      /* 'any' trigger means d0i3 was used */
 +      if (hw->wiphy->wowlan_config->any)
 +              return iwl_mvm_resume_d0i3(mvm);
 +      else
 +              return iwl_mvm_resume_d3(mvm);
 +}
 +
 +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      device_set_wakeup_enable(mvm->trans->dev, enabled);
 +}
 +
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +static int iwl_mvm_d3_test_open(struct inode *inode, struct file *file)
 +{
 +      struct iwl_mvm *mvm = inode->i_private;
 +      int err;
 +
 +      if (mvm->d3_test_active)
 +              return -EBUSY;
 +
 +      file->private_data = inode->i_private;
 +
 +      ieee80211_stop_queues(mvm->hw);
 +      synchronize_net();
 +
 +      /* start pseudo D3 */
 +      rtnl_lock();
 +      err = __iwl_mvm_suspend(mvm->hw, mvm->hw->wiphy->wowlan_config, true);
 +      rtnl_unlock();
 +      if (err > 0)
 +              err = -EINVAL;
 +      if (err) {
 +              ieee80211_wake_queues(mvm->hw);
 +              return err;
 +      }
 +      mvm->d3_test_active = true;
 +      mvm->keep_vif = NULL;
 +      return 0;
 +}
 +
 +static ssize_t iwl_mvm_d3_test_read(struct file *file, char __user *user_buf,
 +                                  size_t count, loff_t *ppos)
 +{
 +      struct iwl_mvm *mvm = file->private_data;
 +      u32 pme_asserted;
 +
 +      while (true) {
 +              /* read pme_ptr if available */
 +              if (mvm->d3_test_pme_ptr) {
 +                      pme_asserted = iwl_trans_read_mem32(mvm->trans,
 +                                              mvm->d3_test_pme_ptr);
 +                      if (pme_asserted)
 +                              break;
 +              }
 +
 +              if (msleep_interruptible(100))
 +                      break;
 +      }
 +
 +      return 0;
 +}
 +
 +static void iwl_mvm_d3_test_disconn_work_iter(void *_data, u8 *mac,
 +                                            struct ieee80211_vif *vif)
 +{
 +      /* skip the one we keep connection on */
 +      if (_data == vif)
 +              return;
 +
 +      if (vif->type == NL80211_IFTYPE_STATION)
 +              ieee80211_connection_loss(vif);
 +}
 +
 +static int iwl_mvm_d3_test_release(struct inode *inode, struct file *file)
 +{
 +      struct iwl_mvm *mvm = inode->i_private;
 +      int remaining_time = 10;
 +
 +      mvm->d3_test_active = false;
 +      rtnl_lock();
 +      __iwl_mvm_resume(mvm, true);
 +      rtnl_unlock();
 +      iwl_abort_notification_waits(&mvm->notif_wait);
 +      ieee80211_restart_hw(mvm->hw);
 +
 +      /* wait for restart and disconnect all interfaces */
 +      while (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
 +             remaining_time > 0) {
 +              remaining_time--;
 +              msleep(1000);
 +      }
 +
 +      if (remaining_time == 0)
 +              IWL_ERR(mvm, "Timed out waiting for HW restart to finish!\n");
 +
 +      ieee80211_iterate_active_interfaces_atomic(
 +              mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
 +              iwl_mvm_d3_test_disconn_work_iter, mvm->keep_vif);
 +
 +      ieee80211_wake_queues(mvm->hw);
 +
 +      return 0;
 +}
 +
 +const struct file_operations iwl_dbgfs_d3_test_ops = {
 +      .llseek = no_llseek,
 +      .open = iwl_mvm_d3_test_open,
 +      .read = iwl_mvm_d3_test_read,
 +      .release = iwl_mvm_d3_test_release,
 +};
 +#endif
index 1fb6846,0000000..e88afac
mode 100644,000000..100644
--- /dev/null
@@@ -1,4260 -1,0 +1,4265 @@@
-               ret = iwl_mvm_set_sta_key(mvm, vif, sta, key,
-                                         test_bit(IWL_MVM_STATUS_IN_HW_RESTART,
-                                                  &mvm->status));
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * 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
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +#include <linux/kernel.h>
 +#include <linux/slab.h>
 +#include <linux/skbuff.h>
 +#include <linux/netdevice.h>
 +#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>
 +
 +#include "iwl-op-mode.h"
 +#include "iwl-io.h"
 +#include "mvm.h"
 +#include "sta.h"
 +#include "time-event.h"
 +#include "iwl-eeprom-parse.h"
 +#include "iwl-phy-db.h"
 +#include "testmode.h"
 +#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[] = {
 +      {
 +              .max = 1,
 +              .types = BIT(NL80211_IFTYPE_STATION),
 +      },
 +      {
 +              .max = 1,
 +              .types = BIT(NL80211_IFTYPE_AP) |
 +                      BIT(NL80211_IFTYPE_P2P_CLIENT) |
 +                      BIT(NL80211_IFTYPE_P2P_GO),
 +      },
 +      {
 +              .max = 1,
 +              .types = BIT(NL80211_IFTYPE_P2P_DEVICE),
 +      },
 +};
 +
 +static const struct ieee80211_iface_combination iwl_mvm_iface_combinations[] = {
 +      {
 +              .num_different_channels = 2,
 +              .max_interfaces = 3,
 +              .limits = iwl_mvm_limits,
 +              .n_limits = ARRAY_SIZE(iwl_mvm_limits),
 +      },
 +};
 +
 +#ifdef CONFIG_PM_SLEEP
 +static const struct nl80211_wowlan_tcp_data_token_feature
 +iwl_mvm_wowlan_tcp_token_feature = {
 +      .min_len = 0,
 +      .max_len = 255,
 +      .bufsize = IWL_WOWLAN_REMOTE_WAKE_MAX_TOKENS,
 +};
 +
 +static const struct wiphy_wowlan_tcp_support iwl_mvm_wowlan_tcp_support = {
 +      .tok = &iwl_mvm_wowlan_tcp_token_feature,
 +      .data_payload_max = IWL_WOWLAN_TCP_MAX_PACKET_LEN -
 +                          sizeof(struct ethhdr) -
 +                          sizeof(struct iphdr) -
 +                          sizeof(struct tcphdr),
 +      .data_interval_max = 65535, /* __le16 in API */
 +      .wake_payload_max = IWL_WOWLAN_REMOTE_WAKE_MAX_PACKET_LEN -
 +                          sizeof(struct ethhdr) -
 +                          sizeof(struct iphdr) -
 +                          sizeof(struct tcphdr),
 +      .seq = true,
 +};
 +#endif
 +
 +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING
 +/*
 + * Use the reserved field to indicate magic values.
 + * these values will only be used internally by the driver,
 + * and won't make it to the fw (reserved will be 0).
 + * BC_FILTER_MAGIC_IP - configure the val of this attribute to
 + *    be the vif's ip address. in case there is not a single
 + *    ip address (0, or more than 1), this attribute will
 + *    be skipped.
 + * BC_FILTER_MAGIC_MAC - set the val of this attribute to
 + *    the LSB bytes of the vif's mac address
 + */
 +enum {
 +      BC_FILTER_MAGIC_NONE = 0,
 +      BC_FILTER_MAGIC_IP,
 +      BC_FILTER_MAGIC_MAC,
 +};
 +
 +static const struct iwl_fw_bcast_filter iwl_mvm_default_bcast_filters[] = {
 +      {
 +              /* arp */
 +              .discard = 0,
 +              .frame_type = BCAST_FILTER_FRAME_TYPE_ALL,
 +              .attrs = {
 +                      {
 +                              /* frame type - arp, hw type - ethernet */
 +                              .offset_type =
 +                                      BCAST_FILTER_OFFSET_PAYLOAD_START,
 +                              .offset = sizeof(rfc1042_header),
 +                              .val = cpu_to_be32(0x08060001),
 +                              .mask = cpu_to_be32(0xffffffff),
 +                      },
 +                      {
 +                              /* arp dest ip */
 +                              .offset_type =
 +                                      BCAST_FILTER_OFFSET_PAYLOAD_START,
 +                              .offset = sizeof(rfc1042_header) + 2 +
 +                                        sizeof(struct arphdr) +
 +                                        ETH_ALEN + sizeof(__be32) +
 +                                        ETH_ALEN,
 +                              .mask = cpu_to_be32(0xffffffff),
 +                              /* mark it as special field */
 +                              .reserved1 = cpu_to_le16(BC_FILTER_MAGIC_IP),
 +                      },
 +              },
 +      },
 +      {
 +              /* dhcp offer bcast */
 +              .discard = 0,
 +              .frame_type = BCAST_FILTER_FRAME_TYPE_IPV4,
 +              .attrs = {
 +                      {
 +                              /* udp dest port - 68 (bootp client)*/
 +                              .offset_type = BCAST_FILTER_OFFSET_IP_END,
 +                              .offset = offsetof(struct udphdr, dest),
 +                              .val = cpu_to_be32(0x00440000),
 +                              .mask = cpu_to_be32(0xffff0000),
 +                      },
 +                      {
 +                              /* dhcp - lsb bytes of client hw address */
 +                              .offset_type = BCAST_FILTER_OFFSET_IP_END,
 +                              .offset = 38,
 +                              .mask = cpu_to_be32(0xffffffff),
 +                              /* mark it as special field */
 +                              .reserved1 = cpu_to_le16(BC_FILTER_MAGIC_MAC),
 +                      },
 +              },
 +      },
 +      /* last filter must be empty */
 +      {},
 +};
 +#endif
 +
 +void iwl_mvm_ref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
 +{
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return;
 +
 +      IWL_DEBUG_RPM(mvm, "Take mvm reference - type %d\n", ref_type);
 +      spin_lock_bh(&mvm->refs_lock);
 +      mvm->refs[ref_type]++;
 +      spin_unlock_bh(&mvm->refs_lock);
 +      iwl_trans_ref(mvm->trans);
 +}
 +
 +void iwl_mvm_unref(struct iwl_mvm *mvm, enum iwl_mvm_ref_type ref_type)
 +{
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return;
 +
 +      IWL_DEBUG_RPM(mvm, "Leave mvm reference - type %d\n", ref_type);
 +      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 except_ref)
 +{
 +      int i, j;
 +
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return;
 +
 +      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 (%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);
 +}
 +
 +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);
 +
 +      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)
 +{
 +      int i;
 +
 +      memset(mvm->phy_ctxts, 0, sizeof(mvm->phy_ctxts));
 +      for (i = 0; i < NUM_PHY_CTX; i++) {
 +              mvm->phy_ctxts[i].id = i;
 +              mvm->phy_ctxts[i].ref = 0;
 +      }
 +}
 +
 +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_ERR_OR_ZERO(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_ERR_OR_ZERO(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;
 +      int ret;
 +      bool changed;
 +      const struct ieee80211_regdomain *r =
 +                      rtnl_dereference(mvm->hw->wiphy->regd);
 +
 +      if (!r)
 +              return -ENOENT;
 +
 +      /* 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,
 +                                   &changed);
 +      if (IS_ERR_OR_NULL(regd))
 +              return -EIO;
 +
 +      /* update cfg80211 if the regdomain was changed */
 +      if (changed)
 +              ret = regulatory_set_wiphy_regd_sync_rtnl(mvm->hw->wiphy, regd);
 +      else
 +              ret = 0;
 +
 +      kfree(regd);
 +      return ret;
 +}
 +
 +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm)
 +{
 +      struct ieee80211_hw *hw = mvm->hw;
 +      int num_mac, ret, i;
 +      static const u32 mvm_ciphers[] = {
 +              WLAN_CIPHER_SUITE_WEP40,
 +              WLAN_CIPHER_SUITE_WEP104,
 +              WLAN_CIPHER_SUITE_TKIP,
 +              WLAN_CIPHER_SUITE_CCMP,
 +      };
 +
 +      /* Tell mac80211 our characteristics */
 +      ieee80211_hw_set(hw, SIGNAL_DBM);
 +      ieee80211_hw_set(hw, SPECTRUM_MGMT);
 +      ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS);
 +      ieee80211_hw_set(hw, QUEUE_CONTROL);
 +      ieee80211_hw_set(hw, WANT_MONITOR_VIF);
 +      ieee80211_hw_set(hw, SUPPORTS_PS);
 +      ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS);
 +      ieee80211_hw_set(hw, AMPDU_AGGREGATION);
 +      ieee80211_hw_set(hw, TIMING_BEACON_ONLY);
 +      ieee80211_hw_set(hw, CONNECTION_MONITOR);
 +      ieee80211_hw_set(hw, CHANCTX_STA_CSA);
 +      ieee80211_hw_set(hw, SUPPORT_FAST_XMIT);
 +      ieee80211_hw_set(hw, SUPPORTS_CLONED_SKBS);
 +
 +      hw->queues = mvm->first_agg_queue;
 +      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 |
 +              IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED;
 +      hw->rate_control_algorithm = "iwl-mvm-rs";
 +      hw->uapsd_queues = IWL_MVM_UAPSD_QUEUES;
 +      hw->uapsd_max_sp_len = IWL_UAPSD_MAX_SP;
 +
 +      BUILD_BUG_ON(ARRAY_SIZE(mvm->ciphers) < ARRAY_SIZE(mvm_ciphers) + 2);
 +      memcpy(mvm->ciphers, mvm_ciphers, sizeof(mvm_ciphers));
 +      hw->wiphy->n_cipher_suites = ARRAY_SIZE(mvm_ciphers);
 +      hw->wiphy->cipher_suites = mvm->ciphers;
 +
 +      /*
 +       * Enable 11w if advertised by firmware and software crypto
 +       * is not enabled (as the firmware will interpret some mgmt
 +       * packets, so enabling it with software crypto isn't safe)
 +       */
 +      if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_MFP &&
 +          !iwlwifi_mod_params.sw_crypto) {
 +              ieee80211_hw_set(hw, MFP_CAPABLE);
 +              mvm->ciphers[hw->wiphy->n_cipher_suites] =
 +                      WLAN_CIPHER_SUITE_AES_CMAC;
 +              hw->wiphy->n_cipher_suites++;
 +      }
 +
 +      /* currently FW API supports only one optional cipher scheme */
 +      if (mvm->fw->cs[0].cipher) {
 +              mvm->hw->n_cipher_schemes = 1;
 +              mvm->hw->cipher_schemes = &mvm->fw->cs[0];
 +              mvm->ciphers[hw->wiphy->n_cipher_suites] =
 +                      mvm->fw->cs[0].cipher;
 +              hw->wiphy->n_cipher_suites++;
 +      }
 +
 +      ieee80211_hw_set(hw, SINGLE_SCAN_ON_ALL_BANDS);
 +      hw->wiphy->features |=
 +              NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR |
 +              NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR |
 +              NL80211_FEATURE_ND_RANDOM_MAC_ADDR;
 +
 +      hw->sta_data_size = sizeof(struct iwl_mvm_sta);
 +      hw->vif_data_size = sizeof(struct iwl_mvm_vif);
 +      hw->chanctx_data_size = sizeof(u16);
 +
 +      hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) |
 +              BIT(NL80211_IFTYPE_P2P_CLIENT) |
 +              BIT(NL80211_IFTYPE_AP) |
 +              BIT(NL80211_IFTYPE_P2P_GO) |
 +              BIT(NL80211_IFTYPE_P2P_DEVICE) |
 +              BIT(NL80211_IFTYPE_ADHOC);
 +
 +      hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN;
 +      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;
 +
 +      hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;
 +
 +      hw->wiphy->iface_combinations = iwl_mvm_iface_combinations;
 +      hw->wiphy->n_iface_combinations =
 +              ARRAY_SIZE(iwl_mvm_iface_combinations);
 +
 +      hw->wiphy->max_remain_on_channel_duration = 10000;
 +      hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL;
 +      /* we can compensate an offset of up to 3 channels = 15 MHz */
 +      hw->wiphy->max_adj_channel_rssi_comp = 3 * 5;
 +
 +      /* Extract MAC address */
 +      memcpy(mvm->addresses[0].addr, mvm->nvm_data->hw_addr, ETH_ALEN);
 +      hw->wiphy->addresses = mvm->addresses;
 +      hw->wiphy->n_addresses = 1;
 +
 +      /* Extract additional MAC addresses if available */
 +      num_mac = (mvm->nvm_data->n_hw_addrs > 1) ?
 +              min(IWL_MVM_MAX_ADDRESSES, mvm->nvm_data->n_hw_addrs) : 1;
 +
 +      for (i = 1; i < num_mac; i++) {
 +              memcpy(mvm->addresses[i].addr, mvm->addresses[i-1].addr,
 +                     ETH_ALEN);
 +              mvm->addresses[i].addr[5]++;
 +              hw->wiphy->n_addresses++;
 +      }
 +
 +      iwl_mvm_reset_phy_ctxts(mvm);
 +
 +      hw->wiphy->max_scan_ie_len = iwl_mvm_max_scan_ie_len(mvm);
 +
 +      hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX;
 +
 +      BUILD_BUG_ON(IWL_MVM_SCAN_STOPPING_MASK & IWL_MVM_SCAN_MASK);
 +      BUILD_BUG_ON(IWL_MVM_MAX_UMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK) ||
 +                   IWL_MVM_MAX_LMAC_SCANS > HWEIGHT32(IWL_MVM_SCAN_MASK));
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN))
 +              mvm->max_scans = IWL_MVM_MAX_UMAC_SCANS;
 +      else
 +              mvm->max_scans = IWL_MVM_MAX_LMAC_SCANS;
 +
 +      if (mvm->nvm_data->bands[IEEE80211_BAND_2GHZ].n_channels)
 +              hw->wiphy->bands[IEEE80211_BAND_2GHZ] =
 +                      &mvm->nvm_data->bands[IEEE80211_BAND_2GHZ];
 +      if (mvm->nvm_data->bands[IEEE80211_BAND_5GHZ].n_channels) {
 +              hw->wiphy->bands[IEEE80211_BAND_5GHZ] =
 +                      &mvm->nvm_data->bands[IEEE80211_BAND_5GHZ];
 +
 +              if (fw_has_capa(&mvm->fw->ucode_capa,
 +                              IWL_UCODE_TLV_CAPA_BEAMFORMER) &&
 +                  fw_has_api(&mvm->fw->ucode_capa,
 +                             IWL_UCODE_TLV_API_LQ_SS_PARAMS))
 +                      hw->wiphy->bands[IEEE80211_BAND_5GHZ]->vht_cap.cap |=
 +                              IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE;
 +      }
 +
 +      hw->wiphy->hw_version = mvm->trans->hw_id;
 +
 +      if (iwlmvm_mod_params.power_scheme != IWL_POWER_SCHEME_CAM)
 +              hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
 +      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;
 +      hw->wiphy->max_sched_scan_plans = IWL_MAX_SCHED_SCAN_PLANS;
 +      hw->wiphy->max_sched_scan_plan_interval = U16_MAX;
 +
 +      /*
 +       * the firmware uses u8 for num of iterations, but 0xff is saved for
 +       * infinite loop, so the maximum number of iterations is actually 254.
 +       */
 +      hw->wiphy->max_sched_scan_plan_iterations = 254;
 +
 +      hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN |
 +                             NL80211_FEATURE_LOW_PRIORITY_SCAN |
 +                             NL80211_FEATURE_P2P_GO_OPPPS |
 +                             NL80211_FEATURE_DYNAMIC_SMPS |
 +                             NL80211_FEATURE_STATIC_SMPS |
 +                             NL80211_FEATURE_SUPPORTS_WMM_ADMISSION;
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_TXPOWER_INSERTION_SUPPORT))
 +              hw->wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION;
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_QUIET_PERIOD_SUPPORT))
 +              hw->wiphy->features |= NL80211_FEATURE_QUIET;
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_DS_PARAM_SET_IE_SUPPORT))
 +              hw->wiphy->features |=
 +                      NL80211_FEATURE_DS_PARAM_SET_IE_IN_PROBES;
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      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;
 +
 +#ifdef CONFIG_PM_SLEEP
 +      if (iwl_mvm_is_d0i3_supported(mvm) &&
 +          device_can_wakeup(mvm->trans->dev)) {
 +              mvm->wowlan.flags = WIPHY_WOWLAN_ANY;
 +              hw->wiphy->wowlan = &mvm->wowlan;
 +      }
 +
 +      if (mvm->fw->img[IWL_UCODE_WOWLAN].sec[0].len &&
 +          mvm->trans->ops->d3_suspend &&
 +          mvm->trans->ops->d3_resume &&
 +          device_can_wakeup(mvm->trans->dev)) {
 +              mvm->wowlan.flags |= WIPHY_WOWLAN_MAGIC_PKT |
 +                                   WIPHY_WOWLAN_DISCONNECT |
 +                                   WIPHY_WOWLAN_EAP_IDENTITY_REQ |
 +                                   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 |
 +                                           WIPHY_WOWLAN_4WAY_HANDSHAKE;
 +
 +              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;
 +      }
 +#endif
 +
 +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING
 +      /* assign default bcast filtering configuration */
 +      mvm->bcast_filters = iwl_mvm_default_bcast_filters;
 +#endif
 +
 +      ret = iwl_mvm_leds_init(mvm);
 +      if (ret)
 +              return ret;
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_TDLS_SUPPORT)) {
 +              IWL_DEBUG_TDLS(mvm, "TDLS supported\n");
 +              hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
 +              ieee80211_hw_set(hw, TDLS_WIDER_BW);
 +      }
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_TDLS_CHANNEL_SWITCH)) {
 +              IWL_DEBUG_TDLS(mvm, "TDLS channel switch supported\n");
 +              hw->wiphy->features |= NL80211_FEATURE_TDLS_CHANNEL_SWITCH;
 +      }
 +
 +      hw->netdev_features |= mvm->cfg->features;
 +      if (!iwl_mvm_is_csum_supported(mvm))
 +              hw->netdev_features &= ~NETIF_F_RXCSUM;
 +
 +      ret = ieee80211_register_hw(mvm->hw);
 +      if (ret)
 +              iwl_mvm_leds_exit(mvm);
 +
 +      return ret;
 +}
 +
 +static bool iwl_mvm_defer_tx(struct iwl_mvm *mvm,
 +                           struct ieee80211_sta *sta,
 +                           struct sk_buff *skb)
 +{
 +      struct iwl_mvm_sta *mvmsta;
 +      bool defer = false;
 +
 +      /*
 +       * double check the IN_D0I3 flag both before and after
 +       * taking the spinlock, in order to prevent taking
 +       * the spinlock when not needed.
 +       */
 +      if (likely(!test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status)))
 +              return false;
 +
 +      spin_lock(&mvm->d0i3_tx_lock);
 +      /*
 +       * testing the flag again ensures the skb dequeue
 +       * loop (on d0i3 exit) hasn't run yet.
 +       */
 +      if (!test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status))
 +              goto out;
 +
 +      mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      if (mvmsta->sta_id == IWL_MVM_STATION_COUNT ||
 +          mvmsta->sta_id != mvm->d0i3_ap_sta_id)
 +              goto out;
 +
 +      __skb_queue_tail(&mvm->d0i3_tx, skb);
 +      ieee80211_stop_queues(mvm->hw);
 +
 +      /* trigger wakeup */
 +      iwl_mvm_ref(mvm, IWL_MVM_REF_TX);
 +      iwl_mvm_unref(mvm, IWL_MVM_REF_TX);
 +
 +      defer = true;
 +out:
 +      spin_unlock(&mvm->d0i3_tx_lock);
 +      return defer;
 +}
 +
 +static void iwl_mvm_mac_tx(struct ieee80211_hw *hw,
 +                         struct ieee80211_tx_control *control,
 +                         struct sk_buff *skb)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct ieee80211_sta *sta = control->sta;
 +      struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
 +      struct ieee80211_hdr *hdr = (void *)skb->data;
 +
 +      if (iwl_mvm_is_radio_killed(mvm)) {
 +              IWL_DEBUG_DROP(mvm, "Dropping - RF/CT KILL\n");
 +              goto drop;
 +      }
 +
 +      if (IEEE80211_SKB_CB(skb)->hw_queue == IWL_MVM_OFFCHANNEL_QUEUE &&
 +          !test_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status) &&
 +          !test_bit(IWL_MVM_STATUS_ROC_AUX_RUNNING, &mvm->status))
 +              goto drop;
 +
 +      /* treat non-bufferable MMPDUs as broadcast if sta is sleeping */
 +      if (unlikely(info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER &&
 +                   ieee80211_is_mgmt(hdr->frame_control) &&
 +                   !ieee80211_is_deauth(hdr->frame_control) &&
 +                   !ieee80211_is_disassoc(hdr->frame_control) &&
 +                   !ieee80211_is_action(hdr->frame_control)))
 +              sta = NULL;
 +
 +      if (sta) {
 +              if (iwl_mvm_defer_tx(mvm, sta, skb))
 +                      return;
 +              if (iwl_mvm_tx_skb(mvm, skb, sta))
 +                      goto drop;
 +              return;
 +      }
 +
 +      if (iwl_mvm_tx_skb_non_sta(mvm, skb))
 +              goto drop;
 +      return;
 + drop:
 +      ieee80211_free_txskb(hw, skb);
 +}
 +
 +static inline bool iwl_enable_rx_ampdu(const struct iwl_cfg *cfg)
 +{
 +      if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG)
 +              return false;
 +      return true;
 +}
 +
 +static inline bool iwl_enable_tx_ampdu(const struct iwl_cfg *cfg)
 +{
 +      if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_TXAGG)
 +              return false;
 +      if (iwlwifi_mod_params.disable_11n & IWL_ENABLE_HT_TXAGG)
 +              return true;
 +
 +      /* enabled by default */
 +      return true;
 +}
 +
 +#define CHECK_BA_TRIGGER(_mvm, _trig, _tid_bm, _tid, _fmt...) \
 +      do {                                                    \
 +              if (!(le16_to_cpu(_tid_bm) & BIT(_tid)))        \
 +                      break;                                  \
 +              iwl_mvm_fw_dbg_collect_trig(_mvm, _trig, _fmt); \
 +      } while (0)
 +
 +static void
 +iwl_mvm_ampdu_check_trigger(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid, u16 rx_ba_ssn,
 +                          enum ieee80211_ampdu_mlme_action action)
 +{
 +      struct iwl_fw_dbg_trigger_tlv *trig;
 +      struct iwl_fw_dbg_trigger_ba *ba_trig;
 +
 +      if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_BA))
 +              return;
 +
 +      trig = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_BA);
 +      ba_trig = (void *)trig->data;
 +
 +      if (!iwl_fw_dbg_trigger_check_stop(mvm, vif, trig))
 +              return;
 +
 +      switch (action) {
 +      case IEEE80211_AMPDU_TX_OPERATIONAL: {
 +              struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +              struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
 +
 +              CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_start, tid,
 +                               "TX AGG START: MAC %pM tid %d ssn %d\n",
 +                               sta->addr, tid, tid_data->ssn);
 +              break;
 +              }
 +      case IEEE80211_AMPDU_TX_STOP_CONT:
 +              CHECK_BA_TRIGGER(mvm, trig, ba_trig->tx_ba_stop, tid,
 +                               "TX AGG STOP: MAC %pM tid %d\n",
 +                               sta->addr, tid);
 +              break;
 +      case IEEE80211_AMPDU_RX_START:
 +              CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_start, tid,
 +                               "RX AGG START: MAC %pM tid %d ssn %d\n",
 +                               sta->addr, tid, rx_ba_ssn);
 +              break;
 +      case IEEE80211_AMPDU_RX_STOP:
 +              CHECK_BA_TRIGGER(mvm, trig, ba_trig->rx_ba_stop, tid,
 +                               "RX AGG STOP: MAC %pM tid %d\n",
 +                               sta->addr, tid);
 +              break;
 +      default:
 +              break;
 +      }
 +}
 +
 +static int iwl_mvm_mac_ampdu_action(struct ieee80211_hw *hw,
 +                                  struct ieee80211_vif *vif,
 +                                  enum ieee80211_ampdu_mlme_action action,
 +                                  struct ieee80211_sta *sta, u16 tid,
 +                                  u16 *ssn, u8 buf_size, bool amsdu)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
 +      bool tx_agg_ref = false;
 +
 +      IWL_DEBUG_HT(mvm, "A-MPDU action on addr %pM tid %d: action %d\n",
 +                   sta->addr, tid, action);
 +
 +      if (!(mvm->nvm_data->sku_cap_11n_enable))
 +              return -EACCES;
 +
 +      /* return from D0i3 before starting a new Tx aggregation */
 +      switch (action) {
 +      case IEEE80211_AMPDU_TX_START:
 +      case IEEE80211_AMPDU_TX_STOP_CONT:
 +      case IEEE80211_AMPDU_TX_STOP_FLUSH:
 +      case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
 +      case IEEE80211_AMPDU_TX_OPERATIONAL:
 +              /*
 +               * for tx start, wait synchronously until D0i3 exit to
 +               * get the correct sequence number for the tid.
 +               * additionally, some other ampdu actions use direct
 +               * target access, which is not handled automatically
 +               * by the trans layer (unlike commands), so wait for
 +               * d0i3 exit in these cases as well.
 +               */
 +              ret = iwl_mvm_ref_sync(mvm, IWL_MVM_REF_TX_AGG);
 +              if (ret)
 +                      return ret;
 +
 +              tx_agg_ref = true;
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      switch (action) {
 +      case IEEE80211_AMPDU_RX_START:
 +              if (!iwl_enable_rx_ampdu(mvm->cfg)) {
 +                      ret = -EINVAL;
 +                      break;
 +              }
 +              ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, *ssn, true);
 +              break;
 +      case IEEE80211_AMPDU_RX_STOP:
 +              ret = iwl_mvm_sta_rx_agg(mvm, sta, tid, 0, false);
 +              break;
 +      case IEEE80211_AMPDU_TX_START:
 +              if (!iwl_enable_tx_ampdu(mvm->cfg)) {
 +                      ret = -EINVAL;
 +                      break;
 +              }
 +              ret = iwl_mvm_sta_tx_agg_start(mvm, vif, sta, tid, ssn);
 +              break;
 +      case IEEE80211_AMPDU_TX_STOP_CONT:
 +              ret = iwl_mvm_sta_tx_agg_stop(mvm, vif, sta, tid);
 +              break;
 +      case IEEE80211_AMPDU_TX_STOP_FLUSH:
 +      case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT:
 +              ret = iwl_mvm_sta_tx_agg_flush(mvm, vif, sta, tid);
 +              break;
 +      case IEEE80211_AMPDU_TX_OPERATIONAL:
 +              ret = iwl_mvm_sta_tx_agg_oper(mvm, vif, sta, tid, buf_size);
 +              break;
 +      default:
 +              WARN_ON_ONCE(1);
 +              ret = -EINVAL;
 +              break;
 +      }
 +
 +      if (!ret) {
 +              u16 rx_ba_ssn = 0;
 +
 +              if (action == IEEE80211_AMPDU_RX_START)
 +                      rx_ba_ssn = *ssn;
 +
 +              iwl_mvm_ampdu_check_trigger(mvm, vif, sta, tid,
 +                                          rx_ba_ssn, action);
 +      }
 +      mutex_unlock(&mvm->mutex);
 +
 +      /*
 +       * If the tid is marked as started, we won't use it for offloaded
 +       * traffic on the next D0i3 entry. It's safe to unref.
 +       */
 +      if (tx_agg_ref)
 +              iwl_mvm_unref(mvm, IWL_MVM_REF_TX_AGG);
 +
 +      return ret;
 +}
 +
 +static void iwl_mvm_cleanup_iterator(void *data, u8 *mac,
 +                                   struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = data;
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      mvmvif->uploaded = false;
 +      mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT;
 +
 +      spin_lock_bh(&mvm->time_event_lock);
 +      iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data);
 +      spin_unlock_bh(&mvm->time_event_lock);
 +
 +      mvmvif->phy_ctxt = NULL;
 +      memset(&mvmvif->bf_data, 0, sizeof(mvmvif->bf_data));
 +}
 +
 +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);
 +}
 +
 +static void iwl_mvm_dump_fifos(struct iwl_mvm *mvm,
 +                             struct iwl_fw_error_dump_data **dump_data)
 +{
 +      struct iwl_fw_error_dump_fifo *fifo_hdr;
 +      u32 *fifo_data;
 +      u32 fifo_len;
 +      unsigned long flags;
 +      int i, j;
 +
 +      if (!iwl_trans_grab_nic_access(mvm->trans, false, &flags))
 +              return;
 +
 +      /* Pull RXF data from all RXFs */
 +      for (i = 0; i < ARRAY_SIZE(mvm->shared_mem_cfg.rxfifo_size); i++) {
 +              /*
 +               * Keep aside the additional offset that might be needed for
 +               * next RXF
 +               */
 +              u32 offset_diff = RXF_DIFF_FROM_PREV * i;
 +
 +              fifo_hdr = (void *)(*dump_data)->data;
 +              fifo_data = (void *)fifo_hdr->data;
 +              fifo_len = mvm->shared_mem_cfg.rxfifo_size[i];
 +
 +              /* No need to try to read the data if the length is 0 */
 +              if (fifo_len == 0)
 +                      continue;
 +
 +              /* Add a TLV for the RXF */
 +              (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_RXF);
 +              (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr));
 +
 +              fifo_hdr->fifo_num = cpu_to_le32(i);
 +              fifo_hdr->available_bytes =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      RXF_RD_D_SPACE +
 +                                                      offset_diff));
 +              fifo_hdr->wr_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      RXF_RD_WR_PTR +
 +                                                      offset_diff));
 +              fifo_hdr->rd_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      RXF_RD_RD_PTR +
 +                                                      offset_diff));
 +              fifo_hdr->fence_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      RXF_RD_FENCE_PTR +
 +                                                      offset_diff));
 +              fifo_hdr->fence_mode =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      RXF_SET_FENCE_MODE +
 +                                                      offset_diff));
 +
 +              /* Lock fence */
 +              iwl_trans_write_prph(mvm->trans,
 +                                   RXF_SET_FENCE_MODE + offset_diff, 0x1);
 +              /* Set fence pointer to the same place like WR pointer */
 +              iwl_trans_write_prph(mvm->trans,
 +                                   RXF_LD_WR2FENCE + offset_diff, 0x1);
 +              /* Set fence offset */
 +              iwl_trans_write_prph(mvm->trans,
 +                                   RXF_LD_FENCE_OFFSET_ADDR + offset_diff,
 +                                   0x0);
 +
 +              /* Read FIFO */
 +              fifo_len /= sizeof(u32); /* Size in DWORDS */
 +              for (j = 0; j < fifo_len; j++)
 +                      fifo_data[j] = iwl_trans_read_prph(mvm->trans,
 +                                                       RXF_FIFO_RD_FENCE_INC +
 +                                                       offset_diff);
 +              *dump_data = iwl_fw_error_next_data(*dump_data);
 +      }
 +
 +      /* Pull TXF data from all TXFs */
 +      for (i = 0; i < ARRAY_SIZE(mvm->shared_mem_cfg.txfifo_size); i++) {
 +              /* Mark the number of TXF we're pulling now */
 +              iwl_trans_write_prph(mvm->trans, TXF_LARC_NUM, i);
 +
 +              fifo_hdr = (void *)(*dump_data)->data;
 +              fifo_data = (void *)fifo_hdr->data;
 +              fifo_len = mvm->shared_mem_cfg.txfifo_size[i];
 +
 +              /* No need to try to read the data if the length is 0 */
 +              if (fifo_len == 0)
 +                      continue;
 +
 +              /* Add a TLV for the FIFO */
 +              (*dump_data)->type = cpu_to_le32(IWL_FW_ERROR_DUMP_TXF);
 +              (*dump_data)->len = cpu_to_le32(fifo_len + sizeof(*fifo_hdr));
 +
 +              fifo_hdr->fifo_num = cpu_to_le32(i);
 +              fifo_hdr->available_bytes =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      TXF_FIFO_ITEM_CNT));
 +              fifo_hdr->wr_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      TXF_WR_PTR));
 +              fifo_hdr->rd_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      TXF_RD_PTR));
 +              fifo_hdr->fence_ptr =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      TXF_FENCE_PTR));
 +              fifo_hdr->fence_mode =
 +                      cpu_to_le32(iwl_trans_read_prph(mvm->trans,
 +                                                      TXF_LOCK_FENCE));
 +
 +              /* Set the TXF_READ_MODIFY_ADDR to TXF_WR_PTR */
 +              iwl_trans_write_prph(mvm->trans, TXF_READ_MODIFY_ADDR,
 +                                   TXF_WR_PTR);
 +
 +              /* Dummy-read to advance the read pointer to the head */
 +              iwl_trans_read_prph(mvm->trans, TXF_READ_MODIFY_DATA);
 +
 +              /* Read FIFO */
 +              fifo_len /= sizeof(u32); /* Size in DWORDS */
 +              for (j = 0; j < fifo_len; j++)
 +                      fifo_data[j] = iwl_trans_read_prph(mvm->trans,
 +                                                        TXF_READ_MODIFY_DATA);
 +              *dump_data = iwl_fw_error_next_data(*dump_data);
 +      }
 +
 +      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;
 +}
 +
 +#define IWL8260_ICCM_OFFSET           0x44000 /* Only for B-step */
 +#define IWL8260_ICCM_LEN              0xC000 /* Only for B-step */
 +
 +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;
 +      u32 smem_len = mvm->cfg->smem_len;
 +      u32 sram2_len = mvm->cfg->dccm2_len;
 +      bool monitor_dump_only = false;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* there's no point in fw dump if the bus is dead */
 +      if (test_bit(STATUS_TRANS_DEAD, &mvm->trans->status)) {
 +              IWL_ERR(mvm, "Skip fw error dump since bus is dead\n");
 +              return;
 +      }
 +
 +      if (mvm->fw_dump_trig &&
 +          mvm->fw_dump_trig->mode & IWL_FW_DBG_TRIGGER_MONITOR_ONLY)
 +              monitor_dump_only = true;
 +
 +      fw_error_dump = kzalloc(sizeof(*fw_error_dump), GFP_KERNEL);
 +      if (!fw_error_dump)
 +              return;
 +
 +      /* SRAM - include stack CCM if driver knows the values for it */
 +      if (!mvm->cfg->dccm_offset || !mvm->cfg->dccm_len) {
 +              const struct fw_img *img;
 +
 +              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;
 +      } else {
 +              sram_ofs = mvm->cfg->dccm_offset;
 +              sram_len = mvm->cfg->dccm_len;
 +      }
 +
 +      /* reading RXF/TXF sizes */
 +      if (test_bit(STATUS_FW_ERROR, &mvm->trans->status)) {
 +              struct iwl_mvm_shared_mem_cfg *mem_cfg = &mvm->shared_mem_cfg;
 +              int i;
 +
 +              fifo_data_len = 0;
 +
 +              /* Count RXF size */
 +              for (i = 0; i < ARRAY_SIZE(mem_cfg->rxfifo_size); i++) {
 +                      if (!mem_cfg->rxfifo_size[i])
 +                              continue;
 +
 +                      /* Add header info */
 +                      fifo_data_len += mem_cfg->rxfifo_size[i] +
 +                                       sizeof(*dump_data) +
 +                                       sizeof(struct iwl_fw_error_dump_fifo);
 +              }
 +
 +              for (i = 0; i < ARRAY_SIZE(mem_cfg->txfifo_size); i++) {
 +                      if (!mem_cfg->txfifo_size[i])
 +                              continue;
 +
 +                      /* Add header info */
 +                      fifo_data_len += mem_cfg->txfifo_size[i] +
 +                                       sizeof(*dump_data) +
 +                                       sizeof(struct iwl_fw_error_dump_fifo);
 +              }
 +      }
 +
 +      file_len = sizeof(*dump_file) +
 +                 sizeof(*dump_data) * 2 +
 +                 sram_len + sizeof(*dump_mem) +
 +                 fifo_data_len +
 +                 sizeof(*dump_info);
 +
 +      /* Make room for the SMEM, if it exists */
 +      if (smem_len)
 +              file_len += sizeof(*dump_data) + sizeof(*dump_mem) + smem_len;
 +
 +      /* Make room for the secondary SRAM, if it exists */
 +      if (sram2_len)
 +              file_len += sizeof(*dump_data) + sizeof(*dump_mem) + sram2_len;
 +
 +      /* Make room for fw's virtual image pages, if it exists */
 +      if (mvm->fw->img[mvm->cur_ucode].paging_mem_size)
 +              file_len += mvm->num_of_paging_blk *
 +                      (sizeof(*dump_data) +
 +                       sizeof(struct iwl_fw_error_dump_paging) +
 +                       PAGING_BLOCK_SIZE);
 +
 +      /* If we only want a monitor dump, reset the file length */
 +      if (monitor_dump_only) {
 +              file_len = sizeof(*dump_file) + sizeof(*dump_data) +
 +                         sizeof(*dump_info);
 +      }
 +
 +      /*
 +       * In 8000 HW family B-step include the ICCM (which resides separately)
 +       */
 +      if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
 +          CSR_HW_REV_STEP(mvm->trans->hw_rev) == SILICON_B_STEP)
 +              file_len += sizeof(*dump_data) + sizeof(*dump_mem) +
 +                          IWL8260_ICCM_LEN;
 +
 +      if (mvm->fw_dump_desc)
 +              file_len += sizeof(*dump_data) + sizeof(*dump_trig) +
 +                          mvm->fw_dump_desc->len;
 +
 +      dump_file = vzalloc(file_len);
 +      if (!dump_file) {
 +              kfree(fw_error_dump);
 +              iwl_mvm_free_fw_dump_desc(mvm);
 +              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);
 +      dump_info->hw_step = cpu_to_le32(CSR_HW_REV_STEP(mvm->trans->hw_rev));
 +      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);
 +      /* We only dump the FIFOs if the FW is in error state */
 +      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);
 +      }
 +
 +      /* In case we only want monitor dump, skip to dump trasport data */
 +      if (monitor_dump_only)
 +              goto dump_trans_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;
 +      dump_mem->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_SRAM);
 +      dump_mem->offset = cpu_to_le32(sram_ofs);
 +      iwl_trans_read_mem_bytes(mvm->trans, sram_ofs, dump_mem->data,
 +                               sram_len);
 +
 +      if (smem_len) {
 +              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(smem_len + sizeof(*dump_mem));
 +              dump_mem = (void *)dump_data->data;
 +              dump_mem->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_SMEM);
 +              dump_mem->offset = cpu_to_le32(mvm->cfg->smem_offset);
 +              iwl_trans_read_mem_bytes(mvm->trans, mvm->cfg->smem_offset,
 +                                       dump_mem->data, smem_len);
 +      }
 +
 +      if (sram2_len) {
 +              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(sram2_len + sizeof(*dump_mem));
 +              dump_mem = (void *)dump_data->data;
 +              dump_mem->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_SRAM);
 +              dump_mem->offset = cpu_to_le32(mvm->cfg->dccm2_offset);
 +              iwl_trans_read_mem_bytes(mvm->trans, mvm->cfg->dccm2_offset,
 +                                       dump_mem->data, sram2_len);
 +      }
 +
 +      if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_8000 &&
 +          CSR_HW_REV_STEP(mvm->trans->hw_rev) == SILICON_B_STEP) {
 +              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(IWL8260_ICCM_LEN +
 +                                           sizeof(*dump_mem));
 +              dump_mem = (void *)dump_data->data;
 +              dump_mem->type = cpu_to_le32(IWL_FW_ERROR_DUMP_MEM_SRAM);
 +              dump_mem->offset = cpu_to_le32(IWL8260_ICCM_OFFSET);
 +              iwl_trans_read_mem_bytes(mvm->trans, IWL8260_ICCM_OFFSET,
 +                                       dump_mem->data, IWL8260_ICCM_LEN);
 +      }
 +
 +      /* Dump fw's virtual image */
 +      if (mvm->fw->img[mvm->cur_ucode].paging_mem_size) {
 +              u32 i;
 +
 +              for (i = 1; i < mvm->num_of_paging_blk + 1; i++) {
 +                      struct iwl_fw_error_dump_paging *paging;
 +                      struct page *pages =
 +                              mvm->fw_paging_db[i].fw_paging_block;
 +
 +                      dump_data = iwl_fw_error_next_data(dump_data);
 +                      dump_data->type = cpu_to_le32(IWL_FW_ERROR_DUMP_PAGING);
 +                      dump_data->len = cpu_to_le32(sizeof(*paging) +
 +                                                   PAGING_BLOCK_SIZE);
 +                      paging = (void *)dump_data->data;
 +                      paging->index = cpu_to_le32(i);
 +                      memcpy(paging->data, page_address(pages),
 +                             PAGING_BLOCK_SIZE);
 +              }
 +      }
 +
 +dump_trans_data:
 +      fw_error_dump->trans_ptr = iwl_trans_dump_data(mvm->trans,
 +                                                     mvm->fw_dump_trig);
 +      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);
 +
 +      dev_coredumpm(mvm->trans->dev, THIS_MODULE, fw_error_dump, 0,
 +                    GFP_KERNEL, iwl_mvm_read_coredump, iwl_mvm_free_coredump);
 +
 +      mvm->fw_dump_trig = NULL;
 +      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)) {
 +              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
 +       */
 +      iwl_mvm_unref_all_except(mvm, IWL_MVM_REF_UCODE_DOWN);
 +
 +      iwl_trans_stop_device(mvm->trans);
 +
 +      mvm->scan_status = 0;
 +      mvm->ps_disabled = false;
 +      mvm->calibrating = false;
 +
 +      /* just in case one was running */
 +      ieee80211_remain_on_channel_expired(mvm->hw);
 +
 +      /*
 +       * cleanup all interfaces, even inactive ones, as some might have
 +       * gone down during the HW restart
 +       */
 +      ieee80211_iterate_interfaces(mvm->hw, 0, iwl_mvm_cleanup_iterator, mvm);
 +
 +      mvm->p2p_device_vif = NULL;
 +      mvm->d0i3_ap_sta_id = IWL_MVM_STATION_COUNT;
 +
 +      iwl_mvm_reset_phy_ctxts(mvm);
 +      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));
 +      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);
 +
 +      /* clear any stale d0i3 state */
 +      clear_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status);
 +
 +      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);
 +}
 +
 +int __iwl_mvm_mac_start(struct iwl_mvm *mvm)
 +{
 +      int ret;
 +
 +      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;
 +
 +      /* Some hw restart cleanups must not hold the mutex */
 +      if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
 +              /*
 +               * Make sure we are out of d0i3. This is needed
 +               * to make sure the reference accounting is correct
 +               * (and there is no stale d0i3_exit_work).
 +               */
 +              wait_event_timeout(mvm->d0i3_exit_waitq,
 +                                 !test_bit(IWL_MVM_STATUS_IN_D0I3,
 +                                           &mvm->status),
 +                                 HZ);
 +      }
 +
 +      mutex_lock(&mvm->mutex);
 +      ret = __iwl_mvm_mac_start(mvm);
 +      mutex_unlock(&mvm->mutex);
 +
 +      return ret;
 +}
 +
 +static void iwl_mvm_restart_complete(struct iwl_mvm *mvm)
 +{
 +      int ret;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status);
 +      iwl_mvm_d0i3_enable_tx(mvm, NULL);
 +      ret = iwl_mvm_update_quotas(mvm, true, NULL);
 +      if (ret)
 +              IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n",
 +                      ret);
 +
 +      /* 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)
 +{
 +      if (!iwl_mvm_is_d0i3_supported(mvm))
 +              return;
 +
 +      if (mvm->trans->d0i3_mode == IWL_D0I3_MODE_ON_SUSPEND)
 +              if (!wait_event_timeout(mvm->d0i3_exit_waitq,
 +                                      !test_bit(IWL_MVM_STATUS_IN_D0I3,
 +                                                &mvm->status),
 +                                      HZ))
 +                      WARN_ONCE(1, "D0i3 exit on resume timed out\n");
 +}
 +
 +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);
 +
 +      /* firmware counters are obviously reset now, but we shouldn't
 +       * partially track so also clear the fw_reset_accu counters.
 +       */
 +      memset(&mvm->accu_radio_stats, 0, sizeof(mvm->accu_radio_stats));
 +
 +      /*
 +       * Disallow low power states when the FW is down by taking
 +       * the UCODE_DOWN ref. in case of ongoing hw restart the
 +       * ref is already taken, so don't take it again.
 +       */
 +      if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
 +              iwl_mvm_ref(mvm, IWL_MVM_REF_UCODE_DOWN);
 +
 +      /* async_handlers_wk is now blocked */
 +
 +      /*
 +       * The work item could be running or queued if the
 +       * ROC time event stops just as we get here.
 +       */
 +      flush_work(&mvm->roc_done_wk);
 +
 +      iwl_trans_stop_device(mvm->trans);
 +
 +      iwl_mvm_async_handlers_purge(mvm);
 +      /* 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_del_aux_sta(mvm);
 +
 +      /*
 +       * Clear IN_HW_RESTART flag when stopping the hw (as restart_complete()
 +       * won't be called in this case).
 +       * But make sure to cleanup interfaces that have gone down before/during
 +       * HW restart was requested.
 +       */
 +      if (test_and_clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
 +              ieee80211_iterate_interfaces(mvm->hw, 0,
 +                                           iwl_mvm_cleanup_iterator, mvm);
 +
 +      /* We shouldn't have any UIDs still set.  Loop over all the UIDs to
 +       * make sure there's nothing left there and warn if any is found.
 +       */
 +      if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_UMAC_SCAN)) {
 +              int i;
 +
 +              for (i = 0; i < mvm->max_scans; i++) {
 +                      if (WARN_ONCE(mvm->scan_uid_status[i],
 +                                    "UMAC scan UID %d status was not cleaned\n",
 +                                    i))
 +                              mvm->scan_uid_status[i] = 0;
 +              }
 +      }
 +
 +      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);
 +      cancel_delayed_work_sync(&mvm->fw_dump_wk);
 +      iwl_mvm_free_fw_dump_desc(mvm);
 +
 +      mutex_lock(&mvm->mutex);
 +      __iwl_mvm_mac_stop(mvm);
 +      mutex_unlock(&mvm->mutex);
 +
 +      /*
 +       * The worker might have been waiting for the mutex, let it run and
 +       * discover that its list is now empty.
 +       */
 +      cancel_work_sync(&mvm->async_handlers_wk);
 +}
 +
 +static struct iwl_mvm_phy_ctxt *iwl_mvm_get_free_phy_ctxt(struct iwl_mvm *mvm)
 +{
 +      u16 i;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      for (i = 0; i < NUM_PHY_CTX; i++)
 +              if (!mvm->phy_ctxts[i].ref)
 +                      return &mvm->phy_ctxts[i];
 +
 +      IWL_ERR(mvm, "No available PHY context\n");
 +      return NULL;
 +}
 +
 +static int iwl_mvm_set_tx_power(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                              s16 tx_power)
 +{
 +      struct iwl_dev_tx_power_cmd cmd = {
 +              .v2.set_mode = cpu_to_le32(IWL_TX_POWER_MODE_SET_MAC),
 +              .v2.mac_context_id =
 +                      cpu_to_le32(iwl_mvm_vif_from_mac80211(vif)->id),
 +              .v2.pwr_restriction = cpu_to_le16(8 * tx_power),
 +      };
 +      int len = sizeof(cmd);
 +
 +      if (tx_power == IWL_DEFAULT_MAX_TX_POWER)
 +              cmd.v2.pwr_restriction = cpu_to_le16(IWL_DEV_MAX_TX_POWER);
 +
 +      if (!fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_TX_POWER_CHAIN))
 +              len = sizeof(cmd.v2);
 +
 +      return iwl_mvm_send_cmd_pdu(mvm, REDUCE_TX_POWER_CMD, 0, len, &cmd);
 +}
 +
 +static int iwl_mvm_mac_add_interface(struct ieee80211_hw *hw,
 +                                   struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(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
 +       * 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
 +       * don't really have to check the types.
 +       */
 +
 +      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)
 +              goto out_unlock;
 +
 +      /* Counting number of interfaces is needed for legacy PM */
 +      if (vif->type != NL80211_IFTYPE_P2P_DEVICE)
 +              mvm->vif_count++;
 +
 +      /*
 +       * The AP binding flow can be done only after the beacon
 +       * template is configured (which happens only in the mac80211
 +       * start_ap() flow), and adding the broadcast station can happen
 +       * only after the binding.
 +       * In addition, since modifying the MAC before adding a bcast
 +       * station is not allowed by the FW, delay the adding of MAC context to
 +       * the point where we can also add the bcast station.
 +       * In short: there's not much we can do at this point, other than
 +       * allocating resources :)
 +       */
 +      if (vif->type == NL80211_IFTYPE_AP ||
 +          vif->type == NL80211_IFTYPE_ADHOC) {
 +              ret = iwl_mvm_alloc_bcast_sta(mvm, vif);
 +              if (ret) {
 +                      IWL_ERR(mvm, "Failed to allocate bcast sta\n");
 +                      goto out_release;
 +              }
 +
 +              iwl_mvm_vif_dbgfs_register(mvm, vif);
 +              goto out_unlock;
 +      }
 +
 +      mvmvif->features |= hw->netdev_features;
 +
 +      ret = iwl_mvm_mac_ctxt_add(mvm, vif);
 +      if (ret)
 +              goto out_release;
 +
 +      ret = iwl_mvm_power_update_mac(mvm);
 +      if (ret)
 +              goto out_remove_mac;
 +
 +      /* beacon filtering */
 +      ret = iwl_mvm_disable_beacon_filter(mvm, vif, 0);
 +      if (ret)
 +              goto out_remove_mac;
 +
 +      if (!mvm->bf_allowed_vif &&
 +          vif->type == NL80211_IFTYPE_STATION && !vif->p2p) {
 +              mvm->bf_allowed_vif = mvmvif;
 +              vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER |
 +                                   IEEE80211_VIF_SUPPORTS_CQM_RSSI;
 +      }
 +
 +      /*
 +       * P2P_DEVICE interface does not have a channel context assigned to it,
 +       * so a dedicated PHY context is allocated to it and the corresponding
 +       * MAC context is bound to it at this stage.
 +       */
 +      if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {
 +
 +              mvmvif->phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm);
 +              if (!mvmvif->phy_ctxt) {
 +                      ret = -ENOSPC;
 +                      goto out_free_bf;
 +              }
 +
 +              iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt);
 +              ret = iwl_mvm_binding_add_vif(mvm, vif);
 +              if (ret)
 +                      goto out_unref_phy;
 +
 +              ret = iwl_mvm_add_bcast_sta(mvm, vif);
 +              if (ret)
 +                      goto out_unbind;
 +
 +              /* Save a pointer to p2p device vif, so it can later be used to
 +               * update the p2p device MAC when a GO is started/stopped */
 +              mvm->p2p_device_vif = vif;
 +      }
 +
 +      iwl_mvm_vif_dbgfs_register(mvm, vif);
 +      goto out_unlock;
 +
 + out_unbind:
 +      iwl_mvm_binding_remove_vif(mvm, vif);
 + out_unref_phy:
 +      iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt);
 + out_free_bf:
 +      if (mvm->bf_allowed_vif == mvmvif) {
 +              mvm->bf_allowed_vif = NULL;
 +              vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER |
 +                                     IEEE80211_VIF_SUPPORTS_CQM_RSSI);
 +      }
 + out_remove_mac:
 +      mvmvif->phy_ctxt = NULL;
 +      iwl_mvm_mac_ctxt_remove(mvm, vif);
 + out_release:
 +      if (vif->type != NL80211_IFTYPE_P2P_DEVICE)
 +              mvm->vif_count--;
 +
 +      iwl_mvm_mac_ctxt_release(mvm, vif);
 + 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 = iwl_mvm_mac_get_queues_mask(vif);
 +
 +      if (tfd_msk) {
 +              /*
 +               * mac80211 first removes all the stations of the vif and
 +               * then removes the vif. When it removes a station it also
 +               * flushes the AMPDU session. So by now, all the AMPDU sessions
 +               * of all the stations of this vif are closed, and the queues
 +               * of these AMPDU sessions are properly closed.
 +               * We still need to take care of the shared queues of the vif.
 +               * Flush them here.
 +               */
 +              mutex_lock(&mvm->mutex);
 +              iwl_mvm_flush_tx_path(mvm, tfd_msk, 0);
 +              mutex_unlock(&mvm->mutex);
 +
 +              /*
 +               * There are transports that buffer a few frames in the host.
 +               * For these, the flush above isn't enough since while we were
 +               * flushing, the transport might have sent more frames to the
 +               * device. To solve this, wait here until the transport is
 +               * empty. Technically, this could have replaced the flush
 +               * above, but flush is much faster than draining. So flush
 +               * first, and drain to make sure we have no frames in the
 +               * transport anymore.
 +               * If a station still had frames on the shared queues, it is
 +               * already marked as draining, so to complete the draining, we
 +               * just need to wait until the transport is empty.
 +               */
 +              iwl_trans_wait_tx_queue_empty(mvm->trans, tfd_msk);
 +      }
 +
 +      if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {
 +              /*
 +               * Flush the ROC worker which will flush the OFFCHANNEL queue.
 +               * We assume here that all the packets sent to the OFFCHANNEL
 +               * queue are sent in ROC session.
 +               */
 +              flush_work(&mvm->roc_done_wk);
 +      } else {
 +              /*
 +               * By now, all the AC queues are empty. The AGG queues are
 +               * empty too. We already got all the Tx responses for all the
 +               * packets in the queues. The drain work can have been
 +               * triggered. Flush it.
 +               */
 +              flush_work(&mvm->sta_drained_wk);
 +      }
 +}
 +
 +static void iwl_mvm_mac_remove_interface(struct ieee80211_hw *hw,
 +                                       struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      iwl_mvm_prepare_mac_removal(mvm, vif);
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      if (mvm->bf_allowed_vif == mvmvif) {
 +              mvm->bf_allowed_vif = NULL;
 +              vif->driver_flags &= ~(IEEE80211_VIF_BEACON_FILTER |
 +                                     IEEE80211_VIF_SUPPORTS_CQM_RSSI);
 +      }
 +
 +      iwl_mvm_vif_dbgfs_clean(mvm, vif);
 +
 +      /*
 +       * For AP/GO interface, the tear down of the resources allocated to the
 +       * interface is be handled as part of the stop_ap flow.
 +       */
 +      if (vif->type == NL80211_IFTYPE_AP ||
 +          vif->type == NL80211_IFTYPE_ADHOC) {
 +#ifdef CONFIG_NL80211_TESTMODE
 +              if (vif == mvm->noa_vif) {
 +                      mvm->noa_vif = NULL;
 +                      mvm->noa_duration = 0;
 +              }
 +#endif
 +              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, vif);
 +              iwl_mvm_binding_remove_vif(mvm, vif);
 +              iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt);
 +              mvmvif->phy_ctxt = NULL;
 +      }
 +
 +      if (mvm->vif_count && vif->type != NL80211_IFTYPE_P2P_DEVICE)
 +              mvm->vif_count--;
 +
 +      iwl_mvm_power_update_mac(mvm);
 +      iwl_mvm_mac_ctxt_remove(mvm, vif);
 +
 +out_release:
 +      iwl_mvm_mac_ctxt_release(mvm, vif);
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static int iwl_mvm_mac_config(struct ieee80211_hw *hw, u32 changed)
 +{
 +      return 0;
 +}
 +
 +struct iwl_mvm_mc_iter_data {
 +      struct iwl_mvm *mvm;
 +      int port_id;
 +};
 +
 +static void iwl_mvm_mc_iface_iterator(void *_data, u8 *mac,
 +                                    struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_mc_iter_data *data = _data;
 +      struct iwl_mvm *mvm = data->mvm;
 +      struct iwl_mcast_filter_cmd *cmd = mvm->mcast_filter_cmd;
 +      int ret, len;
 +
 +      /* if we don't have free ports, mcast frames will be dropped */
 +      if (WARN_ON_ONCE(data->port_id >= MAX_PORT_ID_NUM))
 +              return;
 +
 +      if (vif->type != NL80211_IFTYPE_STATION ||
 +          !vif->bss_conf.assoc)
 +              return;
 +
 +      cmd->port_id = data->port_id++;
 +      memcpy(cmd->bssid, vif->bss_conf.bssid, ETH_ALEN);
 +      len = roundup(sizeof(*cmd) + cmd->count * ETH_ALEN, 4);
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, MCAST_FILTER_CMD, CMD_ASYNC, len, cmd);
 +      if (ret)
 +              IWL_ERR(mvm, "mcast filter cmd error. ret=%d\n", ret);
 +}
 +
 +static void iwl_mvm_recalc_multicast(struct iwl_mvm *mvm)
 +{
 +      struct iwl_mvm_mc_iter_data iter_data = {
 +              .mvm = mvm,
 +      };
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (WARN_ON_ONCE(!mvm->mcast_filter_cmd))
 +              return;
 +
 +      ieee80211_iterate_active_interfaces_atomic(
 +              mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
 +              iwl_mvm_mc_iface_iterator, &iter_data);
 +}
 +
 +static u64 iwl_mvm_prepare_multicast(struct ieee80211_hw *hw,
 +                                   struct netdev_hw_addr_list *mc_list)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mcast_filter_cmd *cmd;
 +      struct netdev_hw_addr *addr;
 +      int addr_count;
 +      bool pass_all;
 +      int len;
 +
 +      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);
 +      if (!cmd)
 +              return 0;
 +
 +      if (pass_all) {
 +              cmd->pass_all = 1;
 +              return (u64)(unsigned long)cmd;
 +      }
 +
 +      netdev_hw_addr_list_for_each(addr, mc_list) {
 +              IWL_DEBUG_MAC80211(mvm, "mcast addr (%d): %pM\n",
 +                                 cmd->count, addr->addr);
 +              memcpy(&cmd->addr_list[cmd->count * ETH_ALEN],
 +                     addr->addr, ETH_ALEN);
 +              cmd->count++;
 +      }
 +
 +      return (u64)(unsigned long)cmd;
 +}
 +
 +static void iwl_mvm_configure_filter(struct ieee80211_hw *hw,
 +                                   unsigned int changed_flags,
 +                                   unsigned int *total_flags,
 +                                   u64 multicast)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mcast_filter_cmd *cmd = (void *)(unsigned long)multicast;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      /* replace previous configuration */
 +      kfree(mvm->mcast_filter_cmd);
 +      mvm->mcast_filter_cmd = cmd;
 +
 +      if (!cmd)
 +              goto out;
 +
 +      iwl_mvm_recalc_multicast(mvm);
 +out:
 +      mutex_unlock(&mvm->mutex);
 +      *total_flags = 0;
 +}
 +
 +static void iwl_mvm_config_iface_filter(struct ieee80211_hw *hw,
 +                                      struct ieee80211_vif *vif,
 +                                      unsigned int filter_flags,
 +                                      unsigned int changed_flags)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      /* We support only filter for probe requests */
 +      if (!(changed_flags & FIF_PROBE_REQ))
 +              return;
 +
 +      /* Supported only for p2p client interfaces */
 +      if (vif->type != NL80211_IFTYPE_STATION || !vif->bss_conf.assoc ||
 +          !vif->p2p)
 +              return;
 +
 +      mutex_lock(&mvm->mutex);
 +      iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +#ifdef CONFIG_IWLWIFI_BCAST_FILTERING
 +struct iwl_bcast_iter_data {
 +      struct iwl_mvm *mvm;
 +      struct iwl_bcast_filter_cmd *cmd;
 +      u8 current_filter;
 +};
 +
 +static void
 +iwl_mvm_set_bcast_filter(struct ieee80211_vif *vif,
 +                       const struct iwl_fw_bcast_filter *in_filter,
 +                       struct iwl_fw_bcast_filter *out_filter)
 +{
 +      struct iwl_fw_bcast_filter_attr *attr;
 +      int i;
 +
 +      memcpy(out_filter, in_filter, sizeof(*out_filter));
 +
 +      for (i = 0; i < ARRAY_SIZE(out_filter->attrs); i++) {
 +              attr = &out_filter->attrs[i];
 +
 +              if (!attr->mask)
 +                      break;
 +
 +              switch (attr->reserved1) {
 +              case cpu_to_le16(BC_FILTER_MAGIC_IP):
 +                      if (vif->bss_conf.arp_addr_cnt != 1) {
 +                              attr->mask = 0;
 +                              continue;
 +                      }
 +
 +                      attr->val = vif->bss_conf.arp_addr_list[0];
 +                      break;
 +              case cpu_to_le16(BC_FILTER_MAGIC_MAC):
 +                      attr->val = *(__be32 *)&vif->addr[2];
 +                      break;
 +              default:
 +                      break;
 +              }
 +              attr->reserved1 = 0;
 +              out_filter->num_attrs++;
 +      }
 +}
 +
 +static void iwl_mvm_bcast_filter_iterator(void *_data, u8 *mac,
 +                                        struct ieee80211_vif *vif)
 +{
 +      struct iwl_bcast_iter_data *data = _data;
 +      struct iwl_mvm *mvm = data->mvm;
 +      struct iwl_bcast_filter_cmd *cmd = data->cmd;
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_fw_bcast_mac *bcast_mac;
 +      int i;
 +
 +      if (WARN_ON(mvmvif->id >= ARRAY_SIZE(cmd->macs)))
 +              return;
 +
 +      bcast_mac = &cmd->macs[mvmvif->id];
 +
 +      /*
 +       * enable filtering only for associated stations, but not for P2P
 +       * Clients
 +       */
 +      if (vif->type != NL80211_IFTYPE_STATION || vif->p2p ||
 +          !vif->bss_conf.assoc)
 +              return;
 +
 +      bcast_mac->default_discard = 1;
 +
 +      /* copy all configured filters */
 +      for (i = 0; mvm->bcast_filters[i].attrs[0].mask; i++) {
 +              /*
 +               * Make sure we don't exceed our filters limit.
 +               * if there is still a valid filter to be configured,
 +               * be on the safe side and just allow bcast for this mac.
 +               */
 +              if (WARN_ON_ONCE(data->current_filter >=
 +                               ARRAY_SIZE(cmd->filters))) {
 +                      bcast_mac->default_discard = 0;
 +                      bcast_mac->attached_filters = 0;
 +                      break;
 +              }
 +
 +              iwl_mvm_set_bcast_filter(vif,
 +                                       &mvm->bcast_filters[i],
 +                                       &cmd->filters[data->current_filter]);
 +
 +              /* skip current filter if it contains no attributes */
 +              if (!cmd->filters[data->current_filter].num_attrs)
 +                      continue;
 +
 +              /* attach the filter to current mac */
 +              bcast_mac->attached_filters |=
 +                              cpu_to_le16(BIT(data->current_filter));
 +
 +              data->current_filter++;
 +      }
 +}
 +
 +bool iwl_mvm_bcast_filter_build_cmd(struct iwl_mvm *mvm,
 +                                  struct iwl_bcast_filter_cmd *cmd)
 +{
 +      struct iwl_bcast_iter_data iter_data = {
 +              .mvm = mvm,
 +              .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);
 +
 +#ifdef CONFIG_IWLWIFI_DEBUGFS
 +      /* use debugfs filters/macs if override is configured */
 +      if (mvm->dbgfs_bcast_filtering.override) {
 +              memcpy(cmd->filters, &mvm->dbgfs_bcast_filtering.cmd.filters,
 +                     sizeof(cmd->filters));
 +              memcpy(cmd->macs, &mvm->dbgfs_bcast_filtering.cmd.macs,
 +                     sizeof(cmd->macs));
 +              return true;
 +      }
 +#endif
 +
 +      /* if no filters are configured, do nothing */
 +      if (!mvm->bcast_filters)
 +              return false;
 +
 +      /* configure and attach these filters for each associated sta vif */
 +      ieee80211_iterate_active_interfaces(
 +              mvm->hw, IEEE80211_IFACE_ITER_NORMAL,
 +              iwl_mvm_bcast_filter_iterator, &iter_data);
 +
 +      return true;
 +}
 +static int iwl_mvm_configure_bcast_filter(struct iwl_mvm *mvm,
 +                                        struct ieee80211_vif *vif)
 +{
 +      struct iwl_bcast_filter_cmd cmd;
 +
 +      if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_BCAST_FILTERING))
 +              return 0;
 +
 +      if (!iwl_mvm_bcast_filter_build_cmd(mvm, &cmd))
 +              return 0;
 +
 +      return iwl_mvm_send_cmd_pdu(mvm, BCAST_FILTER_CMD, 0,
 +                                  sizeof(cmd), &cmd);
 +}
 +#else
 +static inline int iwl_mvm_configure_bcast_filter(struct iwl_mvm *mvm,
 +                                               struct ieee80211_vif *vif)
 +{
 +      return 0;
 +}
 +#endif
 +
 +static void iwl_mvm_bss_info_changed_station(struct iwl_mvm *mvm,
 +                                           struct ieee80211_vif *vif,
 +                                           struct ieee80211_bss_conf *bss_conf,
 +                                           u32 changes)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      int ret;
 +
 +      /*
 +       * Re-calculate the tsf id, as the master-slave relations depend on the
 +       * beacon interval, which was not known when the station interface was
 +       * added.
 +       */
 +      if (changes & BSS_CHANGED_ASSOC && bss_conf->assoc)
 +              iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif);
 +
 +      /*
 +       * 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) {
 +                      /* 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, true, NULL);
 +                      if (ret) {
 +                              IWL_ERR(mvm, "failed to update quotas\n");
 +                              return;
 +                      }
 +
 +                      if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART,
 +                                   &mvm->status)) {
 +                              /*
 +                               * If we're restarting then the firmware will
 +                               * obviously have lost synchronisation with
 +                               * the AP. It will attempt to synchronise by
 +                               * itself, but we can make it more reliable by
 +                               * scheduling a session protection time event.
 +                               *
 +                               * The firmware needs to receive a beacon to
 +                               * catch up with synchronisation, use 110% of
 +                               * the beacon interval.
 +                               *
 +                               * Set a large maximum delay to allow for more
 +                               * than a single interface.
 +                               */
 +                              u32 dur = (11 * vif->bss_conf.beacon_int) / 10;
 +                              iwl_mvm_protect_session(mvm, vif, dur, dur,
 +                                                      5 * dur, false);
 +                      }
 +
 +                      iwl_mvm_sf_update(mvm, vif, false);
 +                      iwl_mvm_power_vif_assoc(mvm, vif);
 +                      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
 +                       * mode while disassociated - which is forbidden.
 +                       */
 +                      WARN_ONCE(iwl_mvm_sf_update(mvm, vif, false),
 +                                "Failed to update SF upon disassociation\n");
 +
 +                      /* remove AP station now that the MAC is unassoc */
 +                      ret = iwl_mvm_rm_sta_id(mvm, vif, mvmvif->ap_sta_id);
 +                      if (ret)
 +                              IWL_ERR(mvm, "failed to remove AP station\n");
 +
 +                      if (mvm->d0i3_ap_sta_id == mvmvif->ap_sta_id)
 +                              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, false, NULL);
 +                      if (ret)
 +                              IWL_ERR(mvm, "failed to update quotas\n");
 +
 +                      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);
 +              iwl_mvm_configure_bcast_filter(mvm, vif);
 +
 +              /* reset rssi values */
 +              mvmvif->bf_data.ave_beacon_signal = 0;
 +
 +              iwl_mvm_bt_coex_vif_change(mvm);
 +              iwl_mvm_update_smps(mvm, vif, IWL_MVM_SMPS_REQ_TT,
 +                                  IEEE80211_SMPS_AUTOMATIC);
 +      } else if (changes & BSS_CHANGED_BEACON_INFO) {
 +              /*
 +               * We received a beacon _after_ association so
 +               * remove the session protection.
 +               */
 +              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));
 +      }
 +
 +      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);
 +              iwl_mvm_set_tx_power(mvm, vif, bss_conf->txpower);
 +      }
 +
 +      if (changes & BSS_CHANGED_CQM) {
 +              IWL_DEBUG_MAC80211(mvm, "cqm info_changed\n");
 +              /* reset cqm events tracking */
 +              mvmvif->bf_data.last_cqm_event = 0;
 +              if (mvmvif->bf_data.bf_enabled) {
 +                      ret = iwl_mvm_enable_beacon_filter(mvm, vif, 0);
 +                      if (ret)
 +                              IWL_ERR(mvm,
 +                                      "failed to update CQM thresholds\n");
 +              }
 +      }
 +
 +      if (changes & BSS_CHANGED_ARP_FILTER) {
 +              IWL_DEBUG_MAC80211(mvm, "arp filter changed\n");
 +              iwl_mvm_configure_bcast_filter(mvm, vif);
 +      }
 +}
 +
 +static int iwl_mvm_start_ap_ibss(struct ieee80211_hw *hw,
 +                               struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(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 */
 +      ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif);
 +      if (ret)
 +              goto out_unlock;
 +
 +      /*
 +       * Re-calculate the tsf id, as the master-slave relations depend on the
 +       * beacon interval, which was not known when the AP interface was added.
 +       */
 +      if (vif->type == NL80211_IFTYPE_AP)
 +              iwl_mvm_mac_ctxt_recalc_tsf_id(mvm, vif);
 +
 +      mvmvif->ap_assoc_sta_count = 0;
 +
 +      /* Add the mac context */
 +      ret = iwl_mvm_mac_ctxt_add(mvm, vif);
 +      if (ret)
 +              goto out_unlock;
 +
 +      /* Perform the binding */
 +      ret = iwl_mvm_binding_add_vif(mvm, vif);
 +      if (ret)
 +              goto out_remove;
 +
 +      /* Send the bcast station. At this stage the TBTT and DTIM time events
 +       * are added and applied to the scheduler */
 +      ret = iwl_mvm_send_add_bcast_sta(mvm, vif);
 +      if (ret)
 +              goto out_unbind;
 +
 +      /* must be set before quota calculations */
 +      mvmvif->ap_ibss_active = true;
 +
 +      /* power updated needs to be done before quotas */
 +      iwl_mvm_power_update_mac(mvm);
 +
 +      ret = iwl_mvm_update_quotas(mvm, false, 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, 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);
 +
 +      goto out_unlock;
 +
 +out_quota_failed:
 +      iwl_mvm_power_update_mac(mvm);
 +      mvmvif->ap_ibss_active = false;
 +      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;
 +}
 +
 +static void iwl_mvm_stop_ap_ibss(struct ieee80211_hw *hw,
 +                               struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      iwl_mvm_prepare_mac_removal(mvm, vif);
 +
 +      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);
 +              mvmvif->csa_countdown = false;
 +      }
 +
 +      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);
 +
 +      iwl_mvm_unref(mvm, IWL_MVM_REF_AP_IBSS);
 +
 +      /* 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, NULL);
 +
 +      iwl_mvm_update_quotas(mvm, false, NULL);
 +      iwl_mvm_send_rm_bcast_sta(mvm, vif);
 +      iwl_mvm_binding_remove_vif(mvm, vif);
 +
 +      iwl_mvm_power_update_mac(mvm);
 +
 +      iwl_mvm_mac_ctxt_remove(mvm, vif);
 +
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static void
 +iwl_mvm_bss_info_changed_ap_ibss(struct iwl_mvm *mvm,
 +                               struct ieee80211_vif *vif,
 +                               struct ieee80211_bss_conf *bss_conf,
 +                               u32 changes)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      /* Changes will be applied when the AP/IBSS is started */
 +      if (!mvmvif->ap_ibss_active)
 +              return;
 +
 +      if (changes & (BSS_CHANGED_ERP_CTS_PROT | BSS_CHANGED_HT |
 +                     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 */
 +      if (changes & BSS_CHANGED_BEACON &&
 +          iwl_mvm_mac_ctxt_beacon_changed(mvm, vif))
 +              IWL_WARN(mvm, "Failed updating beacon data\n");
 +
 +      if (changes & BSS_CHANGED_TXPOWER) {
 +              IWL_DEBUG_CALIB(mvm, "Changing TX Power to %d\n",
 +                              bss_conf->txpower);
 +              iwl_mvm_set_tx_power(mvm, vif, bss_conf->txpower);
 +      }
 +
 +}
 +
 +static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw,
 +                                   struct ieee80211_vif *vif,
 +                                   struct ieee80211_bss_conf *bss_conf,
 +                                   u32 changes)
 +{
 +      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_scan_stop(mvm, IWL_MVM_SCAN_SCHED, true);
 +
 +      switch (vif->type) {
 +      case NL80211_IFTYPE_STATION:
 +              iwl_mvm_bss_info_changed_station(mvm, vif, bss_conf, changes);
 +              break;
 +      case NL80211_IFTYPE_AP:
 +      case NL80211_IFTYPE_ADHOC:
 +              iwl_mvm_bss_info_changed_ap_ibss(mvm, vif, bss_conf, changes);
 +              break;
 +      default:
 +              /* shouldn't happen */
 +              WARN_ON_ONCE(1);
 +      }
 +
 +      mutex_unlock(&mvm->mutex);
 +      iwl_mvm_unref(mvm, IWL_MVM_REF_BSS_CHANGED);
 +}
 +
 +static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw,
 +                             struct ieee80211_vif *vif,
 +                             struct ieee80211_scan_request *hw_req)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
 +
 +      if (hw_req->req.n_channels == 0 ||
 +          hw_req->req.n_channels > mvm->fw->ucode_capa.n_scan_channels)
 +              return -EINVAL;
 +
 +      mutex_lock(&mvm->mutex);
 +      ret = iwl_mvm_reg_scan_start(mvm, vif, &hw_req->req, &hw_req->ies);
 +      mutex_unlock(&mvm->mutex);
 +
 +      return ret;
 +}
 +
 +static void iwl_mvm_mac_cancel_hw_scan(struct ieee80211_hw *hw,
 +                                     struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      /* 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.
 +      */
 +      if (mvm->scan_status & IWL_MVM_SCAN_REGULAR)
 +              iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_REGULAR, true);
 +
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static void
 +iwl_mvm_mac_allow_buffered_frames(struct ieee80211_hw *hw,
 +                                struct ieee80211_sta *sta, u16 tids,
 +                                int num_frames,
 +                                enum ieee80211_frame_release_type reason,
 +                                bool more_data)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      /* Called when we need to transmit (a) frame(s) from mac80211 */
 +
 +      iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames,
 +                                        tids, more_data, false);
 +}
 +
 +static void
 +iwl_mvm_mac_release_buffered_frames(struct ieee80211_hw *hw,
 +                                  struct ieee80211_sta *sta, u16 tids,
 +                                  int num_frames,
 +                                  enum ieee80211_frame_release_type reason,
 +                                  bool more_data)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      /* Called when we need to transmit (a) frame(s) from agg queue */
 +
 +      iwl_mvm_sta_modify_sleep_tx_count(mvm, sta, reason, num_frames,
 +                                        tids, more_data, true);
 +}
 +
 +static void iwl_mvm_mac_sta_notify(struct ieee80211_hw *hw,
 +                                 struct ieee80211_vif *vif,
 +                                 enum sta_notify_cmd cmd,
 +                                 struct ieee80211_sta *sta)
 +{
 +      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);
 +
 +              for_each_set_bit(tid, &tids, IWL_MAX_TID_COUNT)
 +                      ieee80211_sta_set_buffered(sta, tid, true);
 +
 +              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
 +               * send a Tx response with TX_STATUS_FAIL_DEST_PS.
 +               */
 +              break;
 +      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,
 +                                     struct ieee80211_vif *vif,
 +                                     struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +
 +      /*
 +       * This is called before mac80211 does RCU synchronisation,
 +       * so here we already invalidate our internal RCU-protected
 +       * station pointer. The rest of the code will thus no longer
 +       * be able to find the station this way, and we don't rely
 +       * on further RCU synchronisation after the sta_state()
 +       * callback deleted the station.
 +       */
 +      mutex_lock(&mvm->mutex);
 +      if (sta == rcu_access_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id]))
 +              rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id],
 +                                 ERR_PTR(-ENOENT));
 +
 +      if (mvm_sta->vif->type == NL80211_IFTYPE_AP) {
 +              mvmvif->ap_assoc_sta_count--;
 +              iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +      }
 +
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static void iwl_mvm_check_uapsd(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                              const u8 *bssid)
 +{
 +      if (!(mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_UAPSD_SUPPORT))
 +              return;
 +
 +      if (iwlwifi_mod_params.uapsd_disable) {
 +              vif->driver_flags &= ~IEEE80211_VIF_SUPPORTS_UAPSD;
 +              return;
 +      }
 +
 +      vif->driver_flags |= IEEE80211_VIF_SUPPORTS_UAPSD;
 +}
 +
 +static int iwl_mvm_mac_sta_state(struct ieee80211_hw *hw,
 +                               struct ieee80211_vif *vif,
 +                               struct ieee80211_sta *sta,
 +                               enum ieee80211_sta_state old_state,
 +                               enum ieee80211_sta_state new_state)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      int ret;
 +
 +      IWL_DEBUG_MAC80211(mvm, "station %pM state change %d->%d\n",
 +                         sta->addr, old_state, new_state);
 +
 +      /* this would be a mac80211 bug ... but don't crash */
 +      if (WARN_ON_ONCE(!mvmvif->phy_ctxt))
 +              return -EINVAL;
 +
 +      /* if a STA is being removed, reuse its ID */
 +      flush_work(&mvm->sta_drained_wk);
 +
 +      mutex_lock(&mvm->mutex);
 +      if (old_state == IEEE80211_STA_NOTEXIST &&
 +          new_state == IEEE80211_STA_NONE) {
 +              /*
 +               * Firmware bug - it'll crash if the beacon interval is less
 +               * than 16. We can't avoid connecting at all, so refuse the
 +               * station state change, this will cause mac80211 to abandon
 +               * attempts to connect to this AP, and eventually wpa_s will
 +               * blacklist the AP...
 +               */
 +              if (vif->type == NL80211_IFTYPE_STATION &&
 +                  vif->bss_conf.beacon_int < 16) {
 +                      IWL_ERR(mvm,
 +                              "AP %pM beacon interval is %d, refusing due to firmware bug!\n",
 +                              sta->addr, vif->bss_conf.beacon_int);
 +                      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) {
 +              /*
 +               * EBS may be disabled due to previous failures reported by FW.
 +               * Reset EBS status here assuming environment has been changed.
 +               */
 +              mvm->last_ebs_successful = true;
 +              iwl_mvm_check_uapsd(mvm, vif, sta->addr);
 +              ret = 0;
 +      } else if (old_state == IEEE80211_STA_AUTH &&
 +                 new_state == IEEE80211_STA_ASSOC) {
 +              ret = iwl_mvm_update_sta(mvm, vif, sta);
 +              if (ret == 0)
 +                      iwl_mvm_rs_rate_init(mvm, sta,
 +                                           mvmvif->phy_ctxt->channel->band,
 +                                           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;
 +      } else if (old_state == IEEE80211_STA_AUTHORIZED &&
 +                 new_state == IEEE80211_STA_ASSOC) {
 +              /* disable beacon filtering */
 +              WARN_ON(iwl_mvm_disable_beacon_filter(mvm, vif, 0));
 +              ret = 0;
 +      } else if (old_state == IEEE80211_STA_ASSOC &&
 +                 new_state == IEEE80211_STA_AUTH) {
 +              ret = 0;
 +      } else if (old_state == IEEE80211_STA_AUTH &&
 +                 new_state == IEEE80211_STA_NONE) {
 +              ret = 0;
 +      } 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;
 +      }
 + 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;
 +}
 +
 +static int iwl_mvm_mac_set_rts_threshold(struct ieee80211_hw *hw, u32 value)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      mvm->rts_threshold = value;
 +
 +      return 0;
 +}
 +
 +static void iwl_mvm_sta_rc_update(struct ieee80211_hw *hw,
 +                                struct ieee80211_vif *vif,
 +                                struct ieee80211_sta *sta, u32 changed)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      if (vif->type == NL80211_IFTYPE_STATION &&
 +          changed & IEEE80211_RC_NSS_CHANGED)
 +              iwl_mvm_sf_update(mvm, vif, false);
 +}
 +
 +static int iwl_mvm_mac_conf_tx(struct ieee80211_hw *hw,
 +                             struct ieee80211_vif *vif, u16 ac,
 +                             const struct ieee80211_tx_queue_params *params)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      mvmvif->queue_params[ac] = *params;
 +
 +      /*
 +       * No need to update right away, we'll get BSS_CHANGED_QOS
 +       * The exception is P2P_DEVICE interface which needs immediate update.
 +       */
 +      if (vif->type == NL80211_IFTYPE_P2P_DEVICE) {
 +              int ret;
 +
 +              mutex_lock(&mvm->mutex);
 +              ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +              mutex_unlock(&mvm->mutex);
 +              return ret;
 +      }
 +      return 0;
 +}
 +
 +static void iwl_mvm_mac_mgd_prepare_tx(struct ieee80211_hw *hw,
 +                                    struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      u32 duration = min(IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS,
 +                         200 + vif->bss_conf.beacon_int);
 +      u32 min_duration = min(IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS,
 +                             100 + vif->bss_conf.beacon_int);
 +
 +      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, 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_scan_ies *ies)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      int ret;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      if (!vif->bss_conf.idle) {
 +              ret = -EBUSY;
 +              goto out;
 +      }
 +
 +      ret = iwl_mvm_sched_scan_start(mvm, vif, req, ies, IWL_MVM_SCAN_SCHED);
 +
 +out:
 +      mutex_unlock(&mvm->mutex);
 +      return ret;
 +}
 +
 +static int iwl_mvm_mac_sched_scan_stop(struct ieee80211_hw *hw,
 +                                     struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(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.
 +      */
 +      if (!(mvm->scan_status & IWL_MVM_SCAN_SCHED)) {
 +              mutex_unlock(&mvm->mutex);
 +              return 0;
 +      }
 +
 +      ret = iwl_mvm_scan_stop(mvm, IWL_MVM_SCAN_SCHED, 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,
 +                             enum set_key_cmd cmd,
 +                             struct ieee80211_vif *vif,
 +                             struct ieee80211_sta *sta,
 +                             struct ieee80211_key_conf *key)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
++      u8 key_offset;
 +
 +      if (iwlwifi_mod_params.sw_crypto) {
 +              IWL_DEBUG_MAC80211(mvm, "leave - hwcrypto disabled\n");
 +              return -EOPNOTSUPP;
 +      }
 +
 +      switch (key->cipher) {
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
 +              key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
 +              break;
 +      case WLAN_CIPHER_SUITE_CCMP:
 +              key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
 +              break;
 +      case WLAN_CIPHER_SUITE_AES_CMAC:
 +              WARN_ON_ONCE(!ieee80211_hw_check(hw, MFP_CAPABLE));
 +              break;
 +      case WLAN_CIPHER_SUITE_WEP40:
 +      case WLAN_CIPHER_SUITE_WEP104:
 +              /* 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.
 +               */
 +              if (vif->type != NL80211_IFTYPE_STATION)
 +                      return 0;
 +              break;
 +      default:
 +              /* currently FW supports only one optional cipher scheme */
 +              if (hw->n_cipher_schemes &&
 +                  hw->cipher_schemes->cipher == key->cipher)
 +                      key->flags |= IEEE80211_KEY_FLAG_PUT_IV_SPACE;
 +              else
 +                      return -EOPNOTSUPP;
 +      }
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      switch (cmd) {
 +      case SET_KEY:
 +              if ((vif->type == NL80211_IFTYPE_ADHOC ||
 +                   vif->type == NL80211_IFTYPE_AP) && !sta) {
 +                      /*
 +                       * GTK on AP interface is a TX-only key, return 0;
 +                       * on IBSS they're per-station and because we're lazy
 +                       * we don't support them for RX, so do the same.
 +                       */
 +                      ret = 0;
 +                      key->hw_key_idx = STA_KEY_IDX_INVALID;
 +                      break;
 +              }
 +
 +              /* During FW restart, in order to restore the state as it was,
 +               * don't try to reprogram keys we previously failed for.
 +               */
 +              if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) &&
 +                  key->hw_key_idx == STA_KEY_IDX_INVALID) {
 +                      IWL_DEBUG_MAC80211(mvm,
 +                                         "skip invalid idx key programming during restart\n");
 +                      ret = 0;
 +                      break;
 +              }
 +
++              /* in HW restart reuse the index, otherwise request a new one */
++              if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
++                      key_offset = key->hw_key_idx;
++              else
++                      key_offset = STA_KEY_IDX_INVALID;
++
 +              IWL_DEBUG_MAC80211(mvm, "set hwcrypto key\n");
++              ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, key_offset);
 +              if (ret) {
 +                      IWL_WARN(mvm, "set key failed\n");
 +                      /*
 +                       * can't add key for RX, but we don't need it
 +                       * in the device for TX so still return 0
 +                       */
 +                      key->hw_key_idx = STA_KEY_IDX_INVALID;
 +                      ret = 0;
 +              }
 +
 +              break;
 +      case DISABLE_KEY:
 +              if (key->hw_key_idx == STA_KEY_IDX_INVALID) {
 +                      ret = 0;
 +                      break;
 +              }
 +
 +              IWL_DEBUG_MAC80211(mvm, "disable hwcrypto key\n");
 +              ret = iwl_mvm_remove_sta_key(mvm, vif, sta, key);
 +              break;
 +      default:
 +              ret = -EINVAL;
 +      }
 +
 +      mutex_unlock(&mvm->mutex);
 +      return ret;
 +}
 +
 +static void iwl_mvm_mac_update_tkip_key(struct ieee80211_hw *hw,
 +                                      struct ieee80211_vif *vif,
 +                                      struct ieee80211_key_conf *keyconf,
 +                                      struct ieee80211_sta *sta,
 +                                      u32 iv32, u16 *phase1key)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      if (keyconf->hw_key_idx == STA_KEY_IDX_INVALID)
 +              return;
 +
 +      iwl_mvm_update_tkip_key(mvm, vif, keyconf, sta, iv32, phase1key);
 +}
 +
 +
 +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 200
 +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 u16 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);
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      spin_lock_bh(&mvm->time_event_lock);
 +
 +      if (WARN_ON(te_data->id == HOT_SPOT_CMD)) {
 +              spin_unlock_bh(&mvm->time_event_lock);
 +              return -EIO;
 +      }
 +
 +      te_data->vif = vif;
 +      te_data->duration = duration;
 +      te_data->id = HOT_SPOT_CMD;
 +
 +      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,
 +                     int duration,
 +                     enum ieee80211_roc_type type)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct cfg80211_chan_def chandef;
 +      struct iwl_mvm_phy_ctxt *phy_ctxt;
 +      int ret, i;
 +
 +      IWL_DEBUG_MAC80211(mvm, "enter (%d, %d, %d)\n", channel->hw_value,
 +                         duration, type);
 +
 +      flush_work(&mvm->roc_done_wk);
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      switch (vif->type) {
 +      case NL80211_IFTYPE_STATION:
 +              if (fw_has_capa(&mvm->fw->ucode_capa,
 +                              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 */
 +              break;
 +      default:
 +              IWL_ERR(mvm, "vif isn't P2P_DEVICE: %d\n", vif->type);
 +              ret = -EINVAL;
 +              goto out_unlock;
 +      }
 +
 +      for (i = 0; i < NUM_PHY_CTX; i++) {
 +              phy_ctxt = &mvm->phy_ctxts[i];
 +              if (phy_ctxt->ref == 0 || mvmvif->phy_ctxt == phy_ctxt)
 +                      continue;
 +
 +              if (phy_ctxt->ref && channel == phy_ctxt->channel) {
 +                      /*
 +                       * Unbind the P2P_DEVICE from the current PHY context,
 +                       * and if the PHY context is not used remove it.
 +                       */
 +                      ret = iwl_mvm_binding_remove_vif(mvm, vif);
 +                      if (WARN(ret, "Failed unbinding P2P_DEVICE\n"))
 +                              goto out_unlock;
 +
 +                      iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt);
 +
 +                      /* Bind the P2P_DEVICE to the current PHY Context */
 +                      mvmvif->phy_ctxt = phy_ctxt;
 +
 +                      ret = iwl_mvm_binding_add_vif(mvm, vif);
 +                      if (WARN(ret, "Failed binding P2P_DEVICE\n"))
 +                              goto out_unlock;
 +
 +                      iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt);
 +                      goto schedule_time_event;
 +              }
 +      }
 +
 +      /* Need to update the PHY context only if the ROC channel changed */
 +      if (channel == mvmvif->phy_ctxt->channel)
 +              goto schedule_time_event;
 +
 +      cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT);
 +
 +      /*
 +       * Change the PHY context configuration as it is currently referenced
 +       * only by the P2P Device MAC
 +       */
 +      if (mvmvif->phy_ctxt->ref == 1) {
 +              ret = iwl_mvm_phy_ctxt_changed(mvm, mvmvif->phy_ctxt,
 +                                             &chandef, 1, 1);
 +              if (ret)
 +                      goto out_unlock;
 +      } else {
 +              /*
 +               * The PHY context is shared with other MACs. Need to remove the
 +               * P2P Device from the binding, allocate an new PHY context and
 +               * create a new binding
 +               */
 +              phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm);
 +              if (!phy_ctxt) {
 +                      ret = -ENOSPC;
 +                      goto out_unlock;
 +              }
 +
 +              ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &chandef,
 +                                             1, 1);
 +              if (ret) {
 +                      IWL_ERR(mvm, "Failed to change PHY context\n");
 +                      goto out_unlock;
 +              }
 +
 +              /* Unbind the P2P_DEVICE from the current PHY context */
 +              ret = iwl_mvm_binding_remove_vif(mvm, vif);
 +              if (WARN(ret, "Failed unbinding P2P_DEVICE\n"))
 +                      goto out_unlock;
 +
 +              iwl_mvm_phy_ctxt_unref(mvm, mvmvif->phy_ctxt);
 +
 +              /* Bind the P2P_DEVICE to the new allocated PHY context */
 +              mvmvif->phy_ctxt = phy_ctxt;
 +
 +              ret = iwl_mvm_binding_add_vif(mvm, vif);
 +              if (WARN(ret, "Failed binding P2P_DEVICE\n"))
 +                      goto out_unlock;
 +
 +              iwl_mvm_phy_ctxt_ref(mvm, mvmvif->phy_ctxt);
 +      }
 +
 +schedule_time_event:
 +      /* Schedule the time events */
 +      ret = iwl_mvm_start_p2p_roc(mvm, vif, duration, type);
 +
 +out_unlock:
 +      mutex_unlock(&mvm->mutex);
 +      IWL_DEBUG_MAC80211(mvm, "leave\n");
 +      return ret;
 +}
 +
 +static int iwl_mvm_cancel_roc(struct ieee80211_hw *hw)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      IWL_DEBUG_MAC80211(mvm, "enter\n");
 +
 +      mutex_lock(&mvm->mutex);
 +      iwl_mvm_stop_roc(mvm);
 +      mutex_unlock(&mvm->mutex);
 +
 +      IWL_DEBUG_MAC80211(mvm, "leave\n");
 +      return 0;
 +}
 +
 +static int __iwl_mvm_add_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;
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      IWL_DEBUG_MAC80211(mvm, "Add channel context\n");
 +
 +      phy_ctxt = iwl_mvm_get_free_phy_ctxt(mvm);
 +      if (!phy_ctxt) {
 +              ret = -ENOSPC;
 +              goto out;
 +      }
 +
 +      ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx->min_def,
 +                                     ctx->rx_chains_static,
 +                                     ctx->rx_chains_dynamic);
 +      if (ret) {
 +              IWL_ERR(mvm, "Failed to add PHY context\n");
 +              goto out;
 +      }
 +
 +      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);
 +
 +      mutex_lock(&mvm->mutex);
 +      __iwl_mvm_remove_chanctx(mvm, ctx);
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static void iwl_mvm_change_chanctx(struct ieee80211_hw *hw,
 +                                 struct ieee80211_chanctx_conf *ctx,
 +                                 u32 changed)
 +{
 +      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];
 +
 +      if (WARN_ONCE((phy_ctxt->ref > 1) &&
 +                    (changed & ~(IEEE80211_CHANCTX_CHANGE_WIDTH |
 +                                 IEEE80211_CHANCTX_CHANGE_RX_CHAINS |
 +                                 IEEE80211_CHANCTX_CHANGE_RADAR |
 +                                 IEEE80211_CHANCTX_CHANGE_MIN_WIDTH)),
 +                    "Cannot change PHY. Ref=%d, changed=0x%X\n",
 +                    phy_ctxt->ref, changed))
 +              return;
 +
 +      mutex_lock(&mvm->mutex);
 +      iwl_mvm_bt_coex_vif_change(mvm);
 +      iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx->min_def,
 +                               ctx->rx_chains_static,
 +                               ctx->rx_chains_dynamic);
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +static int __iwl_mvm_assign_vif_chanctx(struct iwl_mvm *mvm,
 +                                      struct ieee80211_vif *vif,
 +                                      struct ieee80211_chanctx_conf *ctx,
 +                                      bool switching_chanctx)
 +{
 +      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;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      mvmvif->phy_ctxt = phy_ctxt;
 +
 +      switch (vif->type) {
 +      case NL80211_IFTYPE_AP:
 +              /* only needed if we're switching chanctx (i.e. during CSA) */
 +              if (switching_chanctx) {
 +                      mvmvif->ap_ibss_active = true;
 +                      break;
 +              }
 +      case NL80211_IFTYPE_ADHOC:
 +              /*
 +               * The AP binding flow is handled as part of the start_ap flow
 +               * (in bss_info_changed), similarly for IBSS.
 +               */
 +              ret = 0;
 +              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;
 +      }
 +
 +      ret = iwl_mvm_binding_add_vif(mvm, vif);
 +      if (ret)
 +              goto out;
 +
 +      /*
 +       * Power state must be updated before quotas,
 +       * otherwise fw will complain.
 +       */
 +      iwl_mvm_power_update_mac(mvm);
 +
 +      /* Setting the quota at this stage is only required for monitor
 +       * interfaces. For the other types, the bss_info changed flow
 +       * will handle quota settings.
 +       */
 +      if (vif->type == NL80211_IFTYPE_MONITOR) {
 +              mvmvif->monitor_active = true;
 +              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, false, NULL);
 +              iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +      }
 +
 +      if (switching_chanctx && vif->type == NL80211_IFTYPE_STATION) {
 +              u32 duration = 2 * vif->bss_conf.beacon_int;
 +
 +              /* 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;
 +
 +              /* 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);
 +
 +              iwl_mvm_unref(mvm, IWL_MVM_REF_PROTECT_CSA);
 +
 +              iwl_mvm_update_quotas(mvm, false, NULL);
 +      }
 +
 +      goto out;
 +
 +out_remove_binding:
 +      iwl_mvm_binding_remove_vif(mvm, vif);
 +      iwl_mvm_power_update_mac(mvm);
 +out:
 +      if (ret)
 +              mvmvif->phy_ctxt = NULL;
 +      return ret;
 +}
 +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);
 +      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;
 +
 +      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;
 +      case NL80211_IFTYPE_MONITOR:
 +              mvmvif->monitor_active = false;
 +              mvmvif->ps_disabled = false;
 +              break;
 +      case NL80211_IFTYPE_AP:
 +              /* This part is triggered only during CSA */
 +              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);
 +
 +              /* 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;
 +              break;
 +      case NL80211_IFTYPE_STATION:
 +              if (!switching_chanctx)
 +                      break;
 +
 +              disabled_vif = vif;
 +
 +              iwl_mvm_mac_ctxt_changed(mvm, vif, true, NULL);
 +              break;
 +      default:
 +              break;
 +      }
 +
 +      iwl_mvm_update_quotas(mvm, false, disabled_vif);
 +      iwl_mvm_binding_remove_vif(mvm, vif);
 +
 +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_swap(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);
 +      __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:
 +      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;
 +      }
 +
 +      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;
 +      }
 +
 +      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(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;
 +}
 +
 +static int iwl_mvm_set_tim(struct ieee80211_hw *hw,
 +                         struct ieee80211_sta *sta,
 +                         bool set)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +
 +      if (!mvm_sta || !mvm_sta->vif) {
 +              IWL_ERR(mvm, "Station is not associated to a vif\n");
 +              return -EINVAL;
 +      }
 +
 +      return iwl_mvm_mac_ctxt_beacon_changed(mvm, mvm_sta->vif);
 +}
 +
 +#ifdef CONFIG_NL80211_TESTMODE
 +static const struct nla_policy iwl_mvm_tm_policy[IWL_MVM_TM_ATTR_MAX + 1] = {
 +      [IWL_MVM_TM_ATTR_CMD] = { .type = NLA_U32 },
 +      [IWL_MVM_TM_ATTR_NOA_DURATION] = { .type = NLA_U32 },
 +      [IWL_MVM_TM_ATTR_BEACON_FILTER_STATE] = { .type = NLA_U32 },
 +};
 +
 +static int __iwl_mvm_mac_testmode_cmd(struct iwl_mvm *mvm,
 +                                    struct ieee80211_vif *vif,
 +                                    void *data, int len)
 +{
 +      struct nlattr *tb[IWL_MVM_TM_ATTR_MAX + 1];
 +      int err;
 +      u32 noa_duration;
 +
 +      err = nla_parse(tb, IWL_MVM_TM_ATTR_MAX, data, len, iwl_mvm_tm_policy);
 +      if (err)
 +              return err;
 +
 +      if (!tb[IWL_MVM_TM_ATTR_CMD])
 +              return -EINVAL;
 +
 +      switch (nla_get_u32(tb[IWL_MVM_TM_ATTR_CMD])) {
 +      case IWL_MVM_TM_CMD_SET_NOA:
 +              if (!vif || vif->type != NL80211_IFTYPE_AP || !vif->p2p ||
 +                  !vif->bss_conf.enable_beacon ||
 +                  !tb[IWL_MVM_TM_ATTR_NOA_DURATION])
 +                      return -EINVAL;
 +
 +              noa_duration = nla_get_u32(tb[IWL_MVM_TM_ATTR_NOA_DURATION]);
 +              if (noa_duration >= vif->bss_conf.beacon_int)
 +                      return -EINVAL;
 +
 +              mvm->noa_duration = noa_duration;
 +              mvm->noa_vif = vif;
 +
 +              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 ||
 +                  !vif->bss_conf.assoc || !vif->bss_conf.dtim_period ||
 +                  !tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE])
 +                      return -EINVAL;
 +
 +              if (nla_get_u32(tb[IWL_MVM_TM_ATTR_BEACON_FILTER_STATE]))
 +                      return iwl_mvm_enable_beacon_filter(mvm, vif, 0);
 +              return iwl_mvm_disable_beacon_filter(mvm, vif, 0);
 +      }
 +
 +      return -EOPNOTSUPP;
 +}
 +
 +static int iwl_mvm_mac_testmode_cmd(struct ieee80211_hw *hw,
 +                                  struct ieee80211_vif *vif,
 +                                  void *data, int len)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int err;
 +
 +      mutex_lock(&mvm->mutex);
 +      err = __iwl_mvm_mac_testmode_cmd(mvm, vif, data, len);
 +      mutex_unlock(&mvm->mutex);
 +
 +      return err;
 +}
 +#endif
 +
 +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);
 +
 +      mvmvif->csa_failed = false;
 +
 +      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);
 +
 +      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;
 +
 +      /* 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 (mvmvif->csa_failed) {
 +              mvmvif->csa_failed = false;
 +              ret = -EIO;
 +              goto out_unlock;
 +      }
 +
 +      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 ieee80211_vif *vif, u32 queues, bool drop)
 +{
 +      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);
 +
 +      /* 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, msk, 0))
 +                      IWL_ERR(mvm, "flush request fail\n");
 +              mutex_unlock(&mvm->mutex);
 +      } else {
 +              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);
 +      }
 +}
 +
 +static int iwl_mvm_mac_get_survey(struct ieee80211_hw *hw, int idx,
 +                                struct survey_info *survey)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +      int ret;
 +
 +      memset(survey, 0, sizeof(*survey));
 +
 +      /* only support global statistics right now */
 +      if (idx != 0)
 +              return -ENOENT;
 +
 +      if (fw_has_capa(&mvm->fw->ucode_capa,
 +                      IWL_UCODE_TLV_CAPA_RADIO_BEACON_STATS))
 +              return -ENOENT;
 +
 +      mutex_lock(&mvm->mutex);
 +
 +      if (mvm->ucode_loaded) {
 +              ret = iwl_mvm_request_statistics(mvm, false);
 +              if (ret)
 +                      goto out;
 +      }
 +
 +      survey->filled = SURVEY_INFO_TIME |
 +                       SURVEY_INFO_TIME_RX |
 +                       SURVEY_INFO_TIME_TX |
 +                       SURVEY_INFO_TIME_SCAN;
 +      survey->time = mvm->accu_radio_stats.on_time_rf +
 +                     mvm->radio_stats.on_time_rf;
 +      do_div(survey->time, USEC_PER_MSEC);
 +
 +      survey->time_rx = mvm->accu_radio_stats.rx_time +
 +                        mvm->radio_stats.rx_time;
 +      do_div(survey->time_rx, USEC_PER_MSEC);
 +
 +      survey->time_tx = mvm->accu_radio_stats.tx_time +
 +                        mvm->radio_stats.tx_time;
 +      do_div(survey->time_tx, USEC_PER_MSEC);
 +
 +      survey->time_scan = mvm->accu_radio_stats.on_time_scan +
 +                          mvm->radio_stats.on_time_scan;
 +      do_div(survey->time_scan, USEC_PER_MSEC);
 +
 +      ret = 0;
 + out:
 +      mutex_unlock(&mvm->mutex);
 +      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 (fw_has_capa(&mvm->fw->ucode_capa,
 +                      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);
 +}
 +
 +static void iwl_mvm_event_mlme_callback(struct iwl_mvm *mvm,
 +                                      struct ieee80211_vif *vif,
 +                                      const struct ieee80211_event *event)
 +{
 +#define CHECK_MLME_TRIGGER(_mvm, _trig, _buf, _cnt, _fmt...)  \
 +      do {                                                    \
 +              if ((_cnt) && --(_cnt))                         \
 +                      break;                                  \
 +              iwl_mvm_fw_dbg_collect_trig(_mvm, _trig, _fmt);\
 +      } while (0)
 +
 +      struct iwl_fw_dbg_trigger_tlv *trig;
 +      struct iwl_fw_dbg_trigger_mlme *trig_mlme;
 +
 +      if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_MLME))
 +              return;
 +
 +      trig = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_MLME);
 +      trig_mlme = (void *)trig->data;
 +      if (!iwl_fw_dbg_trigger_check_stop(mvm, vif, trig))
 +              return;
 +
 +      if (event->u.mlme.data == ASSOC_EVENT) {
 +              if (event->u.mlme.status == MLME_DENIED)
 +                      CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                         trig_mlme->stop_assoc_denied,
 +                                         "DENIED ASSOC: reason %d",
 +                                          event->u.mlme.reason);
 +              else if (event->u.mlme.status == MLME_TIMEOUT)
 +                      CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                         trig_mlme->stop_assoc_timeout,
 +                                         "ASSOC TIMEOUT");
 +      } else if (event->u.mlme.data == AUTH_EVENT) {
 +              if (event->u.mlme.status == MLME_DENIED)
 +                      CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                         trig_mlme->stop_auth_denied,
 +                                         "DENIED AUTH: reason %d",
 +                                         event->u.mlme.reason);
 +              else if (event->u.mlme.status == MLME_TIMEOUT)
 +                      CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                         trig_mlme->stop_auth_timeout,
 +                                         "AUTH TIMEOUT");
 +      } else if (event->u.mlme.data == DEAUTH_RX_EVENT) {
 +              CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                 trig_mlme->stop_rx_deauth,
 +                                 "DEAUTH RX %d", event->u.mlme.reason);
 +      } else if (event->u.mlme.data == DEAUTH_TX_EVENT) {
 +              CHECK_MLME_TRIGGER(mvm, trig, buf,
 +                                 trig_mlme->stop_tx_deauth,
 +                                 "DEAUTH TX %d", event->u.mlme.reason);
 +      }
 +#undef CHECK_MLME_TRIGGER
 +}
 +
 +static void iwl_mvm_event_bar_rx_callback(struct iwl_mvm *mvm,
 +                                        struct ieee80211_vif *vif,
 +                                        const struct ieee80211_event *event)
 +{
 +      struct iwl_fw_dbg_trigger_tlv *trig;
 +      struct iwl_fw_dbg_trigger_ba *ba_trig;
 +
 +      if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_BA))
 +              return;
 +
 +      trig = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_BA);
 +      ba_trig = (void *)trig->data;
 +      if (!iwl_fw_dbg_trigger_check_stop(mvm, vif, trig))
 +              return;
 +
 +      if (!(le16_to_cpu(ba_trig->rx_bar) & BIT(event->u.ba.tid)))
 +              return;
 +
 +      iwl_mvm_fw_dbg_collect_trig(mvm, trig,
 +                                  "BAR received from %pM, tid %d, ssn %d",
 +                                  event->u.ba.sta->addr, event->u.ba.tid,
 +                                  event->u.ba.ssn);
 +}
 +
 +static void
 +iwl_mvm_event_frame_timeout_callback(struct iwl_mvm *mvm,
 +                                   struct ieee80211_vif *vif,
 +                                   const struct ieee80211_event *event)
 +{
 +      struct iwl_fw_dbg_trigger_tlv *trig;
 +      struct iwl_fw_dbg_trigger_ba *ba_trig;
 +
 +      if (!iwl_fw_dbg_trigger_enabled(mvm->fw, FW_DBG_TRIGGER_BA))
 +              return;
 +
 +      trig = iwl_fw_dbg_get_trigger(mvm->fw, FW_DBG_TRIGGER_BA);
 +      ba_trig = (void *)trig->data;
 +      if (!iwl_fw_dbg_trigger_check_stop(mvm, vif, trig))
 +              return;
 +
 +      if (!(le16_to_cpu(ba_trig->frame_timeout) & BIT(event->u.ba.tid)))
 +              return;
 +
 +      iwl_mvm_fw_dbg_collect_trig(mvm, trig,
 +                                  "Frame from %pM timed out, tid %d",
 +                                  event->u.ba.sta->addr, event->u.ba.tid);
 +}
 +
 +static void iwl_mvm_mac_event_callback(struct ieee80211_hw *hw,
 +                                     struct ieee80211_vif *vif,
 +                                     const struct ieee80211_event *event)
 +{
 +      struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
 +
 +      switch (event->type) {
 +      case MLME_EVENT:
 +              iwl_mvm_event_mlme_callback(mvm, vif, event);
 +              break;
 +      case BAR_RX_EVENT:
 +              iwl_mvm_event_bar_rx_callback(mvm, vif, event);
 +              break;
 +      case BA_FRAME_TIMEOUT:
 +              iwl_mvm_event_frame_timeout_callback(mvm, vif, event);
 +              break;
 +      default:
 +              break;
 +      }
 +}
 +
 +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,
 +      .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,
 +      .config = iwl_mvm_mac_config,
 +      .prepare_multicast = iwl_mvm_prepare_multicast,
 +      .configure_filter = iwl_mvm_configure_filter,
 +      .config_iface_filter = iwl_mvm_config_iface_filter,
 +      .bss_info_changed = iwl_mvm_bss_info_changed,
 +      .hw_scan = iwl_mvm_mac_hw_scan,
 +      .cancel_hw_scan = iwl_mvm_mac_cancel_hw_scan,
 +      .sta_pre_rcu_remove = iwl_mvm_sta_pre_rcu_remove,
 +      .sta_state = iwl_mvm_mac_sta_state,
 +      .sta_notify = iwl_mvm_mac_sta_notify,
 +      .allow_buffered_frames = iwl_mvm_mac_allow_buffered_frames,
 +      .release_buffered_frames = iwl_mvm_mac_release_buffered_frames,
 +      .set_rts_threshold = iwl_mvm_mac_set_rts_threshold,
 +      .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,
 +      .set_key = iwl_mvm_mac_set_key,
 +      .update_tkip_key = iwl_mvm_mac_update_tkip_key,
 +      .remain_on_channel = iwl_mvm_roc,
 +      .cancel_remain_on_channel = iwl_mvm_cancel_roc,
 +      .add_chanctx = iwl_mvm_add_chanctx,
 +      .remove_chanctx = iwl_mvm_remove_chanctx,
 +      .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,
 +      .join_ibss = iwl_mvm_start_ap_ibss,
 +      .leave_ibss = iwl_mvm_stop_ap_ibss,
 +
 +      .set_tim = iwl_mvm_set_tim,
 +
 +      .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,
 +
 +      .event_callback = iwl_mvm_mac_event_callback,
 +
 +      CFG80211_TESTMODE_CMD(iwl_mvm_mac_testmode_cmd)
 +
 +#ifdef CONFIG_PM_SLEEP
 +      /* look at d3.c */
 +      .suspend = iwl_mvm_suspend,
 +      .resume = iwl_mvm_resume,
 +      .set_wakeup = iwl_mvm_set_wakeup,
 +      .set_rekey_data = iwl_mvm_set_rekey_data,
 +#if IS_ENABLED(CONFIG_IPV6)
 +      .ipv6_addr_change = iwl_mvm_ipv6_addr_change,
 +#endif
 +      .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,
 +};
index 300a249,0000000..354acbd
mode 100644,000000..100644
--- /dev/null
@@@ -1,1810 -1,0 +1,1816 @@@
- static u8 iwl_mvm_get_key_sta_id(struct ieee80211_vif *vif,
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * GPL LICENSE SUMMARY
 + *
 + * Copyright(c) 2012 - 2015 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * BSD LICENSE
 + *
 + * Copyright(c) 2012 - 2015 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
 + * All rights reserved.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +#include <net/mac80211.h>
 +
 +#include "mvm.h"
 +#include "sta.h"
 +#include "rs.h"
 +
 +static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm,
 +                                  enum nl80211_iftype iftype)
 +{
 +      int sta_id;
 +      u32 reserved_ids = 0;
 +
 +      BUILD_BUG_ON(IWL_MVM_STATION_COUNT > 32);
 +      WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status));
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* d0i3/d3 assumes the AP's sta_id (of sta vif) is 0. reserve it. */
 +      if (iftype != NL80211_IFTYPE_STATION)
 +              reserved_ids = BIT(0);
 +
 +      /* Don't take rcu_read_lock() since we are protected by mvm->mutex */
 +      for (sta_id = 0; sta_id < IWL_MVM_STATION_COUNT; sta_id++) {
 +              if (BIT(sta_id) & reserved_ids)
 +                      continue;
 +
 +              if (!rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
 +                                             lockdep_is_held(&mvm->mutex)))
 +                      return sta_id;
 +      }
 +      return IWL_MVM_STATION_COUNT;
 +}
 +
 +/* send station add/update command to firmware */
 +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
 +                         bool update)
 +{
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_add_sta_cmd add_sta_cmd = {
 +              .sta_id = mvm_sta->sta_id,
 +              .mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color),
 +              .add_modify = update ? 1 : 0,
 +              .station_flags_msk = cpu_to_le32(STA_FLG_FAT_EN_MSK |
 +                                               STA_FLG_MIMO_EN_MSK),
 +      };
 +      int ret;
 +      u32 status;
 +      u32 agg_size = 0, mpdu_dens = 0;
 +
 +      if (!update) {
 +              add_sta_cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk);
 +              memcpy(&add_sta_cmd.addr, sta->addr, ETH_ALEN);
 +      }
 +
 +      switch (sta->bandwidth) {
 +      case IEEE80211_STA_RX_BW_160:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_160MHZ);
 +              /* fall through */
 +      case IEEE80211_STA_RX_BW_80:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_80MHZ);
 +              /* fall through */
 +      case IEEE80211_STA_RX_BW_40:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_FAT_EN_40MHZ);
 +              /* fall through */
 +      case IEEE80211_STA_RX_BW_20:
 +              if (sta->ht_cap.ht_supported)
 +                      add_sta_cmd.station_flags |=
 +                              cpu_to_le32(STA_FLG_FAT_EN_20MHZ);
 +              break;
 +      }
 +
 +      switch (sta->rx_nss) {
 +      case 1:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO);
 +              break;
 +      case 2:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO2);
 +              break;
 +      case 3 ... 8:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_MIMO3);
 +              break;
 +      }
 +
 +      switch (sta->smps_mode) {
 +      case IEEE80211_SMPS_AUTOMATIC:
 +      case IEEE80211_SMPS_NUM_MODES:
 +              WARN_ON(1);
 +              break;
 +      case IEEE80211_SMPS_STATIC:
 +              /* override NSS */
 +              add_sta_cmd.station_flags &= ~cpu_to_le32(STA_FLG_MIMO_EN_MSK);
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_MIMO_EN_SISO);
 +              break;
 +      case IEEE80211_SMPS_DYNAMIC:
 +              add_sta_cmd.station_flags |= cpu_to_le32(STA_FLG_RTS_MIMO_PROT);
 +              break;
 +      case IEEE80211_SMPS_OFF:
 +              /* nothing */
 +              break;
 +      }
 +
 +      if (sta->ht_cap.ht_supported) {
 +              add_sta_cmd.station_flags_msk |=
 +                      cpu_to_le32(STA_FLG_MAX_AGG_SIZE_MSK |
 +                                  STA_FLG_AGG_MPDU_DENS_MSK);
 +
 +              mpdu_dens = sta->ht_cap.ampdu_density;
 +      }
 +
 +      if (sta->vht_cap.vht_supported) {
 +              agg_size = sta->vht_cap.cap &
 +                      IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
 +              agg_size >>=
 +                      IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT;
 +      } else if (sta->ht_cap.ht_supported) {
 +              agg_size = sta->ht_cap.ampdu_factor;
 +      }
 +
 +      add_sta_cmd.station_flags |=
 +              cpu_to_le32(agg_size << STA_FLG_MAX_AGG_SIZE_SHIFT);
 +      add_sta_cmd.station_flags |=
 +              cpu_to_le32(mpdu_dens << STA_FLG_AGG_MPDU_DENS_SHIFT);
 +
 +      status = ADD_STA_SUCCESS;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(add_sta_cmd),
 +                                        &add_sta_cmd, &status);
 +      if (ret)
 +              return ret;
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_ASSOC(mvm, "ADD_STA PASSED\n");
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "ADD_STA failed\n");
 +              break;
 +      }
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_tdls_sta_init(struct iwl_mvm *mvm,
 +                               struct ieee80211_sta *sta)
 +{
 +      unsigned long used_hw_queues;
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      unsigned int wdg_timeout =
 +              iwl_mvm_get_wd_timeout(mvm, NULL, true, false);
 +      u32 ac;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      used_hw_queues = iwl_mvm_get_used_hw_queues(mvm, NULL);
 +
 +      /* Find available queues, and allocate them to the ACs */
 +      for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
 +              u8 queue = find_first_zero_bit(&used_hw_queues,
 +                                             mvm->first_agg_queue);
 +
 +              if (queue >= mvm->first_agg_queue) {
 +                      IWL_ERR(mvm, "Failed to allocate STA queue\n");
 +                      return -EBUSY;
 +              }
 +
 +              __set_bit(queue, &used_hw_queues);
 +              mvmsta->hw_queue[ac] = queue;
 +      }
 +
 +      /* Found a place for all queues - enable them */
 +      for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
 +              iwl_mvm_enable_ac_txq(mvm, mvmsta->hw_queue[ac],
 +                                    mvmsta->hw_queue[ac],
 +                                    iwl_mvm_ac_to_tx_fifo[ac], 0,
 +                                    wdg_timeout);
 +              mvmsta->tfd_queue_msk |= BIT(mvmsta->hw_queue[ac]);
 +      }
 +
 +      return 0;
 +}
 +
 +static void iwl_mvm_tdls_sta_deinit(struct iwl_mvm *mvm,
 +                                  struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      unsigned long sta_msk;
 +      int i;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* disable the TDLS STA-specific queues */
 +      sta_msk = mvmsta->tfd_queue_msk;
 +      for_each_set_bit(i, &sta_msk, sizeof(sta_msk) * BITS_PER_BYTE)
 +              iwl_mvm_disable_txq(mvm, i, i, IWL_MAX_TID_COUNT, 0);
 +}
 +
 +int iwl_mvm_add_sta(struct iwl_mvm *mvm,
 +                  struct ieee80211_vif *vif,
 +                  struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      int i, ret, sta_id;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
 +              sta_id = iwl_mvm_find_free_sta_id(mvm,
 +                                                ieee80211_vif_type_p2p(vif));
 +      else
 +              sta_id = mvm_sta->sta_id;
 +
 +      if (sta_id == IWL_MVM_STATION_COUNT)
 +              return -ENOSPC;
 +
 +      if (vif->type == NL80211_IFTYPE_AP) {
 +              mvmvif->ap_assoc_sta_count++;
 +              iwl_mvm_mac_ctxt_changed(mvm, vif, false, NULL);
 +      }
 +
 +      spin_lock_init(&mvm_sta->lock);
 +
 +      mvm_sta->sta_id = sta_id;
 +      mvm_sta->mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id,
 +                                                    mvmvif->color);
 +      mvm_sta->vif = vif;
 +      mvm_sta->max_agg_bufsize = LINK_QUAL_AGG_FRAME_LIMIT_DEF;
 +      mvm_sta->tx_protection = 0;
 +      mvm_sta->tt_tx_protection = false;
 +
 +      /* HW restart, don't assume the memory has been zeroed */
 +      atomic_set(&mvm->pending_frames[sta_id], 0);
 +      mvm_sta->tid_disable_agg = 0xffff; /* No aggs at first */
 +      mvm_sta->tfd_queue_msk = 0;
 +
 +      /* allocate new queues for a TDLS station */
 +      if (sta->tdls) {
 +              ret = iwl_mvm_tdls_sta_init(mvm, sta);
 +              if (ret)
 +                      return ret;
 +      } else {
 +              for (i = 0; i < IEEE80211_NUM_ACS; i++)
 +                      if (vif->hw_queue[i] != IEEE80211_INVAL_HW_QUEUE)
 +                              mvm_sta->tfd_queue_msk |= BIT(vif->hw_queue[i]);
 +      }
 +
 +      /* for HW restart - reset everything but the sequence number */
 +      for (i = 0; i < IWL_MAX_TID_COUNT; i++) {
 +              u16 seq = mvm_sta->tid_data[i].seq_number;
 +              memset(&mvm_sta->tid_data[i], 0, sizeof(mvm_sta->tid_data[i]));
 +              mvm_sta->tid_data[i].seq_number = seq;
 +      }
 +      mvm_sta->agg_tids = 0;
 +
 +      ret = iwl_mvm_sta_send_to_fw(mvm, sta, false);
 +      if (ret)
 +              goto err;
 +
 +      if (vif->type == NL80211_IFTYPE_STATION) {
 +              if (!sta->tdls) {
 +                      WARN_ON(mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT);
 +                      mvmvif->ap_sta_id = sta_id;
 +              } else {
 +                      WARN_ON(mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT);
 +              }
 +      }
 +
 +      rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta);
 +
 +      return 0;
 +
 +err:
 +      iwl_mvm_tdls_sta_deinit(mvm, sta);
 +      return ret;
 +}
 +
 +int iwl_mvm_update_sta(struct iwl_mvm *mvm,
 +                     struct ieee80211_vif *vif,
 +                     struct ieee80211_sta *sta)
 +{
 +      return iwl_mvm_sta_send_to_fw(mvm, sta, true);
 +}
 +
 +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta,
 +                    bool drain)
 +{
 +      struct iwl_mvm_add_sta_cmd cmd = {};
 +      int ret;
 +      u32 status;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      cmd.mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color);
 +      cmd.sta_id = mvmsta->sta_id;
 +      cmd.add_modify = STA_MODE_MODIFY;
 +      cmd.station_flags = drain ? cpu_to_le32(STA_FLG_DRAIN_FLOW) : 0;
 +      cmd.station_flags_msk = cpu_to_le32(STA_FLG_DRAIN_FLOW);
 +
 +      status = ADD_STA_SUCCESS;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd),
 +                                        &cmd, &status);
 +      if (ret)
 +              return ret;
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_INFO(mvm, "Frames for staid %d will drained in fw\n",
 +                             mvmsta->sta_id);
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "Couldn't drain frames for staid %d\n",
 +                      mvmsta->sta_id);
 +              break;
 +      }
 +
 +      return ret;
 +}
 +
 +/*
 + * Remove a station from the FW table. Before sending the command to remove
 + * the station validate that the station is indeed known to the driver (sanity
 + * only).
 + */
 +static int iwl_mvm_rm_sta_common(struct iwl_mvm *mvm, u8 sta_id)
 +{
 +      struct ieee80211_sta *sta;
 +      struct iwl_mvm_rm_sta_cmd rm_sta_cmd = {
 +              .sta_id = sta_id,
 +      };
 +      int ret;
 +
 +      sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
 +                                      lockdep_is_held(&mvm->mutex));
 +
 +      /* Note: internal stations are marked as error values */
 +      if (!sta) {
 +              IWL_ERR(mvm, "Invalid station id\n");
 +              return -EINVAL;
 +      }
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, REMOVE_STA, 0,
 +                                 sizeof(rm_sta_cmd), &rm_sta_cmd);
 +      if (ret) {
 +              IWL_ERR(mvm, "Failed to remove station. Id=%d\n", sta_id);
 +              return ret;
 +      }
 +
 +      return 0;
 +}
 +
 +void iwl_mvm_sta_drained_wk(struct work_struct *wk)
 +{
 +      struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, sta_drained_wk);
 +      u8 sta_id;
 +
 +      /*
 +       * The mutex is needed because of the SYNC cmd, but not only: if the
 +       * work would run concurrently with iwl_mvm_rm_sta, it would run before
 +       * iwl_mvm_rm_sta sets the station as busy, and exit. Then
 +       * iwl_mvm_rm_sta would set the station as busy, and nobody will clean
 +       * that later.
 +       */
 +      mutex_lock(&mvm->mutex);
 +
 +      for_each_set_bit(sta_id, mvm->sta_drained, IWL_MVM_STATION_COUNT) {
 +              int ret;
 +              struct ieee80211_sta *sta =
 +                      rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
 +                                                lockdep_is_held(&mvm->mutex));
 +
 +              /*
 +               * This station is in use or RCU-removed; the latter happens in
 +               * managed mode, where mac80211 removes the station before we
 +               * can remove it from firmware (we can only do that after the
 +               * MAC is marked unassociated), and possibly while the deauth
 +               * frame to disconnect from the AP is still queued. Then, the
 +               * station pointer is -ENOENT when the last skb is reclaimed.
 +               */
 +              if (!IS_ERR(sta) || PTR_ERR(sta) == -ENOENT)
 +                      continue;
 +
 +              if (PTR_ERR(sta) == -EINVAL) {
 +                      IWL_ERR(mvm, "Drained sta %d, but it is internal?\n",
 +                              sta_id);
 +                      continue;
 +              }
 +
 +              if (!sta) {
 +                      IWL_ERR(mvm, "Drained sta %d, but it was NULL?\n",
 +                              sta_id);
 +                      continue;
 +              }
 +
 +              WARN_ON(PTR_ERR(sta) != -EBUSY);
 +              /* This station was removed and we waited until it got drained,
 +               * we can now proceed and remove it.
 +               */
 +              ret = iwl_mvm_rm_sta_common(mvm, sta_id);
 +              if (ret) {
 +                      IWL_ERR(mvm,
 +                              "Couldn't remove sta %d after it was drained\n",
 +                              sta_id);
 +                      continue;
 +              }
 +              RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta_id], NULL);
 +              clear_bit(sta_id, mvm->sta_drained);
 +
 +              if (mvm->tfd_drained[sta_id]) {
 +                      unsigned long i, msk = mvm->tfd_drained[sta_id];
 +
 +                      for_each_set_bit(i, &msk, sizeof(msk) * BITS_PER_BYTE)
 +                              iwl_mvm_disable_txq(mvm, i, i,
 +                                                  IWL_MAX_TID_COUNT, 0);
 +
 +                      mvm->tfd_drained[sta_id] = 0;
 +                      IWL_DEBUG_TDLS(mvm, "Drained sta %d, with queues %ld\n",
 +                                     sta_id, msk);
 +              }
 +      }
 +
 +      mutex_unlock(&mvm->mutex);
 +}
 +
 +int iwl_mvm_rm_sta(struct iwl_mvm *mvm,
 +                 struct ieee80211_vif *vif,
 +                 struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (vif->type == NL80211_IFTYPE_STATION &&
 +          mvmvif->ap_sta_id == mvm_sta->sta_id) {
 +              ret = iwl_mvm_drain_sta(mvm, mvm_sta, true);
 +              if (ret)
 +                      return ret;
 +              /* flush its queues here since we are freeing mvm_sta */
 +              ret = iwl_mvm_flush_tx_path(mvm, mvm_sta->tfd_queue_msk, 0);
 +              if (ret)
 +                      return ret;
 +              ret = iwl_trans_wait_tx_queue_empty(mvm->trans,
 +                                                  mvm_sta->tfd_queue_msk);
 +              if (ret)
 +                      return ret;
 +              ret = iwl_mvm_drain_sta(mvm, mvm_sta, false);
 +
 +              /* if we are associated - we can't remove the AP STA now */
 +              if (vif->bss_conf.assoc)
 +                      return ret;
 +
 +              /* unassoc - go ahead - remove the AP STA now */
 +              mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT;
 +
 +              /* clear d0i3_ap_sta_id if no longer relevant */
 +              if (mvm->d0i3_ap_sta_id == mvm_sta->sta_id)
 +                      mvm->d0i3_ap_sta_id = IWL_MVM_STATION_COUNT;
 +      }
 +
 +      /*
 +       * This shouldn't happen - the TDLS channel switch should be canceled
 +       * before the STA is removed.
 +       */
 +      if (WARN_ON_ONCE(mvm->tdls_cs.peer.sta_id == mvm_sta->sta_id)) {
 +              mvm->tdls_cs.peer.sta_id = IWL_MVM_STATION_COUNT;
 +              cancel_delayed_work(&mvm->tdls_cs.dwork);
 +      }
 +
 +      /*
 +       * Make sure that the tx response code sees the station as -EBUSY and
 +       * calls the drain worker.
 +       */
 +      spin_lock_bh(&mvm_sta->lock);
 +      /*
 +       * There are frames pending on the AC queues for this station.
 +       * We need to wait until all the frames are drained...
 +       */
 +      if (atomic_read(&mvm->pending_frames[mvm_sta->sta_id])) {
 +              rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id],
 +                                 ERR_PTR(-EBUSY));
 +              spin_unlock_bh(&mvm_sta->lock);
 +
 +              /* disable TDLS sta queues on drain complete */
 +              if (sta->tdls) {
 +                      mvm->tfd_drained[mvm_sta->sta_id] =
 +                                                      mvm_sta->tfd_queue_msk;
 +                      IWL_DEBUG_TDLS(mvm, "Draining TDLS sta %d\n",
 +                                     mvm_sta->sta_id);
 +              }
 +
 +              ret = iwl_mvm_drain_sta(mvm, mvm_sta, true);
 +      } else {
 +              spin_unlock_bh(&mvm_sta->lock);
 +
 +              if (sta->tdls)
 +                      iwl_mvm_tdls_sta_deinit(mvm, sta);
 +
 +              ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id);
 +              RCU_INIT_POINTER(mvm->fw_id_to_mac_id[mvm_sta->sta_id], NULL);
 +      }
 +
 +      return ret;
 +}
 +
 +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm,
 +                    struct ieee80211_vif *vif,
 +                    u8 sta_id)
 +{
 +      int ret = iwl_mvm_rm_sta_common(mvm, sta_id);
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta_id], NULL);
 +      return ret;
 +}
 +
 +static int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm,
 +                                  struct iwl_mvm_int_sta *sta,
 +                                  u32 qmask, enum nl80211_iftype iftype)
 +{
 +      if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
 +              sta->sta_id = iwl_mvm_find_free_sta_id(mvm, iftype);
 +              if (WARN_ON_ONCE(sta->sta_id == IWL_MVM_STATION_COUNT))
 +                      return -ENOSPC;
 +      }
 +
 +      sta->tfd_queue_msk = qmask;
 +
 +      /* put a non-NULL value so iterating over the stations won't stop */
 +      rcu_assign_pointer(mvm->fw_id_to_mac_id[sta->sta_id], ERR_PTR(-EINVAL));
 +      return 0;
 +}
 +
 +static void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm,
 +                                  struct iwl_mvm_int_sta *sta)
 +{
 +      RCU_INIT_POINTER(mvm->fw_id_to_mac_id[sta->sta_id], NULL);
 +      memset(sta, 0, sizeof(struct iwl_mvm_int_sta));
 +      sta->sta_id = IWL_MVM_STATION_COUNT;
 +}
 +
 +static int iwl_mvm_add_int_sta_common(struct iwl_mvm *mvm,
 +                                    struct iwl_mvm_int_sta *sta,
 +                                    const u8 *addr,
 +                                    u16 mac_id, u16 color)
 +{
 +      struct iwl_mvm_add_sta_cmd cmd;
 +      int ret;
 +      u32 status;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      memset(&cmd, 0, sizeof(cmd));
 +      cmd.sta_id = sta->sta_id;
 +      cmd.mac_id_n_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mac_id,
 +                                                           color));
 +
 +      cmd.tfd_queue_msk = cpu_to_le32(sta->tfd_queue_msk);
 +
 +      if (addr)
 +              memcpy(cmd.addr, addr, ETH_ALEN);
 +
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd),
 +                                        &cmd, &status);
 +      if (ret)
 +              return ret;
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_INFO(mvm, "Internal station added.\n");
 +              return 0;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "Add internal station failed, status=0x%x\n",
 +                      status);
 +              break;
 +      }
 +      return ret;
 +}
 +
 +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm)
 +{
 +      unsigned int wdg_timeout = iwlmvm_mod_params.tfd_q_hang_detect ?
 +                                      mvm->cfg->base_params->wd_timeout :
 +                                      IWL_WATCHDOG_DISABLED;
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* Map Aux queue to fifo - needs to happen before adding Aux station */
 +      iwl_mvm_enable_ac_txq(mvm, mvm->aux_queue, mvm->aux_queue,
 +                            IWL_MVM_TX_FIFO_MCAST, 0, wdg_timeout);
 +
 +      /* Allocate aux station and assign to it the aux queue */
 +      ret = iwl_mvm_allocate_int_sta(mvm, &mvm->aux_sta, BIT(mvm->aux_queue),
 +                                     NL80211_IFTYPE_UNSPECIFIED);
 +      if (ret)
 +              return ret;
 +
 +      ret = iwl_mvm_add_int_sta_common(mvm, &mvm->aux_sta, NULL,
 +                                       MAC_INDEX_AUX, 0);
 +
 +      if (ret)
 +              iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta);
 +      return ret;
 +}
 +
 +void iwl_mvm_del_aux_sta(struct iwl_mvm *mvm)
 +{
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      iwl_mvm_dealloc_int_sta(mvm, &mvm->aux_sta);
 +}
 +
 +/*
 + * Send the add station command for the vif's broadcast station.
 + * Assumes that the station was already allocated.
 + *
 + * @mvm: the mvm component
 + * @vif: the interface to which the broadcast station is added
 + * @bsta: the broadcast station to add.
 + */
 +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta;
 +      static const u8 _baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 +      const u8 *baddr = _baddr;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (vif->type == NL80211_IFTYPE_ADHOC)
 +              baddr = vif->bss_conf.bssid;
 +
 +      if (WARN_ON_ONCE(bsta->sta_id == IWL_MVM_STATION_COUNT))
 +              return -ENOSPC;
 +
 +      return iwl_mvm_add_int_sta_common(mvm, bsta, baddr,
 +                                        mvmvif->id, mvmvif->color);
 +}
 +
 +/* Send the FW a request to remove the station from it's internal data
 + * structures, but DO NOT remove the entry from the local data structures. */
 +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      ret = iwl_mvm_rm_sta_common(mvm, mvmvif->bcast_sta.sta_id);
 +      if (ret)
 +              IWL_WARN(mvm, "Failed sending remove station\n");
 +      return ret;
 +}
 +
 +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      u32 qmask;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      qmask = iwl_mvm_mac_get_queues_mask(vif);
 +
 +      /*
 +       * The firmware defines the TFD queue mask to only be relevant
 +       * for *unicast* queues, so the multicast (CAB) queue shouldn't
 +       * be included.
 +       */
 +      if (vif->type == NL80211_IFTYPE_AP)
 +              qmask &= ~BIT(vif->cab_queue);
 +
 +      return iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta, qmask,
 +                                      ieee80211_vif_type_p2p(vif));
 +}
 +
 +/* Allocate a new station entry for the broadcast station to the given vif,
 + * and send it to the FW.
 + * Note that each P2P mac should have its own broadcast station.
 + *
 + * @mvm: the mvm component
 + * @vif: the interface to which the broadcast station is added
 + * @bsta: the broadcast station to add. */
 +int iwl_mvm_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_int_sta *bsta = &mvmvif->bcast_sta;
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      ret = iwl_mvm_alloc_bcast_sta(mvm, vif);
 +      if (ret)
 +              return ret;
 +
 +      ret = iwl_mvm_send_add_bcast_sta(mvm, vif);
 +
 +      if (ret)
 +              iwl_mvm_dealloc_int_sta(mvm, bsta);
 +
 +      return ret;
 +}
 +
 +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta);
 +}
 +
 +/*
 + * Send the FW a request to remove the station from it's internal data
 + * structures, and in addition remove it from the local data structure.
 + */
 +int iwl_mvm_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      int ret;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      ret = iwl_mvm_send_rm_bcast_sta(mvm, vif);
 +
 +      iwl_mvm_dealloc_bcast_sta(mvm, vif);
 +
 +      return ret;
 +}
 +
 +#define IWL_MAX_RX_BA_SESSIONS 16
 +
 +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
 +                     int tid, u16 ssn, bool start)
 +{
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_add_sta_cmd cmd = {};
 +      int ret;
 +      u32 status;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (start && mvm->rx_ba_sessions >= IWL_MAX_RX_BA_SESSIONS) {
 +              IWL_WARN(mvm, "Not enough RX BA SESSIONS\n");
 +              return -ENOSPC;
 +      }
 +
 +      cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color);
 +      cmd.sta_id = mvm_sta->sta_id;
 +      cmd.add_modify = STA_MODE_MODIFY;
 +      if (start) {
 +              cmd.add_immediate_ba_tid = (u8) tid;
 +              cmd.add_immediate_ba_ssn = cpu_to_le16(ssn);
 +      } else {
 +              cmd.remove_immediate_ba_tid = (u8) tid;
 +      }
 +      cmd.modify_mask = start ? STA_MODIFY_ADD_BA_TID :
 +                                STA_MODIFY_REMOVE_BA_TID;
 +
 +      status = ADD_STA_SUCCESS;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd),
 +                                        &cmd, &status);
 +      if (ret)
 +              return ret;
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_INFO(mvm, "RX BA Session %sed in fw\n",
 +                             start ? "start" : "stopp");
 +              break;
 +      case ADD_STA_IMMEDIATE_BA_FAILURE:
 +              IWL_WARN(mvm, "RX BA Session refused by fw\n");
 +              ret = -ENOSPC;
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "RX BA Session failed %sing, status 0x%x\n",
 +                      start ? "start" : "stopp", status);
 +              break;
 +      }
 +
 +      if (!ret) {
 +              if (start)
 +                      mvm->rx_ba_sessions++;
 +              else if (mvm->rx_ba_sessions > 0)
 +                      /* check that restart flow didn't zero the counter */
 +                      mvm->rx_ba_sessions--;
 +      }
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_sta_tx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
 +                            int tid, u8 queue, bool start)
 +{
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_add_sta_cmd cmd = {};
 +      int ret;
 +      u32 status;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      if (start) {
 +              mvm_sta->tfd_queue_msk |= BIT(queue);
 +              mvm_sta->tid_disable_agg &= ~BIT(tid);
 +      } else {
 +              mvm_sta->tfd_queue_msk &= ~BIT(queue);
 +              mvm_sta->tid_disable_agg |= BIT(tid);
 +      }
 +
 +      cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color);
 +      cmd.sta_id = mvm_sta->sta_id;
 +      cmd.add_modify = STA_MODE_MODIFY;
 +      cmd.modify_mask = STA_MODIFY_QUEUES | STA_MODIFY_TID_DISABLE_TX;
 +      cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk);
 +      cmd.tid_disable_tx = cpu_to_le16(mvm_sta->tid_disable_agg);
 +
 +      status = ADD_STA_SUCCESS;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd),
 +                                        &cmd, &status);
 +      if (ret)
 +              return ret;
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "TX BA Session failed %sing, status 0x%x\n",
 +                      start ? "start" : "stopp", status);
 +              break;
 +      }
 +
 +      return ret;
 +}
 +
 +const u8 tid_to_mac80211_ac[] = {
 +      IEEE80211_AC_BE,
 +      IEEE80211_AC_BK,
 +      IEEE80211_AC_BK,
 +      IEEE80211_AC_BE,
 +      IEEE80211_AC_VI,
 +      IEEE80211_AC_VI,
 +      IEEE80211_AC_VO,
 +      IEEE80211_AC_VO,
 +};
 +
 +static const u8 tid_to_ucode_ac[] = {
 +      AC_BE,
 +      AC_BK,
 +      AC_BK,
 +      AC_BE,
 +      AC_VI,
 +      AC_VI,
 +      AC_VO,
 +      AC_VO,
 +};
 +
 +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                           struct ieee80211_sta *sta, u16 tid, u16 *ssn)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_tid_data *tid_data;
 +      int txq_id;
 +      int ret;
 +
 +      if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT))
 +              return -EINVAL;
 +
 +      if (mvmsta->tid_data[tid].state != IWL_AGG_OFF) {
 +              IWL_ERR(mvm, "Start AGG when state is not IWL_AGG_OFF %d!\n",
 +                      mvmsta->tid_data[tid].state);
 +              return -ENXIO;
 +      }
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      spin_lock_bh(&mvmsta->lock);
 +
 +      /* possible race condition - we entered D0i3 while starting agg */
 +      if (test_bit(IWL_MVM_STATUS_IN_D0I3, &mvm->status)) {
 +              spin_unlock_bh(&mvmsta->lock);
 +              IWL_ERR(mvm, "Entered D0i3 while starting Tx agg\n");
 +              return -EIO;
 +      }
 +
 +      spin_lock_bh(&mvm->queue_info_lock);
 +
 +      txq_id = iwl_mvm_find_free_queue(mvm, mvm->first_agg_queue,
 +                                       mvm->last_agg_queue);
 +      if (txq_id < 0) {
 +              ret = txq_id;
 +              spin_unlock_bh(&mvm->queue_info_lock);
 +              IWL_ERR(mvm, "Failed to allocate agg queue\n");
 +              goto release_locks;
 +      }
 +      mvm->queue_info[txq_id].setup_reserved = true;
 +      spin_unlock_bh(&mvm->queue_info_lock);
 +
 +      tid_data = &mvmsta->tid_data[tid];
 +      tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number);
 +      tid_data->txq_id = txq_id;
 +      *ssn = tid_data->ssn;
 +
 +      IWL_DEBUG_TX_QUEUES(mvm,
 +                          "Start AGG: sta %d tid %d queue %d - ssn = %d, next_recl = %d\n",
 +                          mvmsta->sta_id, tid, txq_id, tid_data->ssn,
 +                          tid_data->next_reclaimed);
 +
 +      if (tid_data->ssn == tid_data->next_reclaimed) {
 +              tid_data->state = IWL_AGG_STARTING;
 +              ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 +      } else {
 +              tid_data->state = IWL_EMPTYING_HW_QUEUE_ADDBA;
 +      }
 +
 +      ret = 0;
 +
 +release_locks:
 +      spin_unlock_bh(&mvmsta->lock);
 +
 +      return ret;
 +}
 +
 +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid, u8 buf_size)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
 +      unsigned int wdg_timeout =
 +              iwl_mvm_get_wd_timeout(mvm, vif, sta->tdls, false);
 +      int queue, fifo, ret;
 +      u16 ssn;
 +
 +      BUILD_BUG_ON((sizeof(mvmsta->agg_tids) * BITS_PER_BYTE)
 +                   != IWL_MAX_TID_COUNT);
 +
 +      buf_size = min_t(int, buf_size, LINK_QUAL_AGG_FRAME_LIMIT_DEF);
 +
 +      spin_lock_bh(&mvmsta->lock);
 +      ssn = tid_data->ssn;
 +      queue = tid_data->txq_id;
 +      tid_data->state = IWL_AGG_ON;
 +      mvmsta->agg_tids |= BIT(tid);
 +      tid_data->ssn = 0xffff;
 +      spin_unlock_bh(&mvmsta->lock);
 +
 +      fifo = iwl_mvm_ac_to_tx_fifo[tid_to_mac80211_ac[tid]];
 +
 +      iwl_mvm_enable_agg_txq(mvm, queue,
 +                             vif->hw_queue[tid_to_mac80211_ac[tid]], fifo,
 +                             mvmsta->sta_id, tid, buf_size, ssn, wdg_timeout);
 +
 +      ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true);
 +      if (ret)
 +              return -EIO;
 +
 +      /* No need to mark as reserved */
 +      spin_lock_bh(&mvm->queue_info_lock);
 +      mvm->queue_info[queue].setup_reserved = false;
 +      spin_unlock_bh(&mvm->queue_info_lock);
 +
 +      /*
 +       * Even though in theory the peer could have different
 +       * aggregation reorder buffer sizes for different sessions,
 +       * our ucode doesn't allow for that and has a global limit
 +       * for each station. Therefore, use the minimum of all the
 +       * aggregation sessions and our default value.
 +       */
 +      mvmsta->max_agg_bufsize =
 +              min(mvmsta->max_agg_bufsize, buf_size);
 +      mvmsta->lq_sta.lq.agg_frame_cnt_limit = mvmsta->max_agg_bufsize;
 +
 +      IWL_DEBUG_HT(mvm, "Tx aggregation enabled on ra = %pM tid = %d\n",
 +                   sta->addr, tid);
 +
 +      return iwl_mvm_send_lq_cmd(mvm, &mvmsta->lq_sta.lq, false);
 +}
 +
 +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
 +      u16 txq_id;
 +      int err;
 +
 +
 +      /*
 +       * If mac80211 is cleaning its state, then say that we finished since
 +       * our state has been cleared anyway.
 +       */
 +      if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) {
 +              ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 +              return 0;
 +      }
 +
 +      spin_lock_bh(&mvmsta->lock);
 +
 +      txq_id = tid_data->txq_id;
 +
 +      IWL_DEBUG_TX_QUEUES(mvm, "Stop AGG: sta %d tid %d q %d state %d\n",
 +                          mvmsta->sta_id, tid, txq_id, tid_data->state);
 +
 +      mvmsta->agg_tids &= ~BIT(tid);
 +
 +      /* No need to mark as reserved anymore */
 +      spin_lock_bh(&mvm->queue_info_lock);
 +      mvm->queue_info[txq_id].setup_reserved = false;
 +      spin_unlock_bh(&mvm->queue_info_lock);
 +
 +      switch (tid_data->state) {
 +      case IWL_AGG_ON:
 +              tid_data->ssn = IEEE80211_SEQ_TO_SN(tid_data->seq_number);
 +
 +              IWL_DEBUG_TX_QUEUES(mvm,
 +                                  "ssn = %d, next_recl = %d\n",
 +                                  tid_data->ssn, tid_data->next_reclaimed);
 +
 +              /* There are still packets for this RA / TID in the HW */
 +              if (tid_data->ssn != tid_data->next_reclaimed) {
 +                      tid_data->state = IWL_EMPTYING_HW_QUEUE_DELBA;
 +                      err = 0;
 +                      break;
 +              }
 +
 +              tid_data->ssn = 0xffff;
 +              tid_data->state = IWL_AGG_OFF;
 +              spin_unlock_bh(&mvmsta->lock);
 +
 +              ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 +
 +              iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false);
 +
 +              iwl_mvm_disable_txq(mvm, txq_id,
 +                                  vif->hw_queue[tid_to_mac80211_ac[tid]], tid,
 +                                  0);
 +              return 0;
 +      case IWL_AGG_STARTING:
 +      case IWL_EMPTYING_HW_QUEUE_ADDBA:
 +              /*
 +               * The agg session has been stopped before it was set up. This
 +               * can happen when the AddBA timer times out for example.
 +               */
 +
 +              /* No barriers since we are under mutex */
 +              lockdep_assert_held(&mvm->mutex);
 +
 +              ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid);
 +              tid_data->state = IWL_AGG_OFF;
 +              err = 0;
 +              break;
 +      default:
 +              IWL_ERR(mvm,
 +                      "Stopping AGG while state not ON or starting for %d on %d (%d)\n",
 +                      mvmsta->sta_id, tid, tid_data->state);
 +              IWL_ERR(mvm,
 +                      "\ttid_data->txq_id = %d\n", tid_data->txq_id);
 +              err = -EINVAL;
 +      }
 +
 +      spin_unlock_bh(&mvmsta->lock);
 +
 +      return err;
 +}
 +
 +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid];
 +      u16 txq_id;
 +      enum iwl_mvm_agg_state old_state;
 +
 +      /*
 +       * First set the agg state to OFF to avoid calling
 +       * ieee80211_stop_tx_ba_cb in iwl_mvm_check_ratid_empty.
 +       */
 +      spin_lock_bh(&mvmsta->lock);
 +      txq_id = tid_data->txq_id;
 +      IWL_DEBUG_TX_QUEUES(mvm, "Flush AGG: sta %d tid %d q %d state %d\n",
 +                          mvmsta->sta_id, tid, txq_id, tid_data->state);
 +      old_state = tid_data->state;
 +      tid_data->state = IWL_AGG_OFF;
 +      mvmsta->agg_tids &= ~BIT(tid);
 +      spin_unlock_bh(&mvmsta->lock);
 +
 +      /* No need to mark as reserved */
 +      spin_lock_bh(&mvm->queue_info_lock);
 +      mvm->queue_info[txq_id].setup_reserved = false;
 +      spin_unlock_bh(&mvm->queue_info_lock);
 +
 +      if (old_state >= IWL_AGG_ON) {
 +              iwl_mvm_drain_sta(mvm, mvmsta, true);
 +              if (iwl_mvm_flush_tx_path(mvm, BIT(txq_id), 0))
 +                      IWL_ERR(mvm, "Couldn't flush the AGG queue\n");
 +              iwl_trans_wait_tx_queue_empty(mvm->trans,
 +                                            mvmsta->tfd_queue_msk);
 +              iwl_mvm_drain_sta(mvm, mvmsta, false);
 +
 +              iwl_mvm_sta_tx_agg(mvm, sta, tid, txq_id, false);
 +
 +              iwl_mvm_disable_txq(mvm, tid_data->txq_id,
 +                                  vif->hw_queue[tid_to_mac80211_ac[tid]], tid,
 +                                  0);
 +      }
 +
 +      return 0;
 +}
 +
 +static int iwl_mvm_set_fw_key_idx(struct iwl_mvm *mvm)
 +{
 +      int i, max = -1, max_offs = -1;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* Pick the unused key offset with the highest 'deleted'
 +       * counter. Every time a key is deleted, all the counters
 +       * are incremented and the one that was just deleted is
 +       * reset to zero. Thus, the highest counter is the one
 +       * that was deleted longest ago. Pick that one.
 +       */
 +      for (i = 0; i < STA_KEY_MAX_NUM; i++) {
 +              if (test_bit(i, mvm->fw_key_table))
 +                      continue;
 +              if (mvm->fw_key_deleted[i] > max) {
 +                      max = mvm->fw_key_deleted[i];
 +                      max_offs = i;
 +              }
 +      }
 +
 +      if (max_offs < 0)
 +              return STA_KEY_IDX_INVALID;
 +
 +      __set_bit(max_offs, mvm->fw_key_table);
 +
 +      return max_offs;
 +}
 +
-           mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT)
-               return mvmvif->ap_sta_id;
++static u8 iwl_mvm_get_key_sta_id(struct iwl_mvm *mvm,
++                               struct ieee80211_vif *vif,
 +                               struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      if (sta) {
 +              struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +
 +              return mvm_sta->sta_id;
 +      }
 +
 +      /*
 +       * The device expects GTKs for station interfaces to be
 +       * installed as GTKs for the AP station. If we have no
 +       * station ID, then use AP's station ID.
 +       */
 +      if (vif->type == NL80211_IFTYPE_STATION &&
-                               u32 tkip_iv32, u16 *tkip_p1k, u32 cmd_flags)
++          mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) {
++              u8 sta_id = mvmvif->ap_sta_id;
++
++              sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
++                                              lockdep_is_held(&mvm->mutex));
++              /*
++               * It is possible that the 'sta' parameter is NULL,
++               * for example when a GTK is removed - the sta_id will then
++               * be the AP ID, and no station was passed by mac80211.
++               */
++              if (IS_ERR_OR_NULL(sta))
++                      return IWL_MVM_STATION_COUNT;
++
++              return sta_id;
++      }
 +
 +      return IWL_MVM_STATION_COUNT;
 +}
 +
 +static int iwl_mvm_send_sta_key(struct iwl_mvm *mvm,
 +                              struct iwl_mvm_sta *mvm_sta,
 +                              struct ieee80211_key_conf *keyconf, bool mcast,
-       cmd.key_offset = keyconf->hw_key_idx;
++                              u32 tkip_iv32, u16 *tkip_p1k, u32 cmd_flags,
++                              u8 key_offset)
 +{
 +      struct iwl_mvm_add_sta_key_cmd cmd = {};
 +      __le16 key_flags;
 +      int ret;
 +      u32 status;
 +      u16 keyidx;
 +      int i;
 +      u8 sta_id = mvm_sta->sta_id;
 +
 +      keyidx = (keyconf->keyidx << STA_KEY_FLG_KEYID_POS) &
 +               STA_KEY_FLG_KEYID_MSK;
 +      key_flags = cpu_to_le16(keyidx);
 +      key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_KEY_MAP);
 +
 +      switch (keyconf->cipher) {
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              key_flags |= cpu_to_le16(STA_KEY_FLG_TKIP);
 +              cmd.tkip_rx_tsc_byte2 = tkip_iv32;
 +              for (i = 0; i < 5; i++)
 +                      cmd.tkip_rx_ttak[i] = cpu_to_le16(tkip_p1k[i]);
 +              memcpy(cmd.key, keyconf->key, keyconf->keylen);
 +              break;
 +      case WLAN_CIPHER_SUITE_CCMP:
 +              key_flags |= cpu_to_le16(STA_KEY_FLG_CCM);
 +              memcpy(cmd.key, keyconf->key, keyconf->keylen);
 +              break;
 +      case WLAN_CIPHER_SUITE_WEP104:
 +              key_flags |= cpu_to_le16(STA_KEY_FLG_WEP_13BYTES);
 +              /* fall through */
 +      case WLAN_CIPHER_SUITE_WEP40:
 +              key_flags |= cpu_to_le16(STA_KEY_FLG_WEP);
 +              memcpy(cmd.key + 3, keyconf->key, keyconf->keylen);
 +              break;
 +      default:
 +              key_flags |= cpu_to_le16(STA_KEY_FLG_EXT);
 +              memcpy(cmd.key, keyconf->key, keyconf->keylen);
 +      }
 +
 +      if (mcast)
 +              key_flags |= cpu_to_le16(STA_KEY_MULTICAST);
 +
-                                          seq.tkip.iv32, p1k, 0);
++      cmd.key_offset = key_offset;
 +      cmd.key_flags = key_flags;
 +      cmd.sta_id = sta_id;
 +
 +      status = ADD_STA_SUCCESS;
 +      if (cmd_flags & CMD_ASYNC)
 +              ret =  iwl_mvm_send_cmd_pdu(mvm, ADD_STA_KEY, CMD_ASYNC,
 +                                          sizeof(cmd), &cmd);
 +      else
 +              ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, sizeof(cmd),
 +                                                &cmd, &status);
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_WEP(mvm, "MODIFY_STA: set dynamic key passed\n");
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "MODIFY_STA: set dynamic key failed\n");
 +              break;
 +      }
 +
 +      return ret;
 +}
 +
 +static int iwl_mvm_send_sta_igtk(struct iwl_mvm *mvm,
 +                               struct ieee80211_key_conf *keyconf,
 +                               u8 sta_id, bool remove_key)
 +{
 +      struct iwl_mvm_mgmt_mcast_key_cmd igtk_cmd = {};
 +
 +      /* verify the key details match the required command's expectations */
 +      if (WARN_ON((keyconf->cipher != WLAN_CIPHER_SUITE_AES_CMAC) ||
 +                  (keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE) ||
 +                  (keyconf->keyidx != 4 && keyconf->keyidx != 5)))
 +              return -EINVAL;
 +
 +      igtk_cmd.key_id = cpu_to_le32(keyconf->keyidx);
 +      igtk_cmd.sta_id = cpu_to_le32(sta_id);
 +
 +      if (remove_key) {
 +              igtk_cmd.ctrl_flags |= cpu_to_le32(STA_KEY_NOT_VALID);
 +      } else {
 +              struct ieee80211_key_seq seq;
 +              const u8 *pn;
 +
 +              memcpy(igtk_cmd.IGTK, keyconf->key, keyconf->keylen);
 +              ieee80211_get_key_rx_seq(keyconf, 0, &seq);
 +              pn = seq.aes_cmac.pn;
 +              igtk_cmd.receive_seq_cnt = cpu_to_le64(((u64) pn[5] << 0) |
 +                                                     ((u64) pn[4] << 8) |
 +                                                     ((u64) pn[3] << 16) |
 +                                                     ((u64) pn[2] << 24) |
 +                                                     ((u64) pn[1] << 32) |
 +                                                     ((u64) pn[0] << 40));
 +      }
 +
 +      IWL_DEBUG_INFO(mvm, "%s igtk for sta %u\n",
 +                     remove_key ? "removing" : "installing",
 +                     igtk_cmd.sta_id);
 +
 +      return iwl_mvm_send_cmd_pdu(mvm, MGMT_MCAST_KEY, 0,
 +                                  sizeof(igtk_cmd), &igtk_cmd);
 +}
 +
 +
 +static inline u8 *iwl_mvm_get_mac_addr(struct iwl_mvm *mvm,
 +                                     struct ieee80211_vif *vif,
 +                                     struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +
 +      if (sta)
 +              return sta->addr;
 +
 +      if (vif->type == NL80211_IFTYPE_STATION &&
 +          mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) {
 +              u8 sta_id = mvmvif->ap_sta_id;
 +              sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
 +                                              lockdep_is_held(&mvm->mutex));
 +              return sta->addr;
 +      }
 +
 +
 +      return NULL;
 +}
 +
 +static int __iwl_mvm_set_sta_key(struct iwl_mvm *mvm,
 +                               struct ieee80211_vif *vif,
 +                               struct ieee80211_sta *sta,
 +                               struct ieee80211_key_conf *keyconf,
++                               u8 key_offset,
 +                               bool mcast)
 +{
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      int ret;
 +      const u8 *addr;
 +      struct ieee80211_key_seq seq;
 +      u16 p1k[5];
 +
 +      switch (keyconf->cipher) {
 +      case WLAN_CIPHER_SUITE_TKIP:
 +              addr = iwl_mvm_get_mac_addr(mvm, vif, sta);
 +              /* get phase 1 key from mac80211 */
 +              ieee80211_get_key_rx_seq(keyconf, 0, &seq);
 +              ieee80211_get_tkip_rx_p1k(keyconf, addr, seq.tkip.iv32, p1k);
 +              ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, mcast,
-                                          0, NULL, 0);
++                                         seq.tkip.iv32, p1k, 0, key_offset);
 +              break;
 +      case WLAN_CIPHER_SUITE_CCMP:
 +      case WLAN_CIPHER_SUITE_WEP40:
 +      case WLAN_CIPHER_SUITE_WEP104:
 +              ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, mcast,
-                                          0, NULL, 0);
++                                         0, NULL, 0, key_offset);
 +              break;
 +      default:
 +              ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, mcast,
-                       bool have_key_offset)
++                                         0, NULL, 0, key_offset);
 +      }
 +
 +      return ret;
 +}
 +
 +static int __iwl_mvm_remove_sta_key(struct iwl_mvm *mvm, u8 sta_id,
 +                                  struct ieee80211_key_conf *keyconf,
 +                                  bool mcast)
 +{
 +      struct iwl_mvm_add_sta_key_cmd cmd = {};
 +      __le16 key_flags;
 +      int ret;
 +      u32 status;
 +
 +      key_flags = cpu_to_le16((keyconf->keyidx << STA_KEY_FLG_KEYID_POS) &
 +                               STA_KEY_FLG_KEYID_MSK);
 +      key_flags |= cpu_to_le16(STA_KEY_FLG_NO_ENC | STA_KEY_FLG_WEP_KEY_MAP);
 +      key_flags |= cpu_to_le16(STA_KEY_NOT_VALID);
 +
 +      if (mcast)
 +              key_flags |= cpu_to_le16(STA_KEY_MULTICAST);
 +
 +      cmd.key_flags = key_flags;
 +      cmd.key_offset = keyconf->hw_key_idx;
 +      cmd.sta_id = sta_id;
 +
 +      status = ADD_STA_SUCCESS;
 +      ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA_KEY, sizeof(cmd),
 +                                        &cmd, &status);
 +
 +      switch (status) {
 +      case ADD_STA_SUCCESS:
 +              IWL_DEBUG_WEP(mvm, "MODIFY_STA: remove sta key passed\n");
 +              break;
 +      default:
 +              ret = -EIO;
 +              IWL_ERR(mvm, "MODIFY_STA: remove sta key failed\n");
 +              break;
 +      }
 +
 +      return ret;
 +}
 +
 +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm,
 +                      struct ieee80211_vif *vif,
 +                      struct ieee80211_sta *sta,
 +                      struct ieee80211_key_conf *keyconf,
-       sta_id = iwl_mvm_get_key_sta_id(vif, sta);
++                      u8 key_offset)
 +{
 +      bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE);
 +      u8 sta_id;
 +      int ret;
 +      static const u8 __maybe_unused zero_addr[ETH_ALEN] = {0};
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* Get the station id from the mvm local station table */
-       if (!have_key_offset) {
-               /*
-                * The D3 firmware hardcodes the PTK offset to 0, so we have to
-                * configure it there. As a result, this workaround exists to
-                * let the caller set the key offset (hw_key_idx), see d3.c.
-                */
-               keyconf->hw_key_idx = iwl_mvm_set_fw_key_idx(mvm);
-               if (keyconf->hw_key_idx == STA_KEY_IDX_INVALID)
++      sta_id = iwl_mvm_get_key_sta_id(mvm, vif, sta);
 +      if (sta_id == IWL_MVM_STATION_COUNT) {
 +              IWL_ERR(mvm, "Failed to find station id\n");
 +              return -EINVAL;
 +      }
 +
 +      if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC) {
 +              ret = iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, false);
 +              goto end;
 +      }
 +
 +      /*
 +       * It is possible that the 'sta' parameter is NULL, and thus
 +       * there is a need to retrieve  the sta from the local station table.
 +       */
 +      if (!sta) {
 +              sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
 +                                              lockdep_is_held(&mvm->mutex));
 +              if (IS_ERR_OR_NULL(sta)) {
 +                      IWL_ERR(mvm, "Invalid station id\n");
 +                      return -EINVAL;
 +              }
 +      }
 +
 +      if (WARN_ON_ONCE(iwl_mvm_sta_from_mac80211(sta)->vif != vif))
 +              return -EINVAL;
 +
-       ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, mcast);
++      /* If the key_offset is not pre-assigned, we need to find a
++       * new offset to use.  In normal cases, the offset is not
++       * pre-assigned, but during HW_RESTART we want to reuse the
++       * same indices, so we pass them when this function is called.
++       *
++       * In D3 entry, we need to hardcoded the indices (because the
++       * firmware hardcodes the PTK offset to 0).  In this case, we
++       * need to make sure we don't overwrite the hw_key_idx in the
++       * keyconf structure, because otherwise we cannot configure
++       * the original ones back when resuming.
++       */
++      if (key_offset == STA_KEY_IDX_INVALID) {
++              key_offset  = iwl_mvm_set_fw_key_idx(mvm);
++              if (key_offset == STA_KEY_IDX_INVALID)
 +                      return -ENOSPC;
++              keyconf->hw_key_idx = key_offset;
 +      }
 +
-               ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, !mcast);
++      ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf, key_offset, mcast);
 +      if (ret) {
 +              __clear_bit(keyconf->hw_key_idx, mvm->fw_key_table);
 +              goto end;
 +      }
 +
 +      /*
 +       * For WEP, the same key is used for multicast and unicast. Upload it
 +       * again, using the same key offset, and now pointing the other one
 +       * to the same key slot (offset).
 +       * If this fails, remove the original as well.
 +       */
 +      if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 ||
 +          keyconf->cipher == WLAN_CIPHER_SUITE_WEP104) {
-       sta_id = iwl_mvm_get_key_sta_id(vif, sta);
++              ret = __iwl_mvm_set_sta_key(mvm, vif, sta, keyconf,
++                                          key_offset, !mcast);
 +              if (ret) {
 +                      __clear_bit(keyconf->hw_key_idx, mvm->fw_key_table);
 +                      __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast);
 +              }
 +      }
 +
 +end:
 +      IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n",
 +                    keyconf->cipher, keyconf->keylen, keyconf->keyidx,
 +                    sta ? sta->addr : zero_addr, ret);
 +      return ret;
 +}
 +
 +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm,
 +                         struct ieee80211_vif *vif,
 +                         struct ieee80211_sta *sta,
 +                         struct ieee80211_key_conf *keyconf)
 +{
 +      bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE);
 +      u8 sta_id;
 +      int ret, i;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* Get the station id from the mvm local station table */
-       /*
-        * It is possible that the 'sta' parameter is NULL, and thus
-        * there is a need to retrieve the sta from the local station table,
-        * for example when a GTK is removed (where the sta_id will then be
-        * the AP ID, and no station was passed by mac80211.)
-        */
-       if (!sta) {
-               sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id],
-                                               lockdep_is_held(&mvm->mutex));
-               if (!sta) {
-                       IWL_ERR(mvm, "Invalid station id\n");
-                       return -EINVAL;
-               }
-       }
-       if (WARN_ON_ONCE(iwl_mvm_sta_from_mac80211(sta)->vif != vif))
-               return -EINVAL;
++      sta_id = iwl_mvm_get_key_sta_id(mvm, vif, sta);
 +
 +      IWL_DEBUG_WEP(mvm, "mvm remove dynamic key: idx=%d sta=%d\n",
 +                    keyconf->keyidx, sta_id);
 +
 +      if (keyconf->cipher == WLAN_CIPHER_SUITE_AES_CMAC)
 +              return iwl_mvm_send_sta_igtk(mvm, keyconf, sta_id, true);
 +
 +      if (!__test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table)) {
 +              IWL_ERR(mvm, "offset %d not used in fw key table.\n",
 +                      keyconf->hw_key_idx);
 +              return -ENOENT;
 +      }
 +
 +      /* track which key was deleted last */
 +      for (i = 0; i < STA_KEY_MAX_NUM; i++) {
 +              if (mvm->fw_key_deleted[i] < U8_MAX)
 +                      mvm->fw_key_deleted[i]++;
 +      }
 +      mvm->fw_key_deleted[keyconf->hw_key_idx] = 0;
 +
 +      if (sta_id == IWL_MVM_STATION_COUNT) {
 +              IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n");
 +              return 0;
 +      }
 +
-       u8 sta_id = iwl_mvm_get_key_sta_id(vif, sta);
 +      ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, mcast);
 +      if (ret)
 +              return ret;
 +
 +      /* delete WEP key twice to get rid of (now useless) offset */
 +      if (keyconf->cipher == WLAN_CIPHER_SUITE_WEP40 ||
 +          keyconf->cipher == WLAN_CIPHER_SUITE_WEP104)
 +              ret = __iwl_mvm_remove_sta_key(mvm, sta_id, keyconf, !mcast);
 +
 +      return ret;
 +}
 +
 +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm,
 +                           struct ieee80211_vif *vif,
 +                           struct ieee80211_key_conf *keyconf,
 +                           struct ieee80211_sta *sta, u32 iv32,
 +                           u16 *phase1key)
 +{
 +      struct iwl_mvm_sta *mvm_sta;
-                            iv32, phase1key, CMD_ASYNC);
++      u8 sta_id = iwl_mvm_get_key_sta_id(mvm, vif, sta);
 +      bool mcast = !(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE);
 +
 +      if (WARN_ON_ONCE(sta_id == IWL_MVM_STATION_COUNT))
 +              return;
 +
 +      rcu_read_lock();
 +
 +      if (!sta) {
 +              sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]);
 +              if (WARN_ON(IS_ERR_OR_NULL(sta))) {
 +                      rcu_read_unlock();
 +                      return;
 +              }
 +      }
 +
 +      mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +      iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, mcast,
++                           iv32, phase1key, CMD_ASYNC, keyconf->hw_key_idx);
 +      rcu_read_unlock();
 +}
 +
 +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm,
 +                              struct ieee80211_sta *sta)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_add_sta_cmd cmd = {
 +              .add_modify = STA_MODE_MODIFY,
 +              .sta_id = mvmsta->sta_id,
 +              .station_flags_msk = cpu_to_le32(STA_FLG_PS),
 +              .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color),
 +      };
 +      int ret;
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, sizeof(cmd), &cmd);
 +      if (ret)
 +              IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret);
 +}
 +
 +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm,
 +                                     struct ieee80211_sta *sta,
 +                                     enum ieee80211_frame_release_type reason,
 +                                     u16 cnt, u16 tids, bool more_data,
 +                                     bool agg)
 +{
 +      struct iwl_mvm_sta *mvmsta = iwl_mvm_sta_from_mac80211(sta);
 +      struct iwl_mvm_add_sta_cmd cmd = {
 +              .add_modify = STA_MODE_MODIFY,
 +              .sta_id = mvmsta->sta_id,
 +              .modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT,
 +              .sleep_tx_count = cpu_to_le16(cnt),
 +              .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color),
 +      };
 +      int tid, ret;
 +      unsigned long _tids = tids;
 +
 +      /* convert TIDs to ACs - we don't support TSPEC so that's OK
 +       * Note that this field is reserved and unused by firmware not
 +       * supporting GO uAPSD, so it's safe to always do this.
 +       */
 +      for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT)
 +              cmd.awake_acs |= BIT(tid_to_ucode_ac[tid]);
 +
 +      /* If we're releasing frames from aggregation queues then check if the
 +       * all queues combined that we're releasing frames from have
 +       *  - more frames than the service period, in which case more_data
 +       *    needs to be set
 +       *  - fewer than 'cnt' frames, in which case we need to adjust the
 +       *    firmware command (but do that unconditionally)
 +       */
 +      if (agg) {
 +              int remaining = cnt;
 +
 +              spin_lock_bh(&mvmsta->lock);
 +              for_each_set_bit(tid, &_tids, IWL_MAX_TID_COUNT) {
 +                      struct iwl_mvm_tid_data *tid_data;
 +                      u16 n_queued;
 +
 +                      tid_data = &mvmsta->tid_data[tid];
 +                      if (WARN(tid_data->state != IWL_AGG_ON &&
 +                               tid_data->state != IWL_EMPTYING_HW_QUEUE_DELBA,
 +                               "TID %d state is %d\n",
 +                               tid, tid_data->state)) {
 +                              spin_unlock_bh(&mvmsta->lock);
 +                              ieee80211_sta_eosp(sta);
 +                              return;
 +                      }
 +
 +                      n_queued = iwl_mvm_tid_queued(tid_data);
 +                      if (n_queued > remaining) {
 +                              more_data = true;
 +                              remaining = 0;
 +                              break;
 +                      }
 +                      remaining -= n_queued;
 +              }
 +              spin_unlock_bh(&mvmsta->lock);
 +
 +              cmd.sleep_tx_count = cpu_to_le16(cnt - remaining);
 +              if (WARN_ON(cnt - remaining == 0)) {
 +                      ieee80211_sta_eosp(sta);
 +                      return;
 +              }
 +      }
 +
 +      /* Note: this is ignored by firmware not supporting GO uAPSD */
 +      if (more_data)
 +              cmd.sleep_state_flags |= cpu_to_le16(STA_SLEEP_STATE_MOREDATA);
 +
 +      if (reason == IEEE80211_FRAME_RELEASE_PSPOLL) {
 +              mvmsta->next_status_eosp = true;
 +              cmd.sleep_state_flags |= cpu_to_le16(STA_SLEEP_STATE_PS_POLL);
 +      } else {
 +              cmd.sleep_state_flags |= cpu_to_le16(STA_SLEEP_STATE_UAPSD);
 +      }
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, sizeof(cmd), &cmd);
 +      if (ret)
 +              IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret);
 +}
 +
 +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm,
 +                         struct iwl_rx_cmd_buffer *rxb)
 +{
 +      struct iwl_rx_packet *pkt = rxb_addr(rxb);
 +      struct iwl_mvm_eosp_notification *notif = (void *)pkt->data;
 +      struct ieee80211_sta *sta;
 +      u32 sta_id = le32_to_cpu(notif->sta_id);
 +
 +      if (WARN_ON_ONCE(sta_id >= IWL_MVM_STATION_COUNT))
 +              return;
 +
 +      rcu_read_lock();
 +      sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]);
 +      if (!IS_ERR_OR_NULL(sta))
 +              ieee80211_sta_eosp(sta);
 +      rcu_read_unlock();
 +}
 +
 +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm,
 +                                 struct iwl_mvm_sta *mvmsta, bool disable)
 +{
 +      struct iwl_mvm_add_sta_cmd cmd = {
 +              .add_modify = STA_MODE_MODIFY,
 +              .sta_id = mvmsta->sta_id,
 +              .station_flags = disable ? cpu_to_le32(STA_FLG_DISABLE_TX) : 0,
 +              .station_flags_msk = cpu_to_le32(STA_FLG_DISABLE_TX),
 +              .mac_id_n_color = cpu_to_le32(mvmsta->mac_id_n_color),
 +      };
 +      int ret;
 +
 +      ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, sizeof(cmd), &cmd);
 +      if (ret)
 +              IWL_ERR(mvm, "Failed to send ADD_STA command (%d)\n", ret);
 +}
 +
 +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm,
 +                                    struct ieee80211_sta *sta,
 +                                    bool disable)
 +{
 +      struct iwl_mvm_sta *mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +
 +      spin_lock_bh(&mvm_sta->lock);
 +
 +      if (mvm_sta->disable_tx == disable) {
 +              spin_unlock_bh(&mvm_sta->lock);
 +              return;
 +      }
 +
 +      mvm_sta->disable_tx = disable;
 +
 +      /*
 +       * Tell mac80211 to start/stop queuing tx for this station,
 +       * but don't stop queuing if there are still pending frames
 +       * for this station.
 +       */
 +      if (disable || !atomic_read(&mvm->pending_frames[mvm_sta->sta_id]))
 +              ieee80211_sta_block_awake(mvm->hw, sta, disable);
 +
 +      iwl_mvm_sta_modify_disable_tx(mvm, mvm_sta, disable);
 +
 +      spin_unlock_bh(&mvm_sta->lock);
 +}
 +
 +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm,
 +                                     struct iwl_mvm_vif *mvmvif,
 +                                     bool disable)
 +{
 +      struct ieee80211_sta *sta;
 +      struct iwl_mvm_sta *mvm_sta;
 +      int i;
 +
 +      lockdep_assert_held(&mvm->mutex);
 +
 +      /* Block/unblock all the stations of the given mvmvif */
 +      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;
 +
 +              mvm_sta = iwl_mvm_sta_from_mac80211(sta);
 +              if (mvm_sta->mac_id_n_color !=
 +                  FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color))
 +                      continue;
 +
 +              iwl_mvm_sta_modify_disable_tx_ap(mvm, sta, disable);
 +      }
 +}
 +
 +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
 +{
 +      struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
 +      struct iwl_mvm_sta *mvmsta;
 +
 +      rcu_read_lock();
 +
 +      mvmsta = iwl_mvm_sta_from_staid_rcu(mvm, mvmvif->ap_sta_id);
 +
 +      if (!WARN_ON(!mvmsta))
 +              iwl_mvm_sta_modify_disable_tx(mvm, mvmsta, true);
 +
 +      rcu_read_unlock();
 +}
index eedb215,0000000..0631cc0
mode 100644,000000..100644
--- /dev/null
@@@ -1,426 -1,0 +1,426 @@@
-                       struct ieee80211_key_conf *key,
-                       bool have_key_offset);
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * 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
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +
 +#ifndef __sta_h__
 +#define __sta_h__
 +
 +#include <linux/spinlock.h>
 +#include <net/mac80211.h>
 +#include <linux/wait.h>
 +
 +#include "iwl-trans.h" /* for IWL_MAX_TID_COUNT */
 +#include "fw-api.h" /* IWL_MVM_STATION_COUNT */
 +#include "rs.h"
 +
 +struct iwl_mvm;
 +struct iwl_mvm_vif;
 +
 +/**
 + * DOC: station table - introduction
 + *
 + * The station table is a list of data structure that reprensent the stations.
 + * In STA/P2P client mode, the driver will hold one station for the AP/ GO.
 + * In GO/AP mode, the driver will have as many stations as associated clients.
 + * All these stations are reflected in the fw's station table. The driver
 + * keeps the fw's station table up to date with the ADD_STA command. Stations
 + * can be removed by the REMOVE_STA command.
 + *
 + * All the data related to a station is held in the structure %iwl_mvm_sta
 + * which is embed in the mac80211's %ieee80211_sta (in the drv_priv) area.
 + * This data includes the index of the station in the fw, per tid information
 + * (sequence numbers, Block-ack state machine, etc...). The stations are
 + * created and deleted by the %sta_state callback from %ieee80211_ops.
 + *
 + * The driver holds a map: %fw_id_to_mac_id that allows to fetch a
 + * %ieee80211_sta (and the %iwl_mvm_sta embedded into it) based on a fw
 + * station index. That way, the driver is able to get the tid related data in
 + * O(1) in time sensitive paths (Tx / Tx response / BA notification). These
 + * paths are triggered by the fw, and the driver needs to get a pointer to the
 + * %ieee80211 structure. This map helps to get that pointer quickly.
 + */
 +
 +/**
 + * DOC: station table - locking
 + *
 + * As stated before, the station is created / deleted by mac80211's %sta_state
 + * callback from %ieee80211_ops which can sleep. The next paragraph explains
 + * the locking of a single stations, the next ones relates to the station
 + * table.
 + *
 + * The station holds the sequence number per tid. So this data needs to be
 + * accessed in the Tx path (which is softIRQ). It also holds the Block-Ack
 + * information (the state machine / and the logic that checks if the queues
 + * were drained), so it also needs to be accessible from the Tx response flow.
 + * In short, the station needs to be access from sleepable context as well as
 + * from tasklets, so the station itself needs a spinlock.
 + *
 + * The writers of %fw_id_to_mac_id map are serialized by the global mutex of
 + * the mvm op_mode. This is possible since %sta_state can sleep.
 + * The pointers in this map are RCU protected, hence we won't replace the
 + * station while we have Tx / Tx response / BA notification running.
 + *
 + * If a station is deleted while it still has packets in its A-MPDU queues,
 + * then the reclaim flow will notice that there is no station in the map for
 + * sta_id and it will dump the responses.
 + */
 +
 +/**
 + * DOC: station table - internal stations
 + *
 + * The FW needs a few internal stations that are not reflected in
 + * mac80211, such as broadcast station in AP / GO mode, or AUX sta for
 + * scanning and P2P device (during the GO negotiation).
 + * For these kind of stations we have %iwl_mvm_int_sta struct which holds the
 + * data relevant for them from both %iwl_mvm_sta and %ieee80211_sta.
 + * Usually the data for these stations is static, so no locking is required,
 + * and no TID data as this is also not needed.
 + * One thing to note, is that these stations have an ID in the fw, but not
 + * in mac80211. In order to "reserve" them a sta_id in %fw_id_to_mac_id
 + * we fill ERR_PTR(EINVAL) in this mapping and all other dereferencing of
 + * pointers from this mapping need to check that the value is not error
 + * or NULL.
 + *
 + * Currently there is only one auxiliary station for scanning, initialized
 + * on init.
 + */
 +
 +/**
 + * DOC: station table - AP Station in STA mode
 + *
 + * %iwl_mvm_vif includes the index of the AP station in the fw's STA table:
 + * %ap_sta_id. To get the point to the corresponding %ieee80211_sta,
 + * &fw_id_to_mac_id can be used. Due to the way the fw works, we must not remove
 + * the AP station from the fw before setting the MAC context as unassociated.
 + * Hence, %fw_id_to_mac_id[%ap_sta_id] will be NULLed when the AP station is
 + * removed by mac80211, but the station won't be removed in the fw until the
 + * VIF is set as unassociated. Then, %ap_sta_id will be invalidated.
 + */
 +
 +/**
 + * DOC: station table - Drain vs. Flush
 + *
 + * Flush means that all the frames in the SCD queue are dumped regardless the
 + * station to which they were sent. We do that when we disassociate and before
 + * we remove the STA of the AP. The flush can be done synchronously against the
 + * fw.
 + * Drain means that the fw will drop all the frames sent to a specific station.
 + * This is useful when a client (if we are IBSS / GO or AP) disassociates. In
 + * that case, we need to drain all the frames for that client from the AC queues
 + * that are shared with the other clients. Only then, we can remove the STA in
 + * the fw. In order to do so, we track the non-AMPDU packets for each station.
 + * If mac80211 removes a STA and if it still has non-AMPDU packets pending in
 + * the queues, we mark this station as %EBUSY in %fw_id_to_mac_id, and drop all
 + * the frames for this STA (%iwl_mvm_rm_sta). When the last frame is dropped
 + * (we know about it with its Tx response), we remove the station in fw and set
 + * it as %NULL in %fw_id_to_mac_id: this is the purpose of
 + * %iwl_mvm_sta_drained_wk.
 + */
 +
 +/**
 + * DOC: station table - fw restart
 + *
 + * When the fw asserts, or we have any other issue that requires to reset the
 + * driver, we require mac80211 to reconfigure the driver. Since the private
 + * data of the stations is embed in mac80211's %ieee80211_sta, that data will
 + * not be zeroed and needs to be reinitialized manually.
 + * %IWL_MVM_STATUS_IN_HW_RESTART is set during restart and that will hint us
 + * that we must not allocate a new sta_id but reuse the previous one. This
 + * means that the stations being re-added after the reset will have the same
 + * place in the fw as before the reset. We do need to zero the %fw_id_to_mac_id
 + * map, since the stations aren't in the fw any more. Internal stations that
 + * are not added by mac80211 will be re-added in the init flow that is called
 + * after the restart: mac80211 call's %iwl_mvm_mac_start which calls to
 + * %iwl_mvm_up.
 + */
 +
 +/**
 + * DOC: AP mode - PS
 + *
 + * When a station is asleep, the fw will set it as "asleep". All frames on
 + * shared queues (i.e. non-aggregation queues) to that station will be dropped
 + * by the fw (%TX_STATUS_FAIL_DEST_PS failure code).
 + *
 + * AMPDUs are in a separate queue that is stopped by the fw. We just need to
 + * let mac80211 know when there are frames in these queues so that it can
 + * properly handle trigger frames.
 + *
 + * When a trigger frame is received, mac80211 tells the driver to send frames
 + * from the AMPDU queues or sends frames to non-aggregation queues itself,
 + * depending on which ACs are delivery-enabled and what TID has frames to
 + * transmit. Note that mac80211 has all the knowledge since all the non-agg
 + * frames are buffered / filtered, and the driver tells mac80211 about agg
 + * frames). The driver needs to tell the fw to let frames out even if the
 + * station is asleep. This is done by %iwl_mvm_sta_modify_sleep_tx_count.
 + *
 + * When we receive a frame from that station with PM bit unset, the driver
 + * needs to let the fw know that this station isn't asleep any more. This is
 + * done by %iwl_mvm_sta_modify_ps_wake in response to mac80211 signaling the
 + * station's wakeup.
 + *
 + * For a GO, the Service Period might be cut short due to an absence period
 + * of the GO. In this (and all other cases) the firmware notifies us with the
 + * EOSP_NOTIFICATION, and we notify mac80211 of that. Further frames that we
 + * already sent to the device will be rejected again.
 + *
 + * See also "AP support for powersaving clients" in mac80211.h.
 + */
 +
 +/**
 + * enum iwl_mvm_agg_state
 + *
 + * The state machine of the BA agreement establishment / tear down.
 + * These states relate to a specific RA / TID.
 + *
 + * @IWL_AGG_OFF: aggregation is not used
 + * @IWL_AGG_STARTING: aggregation are starting (between start and oper)
 + * @IWL_AGG_ON: aggregation session is up
 + * @IWL_EMPTYING_HW_QUEUE_ADDBA: establishing a BA session - waiting for the
 + *    HW queue to be empty from packets for this RA /TID.
 + * @IWL_EMPTYING_HW_QUEUE_DELBA: tearing down a BA session - waiting for the
 + *    HW queue to be empty from packets for this RA /TID.
 + */
 +enum iwl_mvm_agg_state {
 +      IWL_AGG_OFF = 0,
 +      IWL_AGG_STARTING,
 +      IWL_AGG_ON,
 +      IWL_EMPTYING_HW_QUEUE_ADDBA,
 +      IWL_EMPTYING_HW_QUEUE_DELBA,
 +};
 +
 +/**
 + * struct iwl_mvm_tid_data - holds the states for each RA / TID
 + * @seq_number: the next WiFi sequence number to use
 + * @next_reclaimed: the WiFi sequence number of the next packet to be acked.
 + *    This is basically (last acked packet++).
 + * @rate_n_flags: Rate at which Tx was attempted. Holds the data between the
 + *    Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA).
 + * @reduced_tpc: Reduced tx power. Holds the data between the
 + *    Tx response (TX_CMD), and the block ack notification (COMPRESSED_BA).
 + * @state: state of the BA agreement establishment / tear down.
 + * @txq_id: Tx queue used by the BA session
 + * @ssn: the first packet to be sent in AGG HW queue in Tx AGG start flow, or
 + *    the first packet to be sent in legacy HW queue in Tx AGG stop flow.
 + *    Basically when next_reclaimed reaches ssn, we can tell mac80211 that
 + *    we are ready to finish the Tx AGG stop / start flow.
 + * @tx_time: medium time consumed by this A-MPDU
 + */
 +struct iwl_mvm_tid_data {
 +      u16 seq_number;
 +      u16 next_reclaimed;
 +      /* The rest is Tx AGG related */
 +      u32 rate_n_flags;
 +      u8 reduced_tpc;
 +      enum iwl_mvm_agg_state state;
 +      u16 txq_id;
 +      u16 ssn;
 +      u16 tx_time;
 +};
 +
 +static inline u16 iwl_mvm_tid_queued(struct iwl_mvm_tid_data *tid_data)
 +{
 +      return ieee80211_sn_sub(IEEE80211_SEQ_TO_SN(tid_data->seq_number),
 +                              tid_data->next_reclaimed);
 +}
 +
 +/**
 + * struct iwl_mvm_sta - representation of a station in the driver
 + * @sta_id: the index of the station in the fw (will be replaced by id_n_color)
 + * @tfd_queue_msk: the tfd queues used by the station
 + * @hw_queue: per-AC mapping of the TFD queues used by station
 + * @mac_id_n_color: the MAC context this station is linked to
 + * @tid_disable_agg: bitmap: if bit(tid) is set, the fw won't send ampdus for
 + *    tid.
 + * @max_agg_bufsize: the maximal size of the AGG buffer for this station
 + * @bt_reduced_txpower: is reduced tx power enabled for this station
 + * @next_status_eosp: the next reclaimed packet is a PS-Poll response and
 + *    we need to signal the EOSP
 + * @lock: lock to protect the whole struct. Since %tid_data is access from Tx
 + * and from Tx response flow, it needs a spinlock.
 + * @tid_data: per tid data. Look at %iwl_mvm_tid_data.
 + * @tx_protection: reference counter for controlling the Tx protection.
 + * @tt_tx_protection: is thermal throttling enable Tx protection?
 + * @disable_tx: is tx to this STA disabled?
 + * @agg_tids: bitmap of tids whose status is operational aggregated (IWL_AGG_ON)
 + *
 + * When mac80211 creates a station it reserves some space (hw->sta_data_size)
 + * in the structure for use by driver. This structure is placed in that
 + * space.
 + *
 + */
 +struct iwl_mvm_sta {
 +      u32 sta_id;
 +      u32 tfd_queue_msk;
 +      u8 hw_queue[IEEE80211_NUM_ACS];
 +      u32 mac_id_n_color;
 +      u16 tid_disable_agg;
 +      u8 max_agg_bufsize;
 +      bool bt_reduced_txpower;
 +      bool next_status_eosp;
 +      spinlock_t lock;
 +      struct iwl_mvm_tid_data tid_data[IWL_MAX_TID_COUNT];
 +      struct iwl_lq_sta lq_sta;
 +      struct ieee80211_vif *vif;
 +
 +      /* Temporary, until the new TLC will control the Tx protection */
 +      s8 tx_protection;
 +      bool tt_tx_protection;
 +
 +      bool disable_tx;
 +      u8 agg_tids;
 +};
 +
 +static inline struct iwl_mvm_sta *
 +iwl_mvm_sta_from_mac80211(struct ieee80211_sta *sta)
 +{
 +      return (void *)sta->drv_priv;
 +}
 +
 +/**
 + * struct iwl_mvm_int_sta - representation of an internal station (auxiliary or
 + * broadcast)
 + * @sta_id: the index of the station in the fw (will be replaced by id_n_color)
 + * @tfd_queue_msk: the tfd queues used by the station
 + */
 +struct iwl_mvm_int_sta {
 +      u32 sta_id;
 +      u32 tfd_queue_msk;
 +};
 +
 +int iwl_mvm_sta_send_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
 +                         bool update);
 +int iwl_mvm_add_sta(struct iwl_mvm *mvm,
 +                  struct ieee80211_vif *vif,
 +                  struct ieee80211_sta *sta);
 +int iwl_mvm_update_sta(struct iwl_mvm *mvm,
 +                     struct ieee80211_vif *vif,
 +                     struct ieee80211_sta *sta);
 +int iwl_mvm_rm_sta(struct iwl_mvm *mvm,
 +                 struct ieee80211_vif *vif,
 +                 struct ieee80211_sta *sta);
 +int iwl_mvm_rm_sta_id(struct iwl_mvm *mvm,
 +                    struct ieee80211_vif *vif,
 +                    u8 sta_id);
 +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm,
 +                      struct ieee80211_vif *vif,
 +                      struct ieee80211_sta *sta,
++                      struct ieee80211_key_conf *keyconf,
++                      u8 key_offset);
 +int iwl_mvm_remove_sta_key(struct iwl_mvm *mvm,
 +                         struct ieee80211_vif *vif,
 +                         struct ieee80211_sta *sta,
 +                         struct ieee80211_key_conf *keyconf);
 +
 +void iwl_mvm_update_tkip_key(struct iwl_mvm *mvm,
 +                           struct ieee80211_vif *vif,
 +                           struct ieee80211_key_conf *keyconf,
 +                           struct ieee80211_sta *sta, u32 iv32,
 +                           u16 *phase1key);
 +
 +void iwl_mvm_rx_eosp_notif(struct iwl_mvm *mvm,
 +                         struct iwl_rx_cmd_buffer *rxb);
 +
 +/* AMPDU */
 +int iwl_mvm_sta_rx_agg(struct iwl_mvm *mvm, struct ieee80211_sta *sta,
 +                     int tid, u16 ssn, bool start);
 +int iwl_mvm_sta_tx_agg_start(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                      struct ieee80211_sta *sta, u16 tid, u16 *ssn);
 +int iwl_mvm_sta_tx_agg_oper(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                      struct ieee80211_sta *sta, u16 tid, u8 buf_size);
 +int iwl_mvm_sta_tx_agg_stop(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid);
 +int iwl_mvm_sta_tx_agg_flush(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
 +                          struct ieee80211_sta *sta, u16 tid);
 +
 +int iwl_mvm_add_aux_sta(struct iwl_mvm *mvm);
 +void iwl_mvm_del_aux_sta(struct iwl_mvm *mvm);
 +
 +int iwl_mvm_alloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +int iwl_mvm_send_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +int iwl_mvm_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +int iwl_mvm_rm_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +void iwl_mvm_dealloc_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +
 +void iwl_mvm_sta_drained_wk(struct work_struct *wk);
 +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm,
 +                              struct ieee80211_sta *sta);
 +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm,
 +                                     struct ieee80211_sta *sta,
 +                                     enum ieee80211_frame_release_type reason,
 +                                     u16 cnt, u16 tids, bool more_data,
 +                                     bool agg);
 +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta,
 +                    bool drain);
 +void iwl_mvm_sta_modify_disable_tx(struct iwl_mvm *mvm,
 +                                 struct iwl_mvm_sta *mvmsta, bool disable);
 +void iwl_mvm_sta_modify_disable_tx_ap(struct iwl_mvm *mvm,
 +                                    struct ieee80211_sta *sta,
 +                                    bool disable);
 +void iwl_mvm_modify_all_sta_disable_tx(struct iwl_mvm *mvm,
 +                                     struct iwl_mvm_vif *mvmvif,
 +                                     bool disable);
 +void iwl_mvm_csa_client_absent(struct iwl_mvm *mvm, struct ieee80211_vif *vif);
 +
 +#endif /* __sta_h__ */
index 644b58b,0000000..639761f
mode 100644,000000..100644
--- /dev/null
@@@ -1,685 -1,0 +1,702 @@@
-       {IWL_PCI_DEVICE(0x24F4, 0x1130, iwl8260_2ac_cfg)},
 +/******************************************************************************
 + *
 + * This file is provided under a dual BSD/GPLv2 license.  When using or
 + * redistributing this file, you may do so under either license.
 + *
 + * GPL LICENSE SUMMARY
 + *
 + * Copyright(c) 2007 - 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 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
 + * published by the Free Software Foundation.
 + *
 + * This program is distributed in the hope that it will be useful, but
 + * WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 + * USA
 + *
 + * The full GNU General Public License is included in this distribution
 + * in the file called COPYING.
 + *
 + * Contact Information:
 + *  Intel Linux Wireless <ilw@linux.intel.com>
 + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 + *
 + * BSD LICENSE
 + *
 + * Copyright(c) 2005 - 2014 Intel Corporation. All rights reserved.
 + * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
 + * All rights reserved.
 + *
 + * Redistribution and use in source and binary forms, with or without
 + * modification, are permitted provided that the following conditions
 + * are met:
 + *
 + *  * Redistributions of source code must retain the above copyright
 + *    notice, this list of conditions and the following disclaimer.
 + *  * Redistributions in binary form must reproduce the above copyright
 + *    notice, this list of conditions and the following disclaimer in
 + *    the documentation and/or other materials provided with the
 + *    distribution.
 + *  * Neither the name Intel Corporation nor the names of its
 + *    contributors may be used to endorse or promote products derived
 + *    from this software without specific prior written permission.
 + *
 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 + *
 + *****************************************************************************/
 +
 +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 +
 +#include <linux/module.h>
 +#include <linux/pci.h>
 +#include <linux/pci-aspm.h>
 +#include <linux/acpi.h>
 +
 +#include "iwl-trans.h"
 +#include "iwl-drv.h"
 +#include "internal.h"
 +
 +#define IWL_PCI_DEVICE(dev, subdev, cfg) \
 +      .vendor = PCI_VENDOR_ID_INTEL,  .device = (dev), \
 +      .subvendor = PCI_ANY_ID, .subdevice = (subdev), \
 +      .driver_data = (kernel_ulong_t)&(cfg)
 +
 +/* Hardware specific file defines the PCI IDs table for that hardware module */
 +static const struct pci_device_id iwl_hw_card_ids[] = {
 +#if IS_ENABLED(CONFIG_IWLDVM)
 +      {IWL_PCI_DEVICE(0x4232, 0x1201, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1301, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1204, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1304, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1205, iwl5100_bgn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1305, iwl5100_bgn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1206, iwl5100_abg_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1306, iwl5100_abg_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1221, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1321, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1224, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1324, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1225, iwl5100_bgn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1325, iwl5100_bgn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1226, iwl5100_abg_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4232, 0x1326, iwl5100_abg_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1211, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1311, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1214, iwl5100_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1314, iwl5100_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1215, iwl5100_bgn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1315, iwl5100_bgn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1216, iwl5100_abg_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4237, 0x1316, iwl5100_abg_cfg)}, /* Half Mini Card */
 +
 +/* 5300 Series WiFi */
 +      {IWL_PCI_DEVICE(0x4235, 0x1021, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1121, iwl5300_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1024, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1124, iwl5300_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1001, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1101, iwl5300_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1004, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4235, 0x1104, iwl5300_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4236, 0x1011, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4236, 0x1111, iwl5300_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x4236, 0x1014, iwl5300_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x4236, 0x1114, iwl5300_agn_cfg)}, /* Half Mini Card */
 +
 +/* 5350 Series WiFi/WiMax */
 +      {IWL_PCI_DEVICE(0x423A, 0x1001, iwl5350_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423A, 0x1021, iwl5350_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423B, 0x1011, iwl5350_agn_cfg)}, /* Mini Card */
 +
 +/* 5150 Series Wifi/WiMax */
 +      {IWL_PCI_DEVICE(0x423C, 0x1201, iwl5150_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1301, iwl5150_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1206, iwl5150_abg_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1306, iwl5150_abg_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1221, iwl5150_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1321, iwl5150_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x423C, 0x1326, iwl5150_abg_cfg)}, /* Half Mini Card */
 +
 +      {IWL_PCI_DEVICE(0x423D, 0x1211, iwl5150_agn_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423D, 0x1311, iwl5150_agn_cfg)}, /* Half Mini Card */
 +      {IWL_PCI_DEVICE(0x423D, 0x1216, iwl5150_abg_cfg)}, /* Mini Card */
 +      {IWL_PCI_DEVICE(0x423D, 0x1316, iwl5150_abg_cfg)}, /* Half Mini Card */
 +
 +/* 6x00 Series */
 +      {IWL_PCI_DEVICE(0x422B, 0x1101, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422B, 0x1108, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422B, 0x1121, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422B, 0x1128, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422C, 0x1301, iwl6000i_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422C, 0x1306, iwl6000i_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x422C, 0x1307, iwl6000i_2bg_cfg)},
 +      {IWL_PCI_DEVICE(0x422C, 0x1321, iwl6000i_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x422C, 0x1326, iwl6000i_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x4238, 0x1111, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x4238, 0x1118, iwl6000_3agn_cfg)},
 +      {IWL_PCI_DEVICE(0x4239, 0x1311, iwl6000i_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x4239, 0x1316, iwl6000i_2abg_cfg)},
 +
 +/* 6x05 Series */
 +      {IWL_PCI_DEVICE(0x0082, 0x1301, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1306, iwl6005_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1307, iwl6005_2bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1308, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1321, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1326, iwl6005_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1328, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0085, 0x1311, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0085, 0x1318, iwl6005_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0085, 0x1316, iwl6005_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0xC020, iwl6005_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x0085, 0xC220, iwl6005_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x0085, 0xC228, iwl6005_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x4820, iwl6005_2agn_d_cfg)},
 +      {IWL_PCI_DEVICE(0x0082, 0x1304, iwl6005_2agn_mow1_cfg)},/* low 5GHz active */
 +      {IWL_PCI_DEVICE(0x0082, 0x1305, iwl6005_2agn_mow2_cfg)},/* high 5GHz active */
 +
 +/* 6x30 Series */
 +      {IWL_PCI_DEVICE(0x008A, 0x5305, iwl1030_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x008A, 0x5307, iwl1030_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x008A, 0x5325, iwl1030_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x008A, 0x5327, iwl1030_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x008B, 0x5315, iwl1030_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x008B, 0x5317, iwl1030_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0090, 0x5211, iwl6030_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0090, 0x5215, iwl6030_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0090, 0x5216, iwl6030_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5201, iwl6030_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5205, iwl6030_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5206, iwl6030_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5207, iwl6030_2bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5221, iwl6030_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5225, iwl6030_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0091, 0x5226, iwl6030_2abg_cfg)},
 +
 +/* 6x50 WiFi/WiMax Series */
 +      {IWL_PCI_DEVICE(0x0087, 0x1301, iwl6050_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0087, 0x1306, iwl6050_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0087, 0x1321, iwl6050_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0087, 0x1326, iwl6050_2abg_cfg)},
 +      {IWL_PCI_DEVICE(0x0089, 0x1311, iwl6050_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x0089, 0x1316, iwl6050_2abg_cfg)},
 +
 +/* 6150 WiFi/WiMax Series */
 +      {IWL_PCI_DEVICE(0x0885, 0x1305, iwl6150_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0885, 0x1307, iwl6150_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0885, 0x1325, iwl6150_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0885, 0x1327, iwl6150_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0886, 0x1315, iwl6150_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0886, 0x1317, iwl6150_bg_cfg)},
 +
 +/* 1000 Series WiFi */
 +      {IWL_PCI_DEVICE(0x0083, 0x1205, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1305, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1225, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1325, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0084, 0x1215, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0084, 0x1315, iwl1000_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1206, iwl1000_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1306, iwl1000_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1226, iwl1000_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0083, 0x1326, iwl1000_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0084, 0x1216, iwl1000_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0084, 0x1316, iwl1000_bg_cfg)},
 +
 +/* 100 Series WiFi */
 +      {IWL_PCI_DEVICE(0x08AE, 0x1005, iwl100_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x08AE, 0x1007, iwl100_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x08AF, 0x1015, iwl100_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x08AF, 0x1017, iwl100_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x08AE, 0x1025, iwl100_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x08AE, 0x1027, iwl100_bg_cfg)},
 +
 +/* 130 Series WiFi */
 +      {IWL_PCI_DEVICE(0x0896, 0x5005, iwl130_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0896, 0x5007, iwl130_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0897, 0x5015, iwl130_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0897, 0x5017, iwl130_bg_cfg)},
 +      {IWL_PCI_DEVICE(0x0896, 0x5025, iwl130_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0896, 0x5027, iwl130_bg_cfg)},
 +
 +/* 2x00 Series */
 +      {IWL_PCI_DEVICE(0x0890, 0x4022, iwl2000_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0891, 0x4222, iwl2000_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0890, 0x4422, iwl2000_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0890, 0x4822, iwl2000_2bgn_d_cfg)},
 +
 +/* 2x30 Series */
 +      {IWL_PCI_DEVICE(0x0887, 0x4062, iwl2030_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0888, 0x4262, iwl2030_2bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0887, 0x4462, iwl2030_2bgn_cfg)},
 +
 +/* 6x35 Series */
 +      {IWL_PCI_DEVICE(0x088E, 0x4060, iwl6035_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x088E, 0x406A, iwl6035_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x088F, 0x4260, iwl6035_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x088F, 0x426A, iwl6035_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x088E, 0x4460, iwl6035_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x088E, 0x446A, iwl6035_2agn_sff_cfg)},
 +      {IWL_PCI_DEVICE(0x088E, 0x4860, iwl6035_2agn_cfg)},
 +      {IWL_PCI_DEVICE(0x088F, 0x5260, iwl6035_2agn_cfg)},
 +
 +/* 105 Series */
 +      {IWL_PCI_DEVICE(0x0894, 0x0022, iwl105_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0895, 0x0222, iwl105_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0894, 0x0422, iwl105_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0894, 0x0822, iwl105_bgn_d_cfg)},
 +
 +/* 135 Series */
 +      {IWL_PCI_DEVICE(0x0892, 0x0062, iwl135_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0893, 0x0262, iwl135_bgn_cfg)},
 +      {IWL_PCI_DEVICE(0x0892, 0x0462, iwl135_bgn_cfg)},
 +#endif /* CONFIG_IWLDVM */
 +
 +#if IS_ENABLED(CONFIG_IWLMVM)
 +/* 7260 Series */
 +      {IWL_PCI_DEVICE(0x08B1, 0x4070, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4072, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4170, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4C60, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4C70, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4060, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x406A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4160, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4062, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4162, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4270, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4272, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4260, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x426A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4262, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4470, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4472, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4460, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x446A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4462, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4870, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x486E, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4A70, iwl7260_2ac_cfg_high_temp)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4A6E, iwl7260_2ac_cfg_high_temp)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4A6C, iwl7260_2ac_cfg_high_temp)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4570, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4560, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4370, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4360, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x5070, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x5072, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x5170, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x5770, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4020, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x402A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0x4220, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0x4420, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC070, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC072, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC170, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC060, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC06A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC160, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC062, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC162, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC770, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC760, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC270, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xCC70, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xCC60, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC272, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC260, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC26A, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC262, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC470, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC472, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC460, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC462, iwl7260_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC570, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC560, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC370, iwl7260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC360, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC020, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC02A, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B2, 0xC220, iwl7260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B1, 0xC420, iwl7260_2n_cfg)},
 +
 +/* 3160 Series */
 +      {IWL_PCI_DEVICE(0x08B3, 0x0070, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0072, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0170, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0172, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0060, iwl3160_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0062, iwl3160_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x0270, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x0272, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0470, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x0472, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x0370, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8070, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8072, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8170, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8172, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8060, iwl3160_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8062, iwl3160_n_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x8270, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x8370, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B4, 0x8272, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8470, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x8570, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x1070, iwl3160_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x08B3, 0x1170, iwl3160_2ac_cfg)},
 +
 +/* 3165 Series */
 +      {IWL_PCI_DEVICE(0x3165, 0x4010, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x4012, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3166, 0x4212, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x4410, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x4510, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x4110, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3166, 0x4310, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3166, 0x4210, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x8010, iwl3165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x3165, 0x8110, iwl3165_2ac_cfg)},
 +
 +/* 7265 Series */
 +      {IWL_PCI_DEVICE(0x095A, 0x5010, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5110, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5100, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5310, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5302, iwl7265_n_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5210, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5012, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5412, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5410, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5510, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5400, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x1010, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5000, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x500A, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5200, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5002, iwl7265_n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5102, iwl7265_n_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5202, iwl7265_n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9010, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9012, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x900A, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9110, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9112, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9210, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x9200, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9510, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9310, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9410, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5020, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x502A, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5420, iwl7265_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5090, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5190, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5590, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5290, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5490, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x5F10, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x5212, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095B, 0x520A, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9000, iwl7265_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x095A, 0x9400, iwl7265_2ac_cfg)},
 +
 +/* 8000 Series */
 +      {IWL_PCI_DEVICE(0x24F3, 0x0010, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x1010, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x0130, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x1130, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x0132, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x1132, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0110, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x01F0, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x0012, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x1012, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x1110, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0050, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0250, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x1050, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0150, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x1150, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F4, 0x0030, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F4, 0x1030, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0xC010, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0xC110, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0xD010, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0xC050, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0xD050, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x8010, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x8110, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x9010, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x9110, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F4, 0x8030, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F4, 0x9030, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x8130, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x9130, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x8132, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x9132, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x8050, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x8150, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x9050, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x9150, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0004, iwl8260_2n_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x0044, iwl8260_2n_cfg)},
 +      {IWL_PCI_DEVICE(0x24F5, 0x0010, iwl4165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F6, 0x0030, iwl4165_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0810, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0910, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0850, iwl8260_2ac_cfg)},
 +      {IWL_PCI_DEVICE(0x24F3, 0x0950, iwl8260_2ac_cfg)},
++      {IWL_PCI_DEVICE(0x24F3, 0x0930, iwl8260_2ac_cfg)},
 +#endif /* CONFIG_IWLMVM */
 +
 +      {0}
 +};
 +MODULE_DEVICE_TABLE(pci, iwl_hw_card_ids);
 +
 +#ifdef CONFIG_ACPI
 +#define SPL_METHOD            "SPLC"
 +#define SPL_DOMAINTYPE_MODULE BIT(0)
 +#define SPL_DOMAINTYPE_WIFI   BIT(1)
 +#define SPL_DOMAINTYPE_WIGIG  BIT(2)
 +#define SPL_DOMAINTYPE_RFEM   BIT(3)
 +
 +static u64 splx_get_pwr_limit(struct iwl_trans *trans, union acpi_object *splx)
 +{
 +      union acpi_object *limits, *domain_type, *power_limit;
 +
 +      if (splx->type != ACPI_TYPE_PACKAGE ||
 +          splx->package.count != 2 ||
 +          splx->package.elements[0].type != ACPI_TYPE_INTEGER ||
 +          splx->package.elements[0].integer.value != 0) {
 +              IWL_ERR(trans, "Unsupported splx structure\n");
 +              return 0;
 +      }
 +
 +      limits = &splx->package.elements[1];
 +      if (limits->type != ACPI_TYPE_PACKAGE ||
 +          limits->package.count < 2 ||
 +          limits->package.elements[0].type != ACPI_TYPE_INTEGER ||
 +          limits->package.elements[1].type != ACPI_TYPE_INTEGER) {
 +              IWL_ERR(trans, "Invalid limits element\n");
 +              return 0;
 +      }
 +
 +      domain_type = &limits->package.elements[0];
 +      power_limit = &limits->package.elements[1];
 +      if (!(domain_type->integer.value & SPL_DOMAINTYPE_WIFI)) {
 +              IWL_DEBUG_INFO(trans, "WiFi power is not limited\n");
 +              return 0;
 +      }
 +
 +      return power_limit->integer.value;
 +}
 +
 +static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev)
 +{
 +      acpi_handle pxsx_handle;
 +      acpi_handle handle;
 +      struct acpi_buffer splx = {ACPI_ALLOCATE_BUFFER, NULL};
 +      acpi_status status;
 +
 +      pxsx_handle = ACPI_HANDLE(&pdev->dev);
 +      if (!pxsx_handle) {
 +              IWL_DEBUG_INFO(trans,
 +                             "Could not retrieve root port ACPI handle\n");
 +              return;
 +      }
 +
 +      /* Get the method's handle */
 +      status = acpi_get_handle(pxsx_handle, (acpi_string)SPL_METHOD, &handle);
 +      if (ACPI_FAILURE(status)) {
 +              IWL_DEBUG_INFO(trans, "SPL method not found\n");
 +              return;
 +      }
 +
 +      /* Call SPLC with no arguments */
 +      status = acpi_evaluate_object(handle, NULL, NULL, &splx);
 +      if (ACPI_FAILURE(status)) {
 +              IWL_ERR(trans, "SPLC invocation failed (0x%x)\n", status);
 +              return;
 +      }
 +
 +      trans->dflt_pwr_limit = splx_get_pwr_limit(trans, splx.pointer);
 +      IWL_DEBUG_INFO(trans, "Default power limit set to %lld\n",
 +                     trans->dflt_pwr_limit);
 +      kfree(splx.pointer);
 +}
 +
 +#else /* CONFIG_ACPI */
 +static void set_dflt_pwr_limit(struct iwl_trans *trans, struct pci_dev *pdev) {}
 +#endif
 +
 +/* PCI registers */
 +#define PCI_CFG_RETRY_TIMEOUT 0x041
 +
 +static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
 +{
 +      const struct iwl_cfg *cfg = (struct iwl_cfg *)(ent->driver_data);
 +      const struct iwl_cfg *cfg_7265d __maybe_unused = NULL;
 +      struct iwl_trans *iwl_trans;
 +      struct iwl_trans_pcie *trans_pcie;
 +      int ret;
 +
 +      iwl_trans = iwl_trans_pcie_alloc(pdev, ent, cfg);
 +      if (IS_ERR(iwl_trans))
 +              return PTR_ERR(iwl_trans);
 +
 +#if IS_ENABLED(CONFIG_IWLMVM)
 +      /*
 +       * special-case 7265D, it has the same PCI IDs.
 +       *
 +       * Note that because we already pass the cfg to the transport above,
 +       * all the parameters that the transport uses must, until that is
 +       * changed, be identical to the ones in the 7265D configuration.
 +       */
 +      if (cfg == &iwl7265_2ac_cfg)
 +              cfg_7265d = &iwl7265d_2ac_cfg;
 +      else if (cfg == &iwl7265_2n_cfg)
 +              cfg_7265d = &iwl7265d_2n_cfg;
 +      else if (cfg == &iwl7265_n_cfg)
 +              cfg_7265d = &iwl7265d_n_cfg;
 +      if (cfg_7265d &&
 +          (iwl_trans->hw_rev & CSR_HW_REV_TYPE_MSK) == CSR_HW_REV_TYPE_7265D) {
 +              cfg = cfg_7265d;
 +              iwl_trans->cfg = cfg_7265d;
 +      }
 +#endif
 +
 +      pci_set_drvdata(pdev, iwl_trans);
 +
 +      trans_pcie = IWL_TRANS_GET_PCIE_TRANS(iwl_trans);
 +      trans_pcie->drv = iwl_drv_start(iwl_trans, cfg);
 +
 +      if (IS_ERR(trans_pcie->drv)) {
 +              ret = PTR_ERR(trans_pcie->drv);
 +              goto out_free_trans;
 +      }
 +
 +      set_dflt_pwr_limit(iwl_trans, pdev);
 +
 +      /* register transport layer debugfs here */
 +      ret = iwl_trans_dbgfs_register(iwl_trans, iwl_trans->dbgfs_dir);
 +      if (ret)
 +              goto out_free_drv;
 +
 +      return 0;
 +
 +out_free_drv:
 +      iwl_drv_stop(trans_pcie->drv);
 +out_free_trans:
 +      iwl_trans_pcie_free(iwl_trans);
 +      return ret;
 +}
 +
 +static void iwl_pci_remove(struct pci_dev *pdev)
 +{
 +      struct iwl_trans *trans = pci_get_drvdata(pdev);
 +      struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 +
 +      iwl_drv_stop(trans_pcie->drv);
 +      iwl_trans_pcie_free(trans);
 +}
 +
 +#ifdef CONFIG_PM_SLEEP
 +
 +static int iwl_pci_suspend(struct device *device)
 +{
 +      /* Before you put code here, think about WoWLAN. You cannot check here
 +       * whether WoWLAN is enabled or not, and your code will run even if
 +       * WoWLAN is enabled - don't kill the NIC, someone may need it in Sx.
 +       */
 +
 +      return 0;
 +}
 +
 +static int iwl_pci_resume(struct device *device)
 +{
 +      struct pci_dev *pdev = to_pci_dev(device);
 +      struct iwl_trans *trans = pci_get_drvdata(pdev);
 +      struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
 +      bool hw_rfkill;
 +
 +      /* Before you put code here, think about WoWLAN. You cannot check here
 +       * whether WoWLAN is enabled or not, and your code will run even if
 +       * WoWLAN is enabled - the NIC may be alive.
 +       */
 +
 +      /*
 +       * We disable the RETRY_TIMEOUT register (0x41) to keep
 +       * PCI Tx retries from interfering with C3 CPU state.
 +       */
 +      pci_write_config_byte(pdev, PCI_CFG_RETRY_TIMEOUT, 0x00);
 +
 +      if (!trans->op_mode)
 +              return 0;
 +
 +      /*
 +       * Enable rfkill interrupt (in order to keep track of
 +       * the rfkill status)
 +       */
 +      iwl_enable_rfkill_int(trans);
 +
 +      hw_rfkill = iwl_is_rfkill_set(trans);
 +
 +      mutex_lock(&trans_pcie->mutex);
 +      iwl_trans_pcie_rf_kill(trans, hw_rfkill);
 +      mutex_unlock(&trans_pcie->mutex);
 +
 +      return 0;
 +}
 +
 +static SIMPLE_DEV_PM_OPS(iwl_dev_pm_ops, iwl_pci_suspend, iwl_pci_resume);
 +
 +#define IWL_PM_OPS    (&iwl_dev_pm_ops)
 +
 +#else
 +
 +#define IWL_PM_OPS    NULL
 +
 +#endif
 +
 +static struct pci_driver iwl_pci_driver = {
 +      .name = DRV_NAME,
 +      .id_table = iwl_hw_card_ids,
 +      .probe = iwl_pci_probe,
 +      .remove = iwl_pci_remove,
 +      .driver.pm = IWL_PM_OPS,
 +};
 +
 +int __must_check iwl_pci_register_driver(void)
 +{
 +      int ret;
 +      ret = pci_register_driver(&iwl_pci_driver);
 +      if (ret)
 +              pr_err("Unable to initialize PCI module\n");
 +
 +      return ret;
 +}
 +
 +void iwl_pci_unregister_driver(void)
 +{
 +      pci_unregister_driver(&iwl_pci_driver);
 +}