diff options
Diffstat (limited to 'drivers')
37 files changed, 18263 insertions, 6 deletions
diff --git a/drivers/net/wireless/iwlwifi/Kconfig b/drivers/net/wireless/iwlwifi/Kconfig index 5cf43236421..ba319cba3f1 100644 --- a/drivers/net/wireless/iwlwifi/Kconfig +++ b/drivers/net/wireless/iwlwifi/Kconfig @@ -43,8 +43,20 @@ config IWLWIFI  	  module will be called iwlwifi.  config IWLDVM -	tristate "Intel Wireless WiFi" +	tristate "Intel Wireless WiFi DVM Firmware support"  	depends on IWLWIFI +	help +	  This is the driver supporting the DVM firmware which is +	  currently the only firmware available for existing devices. + +config IWLMVM +	tristate "Intel Wireless WiFi MVM Firmware support" +	depends on IWLWIFI +	help +	  This is the driver supporting the MVM firmware which is +	  currently only available for 7000 series devices. + +	  Say yes if you have such a device.  menu "Debugging Options"  	depends on IWLWIFI diff --git a/drivers/net/wireless/iwlwifi/Makefile b/drivers/net/wireless/iwlwifi/Makefile index 2d4d47a6ef1..6c7800044a0 100644 --- a/drivers/net/wireless/iwlwifi/Makefile +++ b/drivers/net/wireless/iwlwifi/Makefile @@ -17,5 +17,6 @@ ccflags-y += -D__CHECK_ENDIAN__ -I$(src)  obj-$(CONFIG_IWLDVM)	+= dvm/ +obj-$(CONFIG_IWLMVM)	+= mvm/  CFLAGS_iwl-devtrace.o := -I$(src) diff --git a/drivers/net/wireless/iwlwifi/iwl-debug.h b/drivers/net/wireless/iwlwifi/iwl-debug.h index b59ec4c5c8c..8cf5db7fb5c 100644 --- a/drivers/net/wireless/iwlwifi/iwl-debug.h +++ b/drivers/net/wireless/iwlwifi/iwl-debug.h @@ -116,6 +116,7 @@ do {                                            			\  #define IWL_DL_HCMD		0x00000004  #define IWL_DL_STATE		0x00000008  /* 0x000000F0 - 0x00000010 */ +#define IWL_DL_TE		0x00000020  #define IWL_DL_EEPROM		0x00000040  #define IWL_DL_RADIO		0x00000080  /* 0x00000F00 - 0x00000100 */ @@ -156,6 +157,7 @@ do {                                            			\  #define IWL_DEBUG_LED(p, f, a...)	IWL_DEBUG(p, IWL_DL_LED, f, ## a)  #define IWL_DEBUG_WEP(p, f, a...)	IWL_DEBUG(p, IWL_DL_WEP, f, ## a)  #define IWL_DEBUG_HC(p, f, a...)	IWL_DEBUG(p, IWL_DL_HCMD, f, ## a) +#define IWL_DEBUG_TE(p, f, a...)	IWL_DEBUG(p, IWL_DL_TE, f, ## a)  #define IWL_DEBUG_EEPROM(d, f, a...)	IWL_DEBUG_DEV(d, IWL_DL_EEPROM, f, ## a)  #define IWL_DEBUG_CALIB(p, f, a...)	IWL_DEBUG(p, IWL_DL_CALIB, f, ## a)  #define IWL_DEBUG_FW(p, f, a...)	IWL_DEBUG(p, IWL_DL_FW, f, ## a) diff --git a/drivers/net/wireless/iwlwifi/iwl-drv.c b/drivers/net/wireless/iwlwifi/iwl-drv.c index c6751962b2d..6f228bb2b84 100644 --- a/drivers/net/wireless/iwlwifi/iwl-drv.c +++ b/drivers/net/wireless/iwlwifi/iwl-drv.c @@ -139,8 +139,10 @@ struct iwl_drv {  #endif  }; -#define DVM_OP_MODE	0 -#define MVM_OP_MODE	1 +enum { +	DVM_OP_MODE =	0, +	MVM_OP_MODE =	1, +};  /* Protects the table contents, i.e. the ops pointer & drv list */  static struct mutex iwlwifi_opmode_table_mtx; @@ -149,8 +151,8 @@ static struct iwlwifi_opmode_table {  	const struct iwl_op_mode_ops *ops;	/* pointer to op_mode ops */  	struct list_head drv;		/* list of devices using this op_mode */  } iwlwifi_opmode_table[] = {		/* ops set when driver is initialized */ -	{ .name = "iwldvm", .ops = NULL }, -	{ .name = "iwlmvm", .ops = NULL }, +	[DVM_OP_MODE] = { .name = "iwldvm", .ops = NULL }, +	[MVM_OP_MODE] = { .name = "iwlmvm", .ops = NULL },  };  /* @@ -963,7 +965,10 @@ static void iwl_req_fw_callback(const struct firmware *ucode_raw, void *context)  	release_firmware(ucode_raw);  	mutex_lock(&iwlwifi_opmode_table_mtx); -	op = &iwlwifi_opmode_table[DVM_OP_MODE]; +	if (fw->mvm_fw) +		op = &iwlwifi_opmode_table[MVM_OP_MODE]; +	else +		op = &iwlwifi_opmode_table[DVM_OP_MODE];  	/* add this device to the list of devices using this op_mode */  	list_add_tail(&drv->list, &op->drv); diff --git a/drivers/net/wireless/iwlwifi/iwl-fw.h b/drivers/net/wireless/iwlwifi/iwl-fw.h index 1ad31a9fa3e..de3c24a5a62 100644 --- a/drivers/net/wireless/iwlwifi/iwl-fw.h +++ b/drivers/net/wireless/iwlwifi/iwl-fw.h @@ -166,6 +166,7 @@ struct iwl_tlv_calib_ctrl {   * @inst_evtlog_ptr: event log offset for runtime ucode.   * @inst_evtlog_size: event log size for runtime ucode.   * @inst_errlog_ptr: error log offfset for runtime ucode. + * @mvm_fw: indicates this is MVM firmware   */  struct iwl_fw {  	u32 ucode_ver; diff --git a/drivers/net/wireless/iwlwifi/mvm/Makefile b/drivers/net/wireless/iwlwifi/mvm/Makefile new file mode 100644 index 00000000000..807b250ec39 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_IWLMVM)   += iwlmvm.o +iwlmvm-y += fw.o mac80211.o nvm.o ops.o phy-ctxt.o mac-ctxt.o +iwlmvm-y += utils.o rx.o tx.o binding.o quota.o sta.o +iwlmvm-y += scan.o time-event.o rs.o +iwlmvm-y += power.o +iwlmvm-y += led.o +iwlmvm-$(CONFIG_IWLWIFI_DEBUGFS) += debugfs.o +iwlmvm-$(CONFIG_PM_SLEEP) += d3.o + +ccflags-y += -D__CHECK_ENDIAN__ -I$(src)/../ diff --git a/drivers/net/wireless/iwlwifi/mvm/binding.c b/drivers/net/wireless/iwlwifi/mvm/binding.c new file mode 100644 index 00000000000..73d24aacb90 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/binding.c @@ -0,0 +1,197 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_iface_iterator_data { +	struct ieee80211_vif *ignore_vif; +	int idx; + +	struct iwl_mvm_phy_ctxt *phyctxt; + +	u16 ids[MAX_MACS_IN_BINDING]; +	u16 colors[MAX_MACS_IN_BINDING]; +}; + +static int iwl_mvm_binding_cmd(struct iwl_mvm *mvm, u32 action, +			       struct iwl_mvm_iface_iterator_data *data) +{ +	struct iwl_binding_cmd cmd; +	struct iwl_mvm_phy_ctxt *phyctxt = data->phyctxt; +	int i, ret; +	u32 status; + +	memset(&cmd, 0, sizeof(cmd)); + +	cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, +							   phyctxt->color)); +	cmd.action = cpu_to_le32(action); +	cmd.phy = cpu_to_le32(FW_CMD_ID_AND_COLOR(phyctxt->id, +						  phyctxt->color)); + +	for (i = 0; i < MAX_MACS_IN_BINDING; i++) +		cmd.macs[i] = cpu_to_le32(FW_CTXT_INVALID); +	for (i = 0; i < data->idx; i++) +		cmd.macs[i] = cpu_to_le32(FW_CMD_ID_AND_COLOR(data->ids[i], +							      data->colors[i])); + +	status = 0; +	ret = iwl_mvm_send_cmd_pdu_status(mvm, BINDING_CONTEXT_CMD, +					  sizeof(cmd), &cmd, &status); +	if (ret) { +		IWL_ERR(mvm, "Failed to send binding (action:%d): %d\n", +			action, ret); +		return ret; +	} + +	if (status) { +		IWL_ERR(mvm, "Binding command failed: %u\n", status); +		ret = -EIO; +	} + +	return ret; +} + +static void iwl_mvm_iface_iterator(void *_data, u8 *mac, +				   struct ieee80211_vif *vif) +{ +	struct iwl_mvm_iface_iterator_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (vif == data->ignore_vif) +		return; + +	if (mvmvif->phy_ctxt != data->phyctxt) +		return; + +	if (WARN_ON_ONCE(data->idx >= MAX_MACS_IN_BINDING)) +		return; + +	data->ids[data->idx] = mvmvif->id; +	data->colors[data->idx] = mvmvif->color; +	data->idx++; +} + +static int iwl_mvm_binding_update(struct iwl_mvm *mvm, +				  struct ieee80211_vif *vif, +				  struct iwl_mvm_phy_ctxt *phyctxt, +				  bool add) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_iface_iterator_data data = { +		.ignore_vif = vif, +		.phyctxt = phyctxt, +	}; +	u32 action = FW_CTXT_ACTION_MODIFY; + +	lockdep_assert_held(&mvm->mutex); + +	ieee80211_iterate_active_interfaces_atomic(mvm->hw, +						   IEEE80211_IFACE_ITER_NORMAL, +						   iwl_mvm_iface_iterator, +						   &data); + +	/* +	 * If there are no other interfaces yet we +	 * need to create a new binding. +	 */ +	if (data.idx == 0) { +		if (add) +			action = FW_CTXT_ACTION_ADD; +		else +			action = FW_CTXT_ACTION_REMOVE; +	} + +	if (add) { +		if (WARN_ON_ONCE(data.idx >= MAX_MACS_IN_BINDING)) +			return -EINVAL; + +		data.ids[data.idx] = mvmvif->id; +		data.colors[data.idx] = mvmvif->color; +		data.idx++; +	} + +	return iwl_mvm_binding_cmd(mvm, action, &data); +} + +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) +		return -EINVAL; + +	return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, true); +} + +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (WARN_ON_ONCE(!mvmvif->phy_ctxt)) +		return -EINVAL; + +	return iwl_mvm_binding_update(mvm, vif, mvmvif->phy_ctxt, false); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/d3.c b/drivers/net/wireless/iwlwifi/mvm/d3.c new file mode 100644 index 00000000000..9a95c374990 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/d3.c @@ -0,0 +1,841 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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/cfg80211.h> +#include <net/ipv6.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(&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) +			break; +	} +	read_unlock(&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 gtk_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->gtk_key_idx++; +			wkc.wep_key.key_offset = data->gtk_key_idx; +		} + +		ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, CMD_SYNC, +					   sizeof(wkc), &wkc); +		data->error = ret != 0; + +		/* 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) { +			u8 *pn = seq.ccmp.pn; + +			aes_sc = data->rsc_tsc->all_tsc_rsc.aes.unicast_rsc; +			aes_tx_sc = &data->rsc_tsc->all_tsc_rsc.aes.tsc; + +			ieee80211_get_key_tx_seq(key, &seq); +			aes_tx_sc->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)); +		} 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->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 = 0; +	} else { +		data->gtk_key_idx++; +		key->hw_key_idx = data->gtk_key_idx; +	} + +	ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, true); +	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, +		.flags = CMD_SYNC, +	}; +	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; +} + +static int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, +				      struct ieee80211_vif *vif) +{ +	struct iwl_proto_offload_cmd cmd = {}; +#if IS_ENABLED(CONFIG_IPV6) +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	int i; + +	if (mvmvif->num_target_ipv6_addrs) { +		cmd.enabled |= cpu_to_le32(IWL_D3_PROTO_OFFLOAD_NS); +		memcpy(cmd.ndp_mac_addr, vif->addr, ETH_ALEN); +	} + +	BUILD_BUG_ON(sizeof(cmd.target_ipv6_addr[i]) != +		     sizeof(mvmvif->target_ipv6_addrs[i])); + +	for (i = 0; i < mvmvif->num_target_ipv6_addrs; i++) +		memcpy(cmd.target_ipv6_addr[i], +		       &mvmvif->target_ipv6_addrs[i], +		       sizeof(cmd.target_ipv6_addr[i])); +#endif + +	if (vif->bss_conf.arp_addr_cnt) { +		cmd.enabled |= cpu_to_le32(IWL_D3_PROTO_OFFLOAD_ARP); +		cmd.host_ipv4_addr = vif->bss_conf.arp_addr_list[0]; +		memcpy(cmd.arp_mac_addr, vif->addr, ETH_ALEN); +	} + +	if (!cmd.enabled) +		return 0; + +	return iwl_mvm_send_cmd_pdu(mvm, PROT_OFFLOAD_CONFIG_CMD, CMD_SYNC, +				    sizeof(cmd), &cmd); +} + +struct iwl_d3_iter_data { +	struct iwl_mvm *mvm; +	struct ieee80211_vif *vif; +	bool error; +}; + +static void iwl_mvm_d3_iface_iterator(void *_data, u8 *mac, +				      struct ieee80211_vif *vif) +{ +	struct iwl_d3_iter_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) +		return; + +	if (mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) +		return; + +	if (data->vif) { +		IWL_ERR(data->mvm, "More than one managed interface active!\n"); +		data->error = true; +		return; +	} + +	data->vif = vif; +} + +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; +	/* hack */ +	vif->bss_conf.assoc = false; +	ret = iwl_mvm_mac_ctxt_add(mvm, vif); +	vif->bss_conf.assoc = true; +	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_add_to_fw(mvm, ap_sta); +	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); +	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(100); +	quota_cmd.quotas[0].max_duration = cpu_to_le32(1000); + +	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, CMD_SYNC, +				   sizeof(quota_cmd), "a_cmd); +	if (ret) +		IWL_ERR(mvm, "Failed to send quota: %d\n", ret); + +	return 0; +} + +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_d3_iter_data suspend_iter_data = { +		.mvm = mvm, +	}; +	struct ieee80211_vif *vif; +	struct iwl_mvm_vif *mvmvif; +	struct ieee80211_sta *ap_sta; +	struct iwl_mvm_sta *mvm_ap_sta; +	struct iwl_wowlan_config_cmd wowlan_config_cmd = {}; +	struct iwl_wowlan_kek_kck_material_cmd kek_kck_cmd = {}; +	struct iwl_wowlan_tkip_params_cmd tkip_cmd = {}; +	struct iwl_d3_manager_config d3_cfg_cmd = {}; +	struct wowlan_key_data key_data = { +		.use_rsc_tsc = false, +		.tkip = &tkip_cmd, +		.use_tkip = false, +	}; +	int ret, i; +	u16 seq; +	u8 old_aux_sta_id, old_ap_sta_id = IWL_MVM_STATION_COUNT; + +	if (WARN_ON(!wowlan)) +		return -EINVAL; + +	key_data.rsc_tsc = kzalloc(sizeof(*key_data.rsc_tsc), GFP_KERNEL); +	if (!key_data.rsc_tsc) +		return -ENOMEM; + +	mutex_lock(&mvm->mutex); + +	old_aux_sta_id = mvm->aux_sta.sta_id; + +	/* see if there's only a single BSS vif and it's associated */ +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_NORMAL, +		iwl_mvm_d3_iface_iterator, &suspend_iter_data); + +	if (suspend_iter_data.error || !suspend_iter_data.vif) { +		ret = 1; +		goto out_noreset; +	} + +	vif = suspend_iter_data.vif; +	mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	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; +	} + +	mvm_ap_sta = (struct iwl_mvm_sta *)ap_sta->drv_priv; + +	/* +	 * The D3 firmware still hardcodes the AP station ID for the +	 * BSS we're associated with as 0. Store the real STA ID here +	 * and assign 0. When we leave this function, we'll restore +	 * the original value for the resume code. +	 */ +	old_ap_sta_id = mvm_ap_sta->sta_id; +	mvm_ap_sta->sta_id = 0; +	mvmvif->ap_sta_id = 0; + +	/* TODO: wowlan_config_cmd.wowlan_ba_teardown_tids */ + +	wowlan_config_cmd.is_11n_connection = ap_sta->ht_cap.ht_supported; + +	/* +	 * We know the last used seqno, and the uCode expects to know that +	 * one, it will increment before TX. +	 */ +	seq = mvm_ap_sta->last_seq_ctl & IEEE80211_SCTL_SEQ; +	wowlan_config_cmd.non_qos_seq = cpu_to_le16(seq); + +	/* +	 * For QoS counters, we store the one to use next, so subtract 0x10 +	 * since the uCode will add 0x10 *before* using the value while we +	 * increment after using the value (i.e. store the next value to use). +	 */ +	for (i = 0; i < IWL_MAX_TID_COUNT; i++) { +		seq = mvm_ap_sta->tid_data[i].seq_number; +		seq -= 0x10; +		wowlan_config_cmd.qos_seq[i] = cpu_to_le16(seq); +	} + +	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) +		d3_cfg_cmd.wakeup_flags |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT); + +	iwl_mvm_cancel_scan(mvm); + +	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); + +	/* We reprogram keys and shouldn't allocate new key indices */ +	memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); + +	/* +	 * The D3 firmware still hardcodes the AP station ID for the +	 * BSS we're associated with as 0. As a result, we have to move +	 * the auxiliary station to ID 1 so the ID 0 remains free for +	 * the AP station for later. +	 * We set the sta_id to 1 here, and reset it to its previous +	 * value (that we stored above) later. +	 */ +	mvm->aux_sta.sta_id = 1; + +	ret = iwl_mvm_load_d3_fw(mvm); +	if (ret) +		goto out; + +	ret = iwl_mvm_d3_reprogram(mvm, vif, ap_sta); +	if (ret) +		goto out; + +	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, +				.flags = CMD_SYNC, +				.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, +						   CMD_SYNC, 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, +						   CMD_SYNC, +						   sizeof(kek_kck_cmd), +						   &kek_kck_cmd); +			if (ret) +				goto out; +		} +	} + +	ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, +				   CMD_SYNC, 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); +	if (ret) +		goto out; + +	/* must be last -- this switches firmware state */ +	ret = iwl_mvm_send_cmd_pdu(mvm, D3_CONFIG_CMD, CMD_SYNC, +				   sizeof(d3_cfg_cmd), &d3_cfg_cmd); +	if (ret) +		goto out; + +	clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); + +	iwl_trans_d3_suspend(mvm->trans); + out: +	mvm->aux_sta.sta_id = old_aux_sta_id; +	mvm_ap_sta->sta_id = old_ap_sta_id; +	mvmvif->ap_sta_id = old_ap_sta_id; + out_noreset: +	kfree(key_data.rsc_tsc); +	if (ret < 0) +		ieee80211_restart_hw(mvm->hw); + +	mutex_unlock(&mvm->mutex); + +	return ret; +} + +int iwl_mvm_resume(struct ieee80211_hw *hw) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_d3_iter_data resume_iter_data = { +		.mvm = mvm, +	}; +	struct ieee80211_vif *vif = NULL; +	u32 base; +	int ret; +	enum iwl_d3_status d3_status; +	struct error_table_start { +		/* cf. struct iwl_error_event_table */ +		u32 valid; +		u32 error_id; +	} err_info; + +	mutex_lock(&mvm->mutex); + +	/* get the BSS vif pointer again */ +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_NORMAL, +		iwl_mvm_d3_iface_iterator, &resume_iter_data); + +	if (WARN_ON(resume_iter_data.error || !resume_iter_data.vif)) +		goto out_unlock; + +	vif = resume_iter_data.vif; + +	ret = iwl_trans_d3_resume(mvm->trans, &d3_status); +	if (ret) +		goto out_unlock; + +	if (d3_status != IWL_D3_STATUS_ALIVE) { +		IWL_INFO(mvm, "Device was reset during suspend\n"); +		goto out_unlock; +	} + +	base = mvm->error_event_table; + +	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)\n", +			 err_info.valid); +		if (err_info.error_id == RF_KILL_INDICATOR_FOR_WOWLAN) +			IWL_ERR(mvm, "this was due to RF-kill\n"); +		goto out_unlock; +	} + +	/* TODO: get status and whatever else ... */ +	ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_GET_STATUSES, CMD_SYNC, 0, NULL); +	if (ret) +		IWL_ERR(mvm, "failed to query status (%d)\n", ret); + +	ret = iwl_mvm_send_cmd_pdu(mvm, OFFLOADS_QUERY_CMD, CMD_SYNC, 0, NULL); +	if (ret) +		IWL_ERR(mvm, "failed to query offloads (%d)\n", ret); + + out_unlock: +	mutex_unlock(&mvm->mutex); + +	if (vif) +		ieee80211_resume_disconnect(vif); + +	/* return 1 to reconfigure the device */ +	set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); +	return 1; +} + +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); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/debugfs.c b/drivers/net/wireless/iwlwifi/mvm/debugfs.c new file mode 100644 index 00000000000..c1bdb558212 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/debugfs.c @@ -0,0 +1,378 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "mvm.h" +#include "sta.h" +#include "iwl-io.h" + +struct iwl_dbgfs_mvm_ctx { +	struct iwl_mvm *mvm; +	struct ieee80211_vif *vif; +}; + +static int iwl_dbgfs_open_file_generic(struct inode *inode, struct file *file) +{ +	file->private_data = inode->i_private; +	return 0; +} + +static ssize_t iwl_dbgfs_tx_flush_write(struct file *file, +					const char __user *user_buf, +					size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; + +	char buf[16]; +	int buf_size, ret; +	u32 scd_q_msk; + +	if (!mvm->ucode_loaded || mvm->cur_ucode != IWL_UCODE_REGULAR) +		return -EIO; + +	memset(buf, 0, sizeof(buf)); +	buf_size = min(count, sizeof(buf) - 1); +	if (copy_from_user(buf, user_buf, buf_size)) +		return -EFAULT; + +	if (sscanf(buf, "%x", &scd_q_msk) != 1) +		return -EINVAL; + +	IWL_ERR(mvm, "FLUSHING queues: scd_q_msk = 0x%x\n", scd_q_msk); + +	mutex_lock(&mvm->mutex); +	ret =  iwl_mvm_flush_tx_path(mvm, scd_q_msk, true) ? : count; +	mutex_unlock(&mvm->mutex); + +	return ret; +} + +static ssize_t iwl_dbgfs_sta_drain_write(struct file *file, +					 const char __user *user_buf, +					 size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	struct ieee80211_sta *sta; + +	char buf[8]; +	int buf_size, sta_id, drain, ret; + +	if (!mvm->ucode_loaded || mvm->cur_ucode != IWL_UCODE_REGULAR) +		return -EIO; + +	memset(buf, 0, sizeof(buf)); +	buf_size = min(count, sizeof(buf) - 1); +	if (copy_from_user(buf, user_buf, buf_size)) +		return -EFAULT; + +	if (sscanf(buf, "%d %d", &sta_id, &drain) != 2) +		return -EINVAL; + +	mutex_lock(&mvm->mutex); + +	sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], +					lockdep_is_held(&mvm->mutex)); +	if (IS_ERR_OR_NULL(sta)) +		ret = -ENOENT; +	else +		ret = iwl_mvm_drain_sta(mvm, (void *)sta->drv_priv, drain) ? : +			count; + +	mutex_unlock(&mvm->mutex); + +	return ret; +} + +static ssize_t iwl_dbgfs_sram_read(struct file *file, char __user *user_buf, +				   size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	const struct fw_img *img; +	int ofs, len, pos = 0; +	size_t bufsz, ret; +	char *buf; +	u8 *ptr; + +	/* default is to dump the entire data segment */ +	if (!mvm->dbgfs_sram_offset && !mvm->dbgfs_sram_len) { +		mvm->dbgfs_sram_offset = 0x800000; +		if (!mvm->ucode_loaded) +			return -EINVAL; +		img = &mvm->fw->img[mvm->cur_ucode]; +		mvm->dbgfs_sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; +	} +	len = mvm->dbgfs_sram_len; + +	bufsz = len * 4 + 256; +	buf = kzalloc(bufsz, GFP_KERNEL); +	if (!buf) +		return -ENOMEM; + +	ptr = kzalloc(len, GFP_KERNEL); +	if (!ptr) { +		kfree(buf); +		return -ENOMEM; +	} + +	pos += scnprintf(buf + pos, bufsz - pos, "sram_len: 0x%x\n", len); +	pos += scnprintf(buf + pos, bufsz - pos, "sram_offset: 0x%x\n", +			 mvm->dbgfs_sram_offset); + +	iwl_trans_read_mem_bytes(mvm->trans, +				 mvm->dbgfs_sram_offset, +				 ptr, len); +	for (ofs = 0; ofs < len; ofs += 16) { +		pos += scnprintf(buf + pos, bufsz - pos, "0x%.4x ", ofs); +		hex_dump_to_buffer(ptr + ofs, 16, 16, 1, buf + pos, +				   bufsz - pos, false); +		pos += strlen(buf + pos); +		if (bufsz - pos > 0) +			buf[pos++] = '\n'; +	} + +	ret = simple_read_from_buffer(user_buf, count, ppos, buf, pos); + +	kfree(buf); +	kfree(ptr); + +	return ret; +} + +static ssize_t iwl_dbgfs_sram_write(struct file *file, +				    const char __user *user_buf, size_t count, +				    loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	char buf[64]; +	int buf_size; +	u32 offset, len; + +	memset(buf, 0, sizeof(buf)); +	buf_size = min(count, sizeof(buf) -  1); +	if (copy_from_user(buf, user_buf, buf_size)) +		return -EFAULT; + +	if (sscanf(buf, "%x,%x", &offset, &len) == 2) { +		if ((offset & 0x3) || (len & 0x3)) +			return -EINVAL; +		mvm->dbgfs_sram_offset = offset; +		mvm->dbgfs_sram_len = len; +	} else { +		mvm->dbgfs_sram_offset = 0; +		mvm->dbgfs_sram_len = 0; +	} + +	return count; +} + +static ssize_t iwl_dbgfs_stations_read(struct file *file, char __user *user_buf, +				       size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	struct ieee80211_sta *sta; +	char buf[400]; +	int i, pos = 0, bufsz = sizeof(buf); + +	mutex_lock(&mvm->mutex); + +	for (i = 0; i < IWL_MVM_STATION_COUNT; i++) { +		pos += scnprintf(buf + pos, bufsz - pos, "%.2d: ", i); +		sta = rcu_dereference_protected(mvm->fw_id_to_mac_id[i], +						lockdep_is_held(&mvm->mutex)); +		if (!sta) +			pos += scnprintf(buf + pos, bufsz - pos, "N/A\n"); +		else if (IS_ERR(sta)) +			pos += scnprintf(buf + pos, bufsz - pos, "%ld\n", +					 PTR_ERR(sta)); +		else +			pos += scnprintf(buf + pos, bufsz - pos, "%pM\n", +					 sta->addr); +	} + +	mutex_unlock(&mvm->mutex); + +	return simple_read_from_buffer(user_buf, count, ppos, buf, pos); +} + +static ssize_t iwl_dbgfs_power_down_allow_write(struct file *file, +						const char __user *user_buf, +						size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	char buf[8] = {}; +	int allow; + +	if (!mvm->ucode_loaded) +		return -EIO; + +	if (copy_from_user(buf, user_buf, sizeof(buf))) +		return -EFAULT; + +	if (sscanf(buf, "%d", &allow) != 1) +		return -EINVAL; + +	IWL_DEBUG_POWER(mvm, "%s device power down\n", +			allow ? "allow" : "prevent"); + +	/* +	 * TODO: Send REPLY_DEBUG_CMD (0xf0) when FW support it +	 */ + +	return count; +} + +static ssize_t iwl_dbgfs_power_down_d3_allow_write(struct file *file, +						   const char __user *user_buf, +						   size_t count, loff_t *ppos) +{ +	struct iwl_mvm *mvm = file->private_data; +	char buf[8] = {}; +	int allow; + +	if (copy_from_user(buf, user_buf, sizeof(buf))) +		return -EFAULT; + +	if (sscanf(buf, "%d", &allow) != 1) +		return -EINVAL; + +	IWL_DEBUG_POWER(mvm, "%s device power down in d3\n", +			allow ? "allow" : "prevent"); + +	/* +	 * TODO: When WoWLAN FW alive notification happens, driver will send +	 * REPLY_DEBUG_CMD setting power_down_allow flag according to +	 * mvm->prevent_power_down_d3 +	 */ +	mvm->prevent_power_down_d3 = !allow; + +	return count; +} + +#define MVM_DEBUGFS_READ_FILE_OPS(name)					\ +static const struct file_operations iwl_dbgfs_##name##_ops = {	\ +	.read = iwl_dbgfs_##name##_read,				\ +	.open = iwl_dbgfs_open_file_generic,				\ +	.llseek = generic_file_llseek,					\ +} + +#define MVM_DEBUGFS_READ_WRITE_FILE_OPS(name)				\ +static const struct file_operations iwl_dbgfs_##name##_ops = {	\ +	.write = iwl_dbgfs_##name##_write,				\ +	.read = iwl_dbgfs_##name##_read,				\ +	.open = iwl_dbgfs_open_file_generic,				\ +	.llseek = generic_file_llseek,					\ +}; + +#define MVM_DEBUGFS_WRITE_FILE_OPS(name)				\ +static const struct file_operations iwl_dbgfs_##name##_ops = {	\ +	.write = iwl_dbgfs_##name##_write,				\ +	.open = iwl_dbgfs_open_file_generic,				\ +	.llseek = generic_file_llseek,					\ +}; + +#define MVM_DEBUGFS_ADD_FILE(name, parent, mode) do {			\ +		if (!debugfs_create_file(#name, mode, parent, mvm,	\ +					 &iwl_dbgfs_##name##_ops))	\ +			goto err;					\ +	} while (0) + +#define MVM_DEBUGFS_ADD_FILE_VIF(name, parent, mode) do {		\ +		if (!debugfs_create_file(#name, mode, parent, vif,	\ +					 &iwl_dbgfs_##name##_ops))	\ +			goto err;					\ +	} while (0) + +/* Device wide debugfs entries */ +MVM_DEBUGFS_WRITE_FILE_OPS(tx_flush); +MVM_DEBUGFS_WRITE_FILE_OPS(sta_drain); +MVM_DEBUGFS_READ_WRITE_FILE_OPS(sram); +MVM_DEBUGFS_READ_FILE_OPS(stations); +MVM_DEBUGFS_WRITE_FILE_OPS(power_down_allow); +MVM_DEBUGFS_WRITE_FILE_OPS(power_down_d3_allow); + +int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir) +{ +	char buf[100]; + +	mvm->debugfs_dir = dbgfs_dir; + +	MVM_DEBUGFS_ADD_FILE(tx_flush, mvm->debugfs_dir, S_IWUSR); +	MVM_DEBUGFS_ADD_FILE(sta_drain, mvm->debugfs_dir, S_IWUSR); +	MVM_DEBUGFS_ADD_FILE(sram, mvm->debugfs_dir, S_IWUSR | S_IRUSR); +	MVM_DEBUGFS_ADD_FILE(stations, dbgfs_dir, S_IRUSR); +	MVM_DEBUGFS_ADD_FILE(power_down_allow, mvm->debugfs_dir, S_IWUSR); +	MVM_DEBUGFS_ADD_FILE(power_down_d3_allow, mvm->debugfs_dir, S_IWUSR); + +	/* +	 * Create a symlink with mac80211. It will be removed when mac80211 +	 * exists (before the opmode exists which removes the target.) +	 */ +	snprintf(buf, 100, "../../%s/%s", +		 dbgfs_dir->d_parent->d_parent->d_name.name, +		 dbgfs_dir->d_parent->d_name.name); +	if (!debugfs_create_symlink("iwlwifi", mvm->hw->wiphy->debugfsdir, buf)) +		goto err; + +	return 0; +err: +	IWL_ERR(mvm, "Can't create the mvm debugfs directory\n"); +	return -ENOMEM; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h new file mode 100644 index 00000000000..cf6f9a02fb7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-d3.h @@ -0,0 +1,282 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_d3_h__ +#define __fw_api_d3_h__ + +/** + * enum iwl_d3_wakeup_flags - D3 manager wakeup flags + * @IWL_WAKEUP_D3_CONFIG_FW_ERROR: wake up on firmware sysassert + */ +enum iwl_d3_wakeup_flags { +	IWL_WAKEUP_D3_CONFIG_FW_ERROR = BIT(0), +}; /* D3_MANAGER_WAKEUP_CONFIG_API_E_VER_3 */ + +/** + * struct iwl_d3_manager_config - D3 manager configuration command + * @min_sleep_time: minimum sleep time (in usec) + * @wakeup_flags: wakeup flags, see &enum iwl_d3_wakeup_flags + * + * The structure is used for the D3_CONFIG_CMD command. + */ +struct iwl_d3_manager_config { +	__le32 min_sleep_time; +	__le32 wakeup_flags; +} __packed; /* D3_MANAGER_CONFIG_CMD_S_VER_3 */ + + +/* TODO: OFFLOADS_QUERY_API_S_VER_1 */ + +/** + * enum iwl_d3_proto_offloads - enabled protocol offloads + * @IWL_D3_PROTO_OFFLOAD_ARP: ARP data is enabled + * @IWL_D3_PROTO_OFFLOAD_NS: NS (Neighbor Solicitation) is enabled + */ +enum iwl_proto_offloads { +	IWL_D3_PROTO_OFFLOAD_ARP = BIT(0), +	IWL_D3_PROTO_OFFLOAD_NS = BIT(1), +}; + +#define IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS	2 + +/** + * struct iwl_proto_offload_cmd - ARP/NS offload configuration + * @enabled: enable flags + * @remote_ipv4_addr: remote address to answer to (or zero if all) + * @host_ipv4_addr: our IPv4 address to respond to queries for + * @arp_mac_addr: our MAC address for ARP responses + * @remote_ipv6_addr: remote address to answer to (or zero if all) + * @solicited_node_ipv6_addr: broken -- solicited node address exists + *	for each target address + * @target_ipv6_addr: our target addresses + * @ndp_mac_addr: neighbor soliciation response MAC address + */ +struct iwl_proto_offload_cmd { +	__le32 enabled; +	__be32 remote_ipv4_addr; +	__be32 host_ipv4_addr; +	u8 arp_mac_addr[ETH_ALEN]; +	__le16 reserved1; + +	u8 remote_ipv6_addr[16]; +	u8 solicited_node_ipv6_addr[16]; +	u8 target_ipv6_addr[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS][16]; +	u8 ndp_mac_addr[ETH_ALEN]; +	__le16 reserved2; +} __packed; /* PROT_OFFLOAD_CONFIG_CMD_DB_S_VER_1 */ + + +/* + * WOWLAN_PATTERNS + */ +#define IWL_WOWLAN_MIN_PATTERN_LEN	16 +#define IWL_WOWLAN_MAX_PATTERN_LEN	128 + +struct iwl_wowlan_pattern { +	u8 mask[IWL_WOWLAN_MAX_PATTERN_LEN / 8]; +	u8 pattern[IWL_WOWLAN_MAX_PATTERN_LEN]; +	u8 mask_size; +	u8 pattern_size; +	__le16 reserved; +} __packed; /* WOWLAN_PATTERN_API_S_VER_1 */ + +#define IWL_WOWLAN_MAX_PATTERNS	20 + +struct iwl_wowlan_patterns_cmd { +	__le32 n_patterns; +	struct iwl_wowlan_pattern patterns[]; +} __packed; /* WOWLAN_PATTERN_ARRAY_API_S_VER_1 */ + +enum iwl_wowlan_wakeup_filters { +	IWL_WOWLAN_WAKEUP_MAGIC_PACKET			= BIT(0), +	IWL_WOWLAN_WAKEUP_PATTERN_MATCH			= BIT(1), +	IWL_WOWLAN_WAKEUP_BEACON_MISS			= BIT(2), +	IWL_WOWLAN_WAKEUP_LINK_CHANGE			= BIT(3), +	IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL		= BIT(4), +	IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ			= BIT(5), +	IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE		= BIT(6), +	IWL_WOWLAN_WAKEUP_ENABLE_NET_DETECT		= BIT(7), +	IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT		= BIT(8), +	IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS		= BIT(9), +	IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE	= BIT(10), +	/* BIT(11) reserved */ +	IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET		= BIT(12), +}; /* WOWLAN_WAKEUP_FILTER_API_E_VER_4 */ + +struct iwl_wowlan_config_cmd { +	__le32 wakeup_filter; +	__le16 non_qos_seq; +	__le16 qos_seq[8]; +	u8 wowlan_ba_teardown_tids; +	u8 is_11n_connection; +} __packed; /* WOWLAN_CONFIG_API_S_VER_2 */ + +/* + * WOWLAN_TSC_RSC_PARAMS + */ +#define IWL_NUM_RSC	16 + +struct tkip_sc { +	__le16 iv16; +	__le16 pad; +	__le32 iv32; +} __packed; /* TKIP_SC_API_U_VER_1 */ + +struct iwl_tkip_rsc_tsc { +	struct tkip_sc unicast_rsc[IWL_NUM_RSC]; +	struct tkip_sc multicast_rsc[IWL_NUM_RSC]; +	struct tkip_sc tsc; +} __packed; /* TKIP_TSC_RSC_API_S_VER_1 */ + +struct aes_sc { +	__le64 pn; +} __packed; /* TKIP_AES_SC_API_U_VER_1 */ + +struct iwl_aes_rsc_tsc { +	struct aes_sc unicast_rsc[IWL_NUM_RSC]; +	struct aes_sc multicast_rsc[IWL_NUM_RSC]; +	struct aes_sc tsc; +} __packed; /* AES_TSC_RSC_API_S_VER_1 */ + +union iwl_all_tsc_rsc { +	struct iwl_tkip_rsc_tsc tkip; +	struct iwl_aes_rsc_tsc aes; +}; /* ALL_TSC_RSC_API_S_VER_2 */ + +struct iwl_wowlan_rsc_tsc_params_cmd { +	union iwl_all_tsc_rsc all_tsc_rsc; +} __packed; /* ALL_TSC_RSC_API_S_VER_2 */ + +#define IWL_MIC_KEY_SIZE	8 +struct iwl_mic_keys { +	u8 tx[IWL_MIC_KEY_SIZE]; +	u8 rx_unicast[IWL_MIC_KEY_SIZE]; +	u8 rx_mcast[IWL_MIC_KEY_SIZE]; +} __packed; /* MIC_KEYS_API_S_VER_1 */ + +#define IWL_P1K_SIZE		5 +struct iwl_p1k_cache { +	__le16 p1k[IWL_P1K_SIZE]; +} __packed; + +#define IWL_NUM_RX_P1K_CACHE	2 + +struct iwl_wowlan_tkip_params_cmd { +	struct iwl_mic_keys mic_keys; +	struct iwl_p1k_cache tx; +	struct iwl_p1k_cache rx_uni[IWL_NUM_RX_P1K_CACHE]; +	struct iwl_p1k_cache rx_multi[IWL_NUM_RX_P1K_CACHE]; +} __packed; /* WOWLAN_TKIP_SETTING_API_S_VER_1 */ + +#define IWL_KCK_MAX_SIZE	32 +#define IWL_KEK_MAX_SIZE	32 + +struct iwl_wowlan_kek_kck_material_cmd { +	u8	kck[IWL_KCK_MAX_SIZE]; +	u8	kek[IWL_KEK_MAX_SIZE]; +	__le16	kck_len; +	__le16	kek_len; +	__le64	replay_ctr; +} __packed; /* KEK_KCK_MATERIAL_API_S_VER_2 */ + +#define RF_KILL_INDICATOR_FOR_WOWLAN	0x87 + +enum iwl_wowlan_rekey_status { +	IWL_WOWLAN_REKEY_POST_REKEY = 0, +	IWL_WOWLAN_REKEY_WHILE_REKEY = 1, +}; /* WOWLAN_REKEY_STATUS_API_E_VER_1 */ + +enum iwl_wowlan_wakeup_reason { +	IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS			= 0, +	IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET			= BIT(0), +	IWL_WOWLAN_WAKEUP_BY_PATTERN				= BIT(1), +	IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON	= BIT(2), +	IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH		= BIT(3), +	IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE			= BIT(4), +	IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED			= BIT(5), +	IWL_WOWLAN_WAKEUP_BY_UCODE_ERROR			= BIT(6), +	IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST			= BIT(7), +	IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE			= BIT(8), +	IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS			= BIT(9), +	IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE		= BIT(10), +	IWL_WOWLAN_WAKEUP_BY_REM_WAKE_TCP_EXTERNAL		= BIT(11), +	IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET		= BIT(12), +}; /* WOWLAN_WAKE_UP_REASON_API_E_VER_2 */ + +struct iwl_wowlan_status { +	__le64 replay_ctr; +	__le16 pattern_number; +	__le16 non_qos_seq_ctr; +	__le16 qos_seq_ctr[8]; +	__le32 wakeup_reasons; +	__le32 rekey_status; +	__le32 num_of_gtk_rekeys; +	__le32 transmitted_ndps; +	__le32 received_beacons; +	__le32 wake_packet_length; +	__le32 wake_packet_bufsize; +	u8 wake_packet[]; /* can be truncated from _length to _bufsize */ +} __packed; /* WOWLAN_STATUSES_API_S_VER_4 */ + +/* TODO: NetDetect API */ + +#endif /* __fw_api_d3_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h new file mode 100644 index 00000000000..ae39b7dfda7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-mac.h @@ -0,0 +1,369 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_mac_h__ +#define __fw_api_mac_h__ + +/* + * The first MAC indices (starting from 0) + * are available to the driver, AUX follows + */ +#define MAC_INDEX_AUX		4 +#define MAC_INDEX_MIN_DRIVER	0 +#define NUM_MAC_INDEX_DRIVER	MAC_INDEX_AUX + +#define AC_NUM	4 /* Number of access categories */ + +/** + * enum iwl_mac_protection_flags - MAC context flags + * @MAC_PROT_FLG_TGG_PROTECT: 11g protection when transmitting OFDM frames, + *	this will require CCK RTS/CTS2self. + *	RTS/CTS will protect full burst time. + * @MAC_PROT_FLG_HT_PROT: enable HT protection + * @MAC_PROT_FLG_FAT_PROT: protect 40 MHz transmissions + * @MAC_PROT_FLG_SELF_CTS_EN: allow CTS2self + */ +enum iwl_mac_protection_flags { +	MAC_PROT_FLG_TGG_PROTECT	= BIT(3), +	MAC_PROT_FLG_HT_PROT		= BIT(23), +	MAC_PROT_FLG_FAT_PROT		= BIT(24), +	MAC_PROT_FLG_SELF_CTS_EN	= BIT(30), +}; + +#define MAC_FLG_SHORT_SLOT		BIT(4) +#define MAC_FLG_SHORT_PREAMBLE		BIT(5) + +/** + * enum iwl_mac_types - Supported MAC types + * @FW_MAC_TYPE_FIRST: lowest supported MAC type + * @FW_MAC_TYPE_AUX: Auxiliary MAC (internal) + * @FW_MAC_TYPE_LISTENER: monitor MAC type (?) + * @FW_MAC_TYPE_PIBSS: Pseudo-IBSS + * @FW_MAC_TYPE_IBSS: IBSS + * @FW_MAC_TYPE_BSS_STA: BSS (managed) station + * @FW_MAC_TYPE_P2P_DEVICE: P2P Device + * @FW_MAC_TYPE_P2P_STA: P2P client + * @FW_MAC_TYPE_GO: P2P GO + * @FW_MAC_TYPE_TEST: ? + * @FW_MAC_TYPE_MAX: highest support MAC type + */ +enum iwl_mac_types { +	FW_MAC_TYPE_FIRST = 1, +	FW_MAC_TYPE_AUX = FW_MAC_TYPE_FIRST, +	FW_MAC_TYPE_LISTENER, +	FW_MAC_TYPE_PIBSS, +	FW_MAC_TYPE_IBSS, +	FW_MAC_TYPE_BSS_STA, +	FW_MAC_TYPE_P2P_DEVICE, +	FW_MAC_TYPE_P2P_STA, +	FW_MAC_TYPE_GO, +	FW_MAC_TYPE_TEST, +	FW_MAC_TYPE_MAX = FW_MAC_TYPE_TEST +}; /* MAC_CONTEXT_TYPE_API_E_VER_1 */ + +/** + * enum iwl_tsf_id - TSF hw timer ID + * @TSF_ID_A: use TSF A + * @TSF_ID_B: use TSF B + * @TSF_ID_C: use TSF C + * @TSF_ID_D: use TSF D + * @NUM_TSF_IDS: number of TSF timers available + */ +enum iwl_tsf_id { +	TSF_ID_A = 0, +	TSF_ID_B = 1, +	TSF_ID_C = 2, +	TSF_ID_D = 3, +	NUM_TSF_IDS = 4, +}; /* TSF_ID_API_E_VER_1 */ + +/** + * struct iwl_mac_data_ap - configuration data for AP MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @bi_reciprocal: 2^32 / bi + * @dtim_interval: dtim transmit time in TU + * @dtim_reciprocal: 2^32 / dtim_interval + * @mcast_qid: queue ID for multicast traffic + * @beacon_template: beacon template ID + */ +struct iwl_mac_data_ap { +	__le32 beacon_time; +	__le64 beacon_tsf; +	__le32 bi; +	__le32 bi_reciprocal; +	__le32 dtim_interval; +	__le32 dtim_reciprocal; +	__le32 mcast_qid; +	__le32 beacon_template; +} __packed; /* AP_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_ibss - configuration data for IBSS MAC context + * @beacon_time: beacon transmit time in system time + * @beacon_tsf: beacon transmit time in TSF + * @bi: beacon interval in TU + * @bi_reciprocal: 2^32 / bi + */ +struct iwl_mac_data_ibss { +	__le32 beacon_time; +	__le64 beacon_tsf; +	__le32 bi; +	__le32 bi_reciprocal; +} __packed; /* IBSS_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_sta - configuration data for station MAC context + * @is_assoc: 1 for associated state, 0 otherwise + * @dtim_time: DTIM arrival time in system time + * @dtim_tsf: DTIM arrival time in TSF + * @bi: beacon interval in TU, applicable only when associated + * @bi_reciprocal: 2^32 / bi , applicable only when associated + * @dtim_interval: DTIM interval in TU, applicable only when associated + * @dtim_reciprocal: 2^32 / dtim_interval , applicable only when associated + * @listen_interval: in beacon intervals, applicable only when associated + * @assoc_id: unique ID assigned by the AP during association + */ +struct iwl_mac_data_sta { +	__le32 is_assoc; +	__le32 dtim_time; +	__le64 dtim_tsf; +	__le32 bi; +	__le32 bi_reciprocal; +	__le32 dtim_interval; +	__le32 dtim_reciprocal; +	__le32 listen_interval; +	__le32 assoc_id; +	__le32 assoc_beacon_arrive_time; +} __packed; /* STA_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_go - configuration data for P2P GO MAC context + * @ap: iwl_mac_data_ap struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + *	0 indicates that there is no CT window. + * @opp_ps_enabled: indicate that opportunistic PS allowed + */ +struct iwl_mac_data_go { +	struct iwl_mac_data_ap ap; +	__le32 ctwin; +	__le32 opp_ps_enabled; +} __packed; /* GO_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_p2p_sta - configuration data for P2P client MAC context + * @sta: iwl_mac_data_sta struct with most config data + * @ctwin: client traffic window in TU (period after TBTT when GO is present). + *	0 indicates that there is no CT window. + */ +struct iwl_mac_data_p2p_sta { +	struct iwl_mac_data_sta sta; +	__le32 ctwin; +} __packed; /* P2P_STA_MAC_DATA_API_S_VER_1 */ + +/** + * struct iwl_mac_data_pibss - Pseudo IBSS config data + * @stats_interval: interval in TU between statistics notifications to host. + */ +struct iwl_mac_data_pibss { +	__le32 stats_interval; +} __packed; /* PIBSS_MAC_DATA_API_S_VER_1 */ + +/* + * struct iwl_mac_data_p2p_dev - configuration data for the P2P Device MAC + * context. + * @is_disc_extended: if set to true, P2P Device discoverability is enabled on + *	other channels as well. This should be to true only in case that the + *	device is discoverable and there is an active GO. Note that setting this + *	field when not needed, will increase the number of interrupts and have + *	effect on the platform power, as this setting opens the Rx filters on + *	all macs. + */ +struct iwl_mac_data_p2p_dev { +	__le32 is_disc_extended; +} __packed; /* _P2P_DEV_MAC_DATA_API_S_VER_1 */ + +/** + * enum iwl_mac_filter_flags - MAC context filter flags + * @MAC_FILTER_IN_PROMISC: accept all data frames + * @MAC_FILTER_IN_CONTROL_AND_MGMT: pass all mangement and + *	control frames to the host + * @MAC_FILTER_ACCEPT_GRP: accept multicast frames + * @MAC_FILTER_DIS_DECRYPT: don't decrypt unicast frames + * @MAC_FILTER_DIS_GRP_DECRYPT: don't decrypt multicast frames + * @MAC_FILTER_IN_BEACON: transfer foreign BSS's beacons to host + *	(in station mode when associated) + * @MAC_FILTER_OUT_BCAST: filter out all broadcast frames + * @MAC_FILTER_IN_CRC32: extract FCS and append it to frames + * @MAC_FILTER_IN_PROBE_REQUEST: pass probe requests to host + */ +enum iwl_mac_filter_flags { +	MAC_FILTER_IN_PROMISC		= BIT(0), +	MAC_FILTER_IN_CONTROL_AND_MGMT	= BIT(1), +	MAC_FILTER_ACCEPT_GRP		= BIT(2), +	MAC_FILTER_DIS_DECRYPT		= BIT(3), +	MAC_FILTER_DIS_GRP_DECRYPT	= BIT(4), +	MAC_FILTER_IN_BEACON		= BIT(6), +	MAC_FILTER_OUT_BCAST		= BIT(8), +	MAC_FILTER_IN_CRC32		= BIT(11), +	MAC_FILTER_IN_PROBE_REQUEST	= BIT(12), +}; + +/** + * enum iwl_mac_qos_flags - QoS flags + * @MAC_QOS_FLG_UPDATE_EDCA: ? + * @MAC_QOS_FLG_TGN: HT is enabled + * @MAC_QOS_FLG_TXOP_TYPE: ? + * + */ +enum iwl_mac_qos_flags { +	MAC_QOS_FLG_UPDATE_EDCA	= BIT(0), +	MAC_QOS_FLG_TGN		= BIT(1), +	MAC_QOS_FLG_TXOP_TYPE	= BIT(4), +}; + +/** + * struct iwl_ac_qos - QOS timing params for MAC_CONTEXT_CMD + * @cw_min: Contention window, start value in numbers of slots. + *	Should be a power-of-2, minus 1.  Device's default is 0x0f. + * @cw_max: Contention window, max value in numbers of slots. + *	Should be a power-of-2, minus 1.  Device's default is 0x3f. + * @aifsn:  Number of slots in Arbitration Interframe Space (before + *	performing random backoff timing prior to Tx).  Device default 1. + * @fifos_mask: FIFOs used by this MAC for this AC + * @edca_txop:  Length of Tx opportunity, in uSecs.  Device default is 0. + * + * One instance of this config struct for each of 4 EDCA access categories + * in struct iwl_qosparam_cmd. + * + * Device will automatically increase contention window by (2*CW) + 1 for each + * transmission retry.  Device uses cw_max as a bit mask, ANDed with new CW + * value, to cap the CW value. + */ +struct iwl_ac_qos { +	__le16 cw_min; +	__le16 cw_max; +	u8 aifsn; +	u8 fifos_mask; +	__le16 edca_txop; +} __packed; /* AC_QOS_API_S_VER_2 */ + +/** + * struct iwl_mac_ctx_cmd - command structure to configure MAC contexts + * ( MAC_CONTEXT_CMD = 0x28 ) + * @id_and_color: ID and color of the MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @mac_type: one of FW_MAC_TYPE_* + * @tsd_id: TSF HW timer, one of TSF_ID_* + * @node_addr: MAC address + * @bssid_addr: BSSID + * @cck_rates: basic rates available for CCK + * @ofdm_rates: basic rates available for OFDM + * @protection_flags: combination of MAC_PROT_FLG_FLAG_* + * @cck_short_preamble: 0x20 for enabling short preamble, 0 otherwise + * @short_slot: 0x10 for enabling short slots, 0 otherwise + * @filter_flags: combination of MAC_FILTER_* + * @qos_flags: from MAC_QOS_FLG_* + * @ac: one iwl_mac_qos configuration for each AC + * @mac_specific: one of struct iwl_mac_data_*, according to mac_type + */ +struct iwl_mac_ctx_cmd { +	/* COMMON_INDEX_HDR_API_S_VER_1 */ +	__le32 id_and_color; +	__le32 action; +	/* MAC_CONTEXT_COMMON_DATA_API_S_VER_1 */ +	__le32 mac_type; +	__le32 tsf_id; +	u8 node_addr[6]; +	__le16 reserved_for_node_addr; +	u8 bssid_addr[6]; +	__le16 reserved_for_bssid_addr; +	__le32 cck_rates; +	__le32 ofdm_rates; +	__le32 protection_flags; +	__le32 cck_short_preamble; +	__le32 short_slot; +	__le32 filter_flags; +	/* MAC_QOS_PARAM_API_S_VER_1 */ +	__le32 qos_flags; +	struct iwl_ac_qos ac[AC_NUM+1]; +	/* MAC_CONTEXT_COMMON_DATA_API_S */ +	union { +		struct iwl_mac_data_ap ap; +		struct iwl_mac_data_go go; +		struct iwl_mac_data_sta sta; +		struct iwl_mac_data_p2p_sta p2p_sta; +		struct iwl_mac_data_p2p_dev p2p_dev; +		struct iwl_mac_data_pibss pibss; +		struct iwl_mac_data_ibss ibss; +	}; +} __packed; /* MAC_CONTEXT_CMD_API_S_VER_1 */ + +static inline u32 iwl_mvm_reciprocal(u32 v) +{ +	if (!v) +		return 0; +	return 0xFFFFFFFF / v; +} + +#endif /* __fw_api_mac_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h new file mode 100644 index 00000000000..be36b7604b7 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-power.h @@ -0,0 +1,140 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_power_h__ +#define __fw_api_power_h__ + +/* Power Management Commands, Responses, Notifications */ + +/** + * enum iwl_scan_flags - masks for power table command flags + * @POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK: '0' Driver disables power management, + *		'1' Driver enables PM (use rest of parameters) + * @POWER_FLAGS_SLEEP_OVER_DTIM_MSK: '0' PM have to walk up every DTIM, + *		'1' PM could sleep over DTIM till listen Interval. + * @POWER_FLAGS_LPRX_ENA_MSK: Low Power RX enable. + * @POWER_FLAGS_SNOOZE_ENA_MSK: Enable snoozing only if uAPSD is enabled and all + *		access categories are both delivery and trigger enabled. + * @POWER_FLAGS_BT_SCO_ENA: Enable BT SCO coex only if uAPSD and + *		PBW Snoozing enabled + * @POWER_FLAGS_ADVANCE_PM_ENA_MSK: Advanced PM (uAPSD) enable mask +*/ +enum iwl_power_flags { +	POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK	= BIT(0), +	POWER_FLAGS_SLEEP_OVER_DTIM_MSK		= BIT(1), +	POWER_FLAGS_LPRX_ENA_MSK		= BIT(2), +	POWER_FLAGS_SNOOZE_ENA_MSK		= BIT(3), +	POWER_FLAGS_BT_SCO_ENA			= BIT(4), +	POWER_FLAGS_ADVANCE_PM_ENA_MSK		= BIT(5) +}; + +/** + * struct iwl_powertable_cmd - Power Table Command + * POWER_TABLE_CMD = 0x77 (command, has simple generic response) + * + * @id_and_color:	MAC contex identifier + * @action:		Action on context - no action, add new, + *			modify existent, remove + * @flags:		Power table command flags from POWER_FLAGS_* + * @keep_alive_seconds: Keep alive period in seconds. Default - 25 sec. + *			Minimum allowed:- 3 * DTIM + * @rx_data_timeout:    Minimum time (usec) from last Rx packet for AM to + *			PSM transition - legacy PM + * @tx_data_timeout:    Minimum time (usec) from last Tx packet for AM to + *			PSM transition - legacy PM + * @rx_data_timeout_uapsd: Minimum time (usec) from last Rx packet for AM to + *			PSM transition - uAPSD + * @tx_data_timeout_uapsd: Minimum time (usec) from last Tx packet for AM to + *			PSM transition - uAPSD + * @lprx_rssi_threshold: Signal strength up to which LP RX can be enabled. + *			Default: 80dbm + * @num_skip_dtim:      Number of DTIMs to skip if Skip over DTIM flag is set + * @snooze_interval:    TBD + * @snooze_window:      TBD + * @snooze_step:        TBD + * @qndp_tid:           TBD + * @uapsd_ac_flags:     TBD + * @uapsd_max_sp:       TBD + */ +struct iwl_powertable_cmd { +	/* COMMON_INDEX_HDR_API_S_VER_1 */ +	__le32 id_and_color; +	__le32 action; +	__le16 flags; +	u8 reserved; +	__le16 keep_alive_seconds; +	__le32 rx_data_timeout; +	__le32 tx_data_timeout; +	__le32 rx_data_timeout_uapsd; +	__le32 tx_data_timeout_uapsd; +	u8 lprx_rssi_threshold; +	u8 num_skip_dtim; +	__le16 snooze_interval; +	__le16 snooze_window; +	u8 snooze_step; +	u8 qndp_tid; +	u8 uapsd_ac_flags; +	u8 uapsd_max_sp; +} __packed; + +#endif diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h new file mode 100644 index 00000000000..aa3474d0823 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-rs.h @@ -0,0 +1,312 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_rs_h__ +#define __fw_api_rs_h__ + +#include "fw-api-mac.h" + +/* + * These serve as indexes into + * struct iwl_rate_info fw_rate_idx_to_plcp[IWL_RATE_COUNT]; + */ +enum { +	IWL_RATE_1M_INDEX = 0, +	IWL_FIRST_CCK_RATE = IWL_RATE_1M_INDEX, +	IWL_RATE_2M_INDEX, +	IWL_RATE_5M_INDEX, +	IWL_RATE_11M_INDEX, +	IWL_LAST_CCK_RATE = IWL_RATE_11M_INDEX, +	IWL_RATE_6M_INDEX, +	IWL_FIRST_OFDM_RATE = IWL_RATE_6M_INDEX, +	IWL_RATE_9M_INDEX, +	IWL_RATE_12M_INDEX, +	IWL_RATE_18M_INDEX, +	IWL_RATE_24M_INDEX, +	IWL_RATE_36M_INDEX, +	IWL_RATE_48M_INDEX, +	IWL_RATE_54M_INDEX, +	IWL_LAST_NON_HT_RATE = IWL_RATE_54M_INDEX, +	IWL_RATE_60M_INDEX, +	IWL_LAST_OFDM_RATE = IWL_RATE_60M_INDEX, +	IWL_RATE_COUNT_LEGACY = IWL_LAST_NON_HT_RATE + 1, +	IWL_RATE_COUNT, +}; + +#define IWL_RATE_BIT_MSK(r) BIT(IWL_RATE_##r##M_INDEX) + +/* fw API values for legacy bit rates, both OFDM and CCK */ +enum { +	IWL_RATE_6M_PLCP  = 13, +	IWL_RATE_9M_PLCP  = 15, +	IWL_RATE_12M_PLCP = 5, +	IWL_RATE_18M_PLCP = 7, +	IWL_RATE_24M_PLCP = 9, +	IWL_RATE_36M_PLCP = 11, +	IWL_RATE_48M_PLCP = 1, +	IWL_RATE_54M_PLCP = 3, +	IWL_RATE_1M_PLCP  = 10, +	IWL_RATE_2M_PLCP  = 20, +	IWL_RATE_5M_PLCP  = 55, +	IWL_RATE_11M_PLCP = 110, +}; + +/* + * rate_n_flags bit fields + * + * The 32-bit value has different layouts in the low 8 bites depending on the + * format. There are three formats, HT, VHT and legacy (11abg, with subformats + * for CCK and OFDM). + * + * High-throughput (HT) rate format + *	bit 8 is 1, bit 26 is 0, bit 9 is 0 (OFDM) + * Very High-throughput (VHT) rate format + *	bit 8 is 0, bit 26 is 1, bit 9 is 0 (OFDM) + * Legacy OFDM rate format for bits 7:0 + *	bit 8 is 0, bit 26 is 0, bit 9 is 0 (OFDM) + * Legacy CCK rate format for bits 7:0: + *	bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK) + */ + +/* Bit 8: (1) HT format, (0) legacy or VHT format */ +#define RATE_MCS_HT_POS 8 +#define RATE_MCS_HT_MSK (1 << RATE_MCS_HT_POS) + +/* Bit 9: (1) CCK, (0) OFDM.  HT (bit 8) must be "0" for this bit to be valid */ +#define RATE_MCS_CCK_POS 9 +#define RATE_MCS_CCK_MSK (1 << RATE_MCS_CCK_POS) + +/* Bit 26: (1) VHT format, (0) legacy format in bits 8:0 */ +#define RATE_MCS_VHT_POS 26 +#define RATE_MCS_VHT_MSK (1 << RATE_MCS_VHT_POS) + + +/* + * High-throughput (HT) rate format for bits 7:0 + * + *  2-0:  MCS rate base + *        0)   6 Mbps + *        1)  12 Mbps + *        2)  18 Mbps + *        3)  24 Mbps + *        4)  36 Mbps + *        5)  48 Mbps + *        6)  54 Mbps + *        7)  60 Mbps + *  4-3:  0)  Single stream (SISO) + *        1)  Dual stream (MIMO) + *        2)  Triple stream (MIMO) + *    5:  Value of 0x20 in bits 7:0 indicates 6 Mbps HT40 duplicate data + *  (bits 7-6 are zero) + * + * Together the low 5 bits work out to the MCS index because we don't + * support MCSes above 15/23, and 0-7 have one stream, 8-15 have two + * streams and 16-23 have three streams. We could also support MCS 32 + * which is the duplicate 20 MHz MCS (bit 5 set, all others zero.) + */ +#define RATE_HT_MCS_RATE_CODE_MSK	0x7 + +/* Bit 10: (1) Use Green Field preamble */ +#define RATE_HT_MCS_GF_POS		10 +#define RATE_HT_MCS_GF_MSK		(1 << RATE_HT_MCS_GF_POS) + +#define RATE_HT_MCS_INDEX_MSK		0x3f + +/* + * Very High-throughput (VHT) rate format for bits 7:0 + * + *  3-0:  VHT MCS (0-9) + *  5-4:  number of streams - 1: + *        0)  Single stream (SISO) + *        1)  Dual stream (MIMO) + *        2)  Triple stream (MIMO) + */ + +/* Bit 4-5: (0) SISO, (1) MIMO2 (2) MIMO3 */ +#define RATE_VHT_MCS_RATE_CODE_MSK	0xf +#define RATE_VHT_MCS_NSS_POS		4 +#define RATE_VHT_MCS_NSS_MSK		(3 << RATE_VHT_MCS_NSS_POS) + +/* + * Legacy OFDM rate format for bits 7:0 + * + *  3-0:  0xD)   6 Mbps + *        0xF)   9 Mbps + *        0x5)  12 Mbps + *        0x7)  18 Mbps + *        0x9)  24 Mbps + *        0xB)  36 Mbps + *        0x1)  48 Mbps + *        0x3)  54 Mbps + * (bits 7-4 are 0) + * + * Legacy CCK rate format for bits 7:0: + * bit 8 is 0, bit 26 is 0, bit 9 is 1 (CCK): + * + *  6-0:   10)  1 Mbps + *         20)  2 Mbps + *         55)  5.5 Mbps + *        110)  11 Mbps + * (bit 7 is 0) + */ +#define RATE_LEGACY_RATE_MSK 0xff + + +/* + * Bit 11-12: (0) 20MHz, (1) 40MHz, (2) 80MHz, (3) 160MHz + * 0 and 1 are valid for HT and VHT, 2 and 3 only for VHT + */ +#define RATE_MCS_CHAN_WIDTH_POS		11 +#define RATE_MCS_CHAN_WIDTH_MSK		(3 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_20		(0 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_40		(1 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_80		(2 << RATE_MCS_CHAN_WIDTH_POS) +#define RATE_MCS_CHAN_WIDTH_160		(3 << RATE_MCS_CHAN_WIDTH_POS) + +/* Bit 13: (1) Short guard interval (0.4 usec), (0) normal GI (0.8 usec) */ +#define RATE_MCS_SGI_POS		13 +#define RATE_MCS_SGI_MSK		(1 << RATE_MCS_SGI_POS) + +/* Bit 14-16: Antenna selection (1) Ant A, (2) Ant B, (4) Ant C */ +#define RATE_MCS_ANT_POS		14 +#define RATE_MCS_ANT_A_MSK		(1 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_B_MSK		(2 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_C_MSK		(4 << RATE_MCS_ANT_POS) +#define RATE_MCS_ANT_AB_MSK		(RATE_MCS_ANT_A_MSK | \ +					 RATE_MCS_ANT_B_MSK) +#define RATE_MCS_ANT_ABC_MSK		(RATE_MCS_ANT_AB_MSK | \ +					 RATE_MCS_ANT_C_MSK) +#define RATE_MCS_ANT_MSK		RATE_MCS_ANT_ABC_MSK +#define RATE_MCS_ANT_NUM 3 + +/* Bit 17-18: (0) SS, (1) SS*2 */ +#define RATE_MCS_STBC_POS		17 +#define RATE_MCS_STBC_MSK		(1 << RATE_MCS_STBC_POS) + +/* Bit 19: (0) Beamforming is off, (1) Beamforming is on */ +#define RATE_MCS_BF_POS			19 +#define RATE_MCS_BF_MSK			(1 << RATE_MCS_BF_POS) + +/* Bit 20: (0) ZLF is off, (1) ZLF is on */ +#define RATE_MCS_ZLF_POS		20 +#define RATE_MCS_ZLF_MSK		(1 << RATE_MCS_ZLF_POS) + +/* Bit 24-25: (0) 20MHz (no dup), (1) 2x20MHz, (2) 4x20MHz, 3 8x20MHz */ +#define RATE_MCS_DUP_POS		24 +#define RATE_MCS_DUP_MSK		(3 << RATE_MCS_DUP_POS) + +/* Bit 27: (1) LDPC enabled, (0) LDPC disabled */ +#define RATE_MCS_LDPC_POS		27 +#define RATE_MCS_LDPC_MSK		(1 << RATE_MCS_LDPC_POS) + + +/* Link Quality definitions */ + +/* # entries in rate scale table to support Tx retries */ +#define  LQ_MAX_RETRY_NUM 16 + +/* Link quality command flags, only this one is available */ +#define  LQ_FLAG_SET_STA_TLC_RTS_MSK	BIT(0) + +/** + * struct iwl_lq_cmd - link quality command + * @sta_id: station to update + * @control: not used + * @flags: combination of LQ_FLAG_* + * @mimo_delim: the first SISO index in rs_table, which separates MIMO + *	and SISO rates + * @single_stream_ant_msk: best antenna for SISO (can be dual in CDD). + *	Should be ANT_[ABC] + * @dual_stream_ant_msk: best antennas for MIMO, combination of ANT_[ABC] + * @initial_rate_index: first index from rs_table per AC category + * @agg_time_limit: aggregation max time threshold in usec/100, meaning + *	value of 100 is one usec. Range is 100 to 8000 + * @agg_disable_start_th: try-count threshold for starting aggregation. + *	If a frame has higher try-count, it should not be selected for + *	starting an aggregation sequence. + * @agg_frame_cnt_limit: max frame count in an aggregation. + *	0: no limit + *	1: no aggregation (one frame per aggregation) + *	2 - 0x3f: maximal number of frames (up to 3f == 63) + * @rs_table: array of rates for each TX try, each is rate_n_flags, + *	meaning it is a combination of RATE_MCS_* and IWL_RATE_*_PLCP + * @bf_params: beam forming params, currently not used + */ +struct iwl_lq_cmd { +	u8 sta_id; +	u8 reserved1; +	u16 control; +	/* LINK_QUAL_GENERAL_PARAMS_API_S_VER_1 */ +	u8 flags; +	u8 mimo_delim; +	u8 single_stream_ant_msk; +	u8 dual_stream_ant_msk; +	u8 initial_rate_index[AC_NUM]; +	/* LINK_QUAL_AGG_PARAMS_API_S_VER_1 */ +	__le16 agg_time_limit; +	u8 agg_disable_start_th; +	u8 agg_frame_cnt_limit; +	__le32 reserved2; +	__le32 rs_table[LQ_MAX_RETRY_NUM]; +	__le32 bf_params; +}; /* LINK_QUALITY_CMD_API_S_VER_1 */ +#endif /* __fw_api_rs_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h new file mode 100644 index 00000000000..670ac8f95e2 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-scan.h @@ -0,0 +1,561 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_scan_h__ +#define __fw_api_scan_h__ + +#include "fw-api.h" + +/* Scan Commands, Responses, Notifications */ + +/* Masks for iwl_scan_channel.type flags */ +#define SCAN_CHANNEL_TYPE_PASSIVE	0 +#define SCAN_CHANNEL_TYPE_ACTIVE	BIT(0) +#define SCAN_CHANNEL_NARROW_BAND	BIT(22) + +/* Max number of IEs for direct SSID scans in a command */ +#define PROBE_OPTION_MAX		20 + +/** + * struct iwl_scan_channel - entry in REPLY_SCAN_CMD channel table + * @channel: band is selected by iwl_scan_cmd "flags" field + * @tx_gain: gain for analog radio + * @dsp_atten: gain for DSP + * @active_dwell: dwell time for active scan in TU, typically 5-50 + * @passive_dwell: dwell time for passive scan in TU, typically 20-500 + * @type: type is broken down to these bits: + *	bit 0: 0 = passive, 1 = active + *	bits 1-20: SSID direct bit map. If any of these bits is set then + *		the corresponding SSID IE is transmitted in probe request + *		(bit i adds IE in position i to the probe request) + *	bit 22: channel width, 0 = regular, 1 = TGj narrow channel + * + * @iteration_count: + * @iteration_interval: + * This struct is used once for each channel in the scan list. + * Each channel can independently select: + * 1)  SSID for directed active scans + * 2)  Txpower setting (for rate specified within Tx command) + * 3)  How long to stay on-channel (behavior may be modified by quiet_time, + *     quiet_plcp_th, good_CRC_th) + * + * To avoid uCode errors, make sure the following are true (see comments + * under struct iwl_scan_cmd about max_out_time and quiet_time): + * 1)  If using passive_dwell (i.e. passive_dwell != 0): + *     active_dwell <= passive_dwell (< max_out_time if max_out_time != 0) + * 2)  quiet_time <= active_dwell + * 3)  If restricting off-channel time (i.e. max_out_time !=0): + *     passive_dwell < max_out_time + *     active_dwell < max_out_time + */ +struct iwl_scan_channel { +	__le32 type; +	__le16 channel; +	__le16 iteration_count; +	__le32 iteration_interval; +	__le16 active_dwell; +	__le16 passive_dwell; +} __packed; /* SCAN_CHANNEL_CONTROL_API_S_VER_1 */ + +/** + * struct iwl_ssid_ie - directed scan network information element + * + * Up to 20 of these may appear in REPLY_SCAN_CMD, + * selected by "type" bit field in struct iwl_scan_channel; + * each channel may select different ssids from among the 20 entries. + * SSID IEs get transmitted in reverse order of entry. + */ +struct iwl_ssid_ie { +	u8 id; +	u8 len; +	u8 ssid[IEEE80211_MAX_SSID_LEN]; +} __packed; /* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ + +/** + * iwl_scan_flags - masks for scan command flags + *@SCAN_FLAGS_PERIODIC_SCAN: + *@SCAN_FLAGS_P2P_PUBLIC_ACTION_FRAME_TX: + *@SCAN_FLAGS_DELAYED_SCAN_LOWBAND: + *@SCAN_FLAGS_DELAYED_SCAN_HIGHBAND: + *@SCAN_FLAGS_FRAGMENTED_SCAN: + */ +enum iwl_scan_flags { +	SCAN_FLAGS_PERIODIC_SCAN		= BIT(0), +	SCAN_FLAGS_P2P_PUBLIC_ACTION_FRAME_TX	= BIT(1), +	SCAN_FLAGS_DELAYED_SCAN_LOWBAND		= BIT(2), +	SCAN_FLAGS_DELAYED_SCAN_HIGHBAND	= BIT(3), +	SCAN_FLAGS_FRAGMENTED_SCAN		= BIT(4), +}; + +/** + * enum iwl_scan_type - Scan types for scan command + * @SCAN_TYPE_FORCED: + * @SCAN_TYPE_BACKGROUND: + * @SCAN_TYPE_OS: + * @SCAN_TYPE_ROAMING: + * @SCAN_TYPE_ACTION: + * @SCAN_TYPE_DISCOVERY: + * @SCAN_TYPE_DISCOVERY_FORCED: + */ +enum iwl_scan_type { +	SCAN_TYPE_FORCED		= 0, +	SCAN_TYPE_BACKGROUND		= 1, +	SCAN_TYPE_OS			= 2, +	SCAN_TYPE_ROAMING		= 3, +	SCAN_TYPE_ACTION		= 4, +	SCAN_TYPE_DISCOVERY		= 5, +	SCAN_TYPE_DISCOVERY_FORCED	= 6, +}; /* SCAN_ACTIVITY_TYPE_E_VER_1 */ + +/* Maximal number of channels to scan */ +#define MAX_NUM_SCAN_CHANNELS 0x24 + +/** + * struct iwl_scan_cmd - scan request command + * ( SCAN_REQUEST_CMD = 0x80 ) + * @len: command length in bytes + * @scan_flags: scan flags from SCAN_FLAGS_* + * @channel_count: num of channels in channel list (1 - MAX_NUM_SCAN_CHANNELS) + * @quiet_time: in msecs, dwell this time for active scan on quiet channels + * @quiet_plcp_th: quiet PLCP threshold (channel is quiet if less than + *	this number of packets were received (typically 1) + * @passive2active: is auto switching from passive to active allowed (0 or 1) + * @rxchain_sel_flags: RXON_RX_CHAIN_* + * @max_out_time: in usecs, max out of serving channel time + * @suspend_time: how long to pause scan when returning to service channel: + *	bits 0-19: beacon interal in usecs (suspend before executing) + *	bits 20-23: reserved + *	bits 24-31: number of beacons (suspend between channels) + * @rxon_flags: RXON_FLG_* + * @filter_flags: RXON_FILTER_* + * @tx_cmd: for active scans (zero for passive), w/o payload, + *	no RS so specify TX rate + * @direct_scan: direct scan SSIDs + * @type: one of SCAN_TYPE_* + * @repeats: how many time to repeat the scan + */ +struct iwl_scan_cmd { +	__le16 len; +	u8 scan_flags; +	u8 channel_count; +	__le16 quiet_time; +	__le16 quiet_plcp_th; +	__le16 passive2active; +	__le16 rxchain_sel_flags; +	__le32 max_out_time; +	__le32 suspend_time; +	/* RX_ON_FLAGS_API_S_VER_1 */ +	__le32 rxon_flags; +	__le32 filter_flags; +	struct iwl_tx_cmd tx_cmd; +	struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +	__le32 type; +	__le32 repeats; + +	/* +	 * Probe request frame, followed by channel list. +	 * +	 * Size of probe request frame is specified by byte count in tx_cmd. +	 * Channel list follows immediately after probe request frame. +	 * Number of channels in list is specified by channel_count. +	 * Each channel in list is of type: +	 * +	 * struct iwl_scan_channel channels[0]; +	 * +	 * NOTE:  Only one band of channels can be scanned per pass.  You +	 * must not mix 2.4GHz channels and 5.2GHz channels, and you must wait +	 * for one scan to complete (i.e. receive SCAN_COMPLETE_NOTIFICATION) +	 * before requesting another scan. +	 */ +	u8 data[0]; +} __packed; /* SCAN_REQUEST_FIXED_PART_API_S_VER_5 */ + +/* Response to scan request contains only status with one of these values */ +#define SCAN_RESPONSE_OK	0x1 +#define SCAN_RESPONSE_ERROR	0x2 + +/* + * SCAN_ABORT_CMD = 0x81 + * When scan abort is requested, the command has no fields except the common + * header. The response contains only a status with one of these values. + */ +#define SCAN_ABORT_POSSIBLE	0x1 +#define SCAN_ABORT_IGNORED	0x2 /* no pending scans */ + +/* TODO: complete documentation */ +#define  SCAN_OWNER_STATUS 0x1 +#define  MEASURE_OWNER_STATUS 0x2 + +/** + * struct iwl_scan_start_notif - notifies start of scan in the device + * ( SCAN_START_NOTIFICATION = 0x82 ) + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @beacon_timer: structured as follows: + *	bits 0:19 - beacon interval in usecs + *	bits 20:23 - reserved (0) + *	bits 24:31 - number of beacons + * @channel: which channel is scanned + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @status: one of *_OWNER_STATUS + */ +struct iwl_scan_start_notif { +	__le32 tsf_low; +	__le32 tsf_high; +	__le32 beacon_timer; +	u8 channel; +	u8 band; +	u8 reserved[2]; +	__le32 status; +} __packed; /* SCAN_START_NTF_API_S_VER_1 */ + +/* scan results probe_status first bit indicates success */ +#define SCAN_PROBE_STATUS_OK		0 +#define SCAN_PROBE_STATUS_TX_FAILED	BIT(0) +/* error statuses combined with TX_FAILED */ +#define SCAN_PROBE_STATUS_FAIL_TTL	BIT(1) +#define SCAN_PROBE_STATUS_FAIL_BT	BIT(2) + +/* How many statistics are gathered for each channel */ +#define SCAN_RESULTS_STATISTICS 1 + +/** + * enum iwl_scan_complete_status - status codes for scan complete notifications + * @SCAN_COMP_STATUS_OK:  scan completed successfully + * @SCAN_COMP_STATUS_ABORT: scan was aborted by user + * @SCAN_COMP_STATUS_ERR_SLEEP: sending null sleep packet failed + * @SCAN_COMP_STATUS_ERR_CHAN_TIMEOUT: timeout before channel is ready + * @SCAN_COMP_STATUS_ERR_PROBE: sending probe request failed + * @SCAN_COMP_STATUS_ERR_WAKEUP: sending null wakeup packet failed + * @SCAN_COMP_STATUS_ERR_ANTENNAS: invalid antennas chosen at scan command + * @SCAN_COMP_STATUS_ERR_INTERNAL: internal error caused scan abort + * @SCAN_COMP_STATUS_ERR_COEX: medium was lost ot WiMax + * @SCAN_COMP_STATUS_P2P_ACTION_OK: P2P public action frame TX was successful + *	(not an error!) + * @SCAN_COMP_STATUS_ITERATION_END: indicates end of one repeatition the driver + *	asked for + * @SCAN_COMP_STATUS_ERR_ALLOC_TE: scan could not allocate time events +*/ +enum iwl_scan_complete_status { +	SCAN_COMP_STATUS_OK = 0x1, +	SCAN_COMP_STATUS_ABORT = 0x2, +	SCAN_COMP_STATUS_ERR_SLEEP = 0x3, +	SCAN_COMP_STATUS_ERR_CHAN_TIMEOUT = 0x4, +	SCAN_COMP_STATUS_ERR_PROBE = 0x5, +	SCAN_COMP_STATUS_ERR_WAKEUP = 0x6, +	SCAN_COMP_STATUS_ERR_ANTENNAS = 0x7, +	SCAN_COMP_STATUS_ERR_INTERNAL = 0x8, +	SCAN_COMP_STATUS_ERR_COEX = 0x9, +	SCAN_COMP_STATUS_P2P_ACTION_OK = 0xA, +	SCAN_COMP_STATUS_ITERATION_END = 0x0B, +	SCAN_COMP_STATUS_ERR_ALLOC_TE = 0x0C, +}; + +/** + * struct iwl_scan_results_notif - scan results for one channel + * ( SCAN_RESULTS_NOTIFICATION = 0x83 ) + * @channel: which channel the results are from + * @band: 0 for 5.2 GHz, 1 for 2.4 GHz + * @probe_status: SCAN_PROBE_STATUS_*, indicates success of probe request + * @num_probe_not_sent: # of request that weren't sent due to not enough time + * @duration: duration spent in channel, in usecs + * @statistics: statistics gathered for this channel + */ +struct iwl_scan_results_notif { +	u8 channel; +	u8 band; +	u8 probe_status; +	u8 num_probe_not_sent; +	__le32 duration; +	__le32 statistics[SCAN_RESULTS_STATISTICS]; +} __packed; /* SCAN_RESULT_NTF_API_S_VER_2 */ + +/** + * struct iwl_scan_complete_notif - notifies end of scanning (all channels) + * ( SCAN_COMPLETE_NOTIFICATION = 0x84 ) + * @scanned_channels: number of channels scanned (and number of valid results) + * @status: one of SCAN_COMP_STATUS_* + * @bt_status: BT on/off status + * @last_channel: last channel that was scanned + * @tsf_low: TSF timer (lower half) in usecs + * @tsf_high: TSF timer (higher half) in usecs + * @results: all scan results, only "scanned_channels" of them are valid + */ +struct iwl_scan_complete_notif { +	u8 scanned_channels; +	u8 status; +	u8 bt_status; +	u8 last_channel; +	__le32 tsf_low; +	__le32 tsf_high; +	struct iwl_scan_results_notif results[MAX_NUM_SCAN_CHANNELS]; +} __packed; /* SCAN_COMPLETE_NTF_API_S_VER_2 */ + +/* scan offload */ +#define IWL_MAX_SCAN_CHANNELS		40 +#define IWL_SCAN_MAX_BLACKLIST_LEN	64 +#define IWL_SCAN_MAX_PROFILES		11 +#define SCAN_OFFLOAD_PROBE_REQ_SIZE	512 + +/* Default watchdog (in MS) for scheduled scan iteration */ +#define IWL_SCHED_SCAN_WATCHDOG cpu_to_le16(15000) + +#define IWL_GOOD_CRC_TH_DEFAULT cpu_to_le16(1) +#define CAN_ABORT_STATUS 1 + +#define IWL_FULL_SCAN_MULTIPLIER 5 +#define IWL_FAST_SCHED_SCAN_ITERATIONS 3 + +/** + * struct iwl_scan_offload_cmd - SCAN_REQUEST_FIXED_PART_API_S_VER_6 + * @scan_flags:		see enum iwl_scan_flags + * @channel_count:	channels in channel list + * @quiet_time:		dwell time, in milisiconds, on quiet channel + * @quiet_plcp_th:	quiet channel num of packets threshold + * @good_CRC_th:	passive to active promotion threshold + * @rx_chain:		RXON rx chain. + * @max_out_time:	max uSec to be out of assoceated channel + * @suspend_time:	pause scan this long when returning to service channel + * @flags:		RXON flags + * @filter_flags:	RXONfilter + * @tx_cmd:		tx command for active scan; for 2GHz and for 5GHz. + * @direct_scan:	list of SSIDs for directed active scan + * @scan_type:		see enum iwl_scan_type. + * @rep_count:		repetition count for each scheduled scan iteration. + */ +struct iwl_scan_offload_cmd { +	__le16 len; +	u8 scan_flags; +	u8 channel_count; +	__le16 quiet_time; +	__le16 quiet_plcp_th; +	__le16 good_CRC_th; +	__le16 rx_chain; +	__le32 max_out_time; +	__le32 suspend_time; +	/* RX_ON_FLAGS_API_S_VER_1 */ +	__le32 flags; +	__le32 filter_flags; +	struct iwl_tx_cmd tx_cmd[2]; +	/* SCAN_DIRECT_SSID_IE_API_S_VER_1 */ +	struct iwl_ssid_ie direct_scan[PROBE_OPTION_MAX]; +	__le32 scan_type; +	__le32 rep_count; +} __packed; + +enum iwl_scan_offload_channel_flags { +	IWL_SCAN_OFFLOAD_CHANNEL_ACTIVE		= BIT(0), +	IWL_SCAN_OFFLOAD_CHANNEL_NARROW		= BIT(22), +	IWL_SCAN_OFFLOAD_CHANNEL_FULL		= BIT(24), +	IWL_SCAN_OFFLOAD_CHANNEL_PARTIAL	= BIT(25), +}; + +/** + * iwl_scan_channel_cfg - SCAN_CHANNEL_CFG_S + * @type:		bitmap - see enum iwl_scan_offload_channel_flags. + *			0:	passive (0) or active (1) scan. + *			1-20:	directed scan to i'th ssid. + *			22:	channel width configuation - 1 for narrow. + *			24:	full scan. + *			25:	partial scan. + * @channel_number:	channel number 1-13 etc. + * @iter_count:		repetition count for the channel. + * @iter_interval:	interval between two innteration on one channel. + * @dwell_time:	entry 0 - active scan, entry 1 - passive scan. + */ +struct iwl_scan_channel_cfg { +	__le32 type[IWL_MAX_SCAN_CHANNELS]; +	__le16 channel_number[IWL_MAX_SCAN_CHANNELS]; +	__le16 iter_count[IWL_MAX_SCAN_CHANNELS]; +	__le32 iter_interval[IWL_MAX_SCAN_CHANNELS]; +	u8 dwell_time[IWL_MAX_SCAN_CHANNELS][2]; +} __packed; + +/** + * iwl_scan_offload_cfg - SCAN_OFFLOAD_CONFIG_API_S + * @scan_cmd:		scan command fixed part + * @channel_cfg:	scan channel configuration + * @data:		probe request frames (one per band) + */ +struct iwl_scan_offload_cfg { +	struct iwl_scan_offload_cmd scan_cmd; +	struct iwl_scan_channel_cfg channel_cfg; +	u8 data[0]; +} __packed; + +/** + * iwl_scan_offload_blacklist - SCAN_OFFLOAD_BLACKLIST_S + * @ssid:		MAC address to filter out + * @reported_rssi:	AP rssi reported to the host + */ +struct iwl_scan_offload_blacklist { +	u8 ssid[ETH_ALEN]; +	u8 reported_rssi; +	u8 reserved; +} __packed; + +enum iwl_scan_offload_network_type { +	IWL_NETWORK_TYPE_BSS	= 1, +	IWL_NETWORK_TYPE_IBSS	= 2, +	IWL_NETWORK_TYPE_ANY	= 3, +}; + +enum iwl_scan_offload_band_selection { +	IWL_SCAN_OFFLOAD_SELECT_2_4	= 0x4, +	IWL_SCAN_OFFLOAD_SELECT_5_2	= 0x8, +	IWL_SCAN_OFFLOAD_SELECT_ANY	= 0xc, +}; + +/** + * iwl_scan_offload_profile - SCAN_OFFLOAD_PROFILE_S + * @ssid_index:		index to ssid list in fixed part + * @unicast_cipher:	encryption olgorithm to match - bitmap + * @aut_alg:		authentication olgorithm to match - bitmap + * @network_type:	enum iwl_scan_offload_network_type + * @band_selection:	enum iwl_scan_offload_band_selection + */ +struct iwl_scan_offload_profile { +	u8 ssid_index; +	u8 unicast_cipher; +	u8 auth_alg; +	u8 network_type; +	u8 band_selection; +	u8 reserved[3]; +} __packed; + +/** + * iwl_scan_offload_profile_cfg - SCAN_OFFLOAD_PROFILES_CFG_API_S_VER_1 + * @blaclist:		AP list to filter off from scan results + * @profiles:		profiles to search for match + * @blacklist_len:	length of blacklist + * @num_profiles:	num of profiles in the list + */ +struct iwl_scan_offload_profile_cfg { +	struct iwl_scan_offload_blacklist blacklist[IWL_SCAN_MAX_BLACKLIST_LEN]; +	struct iwl_scan_offload_profile profiles[IWL_SCAN_MAX_PROFILES]; +	u8 blacklist_len; +	u8 num_profiles; +	u8 reserved[2]; +} __packed; + +/** + * iwl_scan_offload_schedule - schedule of scan offload + * @delay:		delay between iterations, in seconds. + * @iterations:		num of scan iterations + * @full_scan_mul:	number of partial scans before each full scan + */ +struct iwl_scan_offload_schedule { +	u16 delay; +	u8 iterations; +	u8 full_scan_mul; +} __packed; + +/* + * iwl_scan_offload_flags + * + * IWL_SCAN_OFFLOAD_FLAG_FILTER_SSID: filter mode - upload every beacon or match + *	ssid list. + * IWL_SCAN_OFFLOAD_FLAG_CACHED_CHANNEL: add cached channels to partial scan. + * IWL_SCAN_OFFLOAD_FLAG_ENERGY_SCAN: use energy based scan before partial scan + *	on A band. + */ +enum iwl_scan_offload_flags { +	IWL_SCAN_OFFLOAD_FLAG_FILTER_SSID	= BIT(0), +	IWL_SCAN_OFFLOAD_FLAG_CACHED_CHANNEL	= BIT(2), +	IWL_SCAN_OFFLOAD_FLAG_ENERGY_SCAN	= BIT(3), +}; + +/** + * iwl_scan_offload_req - scan offload request command + * @flags:		bitmap - enum iwl_scan_offload_flags. + * @watchdog:		maximum scan duration in TU. + * @delay:		delay in seconds before first iteration. + * @schedule_line:	scan offload schedule, for fast and regular scan. + */ +struct iwl_scan_offload_req { +	__le16 flags; +	__le16 watchdog; +	__le16 delay; +	__le16 reserved; +	struct iwl_scan_offload_schedule schedule_line[2]; +} __packed; + +enum iwl_scan_offload_compleate_status { +	IWL_SCAN_OFFLOAD_COMPLETED	= 1, +	IWL_SCAN_OFFLOAD_ABORTED	= 2, +}; + +/** + * iwl_scan_offload_complete - SCAN_OFFLOAD_COMPLETE_NTF_API_S_VER_1 + * @last_schedule_line:		last schedule line executed (fast or regular) + * @last_schedule_iteration:	last scan iteration executed before scan abort + * @status:			enum iwl_scan_offload_compleate_status + */ +struct iwl_scan_offload_complete { +	u8 last_schedule_line; +	u8 last_schedule_iteration; +	u8 status; +	u8 reserved; +} __packed; + +#endif diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h new file mode 100644 index 00000000000..0acb53dda22 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-sta.h @@ -0,0 +1,380 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_sta_h__ +#define __fw_api_sta_h__ + +/** + * enum iwl_sta_flags - flags for the ADD_STA host command + * @STA_FLG_REDUCED_TX_PWR_CTRL: + * @STA_FLG_REDUCED_TX_PWR_DATA: + * @STA_FLG_FLG_ANT_MSK: Antenna selection + * @STA_FLG_PS: set if STA is in Power Save + * @STA_FLG_INVALID: set if STA is invalid + * @STA_FLG_DLP_EN: Direct Link Protocol is enabled + * @STA_FLG_SET_ALL_KEYS: the current key applies to all key IDs + * @STA_FLG_DRAIN_FLOW: drain flow + * @STA_FLG_PAN: STA is for PAN interface + * @STA_FLG_CLASS_AUTH: + * @STA_FLG_CLASS_ASSOC: + * @STA_FLG_CLASS_MIMO_PROT: + * @STA_FLG_MAX_AGG_SIZE_MSK: maximal size for A-MPDU + * @STA_FLG_AGG_MPDU_DENS_MSK: maximal MPDU density for Tx aggregation + * @STA_FLG_FAT_EN_MSK: support for channel width (for Tx). This flag is + *	initialised by driver and can be updated by fw upon reception of + *	action frames that can change the channel width. When cleared the fw + *	will send all the frames in 20MHz even when FAT channel is requested. + * @STA_FLG_MIMO_EN_MSK: support for MIMO. This flag is initialised by the + *	driver and can be updated by fw upon reception of action frames. + * @STA_FLG_MFP_EN: Management Frame Protection + */ +enum iwl_sta_flags { +	STA_FLG_REDUCED_TX_PWR_CTRL	= BIT(3), +	STA_FLG_REDUCED_TX_PWR_DATA	= BIT(6), + +	STA_FLG_FLG_ANT_A		= (1 << 4), +	STA_FLG_FLG_ANT_B		= (2 << 4), +	STA_FLG_FLG_ANT_MSK		= (STA_FLG_FLG_ANT_A | +					   STA_FLG_FLG_ANT_B), + +	STA_FLG_PS			= BIT(8), +	STA_FLG_INVALID			= BIT(9), +	STA_FLG_DLP_EN			= BIT(10), +	STA_FLG_SET_ALL_KEYS		= BIT(11), +	STA_FLG_DRAIN_FLOW		= BIT(12), +	STA_FLG_PAN			= BIT(13), +	STA_FLG_CLASS_AUTH		= BIT(14), +	STA_FLG_CLASS_ASSOC		= BIT(15), +	STA_FLG_RTS_MIMO_PROT		= BIT(17), + +	STA_FLG_MAX_AGG_SIZE_SHIFT	= 19, +	STA_FLG_MAX_AGG_SIZE_8K		= (0 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_16K	= (1 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_32K	= (2 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_64K	= (3 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_128K	= (4 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_256K	= (5 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_512K	= (6 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_1024K	= (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), +	STA_FLG_MAX_AGG_SIZE_MSK	= (7 << STA_FLG_MAX_AGG_SIZE_SHIFT), + +	STA_FLG_AGG_MPDU_DENS_SHIFT	= 23, +	STA_FLG_AGG_MPDU_DENS_2US	= (4 << STA_FLG_AGG_MPDU_DENS_SHIFT), +	STA_FLG_AGG_MPDU_DENS_4US	= (5 << STA_FLG_AGG_MPDU_DENS_SHIFT), +	STA_FLG_AGG_MPDU_DENS_8US	= (6 << STA_FLG_AGG_MPDU_DENS_SHIFT), +	STA_FLG_AGG_MPDU_DENS_16US	= (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), +	STA_FLG_AGG_MPDU_DENS_MSK	= (7 << STA_FLG_AGG_MPDU_DENS_SHIFT), + +	STA_FLG_FAT_EN_20MHZ		= (0 << 26), +	STA_FLG_FAT_EN_40MHZ		= (1 << 26), +	STA_FLG_FAT_EN_80MHZ		= (2 << 26), +	STA_FLG_FAT_EN_160MHZ		= (3 << 26), +	STA_FLG_FAT_EN_MSK		= (3 << 26), + +	STA_FLG_MIMO_EN_SISO		= (0 << 28), +	STA_FLG_MIMO_EN_MIMO2		= (1 << 28), +	STA_FLG_MIMO_EN_MIMO3		= (2 << 28), +	STA_FLG_MIMO_EN_MSK		= (3 << 28), +}; + +/** + * enum iwl_sta_key_flag - key flags for the ADD_STA host command + * @STA_KEY_FLG_EN_MSK: mask for encryption algorithm + * @STA_KEY_FLG_WEP_KEY_MAP: wep is either a group key (0 - legacy WEP) or from + *	station info array (1 - n 1X mode) + * @STA_KEY_FLG_KEYID_MSK: the index of the key + * @STA_KEY_NOT_VALID: key is invalid + * @STA_KEY_FLG_WEP_13BYTES: set for 13 bytes WEP key + * @STA_KEY_MULTICAST: set for multical key + * @STA_KEY_MFP: key is used for Management Frame Protection + */ +enum iwl_sta_key_flag { +	STA_KEY_FLG_NO_ENC		= (0 << 0), +	STA_KEY_FLG_WEP			= (1 << 0), +	STA_KEY_FLG_CCM			= (2 << 0), +	STA_KEY_FLG_TKIP		= (3 << 0), +	STA_KEY_FLG_CMAC		= (6 << 0), +	STA_KEY_FLG_ENC_UNKNOWN		= (7 << 0), +	STA_KEY_FLG_EN_MSK		= (7 << 0), + +	STA_KEY_FLG_WEP_KEY_MAP		= BIT(3), +	STA_KEY_FLG_KEYID_POS		 = 8, +	STA_KEY_FLG_KEYID_MSK		= (3 << STA_KEY_FLG_KEYID_POS), +	STA_KEY_NOT_VALID		= BIT(11), +	STA_KEY_FLG_WEP_13BYTES		= BIT(12), +	STA_KEY_MULTICAST		= BIT(14), +	STA_KEY_MFP			= BIT(15), +}; + +/** + * enum iwl_sta_modify_flag - indicate to the fw what flag are being changed + * @STA_MODIFY_KEY: this command modifies %key + * @STA_MODIFY_TID_DISABLE_TX: this command modifies %tid_disable_tx + * @STA_MODIFY_TX_RATE: unused + * @STA_MODIFY_ADD_BA_TID: this command modifies %add_immediate_ba_tid + * @STA_MODIFY_REMOVE_BA_TID: this command modifies %remove_immediate_ba_tid + * @STA_MODIFY_SLEEPING_STA_TX_COUNT: this command modifies %sleep_tx_count + * @STA_MODIFY_PROT_TH: + * @STA_MODIFY_QUEUES: modify the queues used by this station + */ +enum iwl_sta_modify_flag { +	STA_MODIFY_KEY				= BIT(0), +	STA_MODIFY_TID_DISABLE_TX		= BIT(1), +	STA_MODIFY_TX_RATE			= BIT(2), +	STA_MODIFY_ADD_BA_TID			= BIT(3), +	STA_MODIFY_REMOVE_BA_TID		= BIT(4), +	STA_MODIFY_SLEEPING_STA_TX_COUNT	= BIT(5), +	STA_MODIFY_PROT_TH			= BIT(6), +	STA_MODIFY_QUEUES			= BIT(7), +}; + +#define STA_MODE_MODIFY	1 + +/** + * enum iwl_sta_sleep_flag - type of sleep of the station + * @STA_SLEEP_STATE_AWAKE: + * @STA_SLEEP_STATE_PS_POLL: + * @STA_SLEEP_STATE_UAPSD: + */ +enum iwl_sta_sleep_flag { +	STA_SLEEP_STATE_AWAKE	= 0, +	STA_SLEEP_STATE_PS_POLL	= BIT(0), +	STA_SLEEP_STATE_UAPSD	= BIT(1), +}; + +/* STA ID and color bits definitions */ +#define STA_ID_SEED		(0x0f) +#define STA_ID_POS		(0) +#define STA_ID_MSK		(STA_ID_SEED << STA_ID_POS) + +#define STA_COLOR_SEED		(0x7) +#define STA_COLOR_POS		(4) +#define STA_COLOR_MSK		(STA_COLOR_SEED << STA_COLOR_POS) + +#define STA_ID_N_COLOR_GET_COLOR(id_n_color) \ +	(((id_n_color) & STA_COLOR_MSK) >> STA_COLOR_POS) +#define STA_ID_N_COLOR_GET_ID(id_n_color)    \ +	(((id_n_color) & STA_ID_MSK) >> STA_ID_POS) + +#define STA_KEY_MAX_NUM (16) +#define STA_KEY_IDX_INVALID (0xff) +#define STA_KEY_MAX_DATA_KEY_NUM (4) +#define IWL_MAX_GLOBAL_KEYS (4) +#define STA_KEY_LEN_WEP40 (5) +#define STA_KEY_LEN_WEP104 (13) + +/** + * struct iwl_mvm_keyinfo - key information + * @key_flags: type %iwl_sta_key_flag + * @tkip_rx_tsc_byte2: TSC[2] for key mix ph1 detection + * @tkip_rx_ttak: 10-byte unicast TKIP TTAK for Rx + * @key_offset: key offset in the fw's key table + * @key: 16-byte unicast decryption key + * @tx_secur_seq_cnt: initial RSC / PN needed for replay check + * @hw_tkip_mic_rx_key: byte: MIC Rx Key - used for TKIP only + * @hw_tkip_mic_tx_key: byte: MIC Tx Key - used for TKIP only + */ +struct iwl_mvm_keyinfo { +	__le16 key_flags; +	u8 tkip_rx_tsc_byte2; +	u8 reserved1; +	__le16 tkip_rx_ttak[5]; +	u8 key_offset; +	u8 reserved2; +	u8 key[16]; +	__le64 tx_secur_seq_cnt; +	__le64 hw_tkip_mic_rx_key; +	__le64 hw_tkip_mic_tx_key; +} __packed; + +/** + * struct iwl_mvm_add_sta_cmd - Add / modify a station in the fw's station table + * ( REPLY_ADD_STA = 0x18 ) + * @add_modify: 1: modify existing, 0: add new station + * @unicast_tx_key_id: unicast tx key id. Relevant only when unicast key sent + * @multicast_tx_key_id: multicast tx key id. Relevant only when multicast key + *	sent + * @mac_id_n_color: the Mac context this station belongs to + * @addr[ETH_ALEN]: station's MAC address + * @sta_id: index of station in uCode's station table + * @modify_mask: STA_MODIFY_*, selects which parameters to modify vs. leave + *	alone. 1 - modify, 0 - don't change. + * @key: look at %iwl_mvm_keyinfo + * @station_flags: look at %iwl_sta_flags + * @station_flags_msk: what of %station_flags have changed + * @tid_disable_tx: is tid BIT(tid) enabled for Tx. Clear BIT(x) to enable + *	AMPDU for tid x. Set %STA_MODIFY_TID_DISABLE_TX to change this field. + * @add_immediate_ba_tid: tid for which to add block-ack support (Rx) + *	Set %STA_MODIFY_ADD_BA_TID to use this field, and also set + *	add_immediate_ba_ssn. + * @remove_immediate_ba_tid: tid for which to remove block-ack support (Rx) + *	Set %STA_MODIFY_REMOVE_BA_TID to use this field + * @add_immediate_ba_ssn: ssn for the Rx block-ack session. Used together with + *	add_immediate_ba_tid. + * @sleep_tx_count: number of packets to transmit to station even though it is + *	asleep. Used to synchronise PS-poll and u-APSD responses while ucode + *	keeps track of STA sleep state. + * @sleep_state_flags: Look at %iwl_sta_sleep_flag. + * @assoc_id: assoc_id to be sent in VHT PLCP (9-bit), for grp use 0, for AP + *	mac-addr. + * @beamform_flags: beam forming controls + * @tfd_queue_msk: tfd queues used by this station + * + * The device contains an internal table of per-station information, with info + * on security keys, aggregation parameters, and Tx rates for initial Tx + * attempt and any retries (set by REPLY_TX_LINK_QUALITY_CMD). + * + * ADD_STA sets up the table entry for one station, either creating a new + * entry, or modifying a pre-existing one. + */ +struct iwl_mvm_add_sta_cmd { +	u8 add_modify; +	u8 unicast_tx_key_id; +	u8 multicast_tx_key_id; +	u8 reserved1; +	__le32 mac_id_n_color; +	u8 addr[ETH_ALEN]; +	__le16 reserved2; +	u8 sta_id; +	u8 modify_mask; +	__le16 reserved3; +	struct iwl_mvm_keyinfo key; +	__le32 station_flags; +	__le32 station_flags_msk; +	__le16 tid_disable_tx; +	__le16 reserved4; +	u8 add_immediate_ba_tid; +	u8 remove_immediate_ba_tid; +	__le16 add_immediate_ba_ssn; +	__le16 sleep_tx_count; +	__le16 sleep_state_flags; +	__le16 assoc_id; +	__le16 beamform_flags; +	__le32 tfd_queue_msk; +} __packed; /* ADD_STA_CMD_API_S_VER_5 */ + +/** + * enum iwl_mvm_add_sta_rsp_status - status in the response to ADD_STA command + * @ADD_STA_SUCCESS: operation was executed successfully + * @ADD_STA_STATIONS_OVERLOAD: no room left in the fw's station table + * @ADD_STA_IMMEDIATE_BA_FAILURE: can't add Rx block ack session + * @ADD_STA_MODIFY_NON_EXISTING_STA: driver requested to modify a station that + *	doesn't exist. + */ +enum iwl_mvm_add_sta_rsp_status { +	ADD_STA_SUCCESS			= 0x1, +	ADD_STA_STATIONS_OVERLOAD	= 0x2, +	ADD_STA_IMMEDIATE_BA_FAILURE	= 0x4, +	ADD_STA_MODIFY_NON_EXISTING_STA	= 0x8, +}; + +/** + * struct iwl_mvm_rm_sta_cmd - Add / modify a station in the fw's station table + * ( REMOVE_STA = 0x19 ) + * @sta_id: the station id of the station to be removed + */ +struct iwl_mvm_rm_sta_cmd { +	u8 sta_id; +	u8 reserved[3]; +} __packed; /* REMOVE_STA_CMD_API_S_VER_2 */ + +/** + * struct iwl_mvm_mgmt_mcast_key_cmd + * ( MGMT_MCAST_KEY = 0x1f ) + * @ctrl_flags: %iwl_sta_key_flag + * @IGTK: + * @K1: IGTK master key + * @K2: IGTK sub key + * @sta_id: station ID that support IGTK + * @key_id: + * @receive_seq_cnt: initial RSC/PN needed for replay check + */ +struct iwl_mvm_mgmt_mcast_key_cmd { +	__le32 ctrl_flags; +	u8 IGTK[16]; +	u8 K1[16]; +	u8 K2[16]; +	__le32 key_id; +	__le32 sta_id; +	__le64 receive_seq_cnt; +} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_1 */ + +struct iwl_mvm_wep_key { +	u8 key_index; +	u8 key_offset; +	__le16 reserved1; +	u8 key_size; +	u8 reserved2[3]; +	u8 key[16]; +} __packed; + +struct iwl_mvm_wep_key_cmd { +	__le32 mac_id_n_color; +	u8 num_keys; +	u8 decryption_type; +	u8 flags; +	u8 reserved; +	struct iwl_mvm_wep_key wep_key[0]; +} __packed; /* SEC_CURR_WEP_KEY_CMD_API_S_VER_2 */ + + +#endif /* __fw_api_sta_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h b/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h new file mode 100644 index 00000000000..2677914bf0a --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api-tx.h @@ -0,0 +1,580 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_tx_h__ +#define __fw_api_tx_h__ + +/** + * enum iwl_tx_flags - bitmasks for tx_flags in TX command + * @TX_CMD_FLG_PROT_REQUIRE: use RTS or CTS-to-self to protect the frame + * @TX_CMD_FLG_ACK: expect ACK from receiving station + * @TX_CMD_FLG_STA_RATE: use RS table with initial index from the TX command. + *	Otherwise, use rate_n_flags from the TX command + * @TX_CMD_FLG_BA: this frame is a block ack + * @TX_CMD_FLG_BAR: this frame is a BA request, immediate BAR is expected + *	Must set TX_CMD_FLG_ACK with this flag. + * @TX_CMD_FLG_TXOP_PROT: protect frame with full TXOP protection + * @TX_CMD_FLG_VHT_NDPA: mark frame is NDPA for VHT beamformer sequence + * @TX_CMD_FLG_HT_NDPA: mark frame is NDPA for HT beamformer sequence + * @TX_CMD_FLG_CSI_FDBK2HOST: mark to send feedback to host (only if good CRC) + * @TX_CMD_FLG_BT_DIS: disable BT priority for this frame + * @TX_CMD_FLG_SEQ_CTL: set if FW should override the sequence control. + *	Should be set for mgmt, non-QOS data, mcast, bcast and in scan command + * @TX_CMD_FLG_MORE_FRAG: this frame is non-last MPDU + * @TX_CMD_FLG_NEXT_FRAME: this frame includes information of the next frame + * @TX_CMD_FLG_TSF: FW should calculate and insert TSF in the frame + *	Should be set for beacons and probe responses + * @TX_CMD_FLG_CALIB: activate PA TX power calibrations + * @TX_CMD_FLG_KEEP_SEQ_CTL: if seq_ctl is set, don't increase inner seq count + * @TX_CMD_FLG_AGG_START: allow this frame to start aggregation + * @TX_CMD_FLG_MH_PAD: driver inserted 2 byte padding after MAC header. + *	Should be set for 26/30 length MAC headers + * @TX_CMD_FLG_RESP_TO_DRV: zero this if the response should go only to FW + * @TX_CMD_FLG_CCMP_AGG: this frame uses CCMP for aggregation acceleration + * @TX_CMD_FLG_TKIP_MIC_DONE: FW already performed TKIP MIC calculation + * @TX_CMD_FLG_CTS_ONLY: send CTS only, no data after that + * @TX_CMD_FLG_DUR: disable duration overwriting used in PS-Poll Assoc-id + * @TX_CMD_FLG_FW_DROP: FW should mark frame to be dropped + * @TX_CMD_FLG_EXEC_PAPD: execute PAPD + * @TX_CMD_FLG_PAPD_TYPE: 0 for reference power, 1 for nominal power + * @TX_CMD_FLG_HCCA_CHUNK: mark start of TSPEC chunk + */ +enum iwl_tx_flags { +	TX_CMD_FLG_PROT_REQUIRE		= BIT(0), +	TX_CMD_FLG_ACK			= BIT(3), +	TX_CMD_FLG_STA_RATE		= BIT(4), +	TX_CMD_FLG_BA			= BIT(5), +	TX_CMD_FLG_BAR			= BIT(6), +	TX_CMD_FLG_TXOP_PROT		= BIT(7), +	TX_CMD_FLG_VHT_NDPA		= BIT(8), +	TX_CMD_FLG_HT_NDPA		= BIT(9), +	TX_CMD_FLG_CSI_FDBK2HOST	= BIT(10), +	TX_CMD_FLG_BT_DIS		= BIT(12), +	TX_CMD_FLG_SEQ_CTL		= BIT(13), +	TX_CMD_FLG_MORE_FRAG		= BIT(14), +	TX_CMD_FLG_NEXT_FRAME		= BIT(15), +	TX_CMD_FLG_TSF			= BIT(16), +	TX_CMD_FLG_CALIB		= BIT(17), +	TX_CMD_FLG_KEEP_SEQ_CTL		= BIT(18), +	TX_CMD_FLG_AGG_START		= BIT(19), +	TX_CMD_FLG_MH_PAD		= BIT(20), +	TX_CMD_FLG_RESP_TO_DRV		= BIT(21), +	TX_CMD_FLG_CCMP_AGG		= BIT(22), +	TX_CMD_FLG_TKIP_MIC_DONE	= BIT(23), +	TX_CMD_FLG_CTS_ONLY		= BIT(24), +	TX_CMD_FLG_DUR			= BIT(25), +	TX_CMD_FLG_FW_DROP		= BIT(26), +	TX_CMD_FLG_EXEC_PAPD		= BIT(27), +	TX_CMD_FLG_PAPD_TYPE		= BIT(28), +	TX_CMD_FLG_HCCA_CHUNK		= BIT(31) +}; /* TX_FLAGS_BITS_API_S_VER_1 */ + +/* + * TX command security control + */ +#define TX_CMD_SEC_WEP			0x01 +#define TX_CMD_SEC_CCM			0x02 +#define TX_CMD_SEC_TKIP			0x03 +#define TX_CMD_SEC_WEP_KEY_IDX_POS	6 +#define TX_CMD_SEC_WEP_KEY_IDX_MSK	0xc0 +#define TX_CMD_SEC_KEY128		0x08 + +/* TODO: how does these values are OK with only 16 bit variable??? */ +/* + * TX command next frame info + * + * bits 0:2 - security control (TX_CMD_SEC_*) + * bit 3 - immediate ACK required + * bit 4 - rate is taken from STA table + * bit 5 - frame belongs to BA stream + * bit 6 - immediate BA response expected + * bit 7 - unused + * bits 8:15 - Station ID + * bits 16:31 - rate + */ +#define TX_CMD_NEXT_FRAME_ACK_MSK		(0x8) +#define TX_CMD_NEXT_FRAME_STA_RATE_MSK		(0x10) +#define TX_CMD_NEXT_FRAME_BA_MSK		(0x20) +#define TX_CMD_NEXT_FRAME_IMM_BA_RSP_MSK	(0x40) +#define TX_CMD_NEXT_FRAME_FLAGS_MSK		(0xf8) +#define TX_CMD_NEXT_FRAME_STA_ID_MSK		(0xff00) +#define TX_CMD_NEXT_FRAME_STA_ID_POS		(8) +#define TX_CMD_NEXT_FRAME_RATE_MSK		(0xffff0000) +#define TX_CMD_NEXT_FRAME_RATE_POS		(16) + +/* + * TX command Frame life time in us - to be written in pm_frame_timeout + */ +#define TX_CMD_LIFE_TIME_INFINITE	0xFFFFFFFF +#define TX_CMD_LIFE_TIME_DEFAULT	2000000 /* 2000 ms*/ +#define TX_CMD_LIFE_TIME_PROBE_RESP	40000 /* 40 ms */ +#define TX_CMD_LIFE_TIME_EXPIRED_FRAME	0 + +/* + * TID for non QoS frames - to be written in tid_tspec + */ +#define IWL_TID_NON_QOS	IWL_MAX_TID_COUNT + +/* + * Limits on the retransmissions - to be written in {data,rts}_retry_limit + */ +#define IWL_DEFAULT_TX_RETRY			15 +#define IWL_MGMT_DFAULT_RETRY_LIMIT		3 +#define IWL_RTS_DFAULT_RETRY_LIMIT		60 +#define IWL_BAR_DFAULT_RETRY_LIMIT		60 +#define IWL_LOW_RETRY_LIMIT			7 + +/* TODO: complete documentation for try_cnt and btkill_cnt */ +/** + * struct iwl_tx_cmd - TX command struct to FW + * ( TX_CMD = 0x1c ) + * @len: in bytes of the payload, see below for details + * @next_frame_len: same as len, but for next frame (0 if not applicable) + *	Used for fragmentation and bursting, but not in 11n aggregation. + * @tx_flags: combination of TX_CMD_FLG_* + * @rate_n_flags: rate for *all* Tx attempts, if TX_CMD_FLG_STA_RATE_MSK is + *	cleared. Combination of RATE_MCS_* + * @sta_id: index of destination station in FW station table + * @sec_ctl: security control, TX_CMD_SEC_* + * @initial_rate_index: index into the the rate table for initial TX attempt. + *	Applied if TX_CMD_FLG_STA_RATE_MSK is set, normally 0 for data frames. + * @key: security key + * @next_frame_flags: TX_CMD_SEC_* and TX_CMD_NEXT_FRAME_* + * @life_time: frame life time (usecs??) + * @dram_lsb_ptr: Physical address of scratch area in the command (try_cnt + + *	btkill_cnd + reserved), first 32 bits. "0" disables usage. + * @dram_msb_ptr: upper bits of the scratch physical address + * @rts_retry_limit: max attempts for RTS + * @data_retry_limit: max attempts to send the data packet + * @tid_spec: TID/tspec + * @pm_frame_timeout: PM TX frame timeout + * @driver_txop: duration od EDCA TXOP, in 32-usec units. Set this if not + *	specified by HCCA protocol + * + * The byte count (both len and next_frame_len) includes MAC header + * (24/26/30/32 bytes) + * + 2 bytes pad if 26/30 header size + * + 8 byte IV for CCM or TKIP (not used for WEP) + * + Data payload + * + 8-byte MIC (not used for CCM/WEP) + * It does not include post-MAC padding, i.e., + * MIC (CCM) 8 bytes, ICV (WEP/TKIP/CKIP) 4 bytes, CRC 4 bytes. + * Range of len: 14-2342 bytes. + * + * After the struct fields the MAC header is placed, plus any padding, + * and then the actial payload. + */ +struct iwl_tx_cmd { +	__le16 len; +	__le16 next_frame_len; +	__le32 tx_flags; +	/* DRAM_SCRATCH_API_U_VER_1 */ +	u8 try_cnt; +	u8 btkill_cnt; +	__le16 reserved; +	__le32 rate_n_flags; +	u8 sta_id; +	u8 sec_ctl; +	u8 initial_rate_index; +	u8 reserved2; +	u8 key[16]; +	__le16 next_frame_flags; +	__le16 reserved3; +	__le32 life_time; +	__le32 dram_lsb_ptr; +	u8 dram_msb_ptr; +	u8 rts_retry_limit; +	u8 data_retry_limit; +	u8 tid_tspec; +	__le16 pm_frame_timeout; +	__le16 driver_txop; +	u8 payload[0]; +	struct ieee80211_hdr hdr[0]; +} __packed; /* TX_CMD_API_S_VER_3 */ + +/* + * TX response related data + */ + +/* + * enum iwl_tx_status - status that is returned by the fw after attempts to Tx + * @TX_STATUS_SUCCESS: + * @TX_STATUS_DIRECT_DONE: + * @TX_STATUS_POSTPONE_DELAY: + * @TX_STATUS_POSTPONE_FEW_BYTES: + * @TX_STATUS_POSTPONE_BT_PRIO: + * @TX_STATUS_POSTPONE_QUIET_PERIOD: + * @TX_STATUS_POSTPONE_CALC_TTAK: + * @TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY: + * @TX_STATUS_FAIL_SHORT_LIMIT: + * @TX_STATUS_FAIL_LONG_LIMIT: + * @TX_STATUS_FAIL_UNDERRUN: + * @TX_STATUS_FAIL_DRAIN_FLOW: + * @TX_STATUS_FAIL_RFKILL_FLUSH: + * @TX_STATUS_FAIL_LIFE_EXPIRE: + * @TX_STATUS_FAIL_DEST_PS: + * @TX_STATUS_FAIL_HOST_ABORTED: + * @TX_STATUS_FAIL_BT_RETRY: + * @TX_STATUS_FAIL_STA_INVALID: + * @TX_TATUS_FAIL_FRAG_DROPPED: + * @TX_STATUS_FAIL_TID_DISABLE: + * @TX_STATUS_FAIL_FIFO_FLUSHED: + * @TX_STATUS_FAIL_SMALL_CF_POLL: + * @TX_STATUS_FAIL_FW_DROP: + * @TX_STATUS_FAIL_STA_COLOR_MISMATCH: mismatch between color of Tx cmd and + *	STA table + * @TX_FRAME_STATUS_INTERNAL_ABORT: + * @TX_MODE_MSK: + * @TX_MODE_NO_BURST: + * @TX_MODE_IN_BURST_SEQ: + * @TX_MODE_FIRST_IN_BURST: + * @TX_QUEUE_NUM_MSK: + * + * Valid only if frame_count =1 + * TODO: complete documentation + */ +enum iwl_tx_status { +	TX_STATUS_MSK = 0x000000ff, +	TX_STATUS_SUCCESS = 0x01, +	TX_STATUS_DIRECT_DONE = 0x02, +	/* postpone TX */ +	TX_STATUS_POSTPONE_DELAY = 0x40, +	TX_STATUS_POSTPONE_FEW_BYTES = 0x41, +	TX_STATUS_POSTPONE_BT_PRIO = 0x42, +	TX_STATUS_POSTPONE_QUIET_PERIOD = 0x43, +	TX_STATUS_POSTPONE_CALC_TTAK = 0x44, +	/* abort TX */ +	TX_STATUS_FAIL_INTERNAL_CROSSED_RETRY = 0x81, +	TX_STATUS_FAIL_SHORT_LIMIT = 0x82, +	TX_STATUS_FAIL_LONG_LIMIT = 0x83, +	TX_STATUS_FAIL_UNDERRUN = 0x84, +	TX_STATUS_FAIL_DRAIN_FLOW = 0x85, +	TX_STATUS_FAIL_RFKILL_FLUSH = 0x86, +	TX_STATUS_FAIL_LIFE_EXPIRE = 0x87, +	TX_STATUS_FAIL_DEST_PS = 0x88, +	TX_STATUS_FAIL_HOST_ABORTED = 0x89, +	TX_STATUS_FAIL_BT_RETRY = 0x8a, +	TX_STATUS_FAIL_STA_INVALID = 0x8b, +	TX_STATUS_FAIL_FRAG_DROPPED = 0x8c, +	TX_STATUS_FAIL_TID_DISABLE = 0x8d, +	TX_STATUS_FAIL_FIFO_FLUSHED = 0x8e, +	TX_STATUS_FAIL_SMALL_CF_POLL = 0x8f, +	TX_STATUS_FAIL_FW_DROP = 0x90, +	TX_STATUS_FAIL_STA_COLOR_MISMATCH = 0x91, +	TX_STATUS_INTERNAL_ABORT = 0x92, +	TX_MODE_MSK = 0x00000f00, +	TX_MODE_NO_BURST = 0x00000000, +	TX_MODE_IN_BURST_SEQ = 0x00000100, +	TX_MODE_FIRST_IN_BURST = 0x00000200, +	TX_QUEUE_NUM_MSK = 0x0001f000, +	TX_NARROW_BW_MSK = 0x00060000, +	TX_NARROW_BW_1DIV2 = 0x00020000, +	TX_NARROW_BW_1DIV4 = 0x00040000, +	TX_NARROW_BW_1DIV8 = 0x00060000, +}; + +/* + * enum iwl_tx_agg_status - TX aggregation status + * @AGG_TX_STATE_STATUS_MSK: + * @AGG_TX_STATE_TRANSMITTED: + * @AGG_TX_STATE_UNDERRUN: + * @AGG_TX_STATE_BT_PRIO: + * @AGG_TX_STATE_FEW_BYTES: + * @AGG_TX_STATE_ABORT: + * @AGG_TX_STATE_LAST_SENT_TTL: + * @AGG_TX_STATE_LAST_SENT_TRY_CNT: + * @AGG_TX_STATE_LAST_SENT_BT_KILL: + * @AGG_TX_STATE_SCD_QUERY: + * @AGG_TX_STATE_TEST_BAD_CRC32: + * @AGG_TX_STATE_RESPONSE: + * @AGG_TX_STATE_DUMP_TX: + * @AGG_TX_STATE_DELAY_TX: + * @AGG_TX_STATE_TRY_CNT_MSK: Retry count for 1st frame in aggregation (retries + *	occur if tx failed for this frame when it was a member of a previous + *	aggregation block). If rate scaling is used, retry count indicates the + *	rate table entry used for all frames in the new agg. + *@ AGG_TX_STATE_SEQ_NUM_MSK: Command ID and sequence number of Tx command for + *	this frame + * + * TODO: complete documentation + */ +enum iwl_tx_agg_status { +	AGG_TX_STATE_STATUS_MSK = 0x00fff, +	AGG_TX_STATE_TRANSMITTED = 0x000, +	AGG_TX_STATE_UNDERRUN = 0x001, +	AGG_TX_STATE_BT_PRIO = 0x002, +	AGG_TX_STATE_FEW_BYTES = 0x004, +	AGG_TX_STATE_ABORT = 0x008, +	AGG_TX_STATE_LAST_SENT_TTL = 0x010, +	AGG_TX_STATE_LAST_SENT_TRY_CNT = 0x020, +	AGG_TX_STATE_LAST_SENT_BT_KILL = 0x040, +	AGG_TX_STATE_SCD_QUERY = 0x080, +	AGG_TX_STATE_TEST_BAD_CRC32 = 0x0100, +	AGG_TX_STATE_RESPONSE = 0x1ff, +	AGG_TX_STATE_DUMP_TX = 0x200, +	AGG_TX_STATE_DELAY_TX = 0x400, +	AGG_TX_STATE_TRY_CNT_POS = 12, +	AGG_TX_STATE_TRY_CNT_MSK = 0xf << AGG_TX_STATE_TRY_CNT_POS, +}; + +#define AGG_TX_STATE_LAST_SENT_MSK  (AGG_TX_STATE_LAST_SENT_TTL| \ +				     AGG_TX_STATE_LAST_SENT_TRY_CNT| \ +				     AGG_TX_STATE_LAST_SENT_BT_KILL) + +/* + * The mask below describes a status where we are absolutely sure that the MPDU + * wasn't sent. For BA/Underrun we cannot be that sure. All we know that we've + * written the bytes to the TXE, but we know nothing about what the DSP did. + */ +#define AGG_TX_STAT_FRAME_NOT_SENT (AGG_TX_STATE_FEW_BYTES | \ +				    AGG_TX_STATE_ABORT | \ +				    AGG_TX_STATE_SCD_QUERY) + +/* + * REPLY_TX = 0x1c (response) + * + * This response may be in one of two slightly different formats, indicated + * by the frame_count field: + * + * 1)	No aggregation (frame_count == 1).  This reports Tx results for a single + *	frame. Multiple attempts, at various bit rates, may have been made for + *	this frame. + * + * 2)	Aggregation (frame_count > 1).  This reports Tx results for two or more + *	frames that used block-acknowledge.  All frames were transmitted at + *	same rate. Rate scaling may have been used if first frame in this new + *	agg block failed in previous agg block(s). + * + *	Note that, for aggregation, ACK (block-ack) status is not delivered + *	here; block-ack has not been received by the time the device records + *	this status. + *	This status relates to reasons the tx might have been blocked or aborted + *	within the device, rather than whether it was received successfully by + *	the destination station. + */ + +/** + * struct agg_tx_status - per packet TX aggregation status + * @status: enum iwl_tx_agg_status + * @sequence: Sequence # for this frame's Tx cmd (not SSN!) + */ +struct agg_tx_status { +	__le16 status; +	__le16 sequence; +} __packed; + +/* + * definitions for initial rate index field + * bits [3:0] initial rate index + * bits [6:4] rate table color, used for the initial rate + * bit-7 invalid rate indication + */ +#define TX_RES_INIT_RATE_INDEX_MSK 0x0f +#define TX_RES_RATE_TABLE_COLOR_MSK 0x70 +#define TX_RES_INV_RATE_INDEX_MSK 0x80 + +#define IWL_MVM_TX_RES_GET_TID(_ra_tid) ((_ra_tid) & 0x0f) +#define IWL_MVM_TX_RES_GET_RA(_ra_tid) ((_ra_tid) >> 4) + +/** + * struct iwl_mvm_tx_resp - notifies that fw is TXing a packet + * ( REPLY_TX = 0x1c ) + * @frame_count: 1 no aggregation, >1 aggregation + * @bt_kill_count: num of times blocked by bluetooth (unused for agg) + * @failure_rts: num of failures due to unsuccessful RTS + * @failure_frame: num failures due to no ACK (unused for agg) + * @initial_rate: for non-agg: rate of the successful Tx. For agg: rate of the + *	Tx of all the batch. RATE_MCS_* + * @wireless_media_time: for non-agg: RTS + CTS + frame tx attempts time + ACK. + *	for agg: RTS + CTS + aggregation tx time + block-ack time. + *	in usec. + * @pa_status: tx power info + * @pa_integ_res_a: tx power info + * @pa_integ_res_b: tx power info + * @pa_integ_res_c: tx power info + * @measurement_req_id: tx power info + * @tfd_info: TFD information set by the FH + * @seq_ctl: sequence control from the Tx cmd + * @byte_cnt: byte count from the Tx cmd + * @tlc_info: TLC rate info + * @ra_tid: bits [3:0] = ra, bits [7:4] = tid + * @frame_ctrl: frame control + * @status: for non-agg:  frame status TX_STATUS_* + *	for agg: status of 1st frame, AGG_TX_STATE_*; other frame status fields + *	follow this one, up to frame_count. + * + * After the array of statuses comes the SSN of the SCD. Look at + * %iwl_mvm_get_scd_ssn for more details. + */ +struct iwl_mvm_tx_resp { +	u8 frame_count; +	u8 bt_kill_count; +	u8 failure_rts; +	u8 failure_frame; +	__le32 initial_rate; +	__le16 wireless_media_time; + +	u8 pa_status; +	u8 pa_integ_res_a[3]; +	u8 pa_integ_res_b[3]; +	u8 pa_integ_res_c[3]; +	__le16 measurement_req_id; +	__le16 reserved; + +	__le32 tfd_info; +	__le16 seq_ctl; +	__le16 byte_cnt; +	u8 tlc_info; +	u8 ra_tid; +	__le16 frame_ctrl; + +	struct agg_tx_status status; +} __packed; /* TX_RSP_API_S_VER_3 */ + +/** + * struct iwl_mvm_ba_notif - notifies about reception of BA + * ( BA_NOTIF = 0xc5 ) + * @sta_addr_lo32: lower 32 bits of the MAC address + * @sta_addr_hi16: upper 16 bits of the MAC address + * @sta_id: Index of recipient (BA-sending) station in fw's station table + * @tid: tid of the session + * @seq_ctl: + * @bitmap: the bitmap of the BA notification as seen in the air + * @scd_flow: the tx queue this BA relates to + * @scd_ssn: the index of the last contiguously sent packet + * @txed: number of Txed frames in this batch + * @txed_2_done: number of Acked frames in this batch + */ +struct iwl_mvm_ba_notif { +	__le32 sta_addr_lo32; +	__le16 sta_addr_hi16; +	__le16 reserved; + +	u8 sta_id; +	u8 tid; +	__le16 seq_ctl; +	__le64 bitmap; +	__le16 scd_flow; +	__le16 scd_ssn; +	u8 txed; +	u8 txed_2_done; +	__le16 reserved1; +} __packed; + +/* + * struct iwl_mac_beacon_cmd - beacon template command + * @tx: the tx commands associated with the beacon frame + * @template_id: currently equal to the mac context id of the coresponding + *  mac. + * @tim_idx: the offset of the tim IE in the beacon + * @tim_size: the length of the tim IE + * @frame: the template of the beacon frame + */ +struct iwl_mac_beacon_cmd { +	struct iwl_tx_cmd tx; +	__le32 template_id; +	__le32 tim_idx; +	__le32 tim_size; +	struct ieee80211_hdr frame[0]; +} __packed; + +/** + * enum iwl_dump_control - dump (flush) control flags + * @DUMP_TX_FIFO_FLUSH: Dump MSDUs until the the FIFO is empty + *	and the TFD queues are empty. + */ +enum iwl_dump_control { +	DUMP_TX_FIFO_FLUSH	= BIT(1), +}; + +/** + * struct iwl_tx_path_flush_cmd -- queue/FIFO flush command + * @queues_ctl: bitmap of queues to flush + * @flush_ctl: control flags + * @reserved: reserved + */ +struct iwl_tx_path_flush_cmd { +	__le32 queues_ctl; +	__le16 flush_ctl; +	__le16 reserved; +} __packed; /* TX_PATH_FLUSH_CMD_API_S_VER_1 */ + +/** + * iwl_mvm_get_scd_ssn - returns the SSN of the SCD + * @tx_resp: the Tx response from the fw (agg or non-agg) + * + * When the fw sends an AMPDU, it fetches the MPDUs one after the other. Since + * it can't know that everything will go well until the end of the AMPDU, it + * can't know in advance the number of MPDUs that will be sent in the current + * batch. This is why it writes the agg Tx response while it fetches the MPDUs. + * Hence, it can't know in advance what the SSN of the SCD will be at the end + * of the batch. This is why the SSN of the SCD is written at the end of the + * whole struct at a variable offset. This function knows how to cope with the + * variable offset and returns the SSN of the SCD. + */ +static inline u32 iwl_mvm_get_scd_ssn(struct iwl_mvm_tx_resp *tx_resp) +{ +	return le32_to_cpup((__le32 *)&tx_resp->status + +			    tx_resp->frame_count) & 0xfff; +} + +#endif /* __fw_api_tx_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw-api.h b/drivers/net/wireless/iwlwifi/mvm/fw-api.h new file mode 100644 index 00000000000..9fd49db32a3 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw-api.h @@ -0,0 +1,949 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __fw_api_h__ +#define __fw_api_h__ + +#include "fw-api-rs.h" +#include "fw-api-tx.h" +#include "fw-api-sta.h" +#include "fw-api-mac.h" +#include "fw-api-power.h" +#include "fw-api-d3.h" + +/* queue and FIFO numbers by usage */ +enum { +	IWL_MVM_OFFCHANNEL_QUEUE = 8, +	IWL_MVM_CMD_QUEUE = 9, +	IWL_MVM_AUX_QUEUE = 15, +	IWL_MVM_FIRST_AGG_QUEUE = 16, +	IWL_MVM_NUM_QUEUES = 20, +	IWL_MVM_LAST_AGG_QUEUE = IWL_MVM_NUM_QUEUES - 1, +	IWL_MVM_CMD_FIFO = 7 +}; + +#define IWL_MVM_STATION_COUNT	16 + +/* commands */ +enum { +	MVM_ALIVE = 0x1, +	REPLY_ERROR = 0x2, + +	INIT_COMPLETE_NOTIF = 0x4, + +	/* PHY context commands */ +	PHY_CONTEXT_CMD = 0x8, +	DBG_CFG = 0x9, + +	/* station table */ +	ADD_STA = 0x18, +	REMOVE_STA = 0x19, + +	/* TX */ +	TX_CMD = 0x1c, +	TXPATH_FLUSH = 0x1e, +	MGMT_MCAST_KEY = 0x1f, + +	/* global key */ +	WEP_KEY = 0x20, + +	/* MAC and Binding commands */ +	MAC_CONTEXT_CMD = 0x28, +	TIME_EVENT_CMD = 0x29, /* both CMD and response */ +	TIME_EVENT_NOTIFICATION = 0x2a, +	BINDING_CONTEXT_CMD = 0x2b, +	TIME_QUOTA_CMD = 0x2c, + +	LQ_CMD = 0x4e, + +	/* Calibration */ +	TEMPERATURE_NOTIFICATION = 0x62, +	CALIBRATION_CFG_CMD = 0x65, +	CALIBRATION_RES_NOTIFICATION = 0x66, +	CALIBRATION_COMPLETE_NOTIFICATION = 0x67, +	RADIO_VERSION_NOTIFICATION = 0x68, + +	/* Scan offload */ +	SCAN_OFFLOAD_REQUEST_CMD = 0x51, +	SCAN_OFFLOAD_ABORT_CMD = 0x52, +	SCAN_OFFLOAD_COMPLETE = 0x6D, +	SCAN_OFFLOAD_UPDATE_PROFILES_CMD = 0x6E, +	SCAN_OFFLOAD_CONFIG_CMD = 0x6f, + +	/* Phy */ +	PHY_CONFIGURATION_CMD = 0x6a, +	CALIB_RES_NOTIF_PHY_DB = 0x6b, +	/* PHY_DB_CMD = 0x6c, */ + +	/* Power */ +	POWER_TABLE_CMD = 0x77, + +	/* Scanning */ +	SCAN_REQUEST_CMD = 0x80, +	SCAN_ABORT_CMD = 0x81, +	SCAN_START_NOTIFICATION = 0x82, +	SCAN_RESULTS_NOTIFICATION = 0x83, +	SCAN_COMPLETE_NOTIFICATION = 0x84, + +	/* NVM */ +	NVM_ACCESS_CMD = 0x88, + +	SET_CALIB_DEFAULT_CMD = 0x8e, + +	BEACON_TEMPLATE_CMD = 0x91, +	TX_ANT_CONFIGURATION_CMD = 0x98, +	STATISTICS_NOTIFICATION = 0x9d, + +	/* RF-KILL commands and notifications */ +	CARD_STATE_CMD = 0xa0, +	CARD_STATE_NOTIFICATION = 0xa1, + +	REPLY_RX_PHY_CMD = 0xc0, +	REPLY_RX_MPDU_CMD = 0xc1, +	BA_NOTIF = 0xc5, + +	REPLY_DEBUG_CMD = 0xf0, +	DEBUG_LOG_MSG = 0xf7, + +	/* D3 commands/notifications */ +	D3_CONFIG_CMD = 0xd3, +	PROT_OFFLOAD_CONFIG_CMD = 0xd4, +	OFFLOADS_QUERY_CMD = 0xd5, +	REMOTE_WAKE_CONFIG_CMD = 0xd6, + +	/* for WoWLAN in particular */ +	WOWLAN_PATTERNS = 0xe0, +	WOWLAN_CONFIGURATION = 0xe1, +	WOWLAN_TSC_RSC_PARAM = 0xe2, +	WOWLAN_TKIP_PARAM = 0xe3, +	WOWLAN_KEK_KCK_MATERIAL = 0xe4, +	WOWLAN_GET_STATUSES = 0xe5, +	WOWLAN_TX_POWER_PER_DB = 0xe6, + +	/* and for NetDetect */ +	NET_DETECT_CONFIG_CMD = 0x54, +	NET_DETECT_PROFILES_QUERY_CMD = 0x56, +	NET_DETECT_PROFILES_CMD = 0x57, +	NET_DETECT_HOTSPOTS_CMD = 0x58, +	NET_DETECT_HOTSPOTS_QUERY_CMD = 0x59, + +	REPLY_MAX = 0xff, +}; + +/** + * struct iwl_cmd_response - generic response struct for most commands + * @status: status of the command asked, changes for each one + */ +struct iwl_cmd_response { +	__le32 status; +}; + +/* + * struct iwl_tx_ant_cfg_cmd + * @valid: valid antenna configuration + */ +struct iwl_tx_ant_cfg_cmd { +	__le32 valid; +} __packed; + +/* + * Calibration control struct. + * Sent as part of the phy configuration command. + * @flow_trigger: bitmap for which calibrations to perform according to + *		flow triggers. + * @event_trigger: bitmap for which calibrations to perform according to + *		event triggers. + */ +struct iwl_calib_ctrl { +	__le32 flow_trigger; +	__le32 event_trigger; +} __packed; + +/* This enum defines the bitmap of various calibrations to enable in both + * init ucode and runtime ucode through CALIBRATION_CFG_CMD. + */ +enum iwl_calib_cfg { +	IWL_CALIB_CFG_XTAL_IDX			= BIT(0), +	IWL_CALIB_CFG_TEMPERATURE_IDX		= BIT(1), +	IWL_CALIB_CFG_VOLTAGE_READ_IDX		= BIT(2), +	IWL_CALIB_CFG_PAPD_IDX			= BIT(3), +	IWL_CALIB_CFG_TX_PWR_IDX		= BIT(4), +	IWL_CALIB_CFG_DC_IDX			= BIT(5), +	IWL_CALIB_CFG_BB_FILTER_IDX		= BIT(6), +	IWL_CALIB_CFG_LO_LEAKAGE_IDX		= BIT(7), +	IWL_CALIB_CFG_TX_IQ_IDX			= BIT(8), +	IWL_CALIB_CFG_TX_IQ_SKEW_IDX		= BIT(9), +	IWL_CALIB_CFG_RX_IQ_IDX			= BIT(10), +	IWL_CALIB_CFG_RX_IQ_SKEW_IDX		= BIT(11), +	IWL_CALIB_CFG_SENSITIVITY_IDX		= BIT(12), +	IWL_CALIB_CFG_CHAIN_NOISE_IDX		= BIT(13), +	IWL_CALIB_CFG_DISCONNECTED_ANT_IDX	= BIT(14), +	IWL_CALIB_CFG_ANT_COUPLING_IDX		= BIT(15), +	IWL_CALIB_CFG_DAC_IDX			= BIT(16), +	IWL_CALIB_CFG_ABS_IDX			= BIT(17), +	IWL_CALIB_CFG_AGC_IDX			= BIT(18), +}; + +/* + * Phy configuration command. + */ +struct iwl_phy_cfg_cmd { +	__le32	phy_cfg; +	struct iwl_calib_ctrl calib_control; +} __packed; + +#define PHY_CFG_RADIO_TYPE	(BIT(0) | BIT(1)) +#define PHY_CFG_RADIO_STEP	(BIT(2) | BIT(3)) +#define PHY_CFG_RADIO_DASH	(BIT(4) | BIT(5)) +#define PHY_CFG_PRODUCT_NUMBER	(BIT(6) | BIT(7)) +#define PHY_CFG_TX_CHAIN_A	BIT(8) +#define PHY_CFG_TX_CHAIN_B	BIT(9) +#define PHY_CFG_TX_CHAIN_C	BIT(10) +#define PHY_CFG_RX_CHAIN_A	BIT(12) +#define PHY_CFG_RX_CHAIN_B	BIT(13) +#define PHY_CFG_RX_CHAIN_C	BIT(14) + + +/* Target of the NVM_ACCESS_CMD */ +enum { +	NVM_ACCESS_TARGET_CACHE = 0, +	NVM_ACCESS_TARGET_OTP = 1, +	NVM_ACCESS_TARGET_EEPROM = 2, +}; + +/** + * struct iwl_nvm_access_cmd_ver1 - Request the device to send the NVM. + * @op_code: 0 - read, 1 - write. + * @target: NVM_ACCESS_TARGET_*. should be 0 for read. + * @cache_refresh: 0 - None, 1- NVM. + * @offset: offset in the nvm data. + * @length: of the chunk. + * @data: empty on read, the NVM chunk on write + */ +struct iwl_nvm_access_cmd_ver1 { +	u8 op_code; +	u8 target; +	u8 cache_refresh; +	u8 reserved; +	__le16 offset; +	__le16 length; +	u8 data[]; +} __packed; /* NVM_ACCESS_CMD_API_S_VER_1 */ + +/** + * struct iwl_nvm_access_resp_ver1 - response to NVM_ACCESS_CMD + * @offset: the offset in the nvm data + * @length: of the chunk + * @data: the nvm chunk on when NVM_ACCESS_CMD was read, nothing on write + */ +struct iwl_nvm_access_resp_ver1 { +	__le16 offset; +	__le16 length; +	u8 data[]; +} __packed; /* NVM_ACCESS_CMD_RESP_API_S_VER_1 */ + +/* Section types for NVM_ACCESS_CMD version 2 */ +enum { +	NVM_SECTION_TYPE_HW = 0, +	NVM_SECTION_TYPE_SW, +	NVM_SECTION_TYPE_PAPD, +	NVM_SECTION_TYPE_BT, +	NVM_SECTION_TYPE_CALIBRATION, +	NVM_SECTION_TYPE_PRODUCTION, +	NVM_SECTION_TYPE_POST_FCS_CALIB, +	NVM_NUM_OF_SECTIONS, +}; + +/** + * struct iwl_nvm_access_cmd_ver2 - Request the device to send an NVM section + * @op_code: 0 - read, 1 - write + * @target: NVM_ACCESS_TARGET_* + * @type: NVM_SECTION_TYPE_* + * @offset: offset in bytes into the section + * @length: in bytes, to read/write + * @data: if write operation, the data to write. On read its empty + */ +struct iwl_nvm_access_cmd_ver2 { +	u8 op_code; +	u8 target; +	__le16 type; +	__le16 offset; +	__le16 length; +	u8 data[]; +} __packed; /* NVM_ACCESS_CMD_API_S_VER_2 */ + +/** + * struct iwl_nvm_access_resp_ver2 - response to NVM_ACCESS_CMD + * @offset: offset in bytes into the section + * @length: in bytes, either how much was written or read + * @type: NVM_SECTION_TYPE_* + * @status: 0 for success, fail otherwise + * @data: if read operation, the data returned. Empty on write. + */ +struct iwl_nvm_access_resp_ver2 { +	__le16 offset; +	__le16 length; +	__le16 type; +	__le16 status; +	u8 data[]; +} __packed; /* NVM_ACCESS_CMD_RESP_API_S_VER_2 */ + +/* MVM_ALIVE 0x1 */ + +/* alive response is_valid values */ +#define ALIVE_RESP_UCODE_OK	BIT(0) +#define ALIVE_RESP_RFKILL	BIT(1) + +/* alive response ver_type values */ +enum { +	FW_TYPE_HW = 0, +	FW_TYPE_PROT = 1, +	FW_TYPE_AP = 2, +	FW_TYPE_WOWLAN = 3, +	FW_TYPE_TIMING = 4, +	FW_TYPE_WIPAN = 5 +}; + +/* alive response ver_subtype values */ +enum { +	FW_SUBTYPE_FULL_FEATURE = 0, +	FW_SUBTYPE_BOOTSRAP = 1, /* Not valid */ +	FW_SUBTYPE_REDUCED = 2, +	FW_SUBTYPE_ALIVE_ONLY = 3, +	FW_SUBTYPE_WOWLAN = 4, +	FW_SUBTYPE_AP_SUBTYPE = 5, +	FW_SUBTYPE_WIPAN = 6, +	FW_SUBTYPE_INITIALIZE = 9 +}; + +#define IWL_ALIVE_STATUS_ERR 0xDEAD +#define IWL_ALIVE_STATUS_OK 0xCAFE + +#define IWL_ALIVE_FLG_RFKILL	BIT(0) + +struct mvm_alive_resp { +	__le16 status; +	__le16 flags; +	u8 ucode_minor; +	u8 ucode_major; +	__le16 id; +	u8 api_minor; +	u8 api_major; +	u8 ver_subtype; +	u8 ver_type; +	u8 mac; +	u8 opt; +	__le16 reserved2; +	__le32 timestamp; +	__le32 error_event_table_ptr;	/* SRAM address for error log */ +	__le32 log_event_table_ptr;	/* SRAM address for event log */ +	__le32 cpu_register_ptr; +	__le32 dbgm_config_ptr; +	__le32 alive_counter_ptr; +	__le32 scd_base_ptr;		/* SRAM address for SCD */ +} __packed; /* ALIVE_RES_API_S_VER_1 */ + +/* Error response/notification */ +enum { +	FW_ERR_UNKNOWN_CMD = 0x0, +	FW_ERR_INVALID_CMD_PARAM = 0x1, +	FW_ERR_SERVICE = 0x2, +	FW_ERR_ARC_MEMORY = 0x3, +	FW_ERR_ARC_CODE = 0x4, +	FW_ERR_WATCH_DOG = 0x5, +	FW_ERR_WEP_GRP_KEY_INDX = 0x10, +	FW_ERR_WEP_KEY_SIZE = 0x11, +	FW_ERR_OBSOLETE_FUNC = 0x12, +	FW_ERR_UNEXPECTED = 0xFE, +	FW_ERR_FATAL = 0xFF +}; + +/** + * struct iwl_error_resp - FW error indication + * ( REPLY_ERROR = 0x2 ) + * @error_type: one of FW_ERR_* + * @cmd_id: the command ID for which the error occured + * @bad_cmd_seq_num: sequence number of the erroneous command + * @error_service: which service created the error, applicable only if + *	error_type = 2, otherwise 0 + * @timestamp: TSF in usecs. + */ +struct iwl_error_resp { +	__le32 error_type; +	u8 cmd_id; +	u8 reserved1; +	__le16 bad_cmd_seq_num; +	__le32 error_service; +	__le64 timestamp; +} __packed; + + +/* Common PHY, MAC and Bindings definitions */ + +#define MAX_MACS_IN_BINDING	(3) +#define MAX_BINDINGS		(4) +#define AUX_BINDING_INDEX	(3) +#define MAX_PHYS		(4) + +/* Used to extract ID and color from the context dword */ +#define FW_CTXT_ID_POS	  (0) +#define FW_CTXT_ID_MSK	  (0xff << FW_CTXT_ID_POS) +#define FW_CTXT_COLOR_POS (8) +#define FW_CTXT_COLOR_MSK (0xff << FW_CTXT_COLOR_POS) +#define FW_CTXT_INVALID	  (0xffffffff) + +#define FW_CMD_ID_AND_COLOR(_id, _color) ((_id << FW_CTXT_ID_POS) |\ +					  (_color << FW_CTXT_COLOR_POS)) + +/* Possible actions on PHYs, MACs and Bindings */ +enum { +	FW_CTXT_ACTION_STUB = 0, +	FW_CTXT_ACTION_ADD, +	FW_CTXT_ACTION_MODIFY, +	FW_CTXT_ACTION_REMOVE, +	FW_CTXT_ACTION_NUM +}; /* COMMON_CONTEXT_ACTION_API_E_VER_1 */ + +/* Time Events */ + +/* Time Event types, according to MAC type */ +enum iwl_time_event_type { +	/* BSS Station Events */ +	TE_BSS_STA_AGGRESSIVE_ASSOC, +	TE_BSS_STA_ASSOC, +	TE_BSS_EAP_DHCP_PROT, +	TE_BSS_QUIET_PERIOD, + +	/* P2P Device Events */ +	TE_P2P_DEVICE_DISCOVERABLE, +	TE_P2P_DEVICE_LISTEN, +	TE_P2P_DEVICE_ACTION_SCAN, +	TE_P2P_DEVICE_FULL_SCAN, + +	/* P2P Client Events */ +	TE_P2P_CLIENT_AGGRESSIVE_ASSOC, +	TE_P2P_CLIENT_ASSOC, +	TE_P2P_CLIENT_QUIET_PERIOD, + +	/* P2P GO Events */ +	TE_P2P_GO_ASSOC_PROT, +	TE_P2P_GO_REPETITIVE_NOA, +	TE_P2P_GO_CT_WINDOW, + +	/* WiDi Sync Events */ +	TE_WIDI_TX_SYNC, + +	TE_MAX +}; /* MAC_EVENT_TYPE_API_E_VER_1 */ + +/* Time Event dependencies: none, on another TE, or in a specific time */ +enum { +	TE_INDEPENDENT		= 0, +	TE_DEP_OTHER		= 1, +	TE_DEP_TSF		= 2, +	TE_EVENT_SOCIOPATHIC	= 4, +}; /* MAC_EVENT_DEPENDENCY_POLICY_API_E_VER_2 */ + +/* When to send Time Event notifications and to whom (internal = FW) */ +enum { +	TE_NOTIF_NONE = 0, +	TE_NOTIF_HOST_START = 0x1, +	TE_NOTIF_HOST_END = 0x2, +	TE_NOTIF_INTERNAL_START = 0x4, +	TE_NOTIF_INTERNAL_END = 0x8 +}; /* MAC_EVENT_ACTION_API_E_VER_1 */ + +/* + * @TE_FRAG_NONE: fragmentation of the time event is NOT allowed. + * @TE_FRAG_SINGLE: fragmentation of the time event is allowed, but only + *  the first fragment is scheduled. + * @TE_FRAG_DUAL: fragmentation of the time event is allowed, but only + *  the first 2 fragments are scheduled. + * @TE_FRAG_ENDLESS: fragmentation of the time event is allowed, and any number + *  of fragments are valid. + * + * Other than the constant defined above, specifying a fragmentation value 'x' + * means that the event can be fragmented but only the first 'x' will be + * scheduled. + */ +enum { +	TE_FRAG_NONE = 0, +	TE_FRAG_SINGLE = 1, +	TE_FRAG_DUAL = 2, +	TE_FRAG_ENDLESS = 0xffffffff +}; + +/* Repeat the time event endlessly (until removed) */ +#define TE_REPEAT_ENDLESS	(0xffffffff) +/* If a Time Event has bounded repetitions, this is the maximal value */ +#define TE_REPEAT_MAX_MSK	(0x0fffffff) +/* If a Time Event can be fragmented, this is the max number of fragments */ +#define TE_FRAG_MAX_MSK		(0x0fffffff) + +/** + * struct iwl_time_event_cmd - configuring Time Events + * ( TIME_EVENT_CMD = 0x29 ) + * @id_and_color: ID and color of the relevant MAC + * @action: action to perform, one of FW_CTXT_ACTION_* + * @id: this field has two meanings, depending on the action: + *	If the action is ADD, then it means the type of event to add. + *	For all other actions it is the unique event ID assigned when the + *	event was added by the FW. + * @apply_time: When to start the Time Event (in GP2) + * @max_delay: maximum delay to event's start (apply time), in TU + * @depends_on: the unique ID of the event we depend on (if any) + * @interval: interval between repetitions, in TU + * @interval_reciprocal: 2^32 / interval + * @duration: duration of event in TU + * @repeat: how many repetitions to do, can be TE_REPEAT_ENDLESS + * @dep_policy: one of TE_INDEPENDENT, TE_DEP_OTHER, TE_DEP_TSF + * @is_present: 0 or 1, are we present or absent during the Time Event + * @max_frags: maximal number of fragments the Time Event can be divided to + * @notify: notifications using TE_NOTIF_* (whom to notify when) + */ +struct iwl_time_event_cmd { +	/* COMMON_INDEX_HDR_API_S_VER_1 */ +	__le32 id_and_color; +	__le32 action; +	__le32 id; +	/* MAC_TIME_EVENT_DATA_API_S_VER_1 */ +	__le32 apply_time; +	__le32 max_delay; +	__le32 dep_policy; +	__le32 depends_on; +	__le32 is_present; +	__le32 max_frags; +	__le32 interval; +	__le32 interval_reciprocal; +	__le32 duration; +	__le32 repeat; +	__le32 notify; +} __packed; /* MAC_TIME_EVENT_CMD_API_S_VER_1 */ + +/** + * struct iwl_time_event_resp - response structure to iwl_time_event_cmd + * @status: bit 0 indicates success, all others specify errors + * @id: the Time Event type + * @unique_id: the unique ID assigned (in ADD) or given (others) to the TE + * @id_and_color: ID and color of the relevant MAC + */ +struct iwl_time_event_resp { +	__le32 status; +	__le32 id; +	__le32 unique_id; +	__le32 id_and_color; +} __packed; /* MAC_TIME_EVENT_RSP_API_S_VER_1 */ + +/** + * struct iwl_time_event_notif - notifications of time event start/stop + * ( TIME_EVENT_NOTIFICATION = 0x2a ) + * @timestamp: action timestamp in GP2 + * @session_id: session's unique id + * @unique_id: unique id of the Time Event itself + * @id_and_color: ID and color of the relevant MAC + * @action: one of TE_NOTIF_START or TE_NOTIF_END + * @status: true if scheduled, false otherwise (not executed) + */ +struct iwl_time_event_notif { +	__le32 timestamp; +	__le32 session_id; +	__le32 unique_id; +	__le32 id_and_color; +	__le32 action; +	__le32 status; +} __packed; /* MAC_TIME_EVENT_NTFY_API_S_VER_1 */ + + +/* Bindings and Time Quota */ + +/** + * struct iwl_binding_cmd - configuring bindings + * ( BINDING_CONTEXT_CMD = 0x2b ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @macs: array of MAC id and colors which belong to the binding + * @phy: PHY id and color which belongs to the binding + */ +struct iwl_binding_cmd { +	/* COMMON_INDEX_HDR_API_S_VER_1 */ +	__le32 id_and_color; +	__le32 action; +	/* BINDING_DATA_API_S_VER_1 */ +	__le32 macs[MAX_MACS_IN_BINDING]; +	__le32 phy; +} __packed; /* BINDING_CMD_API_S_VER_1 */ + +/** + * struct iwl_time_quota_data - configuration of time quota per binding + * @id_and_color: ID and color of the relevant Binding + * @quota: absolute time quota in TU. The scheduler will try to divide the + *	remainig quota (after Time Events) according to this quota. + * @max_duration: max uninterrupted context duration in TU + */ +struct iwl_time_quota_data { +	__le32 id_and_color; +	__le32 quota; +	__le32 max_duration; +} __packed; /* TIME_QUOTA_DATA_API_S_VER_1 */ + +/** + * struct iwl_time_quota_cmd - configuration of time quota between bindings + * ( TIME_QUOTA_CMD = 0x2c ) + * @quotas: allocations per binding + */ +struct iwl_time_quota_cmd { +	struct iwl_time_quota_data quotas[MAX_BINDINGS]; +} __packed; /* TIME_QUOTA_ALLOCATION_CMD_API_S_VER_1 */ + + +/* PHY context */ + +/* Supported bands */ +#define PHY_BAND_5  (0) +#define PHY_BAND_24 (1) + +/* Supported channel width, vary if there is VHT support */ +#define PHY_VHT_CHANNEL_MODE20	(0x0) +#define PHY_VHT_CHANNEL_MODE40	(0x1) +#define PHY_VHT_CHANNEL_MODE80	(0x2) +#define PHY_VHT_CHANNEL_MODE160	(0x3) + +/* + * Control channel position: + * For legacy set bit means upper channel, otherwise lower. + * For VHT - bit-2 marks if the control is lower/upper relative to center-freq + *   bits-1:0 mark the distance from the center freq. for 20Mhz, offset is 0. + *                                   center_freq + *                                        | + * 40Mhz                          |_______|_______| + * 80Mhz                  |_______|_______|_______|_______| + * 160Mhz |_______|_______|_______|_______|_______|_______|_______|_______| + * code      011     010     001     000  |  100     101     110    111 + */ +#define PHY_VHT_CTRL_POS_1_BELOW  (0x0) +#define PHY_VHT_CTRL_POS_2_BELOW  (0x1) +#define PHY_VHT_CTRL_POS_3_BELOW  (0x2) +#define PHY_VHT_CTRL_POS_4_BELOW  (0x3) +#define PHY_VHT_CTRL_POS_1_ABOVE  (0x4) +#define PHY_VHT_CTRL_POS_2_ABOVE  (0x5) +#define PHY_VHT_CTRL_POS_3_ABOVE  (0x6) +#define PHY_VHT_CTRL_POS_4_ABOVE  (0x7) + +/* + * @band: PHY_BAND_* + * @channel: channel number + * @width: PHY_[VHT|LEGACY]_CHANNEL_* + * @ctrl channel: PHY_[VHT|LEGACY]_CTRL_* + */ +struct iwl_fw_channel_info { +	u8 band; +	u8 channel; +	u8 width; +	u8 ctrl_pos; +} __packed; + +#define PHY_RX_CHAIN_DRIVER_FORCE_POS	(0) +#define PHY_RX_CHAIN_DRIVER_FORCE_MSK \ +	(0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS) +#define PHY_RX_CHAIN_VALID_POS		(1) +#define PHY_RX_CHAIN_VALID_MSK \ +	(0x7 << PHY_RX_CHAIN_VALID_POS) +#define PHY_RX_CHAIN_FORCE_SEL_POS	(4) +#define PHY_RX_CHAIN_FORCE_SEL_MSK \ +	(0x7 << PHY_RX_CHAIN_FORCE_SEL_POS) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_POS	(7) +#define PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK \ +	(0x7 << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS) +#define PHY_RX_CHAIN_CNT_POS		(10) +#define PHY_RX_CHAIN_CNT_MSK \ +	(0x3 << PHY_RX_CHAIN_CNT_POS) +#define PHY_RX_CHAIN_MIMO_CNT_POS	(12) +#define PHY_RX_CHAIN_MIMO_CNT_MSK \ +	(0x3 << PHY_RX_CHAIN_MIMO_CNT_POS) +#define PHY_RX_CHAIN_MIMO_FORCE_POS	(14) +#define PHY_RX_CHAIN_MIMO_FORCE_MSK \ +	(0x1 << PHY_RX_CHAIN_MIMO_FORCE_POS) + +/* TODO: fix the value, make it depend on firmware at runtime? */ +#define NUM_PHY_CTX	3 + +/* TODO: complete missing documentation */ +/** + * struct iwl_phy_context_cmd - config of the PHY context + * ( PHY_CONTEXT_CMD = 0x8 ) + * @id_and_color: ID and color of the relevant Binding + * @action: action to perform, one of FW_CTXT_ACTION_* + * @apply_time: 0 means immediate apply and context switch. + *	other value means apply new params after X usecs + * @tx_param_color: ??? + * @channel_info: + * @txchain_info: ??? + * @rxchain_info: ??? + * @acquisition_data: ??? + * @dsp_cfg_flags: set to 0 + */ +struct iwl_phy_context_cmd { +	/* COMMON_INDEX_HDR_API_S_VER_1 */ +	__le32 id_and_color; +	__le32 action; +	/* PHY_CONTEXT_DATA_API_S_VER_1 */ +	__le32 apply_time; +	__le32 tx_param_color; +	struct iwl_fw_channel_info ci; +	__le32 txchain_info; +	__le32 rxchain_info; +	__le32 acquisition_data; +	__le32 dsp_cfg_flags; +} __packed; /* PHY_CONTEXT_CMD_API_VER_1 */ + +#define IWL_RX_INFO_PHY_CNT 8 +#define IWL_RX_INFO_AGC_IDX 1 +#define IWL_RX_INFO_RSSI_AB_IDX 2 +#define IWL_RX_INFO_RSSI_C_IDX 3 +#define IWL_OFDM_AGC_DB_MSK 0xfe00 +#define IWL_OFDM_AGC_DB_POS 9 +#define IWL_OFDM_RSSI_INBAND_A_MSK 0x00ff +#define IWL_OFDM_RSSI_ALLBAND_A_MSK 0xff00 +#define IWL_OFDM_RSSI_A_POS 0 +#define IWL_OFDM_RSSI_INBAND_B_MSK 0xff0000 +#define IWL_OFDM_RSSI_ALLBAND_B_MSK 0xff000000 +#define IWL_OFDM_RSSI_B_POS 16 +#define IWL_OFDM_RSSI_INBAND_C_MSK 0x00ff +#define IWL_OFDM_RSSI_ALLBAND_C_MSK 0xff00 +#define IWL_OFDM_RSSI_C_POS 0 + +/** + * struct iwl_rx_phy_info - phy info + * (REPLY_RX_PHY_CMD = 0xc0) + * @non_cfg_phy_cnt: non configurable DSP phy data byte count + * @cfg_phy_cnt: configurable DSP phy data byte count + * @stat_id: configurable DSP phy data set ID + * @reserved1: + * @system_timestamp: GP2  at on air rise + * @timestamp: TSF at on air rise + * @beacon_time_stamp: beacon at on-air rise + * @phy_flags: general phy flags: band, modulation, ... + * @channel: channel number + * @non_cfg_phy_buf: for various implementations of non_cfg_phy + * @rate_n_flags: RATE_MCS_* + * @byte_count: frame's byte-count + * @frame_time: frame's time on the air, based on byte count and frame rate + *	calculation + * + * Before each Rx, the device sends this data. It contains PHY information + * about the reception of the packet. + */ +struct iwl_rx_phy_info { +	u8 non_cfg_phy_cnt; +	u8 cfg_phy_cnt; +	u8 stat_id; +	u8 reserved1; +	__le32 system_timestamp; +	__le64 timestamp; +	__le32 beacon_time_stamp; +	__le16 phy_flags; +	__le16 channel; +	__le32 non_cfg_phy[IWL_RX_INFO_PHY_CNT]; +	__le32 rate_n_flags; +	__le32 byte_count; +	__le16 reserved2; +	__le16 frame_time; +} __packed; + +struct iwl_rx_mpdu_res_start { +	__le16 byte_count; +	__le16 reserved; +} __packed; + +/** + * enum iwl_rx_phy_flags - to parse %iwl_rx_phy_info phy_flags + * @RX_RES_PHY_FLAGS_BAND_24: true if the packet was received on 2.4 band + * @RX_RES_PHY_FLAGS_MOD_CCK: + * @RX_RES_PHY_FLAGS_SHORT_PREAMBLE: true if packet's preamble was short + * @RX_RES_PHY_FLAGS_NARROW_BAND: + * @RX_RES_PHY_FLAGS_ANTENNA: antenna on which the packet was received + * @RX_RES_PHY_FLAGS_AGG: set if the packet was part of an A-MPDU + * @RX_RES_PHY_FLAGS_OFDM_HT: The frame was an HT frame + * @RX_RES_PHY_FLAGS_OFDM_GF: The frame used GF preamble + * @RX_RES_PHY_FLAGS_OFDM_VHT: The frame was a VHT frame + */ +enum iwl_rx_phy_flags { +	RX_RES_PHY_FLAGS_BAND_24	= BIT(0), +	RX_RES_PHY_FLAGS_MOD_CCK	= BIT(1), +	RX_RES_PHY_FLAGS_SHORT_PREAMBLE	= BIT(2), +	RX_RES_PHY_FLAGS_NARROW_BAND	= BIT(3), +	RX_RES_PHY_FLAGS_ANTENNA	= (0x7 << 4), +	RX_RES_PHY_FLAGS_ANTENNA_POS	= 4, +	RX_RES_PHY_FLAGS_AGG		= BIT(7), +	RX_RES_PHY_FLAGS_OFDM_HT	= BIT(8), +	RX_RES_PHY_FLAGS_OFDM_GF	= BIT(9), +	RX_RES_PHY_FLAGS_OFDM_VHT	= BIT(10), +}; + +/** + * enum iwl_mvm_rx_status - written by fw for each Rx packet + * @RX_MPDU_RES_STATUS_CRC_OK: CRC is fine + * @RX_MPDU_RES_STATUS_OVERRUN_OK: there was no RXE overflow + * @RX_MPDU_RES_STATUS_SRC_STA_FOUND: + * @RX_MPDU_RES_STATUS_KEY_VALID: + * @RX_MPDU_RES_STATUS_KEY_PARAM_OK: + * @RX_MPDU_RES_STATUS_ICV_OK: ICV is fine, if not, the packet is destroyed + * @RX_MPDU_RES_STATUS_MIC_OK: used for CCM alg only. TKIP MIC is checked + *	in the driver. + * @RX_MPDU_RES_STATUS_TTAK_OK: TTAK is fine + * @RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR:  valid for alg = CCM_CMAC or + *	alg = CCM only. Checks replay attack for 11w frames. Relevant only if + *	%RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME is set. + * @RX_MPDU_RES_STATUS_SEC_NO_ENC: this frame is not encrypted + * @RX_MPDU_RES_STATUS_SEC_WEP_ENC: this frame is encrypted using WEP + * @RX_MPDU_RES_STATUS_SEC_CCM_ENC: this frame is encrypted using CCM + * @RX_MPDU_RES_STATUS_SEC_TKIP_ENC: this frame is encrypted using TKIP + * @RX_MPDU_RES_STATUS_SEC_CCM_CMAC_ENC: this frame is encrypted using CCM_CMAC + * @RX_MPDU_RES_STATUS_SEC_ENC_ERR: this frame couldn't be decrypted + * @RX_MPDU_RES_STATUS_SEC_ENC_MSK: bitmask of the encryption algorithm + * @RX_MPDU_RES_STATUS_DEC_DONE: this frame has been successfully decrypted + * @RX_MPDU_RES_STATUS_PROTECT_FRAME_BIT_CMP: + * @RX_MPDU_RES_STATUS_EXT_IV_BIT_CMP: + * @RX_MPDU_RES_STATUS_KEY_ID_CMP_BIT: + * @RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME: this frame is an 11w management frame + * @RX_MPDU_RES_STATUS_HASH_INDEX_MSK: + * @RX_MPDU_RES_STATUS_STA_ID_MSK: + * @RX_MPDU_RES_STATUS_RRF_KILL: + * @RX_MPDU_RES_STATUS_FILTERING_MSK: + * @RX_MPDU_RES_STATUS2_FILTERING_MSK: + */ +enum iwl_mvm_rx_status { +	RX_MPDU_RES_STATUS_CRC_OK			= BIT(0), +	RX_MPDU_RES_STATUS_OVERRUN_OK			= BIT(1), +	RX_MPDU_RES_STATUS_SRC_STA_FOUND		= BIT(2), +	RX_MPDU_RES_STATUS_KEY_VALID			= BIT(3), +	RX_MPDU_RES_STATUS_KEY_PARAM_OK			= BIT(4), +	RX_MPDU_RES_STATUS_ICV_OK			= BIT(5), +	RX_MPDU_RES_STATUS_MIC_OK			= BIT(6), +	RX_MPDU_RES_STATUS_TTAK_OK			= BIT(7), +	RX_MPDU_RES_STATUS_MNG_FRAME_REPLAY_ERR		= BIT(7), +	RX_MPDU_RES_STATUS_SEC_NO_ENC			= (0 << 8), +	RX_MPDU_RES_STATUS_SEC_WEP_ENC			= (1 << 8), +	RX_MPDU_RES_STATUS_SEC_CCM_ENC			= (2 << 8), +	RX_MPDU_RES_STATUS_SEC_TKIP_ENC			= (3 << 8), +	RX_MPDU_RES_STATUS_SEC_CCM_CMAC_ENC		= (6 << 8), +	RX_MPDU_RES_STATUS_SEC_ENC_ERR			= (7 << 8), +	RX_MPDU_RES_STATUS_SEC_ENC_MSK			= (7 << 8), +	RX_MPDU_RES_STATUS_DEC_DONE			= BIT(11), +	RX_MPDU_RES_STATUS_PROTECT_FRAME_BIT_CMP	= BIT(12), +	RX_MPDU_RES_STATUS_EXT_IV_BIT_CMP		= BIT(13), +	RX_MPDU_RES_STATUS_KEY_ID_CMP_BIT		= BIT(14), +	RX_MPDU_RES_STATUS_ROBUST_MNG_FRAME		= BIT(15), +	RX_MPDU_RES_STATUS_HASH_INDEX_MSK		= (0x3F0000), +	RX_MPDU_RES_STATUS_STA_ID_MSK			= (0x1f000000), +	RX_MPDU_RES_STATUS_RRF_KILL			= BIT(29), +	RX_MPDU_RES_STATUS_FILTERING_MSK		= (0xc00000), +	RX_MPDU_RES_STATUS2_FILTERING_MSK		= (0xc0000000), +}; + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( RADIO_VERSION_NOTIFICATION = 0x68 ) + * @radio_flavor: + * @radio_step: + * @radio_dash: + */ +struct iwl_radio_version_notif { +	__le32 radio_flavor; +	__le32 radio_step; +	__le32 radio_dash; +} __packed; /* RADIO_VERSION_NOTOFICATION_S_VER_1 */ + +enum iwl_card_state_flags { +	CARD_ENABLED		= 0x00, +	HW_CARD_DISABLED	= 0x01, +	SW_CARD_DISABLED	= 0x02, +	CT_KILL_CARD_DISABLED	= 0x04, +	HALT_CARD_DISABLED	= 0x08, +	CARD_DISABLED_MSK	= 0x0f, +	CARD_IS_RX_ON		= 0x10, +}; + +/** + * struct iwl_radio_version_notif - information on the radio version + * ( CARD_STATE_NOTIFICATION = 0xa1 ) + * @flags: %iwl_card_state_flags + */ +struct iwl_card_state_notif { +	__le32 flags; +} __packed; /* CARD_STATE_NTFY_API_S_VER_1 */ + +/** + * struct iwl_set_calib_default_cmd - set default value for calibration. + * ( SET_CALIB_DEFAULT_CMD = 0x8e ) + * @calib_index: the calibration to set value for + * @length: of data + * @data: the value to set for the calibration result + */ +struct iwl_set_calib_default_cmd { +	__le16 calib_index; +	__le16 length; +	u8 data[0]; +} __packed; /* PHY_CALIB_OVERRIDE_VALUES_S */ + +#endif /* __fw_api_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/fw.c b/drivers/net/wireless/iwlwifi/mvm/fw.c new file mode 100644 index 00000000000..90473c2ba1c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/fw.c @@ -0,0 +1,644 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-fw.h" +#include "iwl-debug.h" +#include "iwl-csr.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-io.h" /* for iwl_mvm_rx_card_state_notif */ +#include "iwl-eeprom-parse.h" + +#include "mvm.h" +#include "iwl-phy-db.h" + +#define MVM_UCODE_ALIVE_TIMEOUT	HZ +#define MVM_UCODE_CALIB_TIMEOUT	(2*HZ) + +#define UCODE_VALID_OK	cpu_to_le32(0x1) + +/* Default calibration values for WkP - set to INIT image w/o running */ +static const u8 wkp_calib_values_bb_filter[] = { 0xbf, 0x00, 0x5f, 0x00, 0x2f, +						 0x00, 0x18, 0x00 }; +static const u8 wkp_calib_values_rx_dc[] = { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, +					     0x7f, 0x7f, 0x7f }; +static const u8 wkp_calib_values_tx_lo[] = { 0x00, 0x00, 0x00, 0x00 }; +static const u8 wkp_calib_values_tx_iq[] = { 0xff, 0x00, 0xff, 0x00, 0x00, +					     0x00 }; +static const u8 wkp_calib_values_rx_iq[] = { 0xff, 0x00, 0x00, 0x00 }; +static const u8 wkp_calib_values_rx_iq_skew[] = { 0x00, 0x00, 0x01, 0x00 }; +static const u8 wkp_calib_values_tx_iq_skew[] = { 0x01, 0x00, 0x00, 0x00 }; +static const u8 wkp_calib_values_xtal[] = { 0xd2, 0xd2 }; + +struct iwl_calib_default_data { +	u16 size; +	void *data; +}; + +#define CALIB_SIZE_N_DATA(_buf) {.size = sizeof(_buf), .data = &_buf} + +static const struct iwl_calib_default_data wkp_calib_default_data[12] = { +	[5] = CALIB_SIZE_N_DATA(wkp_calib_values_rx_dc), +	[6] = CALIB_SIZE_N_DATA(wkp_calib_values_bb_filter), +	[7] = CALIB_SIZE_N_DATA(wkp_calib_values_tx_lo), +	[8] = CALIB_SIZE_N_DATA(wkp_calib_values_tx_iq), +	[9] = CALIB_SIZE_N_DATA(wkp_calib_values_tx_iq_skew), +	[10] = CALIB_SIZE_N_DATA(wkp_calib_values_rx_iq), +	[11] = CALIB_SIZE_N_DATA(wkp_calib_values_rx_iq_skew), +}; + +struct iwl_mvm_alive_data { +	bool valid; +	u32 scd_base_addr; +}; + +static inline const struct fw_img * +iwl_get_ucode_image(struct iwl_mvm *mvm, enum iwl_ucode_type ucode_type) +{ +	if (ucode_type >= IWL_UCODE_TYPE_MAX) +		return NULL; + +	return &mvm->fw->img[ucode_type]; +} + +static int iwl_send_tx_ant_cfg(struct iwl_mvm *mvm, u8 valid_tx_ant) +{ +	struct iwl_tx_ant_cfg_cmd tx_ant_cmd = { +		.valid = cpu_to_le32(valid_tx_ant), +	}; + +	IWL_DEBUG_HC(mvm, "select valid tx ant: %u\n", valid_tx_ant); +	return iwl_mvm_send_cmd_pdu(mvm, TX_ANT_CONFIGURATION_CMD, CMD_SYNC, +				    sizeof(tx_ant_cmd), &tx_ant_cmd); +} + +static bool iwl_alive_fn(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_mvm_alive_data *alive_data = data; +	struct mvm_alive_resp *palive; + +	palive = (void *)pkt->data; + +	mvm->error_event_table = le32_to_cpu(palive->error_event_table_ptr); +	mvm->log_event_table = le32_to_cpu(palive->log_event_table_ptr); +	alive_data->scd_base_addr = le32_to_cpu(palive->scd_base_ptr); + +	alive_data->valid = le16_to_cpu(palive->status) == IWL_ALIVE_STATUS_OK; +	IWL_DEBUG_FW(mvm, "Alive ucode status 0x%04x revision 0x%01X 0x%01X\n", +		     le16_to_cpu(palive->status), palive->ver_type, +		     palive->ver_subtype); + +	return true; +} + +static bool iwl_wait_phy_db_entry(struct iwl_notif_wait_data *notif_wait, +				  struct iwl_rx_packet *pkt, void *data) +{ +	struct iwl_phy_db *phy_db = data; + +	if (pkt->hdr.cmd != CALIB_RES_NOTIF_PHY_DB) { +		WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF); +		return true; +	} + +	WARN_ON(iwl_phy_db_set_section(phy_db, pkt, GFP_ATOMIC)); + +	return false; +} + +static int iwl_mvm_load_ucode_wait_alive(struct iwl_mvm *mvm, +					 enum iwl_ucode_type ucode_type) +{ +	struct iwl_notification_wait alive_wait; +	struct iwl_mvm_alive_data alive_data; +	const struct fw_img *fw; +	int ret, i; +	enum iwl_ucode_type old_type = mvm->cur_ucode; +	static const u8 alive_cmd[] = { MVM_ALIVE }; + +	mvm->cur_ucode = ucode_type; +	fw = iwl_get_ucode_image(mvm, ucode_type); + +	mvm->ucode_loaded = false; + +	if (!fw) +		return -EINVAL; + +	iwl_init_notification_wait(&mvm->notif_wait, &alive_wait, +				   alive_cmd, ARRAY_SIZE(alive_cmd), +				   iwl_alive_fn, &alive_data); + +	ret = iwl_trans_start_fw(mvm->trans, fw, ucode_type == IWL_UCODE_INIT); +	if (ret) { +		mvm->cur_ucode = old_type; +		iwl_remove_notification(&mvm->notif_wait, &alive_wait); +		return ret; +	} + +	/* +	 * Some things may run in the background now, but we +	 * just wait for the ALIVE notification here. +	 */ +	ret = iwl_wait_notification(&mvm->notif_wait, &alive_wait, +				    MVM_UCODE_ALIVE_TIMEOUT); +	if (ret) { +		mvm->cur_ucode = old_type; +		return ret; +	} + +	if (!alive_data.valid) { +		IWL_ERR(mvm, "Loaded ucode is not valid!\n"); +		mvm->cur_ucode = old_type; +		return -EIO; +	} + +	iwl_trans_fw_alive(mvm->trans, alive_data.scd_base_addr); + +	/* +	 * Note: all the queues are enabled as part of the interface +	 * initialization, but in firmware restart scenarios they +	 * could be stopped, so wake them up. In firmware restart, +	 * mac80211 will have the queues stopped as well until the +	 * reconfiguration completes. During normal startup, they +	 * will be empty. +	 */ + +	for (i = 0; i < IWL_MAX_HW_QUEUES; i++) { +		if (i < IWL_MVM_FIRST_AGG_QUEUE && i != IWL_MVM_CMD_QUEUE) +			mvm->queue_to_mac80211[i] = i; +		else +			mvm->queue_to_mac80211[i] = IWL_INVALID_MAC80211_QUEUE; +		atomic_set(&mvm->queue_stop_count[i], 0); +	} + +	mvm->transport_queue_stop = 0; + +	mvm->ucode_loaded = true; + +	return 0; +} +#define IWL_HW_REV_ID_RAINBOW	0x2 +#define IWL_PROJ_TYPE_LHP	0x5 + +static u32 iwl_mvm_build_phy_cfg(struct iwl_mvm *mvm) +{ +	struct iwl_nvm_data *data = mvm->nvm_data; +	/* Temp calls to static definitions, will be changed to CSR calls */ +	u8 hw_rev_id = IWL_HW_REV_ID_RAINBOW; +	u8 project_type = IWL_PROJ_TYPE_LHP; + +	return data->radio_cfg_dash | (data->radio_cfg_step << 2) | +		(hw_rev_id << 4) | ((project_type & 0x7f) << 6) | +		(data->valid_tx_ant << 16) | (data->valid_rx_ant << 20); +} + +static int iwl_send_phy_cfg_cmd(struct iwl_mvm *mvm) +{ +	struct iwl_phy_cfg_cmd phy_cfg_cmd; +	enum iwl_ucode_type ucode_type = mvm->cur_ucode; + +	/* Set parameters */ +	phy_cfg_cmd.phy_cfg = cpu_to_le32(iwl_mvm_build_phy_cfg(mvm)); +	phy_cfg_cmd.calib_control.event_trigger = +		mvm->fw->default_calib[ucode_type].event_trigger; +	phy_cfg_cmd.calib_control.flow_trigger = +		mvm->fw->default_calib[ucode_type].flow_trigger; + +	IWL_DEBUG_INFO(mvm, "Sending Phy CFG command: 0x%x\n", +		       phy_cfg_cmd.phy_cfg); + +	return iwl_mvm_send_cmd_pdu(mvm, PHY_CONFIGURATION_CMD, CMD_SYNC, +				    sizeof(phy_cfg_cmd), &phy_cfg_cmd); +} + +/* Starting with the new PHY DB implementation - New calibs are enabled */ +/* Value - 0x405e7 */ +#define IWL_CALIB_DEFAULT_FLOW_INIT	(IWL_CALIB_CFG_XTAL_IDX		|\ +					 IWL_CALIB_CFG_TEMPERATURE_IDX	|\ +					 IWL_CALIB_CFG_VOLTAGE_READ_IDX	|\ +					 IWL_CALIB_CFG_DC_IDX		|\ +					 IWL_CALIB_CFG_BB_FILTER_IDX	|\ +					 IWL_CALIB_CFG_LO_LEAKAGE_IDX	|\ +					 IWL_CALIB_CFG_TX_IQ_IDX	|\ +					 IWL_CALIB_CFG_RX_IQ_IDX	|\ +					 IWL_CALIB_CFG_AGC_IDX) + +#define IWL_CALIB_DEFAULT_EVENT_INIT	0x0 + +/* Value 0x41567 */ +#define IWL_CALIB_DEFAULT_FLOW_RUN	(IWL_CALIB_CFG_XTAL_IDX		|\ +					 IWL_CALIB_CFG_TEMPERATURE_IDX	|\ +					 IWL_CALIB_CFG_VOLTAGE_READ_IDX	|\ +					 IWL_CALIB_CFG_BB_FILTER_IDX	|\ +					 IWL_CALIB_CFG_DC_IDX		|\ +					 IWL_CALIB_CFG_TX_IQ_IDX	|\ +					 IWL_CALIB_CFG_RX_IQ_IDX	|\ +					 IWL_CALIB_CFG_SENSITIVITY_IDX	|\ +					 IWL_CALIB_CFG_AGC_IDX) + +#define IWL_CALIB_DEFAULT_EVENT_RUN	(IWL_CALIB_CFG_XTAL_IDX		|\ +					 IWL_CALIB_CFG_TEMPERATURE_IDX	|\ +					 IWL_CALIB_CFG_VOLTAGE_READ_IDX	|\ +					 IWL_CALIB_CFG_TX_PWR_IDX	|\ +					 IWL_CALIB_CFG_DC_IDX		|\ +					 IWL_CALIB_CFG_TX_IQ_IDX	|\ +					 IWL_CALIB_CFG_SENSITIVITY_IDX) + +/* + * Sets the calibrations trigger values that will be sent to the FW for runtime + * and init calibrations. + * The ones given in the FW TLV are not correct. + */ +static void iwl_set_default_calib_trigger(struct iwl_mvm *mvm) +{ +	struct iwl_tlv_calib_ctrl default_calib; + +	/* +	 * WkP FW TLV calib bits are wrong, overwrite them. +	 * This defines the dynamic calibrations which are implemented in the +	 * uCode both for init(flow) calculation and event driven calibs. +	 */ + +	/* Init Image */ +	default_calib.event_trigger = cpu_to_le32(IWL_CALIB_DEFAULT_EVENT_INIT); +	default_calib.flow_trigger = cpu_to_le32(IWL_CALIB_DEFAULT_FLOW_INIT); + +	if (default_calib.event_trigger != +	    mvm->fw->default_calib[IWL_UCODE_INIT].event_trigger) +		IWL_ERR(mvm, +			"Updating the event calib for INIT image: 0x%x -> 0x%x\n", +			mvm->fw->default_calib[IWL_UCODE_INIT].event_trigger, +			default_calib.event_trigger); +	if (default_calib.flow_trigger != +	    mvm->fw->default_calib[IWL_UCODE_INIT].flow_trigger) +		IWL_ERR(mvm, +			"Updating the flow calib for INIT image: 0x%x -> 0x%x\n", +			mvm->fw->default_calib[IWL_UCODE_INIT].flow_trigger, +			default_calib.flow_trigger); + +	memcpy((void *)&mvm->fw->default_calib[IWL_UCODE_INIT], +	       &default_calib, sizeof(struct iwl_tlv_calib_ctrl)); +	IWL_ERR(mvm, +		"Setting uCode init calibrations event 0x%x, trigger 0x%x\n", +		default_calib.event_trigger, +		default_calib.flow_trigger); + +	/* Run time image */ +	default_calib.event_trigger = cpu_to_le32(IWL_CALIB_DEFAULT_EVENT_RUN); +	default_calib.flow_trigger = cpu_to_le32(IWL_CALIB_DEFAULT_FLOW_RUN); + +	if (default_calib.event_trigger != +	    mvm->fw->default_calib[IWL_UCODE_REGULAR].event_trigger) +		IWL_ERR(mvm, +			"Updating the event calib for RT image: 0x%x -> 0x%x\n", +			mvm->fw->default_calib[IWL_UCODE_REGULAR].event_trigger, +			default_calib.event_trigger); +	if (default_calib.flow_trigger != +	    mvm->fw->default_calib[IWL_UCODE_REGULAR].flow_trigger) +		IWL_ERR(mvm, +			"Updating the flow calib for RT image: 0x%x -> 0x%x\n", +			mvm->fw->default_calib[IWL_UCODE_REGULAR].flow_trigger, +			default_calib.flow_trigger); + +	memcpy((void *)&mvm->fw->default_calib[IWL_UCODE_REGULAR], +	       &default_calib, sizeof(struct iwl_tlv_calib_ctrl)); +	IWL_ERR(mvm, +		"Setting uCode runtime calibs event 0x%x, trigger 0x%x\n", +		default_calib.event_trigger, +		default_calib.flow_trigger); +} + +static int iwl_set_default_calibrations(struct iwl_mvm *mvm) +{ +	u8 cmd_raw[16]; /* holds the variable size commands */ +	struct iwl_set_calib_default_cmd *cmd = +		(struct iwl_set_calib_default_cmd *)cmd_raw; +	int ret, i; + +	/* Setting default values for calibrations we don't run */ +	for (i = 0; i < ARRAY_SIZE(wkp_calib_default_data); i++) { +		u16 cmd_len; + +		if (wkp_calib_default_data[i].size == 0) +			continue; + +		memset(cmd_raw, 0, sizeof(cmd_raw)); +		cmd_len = wkp_calib_default_data[i].size + sizeof(cmd); +		cmd->calib_index = cpu_to_le16(i); +		cmd->length = cpu_to_le16(wkp_calib_default_data[i].size); +		if (WARN_ONCE(cmd_len > sizeof(cmd_raw), +			      "Need to enlarge cmd_raw to %d\n", cmd_len)) +			break; +		memcpy(cmd->data, wkp_calib_default_data[i].data, +		       wkp_calib_default_data[i].size); +		ret = iwl_mvm_send_cmd_pdu(mvm, SET_CALIB_DEFAULT_CMD, 0, +					   sizeof(*cmd) + +					   wkp_calib_default_data[i].size, +					   cmd); +		if (ret) +			return ret; +	} + +	return 0; +} + +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm, bool read_nvm) +{ +	struct iwl_notification_wait calib_wait; +	static const u8 init_complete[] = { +		INIT_COMPLETE_NOTIF, +		CALIB_RES_NOTIF_PHY_DB +	}; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	if (mvm->init_ucode_run) +		return 0; + +	iwl_init_notification_wait(&mvm->notif_wait, +				   &calib_wait, +				   init_complete, +				   ARRAY_SIZE(init_complete), +				   iwl_wait_phy_db_entry, +				   mvm->phy_db); + +	/* Will also start the device */ +	ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_INIT); +	if (ret) { +		IWL_ERR(mvm, "Failed to start INIT ucode: %d\n", ret); +		goto error; +	} + +	if (read_nvm) { +		/* Read nvm */ +		ret = iwl_nvm_init(mvm); +		if (ret) { +			IWL_ERR(mvm, "Failed to read NVM: %d\n", ret); +			goto error; +		} +	} + +	ret = iwl_nvm_check_version(mvm->nvm_data, mvm->trans); +	WARN_ON(ret); + +	/* Override the calibrations from TLV and the const of fw */ +	iwl_set_default_calib_trigger(mvm); + +	/* WkP doesn't have all calibrations, need to set default values */ +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		ret = iwl_set_default_calibrations(mvm); +		if (ret) +			goto error; +	} + +	/* +	 * Send phy configurations command to init uCode +	 * to start the 16.0 uCode init image internal calibrations. +	 */ +	ret = iwl_send_phy_cfg_cmd(mvm); +	if (ret) { +		IWL_ERR(mvm, "Failed to run INIT calibrations: %d\n", +			ret); +		goto error; +	} + +	/* +	 * Some things may run in the background now, but we +	 * just wait for the calibration complete notification. +	 */ +	ret = iwl_wait_notification(&mvm->notif_wait, &calib_wait, +			MVM_UCODE_CALIB_TIMEOUT); +	if (!ret) +		mvm->init_ucode_run = true; +	goto out; + +error: +	iwl_remove_notification(&mvm->notif_wait, &calib_wait); +out: +	if (!iwlmvm_mod_params.init_dbg) { +		iwl_trans_stop_device(mvm->trans); +	} else if (!mvm->nvm_data) { +		/* we want to debug INIT and we have no NVM - fake */ +		mvm->nvm_data = kzalloc(sizeof(struct iwl_nvm_data) + +					sizeof(struct ieee80211_channel) + +					sizeof(struct ieee80211_rate), +					GFP_KERNEL); +		if (!mvm->nvm_data) +			return -ENOMEM; +		mvm->nvm_data->valid_rx_ant = 1; +		mvm->nvm_data->valid_tx_ant = 1; +		mvm->nvm_data->bands[0].channels = mvm->nvm_data->channels; +		mvm->nvm_data->bands[0].n_channels = 1; +		mvm->nvm_data->bands[0].n_bitrates = 1; +		mvm->nvm_data->bands[0].bitrates = +			(void *)mvm->nvm_data->channels + 1; +		mvm->nvm_data->bands[0].bitrates->hw_value = 10; +	} + +	return ret; +} + +#define UCODE_CALIB_TIMEOUT	(2*HZ) + +int iwl_mvm_up(struct iwl_mvm *mvm) +{ +	int ret, i; + +	lockdep_assert_held(&mvm->mutex); + +	ret = iwl_trans_start_hw(mvm->trans); +	if (ret) +		return ret; + +	/* If we were in RFKILL during module loading, load init ucode now */ +	if (!mvm->init_ucode_run) { +		ret = iwl_run_init_mvm_ucode(mvm, false); +		if (ret && !iwlmvm_mod_params.init_dbg) { +			IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", ret); +			goto error; +		} +	} + +	if (iwlmvm_mod_params.init_dbg) +		return 0; + +	ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_REGULAR); +	if (ret) { +		IWL_ERR(mvm, "Failed to start RT ucode: %d\n", ret); +		goto error; +	} + +	ret = iwl_send_tx_ant_cfg(mvm, mvm->nvm_data->valid_tx_ant); +	if (ret) +		goto error; + +	/* Send phy db control command and then phy db calibration*/ +	ret = iwl_send_phy_db_data(mvm->phy_db); +	if (ret) +		goto error; + +	ret = iwl_send_phy_cfg_cmd(mvm); +	if (ret) +		goto error; + +	/* init the fw <-> mac80211 STA mapping */ +	for (i = 0; i < IWL_MVM_STATION_COUNT; i++) +		RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + +	/* Add auxiliary station for scanning */ +	ret = iwl_mvm_add_aux_sta(mvm); +	if (ret) +		goto error; + +	IWL_DEBUG_INFO(mvm, "RT uCode started.\n"); + +	return 0; + error: +	iwl_trans_stop_device(mvm->trans); +	return ret; +} + +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm) +{ +	int ret, i; + +	lockdep_assert_held(&mvm->mutex); + +	ret = iwl_trans_start_hw(mvm->trans); +	if (ret) +		return ret; + +	ret = iwl_mvm_load_ucode_wait_alive(mvm, IWL_UCODE_WOWLAN); +	if (ret) { +		IWL_ERR(mvm, "Failed to start WoWLAN firmware: %d\n", ret); +		goto error; +	} + +	ret = iwl_send_tx_ant_cfg(mvm, mvm->nvm_data->valid_tx_ant); +	if (ret) +		goto error; + +	/* Send phy db control command and then phy db calibration*/ +	ret = iwl_send_phy_db_data(mvm->phy_db); +	if (ret) +		goto error; + +	ret = iwl_send_phy_cfg_cmd(mvm); +	if (ret) +		goto error; + +	/* init the fw <-> mac80211 STA mapping */ +	for (i = 0; i < IWL_MVM_STATION_COUNT; i++) +		RCU_INIT_POINTER(mvm->fw_id_to_mac_id[i], NULL); + +	/* Add auxiliary station for scanning */ +	ret = iwl_mvm_add_aux_sta(mvm); +	if (ret) +		goto error; + +	return 0; + error: +	iwl_trans_stop_device(mvm->trans); +	return ret; +} + +int iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, +				    struct iwl_rx_cmd_buffer *rxb, +				    struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_card_state_notif *card_state_notif = (void *)pkt->data; +	u32 flags = le32_to_cpu(card_state_notif->flags); + +	IWL_DEBUG_RF_KILL(mvm, "Card state received: HW:%s SW:%s CT:%s\n", +			  (flags & HW_CARD_DISABLED) ? "Kill" : "On", +			  (flags & SW_CARD_DISABLED) ? "Kill" : "On", +			  (flags & CT_KILL_CARD_DISABLED) ? +			  "Reached" : "Not reached"); + +	if (flags & CARD_DISABLED_MSK) +		iwl_write32(mvm->trans, CSR_UCODE_DRV_GP1_SET, +			    CSR_UCODE_DRV_GP1_BIT_CMD_BLOCKED); + +	return 0; +} + +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			 struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_radio_version_notif *radio_version = (void *)pkt->data; + +	/* TODO: what to do with that? */ +	IWL_DEBUG_INFO(mvm, +		       "Radio version: flavor: 0x%08x, step 0x%08x, dash 0x%08x\n", +		       le32_to_cpu(radio_version->radio_flavor), +		       le32_to_cpu(radio_version->radio_step), +		       le32_to_cpu(radio_version->radio_dash)); +	return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/led.c b/drivers/net/wireless/iwlwifi/mvm/led.c new file mode 100644 index 00000000000..011906e73a0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/led.c @@ -0,0 +1,134 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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/leds.h> +#include "iwl-io.h" +#include "iwl-csr.h" +#include "mvm.h" + +/* Set led register on */ +static void iwl_mvm_led_enable(struct iwl_mvm *mvm) +{ +	iwl_write32(mvm->trans, CSR_LED_REG, CSR_LED_REG_TURN_ON); +} + +/* Set led register off */ +static void iwl_mvm_led_disable(struct iwl_mvm *mvm) +{ +	iwl_write32(mvm->trans, CSR_LED_REG, CSR_LED_REG_TURN_OFF); +} + +static void iwl_led_brightness_set(struct led_classdev *led_cdev, +				   enum led_brightness brightness) +{ +	struct iwl_mvm *mvm = container_of(led_cdev, struct iwl_mvm, led); +	if (brightness > 0) +		iwl_mvm_led_enable(mvm); +	else +		iwl_mvm_led_disable(mvm); +} + +int iwl_mvm_leds_init(struct iwl_mvm *mvm) +{ +	int mode = iwlwifi_mod_params.led_mode; +	int ret; + +	switch (mode) { +	case IWL_LED_DEFAULT: +	case IWL_LED_RF_STATE: +		mode = IWL_LED_RF_STATE; +		break; +	case IWL_LED_DISABLE: +		IWL_INFO(mvm, "Led disabled\n"); +		return 0; +	default: +		return -EINVAL; +	}; + +	mvm->led.name = kasprintf(GFP_KERNEL, "%s-led", +				   wiphy_name(mvm->hw->wiphy)); +	mvm->led.brightness_set = iwl_led_brightness_set; +	mvm->led.max_brightness = 1; + +	if (mode == IWL_LED_RF_STATE) +		mvm->led.default_trigger = +			ieee80211_get_radio_led_name(mvm->hw); + +	ret = led_classdev_register(mvm->trans->dev, &mvm->led); +	if (ret) { +		kfree(mvm->led.name); +		IWL_INFO(mvm, "Failed to enable led\n"); +		return ret; +	} + +	return 0; +} + +void iwl_mvm_leds_exit(struct iwl_mvm *mvm) +{ +	if (iwlwifi_mod_params.led_mode == IWL_LED_DISABLE) +		return; + +	led_classdev_unregister(&mvm->led); +	kfree(mvm->led.name); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c new file mode 100644 index 00000000000..c08a17a3cab --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mac-ctxt.c @@ -0,0 +1,951 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 <net/mac80211.h> +#include "iwl-io.h" +#include "iwl-prph.h" +#include "fw-api.h" +#include "mvm.h" + +const u8 iwl_mvm_ac_to_tx_fifo[] = { +	IWL_MVM_TX_FIFO_BK, +	IWL_MVM_TX_FIFO_BE, +	IWL_MVM_TX_FIFO_VI, +	IWL_MVM_TX_FIFO_VO, +}; + +struct iwl_mvm_mac_iface_iterator_data { +	struct iwl_mvm *mvm; +	struct ieee80211_vif *vif; +	unsigned long available_mac_ids[BITS_TO_LONGS(NUM_MAC_INDEX_DRIVER)]; +	unsigned long available_tsf_ids[BITS_TO_LONGS(NUM_TSF_IDS)]; +	unsigned long used_hw_queues[BITS_TO_LONGS(IWL_MVM_FIRST_AGG_QUEUE)]; +	enum iwl_tsf_id preferred_tsf; +	bool found_vif; +}; + +static void iwl_mvm_mac_iface_iterator(void *_data, u8 *mac, +				       struct ieee80211_vif *vif) +{ +	struct iwl_mvm_mac_iface_iterator_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	u32 ac; + +	/* Iterator may already find the interface being added -- skip it */ +	if (vif == data->vif) { +		data->found_vif = true; +		return; +	} + +	/* Mark the queues used by the vif */ +	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +		if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) +			__set_bit(vif->hw_queue[ac], data->used_hw_queues); + +	if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) +		__set_bit(vif->cab_queue, data->used_hw_queues); + +	/* +	 * Mark MAC IDs as used by clearing the available bit, and +	 * (below) mark TSFs as used if their existing use is not +	 * compatible with the new interface type. +	 * No locking or atomic bit operations are needed since the +	 * data is on the stack of the caller function. +	 */ +	__clear_bit(mvmvif->id, data->available_mac_ids); + +	/* +	 * The TSF is a hardware/firmware resource, there are 4 and +	 * the driver should assign and free them as needed. However, +	 * there are cases where 2 MACs should share the same TSF ID +	 * for the purpose of clock sync, an optimization to avoid +	 * clock drift causing overlapping TBTTs/DTIMs for a GO and +	 * client in the system. +	 * +	 * The firmware will decide according to the MAC type which +	 * will be the master and slave. Clients that need to sync +	 * with a remote station will be the master, and an AP or GO +	 * will be the slave. +	 * +	 * Depending on the new interface type it can be slaved to +	 * or become the master of an existing interface. +	 */ +	switch (data->vif->type) { +	case NL80211_IFTYPE_STATION: +		/* +		 * The new interface is client, so if the existing one +		 * we're iterating is an AP, the TSF should be used to +		 * avoid drift between the new client and existing AP, +		 * the existing AP will get drift updates from the new +		 * client context in this case +		 */ +		if (vif->type == NL80211_IFTYPE_AP) { +			if (data->preferred_tsf == NUM_TSF_IDS && +			    test_bit(mvmvif->tsf_id, data->available_tsf_ids)) +				data->preferred_tsf = mvmvif->tsf_id; +			return; +		} +		break; +	case NL80211_IFTYPE_AP: +		/* +		 * The new interface is AP/GO, so should get drift +		 * updates from an existing client or use the same +		 * TSF as an existing GO. There's no drift between +		 * TSFs internally but if they used different TSFs +		 * then a new client MAC could update one of them +		 * and cause drift that way. +		 */ +		if (vif->type == NL80211_IFTYPE_STATION || +		    vif->type == NL80211_IFTYPE_AP) { +			if (data->preferred_tsf == NUM_TSF_IDS && +			    test_bit(mvmvif->tsf_id, data->available_tsf_ids)) +				data->preferred_tsf = mvmvif->tsf_id; +			return; +		} +		break; +	default: +		/* +		 * For all other interface types there's no need to +		 * take drift into account. Either they're exclusive +		 * like IBSS and monitor, or we don't care much about +		 * their TSF (like P2P Device), but we won't be able +		 * to share the TSF resource. +		 */ +		break; +	} + +	/* +	 * Unless we exited above, we can't share the TSF resource +	 * that the virtual interface we're iterating over is using +	 * with the new one, so clear the available bit and if this +	 * was the preferred one, reset that as well. +	 */ +	__clear_bit(mvmvif->tsf_id, data->available_tsf_ids); + +	if (data->preferred_tsf == mvmvif->tsf_id) +		data->preferred_tsf = NUM_TSF_IDS; +} + +/* + * Get the mask of the queus used by the vif + */ +u32 iwl_mvm_mac_get_queues_mask(struct iwl_mvm *mvm, +				struct ieee80211_vif *vif) +{ +	u32 qmask, ac; + +	if (vif->type == NL80211_IFTYPE_P2P_DEVICE) +		return BIT(IWL_OFFCHANNEL_QUEUE); + +	qmask = (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) ? +		BIT(vif->cab_queue) : 0; + +	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +		if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) +			qmask |= BIT(vif->hw_queue[ac]); + +	return qmask; +} + +static int iwl_mvm_mac_ctxt_allocate_resources(struct iwl_mvm *mvm, +					       struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_mac_iface_iterator_data data = { +		.mvm = mvm, +		.vif = vif, +		.available_mac_ids = { (1 << NUM_MAC_INDEX_DRIVER) - 1 }, +		.available_tsf_ids = { (1 << NUM_TSF_IDS) - 1 }, +		/* no preference yet */ +		.preferred_tsf = NUM_TSF_IDS, +		.used_hw_queues = { +			BIT(IWL_MVM_OFFCHANNEL_QUEUE) | +			BIT(IWL_MVM_AUX_QUEUE) | +			BIT(IWL_MVM_CMD_QUEUE) +		}, +		.found_vif = false, +	}; +	u32 ac; +	int ret; + +	/* +	 * Allocate a MAC ID and a TSF for this MAC, along with the queues +	 * and other resources. +	 */ + +	/* +	 * Before the iterator, we start with all MAC IDs and TSFs available. +	 * +	 * During iteration, all MAC IDs are cleared that are in use by other +	 * virtual interfaces, and all TSF IDs are cleared that can't be used +	 * by this new virtual interface because they're used by an interface +	 * that can't share it with the new one. +	 * At the same time, we check if there's a preferred TSF in the case +	 * that we should share it with another interface. +	 */ + +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, +		iwl_mvm_mac_iface_iterator, &data); + +	/* +	 * In the case we're getting here during resume, it's similar to +	 * firmware restart, and with RESUME_ALL the iterator will find +	 * the vif being added already. +	 * We don't want to reassign any IDs in either case since doing +	 * so would probably assign different IDs (as interfaces aren't +	 * necessarily added in the same order), but the old IDs were +	 * preserved anyway, so skip ID assignment for both resume and +	 * recovery. +	 */ +	if (data.found_vif) +		return 0; + +	/* Therefore, in recovery, we can't get here */ +	WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + +	mvmvif->id = find_first_bit(data.available_mac_ids, +				    NUM_MAC_INDEX_DRIVER); +	if (mvmvif->id == NUM_MAC_INDEX_DRIVER) { +		IWL_ERR(mvm, "Failed to init MAC context - no free ID!\n"); +		ret = -EIO; +		goto exit_fail; +	} + +	if (data.preferred_tsf != NUM_TSF_IDS) +		mvmvif->tsf_id = data.preferred_tsf; +	else +		mvmvif->tsf_id = find_first_bit(data.available_tsf_ids, +						NUM_TSF_IDS); +	if (mvmvif->tsf_id == NUM_TSF_IDS) { +		IWL_ERR(mvm, "Failed to init MAC context - no free TSF!\n"); +		ret = -EIO; +		goto exit_fail; +	} + +	mvmvif->color = 0; + +	/* No need to allocate data queues to P2P Device MAC.*/ +	if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { +		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +			vif->hw_queue[ac] = IEEE80211_INVAL_HW_QUEUE; + +		return 0; +	} + +	/* Find available queues, and allocate them to the ACs */ +	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { +		u8 queue = find_first_zero_bit(data.used_hw_queues, +					       IWL_MVM_FIRST_AGG_QUEUE); + +		if (queue >= IWL_MVM_FIRST_AGG_QUEUE) { +			IWL_ERR(mvm, "Failed to allocate queue\n"); +			ret = -EIO; +			goto exit_fail; +		} + +		__set_bit(queue, data.used_hw_queues); +		vif->hw_queue[ac] = queue; +	} + +	/* Allocate the CAB queue for softAP and GO interfaces */ +	if (vif->type == NL80211_IFTYPE_AP) { +		u8 queue = find_first_zero_bit(data.used_hw_queues, +					       IWL_MVM_FIRST_AGG_QUEUE); + +		if (queue >= IWL_MVM_FIRST_AGG_QUEUE) { +			IWL_ERR(mvm, "Failed to allocate cab queue\n"); +			ret = -EIO; +			goto exit_fail; +		} + +		vif->cab_queue = queue; +	} else { +		vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; +	} + +	mvmvif->bcast_sta.sta_id = IWL_MVM_STATION_COUNT; +	mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; + +	INIT_LIST_HEAD(&mvmvif->time_event_data.list); +	mvmvif->time_event_data.id = TE_MAX; + +	return 0; + +exit_fail: +	memset(mvmvif, 0, sizeof(struct iwl_mvm_vif)); +	memset(vif->hw_queue, IEEE80211_INVAL_HW_QUEUE, sizeof(vif->hw_queue)); +	vif->cab_queue = IEEE80211_INVAL_HW_QUEUE; +	return ret; +} + +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	u32 ac; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	ret = iwl_mvm_mac_ctxt_allocate_resources(mvm, vif); +	if (ret) +		return ret; + +	switch (vif->type) { +	case NL80211_IFTYPE_P2P_DEVICE: +		iwl_trans_ac_txq_enable(mvm->trans, IWL_MVM_OFFCHANNEL_QUEUE, +					IWL_MVM_TX_FIFO_VO); +		break; +	case NL80211_IFTYPE_AP: +		iwl_trans_ac_txq_enable(mvm->trans, vif->cab_queue, +					IWL_MVM_TX_FIFO_VO); +		/* fall through */ +	default: +		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +			iwl_trans_ac_txq_enable(mvm->trans, vif->hw_queue[ac], +						iwl_mvm_ac_to_tx_fifo[ac]); +		break; +	} + +	return 0; +} + +void iwl_mvm_mac_ctxt_release(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	int ac; + +	lockdep_assert_held(&mvm->mutex); + +	switch (vif->type) { +	case NL80211_IFTYPE_P2P_DEVICE: +		iwl_trans_txq_disable(mvm->trans, IWL_MVM_OFFCHANNEL_QUEUE); +		break; +	case NL80211_IFTYPE_AP: +		iwl_trans_txq_disable(mvm->trans, vif->cab_queue); +		/* fall through */ +	default: +		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +			iwl_trans_txq_disable(mvm->trans, vif->hw_queue[ac]); +	} +} + +static void iwl_mvm_ack_rates(struct iwl_mvm *mvm, +			      struct ieee80211_vif *vif, +			      enum ieee80211_band band, +			      u8 *cck_rates, u8 *ofdm_rates) +{ +	struct ieee80211_supported_band *sband; +	unsigned long basic = vif->bss_conf.basic_rates; +	int lowest_present_ofdm = 100; +	int lowest_present_cck = 100; +	u8 cck = 0; +	u8 ofdm = 0; +	int i; + +	sband = mvm->hw->wiphy->bands[band]; + +	for_each_set_bit(i, &basic, BITS_PER_LONG) { +		int hw = sband->bitrates[i].hw_value; +		if (hw >= IWL_FIRST_OFDM_RATE) { +			ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE); +			if (lowest_present_ofdm > hw) +				lowest_present_ofdm = hw; +		} else { +			BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + +			cck |= BIT(hw); +			if (lowest_present_cck > hw) +				lowest_present_cck = hw; +		} +	} + +	/* +	 * Now we've got the basic rates as bitmaps in the ofdm and cck +	 * variables. This isn't sufficient though, as there might not +	 * be all the right rates in the bitmap. E.g. if the only basic +	 * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps +	 * and 6 Mbps because the 802.11-2007 standard says in 9.6: +	 * +	 *    [...] a STA responding to a received frame shall transmit +	 *    its Control Response frame [...] at the highest rate in the +	 *    BSSBasicRateSet parameter that is less than or equal to the +	 *    rate of the immediately previous frame in the frame exchange +	 *    sequence ([...]) and that is of the same modulation class +	 *    ([...]) as the received frame. If no rate contained in the +	 *    BSSBasicRateSet parameter meets these conditions, then the +	 *    control frame sent in response to a received frame shall be +	 *    transmitted at the highest mandatory rate of the PHY that is +	 *    less than or equal to the rate of the received frame, and +	 *    that is of the same modulation class as the received frame. +	 * +	 * As a consequence, we need to add all mandatory rates that are +	 * lower than all of the basic rates to these bitmaps. +	 */ + +	if (IWL_RATE_24M_INDEX < lowest_present_ofdm) +		ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE; +	if (IWL_RATE_12M_INDEX < lowest_present_ofdm) +		ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE; +	/* 6M already there or needed so always add */ +	ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE; + +	/* +	 * CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP. +	 * Note, however: +	 *  - if no CCK rates are basic, it must be ERP since there must +	 *    be some basic rates at all, so they're OFDM => ERP PHY +	 *    (or we're in 5 GHz, and the cck bitmap will never be used) +	 *  - if 11M is a basic rate, it must be ERP as well, so add 5.5M +	 *  - if 5.5M is basic, 1M and 2M are mandatory +	 *  - if 2M is basic, 1M is mandatory +	 *  - if 1M is basic, that's the only valid ACK rate. +	 * As a consequence, it's not as complicated as it sounds, just add +	 * any lower rates to the ACK rate bitmap. +	 */ +	if (IWL_RATE_11M_INDEX < lowest_present_cck) +		cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE; +	if (IWL_RATE_5M_INDEX < lowest_present_cck) +		cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE; +	if (IWL_RATE_2M_INDEX < lowest_present_cck) +		cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE; +	/* 1M already there or needed so always add */ +	cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE; + +	*cck_rates = cck; +	*ofdm_rates = ofdm; +} + +static void iwl_mvm_mac_ctxt_cmd_common(struct iwl_mvm *mvm, +					struct ieee80211_vif *vif, +					struct iwl_mac_ctx_cmd *cmd, +					u32 action) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct ieee80211_chanctx_conf *chanctx; +	u8 cck_ack_rates, ofdm_ack_rates; +	int i; + +	cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							    mvmvif->color)); +	cmd->action = cpu_to_le32(action); + +	switch (vif->type) { +	case NL80211_IFTYPE_STATION: +		if (vif->p2p) +			cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_STA); +		else +			cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_BSS_STA); +		break; +	case NL80211_IFTYPE_AP: +		cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_GO); +		break; +	case NL80211_IFTYPE_MONITOR: +		cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_LISTENER); +		break; +	case NL80211_IFTYPE_P2P_DEVICE: +		cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_P2P_DEVICE); +		break; +	case NL80211_IFTYPE_ADHOC: +		cmd->mac_type = cpu_to_le32(FW_MAC_TYPE_IBSS); +		break; +	default: +		WARN_ON_ONCE(1); +	} + +	cmd->tsf_id = cpu_to_le32(mvmvif->tsf_id); + +	memcpy(cmd->node_addr, vif->addr, ETH_ALEN); +	if (vif->bss_conf.bssid) +		memcpy(cmd->bssid_addr, vif->bss_conf.bssid, ETH_ALEN); +	else +		eth_broadcast_addr(cmd->bssid_addr); + +	rcu_read_lock(); +	chanctx = rcu_dereference(vif->chanctx_conf); +	iwl_mvm_ack_rates(mvm, vif, chanctx ? chanctx->def.chan->band +					    : IEEE80211_BAND_2GHZ, +			  &cck_ack_rates, &ofdm_ack_rates); +	rcu_read_unlock(); + +	cmd->cck_rates = cpu_to_le32((u32)cck_ack_rates); +	cmd->ofdm_rates = cpu_to_le32((u32)ofdm_ack_rates); + +	cmd->cck_short_preamble = +		cpu_to_le32(vif->bss_conf.use_short_preamble ? +			    MAC_FLG_SHORT_PREAMBLE : 0); +	cmd->short_slot = +		cpu_to_le32(vif->bss_conf.use_short_slot ? +			    MAC_FLG_SHORT_SLOT : 0); + +	for (i = 0; i < AC_NUM; i++) { +		cmd->ac[i].cw_min = cpu_to_le16(mvmvif->queue_params[i].cw_min); +		cmd->ac[i].cw_max = cpu_to_le16(mvmvif->queue_params[i].cw_max); +		cmd->ac[i].aifsn = mvmvif->queue_params[i].aifs; +		cmd->ac[i].edca_txop = +			cpu_to_le16(mvmvif->queue_params[i].txop * 32); +		cmd->ac[i].fifos_mask = BIT(iwl_mvm_ac_to_tx_fifo[i]); +	} + +	if (vif->bss_conf.qos) +		cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA); + +	if (vif->bss_conf.use_cts_prot) +		cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT | +						     MAC_PROT_FLG_SELF_CTS_EN); + +	/* +	 * I think that we should enable these 2 flags regardless the HT PROT +	 * fields in the HT IE, but I am not sure. Someone knows whom to ask?... +	 */ +	if (vif->bss_conf.chandef.width != NL80211_CHAN_WIDTH_20_NOHT) { +		cmd->qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN); +		cmd->protection_flags |= cpu_to_le32(MAC_PROT_FLG_HT_PROT | +						     MAC_PROT_FLG_FAT_PROT); +	} + +	cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP); +} + +static int iwl_mvm_mac_ctxt_send_cmd(struct iwl_mvm *mvm, +				     struct iwl_mac_ctx_cmd *cmd) +{ +	int ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, CMD_SYNC, +				       sizeof(*cmd), cmd); +	if (ret) +		IWL_ERR(mvm, "Failed to send MAC context (action:%d): %d\n", +			le32_to_cpu(cmd->action), ret); +	return ret; +} + +/* + * Fill the specific data for mac context of type station or p2p client + */ +static void iwl_mvm_mac_ctxt_cmd_fill_sta(struct iwl_mvm *mvm, +					  struct ieee80211_vif *vif, +					  struct iwl_mac_data_sta *ctxt_sta) +{ +	ctxt_sta->is_assoc = cpu_to_le32(vif->bss_conf.assoc ? 1 : 0); + +	ctxt_sta->bi = cpu_to_le32(vif->bss_conf.beacon_int); +	ctxt_sta->bi_reciprocal = +		cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int)); +	ctxt_sta->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * +					      vif->bss_conf.dtim_period); +	ctxt_sta->dtim_reciprocal = +		cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int * +					       vif->bss_conf.dtim_period)); + +	ctxt_sta->listen_interval = cpu_to_le32(mvm->hw->conf.listen_interval); +	ctxt_sta->assoc_id = cpu_to_le32(vif->bss_conf.aid); +} + +static int iwl_mvm_mac_ctxt_cmd_station(struct iwl_mvm *mvm, +					struct ieee80211_vif *vif, +					u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_STATION || vif->p2p); + +	/* Fill the common data for all mac context types */ +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + +	/* Fill the data specific for station mode */ +	iwl_mvm_mac_ctxt_cmd_fill_sta(mvm, vif, &cmd.sta); + +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_client(struct iwl_mvm *mvm, +					   struct ieee80211_vif *vif, +					   u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_STATION || !vif->p2p); + +	/* Fill the common data for all mac context types */ +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + +	/* Fill the data specific for station mode */ +	iwl_mvm_mac_ctxt_cmd_fill_sta(mvm, vif, &cmd.p2p_sta.sta); + +	cmd.p2p_sta.ctwin = cpu_to_le32(vif->bss_conf.p2p_ctwindow); + +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_listener(struct iwl_mvm *mvm, +					 struct ieee80211_vif *vif, +					 u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_MONITOR); + +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); +	/* No other data to be filled */ +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +struct iwl_mvm_go_iterator_data { +	bool go_active; +}; + +static void iwl_mvm_go_iterator(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_go_iterator_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (vif->type == NL80211_IFTYPE_AP && vif->p2p && mvmvif->ap_active) +		data->go_active = true; +} + +static int iwl_mvm_mac_ctxt_cmd_p2p_device(struct iwl_mvm *mvm, +					   struct ieee80211_vif *vif, +					   u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; +	struct iwl_mvm_go_iterator_data data = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_P2P_DEVICE); + +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + +	cmd.protection_flags |= cpu_to_le32(MAC_PROT_FLG_TGG_PROTECT); +	cmd.filter_flags |= cpu_to_le32(MAC_FILTER_IN_PROMISC); + +	/* +	 * This flag should be set to true when the P2P Device is +	 * discoverable and there is at least another active P2P GO. Settings +	 * this flag will allow the P2P Device to be discoverable on other +	 * channels in addition to its listen channel. +	 * Note that this flag should not be set in other cases as it opens the +	 * Rx filters on all MAC and increases the number of interrupts. +	 */ +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, +		iwl_mvm_go_iterator, &data); + +	cmd.p2p_dev.is_disc_extended = cpu_to_le32(data.go_active ? 1 : 0); +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static void iwl_mvm_mac_ctxt_set_tim(struct iwl_mvm *mvm, +				     struct iwl_mac_beacon_cmd *beacon_cmd, +				     u8 *beacon, u32 frame_size) +{ +	u32 tim_idx; +	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)beacon; + +	/* The index is relative to frame start but we start looking at the +	 * variable-length part of the beacon. */ +	tim_idx = mgmt->u.beacon.variable - beacon; + +	/* Parse variable-length elements of beacon to find WLAN_EID_TIM */ +	while ((tim_idx < (frame_size - 2)) && +			(beacon[tim_idx] != WLAN_EID_TIM)) +		tim_idx += beacon[tim_idx+1] + 2; + +	/* If TIM field was found, set variables */ +	if ((tim_idx < (frame_size - 1)) && (beacon[tim_idx] == WLAN_EID_TIM)) { +		beacon_cmd->tim_idx = cpu_to_le32(tim_idx); +		beacon_cmd->tim_size = cpu_to_le32((u32)beacon[tim_idx+1]); +	} else { +		IWL_WARN(mvm, "Unable to find TIM Element in beacon\n"); +	} +} + +static int iwl_mvm_mac_ctxt_send_beacon(struct iwl_mvm *mvm, +					struct ieee80211_vif *vif, +					struct sk_buff *beacon) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_host_cmd cmd = { +		.id = BEACON_TEMPLATE_CMD, +		.flags = CMD_ASYNC, +	}; +	struct iwl_mac_beacon_cmd beacon_cmd = {}; +	struct ieee80211_tx_info *info; +	u32 beacon_skb_len; +	u32 rate; + +	if (WARN_ON(!beacon)) +		return -EINVAL; + +	beacon_skb_len = beacon->len; + +	/* TODO: for now the beacon template id is set to be the mac context id. +	 * Might be better to handle it as another resource ... */ +	beacon_cmd.template_id = cpu_to_le32((u32)mvmvif->id); + +	/* Set up TX command fields */ +	beacon_cmd.tx.len = cpu_to_le16((u16)beacon_skb_len); +	beacon_cmd.tx.sta_id = mvmvif->bcast_sta.sta_id; +	beacon_cmd.tx.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); +	beacon_cmd.tx.tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL | +					     TX_CMD_FLG_BT_DIS  | +					     TX_CMD_FLG_TSF); + +	mvm->mgmt_last_antenna_idx = +		iwl_mvm_next_antenna(mvm, mvm->nvm_data->valid_tx_ant, +				     mvm->mgmt_last_antenna_idx); + +	beacon_cmd.tx.rate_n_flags = +		cpu_to_le32(BIT(mvm->mgmt_last_antenna_idx) << +			    RATE_MCS_ANT_POS); + +	info = IEEE80211_SKB_CB(beacon); + +	if (info->band == IEEE80211_BAND_5GHZ || vif->p2p) { +		rate = IWL_FIRST_OFDM_RATE; +	} else { +		rate = IWL_FIRST_CCK_RATE; +		beacon_cmd.tx.rate_n_flags |= cpu_to_le32(RATE_MCS_CCK_MSK); +	} +	beacon_cmd.tx.rate_n_flags |= +		cpu_to_le32(iwl_mvm_mac80211_idx_to_hwrate(rate)); + +	/* Set up TX beacon command fields */ +	iwl_mvm_mac_ctxt_set_tim(mvm, &beacon_cmd, +				 beacon->data, +				 beacon_skb_len); + +	/* Submit command */ +	cmd.len[0] = sizeof(beacon_cmd); +	cmd.data[0] = &beacon_cmd; +	cmd.dataflags[0] = 0; +	cmd.len[1] = beacon_skb_len; +	cmd.data[1] = beacon->data; +	cmd.dataflags[1] = IWL_HCMD_DFL_DUP; + +	return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* The beacon template for the AP/GO context has changed and needs update */ +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, +				    struct ieee80211_vif *vif) +{ +	struct sk_buff *beacon; +	int ret; + +	WARN_ON(vif->type != NL80211_IFTYPE_AP); + +	beacon = ieee80211_beacon_get(mvm->hw, vif); +	if (!beacon) +		return -ENOMEM; + +	ret = iwl_mvm_mac_ctxt_send_beacon(mvm, vif, beacon); +	dev_kfree_skb(beacon); +	return ret; +} + +/* + * Fill the specific data for mac context of type AP of P2P GO + */ +static void iwl_mvm_mac_ctxt_cmd_fill_ap(struct iwl_mvm *mvm, +					 struct ieee80211_vif *vif, +					 struct iwl_mac_data_ap *ctxt_ap) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	u32 curr_dev_time; + +	ctxt_ap->bi = cpu_to_le32(vif->bss_conf.beacon_int); +	ctxt_ap->bi_reciprocal = +		cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int)); +	ctxt_ap->dtim_interval = cpu_to_le32(vif->bss_conf.beacon_int * +					     vif->bss_conf.dtim_period); +	ctxt_ap->dtim_reciprocal = +		cpu_to_le32(iwl_mvm_reciprocal(vif->bss_conf.beacon_int * +					       vif->bss_conf.dtim_period)); + +	ctxt_ap->mcast_qid = cpu_to_le32(vif->cab_queue); +	curr_dev_time = iwl_read_prph(mvm->trans, DEVICE_SYSTEM_TIME_REG); +	ctxt_ap->beacon_time = cpu_to_le32(curr_dev_time); + +	ctxt_ap->beacon_tsf = cpu_to_le64(curr_dev_time); + +	/* TODO: Assume that the beacon id == mac context id */ +	ctxt_ap->beacon_template = cpu_to_le32(mvmvif->id); +} + +static int iwl_mvm_mac_ctxt_cmd_ap(struct iwl_mvm *mvm, +				   struct ieee80211_vif *vif, +				   u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_AP || vif->p2p); + +	/* Fill the common data for all mac context types */ +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + +	/* Fill the data specific for ap mode */ +	iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd.ap); + +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctxt_cmd_go(struct iwl_mvm *mvm, +				   struct ieee80211_vif *vif, +				   u32 action) +{ +	struct iwl_mac_ctx_cmd cmd = {}; + +	WARN_ON(vif->type != NL80211_IFTYPE_AP || !vif->p2p); + +	/* Fill the common data for all mac context types */ +	iwl_mvm_mac_ctxt_cmd_common(mvm, vif, &cmd, action); + +	/* Fill the data specific for GO mode */ +	iwl_mvm_mac_ctxt_cmd_fill_ap(mvm, vif, &cmd.go.ap); + +	cmd.go.ctwin = cpu_to_le32(vif->bss_conf.p2p_ctwindow); +	cmd.go.opp_ps_enabled = cpu_to_le32(!!vif->bss_conf.p2p_oppps); + +	return iwl_mvm_mac_ctxt_send_cmd(mvm, &cmd); +} + +static int iwl_mvm_mac_ctx_send(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +				u32 action) +{ +	switch (vif->type) { +	case NL80211_IFTYPE_STATION: +		if (!vif->p2p) +			return iwl_mvm_mac_ctxt_cmd_station(mvm, vif, +							    action); +		else +			return iwl_mvm_mac_ctxt_cmd_p2p_client(mvm, vif, +							       action); +		break; +	case NL80211_IFTYPE_AP: +		if (!vif->p2p) +			return iwl_mvm_mac_ctxt_cmd_ap(mvm, vif, action); +		else +			return iwl_mvm_mac_ctxt_cmd_go(mvm, vif, action); +		break; +	case NL80211_IFTYPE_MONITOR: +		return iwl_mvm_mac_ctxt_cmd_listener(mvm, vif, action); +	case NL80211_IFTYPE_P2P_DEVICE: +		return iwl_mvm_mac_ctxt_cmd_p2p_device(mvm, vif, action); +	default: +		break; +	} + +	return -EOPNOTSUPP; +} + +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	int ret; + +	if (WARN_ONCE(mvmvif->uploaded, "Adding active MAC %pM/%d\n", +		      vif->addr, ieee80211_vif_type_p2p(vif))) +		return -EIO; + +	ret = iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_ADD); +	if (ret) +		return ret; + +	mvmvif->uploaded = true; +	return 0; +} + +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (WARN_ONCE(!mvmvif->uploaded, "Changing inactive MAC %pM/%d\n", +		      vif->addr, ieee80211_vif_type_p2p(vif))) +		return -EIO; + +	return iwl_mvm_mac_ctx_send(mvm, vif, FW_CTXT_ACTION_MODIFY); +} + +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mac_ctx_cmd cmd; +	int ret; + +	if (WARN_ONCE(!mvmvif->uploaded, "Removing inactive MAC %pM/%d\n", +		      vif->addr, ieee80211_vif_type_p2p(vif))) +		return -EIO; + +	memset(&cmd, 0, sizeof(cmd)); + +	cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							   mvmvif->color)); +	cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); + +	ret = iwl_mvm_send_cmd_pdu(mvm, MAC_CONTEXT_CMD, CMD_SYNC, +				   sizeof(cmd), &cmd); +	if (ret) { +		IWL_ERR(mvm, "Failed to remove MAC context: %d\n", ret); +		return ret; +	} + +	mvmvif->uploaded = false; +	return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/iwlwifi/mvm/mac80211.c new file mode 100644 index 00000000000..a6b05a02cfd --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mac80211.c @@ -0,0 +1,1310 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 <net/mac80211.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 "fw-api-scan.h" +#include "iwl-phy-db.h" + +static const struct ieee80211_iface_limit iwl_mvm_limits[] = { +	{ +		.max = 1, +		.types = BIT(NL80211_IFTYPE_STATION) | +			BIT(NL80211_IFTYPE_AP), +	}, +	{ +		.max = 1, +		.types = 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 = 1, +		.max_interfaces = 3, +		.limits = iwl_mvm_limits, +		.n_limits = ARRAY_SIZE(iwl_mvm_limits), +	}, +}; + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm) +{ +	struct ieee80211_hw *hw = mvm->hw; +	int num_mac, ret; + +	/* Tell mac80211 our characteristics */ +	hw->flags = IEEE80211_HW_SIGNAL_DBM | +		    IEEE80211_HW_SPECTRUM_MGMT | +		    IEEE80211_HW_REPORTS_TX_ACK_STATUS | +		    IEEE80211_HW_QUEUE_CONTROL | +		    IEEE80211_HW_WANT_MONITOR_VIF | +		    IEEE80211_HW_SCAN_WHILE_IDLE | +		    IEEE80211_HW_NEED_DTIM_PERIOD | +		    IEEE80211_HW_SUPPORTS_PS | +		    IEEE80211_HW_SUPPORTS_DYNAMIC_PS | +		    IEEE80211_HW_AMPDU_AGGREGATION; + +	hw->queues = IWL_FIRST_AMPDU_QUEUE; +	hw->offchannel_tx_hw_queue = IWL_OFFCHANNEL_QUEUE; +	hw->rate_control_algorithm = "iwl-mvm-rs"; + +	/* +	 * 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) +		hw->flags |= IEEE80211_HW_MFP_CAPABLE; + +	hw->sta_data_size = sizeof(struct iwl_mvm_sta); +	hw->vif_data_size = sizeof(struct iwl_mvm_vif); +	hw->chanctx_data_size = sizeof(struct iwl_mvm_phy_ctxt); + +	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); + +	hw->wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY | +			    WIPHY_FLAG_DISABLE_BEACON_HINTS | +			    WIPHY_FLAG_IBSS_RSN; + +	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 = 500; +	hw->max_listen_interval = IWL_CONN_MAX_LISTEN_INTERVAL; + +	/* 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; +	num_mac = mvm->nvm_data->n_hw_addrs; +	if (num_mac > 1) { +		memcpy(mvm->addresses[1].addr, mvm->addresses[0].addr, +		       ETH_ALEN); +		mvm->addresses[1].addr[5]++; +		hw->wiphy->n_addresses++; +	} + +	/* we create the 802.11 header and a max-length SSID element */ +	hw->wiphy->max_scan_ie_len = +		mvm->fw->ucode_capa.max_probe_length - 24 - 34; +	hw->wiphy->max_scan_ssids = PROBE_OPTION_MAX; + +	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]; + +	hw->wiphy->hw_version = mvm->trans->hw_id; + +	if (iwlwifi_mod_params.power_save) +		hw->wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; +	else +		hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; + +	hw->wiphy->features |= NL80211_FEATURE_P2P_GO_CTWIN | +			       NL80211_FEATURE_P2P_GO_OPPPS; + +	mvm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + +#ifdef CONFIG_PM_SLEEP +	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)) { +		hw->wiphy->wowlan.flags = WIPHY_WOWLAN_MAGIC_PKT | +					  WIPHY_WOWLAN_DISCONNECT | +					  WIPHY_WOWLAN_EAP_IDENTITY_REQ | +					  WIPHY_WOWLAN_RFKILL_RELEASE; +		if (!iwlwifi_mod_params.sw_crypto) +			hw->wiphy->wowlan.flags |= +				WIPHY_WOWLAN_SUPPORTS_GTK_REKEY | +				WIPHY_WOWLAN_GTK_REKEY_FAILURE | +				WIPHY_WOWLAN_4WAY_HANDSHAKE; + +		hw->wiphy->wowlan.n_patterns = IWL_WOWLAN_MAX_PATTERNS; +		hw->wiphy->wowlan.pattern_min_len = IWL_WOWLAN_MIN_PATTERN_LEN; +		hw->wiphy->wowlan.pattern_max_len = IWL_WOWLAN_MAX_PATTERN_LEN; +	} +#endif + +	ret = iwl_mvm_leds_init(mvm); +	if (ret) +		return ret; + +	return ieee80211_register_hw(mvm->hw); +} + +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); + +	if (test_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status)) { +		IWL_DEBUG_DROP(mvm, "Dropping - RF KILL\n"); +		goto drop; +	} + +	if (IEEE80211_SKB_CB(skb)->hw_queue == IWL_OFFCHANNEL_QUEUE && +	    !test_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status)) +		goto drop; + +	if (control->sta) { +		if (iwl_mvm_tx_skb(mvm, skb, control->sta)) +			goto drop; +		return; +	} + +	if (iwl_mvm_tx_skb_non_sta(mvm, skb)) +		goto drop; +	return; + drop: +	ieee80211_free_txskb(hw, skb); +} + +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) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	int ret; + +	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; + +	mutex_lock(&mvm->mutex); + +	switch (action) { +	case IEEE80211_AMPDU_RX_START: +		if (iwlwifi_mod_params.disable_11n & IWL_DISABLE_HT_RXAGG) { +			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: +		ret = iwl_mvm_sta_tx_agg_start(mvm, vif, sta, tid, ssn); +		break; +	case IEEE80211_AMPDU_TX_STOP_CONT: +	case IEEE80211_AMPDU_TX_STOP_FLUSH: +	case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: +		ret = iwl_mvm_sta_tx_agg_stop(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; +	} +	mutex_unlock(&mvm->mutex); + +	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; + +	/* does this make sense at all? */ +	mvmvif->color++; + +	spin_lock_bh(&mvm->time_event_lock); +	iwl_mvm_te_clear_data(mvm, &mvmvif->time_event_data); +	spin_unlock_bh(&mvm->time_event_lock); + +	if (vif->type != NL80211_IFTYPE_P2P_DEVICE) +		mvmvif->phy_ctxt = NULL; +} + +static void iwl_mvm_restart_cleanup(struct iwl_mvm *mvm) +{ +	iwl_trans_stop_device(mvm->trans); +	iwl_trans_stop_hw(mvm->trans, false); + +	mvm->scan_status = IWL_MVM_SCAN_NONE; + +	/* just in case one was running */ +	ieee80211_remain_on_channel_expired(mvm->hw); + +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_RESUME_ALL, +		iwl_mvm_cleanup_iterator, mvm); + +	memset(mvm->fw_key_table, 0, sizeof(mvm->fw_key_table)); +	memset(mvm->sta_drained, 0, sizeof(mvm->sta_drained)); + +	ieee80211_wake_queues(mvm->hw); + +	mvm->vif_count = 0; +} + +static int iwl_mvm_mac_start(struct ieee80211_hw *hw) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	int ret; + +	mutex_lock(&mvm->mutex); + +	/* 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); +	mutex_unlock(&mvm->mutex); + +	return ret; +} + +static void iwl_mvm_mac_restart_complete(struct ieee80211_hw *hw) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	int ret; + +	mutex_lock(&mvm->mutex); + +	clear_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); +	ret = iwl_mvm_update_quotas(mvm, NULL); +	if (ret) +		IWL_ERR(mvm, "Failed to update quotas after restart (%d)\n", +			ret); + +	mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_mac_stop(struct ieee80211_hw *hw) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + +	flush_work(&mvm->async_handlers_wk); + +	mutex_lock(&mvm->mutex); +	/* 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. +	 */ +	cancel_work_sync(&mvm->roc_done_wk); + +	iwl_trans_stop_device(mvm->trans); +	iwl_trans_stop_hw(mvm->trans, false); + +	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_dealloc_int_sta(mvm, &mvm->aux_sta); + +	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 void iwl_mvm_pm_disable_iterator(void *data, u8 *mac, +					struct ieee80211_vif *vif) +{ +	struct iwl_mvm *mvm = data; +	int ret; + +	ret = iwl_mvm_power_disable(mvm, vif); +	if (ret) +		IWL_ERR(mvm, "failed to disable power management\n"); +} + +static void iwl_mvm_power_update_iterator(void *data, u8 *mac, +					  struct ieee80211_vif *vif) +{ +	struct iwl_mvm *mvm = data; + +	iwl_mvm_power_update_mode(mvm, vif); +} + +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; + +	/* +	 * 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); + +	/* Allocate resources for the MAC context, and add it the the fw  */ +	ret = iwl_mvm_mac_ctxt_init(mvm, vif); +	if (ret) +		goto out_unlock; + +	/* +	 * 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) { +		u32 qmask = iwl_mvm_mac_get_queues_mask(mvm, vif); +		ret = iwl_mvm_allocate_int_sta(mvm, &mvmvif->bcast_sta, +					       qmask); +		if (ret) { +			IWL_ERR(mvm, "Failed to allocate bcast sta\n"); +			goto out_release; +		} + +		goto out_unlock; +	} + +	/* +	 * TODO: remove this temporary code. +	 * Currently MVM FW supports power management only on single MAC. +	 * Iterate and disable PM on all active interfaces. +	 * Note: the method below does not count the new interface being added +	 * at this moment. +	 */ +	mvm->vif_count++; +	if (mvm->vif_count > 1) { +		IWL_DEBUG_MAC80211(mvm, +				   "Disable power on existing interfaces\n"); +		ieee80211_iterate_active_interfaces( +					    mvm->hw, +					    IEEE80211_IFACE_ITER_NORMAL, +					    iwl_mvm_pm_disable_iterator, mvm); +	} + +	ret = iwl_mvm_mac_ctxt_add(mvm, vif); +	if (ret) +		goto out_release; + +	/* +	 * Update power state on the new interface. Admittedly, based on +	 * mac80211 logics this power update will disable power management +	 */ +	iwl_mvm_power_update_mode(mvm, vif); + +	/* +	 * 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) { +		struct ieee80211_channel *chan; +		struct cfg80211_chan_def chandef; + +		mvmvif->phy_ctxt = &mvm->phy_ctxt_roc; + +		/* +		 * The channel used here isn't relevant as it's +		 * going to be overwritten as part of the ROC flow. +		 * For now use the first channel we have. +		 */ +		chan = &mvm->hw->wiphy->bands[IEEE80211_BAND_2GHZ]->channels[0]; +		cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_NO_HT); +		ret = iwl_mvm_phy_ctxt_add(mvm, mvmvif->phy_ctxt, +					   &chandef, 1, 1); +		if (ret) +			goto out_remove_mac; + +		ret = iwl_mvm_binding_add_vif(mvm, vif); +		if (ret) +			goto out_remove_phy; + +		ret = iwl_mvm_add_bcast_sta(mvm, vif, &mvmvif->bcast_sta); +		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; +	} + +	goto out_unlock; + + out_unbind: +	iwl_mvm_binding_remove_vif(mvm, vif); + out_remove_phy: +	iwl_mvm_phy_ctxt_remove(mvm, mvmvif->phy_ctxt); + out_remove_mac: +	mvmvif->phy_ctxt = NULL; +	iwl_mvm_mac_ctxt_remove(mvm, vif); + out_release: +	/* +	 * TODO: remove this temporary code. +	 * Currently MVM FW supports power management only on single MAC. +	 * Check if only one additional interface remains after rereasing +	 * current one. Update power mode on the remaining interface. +	 */ +	mvm->vif_count--; +	IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", +			   mvm->vif_count); +	if (mvm->vif_count == 1) { +		ieee80211_iterate_active_interfaces( +					mvm->hw, IEEE80211_IFACE_ITER_NORMAL, +					iwl_mvm_power_update_iterator, mvm); +	} +	iwl_mvm_mac_ctxt_release(mvm, vif); + out_unlock: +	mutex_unlock(&mvm->mutex); + +	return ret; +} + +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); +	u32 tfd_msk = 0, ac; + +	for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) +		if (vif->hw_queue[ac] != IEEE80211_INVAL_HW_QUEUE) +			tfd_msk |= BIT(vif->hw_queue[ac]); + +	if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) +		tfd_msk |= BIT(vif->cab_queue); + +	if (tfd_msk) { +		mutex_lock(&mvm->mutex); +		iwl_mvm_flush_tx_path(mvm, tfd_msk, true); +		mutex_unlock(&mvm->mutex); +	} + +	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. This work item takes the mutex, so kill +		 * it before we take it. +		 */ +		flush_work(&mvm->sta_drained_wk); +	} + +	mutex_lock(&mvm->mutex); + +	/* +	 * For AP/GO interface, the tear down of the resources allocated to the +	 * interface should be handled as part of the bss_info_changed flow. +	 */ +	if (vif->type == NL80211_IFTYPE_AP) { +		iwl_mvm_dealloc_int_sta(mvm, &mvmvif->bcast_sta); +		goto out_release; +	} + +	if (vif->type == NL80211_IFTYPE_P2P_DEVICE) { +		mvm->p2p_device_vif = NULL; +		iwl_mvm_rm_bcast_sta(mvm, &mvmvif->bcast_sta); +		iwl_mvm_binding_remove_vif(mvm, vif); +		iwl_mvm_phy_ctxt_remove(mvm, mvmvif->phy_ctxt); +		mvmvif->phy_ctxt = NULL; +	} + +	/* +	 * TODO: remove this temporary code. +	 * Currently MVM FW supports power management only on single MAC. +	 * Check if only one additional interface remains after removing +	 * current one. Update power mode on the remaining interface. +	 */ +	if (mvm->vif_count) +		mvm->vif_count--; +	IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", +			   mvm->vif_count); +	if (mvm->vif_count == 1) { +		ieee80211_iterate_active_interfaces( +					mvm->hw, IEEE80211_IFACE_ITER_NORMAL, +					iwl_mvm_power_update_iterator, 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; +} + +static void iwl_mvm_configure_filter(struct ieee80211_hw *hw, +				     unsigned int changed_flags, +				     unsigned int *total_flags, +				     u64 multicast) +{ +	*total_flags = 0; +} + +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; + +	ret = iwl_mvm_mac_ctxt_changed(mvm, vif); +	if (ret) +		IWL_ERR(mvm, "failed to update MAC %pM\n", vif->addr); + +	if (changes & BSS_CHANGED_ASSOC) { +		if (bss_conf->assoc) { +			/* add quota for this interface */ +			ret = iwl_mvm_update_quotas(mvm, vif); +			if (ret) { +				IWL_ERR(mvm, "failed to update quotas\n"); +				return; +			} +			iwl_mvm_remove_time_event(mvm, mvmvif, +						  &mvmvif->time_event_data); +		} else if (mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) { +			/* 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"); +			mvmvif->ap_sta_id = IWL_MVM_STATION_COUNT; +			/* remove quota for this interface */ +			ret = iwl_mvm_update_quotas(mvm, NULL); +			if (ret) +				IWL_ERR(mvm, "failed to update quotas\n"); +		} +	} else if (changes & BSS_CHANGED_PS) { +		/* +		 * TODO: remove this temporary code. +		 * Currently MVM FW supports power management only on single +		 * MAC. Avoid power mode update if more than one interface +		 * is active. +		 */ +		IWL_DEBUG_MAC80211(mvm, "Currently %d interfaces active\n", +				   mvm->vif_count); +		if (mvm->vif_count == 1) { +			ret = iwl_mvm_power_update_mode(mvm, vif); +			if (ret) +				IWL_ERR(mvm, "failed to update power mode\n"); +		} +	} +} + +static int iwl_mvm_start_ap(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; + +	mutex_lock(&mvm->mutex); + +	/* Send the beacon template */ +	ret = iwl_mvm_mac_ctxt_beacon_changed(mvm, vif); +	if (ret) +		goto out_unlock; + +	/* 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; + +	mvmvif->ap_active = true; + +	/* Send the bcast station. At this stage the TBTT and DTIM time events +	 * are added and applied to the scheduler */ +	ret = iwl_mvm_send_bcast_sta(mvm, vif, &mvmvif->bcast_sta); +	if (ret) +		goto out_unbind; + +	ret = iwl_mvm_update_quotas(mvm, vif); +	if (ret) +		goto out_rm_bcast; + +	/* Need to update the P2P Device MAC */ +	if (vif->p2p && mvm->p2p_device_vif) +		iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif); + +	mutex_unlock(&mvm->mutex); +	return 0; + +out_rm_bcast: +	iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta); +out_unbind: +	iwl_mvm_binding_remove_vif(mvm, vif); +out_remove: +	iwl_mvm_mac_ctxt_remove(mvm, vif); +out_unlock: +	mutex_unlock(&mvm->mutex); +	return ret; +} + +static void iwl_mvm_stop_ap(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); + +	mutex_lock(&mvm->mutex); + +	mvmvif->ap_active = false; + +	/* Need to update the P2P Device MAC */ +	if (vif->p2p && mvm->p2p_device_vif) +		iwl_mvm_mac_ctxt_changed(mvm, mvm->p2p_device_vif); + +	iwl_mvm_update_quotas(mvm, NULL); +	iwl_mvm_send_rm_bcast_sta(mvm, &mvmvif->bcast_sta); +	iwl_mvm_binding_remove_vif(mvm, vif); +	iwl_mvm_mac_ctxt_remove(mvm, vif); + +	mutex_unlock(&mvm->mutex); +} + +static void iwl_mvm_bss_info_changed_ap(struct iwl_mvm *mvm, +					struct ieee80211_vif *vif, +					struct ieee80211_bss_conf *bss_conf, +					u32 changes) +{ +	/* Need to send a new beacon template to the FW */ +	if (changes & BSS_CHANGED_BEACON) { +		if (iwl_mvm_mac_ctxt_beacon_changed(mvm, vif)) +			IWL_WARN(mvm, "Failed updating beacon data\n"); +	} +} + +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); + +	mutex_lock(&mvm->mutex); + +	switch (vif->type) { +	case NL80211_IFTYPE_STATION: +		iwl_mvm_bss_info_changed_station(mvm, vif, bss_conf, changes); +		break; +	case NL80211_IFTYPE_AP: +		iwl_mvm_bss_info_changed_ap(mvm, vif, bss_conf, changes); +		break; +	default: +		/* shouldn't happen */ +		WARN_ON_ONCE(1); +	} + +	mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw, +			       struct ieee80211_vif *vif, +			       struct cfg80211_scan_request *req) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	int ret; + +	if (req->n_channels == 0 || req->n_channels > MAX_NUM_SCAN_CHANNELS) +		return -EINVAL; + +	mutex_lock(&mvm->mutex); + +	if (mvm->scan_status == IWL_MVM_SCAN_NONE) +		ret = iwl_mvm_scan_request(mvm, vif, req); +	else +		ret = -EBUSY; + +	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); + +	iwl_mvm_cancel_scan(mvm); + +	mutex_unlock(&mvm->mutex); +} + +static void +iwl_mvm_mac_allow_buffered_frames(struct ieee80211_hw *hw, +				  struct ieee80211_sta *sta, u16 tid, +				  int num_frames, +				  enum ieee80211_frame_release_type reason, +				  bool more_data) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; + +	/* TODO: how do we tell the fw to send frames for a specific TID */ + +	/* +	 * The fw will send EOSP notification when the last frame will be +	 * transmitted. +	 */ +	iwl_mvm_sta_modify_sleep_tx_count(mvm, mvmsta->sta_id, reason, +					  num_frames); +} + +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 = (void *)sta->drv_priv; + +	switch (cmd) { +	case STA_NOTIFY_SLEEP: +		if (atomic_read(&mvmsta->pending_frames) > 0) +			ieee80211_sta_block_awake(hw, sta, 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_INVALID_STATION)) +			break; +		iwl_mvm_sta_modify_ps_wake(mvm, mvmsta->sta_id); +		break; +	default: +		break; +	} +} + +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) { +		ret = iwl_mvm_add_sta(mvm, vif, sta); +	} else if (old_state == IEEE80211_STA_NONE && +		   new_state == IEEE80211_STA_AUTH) { +		ret = 0; +	} else if (old_state == IEEE80211_STA_AUTH && +		   new_state == IEEE80211_STA_ASSOC) { +		iwl_mvm_rs_rate_init(mvm, sta, mvmvif->phy_ctxt->channel->band); +		ret = 0; +	} else if (old_state == IEEE80211_STA_ASSOC && +		   new_state == IEEE80211_STA_AUTHORIZED) { +		ret = 0; +	} else if (old_state == IEEE80211_STA_AUTHORIZED && +		   new_state == IEEE80211_STA_ASSOC) { +		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); +	} else { +		ret = -EIO; +	} +	mutex_unlock(&mvm->mutex); + +	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 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); +		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; + +	mutex_lock(&mvm->mutex); +	/* Try really hard to protect the session and hear a beacon */ +	iwl_mvm_protect_session(mvm, vif, duration, min_duration); +	mutex_unlock(&mvm->mutex); +} + +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; + +	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; +		/* fall-through */ +	case WLAN_CIPHER_SUITE_CCMP: +		key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV; +		break; +	case WLAN_CIPHER_SUITE_AES_CMAC: +		WARN_ON_ONCE(!(hw->flags & IEEE80211_HW_MFP_CAPABLE)); +		break; +	case WLAN_CIPHER_SUITE_WEP40: +	case WLAN_CIPHER_SUITE_WEP104: +		/* +		 * Support for TX only, at least for now, so accept +		 * the key and do nothing else. Then mac80211 will +		 * pass it for TX but we don't have to use it for RX. +		 */ +		return 0; +	default: +		return -EOPNOTSUPP; +	} + +	mutex_lock(&mvm->mutex); + +	switch (cmd) { +	case SET_KEY: +		IWL_DEBUG_MAC80211(mvm, "set hwcrypto key\n"); +		ret = iwl_mvm_set_sta_key(mvm, vif, sta, key, false); +		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 +			 */ +			ret = 0; +		} + +		break; +	case DISABLE_KEY: +		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); + +	iwl_mvm_update_tkip_key(mvm, vif, keyconf, sta, iv32, phase1key); +} + + +static int iwl_mvm_roc(struct ieee80211_hw *hw, +		       struct ieee80211_vif *vif, +		       struct ieee80211_channel *channel, +		       int duration) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct cfg80211_chan_def chandef; +	int ret; + +	if (vif->type != NL80211_IFTYPE_P2P_DEVICE) { +		IWL_ERR(mvm, "vif isn't a P2P_DEVICE: %d\n", vif->type); +		return -EINVAL; +	} + +	IWL_DEBUG_MAC80211(mvm, "enter (%d, %d)\n", channel->hw_value, +			   duration); + +	mutex_lock(&mvm->mutex); + +	cfg80211_chandef_create(&chandef, channel, NL80211_CHAN_NO_HT); +	ret = iwl_mvm_phy_ctxt_changed(mvm, &mvm->phy_ctxt_roc, +				       &chandef, 1, 1); + +	/* Schedule the time events */ +	ret = iwl_mvm_start_p2p_roc(mvm, vif, duration); + +	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_p2p_roc(mvm); +	mutex_unlock(&mvm->mutex); + +	IWL_DEBUG_MAC80211(mvm, "leave\n"); +	return 0; +} + +static int iwl_mvm_add_chanctx(struct ieee80211_hw *hw, +			       struct ieee80211_chanctx_conf *ctx) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; +	int ret; + +	mutex_lock(&mvm->mutex); + +	IWL_DEBUG_MAC80211(mvm, "Add PHY context\n"); +	ret = iwl_mvm_phy_ctxt_add(mvm, phy_ctxt, &ctx->def, +				   ctx->rx_chains_static, +				   ctx->rx_chains_dynamic); +	mutex_unlock(&mvm->mutex); +	return ret; +} + +static void iwl_mvm_remove_chanctx(struct ieee80211_hw *hw, +				   struct ieee80211_chanctx_conf *ctx) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + +	mutex_lock(&mvm->mutex); +	iwl_mvm_phy_ctxt_remove(mvm, phy_ctxt); +	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); +	struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + +	mutex_lock(&mvm->mutex); +	iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx->def, +				 ctx->rx_chains_static, +				 ctx->rx_chains_dynamic); +	mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_assign_vif_chanctx(struct ieee80211_hw *hw, +				      struct ieee80211_vif *vif, +				      struct ieee80211_chanctx_conf *ctx) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_mvm_phy_ctxt *phyctx = (void *)ctx->drv_priv; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	int ret; + +	mutex_lock(&mvm->mutex); + +	mvmvif->phy_ctxt = phyctx; + +	switch (vif->type) { +	case NL80211_IFTYPE_AP: +		/* +		 * The AP binding flow is handled as part of the start_ap flow +		 * (in bss_info_changed). +		 */ +		ret = 0; +		goto out_unlock; +	case NL80211_IFTYPE_STATION: +	case NL80211_IFTYPE_ADHOC: +	case NL80211_IFTYPE_MONITOR: +		break; +	default: +		ret = -EINVAL; +		goto out_unlock; +	} + +	ret = iwl_mvm_binding_add_vif(mvm, vif); +	if (ret) +		goto out_unlock; + +	/* +	 * 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) { +		ret = iwl_mvm_update_quotas(mvm, vif); +		if (ret) +			goto out_remove_binding; +	} + +	goto out_unlock; + + out_remove_binding: +	iwl_mvm_binding_remove_vif(mvm, vif); + out_unlock: +	mutex_unlock(&mvm->mutex); +	if (ret) +		mvmvif->phy_ctxt = NULL; +	return ret; +} + +static void iwl_mvm_unassign_vif_chanctx(struct ieee80211_hw *hw, +					 struct ieee80211_vif *vif, +					 struct ieee80211_chanctx_conf *ctx) +{ +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	mutex_lock(&mvm->mutex); + +	iwl_mvm_remove_time_event(mvm, mvmvif, &mvmvif->time_event_data); + +	if (vif->type == NL80211_IFTYPE_AP) +		goto out_unlock; + +	iwl_mvm_binding_remove_vif(mvm, vif); +	switch (vif->type) { +	case NL80211_IFTYPE_MONITOR: +		iwl_mvm_update_quotas(mvm, vif); +		break; +	default: +		break; +	} + +out_unlock: +	mvmvif->phy_ctxt = NULL; +	mutex_unlock(&mvm->mutex); +} + +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 = (void *)sta->drv_priv; + +	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); +} + +struct ieee80211_ops iwl_mvm_hw_ops = { +	.tx = iwl_mvm_mac_tx, +	.ampdu_action = iwl_mvm_mac_ampdu_action, +	.start = iwl_mvm_mac_start, +	.restart_complete = iwl_mvm_mac_restart_complete, +	.stop = iwl_mvm_mac_stop, +	.add_interface = iwl_mvm_mac_add_interface, +	.remove_interface = iwl_mvm_mac_remove_interface, +	.config = iwl_mvm_mac_config, +	.configure_filter = iwl_mvm_configure_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_state = iwl_mvm_mac_sta_state, +	.sta_notify = iwl_mvm_mac_sta_notify, +	.allow_buffered_frames = iwl_mvm_mac_allow_buffered_frames, +	.set_rts_threshold = iwl_mvm_mac_set_rts_threshold, +	.conf_tx = iwl_mvm_mac_conf_tx, +	.mgd_prepare_tx = iwl_mvm_mac_mgd_prepare_tx, +	.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, + +	.start_ap = iwl_mvm_start_ap, +	.stop_ap = iwl_mvm_stop_ap, + +	.set_tim = iwl_mvm_set_tim, + +#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 +}; diff --git a/drivers/net/wireless/iwlwifi/mvm/mvm.h b/drivers/net/wireless/iwlwifi/mvm/mvm.h new file mode 100644 index 00000000000..4e339ccfa80 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/mvm.h @@ -0,0 +1,500 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __IWL_MVM_H__ +#define __IWL_MVM_H__ + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/leds.h> +#include <linux/in6.h> + +#include "iwl-op-mode.h" +#include "iwl-trans.h" +#include "iwl-notif-wait.h" +#include "iwl-eeprom-parse.h" +#include "iwl-test.h" +#include "iwl-trans.h" +#include "sta.h" +#include "fw-api.h" + +#define IWL_INVALID_MAC80211_QUEUE	0xff +#define IWL_MVM_MAX_ADDRESSES		2 +#define IWL_RSSI_OFFSET 44 + +enum iwl_mvm_tx_fifo { +	IWL_MVM_TX_FIFO_BK = 0, +	IWL_MVM_TX_FIFO_BE, +	IWL_MVM_TX_FIFO_VI, +	IWL_MVM_TX_FIFO_VO, +}; + +/* Placeholder */ +#define IWL_OFFCHANNEL_QUEUE 8 +#define IWL_FIRST_AMPDU_QUEUE 11 + +extern struct ieee80211_ops iwl_mvm_hw_ops; +/** + * struct iwl_mvm_mod_params - module parameters for iwlmvm + * @init_dbg: if true, then the NIC won't be stopped if the INIT fw asserted. + *	We will register to mac80211 to have testmode working. The NIC must not + *	be up'ed after the INIT fw asserted. This is useful to be able to use + *	proprietary tools over testmode to debug the INIT fw. + * @power_scheme: CAM(Continuous Active Mode)-1, BPS(Balanced Power + *	Save)-2(default), LP(Low Power)-3 + */ +struct iwl_mvm_mod_params { +	bool init_dbg; +	int power_scheme; +}; +extern struct iwl_mvm_mod_params iwlmvm_mod_params; + +struct iwl_mvm_phy_ctxt { +	u16 id; +	u16 color; + +	/* +	 * TODO: This should probably be removed. Currently here only for rate +	 * scaling algorithm +	 */ +	struct ieee80211_channel *channel; +}; + +struct iwl_mvm_time_event_data { +	struct ieee80211_vif *vif; +	struct list_head list; +	unsigned long end_jiffies; +	u32 duration; +	bool running; +	u32 uid; + +	/* +	 * The access to the 'id' field must be done when the +	 * mvm->time_event_lock is held, as it value is used to indicate +	 * if the te is in the time event list or not (when id == TE_MAX) +	 */ +	u32 id; +}; + + /* Power management */ + +/** + * enum iwl_power_scheme + * @IWL_POWER_LEVEL_CAM - Continuously Active Mode + * @IWL_POWER_LEVEL_BPS - Balanced Power Save (default) + * @IWL_POWER_LEVEL_LP  - Low Power + */ +enum iwl_power_scheme { +	IWL_POWER_SCHEME_CAM = 1, +	IWL_POWER_SCHEME_BPS, +	IWL_POWER_SCHEME_LP +}; + +#define IWL_CONN_MAX_LISTEN_INTERVAL	70 + +/** + * struct iwl_mvm_vif - data per Virtual Interface, it is a MAC context + * @id: between 0 and 3 + * @color: to solve races upon MAC addition and removal + * @ap_sta_id: the sta_id of the AP - valid only if VIF type is STA + * @uploaded: indicates the MAC context has been added to the device + * @ap_active: indicates that ap context is configured, and that the interface + *  should get quota etc. + * @queue_params: QoS params for this MAC + * @bcast_sta: station used for broadcast packets. Used by the following + *  vifs: P2P_DEVICE, GO and AP. + * @beacon_skb: the skb used to hold the AP/GO beacon template + */ +struct iwl_mvm_vif { +	u16 id; +	u16 color; +	u8 ap_sta_id; + +	bool uploaded; +	bool ap_active; + +	enum iwl_tsf_id tsf_id; + +	/* +	 * QoS data from mac80211, need to store this here +	 * as mac80211 has a separate callback but we need +	 * to have the data for the MAC context +	 */ +	struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS]; +	struct iwl_mvm_time_event_data time_event_data; + +	struct iwl_mvm_int_sta bcast_sta; + +	/* +	 * Assigned while mac80211 has the interface in a channel context, +	 * or, for P2P Device, while it exists. +	 */ +	struct iwl_mvm_phy_ctxt *phy_ctxt; + +#ifdef CONFIG_PM_SLEEP +	/* WoWLAN GTK rekey data */ +	struct { +		u8 kck[NL80211_KCK_LEN], kek[NL80211_KEK_LEN]; +		__le64 replay_ctr; +		bool valid; +	} rekey_data; + +	int tx_key_idx; + +#if IS_ENABLED(CONFIG_IPV6) +	/* IPv6 addresses for WoWLAN */ +	struct in6_addr target_ipv6_addrs[IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS]; +	int num_target_ipv6_addrs; +#endif +#endif + +#ifdef CONFIG_IWLWIFI_DEBUGFS +	struct dentry *dbgfs_dir; +	void *dbgfs_data; +#endif +}; + +static inline struct iwl_mvm_vif * +iwl_mvm_vif_from_mac80211(struct ieee80211_vif *vif) +{ +	return (void *)vif->drv_priv; +} + +enum iwl_mvm_status { +	IWL_MVM_STATUS_HW_RFKILL, +	IWL_MVM_STATUS_ROC_RUNNING, +	IWL_MVM_STATUS_IN_HW_RESTART, +}; + +enum iwl_scan_status { +	IWL_MVM_SCAN_NONE, +	IWL_MVM_SCAN_OS, +}; + +/** + * struct iwl_nvm_section - describes an NVM section in memory. + * + * This struct holds an NVM section read from the NIC using NVM_ACCESS_CMD, + * and saved for later use by the driver. Not all NVM sections are saved + * this way, only the needed ones. + */ +struct iwl_nvm_section { +	u16 length; +	const u8 *data; +}; + +struct iwl_mvm { +	/* for logger access */ +	struct device *dev; + +	struct iwl_trans *trans; +	const struct iwl_fw *fw; +	const struct iwl_cfg *cfg; +	struct iwl_phy_db *phy_db; +	struct ieee80211_hw *hw; + +	/* for protecting access to iwl_mvm */ +	struct mutex mutex; +	struct list_head async_handlers_list; +	spinlock_t async_handlers_lock; +	struct work_struct async_handlers_wk; + +	struct work_struct roc_done_wk; + +	unsigned long status; + +	enum iwl_ucode_type cur_ucode; +	bool ucode_loaded; +	bool init_ucode_run; +	u32 error_event_table; +	u32 log_event_table; + +	u32 ampdu_ref; + +	struct iwl_notif_wait_data notif_wait; + +	unsigned long transport_queue_stop; +	u8 queue_to_mac80211[IWL_MAX_HW_QUEUES]; +	atomic_t queue_stop_count[IWL_MAX_HW_QUEUES]; + +	struct iwl_nvm_data *nvm_data; +	/* eeprom blob for debugfs/testmode */ +	u8 *eeprom_blob; +	size_t eeprom_blob_size; +	/* NVM sections for 7000 family */ +	struct iwl_nvm_section nvm_sections[NVM_NUM_OF_SECTIONS]; + +	/* EEPROM MAC addresses */ +	struct mac_address addresses[IWL_MVM_MAX_ADDRESSES]; + +	/* data related to data path */ +	struct iwl_rx_phy_info last_phy_info; +	struct ieee80211_sta __rcu *fw_id_to_mac_id[IWL_MVM_STATION_COUNT]; +	struct work_struct sta_drained_wk; +	unsigned long sta_drained[BITS_TO_LONGS(IWL_MVM_STATION_COUNT)]; + +	/* configured by mac80211 */ +	u32 rts_threshold; + +	/* Scan status, cmd (pre-allocated) and auxiliary station */ +	enum iwl_scan_status scan_status; +	struct iwl_scan_cmd *scan_cmd; + +	/* Internal station */ +	struct iwl_mvm_int_sta aux_sta; + +	u8 scan_last_antenna_idx; /* to toggle TX between antennas */ +	u8 mgmt_last_antenna_idx; + +#ifdef CONFIG_IWLWIFI_DEBUGFS +	struct dentry *debugfs_dir; +	u32 dbgfs_sram_offset, dbgfs_sram_len; +	bool prevent_power_down_d3; +#endif + +	struct iwl_mvm_phy_ctxt phy_ctxt_roc; + +	struct list_head time_event_list; +	spinlock_t time_event_lock; + +	/* +	 * A bitmap indicating the index of the key in use. The firmware +	 * can hold 16 keys at most. Reflect this fact. +	 */ +	unsigned long fw_key_table[BITS_TO_LONGS(STA_KEY_MAX_NUM)]; +	u8 vif_count; + +	struct led_classdev led; + +	struct ieee80211_vif *p2p_device_vif; +}; + +/* Extract MVM priv from op_mode and _hw */ +#define IWL_OP_MODE_GET_MVM(_iwl_op_mode)		\ +	((struct iwl_mvm *)(_iwl_op_mode)->op_mode_specific) + +#define IWL_MAC80211_GET_MVM(_hw)			\ +	IWL_OP_MODE_GET_MVM((struct iwl_op_mode *)((_hw)->priv)) + +extern const u8 iwl_mvm_ac_to_tx_fifo[]; + +struct iwl_rate_info { +	u8 plcp;	/* uCode API:  IWL_RATE_6M_PLCP, etc. */ +	u8 plcp_siso;	/* uCode API:  IWL_RATE_SISO_6M_PLCP, etc. */ +	u8 plcp_mimo2;	/* uCode API:  IWL_RATE_MIMO2_6M_PLCP, etc. */ +	u8 plcp_mimo3;  /* uCode API:  IWL_RATE_MIMO3_6M_PLCP, etc. */ +	u8 ieee;	/* MAC header:  IWL_RATE_6M_IEEE, etc. */ +}; + +/****************** + * MVM Methods + ******************/ +/* uCode */ +int iwl_run_init_mvm_ucode(struct iwl_mvm *mvm, bool read_nvm); + +/* Utils */ +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, +					enum ieee80211_band band); +u8 iwl_mvm_mac80211_idx_to_hwrate(int rate_idx); +void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm); +u8 first_antenna(u8 mask); +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx); + +/* Tx / Host Commands */ +int __must_check iwl_mvm_send_cmd(struct iwl_mvm *mvm, +				  struct iwl_host_cmd *cmd); +int __must_check iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u8 id, +				      u32 flags, u16 len, const void *data); +int __must_check iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, +					 struct iwl_host_cmd *cmd, +					 u32 *status); +int __must_check iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u8 id, +					     u16 len, const void *data, +					     u32 *status); +int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, +		   struct ieee80211_sta *sta); +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb); +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status); +#else +static inline const char *iwl_mvm_get_tx_fail_reason(u32 status) { return ""; } +#endif +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk, bool sync); +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm); + +/* Statistics */ +int iwl_mvm_rx_reply_statistics(struct iwl_mvm *mvm, +				struct iwl_rx_cmd_buffer *rxb, +				struct iwl_device_cmd *cmd); +int iwl_mvm_rx_statistics(struct iwl_mvm *mvm, +			  struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd); + +/* NVM */ +int iwl_nvm_init(struct iwl_mvm *mvm); + +int iwl_mvm_up(struct iwl_mvm *mvm); +int iwl_mvm_load_d3_fw(struct iwl_mvm *mvm); + +int iwl_mvm_mac_setup_register(struct iwl_mvm *mvm); + +/* + * FW notifications / CMD responses handlers + * Convention: iwl_mvm_rx_<NAME OF THE CMD> + */ +int iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd); +int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		       struct iwl_device_cmd *cmd); +int iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		      struct iwl_device_cmd *cmd); +int iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			struct iwl_device_cmd *cmd); +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			 struct iwl_device_cmd *cmd); +int iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd); +int iwl_mvm_rx_card_state_notif(struct iwl_mvm *mvm, +				struct iwl_rx_cmd_buffer *rxb, +				struct iwl_device_cmd *cmd); +int iwl_mvm_rx_radio_ver(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			 struct iwl_device_cmd *cmd); + +/* MVM PHY */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, +			 struct cfg80211_chan_def *chandef, +			 u8 chains_static, u8 chains_dynamic); +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, +			     struct cfg80211_chan_def *chandef, +			     u8 chains_static, u8 chains_dynamic); +void iwl_mvm_phy_ctxt_remove(struct iwl_mvm *mvm, +			     struct iwl_mvm_phy_ctxt *ctxt); + +/* MAC (virtual interface) programming */ +int iwl_mvm_mac_ctxt_init(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +void iwl_mvm_mac_ctxt_release(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_add(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_remove(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +u32 iwl_mvm_mac_get_queues_mask(struct iwl_mvm *mvm, +				struct ieee80211_vif *vif); +int iwl_mvm_mac_ctxt_beacon_changed(struct iwl_mvm *mvm, +				    struct ieee80211_vif *vif); + +/* Bindings */ +int iwl_mvm_binding_add_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_binding_remove_vif(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +/* Quota management */ +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, struct ieee80211_vif *newvif); + +/* Scanning */ +int iwl_mvm_scan_request(struct iwl_mvm *mvm, +			 struct ieee80211_vif *vif, +			 struct cfg80211_scan_request *req); +int iwl_mvm_rx_scan_response(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			     struct iwl_device_cmd *cmd); +int iwl_mvm_rx_scan_complete(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			     struct iwl_device_cmd *cmd); +void iwl_mvm_cancel_scan(struct iwl_mvm *mvm); + +/* MVM debugfs */ +#ifdef CONFIG_IWLWIFI_DEBUGFS +int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, struct dentry *dbgfs_dir); +int iwl_mvm_vif_dbgfs_register(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			       struct dentry *dbgfs_dir); +void iwl_power_get_params(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			  struct iwl_powertable_cmd *cmd); +#else +static inline int iwl_mvm_dbgfs_register(struct iwl_mvm *mvm, +					 struct dentry *dbgfs_dir) +{ +	return 0; +} +#endif /* CONFIG_IWLWIFI_DEBUGFS */ + +/* rate scaling */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, +			u8 flags, bool init); + +/* power managment */ +int iwl_mvm_power_update_mode(struct iwl_mvm *mvm, struct ieee80211_vif *vif); +int iwl_mvm_power_disable(struct iwl_mvm *mvm, struct ieee80211_vif *vif); + +int iwl_mvm_leds_init(struct iwl_mvm *mvm); +void iwl_mvm_leds_exit(struct iwl_mvm *mvm); + +/* D3 (WoWLAN, NetDetect) */ +int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan); +int iwl_mvm_resume(struct ieee80211_hw *hw); +void iwl_mvm_set_wakeup(struct ieee80211_hw *hw, bool enabled); +void iwl_mvm_set_rekey_data(struct ieee80211_hw *hw, +			    struct ieee80211_vif *vif, +			    struct cfg80211_gtk_rekey_data *data); +void iwl_mvm_ipv6_addr_change(struct ieee80211_hw *hw, +			      struct ieee80211_vif *vif, +			      struct inet6_dev *idev); +void iwl_mvm_set_default_unicast_key(struct ieee80211_hw *hw, +				     struct ieee80211_vif *vif, int idx); + +#endif /* __IWL_MVM_H__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/nvm.c b/drivers/net/wireless/iwlwifi/mvm/nvm.c new file mode 100644 index 00000000000..20016bcbdea --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/nvm.c @@ -0,0 +1,311 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "iwl-trans.h" +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "iwl-eeprom-read.h" +#include "iwl-nvm-parse.h" + +/* list of NVM sections we are allowed/need to read */ +static const int nvm_to_read[] = { +	NVM_SECTION_TYPE_HW, +	NVM_SECTION_TYPE_SW, +	NVM_SECTION_TYPE_CALIBRATION, +	NVM_SECTION_TYPE_PRODUCTION, +}; + +/* used to simplify the shared operations on NCM_ACCESS_CMD versions */ +union iwl_nvm_access_cmd { +	struct iwl_nvm_access_cmd_ver1 ver1; +	struct iwl_nvm_access_cmd_ver2 ver2; +}; +union iwl_nvm_access_resp { +	struct iwl_nvm_access_resp_ver1 ver1; +	struct iwl_nvm_access_resp_ver2 ver2; +}; + +static inline void iwl_nvm_fill_read_ver1(struct iwl_nvm_access_cmd_ver1 *cmd, +					  u16 offset, u16 length) +{ +	cmd->offset = cpu_to_le16(offset); +	cmd->length = cpu_to_le16(length); +	cmd->cache_refresh = 1; +} + +static inline void iwl_nvm_fill_read_ver2(struct iwl_nvm_access_cmd_ver2 *cmd, +					  u16 offset, u16 length, u16 section) +{ +	cmd->offset = cpu_to_le16(offset); +	cmd->length = cpu_to_le16(length); +	cmd->type = cpu_to_le16(section); +} + +static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, +			      u16 offset, u16 length, u8 *data) +{ +	union iwl_nvm_access_cmd nvm_access_cmd; +	union iwl_nvm_access_resp *nvm_resp; +	struct iwl_rx_packet *pkt; +	struct iwl_host_cmd cmd = { +		.id = NVM_ACCESS_CMD, +		.flags = CMD_SYNC | CMD_WANT_SKB, +		.data = { &nvm_access_cmd, }, +	}; +	int ret, bytes_read, offset_read; +	u8 *resp_data; + +	memset(&nvm_access_cmd, 0, sizeof(nvm_access_cmd)); + +	/* TODO: not sure family should be the decider, maybe FW version? */ +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		iwl_nvm_fill_read_ver2(&(nvm_access_cmd.ver2), +				       offset, length, section); +		cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver2); +	} else { +		iwl_nvm_fill_read_ver1(&(nvm_access_cmd.ver1), +				       offset, length); +		cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver1); +	} + +	ret = iwl_mvm_send_cmd(mvm, &cmd); +	if (ret) +		return ret; + +	pkt = cmd.resp_pkt; +	if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { +		IWL_ERR(mvm, "Bad return from NVM_ACCES_COMMAND (0x%08X)\n", +			pkt->hdr.flags); +		ret = -EIO; +		goto exit; +	} + +	/* Extract NVM response */ +	nvm_resp = (void *)pkt->data; +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		ret = le16_to_cpu(nvm_resp->ver2.status); +		bytes_read = le16_to_cpu(nvm_resp->ver2.length); +		offset_read = le16_to_cpu(nvm_resp->ver2.offset); +		resp_data = nvm_resp->ver2.data; +	} else { +		ret = le16_to_cpu(nvm_resp->ver1.length) <= 0; +		bytes_read = le16_to_cpu(nvm_resp->ver1.length); +		offset_read = le16_to_cpu(nvm_resp->ver1.offset); +		resp_data = nvm_resp->ver1.data; +	} +	if (ret) { +		IWL_ERR(mvm, +			"NVM access command failed with status %d (device: %s)\n", +			ret, mvm->cfg->name); +		ret = -EINVAL; +		goto exit; +	} + +	if (offset_read != offset) { +		IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", +			offset_read); +		ret = -EINVAL; +		goto exit; +	} + +	/* Write data to NVM */ +	memcpy(data + offset, resp_data, bytes_read); +	ret = bytes_read; + +exit: +	iwl_free_resp(&cmd); +	return ret; +} + +/* + * Reads an NVM section completely. + * NICs prior to 7000 family doesn't have a real NVM, but just read + * section 0 which is the EEPROM. Because the EEPROM reading is unlimited + * by uCode, we need to manually check in this case that we don't + * overflow and try to read more than the EEPROM size. + * For 7000 family NICs, we supply the maximal size we can read, and + * the uCode fills the response with as much data as we can, + * without overflowing, so no check is needed. + */ +static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, +				u8 *data) +{ +	u16 length, offset = 0; +	int ret; +	bool old_eeprom = mvm->cfg->device_family != IWL_DEVICE_FAMILY_7000; + +	length = (iwlwifi_mod_params.amsdu_size_8K ? (8 * 1024) : (4 * 1024)) +		- sizeof(union iwl_nvm_access_cmd) +		- sizeof(struct iwl_rx_packet); +	/* +	 * if length is greater than EEPROM size, truncate it because uCode +	 * doesn't check it by itself, and exit the loop when reached. +	 */ +	if (old_eeprom && length > mvm->cfg->base_params->eeprom_size) +		length = mvm->cfg->base_params->eeprom_size; +	ret = length; + +	/* Read the NVM until exhausted (reading less than requested) */ +	while (ret == length) { +		ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); +		if (ret < 0) { +			IWL_ERR(mvm, +				"Cannot read NVM from section %d offset %d, length %d\n", +				section, offset, length); +			return ret; +		} +		offset += ret; +		if (old_eeprom && offset == mvm->cfg->base_params->eeprom_size) +			break; +	} + +	IWL_INFO(mvm, "NVM section %d read completed\n", section); +	return offset; +} + +static struct iwl_nvm_data * +iwl_parse_nvm_sections(struct iwl_mvm *mvm) +{ +	struct iwl_nvm_section *sections = mvm->nvm_sections; +	const __le16 *hw, *sw, *calib; + +	/* Checking for required sections */ +	if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || +	    !mvm->nvm_sections[NVM_SECTION_TYPE_HW].data) { +		IWL_ERR(mvm, "Can't parse empty NVM sections\n"); +		return NULL; +	} + +	if (WARN_ON(!mvm->cfg)) +		return NULL; + +	hw = (const __le16 *)sections[NVM_SECTION_TYPE_HW].data; +	sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; +	calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; +	return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); +} + +int iwl_nvm_init(struct iwl_mvm *mvm) +{ +	int ret, i, section; +	u8 *nvm_buffer, *temp; + +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { +		/* TODO: find correct NVM max size for a section */ +		nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, +				     GFP_KERNEL); +		if (!nvm_buffer) +			return -ENOMEM; +		for (i = 0; i < ARRAY_SIZE(nvm_to_read); i++) { +			section = nvm_to_read[i]; +			/* we override the constness for initial read */ +			ret = iwl_nvm_read_section(mvm, section, nvm_buffer); +			if (ret < 0) +				break; +			temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); +			if (!temp) { +				ret = -ENOMEM; +				break; +			} +			mvm->nvm_sections[section].data = temp; +			mvm->nvm_sections[section].length = ret; +		} +		kfree(nvm_buffer); +		if (ret < 0) +			return ret; +	} else { +		/* allocate eeprom */ +		mvm->eeprom_blob_size = mvm->cfg->base_params->eeprom_size; +		IWL_DEBUG_EEPROM(mvm->trans->dev, "NVM size = %zd\n", +				 mvm->eeprom_blob_size); +		mvm->eeprom_blob = kzalloc(mvm->eeprom_blob_size, GFP_KERNEL); +		if (!mvm->eeprom_blob) +			return -ENOMEM; + +		ret = iwl_nvm_read_section(mvm, 0, mvm->eeprom_blob); +		if (ret != mvm->eeprom_blob_size) { +			IWL_ERR(mvm, "Read partial NVM %d/%zd\n", +				ret, mvm->eeprom_blob_size); +			kfree(mvm->eeprom_blob); +			mvm->eeprom_blob = NULL; +			return -EINVAL; +		} +	} + +	ret = 0; +	if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) +		mvm->nvm_data = iwl_parse_nvm_sections(mvm); +	else +		mvm->nvm_data = +			iwl_parse_eeprom_data(mvm->trans->dev, +					      mvm->cfg, +					      mvm->eeprom_blob, +					      mvm->eeprom_blob_size); + +	if (!mvm->nvm_data) { +		kfree(mvm->eeprom_blob); +		mvm->eeprom_blob = NULL; +		ret = -ENOMEM; +	} + +	return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/ops.c b/drivers/net/wireless/iwlwifi/mvm/ops.c new file mode 100644 index 00000000000..e675e4a6707 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/ops.c @@ -0,0 +1,679 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 <net/mac80211.h> + +#include "iwl-notif-wait.h" +#include "iwl-trans.h" +#include "iwl-op-mode.h" +#include "iwl-fw.h" +#include "iwl-debug.h" +#include "iwl-drv.h" +#include "iwl-modparams.h" +#include "mvm.h" +#include "iwl-phy-db.h" +#include "iwl-eeprom-parse.h" +#include "iwl-csr.h" +#include "iwl-io.h" +#include "iwl-prph.h" +#include "rs.h" +#include "fw-api-scan.h" +#include "time-event.h" + +/* + * module name, copyright, version, etc. + */ +#define DRV_DESCRIPTION	"The new Intel(R) wireless AGN driver for Linux" + +#define DRV_VERSION     IWLWIFI_VERSION + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR(DRV_COPYRIGHT " " DRV_AUTHOR); +MODULE_LICENSE("GPL"); + +static const struct iwl_op_mode_ops iwl_mvm_ops; + +struct iwl_mvm_mod_params iwlmvm_mod_params = { +	.power_scheme = IWL_POWER_SCHEME_BPS, +	/* rest of fields are 0 by default */ +}; + +module_param_named(init_dbg, iwlmvm_mod_params.init_dbg, bool, S_IRUGO); +MODULE_PARM_DESC(init_dbg, +		 "set to true to debug an ASSERT in INIT fw (default: false"); +module_param_named(power_scheme, iwlmvm_mod_params.power_scheme, int, S_IRUGO); +MODULE_PARM_DESC(power_scheme, +		 "power management scheme: 1-active, 2-balanced, 3-low power, default: 2"); + +/* + * module init and exit functions + */ +static int __init iwl_mvm_init(void) +{ +	int ret; + +	ret = iwl_mvm_rate_control_register(); +	if (ret) { +		pr_err("Unable to register rate control algorithm: %d\n", ret); +		return ret; +	} + +	ret = iwl_opmode_register("iwlmvm", &iwl_mvm_ops); + +	if (ret) { +		pr_err("Unable to register MVM op_mode: %d\n", ret); +		iwl_mvm_rate_control_unregister(); +	} + +	return ret; +} +module_init(iwl_mvm_init); + +static void __exit iwl_mvm_exit(void) +{ +	iwl_opmode_deregister("iwlmvm"); +	iwl_mvm_rate_control_unregister(); +} +module_exit(iwl_mvm_exit); + +static void iwl_mvm_nic_config(struct iwl_op_mode *op_mode) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	u8 radio_cfg_type, radio_cfg_step, radio_cfg_dash; +	u32 reg_val = 0; + +	/* +	 * We can't upload the correct value to the INIT image +	 * as we don't have nvm_data by that time. +	 * +	 * TODO: Figure out what we should do here +	 */ +	if (mvm->nvm_data) { +		radio_cfg_type = mvm->nvm_data->radio_cfg_type; +		radio_cfg_step = mvm->nvm_data->radio_cfg_step; +		radio_cfg_dash = mvm->nvm_data->radio_cfg_dash; +	} else { +		radio_cfg_type = 0; +		radio_cfg_step = 0; +		radio_cfg_dash = 0; +	} + +	/* SKU control */ +	reg_val |= CSR_HW_REV_STEP(mvm->trans->hw_rev) << +				CSR_HW_IF_CONFIG_REG_POS_MAC_STEP; +	reg_val |= CSR_HW_REV_DASH(mvm->trans->hw_rev) << +				CSR_HW_IF_CONFIG_REG_POS_MAC_DASH; + +	/* radio configuration */ +	reg_val |= radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE; +	reg_val |= radio_cfg_step << CSR_HW_IF_CONFIG_REG_POS_PHY_STEP; +	reg_val |= radio_cfg_dash << CSR_HW_IF_CONFIG_REG_POS_PHY_DASH; + +	WARN_ON((radio_cfg_type << CSR_HW_IF_CONFIG_REG_POS_PHY_TYPE) & +		 ~CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE); + +	/* silicon bits */ +	reg_val |= CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI; +	reg_val |= CSR_HW_IF_CONFIG_REG_BIT_MAC_SI; + +	iwl_set_bits_mask(mvm->trans, CSR_HW_IF_CONFIG_REG, +			  CSR_HW_IF_CONFIG_REG_MSK_MAC_DASH | +			  CSR_HW_IF_CONFIG_REG_MSK_MAC_STEP | +			  CSR_HW_IF_CONFIG_REG_MSK_PHY_TYPE | +			  CSR_HW_IF_CONFIG_REG_MSK_PHY_STEP | +			  CSR_HW_IF_CONFIG_REG_MSK_PHY_DASH | +			  CSR_HW_IF_CONFIG_REG_BIT_RADIO_SI | +			  CSR_HW_IF_CONFIG_REG_BIT_MAC_SI, +			  reg_val); + +	IWL_DEBUG_INFO(mvm, "Radio type=0x%x-0x%x-0x%x\n", radio_cfg_type, +		       radio_cfg_step, radio_cfg_dash); + +	/* +	 * W/A : NIC is stuck in a reset state after Early PCIe power off +	 * (PCIe power is lost before PERST# is asserted), causing ME FW +	 * to lose ownership and not being able to obtain it back. +	 */ +	iwl_set_bits_mask_prph(mvm->trans, APMG_PS_CTRL_REG, +			       APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS, +			       ~APMG_PS_CTRL_EARLY_PWR_OFF_RESET_DIS); +} + +struct iwl_rx_handlers { +	u8 cmd_id; +	bool async; +	int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		  struct iwl_device_cmd *cmd); +}; + +#define RX_HANDLER(_cmd_id, _fn, _async)	\ +	{ .cmd_id = _cmd_id , .fn = _fn , .async = _async } + +/* + * Handlers for fw notifications + * Convention: RX_HANDLER(CMD_NAME, iwl_mvm_rx_CMD_NAME + * This list should be in order of frequency for performance purposes. + * + * The handler can be SYNC - this means that it will be called in the Rx path + * which can't acquire mvm->mutex. If the handler needs to hold mvm->mutex (and + * only in this case!), it should be set as ASYNC. In that case, it will be + * called from a worker with mvm->mutex held. + */ +static const struct iwl_rx_handlers iwl_mvm_rx_handlers[] = { +	RX_HANDLER(REPLY_RX_MPDU_CMD, iwl_mvm_rx_rx_mpdu, false), +	RX_HANDLER(REPLY_RX_PHY_CMD, iwl_mvm_rx_rx_phy_cmd, false), +	RX_HANDLER(TX_CMD, iwl_mvm_rx_tx_cmd, false), +	RX_HANDLER(BA_NOTIF, iwl_mvm_rx_ba_notif, false), +	RX_HANDLER(TIME_EVENT_NOTIFICATION, iwl_mvm_rx_time_event_notif, false), + +	RX_HANDLER(SCAN_REQUEST_CMD, iwl_mvm_rx_scan_response, false), +	RX_HANDLER(SCAN_COMPLETE_NOTIFICATION, iwl_mvm_rx_scan_complete, false), + +	RX_HANDLER(RADIO_VERSION_NOTIFICATION, iwl_mvm_rx_radio_ver, false), +	RX_HANDLER(CARD_STATE_NOTIFICATION, iwl_mvm_rx_card_state_notif, false), + +	RX_HANDLER(REPLY_ERROR, iwl_mvm_rx_fw_error, false), +}; +#undef RX_HANDLER +#define CMD(x) [x] = #x + +static const char *iwl_mvm_cmd_strings[REPLY_MAX] = { +	CMD(MVM_ALIVE), +	CMD(REPLY_ERROR), +	CMD(INIT_COMPLETE_NOTIF), +	CMD(PHY_CONTEXT_CMD), +	CMD(MGMT_MCAST_KEY), +	CMD(TX_CMD), +	CMD(TXPATH_FLUSH), +	CMD(MAC_CONTEXT_CMD), +	CMD(TIME_EVENT_CMD), +	CMD(TIME_EVENT_NOTIFICATION), +	CMD(BINDING_CONTEXT_CMD), +	CMD(TIME_QUOTA_CMD), +	CMD(RADIO_VERSION_NOTIFICATION), +	CMD(SCAN_REQUEST_CMD), +	CMD(SCAN_ABORT_CMD), +	CMD(SCAN_START_NOTIFICATION), +	CMD(SCAN_RESULTS_NOTIFICATION), +	CMD(SCAN_COMPLETE_NOTIFICATION), +	CMD(NVM_ACCESS_CMD), +	CMD(PHY_CONFIGURATION_CMD), +	CMD(CALIB_RES_NOTIF_PHY_DB), +	CMD(SET_CALIB_DEFAULT_CMD), +	CMD(CALIBRATION_COMPLETE_NOTIFICATION), +	CMD(ADD_STA), +	CMD(REMOVE_STA), +	CMD(LQ_CMD), +	CMD(SCAN_OFFLOAD_CONFIG_CMD), +	CMD(SCAN_OFFLOAD_REQUEST_CMD), +	CMD(SCAN_OFFLOAD_ABORT_CMD), +	CMD(SCAN_OFFLOAD_COMPLETE), +	CMD(SCAN_OFFLOAD_UPDATE_PROFILES_CMD), +	CMD(POWER_TABLE_CMD), +	CMD(WEP_KEY), +	CMD(REPLY_RX_PHY_CMD), +	CMD(REPLY_RX_MPDU_CMD), +	CMD(BEACON_TEMPLATE_CMD), +	CMD(STATISTICS_NOTIFICATION), +	CMD(TX_ANT_CONFIGURATION_CMD), +	CMD(D3_CONFIG_CMD), +	CMD(PROT_OFFLOAD_CONFIG_CMD), +	CMD(OFFLOADS_QUERY_CMD), +	CMD(REMOTE_WAKE_CONFIG_CMD), +	CMD(WOWLAN_PATTERNS), +	CMD(WOWLAN_CONFIGURATION), +	CMD(WOWLAN_TSC_RSC_PARAM), +	CMD(WOWLAN_TKIP_PARAM), +	CMD(WOWLAN_KEK_KCK_MATERIAL), +	CMD(WOWLAN_GET_STATUSES), +	CMD(WOWLAN_TX_POWER_PER_DB), +	CMD(NET_DETECT_CONFIG_CMD), +	CMD(NET_DETECT_PROFILES_QUERY_CMD), +	CMD(NET_DETECT_PROFILES_CMD), +	CMD(NET_DETECT_HOTSPOTS_CMD), +	CMD(NET_DETECT_HOTSPOTS_QUERY_CMD), +}; +#undef CMD + +/* this forward declaration can avoid to export the function */ +static void iwl_mvm_async_handlers_wk(struct work_struct *wk); + +static struct iwl_op_mode * +iwl_op_mode_mvm_start(struct iwl_trans *trans, const struct iwl_cfg *cfg, +		      const struct iwl_fw *fw, struct dentry *dbgfs_dir) +{ +	struct ieee80211_hw *hw; +	struct iwl_op_mode *op_mode; +	struct iwl_mvm *mvm; +	struct iwl_trans_config trans_cfg = {}; +	static const u8 no_reclaim_cmds[] = { +		TX_CMD, +	}; +	int err, scan_size; + +	switch (cfg->device_family) { +	case IWL_DEVICE_FAMILY_6030: +	case IWL_DEVICE_FAMILY_6005: +	case IWL_DEVICE_FAMILY_7000: +		break; +	default: +		IWL_ERR(trans, "Trying to load mvm on an unsupported device\n"); +		return NULL; +	} + +	/******************************** +	 * 1. Allocating and configuring HW data +	 ********************************/ +	hw = ieee80211_alloc_hw(sizeof(struct iwl_op_mode) + +				sizeof(struct iwl_mvm), +				&iwl_mvm_hw_ops); +	if (!hw) +		return NULL; + +	op_mode = hw->priv; +	op_mode->ops = &iwl_mvm_ops; +	op_mode->trans = trans; + +	mvm = IWL_OP_MODE_GET_MVM(op_mode); +	mvm->dev = trans->dev; +	mvm->trans = trans; +	mvm->cfg = cfg; +	mvm->fw = fw; +	mvm->hw = hw; + +	mutex_init(&mvm->mutex); +	spin_lock_init(&mvm->async_handlers_lock); +	INIT_LIST_HEAD(&mvm->time_event_list); +	INIT_LIST_HEAD(&mvm->async_handlers_list); +	spin_lock_init(&mvm->time_event_lock); + +	INIT_WORK(&mvm->async_handlers_wk, iwl_mvm_async_handlers_wk); +	INIT_WORK(&mvm->roc_done_wk, iwl_mvm_roc_done_wk); +	INIT_WORK(&mvm->sta_drained_wk, iwl_mvm_sta_drained_wk); + +	SET_IEEE80211_DEV(mvm->hw, mvm->trans->dev); + +	/* +	 * Populate the state variables that the transport layer needs +	 * to know about. +	 */ +	trans_cfg.op_mode = op_mode; +	trans_cfg.no_reclaim_cmds = no_reclaim_cmds; +	trans_cfg.n_no_reclaim_cmds = ARRAY_SIZE(no_reclaim_cmds); +	trans_cfg.rx_buf_size_8k = iwlwifi_mod_params.amsdu_size_8K; + +	/* TODO: this should really be a TLV */ +	if (cfg->device_family == IWL_DEVICE_FAMILY_7000) +		trans_cfg.bc_table_dword = true; + +	if (!iwlwifi_mod_params.wd_disable) +		trans_cfg.queue_watchdog_timeout = cfg->base_params->wd_timeout; +	else +		trans_cfg.queue_watchdog_timeout = IWL_WATCHDOG_DISABLED; + +	trans_cfg.command_names = iwl_mvm_cmd_strings; + +	trans_cfg.cmd_queue = IWL_MVM_CMD_QUEUE; +	trans_cfg.cmd_fifo = IWL_MVM_CMD_FIFO; + +	snprintf(mvm->hw->wiphy->fw_version, +		 sizeof(mvm->hw->wiphy->fw_version), +		 "%s", fw->fw_version); + +	/* Configure transport layer */ +	iwl_trans_configure(mvm->trans, &trans_cfg); + +	trans->rx_mpdu_cmd = REPLY_RX_MPDU_CMD; +	trans->rx_mpdu_cmd_hdr_size = sizeof(struct iwl_rx_mpdu_res_start); + +	/* set up notification wait support */ +	iwl_notification_wait_init(&mvm->notif_wait); + +	/* Init phy db */ +	mvm->phy_db = iwl_phy_db_init(trans); +	if (!mvm->phy_db) { +		IWL_ERR(mvm, "Cannot init phy_db\n"); +		goto out_free; +	} + +	IWL_INFO(mvm, "Detected %s, REV=0x%X\n", +		 mvm->cfg->name, mvm->trans->hw_rev); + +	err = iwl_trans_start_hw(mvm->trans); +	if (err) +		goto out_free; + +	mutex_lock(&mvm->mutex); +	err = iwl_run_init_mvm_ucode(mvm, true); +	mutex_unlock(&mvm->mutex); +	if (err && !iwlmvm_mod_params.init_dbg) { +		IWL_ERR(mvm, "Failed to run INIT ucode: %d\n", err); +		goto out_free; +	} + +	/* Stop the hw after the ALIVE and NVM has been read */ +	if (!iwlmvm_mod_params.init_dbg) +		iwl_trans_stop_hw(mvm->trans, false); + +	scan_size = sizeof(struct iwl_scan_cmd) + +		mvm->fw->ucode_capa.max_probe_length + +		(MAX_NUM_SCAN_CHANNELS * sizeof(struct iwl_scan_channel)); +	mvm->scan_cmd = kmalloc(scan_size, GFP_KERNEL); +	if (!mvm->scan_cmd) +		goto out_free; + +	err = iwl_mvm_mac_setup_register(mvm); +	if (err) +		goto out_free; + +	err = iwl_mvm_dbgfs_register(mvm, dbgfs_dir); +	if (err) +		goto out_unregister; + +	return op_mode; + + out_unregister: +	ieee80211_unregister_hw(mvm->hw); + out_free: +	iwl_phy_db_free(mvm->phy_db); +	kfree(mvm->scan_cmd); +	kfree(mvm->eeprom_blob); +	iwl_trans_stop_hw(trans, true); +	ieee80211_free_hw(mvm->hw); +	return NULL; +} + +static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	int i; + +	iwl_mvm_leds_exit(mvm); + +	ieee80211_unregister_hw(mvm->hw); + +	kfree(mvm->scan_cmd); + +	iwl_trans_stop_hw(mvm->trans, true); + +	iwl_phy_db_free(mvm->phy_db); +	mvm->phy_db = NULL; + +	kfree(mvm->eeprom_blob); +	iwl_free_nvm_data(mvm->nvm_data); +	for (i = 0; i < NVM_NUM_OF_SECTIONS; i++) +		kfree(mvm->nvm_sections[i].data); + +	ieee80211_free_hw(mvm->hw); +} + +struct iwl_async_handler_entry { +	struct list_head list; +	struct iwl_rx_cmd_buffer rxb; +	int (*fn)(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		  struct iwl_device_cmd *cmd); +}; + +void iwl_mvm_async_handlers_purge(struct iwl_mvm *mvm) +{ +	struct iwl_async_handler_entry *entry, *tmp; + +	spin_lock_bh(&mvm->async_handlers_lock); +	list_for_each_entry_safe(entry, tmp, &mvm->async_handlers_list, list) { +		iwl_free_rxb(&entry->rxb); +		list_del(&entry->list); +		kfree(entry); +	} +	spin_unlock_bh(&mvm->async_handlers_lock); +} + +static void iwl_mvm_async_handlers_wk(struct work_struct *wk) +{ +	struct iwl_mvm *mvm = +		container_of(wk, struct iwl_mvm, async_handlers_wk); +	struct iwl_async_handler_entry *entry, *tmp; +	struct list_head local_list; + +	INIT_LIST_HEAD(&local_list); + +	/* Ensure that we are not in stop flow (check iwl_mvm_mac_stop) */ +	mutex_lock(&mvm->mutex); + +	/* +	 * Sync with Rx path with a lock. Remove all the entries from this list, +	 * add them to a local one (lock free), and then handle them. +	 */ +	spin_lock_bh(&mvm->async_handlers_lock); +	list_splice_init(&mvm->async_handlers_list, &local_list); +	spin_unlock_bh(&mvm->async_handlers_lock); + +	list_for_each_entry_safe(entry, tmp, &local_list, list) { +		if (entry->fn(mvm, &entry->rxb, NULL)) +			IWL_WARN(mvm, +				 "returned value from ASYNC handlers are ignored\n"); +		iwl_free_rxb(&entry->rxb); +		list_del(&entry->list); +		kfree(entry); +	} +	mutex_unlock(&mvm->mutex); +} + +static int iwl_mvm_rx_dispatch(struct iwl_op_mode *op_mode, +			       struct iwl_rx_cmd_buffer *rxb, +			       struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	u8 i; + +	/* +	 * Do the notification wait before RX handlers so +	 * even if the RX handler consumes the RXB we have +	 * access to it in the notification wait entry. +	 */ +	iwl_notification_wait_notify(&mvm->notif_wait, pkt); + +	for (i = 0; i < ARRAY_SIZE(iwl_mvm_rx_handlers); i++) { +		const struct iwl_rx_handlers *rx_h = &iwl_mvm_rx_handlers[i]; +		if (rx_h->cmd_id == pkt->hdr.cmd) { +			struct iwl_async_handler_entry *entry; +			if (!rx_h->async) +				return rx_h->fn(mvm, rxb, cmd); + +			entry = kzalloc(sizeof(*entry), GFP_ATOMIC); +			/* we can't do much... */ +			if (!entry) +				return 0; + +			entry->rxb._page = rxb_steal_page(rxb); +			entry->rxb._offset = rxb->_offset; +			entry->rxb._rx_page_order = rxb->_rx_page_order; +			entry->fn = rx_h->fn; +			spin_lock(&mvm->async_handlers_lock); +			list_add_tail(&entry->list, &mvm->async_handlers_list); +			spin_unlock(&mvm->async_handlers_lock); +			schedule_work(&mvm->async_handlers_wk); +		} +	} + +	return 0; +} + +static void iwl_mvm_stop_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	int mq = mvm->queue_to_mac80211[queue]; + +	if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) +		return; + +	if (atomic_inc_return(&mvm->queue_stop_count[mq]) > 1) { +		IWL_DEBUG_TX_QUEUES(mvm, +				    "queue %d (mac80211 %d) already stopped\n", +				    queue, mq); +		return; +	} + +	set_bit(mq, &mvm->transport_queue_stop); +	ieee80211_stop_queue(mvm->hw, mq); +} + +static void iwl_mvm_wake_sw_queue(struct iwl_op_mode *op_mode, int queue) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	int mq = mvm->queue_to_mac80211[queue]; + +	if (WARN_ON_ONCE(mq == IWL_INVALID_MAC80211_QUEUE)) +		return; + +	if (atomic_dec_return(&mvm->queue_stop_count[mq]) > 0) { +		IWL_DEBUG_TX_QUEUES(mvm, +				    "queue %d (mac80211 %d) already awake\n", +				    queue, mq); +		return; +	} + +	clear_bit(mq, &mvm->transport_queue_stop); + +	ieee80211_wake_queue(mvm->hw, mq); +} + +static void iwl_mvm_set_hw_rfkill_state(struct iwl_op_mode *op_mode, bool state) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + +	if (state) +		set_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); +	else +		clear_bit(IWL_MVM_STATUS_HW_RFKILL, &mvm->status); + +	wiphy_rfkill_set_hw_state(mvm->hw->wiphy, state); +} + +static void iwl_mvm_free_skb(struct iwl_op_mode *op_mode, struct sk_buff *skb) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	struct ieee80211_tx_info *info; + +	info = IEEE80211_SKB_CB(skb); +	iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); +	ieee80211_free_txskb(mvm->hw, skb); +} + +static void iwl_mvm_nic_error(struct iwl_op_mode *op_mode) +{ +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); + +	iwl_mvm_dump_nic_error_log(mvm); + +	iwl_abort_notification_waits(&mvm->notif_wait); + +	/* +	 * If we're restarting already, don't cycle restarts. +	 * If INIT fw asserted, it will likely fail again. +	 * If WoWLAN fw asserted, don't restart either, mac80211 +	 * can't recover this since we're already half suspended. +	 */ +	if (test_and_set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { +		IWL_ERR(mvm, "Firmware error during reconfiguration! Abort.\n"); +	} else if (mvm->cur_ucode == IWL_UCODE_REGULAR && +		   iwlwifi_mod_params.restart_fw) { +		/* +		 * This is a bit racy, but worst case we tell mac80211 about +		 * a stopped/aborted (sched) scan when that was already done +		 * which is not a problem. It is necessary to abort any scan +		 * here because mac80211 requires having the scan cleared +		 * before restarting. +		 * We'll reset the scan_status to NONE in restart cleanup in +		 * the next start() call from mac80211. +		 */ +		switch (mvm->scan_status) { +		case IWL_MVM_SCAN_NONE: +			break; +		case IWL_MVM_SCAN_OS: +			ieee80211_scan_completed(mvm->hw, true); +			break; +		} + +		ieee80211_restart_hw(mvm->hw); +	} +} + +static void iwl_mvm_cmd_queue_full(struct iwl_op_mode *op_mode) +{ +	WARN_ON(1); +} + +static const struct iwl_op_mode_ops iwl_mvm_ops = { +	.start = iwl_op_mode_mvm_start, +	.stop = iwl_op_mode_mvm_stop, +	.rx = iwl_mvm_rx_dispatch, +	.queue_full = iwl_mvm_stop_sw_queue, +	.queue_not_full = iwl_mvm_wake_sw_queue, +	.hw_rf_kill = iwl_mvm_set_hw_rfkill_state, +	.free_skb = iwl_mvm_free_skb, +	.nic_error = iwl_mvm_nic_error, +	.cmd_queue_full = iwl_mvm_cmd_queue_full, +	.nic_config = iwl_mvm_nic_config, +}; diff --git a/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c b/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c new file mode 100644 index 00000000000..b428448f8dd --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/phy-ctxt.c @@ -0,0 +1,292 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "fw-api.h" +#include "mvm.h" + +/* Maps the driver specific channel width definition to the the fw values */ +static inline u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef) +{ +	switch (chandef->width) { +	case NL80211_CHAN_WIDTH_20_NOHT: +	case NL80211_CHAN_WIDTH_20: +		return PHY_VHT_CHANNEL_MODE20; +	case NL80211_CHAN_WIDTH_40: +		return PHY_VHT_CHANNEL_MODE40; +	case NL80211_CHAN_WIDTH_80: +		return PHY_VHT_CHANNEL_MODE80; +	case NL80211_CHAN_WIDTH_160: +		return PHY_VHT_CHANNEL_MODE160; +	default: +		WARN(1, "Invalid channel width=%u", chandef->width); +		return PHY_VHT_CHANNEL_MODE20; +	} +} + +/* + * Maps the driver specific control channel position (relative to the center + * freq) definitions to the the fw values + */ +static inline u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef) +{ +	switch (chandef->chan->center_freq - chandef->center_freq1) { +	case -70: +		return PHY_VHT_CTRL_POS_4_BELOW; +	case -50: +		return PHY_VHT_CTRL_POS_3_BELOW; +	case -30: +		return PHY_VHT_CTRL_POS_2_BELOW; +	case -10: +		return PHY_VHT_CTRL_POS_1_BELOW; +	case  10: +		return PHY_VHT_CTRL_POS_1_ABOVE; +	case  30: +		return PHY_VHT_CTRL_POS_2_ABOVE; +	case  50: +		return PHY_VHT_CTRL_POS_3_ABOVE; +	case  70: +		return PHY_VHT_CTRL_POS_4_ABOVE; +	default: +		WARN(1, "Invalid channel definition"); +	case 0: +		/* +		 * The FW is expected to check the control channel position only +		 * when in HT/VHT and the channel width is not 20MHz. Return +		 * this value as the default one. +		 */ +		return PHY_VHT_CTRL_POS_1_BELOW; +	} +} + +/* + * Construct the generic fields of the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt *ctxt, +				     struct iwl_phy_context_cmd *cmd, +				     u32 action, u32 apply_time) +{ +	memset(cmd, 0, sizeof(struct iwl_phy_context_cmd)); + +	cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, +							    ctxt->color)); +	cmd->action = cpu_to_le32(action); +	cmd->apply_time = cpu_to_le32(apply_time); +} + +/* + * Add the phy configuration to the PHY context command + */ +static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm *mvm, +				      struct iwl_phy_context_cmd *cmd, +				      struct cfg80211_chan_def *chandef, +				      u8 chains_static, u8 chains_dynamic) +{ +	u8 valid_rx_chains, active_cnt, idle_cnt; + +	/* Set the channel info data */ +	cmd->ci.band = (chandef->chan->band == IEEE80211_BAND_2GHZ ? +	      PHY_BAND_24 : PHY_BAND_5); + +	cmd->ci.channel = chandef->chan->hw_value; +	cmd->ci.width = iwl_mvm_get_channel_width(chandef); +	cmd->ci.ctrl_pos = iwl_mvm_get_ctrl_pos(chandef); + +	/* Set rx the chains */ + +	/* TODO: +	 * Need to add on chain noise calibration limitations, and +	 * BT coex considerations. +	 */ +	valid_rx_chains = mvm->nvm_data->valid_rx_ant; +	idle_cnt = chains_static; +	active_cnt = chains_dynamic; + +	cmd->rxchain_info = cpu_to_le32(valid_rx_chains << +					PHY_RX_CHAIN_VALID_POS); +	cmd->rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); +	cmd->rxchain_info |= cpu_to_le32(active_cnt << +					 PHY_RX_CHAIN_MIMO_CNT_POS); + +	cmd->txchain_info = cpu_to_le32(mvm->nvm_data->valid_tx_ant); +} + +/* + * Send a command to apply the current phy configuration. The command is send + * only if something in the configuration changed: in case that this is the + * first time that the phy configuration is applied or in case that the phy + * configuration changed from the previous apply. + */ +static int iwl_mvm_phy_ctxt_apply(struct iwl_mvm *mvm, +				  struct iwl_mvm_phy_ctxt *ctxt, +				  struct cfg80211_chan_def *chandef, +				  u8 chains_static, u8 chains_dynamic, +				  u32 action, u32 apply_time) +{ +	struct iwl_phy_context_cmd cmd; +	int ret; + +	/* Set the command header fields */ +	iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action, apply_time); + +	/* Set the command data */ +	iwl_mvm_phy_ctxt_cmd_data(mvm, &cmd, chandef, +				  chains_static, chains_dynamic); + +	ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, CMD_SYNC, +				   sizeof(struct iwl_phy_context_cmd), +				   &cmd); +	if (ret) +		IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); +	return ret; +} + + +struct phy_ctx_used_data { +	unsigned long used[BITS_TO_LONGS(NUM_PHY_CTX)]; +}; + +static void iwl_mvm_phy_ctx_used_iter(struct ieee80211_hw *hw, +				      struct ieee80211_chanctx_conf *ctx, +				      void *_data) +{ +	struct phy_ctx_used_data *data = _data; +	struct iwl_mvm_phy_ctxt *phy_ctxt = (void *)ctx->drv_priv; + +	__set_bit(phy_ctxt->id, data->used); +} + +/* + * Send a command to add a PHY context based on the current HW configuration. + */ +int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, +			 struct cfg80211_chan_def *chandef, +			 u8 chains_static, u8 chains_dynamic) +{ +	struct phy_ctx_used_data data = { +		.used = { }, +	}; + +	/* +	 * If this is a regular PHY context (not the ROC one) +	 * skip the ROC PHY context's ID. +	 */ +	if (ctxt != &mvm->phy_ctxt_roc) +		__set_bit(mvm->phy_ctxt_roc.id, data.used); + +	lockdep_assert_held(&mvm->mutex); +	ctxt->color++; + +	if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { +		ieee80211_iter_chan_contexts_atomic( +			mvm->hw, iwl_mvm_phy_ctx_used_iter, &data); + +		ctxt->id = find_first_zero_bit(data.used, NUM_PHY_CTX); +		if (WARN_ONCE(ctxt->id == NUM_PHY_CTX, +			      "Failed to init PHY context - no free ID!\n")) +			return -EIO; +	} + +	ctxt->channel = chandef->chan; +	return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, +				      chains_static, chains_dynamic, +				      FW_CTXT_ACTION_ADD, 0); +} + +/* + * Send a command to modify the PHY context based on the current HW + * configuration. Note that the function does not check that the configuration + * changed. + */ +int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, +			     struct cfg80211_chan_def *chandef, +			     u8 chains_static, u8 chains_dynamic) +{ +	lockdep_assert_held(&mvm->mutex); + +	ctxt->channel = chandef->chan; +	return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, +				      chains_static, chains_dynamic, +				      FW_CTXT_ACTION_MODIFY, 0); +} + +/* + * Send a command to the FW to remove the given phy context. + * Once the command is sent, regardless of success or failure, the context is + * marked as invalid + */ +void iwl_mvm_phy_ctxt_remove(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) +{ +	struct iwl_phy_context_cmd cmd; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, FW_CTXT_ACTION_REMOVE, 0); +	ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, CMD_SYNC, +				   sizeof(struct iwl_phy_context_cmd), +				   &cmd); +	if (ret) +		IWL_ERR(mvm, "Failed to send PHY remove: ctxt id=%d\n", +			ctxt->id); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/power.c b/drivers/net/wireless/iwlwifi/mvm/power.c new file mode 100644 index 00000000000..63628739cf4 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/power.c @@ -0,0 +1,207 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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/module.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <net/mac80211.h> + +#include "iwl-debug.h" +#include "mvm.h" +#include "iwl-modparams.h" +#include "fw-api-power.h" + +#define POWER_KEEP_ALIVE_PERIOD_SEC    25 + +static void iwl_power_build_cmd(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +				struct iwl_powertable_cmd *cmd) +{ +	struct ieee80211_hw *hw = mvm->hw; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct ieee80211_chanctx_conf *chanctx_conf; +	struct ieee80211_channel *chan; +	int dtimper, dtimper_msec; +	int keep_alive; +	bool radar_detect = false; + +	cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							    mvmvif->color)); +	cmd->action = cpu_to_le32(FW_CTXT_ACTION_MODIFY); + +	if ((!vif->bss_conf.ps) || +	    (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_CAM)) +		return; + +	cmd->flags |= cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK); + +	dtimper = hw->conf.ps_dtim_period ?: 1; + +	/* Check if radar detection is required on current channel */ +	rcu_read_lock(); +	chanctx_conf = rcu_dereference(vif->chanctx_conf); +	WARN_ON(!chanctx_conf); +	if (chanctx_conf) { +		chan = chanctx_conf->def.chan; +		radar_detect = chan->flags & IEEE80211_CHAN_RADAR; +	} +	rcu_read_unlock(); + +	/* Check skip over DTIM conditions */ +	if (!radar_detect && (dtimper <= 10) && +	    (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_LP)) { +		cmd->flags |= cpu_to_le16(POWER_FLAGS_SLEEP_OVER_DTIM_MSK); +		cmd->num_skip_dtim = 2; +	} + +	/* Check that keep alive period is at least 3 * DTIM */ +	dtimper_msec = dtimper * vif->bss_conf.beacon_int; +	keep_alive = max_t(int, 3 * dtimper_msec, +			   MSEC_PER_SEC * POWER_KEEP_ALIVE_PERIOD_SEC); +	keep_alive = DIV_ROUND_UP(keep_alive, MSEC_PER_SEC); + +	cmd->keep_alive_seconds = cpu_to_le16(keep_alive); + +	if (iwlmvm_mod_params.power_scheme == IWL_POWER_SCHEME_LP) { +		/* TODO: Also for D3 (device sleep / WoWLAN) */ +		cmd->rx_data_timeout = cpu_to_le32(10); +		cmd->tx_data_timeout = cpu_to_le32(10); +	} else { +		cmd->rx_data_timeout = cpu_to_le32(50); +		cmd->tx_data_timeout = cpu_to_le32(50); +	} +} + +int iwl_mvm_power_update_mode(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_powertable_cmd cmd = {}; + +	if (!iwlwifi_mod_params.power_save) { +		IWL_DEBUG_POWER(mvm, "Power management is not allowed\n"); +		return 0; +	} + +	if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) +		return 0; + +	iwl_power_build_cmd(mvm, vif, &cmd); + +	IWL_DEBUG_POWER(mvm, +			"Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", +			cmd.id_and_color, iwlmvm_mod_params.power_scheme, +			le16_to_cpu(cmd.flags)); + +	if (cmd.flags & cpu_to_le16(POWER_FLAGS_POWER_MANAGEMENT_ENA_MSK)) { +		IWL_DEBUG_POWER(mvm, "Keep alive = %u sec\n", +				le16_to_cpu(cmd.keep_alive_seconds)); +		IWL_DEBUG_POWER(mvm, "Rx timeout = %u usec\n", +				le32_to_cpu(cmd.rx_data_timeout)); +		IWL_DEBUG_POWER(mvm, "Tx timeout = %u usec\n", +				le32_to_cpu(cmd.tx_data_timeout)); +		IWL_DEBUG_POWER(mvm, "Rx timeout (uAPSD) = %u usec\n", +				le32_to_cpu(cmd.rx_data_timeout_uapsd)); +		IWL_DEBUG_POWER(mvm, "Tx timeout = %u usec\n", +				le32_to_cpu(cmd.tx_data_timeout_uapsd)); +		IWL_DEBUG_POWER(mvm, "LP RX RSSI threshold = %u\n", +				cmd.lprx_rssi_threshold); +		IWL_DEBUG_POWER(mvm, "DTIMs to skip = %u\n", cmd.num_skip_dtim); +	} + +	return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, CMD_SYNC, +				    sizeof(cmd), &cmd); +} + +int iwl_mvm_power_disable(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_powertable_cmd cmd = {}; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + +	if (!iwlwifi_mod_params.power_save) { +		IWL_DEBUG_POWER(mvm, "Power management is not allowed\n"); +		return 0; +	} + +	if (vif->type != NL80211_IFTYPE_STATION || vif->p2p) +		return 0; + +	cmd.id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							    mvmvif->color)); +	cmd.action = cpu_to_le32(FW_CTXT_ACTION_MODIFY); + +	IWL_DEBUG_POWER(mvm, +			"Sending power table command on mac id 0x%X for power level %d, flags = 0x%X\n", +			cmd.id_and_color, iwlmvm_mod_params.power_scheme, +			le16_to_cpu(cmd.flags)); + +	return iwl_mvm_send_cmd_pdu(mvm, POWER_TABLE_CMD, CMD_SYNC, +				    sizeof(cmd), &cmd); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_power_get_params(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			  struct iwl_powertable_cmd *cmd) +{ +	iwl_power_build_cmd(mvm, vif, cmd); +} +#endif /* CONFIG_IWLWIFI_DEBUGFS */ diff --git a/drivers/net/wireless/iwlwifi/mvm/quota.c b/drivers/net/wireless/iwlwifi/mvm/quota.c new file mode 100644 index 00000000000..2d4611a563c --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/quota.c @@ -0,0 +1,178 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "fw-api.h" +#include "mvm.h" + +struct iwl_mvm_quota_iterator_data { +	int n_interfaces[MAX_BINDINGS]; +	int colors[MAX_BINDINGS]; +	struct ieee80211_vif *new_vif; +}; + +static void iwl_mvm_quota_iterator(void *_data, u8 *mac, +				   struct ieee80211_vif *vif) +{ +	struct iwl_mvm_quota_iterator_data *data = _data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	u16 id; + +	/* +	 * We'll account for the new interface (if any) below, +	 * skip it here in case we're not called from within +	 * the add_interface callback (otherwise it won't show +	 * up in iteration) +	 */ +	if (vif == data->new_vif) +		return; + +	if (!mvmvif->phy_ctxt) +		return; + +	/* currently, PHY ID == binding ID */ +	id = mvmvif->phy_ctxt->id; + +	/* need at least one binding per PHY */ +	BUILD_BUG_ON(NUM_PHY_CTX > MAX_BINDINGS); + +	if (WARN_ON_ONCE(id >= MAX_BINDINGS)) +		return; + +	if (data->colors[id] < 0) +		data->colors[id] = mvmvif->phy_ctxt->color; +	else +		WARN_ON_ONCE(data->colors[id] != mvmvif->phy_ctxt->color); + +	switch (vif->type) { +	case NL80211_IFTYPE_STATION: +		if (vif->bss_conf.assoc) +			data->n_interfaces[id]++; +		break; +	case NL80211_IFTYPE_AP: +		if (mvmvif->ap_active) +			data->n_interfaces[id]++; +		break; +	case NL80211_IFTYPE_MONITOR: +		data->n_interfaces[id]++; +		break; +	case NL80211_IFTYPE_P2P_DEVICE: +		break; +	case NL80211_IFTYPE_ADHOC: +		if (vif->bss_conf.ibss_joined) +			data->n_interfaces[id]++; +		break; +	default: +		WARN_ON_ONCE(1); +		break; +	} +} + +int iwl_mvm_update_quotas(struct iwl_mvm *mvm, struct ieee80211_vif *newvif) +{ +	struct iwl_time_quota_cmd cmd; +	int i, idx, ret; +	struct iwl_mvm_quota_iterator_data data = { +		.n_interfaces = {}, +		.colors = { -1, -1, -1, -1 }, +		.new_vif = newvif, +	}; + +	/* update all upon completion */ +	if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) +		return 0; + +	BUILD_BUG_ON(data.colors[MAX_BINDINGS - 1] != -1); + +	lockdep_assert_held(&mvm->mutex); + +	memset(&cmd, 0, sizeof(cmd)); + +	ieee80211_iterate_active_interfaces_atomic( +		mvm->hw, IEEE80211_IFACE_ITER_NORMAL, +		iwl_mvm_quota_iterator, &data); +	if (newvif) { +		data.new_vif = NULL; +		iwl_mvm_quota_iterator(&data, newvif->addr, newvif); +	} + +	for (idx = 0, i = 0; i < MAX_BINDINGS; i++) { +		if (data.n_interfaces[i] <= 0) +			continue; + +		cmd.quotas[idx].id_and_color = +			cpu_to_le32(FW_CMD_ID_AND_COLOR(i, data.colors[i])); +		cmd.quotas[idx].quota = cpu_to_le32(100); +		cmd.quotas[idx].max_duration = cpu_to_le32(1000); +		idx++; +	} + +	for (i = idx; i < MAX_BINDINGS; i++) +		cmd.quotas[i].id_and_color = cpu_to_le32(FW_CTXT_INVALID); + +	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, CMD_SYNC, +				   sizeof(cmd), &cmd); +	if (ret) +		IWL_ERR(mvm, "Failed to send quota: %d\n", ret); +	return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/rs.c b/drivers/net/wireless/iwlwifi/mvm/rs.c new file mode 100644 index 00000000000..60a4291ca22 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rs.c @@ -0,0 +1,3096 @@ +/****************************************************************************** + * + * Copyright(c) 2005 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE. + * + * Contact Information: + *  Intel Linux Wireless <ilw@linux.intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/mac80211.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> + +#include <linux/workqueue.h> +#include "rs.h" +#include "fw-api.h" +#include "sta.h" +#include "iwl-op-mode.h" +#include "mvm.h" + +#define RS_NAME "iwl-mvm-rs" + +#define NUM_TRY_BEFORE_ANT_TOGGLE 1 +#define IWL_NUMBER_TRY      1 +#define IWL_HT_NUMBER_TRY   3 + +#define IWL_RATE_MAX_WINDOW		62	/* # tx in history window */ +#define IWL_RATE_MIN_FAILURE_TH		6	/* min failures to calc tpt */ +#define IWL_RATE_MIN_SUCCESS_TH		8	/* min successes to calc tpt */ + +/* max allowed rate miss before sync LQ cmd */ +#define IWL_MISSED_RATE_MAX		15 +/* max time to accum history 2 seconds */ +#define IWL_RATE_SCALE_FLUSH_INTVL   (3*HZ) + +static u8 rs_ht_to_legacy[] = { +	IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, +	IWL_RATE_6M_INDEX, IWL_RATE_6M_INDEX, +	IWL_RATE_6M_INDEX, +	IWL_RATE_6M_INDEX, IWL_RATE_9M_INDEX, +	IWL_RATE_12M_INDEX, IWL_RATE_18M_INDEX, +	IWL_RATE_24M_INDEX, IWL_RATE_36M_INDEX, +	IWL_RATE_48M_INDEX, IWL_RATE_54M_INDEX +}; + +static const u8 ant_toggle_lookup[] = { +	/*ANT_NONE -> */ ANT_NONE, +	/*ANT_A    -> */ ANT_B, +	/*ANT_B    -> */ ANT_C, +	/*ANT_AB   -> */ ANT_BC, +	/*ANT_C    -> */ ANT_A, +	/*ANT_AC   -> */ ANT_AB, +	/*ANT_BC   -> */ ANT_AC, +	/*ANT_ABC  -> */ ANT_ABC, +}; + +#define IWL_DECLARE_RATE_INFO(r, s, ip, in, rp, rn, pp, np)    \ +	[IWL_RATE_##r##M_INDEX] = { IWL_RATE_##r##M_PLCP,      \ +				    IWL_RATE_SISO_##s##M_PLCP, \ +				    IWL_RATE_MIMO2_##s##M_PLCP,\ +				    IWL_RATE_MIMO3_##s##M_PLCP,\ +				    IWL_RATE_##r##M_IEEE,      \ +				    IWL_RATE_##ip##M_INDEX,    \ +				    IWL_RATE_##in##M_INDEX,    \ +				    IWL_RATE_##rp##M_INDEX,    \ +				    IWL_RATE_##rn##M_INDEX,    \ +				    IWL_RATE_##pp##M_INDEX,    \ +				    IWL_RATE_##np##M_INDEX } + +/* + * Parameter order: + *   rate, ht rate, prev rate, next rate, prev tgg rate, next tgg rate + * + * If there isn't a valid next or previous rate then INV is used which + * maps to IWL_RATE_INVALID + * + */ +static const struct iwl_rs_rate_info iwl_rates[IWL_RATE_COUNT] = { +	IWL_DECLARE_RATE_INFO(1, INV, INV, 2, INV, 2, INV, 2),    /*  1mbps */ +	IWL_DECLARE_RATE_INFO(2, INV, 1, 5, 1, 5, 1, 5),          /*  2mbps */ +	IWL_DECLARE_RATE_INFO(5, INV, 2, 6, 2, 11, 2, 11),        /*5.5mbps */ +	IWL_DECLARE_RATE_INFO(11, INV, 9, 12, 9, 12, 5, 18),      /* 11mbps */ +	IWL_DECLARE_RATE_INFO(6, 6, 5, 9, 5, 11, 5, 11),        /*  6mbps */ +	IWL_DECLARE_RATE_INFO(9, 6, 6, 11, 6, 11, 5, 11),       /*  9mbps */ +	IWL_DECLARE_RATE_INFO(12, 12, 11, 18, 11, 18, 11, 18),   /* 12mbps */ +	IWL_DECLARE_RATE_INFO(18, 18, 12, 24, 12, 24, 11, 24),   /* 18mbps */ +	IWL_DECLARE_RATE_INFO(24, 24, 18, 36, 18, 36, 18, 36),   /* 24mbps */ +	IWL_DECLARE_RATE_INFO(36, 36, 24, 48, 24, 48, 24, 48),   /* 36mbps */ +	IWL_DECLARE_RATE_INFO(48, 48, 36, 54, 36, 54, 36, 54),   /* 48mbps */ +	IWL_DECLARE_RATE_INFO(54, 54, 48, INV, 48, INV, 48, INV),/* 54mbps */ +	IWL_DECLARE_RATE_INFO(60, 60, 48, INV, 48, INV, 48, INV),/* 60mbps */ +	/* FIXME:RS:          ^^    should be INV (legacy) */ +}; + +static inline u8 rs_extract_rate(u32 rate_n_flags) +{ +	/* also works for HT because bits 7:6 are zero there */ +	return (u8)(rate_n_flags & RATE_LEGACY_RATE_MSK); +} + +static int iwl_hwrate_to_plcp_idx(u32 rate_n_flags) +{ +	int idx = 0; + +	/* HT rate format */ +	if (rate_n_flags & RATE_MCS_HT_MSK) { +		idx = rs_extract_rate(rate_n_flags); + +		if (idx >= IWL_RATE_MIMO3_6M_PLCP) +			idx = idx - IWL_RATE_MIMO3_6M_PLCP; +		else if (idx >= IWL_RATE_MIMO2_6M_PLCP) +			idx = idx - IWL_RATE_MIMO2_6M_PLCP; + +		idx += IWL_FIRST_OFDM_RATE; +		/* skip 9M not supported in ht*/ +		if (idx >= IWL_RATE_9M_INDEX) +			idx += 1; +		if ((idx >= IWL_FIRST_OFDM_RATE) && (idx <= IWL_LAST_OFDM_RATE)) +			return idx; + +	/* legacy rate format, search for match in table */ +	} else { +		for (idx = 0; idx < ARRAY_SIZE(iwl_rates); idx++) +			if (iwl_rates[idx].plcp == +					rs_extract_rate(rate_n_flags)) +				return idx; +	} + +	return -1; +} + +static void rs_rate_scale_perform(struct iwl_mvm *mvm, +				   struct sk_buff *skb, +				   struct ieee80211_sta *sta, +				   struct iwl_lq_sta *lq_sta); +static void rs_fill_link_cmd(struct iwl_mvm *mvm, +			     struct iwl_lq_sta *lq_sta, u32 rate_n_flags); +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search); + + +#ifdef CONFIG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, +			     u32 *rate_n_flags, int index); +#else +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, +			     u32 *rate_n_flags, int index) +{} +#endif + +/** + * The following tables contain the expected throughput metrics for all rates + * + *	1, 2, 5.5, 11, 6, 9, 12, 18, 24, 36, 48, 54, 60 MBits + * + * where invalid entries are zeros. + * + * CCK rates are only valid in legacy table and will only be used in G + * (2.4 GHz) band. + */ + +static s32 expected_tpt_legacy[IWL_RATE_COUNT] = { +	7, 13, 35, 58, 40, 57, 72, 98, 121, 154, 177, 186, 0 +}; + +static s32 expected_tpt_siso20MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0, 42, 0,  76, 102, 124, 159, 183, 193, 202}, /* Norm */ +	{0, 0, 0, 0, 46, 0,  82, 110, 132, 168, 192, 202, 210}, /* SGI */ +	{0, 0, 0, 0, 47, 0,  91, 133, 171, 242, 305, 334, 362}, /* AGG */ +	{0, 0, 0, 0, 52, 0, 101, 145, 187, 264, 330, 361, 390}, /* AGG+SGI */ +}; + +static s32 expected_tpt_siso40MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0,  77, 0, 127, 160, 184, 220, 242, 250, 257}, /* Norm */ +	{0, 0, 0, 0,  83, 0, 135, 169, 193, 229, 250, 257, 264}, /* SGI */ +	{0, 0, 0, 0,  94, 0, 177, 249, 313, 423, 512, 550, 586}, /* AGG */ +	{0, 0, 0, 0, 104, 0, 193, 270, 338, 454, 545, 584, 620}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo2_20MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0,  74, 0, 123, 155, 179, 214, 236, 244, 251}, /* Norm */ +	{0, 0, 0, 0,  81, 0, 131, 164, 188, 223, 243, 251, 257}, /* SGI */ +	{0, 0, 0, 0,  89, 0, 167, 235, 296, 402, 488, 526, 560}, /* AGG */ +	{0, 0, 0, 0,  97, 0, 182, 255, 320, 431, 520, 558, 593}, /* AGG+SGI*/ +}; + +static s32 expected_tpt_mimo2_40MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0, 123, 0, 182, 214, 235, 264, 279, 285, 289}, /* Norm */ +	{0, 0, 0, 0, 131, 0, 191, 222, 242, 270, 284, 289, 293}, /* SGI */ +	{0, 0, 0, 0, 171, 0, 305, 410, 496, 634, 731, 771, 805}, /* AGG */ +	{0, 0, 0, 0, 186, 0, 329, 439, 527, 667, 764, 803, 838}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_20MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0,  99, 0, 153, 186, 208, 239, 256, 263, 268}, /* Norm */ +	{0, 0, 0, 0, 106, 0, 162, 194, 215, 246, 262, 268, 273}, /* SGI */ +	{0, 0, 0, 0, 134, 0, 249, 346, 431, 574, 685, 732, 775}, /* AGG */ +	{0, 0, 0, 0, 148, 0, 272, 376, 465, 614, 727, 775, 818}, /* AGG+SGI */ +}; + +static s32 expected_tpt_mimo3_40MHz[4][IWL_RATE_COUNT] = { +	{0, 0, 0, 0, 152, 0, 211, 239, 255, 279,  290,  294,  297}, /* Norm */ +	{0, 0, 0, 0, 160, 0, 219, 245, 261, 284,  294,  297,  300}, /* SGI */ +	{0, 0, 0, 0, 254, 0, 443, 584, 695, 868,  984, 1030, 1070}, /* AGG */ +	{0, 0, 0, 0, 277, 0, 478, 624, 737, 911, 1026, 1070, 1109}, /* AGG+SGI */ +}; + +/* mbps, mcs */ +static const struct iwl_rate_mcs_info iwl_rate_mcs[IWL_RATE_COUNT] = { +	{  "1", "BPSK DSSS"}, +	{  "2", "QPSK DSSS"}, +	{"5.5", "BPSK CCK"}, +	{ "11", "QPSK CCK"}, +	{  "6", "BPSK 1/2"}, +	{  "9", "BPSK 1/2"}, +	{ "12", "QPSK 1/2"}, +	{ "18", "QPSK 3/4"}, +	{ "24", "16QAM 1/2"}, +	{ "36", "16QAM 3/4"}, +	{ "48", "64QAM 2/3"}, +	{ "54", "64QAM 3/4"}, +	{ "60", "64QAM 5/6"}, +}; + +#define MCS_INDEX_PER_STREAM	(8) + +static void rs_rate_scale_clear_window(struct iwl_rate_scale_data *window) +{ +	window->data = 0; +	window->success_counter = 0; +	window->success_ratio = IWL_INVALID_VALUE; +	window->counter = 0; +	window->average_tpt = IWL_INVALID_VALUE; +	window->stamp = 0; +} + +static inline u8 rs_is_valid_ant(u8 valid_antenna, u8 ant_type) +{ +	return (ant_type & valid_antenna) == ant_type; +} + +/* + *	removes the old data from the statistics. All data that is older than + *	TID_MAX_TIME_DIFF, will be deleted. + */ +static void rs_tl_rm_old_stats(struct iwl_traffic_load *tl, u32 curr_time) +{ +	/* The oldest age we want to keep */ +	u32 oldest_time = curr_time - TID_MAX_TIME_DIFF; + +	while (tl->queue_count && +	       (tl->time_stamp < oldest_time)) { +		tl->total -= tl->packet_count[tl->head]; +		tl->packet_count[tl->head] = 0; +		tl->time_stamp += TID_QUEUE_CELL_SPACING; +		tl->queue_count--; +		tl->head++; +		if (tl->head >= TID_QUEUE_MAX_SIZE) +			tl->head = 0; +	} +} + +/* + *	increment traffic load value for tid and also remove + *	any old values if passed the certain time period + */ +static u8 rs_tl_add_packet(struct iwl_lq_sta *lq_data, +			   struct ieee80211_hdr *hdr) +{ +	u32 curr_time = jiffies_to_msecs(jiffies); +	u32 time_diff; +	s32 index; +	struct iwl_traffic_load *tl = NULL; +	u8 tid; + +	if (ieee80211_is_data_qos(hdr->frame_control)) { +		u8 *qc = ieee80211_get_qos_ctl(hdr); +		tid = qc[0] & 0xf; +	} else { +		return IWL_MAX_TID_COUNT; +	} + +	if (unlikely(tid >= IWL_MAX_TID_COUNT)) +		return IWL_MAX_TID_COUNT; + +	tl = &lq_data->load[tid]; + +	curr_time -= curr_time % TID_ROUND_VALUE; + +	/* Happens only for the first packet. Initialize the data */ +	if (!(tl->queue_count)) { +		tl->total = 1; +		tl->time_stamp = curr_time; +		tl->queue_count = 1; +		tl->head = 0; +		tl->packet_count[0] = 1; +		return IWL_MAX_TID_COUNT; +	} + +	time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); +	index = time_diff / TID_QUEUE_CELL_SPACING; + +	/* The history is too long: remove data that is older than */ +	/* TID_MAX_TIME_DIFF */ +	if (index >= TID_QUEUE_MAX_SIZE) +		rs_tl_rm_old_stats(tl, curr_time); + +	index = (tl->head + index) % TID_QUEUE_MAX_SIZE; +	tl->packet_count[index] = tl->packet_count[index] + 1; +	tl->total = tl->total + 1; + +	if ((index + 1) > tl->queue_count) +		tl->queue_count = index + 1; + +	return tid; +} + +#ifdef CONFIG_MAC80211_DEBUGFS +/** + * Program the device to use fixed rate for frame transmit + * This is for debugging/testing only + * once the device start use fixed rate, we need to reload the module + * to being back the normal operation. + */ +static void rs_program_fix_rate(struct iwl_mvm *mvm, +				struct iwl_lq_sta *lq_sta) +{ +	lq_sta->active_legacy_rate = 0x0FFF;	/* 1 - 54 MBits, includes CCK */ +	lq_sta->active_siso_rate   = 0x1FD0;	/* 6 - 60 MBits, no 9, no CCK */ +	lq_sta->active_mimo2_rate  = 0x1FD0;	/* 6 - 60 MBits, no 9, no CCK */ +	lq_sta->active_mimo3_rate  = 0x1FD0;	/* 6 - 60 MBits, no 9, no CCK */ + +	IWL_DEBUG_RATE(mvm, "sta_id %d rate 0x%X\n", +		       lq_sta->lq.sta_id, lq_sta->dbg_fixed_rate); + +	if (lq_sta->dbg_fixed_rate) { +		rs_fill_link_cmd(NULL, lq_sta, lq_sta->dbg_fixed_rate); +		iwl_mvm_send_lq_cmd(lq_sta->drv, &lq_sta->lq, CMD_ASYNC, false); +	} +} +#endif + +/* +	get the traffic load value for tid +*/ +static u32 rs_tl_get_load(struct iwl_lq_sta *lq_data, u8 tid) +{ +	u32 curr_time = jiffies_to_msecs(jiffies); +	u32 time_diff; +	s32 index; +	struct iwl_traffic_load *tl = NULL; + +	if (tid >= IWL_MAX_TID_COUNT) +		return 0; + +	tl = &(lq_data->load[tid]); + +	curr_time -= curr_time % TID_ROUND_VALUE; + +	if (!(tl->queue_count)) +		return 0; + +	time_diff = TIME_WRAP_AROUND(tl->time_stamp, curr_time); +	index = time_diff / TID_QUEUE_CELL_SPACING; + +	/* The history is too long: remove data that is older than */ +	/* TID_MAX_TIME_DIFF */ +	if (index >= TID_QUEUE_MAX_SIZE) +		rs_tl_rm_old_stats(tl, curr_time); + +	return tl->total; +} + +static int rs_tl_turn_on_agg_for_tid(struct iwl_mvm *mvm, +				      struct iwl_lq_sta *lq_data, u8 tid, +				      struct ieee80211_sta *sta) +{ +	int ret = -EAGAIN; +	u32 load; + +	load = rs_tl_get_load(lq_data, tid); + +	if ((iwlwifi_mod_params.auto_agg) || (load > IWL_AGG_LOAD_THRESHOLD)) { +		IWL_DEBUG_HT(mvm, "Starting Tx agg: STA: %pM tid: %d\n", +			     sta->addr, tid); +		ret = ieee80211_start_tx_ba_session(sta, tid, 5000); +		if (ret == -EAGAIN) { +			/* +			 * driver and mac80211 is out of sync +			 * this might be cause by reloading firmware +			 * stop the tx ba session here +			 */ +			IWL_ERR(mvm, "Fail start Tx agg on tid: %d\n", +				tid); +			ieee80211_stop_tx_ba_session(sta, tid); +		} +	} else { +		IWL_DEBUG_HT(mvm, +			     "Aggregation not enabled for tid %d because load = %u\n", +			     tid, load); +	} +	return ret; +} + +static void rs_tl_turn_on_agg(struct iwl_mvm *mvm, u8 tid, +			      struct iwl_lq_sta *lq_data, +			      struct ieee80211_sta *sta) +{ +	if (tid < IWL_MAX_TID_COUNT) +		rs_tl_turn_on_agg_for_tid(mvm, lq_data, tid, sta); +	else +		IWL_ERR(mvm, "tid exceeds max TID count: %d/%d\n", +			tid, IWL_MAX_TID_COUNT); +} + +static inline int get_num_of_ant_from_rate(u32 rate_n_flags) +{ +	return !!(rate_n_flags & RATE_MCS_ANT_A_MSK) + +	       !!(rate_n_flags & RATE_MCS_ANT_B_MSK) + +	       !!(rate_n_flags & RATE_MCS_ANT_C_MSK); +} + +/* + * Static function to get the expected throughput from an iwl_scale_tbl_info + * that wraps a NULL pointer check + */ +static s32 get_expected_tpt(struct iwl_scale_tbl_info *tbl, int rs_index) +{ +	if (tbl->expected_tpt) +		return tbl->expected_tpt[rs_index]; +	return 0; +} + +/** + * rs_collect_tx_data - Update the success/failure sliding window + * + * We keep a sliding window of the last 62 packets transmitted + * at this rate.  window->data contains the bitmask of successful + * packets. + */ +static int rs_collect_tx_data(struct iwl_scale_tbl_info *tbl, +			      int scale_index, int attempts, int successes) +{ +	struct iwl_rate_scale_data *window = NULL; +	static const u64 mask = (((u64)1) << (IWL_RATE_MAX_WINDOW - 1)); +	s32 fail_count, tpt; + +	if (scale_index < 0 || scale_index >= IWL_RATE_COUNT) +		return -EINVAL; + +	/* Select window for current tx bit rate */ +	window = &(tbl->win[scale_index]); + +	/* Get expected throughput */ +	tpt = get_expected_tpt(tbl, scale_index); + +	/* +	 * Keep track of only the latest 62 tx frame attempts in this rate's +	 * history window; anything older isn't really relevant any more. +	 * If we have filled up the sliding window, drop the oldest attempt; +	 * if the oldest attempt (highest bit in bitmap) shows "success", +	 * subtract "1" from the success counter (this is the main reason +	 * we keep these bitmaps!). +	 */ +	while (attempts > 0) { +		if (window->counter >= IWL_RATE_MAX_WINDOW) { +			/* remove earliest */ +			window->counter = IWL_RATE_MAX_WINDOW - 1; + +			if (window->data & mask) { +				window->data &= ~mask; +				window->success_counter--; +			} +		} + +		/* Increment frames-attempted counter */ +		window->counter++; + +		/* Shift bitmap by one frame to throw away oldest history */ +		window->data <<= 1; + +		/* Mark the most recent #successes attempts as successful */ +		if (successes > 0) { +			window->success_counter++; +			window->data |= 0x1; +			successes--; +		} + +		attempts--; +	} + +	/* Calculate current success ratio, avoid divide-by-0! */ +	if (window->counter > 0) +		window->success_ratio = 128 * (100 * window->success_counter) +					/ window->counter; +	else +		window->success_ratio = IWL_INVALID_VALUE; + +	fail_count = window->counter - window->success_counter; + +	/* Calculate average throughput, if we have enough history. */ +	if ((fail_count >= IWL_RATE_MIN_FAILURE_TH) || +	    (window->success_counter >= IWL_RATE_MIN_SUCCESS_TH)) +		window->average_tpt = (window->success_ratio * tpt + 64) / 128; +	else +		window->average_tpt = IWL_INVALID_VALUE; + +	/* Tag this window as having been updated */ +	window->stamp = jiffies; + +	return 0; +} + +/* + * Fill uCode API rate_n_flags field, based on "search" or "active" table. + */ +/* FIXME:RS:remove this function and put the flags statically in the table */ +static u32 rate_n_flags_from_tbl(struct iwl_mvm *mvm, +				 struct iwl_scale_tbl_info *tbl, +				 int index, u8 use_green) +{ +	u32 rate_n_flags = 0; + +	if (is_legacy(tbl->lq_type)) { +		rate_n_flags = iwl_rates[index].plcp; +		if (index >= IWL_FIRST_CCK_RATE && index <= IWL_LAST_CCK_RATE) +			rate_n_flags |= RATE_MCS_CCK_MSK; +	} else if (is_Ht(tbl->lq_type)) { +		if (index > IWL_LAST_OFDM_RATE) { +			IWL_ERR(mvm, "Invalid HT rate index %d\n", index); +			index = IWL_LAST_OFDM_RATE; +		} +		rate_n_flags = RATE_MCS_HT_MSK; + +		if (is_siso(tbl->lq_type)) +			rate_n_flags |=	iwl_rates[index].plcp_siso; +		else if (is_mimo2(tbl->lq_type)) +			rate_n_flags |=	iwl_rates[index].plcp_mimo2; +		else +			rate_n_flags |=	iwl_rates[index].plcp_mimo3; +	} else { +		IWL_ERR(mvm, "Invalid tbl->lq_type %d\n", tbl->lq_type); +	} + +	rate_n_flags |= ((tbl->ant_type << RATE_MCS_ANT_POS) & +						     RATE_MCS_ANT_ABC_MSK); + +	if (is_Ht(tbl->lq_type)) { +		if (tbl->is_ht40) +			rate_n_flags |= RATE_MCS_CHAN_WIDTH_40; +		if (tbl->is_SGI) +			rate_n_flags |= RATE_MCS_SGI_MSK; + +		if (use_green) { +			rate_n_flags |= RATE_HT_MCS_GF_MSK; +			if (is_siso(tbl->lq_type) && tbl->is_SGI) { +				rate_n_flags &= ~RATE_MCS_SGI_MSK; +				IWL_ERR(mvm, "GF was set with SGI:SISO\n"); +			} +		} +	} +	return rate_n_flags; +} + +/* + * Interpret uCode API's rate_n_flags format, + * fill "search" or "active" tx mode table. + */ +static int rs_get_tbl_info_from_mcs(const u32 rate_n_flags, +				    enum ieee80211_band band, +				    struct iwl_scale_tbl_info *tbl, +				    int *rate_idx) +{ +	u32 ant_msk = (rate_n_flags & RATE_MCS_ANT_ABC_MSK); +	u8 num_of_ant = get_num_of_ant_from_rate(rate_n_flags); +	u8 mcs; + +	memset(tbl, 0, sizeof(struct iwl_scale_tbl_info)); +	*rate_idx = iwl_hwrate_to_plcp_idx(rate_n_flags); + +	if (*rate_idx  == IWL_RATE_INVALID) { +		*rate_idx = -1; +		return -EINVAL; +	} +	tbl->is_SGI = 0;	/* default legacy setup */ +	tbl->is_ht40 = 0; +	tbl->ant_type = (ant_msk >> RATE_MCS_ANT_POS); +	tbl->lq_type = LQ_NONE; +	tbl->max_search = IWL_MAX_SEARCH; + +	/* legacy rate format */ +	if (!(rate_n_flags & RATE_MCS_HT_MSK)) { +		if (num_of_ant == 1) { +			if (band == IEEE80211_BAND_5GHZ) +				tbl->lq_type = LQ_A; +			else +				tbl->lq_type = LQ_G; +		} +	/* HT rate format */ +	} else { +		if (rate_n_flags & RATE_MCS_SGI_MSK) +			tbl->is_SGI = 1; + +		if (rate_n_flags & RATE_MCS_CHAN_WIDTH_40) /* TODO */ +			tbl->is_ht40 = 1; + +		mcs = rs_extract_rate(rate_n_flags); + +		/* SISO */ +		if (mcs <= IWL_RATE_SISO_60M_PLCP) { +			if (num_of_ant == 1) +				tbl->lq_type = LQ_SISO; /*else NONE*/ +		/* MIMO2 */ +		} else if (mcs <= IWL_RATE_MIMO2_60M_PLCP) { +			if (num_of_ant == 2) +				tbl->lq_type = LQ_MIMO2; +		/* MIMO3 */ +		} else { +			if (num_of_ant == 3) { +				tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; +				tbl->lq_type = LQ_MIMO3; +			} +		} +	} +	return 0; +} + +/* switch to another antenna/antennas and return 1 */ +/* if no other valid antenna found, return 0 */ +static int rs_toggle_antenna(u32 valid_ant, u32 *rate_n_flags, +			     struct iwl_scale_tbl_info *tbl) +{ +	u8 new_ant_type; + +	if (!tbl->ant_type || tbl->ant_type > ANT_ABC) +		return 0; + +	if (!rs_is_valid_ant(valid_ant, tbl->ant_type)) +		return 0; + +	new_ant_type = ant_toggle_lookup[tbl->ant_type]; + +	while ((new_ant_type != tbl->ant_type) && +	       !rs_is_valid_ant(valid_ant, new_ant_type)) +		new_ant_type = ant_toggle_lookup[new_ant_type]; + +	if (new_ant_type == tbl->ant_type) +		return 0; + +	tbl->ant_type = new_ant_type; +	*rate_n_flags &= ~RATE_MCS_ANT_ABC_MSK; +	*rate_n_flags |= new_ant_type << RATE_MCS_ANT_POS; +	return 1; +} + +/** + * Green-field mode is valid if the station supports it and + * there are no non-GF stations present in the BSS. + */ +static bool rs_use_green(struct ieee80211_sta *sta) +{ +	struct iwl_mvm_sta *sta_priv = (void *)sta->drv_priv; + +	bool use_green = !(sta_priv->vif->bss_conf.ht_operation_mode & +				IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT); + +	return (sta->ht_cap.cap & IEEE80211_HT_CAP_GRN_FLD) && use_green; +} + +/** + * rs_get_supported_rates - get the available rates + * + * if management frame or broadcast frame only return + * basic available rates. + * + */ +static u16 rs_get_supported_rates(struct iwl_lq_sta *lq_sta, +				  struct ieee80211_hdr *hdr, +				  enum iwl_table_type rate_type) +{ +	if (is_legacy(rate_type)) { +		return lq_sta->active_legacy_rate; +	} else { +		if (is_siso(rate_type)) +			return lq_sta->active_siso_rate; +		else if (is_mimo2(rate_type)) +			return lq_sta->active_mimo2_rate; +		else +			return lq_sta->active_mimo3_rate; +	} +} + +static u16 rs_get_adjacent_rate(struct iwl_mvm *mvm, u8 index, u16 rate_mask, +				int rate_type) +{ +	u8 high = IWL_RATE_INVALID; +	u8 low = IWL_RATE_INVALID; + +	/* 802.11A or ht walks to the next literal adjacent rate in +	 * the rate table */ +	if (is_a_band(rate_type) || !is_legacy(rate_type)) { +		int i; +		u32 mask; + +		/* Find the previous rate that is in the rate mask */ +		i = index - 1; +		for (mask = (1 << i); i >= 0; i--, mask >>= 1) { +			if (rate_mask & mask) { +				low = i; +				break; +			} +		} + +		/* Find the next rate that is in the rate mask */ +		i = index + 1; +		for (mask = (1 << i); i < IWL_RATE_COUNT; i++, mask <<= 1) { +			if (rate_mask & mask) { +				high = i; +				break; +			} +		} + +		return (high << 8) | low; +	} + +	low = index; +	while (low != IWL_RATE_INVALID) { +		low = iwl_rates[low].prev_rs; +		if (low == IWL_RATE_INVALID) +			break; +		if (rate_mask & (1 << low)) +			break; +		IWL_DEBUG_RATE(mvm, "Skipping masked lower rate: %d\n", low); +	} + +	high = index; +	while (high != IWL_RATE_INVALID) { +		high = iwl_rates[high].next_rs; +		if (high == IWL_RATE_INVALID) +			break; +		if (rate_mask & (1 << high)) +			break; +		IWL_DEBUG_RATE(mvm, "Skipping masked higher rate: %d\n", high); +	} + +	return (high << 8) | low; +} + +static u32 rs_get_lower_rate(struct iwl_lq_sta *lq_sta, +			     struct iwl_scale_tbl_info *tbl, +			     u8 scale_index, u8 ht_possible) +{ +	s32 low; +	u16 rate_mask; +	u16 high_low; +	u8 switch_to_legacy = 0; +	u8 is_green = lq_sta->is_green; +	struct iwl_mvm *mvm = lq_sta->drv; + +	/* check if we need to switch from HT to legacy rates. +	 * assumption is that mandatory rates (1Mbps or 6Mbps) +	 * are always supported (spec demand) */ +	if (!is_legacy(tbl->lq_type) && (!ht_possible || !scale_index)) { +		switch_to_legacy = 1; +		scale_index = rs_ht_to_legacy[scale_index]; +		if (lq_sta->band == IEEE80211_BAND_5GHZ) +			tbl->lq_type = LQ_A; +		else +			tbl->lq_type = LQ_G; + +		if (num_of_ant(tbl->ant_type) > 1) +			tbl->ant_type = +			    first_antenna(mvm->nvm_data->valid_tx_ant); + +		tbl->is_ht40 = 0; +		tbl->is_SGI = 0; +		tbl->max_search = IWL_MAX_SEARCH; +	} + +	rate_mask = rs_get_supported_rates(lq_sta, NULL, tbl->lq_type); + +	/* Mask with station rate restriction */ +	if (is_legacy(tbl->lq_type)) { +		/* supp_rates has no CCK bits in A mode */ +		if (lq_sta->band == IEEE80211_BAND_5GHZ) +			rate_mask  = (u16)(rate_mask & +			   (lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); +		else +			rate_mask = (u16)(rate_mask & lq_sta->supp_rates); +	} + +	/* If we switched from HT to legacy, check current rate */ +	if (switch_to_legacy && (rate_mask & (1 << scale_index))) { +		low = scale_index; +		goto out; +	} + +	high_low = rs_get_adjacent_rate(lq_sta->drv, scale_index, rate_mask, +					tbl->lq_type); +	low = high_low & 0xff; + +	if (low == IWL_RATE_INVALID) +		low = scale_index; + +out: +	return rate_n_flags_from_tbl(lq_sta->drv, tbl, low, is_green); +} + +/* + * Simple function to compare two rate scale table types + */ +static bool table_type_matches(struct iwl_scale_tbl_info *a, +			       struct iwl_scale_tbl_info *b) +{ +	return (a->lq_type == b->lq_type) && (a->ant_type == b->ant_type) && +		(a->is_SGI == b->is_SGI); +} + +/* + * mac80211 sends us Tx status + */ +static void rs_tx_status(void *mvm_r, struct ieee80211_supported_band *sband, +			 struct ieee80211_sta *sta, void *priv_sta, +			 struct sk_buff *skb) +{ +	int legacy_success; +	int retries; +	int rs_index, mac_index, i; +	struct iwl_lq_sta *lq_sta = priv_sta; +	struct iwl_lq_cmd *table; +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	struct iwl_op_mode *op_mode = (struct iwl_op_mode *)mvm_r; +	struct iwl_mvm *mvm = IWL_OP_MODE_GET_MVM(op_mode); +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	enum mac80211_rate_control_flags mac_flags; +	u32 tx_rate; +	struct iwl_scale_tbl_info tbl_type; +	struct iwl_scale_tbl_info *curr_tbl, *other_tbl, *tmp_tbl; + +	IWL_DEBUG_RATE_LIMIT(mvm, +			     "get frame ack response, update rate scale window\n"); + +	/* Treat uninitialized rate scaling data same as non-existing. */ +	if (!lq_sta) { +		IWL_DEBUG_RATE(mvm, "Station rate scaling not created yet.\n"); +		return; +	} else if (!lq_sta->drv) { +		IWL_DEBUG_RATE(mvm, "Rate scaling not initialized yet.\n"); +		return; +	} + +	if (!ieee80211_is_data(hdr->frame_control) || +	    info->flags & IEEE80211_TX_CTL_NO_ACK) +		return; + +	/* This packet was aggregated but doesn't carry status info */ +	if ((info->flags & IEEE80211_TX_CTL_AMPDU) && +	    !(info->flags & IEEE80211_TX_STAT_AMPDU)) +		return; + +	/* +	 * Ignore this Tx frame response if its initial rate doesn't match +	 * that of latest Link Quality command.  There may be stragglers +	 * from a previous Link Quality command, but we're no longer interested +	 * in those; they're either from the "active" mode while we're trying +	 * to check "search" mode, or a prior "search" mode after we've moved +	 * to a new "search" mode (which might become the new "active" mode). +	 */ +	table = &lq_sta->lq; +	tx_rate = le32_to_cpu(table->rs_table[0]); +	rs_get_tbl_info_from_mcs(tx_rate, info->band, &tbl_type, &rs_index); +	if (info->band == IEEE80211_BAND_5GHZ) +		rs_index -= IWL_FIRST_OFDM_RATE; +	mac_flags = info->status.rates[0].flags; +	mac_index = info->status.rates[0].idx; +	/* For HT packets, map MCS to PLCP */ +	if (mac_flags & IEEE80211_TX_RC_MCS) { +		/* Remove # of streams */ +		mac_index &= RATE_HT_MCS_RATE_CODE_MSK; +		if (mac_index >= (IWL_RATE_9M_INDEX - IWL_FIRST_OFDM_RATE)) +			mac_index++; +		/* +		 * mac80211 HT index is always zero-indexed; we need to move +		 * HT OFDM rates after CCK rates in 2.4 GHz band +		 */ +		if (info->band == IEEE80211_BAND_2GHZ) +			mac_index += IWL_FIRST_OFDM_RATE; +	} +	/* Here we actually compare this rate to the latest LQ command */ +	if ((mac_index < 0) || +	    (tbl_type.is_SGI != !!(mac_flags & IEEE80211_TX_RC_SHORT_GI)) || +	    (tbl_type.is_ht40 != !!(mac_flags & IEEE80211_TX_RC_40_MHZ_WIDTH)) || +	    (tbl_type.ant_type != info->status.antenna) || +	    (!!(tx_rate & RATE_MCS_HT_MSK) != +				!!(mac_flags & IEEE80211_TX_RC_MCS)) || +	    (!!(tx_rate & RATE_HT_MCS_GF_MSK) != +				!!(mac_flags & IEEE80211_TX_RC_GREEN_FIELD)) || +	    (rs_index != mac_index)) { +		IWL_DEBUG_RATE(mvm, +			       "initial rate %d does not match %d (0x%x)\n", +			       mac_index, rs_index, tx_rate); +		/* +		 * Since rates mis-match, the last LQ command may have failed. +		 * After IWL_MISSED_RATE_MAX mis-matches, resync the uCode with +		 * ... driver. +		 */ +		lq_sta->missed_rate_counter++; +		if (lq_sta->missed_rate_counter > IWL_MISSED_RATE_MAX) { +			lq_sta->missed_rate_counter = 0; +			iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); +		} +		/* Regardless, ignore this status info for outdated rate */ +		return; +	} else +		/* Rate did match, so reset the missed_rate_counter */ +		lq_sta->missed_rate_counter = 0; + +	/* Figure out if rate scale algorithm is in active or search table */ +	if (table_type_matches(&tbl_type, +			       &(lq_sta->lq_info[lq_sta->active_tbl]))) { +		curr_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +		other_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); +	} else if (table_type_matches( +			&tbl_type, &lq_sta->lq_info[1 - lq_sta->active_tbl])) { +		curr_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); +		other_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +	} else { +		IWL_DEBUG_RATE(mvm, +			       "Neither active nor search matches tx rate\n"); +		tmp_tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +		IWL_DEBUG_RATE(mvm, "active- lq:%x, ant:%x, SGI:%d\n", +			       tmp_tbl->lq_type, tmp_tbl->ant_type, +			       tmp_tbl->is_SGI); +		tmp_tbl = &(lq_sta->lq_info[1 - lq_sta->active_tbl]); +		IWL_DEBUG_RATE(mvm, "search- lq:%x, ant:%x, SGI:%d\n", +			       tmp_tbl->lq_type, tmp_tbl->ant_type, +			       tmp_tbl->is_SGI); +		IWL_DEBUG_RATE(mvm, "actual- lq:%x, ant:%x, SGI:%d\n", +			       tbl_type.lq_type, tbl_type.ant_type, +			       tbl_type.is_SGI); +		/* +		 * no matching table found, let's by-pass the data collection +		 * and continue to perform rate scale to find the rate table +		 */ +		rs_stay_in_table(lq_sta, true); +		goto done; +	} + +	/* +	 * Updating the frame history depends on whether packets were +	 * aggregated. +	 * +	 * For aggregation, all packets were transmitted at the same rate, the +	 * first index into rate scale table. +	 */ +	if (info->flags & IEEE80211_TX_STAT_AMPDU) { +		tx_rate = le32_to_cpu(table->rs_table[0]); +		rs_get_tbl_info_from_mcs(tx_rate, info->band, &tbl_type, +					 &rs_index); +		rs_collect_tx_data(curr_tbl, rs_index, +				   info->status.ampdu_len, +				   info->status.ampdu_ack_len); + +		/* Update success/fail counts if not searching for new mode */ +		if (lq_sta->stay_in_tbl) { +			lq_sta->total_success += info->status.ampdu_ack_len; +			lq_sta->total_failed += (info->status.ampdu_len - +					info->status.ampdu_ack_len); +		} +	} else { +	/* +	 * For legacy, update frame history with for each Tx retry. +	 */ +		retries = info->status.rates[0].count - 1; +		/* HW doesn't send more than 15 retries */ +		retries = min(retries, 15); + +		/* The last transmission may have been successful */ +		legacy_success = !!(info->flags & IEEE80211_TX_STAT_ACK); +		/* Collect data for each rate used during failed TX attempts */ +		for (i = 0; i <= retries; ++i) { +			tx_rate = le32_to_cpu(table->rs_table[i]); +			rs_get_tbl_info_from_mcs(tx_rate, info->band, +						 &tbl_type, &rs_index); +			/* +			 * Only collect stats if retried rate is in the same RS +			 * table as active/search. +			 */ +			if (table_type_matches(&tbl_type, curr_tbl)) +				tmp_tbl = curr_tbl; +			else if (table_type_matches(&tbl_type, other_tbl)) +				tmp_tbl = other_tbl; +			else +				continue; +			rs_collect_tx_data(tmp_tbl, rs_index, 1, +					   i < retries ? 0 : legacy_success); +		} + +		/* Update success/fail counts if not searching for new mode */ +		if (lq_sta->stay_in_tbl) { +			lq_sta->total_success += legacy_success; +			lq_sta->total_failed += retries + (1 - legacy_success); +		} +	} +	/* The last TX rate is cached in lq_sta; it's set in if/else above */ +	lq_sta->last_rate_n_flags = tx_rate; +done: +	/* See if there's a better rate or modulation mode to try. */ +	if (sta && sta->supp_rates[sband->band]) +		rs_rate_scale_perform(mvm, skb, sta, lq_sta); +} + +/* + * Begin a period of staying with a selected modulation mode. + * Set "stay_in_tbl" flag to prevent any mode switches. + * Set frame tx success limits according to legacy vs. high-throughput, + * and reset overall (spanning all rates) tx success history statistics. + * These control how long we stay using same modulation mode before + * searching for a new mode. + */ +static void rs_set_stay_in_table(struct iwl_mvm *mvm, u8 is_legacy, +				 struct iwl_lq_sta *lq_sta) +{ +	IWL_DEBUG_RATE(mvm, "we are staying in the same table\n"); +	lq_sta->stay_in_tbl = 1;	/* only place this gets set */ +	if (is_legacy) { +		lq_sta->table_count_limit = IWL_LEGACY_TABLE_COUNT; +		lq_sta->max_failure_limit = IWL_LEGACY_FAILURE_LIMIT; +		lq_sta->max_success_limit = IWL_LEGACY_SUCCESS_LIMIT; +	} else { +		lq_sta->table_count_limit = IWL_NONE_LEGACY_TABLE_COUNT; +		lq_sta->max_failure_limit = IWL_NONE_LEGACY_FAILURE_LIMIT; +		lq_sta->max_success_limit = IWL_NONE_LEGACY_SUCCESS_LIMIT; +	} +	lq_sta->table_count = 0; +	lq_sta->total_failed = 0; +	lq_sta->total_success = 0; +	lq_sta->flush_timer = jiffies; +	lq_sta->action_counter = 0; +} + +/* + * Find correct throughput table for given mode of modulation + */ +static void rs_set_expected_tpt_table(struct iwl_lq_sta *lq_sta, +				      struct iwl_scale_tbl_info *tbl) +{ +	/* Used to choose among HT tables */ +	s32 (*ht_tbl_pointer)[IWL_RATE_COUNT]; + +	/* Check for invalid LQ type */ +	if (WARN_ON_ONCE(!is_legacy(tbl->lq_type) && !is_Ht(tbl->lq_type))) { +		tbl->expected_tpt = expected_tpt_legacy; +		return; +	} + +	/* Legacy rates have only one table */ +	if (is_legacy(tbl->lq_type)) { +		tbl->expected_tpt = expected_tpt_legacy; +		return; +	} + +	/* Choose among many HT tables depending on number of streams +	 * (SISO/MIMO2/MIMO3), channel width (20/40), SGI, and aggregation +	 * status */ +	if (is_siso(tbl->lq_type) && !tbl->is_ht40) +		ht_tbl_pointer = expected_tpt_siso20MHz; +	else if (is_siso(tbl->lq_type)) +		ht_tbl_pointer = expected_tpt_siso40MHz; +	else if (is_mimo2(tbl->lq_type) && !tbl->is_ht40) +		ht_tbl_pointer = expected_tpt_mimo2_20MHz; +	else if (is_mimo2(tbl->lq_type)) +		ht_tbl_pointer = expected_tpt_mimo2_40MHz; +	else if (is_mimo3(tbl->lq_type) && !tbl->is_ht40) +		ht_tbl_pointer = expected_tpt_mimo3_20MHz; +	else /* if (is_mimo3(tbl->lq_type)) <-- must be true */ +		ht_tbl_pointer = expected_tpt_mimo3_40MHz; + +	if (!tbl->is_SGI && !lq_sta->is_agg)		/* Normal */ +		tbl->expected_tpt = ht_tbl_pointer[0]; +	else if (tbl->is_SGI && !lq_sta->is_agg)	/* SGI */ +		tbl->expected_tpt = ht_tbl_pointer[1]; +	else if (!tbl->is_SGI && lq_sta->is_agg)	/* AGG */ +		tbl->expected_tpt = ht_tbl_pointer[2]; +	else						/* AGG+SGI */ +		tbl->expected_tpt = ht_tbl_pointer[3]; +} + +/* + * Find starting rate for new "search" high-throughput mode of modulation. + * Goal is to find lowest expected rate (under perfect conditions) that is + * above the current measured throughput of "active" mode, to give new mode + * a fair chance to prove itself without too many challenges. + * + * This gets called when transitioning to more aggressive modulation + * (i.e. legacy to SISO or MIMO, or SISO to MIMO), as well as less aggressive + * (i.e. MIMO to SISO).  When moving to MIMO, bit rate will typically need + * to decrease to match "active" throughput.  When moving from MIMO to SISO, + * bit rate will typically need to increase, but not if performance was bad. + */ +static s32 rs_get_best_rate(struct iwl_mvm *mvm, +			    struct iwl_lq_sta *lq_sta, +			    struct iwl_scale_tbl_info *tbl,	/* "search" */ +			    u16 rate_mask, s8 index) +{ +	/* "active" values */ +	struct iwl_scale_tbl_info *active_tbl = +	    &(lq_sta->lq_info[lq_sta->active_tbl]); +	s32 active_sr = active_tbl->win[index].success_ratio; +	s32 active_tpt = active_tbl->expected_tpt[index]; + +	/* expected "search" throughput */ +	s32 *tpt_tbl = tbl->expected_tpt; + +	s32 new_rate, high, low, start_hi; +	u16 high_low; +	s8 rate = index; + +	new_rate = high = low = start_hi = IWL_RATE_INVALID; + +	while (1) { +		high_low = rs_get_adjacent_rate(mvm, rate, rate_mask, +						tbl->lq_type); + +		low = high_low & 0xff; +		high = (high_low >> 8) & 0xff; + +		/* +		 * Lower the "search" bit rate, to give new "search" mode +		 * approximately the same throughput as "active" if: +		 * +		 * 1) "Active" mode has been working modestly well (but not +		 *    great), and expected "search" throughput (under perfect +		 *    conditions) at candidate rate is above the actual +		 *    measured "active" throughput (but less than expected +		 *    "active" throughput under perfect conditions). +		 * OR +		 * 2) "Active" mode has been working perfectly or very well +		 *    and expected "search" throughput (under perfect +		 *    conditions) at candidate rate is above expected +		 *    "active" throughput (under perfect conditions). +		 */ +		if ((((100 * tpt_tbl[rate]) > lq_sta->last_tpt) && +		     ((active_sr > IWL_RATE_DECREASE_TH) && +		      (active_sr <= IWL_RATE_HIGH_TH) && +		      (tpt_tbl[rate] <= active_tpt))) || +		    ((active_sr >= IWL_RATE_SCALE_SWITCH) && +		     (tpt_tbl[rate] > active_tpt))) { +			/* (2nd or later pass) +			 * If we've already tried to raise the rate, and are +			 * now trying to lower it, use the higher rate. */ +			if (start_hi != IWL_RATE_INVALID) { +				new_rate = start_hi; +				break; +			} + +			new_rate = rate; + +			/* Loop again with lower rate */ +			if (low != IWL_RATE_INVALID) +				rate = low; + +			/* Lower rate not available, use the original */ +			else +				break; + +		/* Else try to raise the "search" rate to match "active" */ +		} else { +			/* (2nd or later pass) +			 * If we've already tried to lower the rate, and are +			 * now trying to raise it, use the lower rate. */ +			if (new_rate != IWL_RATE_INVALID) +				break; + +			/* Loop again with higher rate */ +			else if (high != IWL_RATE_INVALID) { +				start_hi = high; +				rate = high; + +			/* Higher rate not available, use the original */ +			} else { +				new_rate = rate; +				break; +			} +		} +	} + +	return new_rate; +} + +static bool iwl_is_ht40_tx_allowed(struct iwl_mvm *mvm, +			    struct ieee80211_sta_ht_cap *ht_cap) +{ +	/* +	 * Remainder of this function checks ht_cap, but if it's +	 * NULL then we can do HT40 (special case for RXON) +	 */ +	if (!ht_cap) +		return true; + +	if (!ht_cap->ht_supported) +		return false; + +	if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40)) +		return false; + +	return true; +} + +/* + * Set up search table for MIMO2 + */ +static int rs_switch_to_mimo2(struct iwl_mvm *mvm, +			     struct iwl_lq_sta *lq_sta, +			     struct ieee80211_sta *sta, +			     struct iwl_scale_tbl_info *tbl, int index) +{ +	u16 rate_mask; +	s32 rate; +	s8 is_green = lq_sta->is_green; + +	if (!sta->ht_cap.ht_supported) +		return -1; + +	if (((sta->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >> 2) +						== WLAN_HT_CAP_SM_PS_STATIC) +		return -1; + +	/* Need both Tx chains/antennas to support MIMO */ +	if (num_of_ant(mvm->nvm_data->valid_tx_ant) < 2) +		return -1; + +	IWL_DEBUG_RATE(mvm, "LQ: try to switch to MIMO2\n"); + +	tbl->lq_type = LQ_MIMO2; +	tbl->action = 0; +	tbl->max_search = IWL_MAX_SEARCH; +	rate_mask = lq_sta->active_mimo2_rate; + +	if (iwl_is_ht40_tx_allowed(mvm, &sta->ht_cap)) +		tbl->is_ht40 = 1; +	else +		tbl->is_ht40 = 0; + +	rs_set_expected_tpt_table(lq_sta, tbl); + +	rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + +	IWL_DEBUG_RATE(mvm, "LQ: MIMO2 best rate %d mask %X\n", +		       rate, rate_mask); +	if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { +		IWL_DEBUG_RATE(mvm, "Can't switch with index %d rate mask %x\n", +			       rate, rate_mask); +		return -1; +	} +	tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); + +	IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", +		       tbl->current_rate, is_green); +	return 0; +} + +/* + * Set up search table for MIMO3 + */ +static int rs_switch_to_mimo3(struct iwl_mvm *mvm, +			     struct iwl_lq_sta *lq_sta, +			     struct ieee80211_sta *sta, +			     struct iwl_scale_tbl_info *tbl, int index) +{ +	u16 rate_mask; +	s32 rate; +	s8 is_green = lq_sta->is_green; + +	if (!sta->ht_cap.ht_supported) +		return -1; + +	if (((sta->ht_cap.cap & IEEE80211_HT_CAP_SM_PS) >> 2) +						== WLAN_HT_CAP_SM_PS_STATIC) +		return -1; + +	/* Need both Tx chains/antennas to support MIMO */ +	if (num_of_ant(mvm->nvm_data->valid_tx_ant) < 3) +		return -1; + +	IWL_DEBUG_RATE(mvm, "LQ: try to switch to MIMO3\n"); + +	tbl->lq_type = LQ_MIMO3; +	tbl->action = 0; +	tbl->max_search = IWL_MAX_11N_MIMO3_SEARCH; +	rate_mask = lq_sta->active_mimo3_rate; + +	if (iwl_is_ht40_tx_allowed(mvm, &sta->ht_cap)) +		tbl->is_ht40 = 1; +	else +		tbl->is_ht40 = 0; + +	rs_set_expected_tpt_table(lq_sta, tbl); + +	rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + +	IWL_DEBUG_RATE(mvm, "LQ: MIMO3 best rate %d mask %X\n", +		       rate, rate_mask); +	if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { +		IWL_DEBUG_RATE(mvm, "Can't switch with index %d rate mask %x\n", +			       rate, rate_mask); +		return -1; +	} +	tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); + +	IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", +		       tbl->current_rate, is_green); +	return 0; +} + +/* + * Set up search table for SISO + */ +static int rs_switch_to_siso(struct iwl_mvm *mvm, +			     struct iwl_lq_sta *lq_sta, +			     struct ieee80211_sta *sta, +			     struct iwl_scale_tbl_info *tbl, int index) +{ +	u16 rate_mask; +	u8 is_green = lq_sta->is_green; +	s32 rate; + +	if (!sta->ht_cap.ht_supported) +		return -1; + +	IWL_DEBUG_RATE(mvm, "LQ: try to switch to SISO\n"); + +	tbl->lq_type = LQ_SISO; +	tbl->action = 0; +	tbl->max_search = IWL_MAX_SEARCH; +	rate_mask = lq_sta->active_siso_rate; + +	if (iwl_is_ht40_tx_allowed(mvm, &sta->ht_cap)) +		tbl->is_ht40 = 1; +	else +		tbl->is_ht40 = 0; + +	if (is_green) +		tbl->is_SGI = 0; /*11n spec: no SGI in SISO+Greenfield*/ + +	rs_set_expected_tpt_table(lq_sta, tbl); +	rate = rs_get_best_rate(mvm, lq_sta, tbl, rate_mask, index); + +	IWL_DEBUG_RATE(mvm, "LQ: get best rate %d mask %X\n", rate, rate_mask); +	if ((rate == IWL_RATE_INVALID) || !((1 << rate) & rate_mask)) { +		IWL_DEBUG_RATE(mvm, +			       "can not switch with index %d rate mask %x\n", +			       rate, rate_mask); +		return -1; +	} +	tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, rate, is_green); +	IWL_DEBUG_RATE(mvm, "LQ: Switch to new mcs %X index is green %X\n", +		       tbl->current_rate, is_green); +	return 0; +} + +/* + * Try to switch to new modulation mode from legacy + */ +static int rs_move_legacy_other(struct iwl_mvm *mvm, +				struct iwl_lq_sta *lq_sta, +				struct ieee80211_sta *sta, +				int index) +{ +	struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +	struct iwl_scale_tbl_info *search_tbl = +				&(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); +	struct iwl_rate_scale_data *window = &(tbl->win[index]); +	u32 sz = (sizeof(struct iwl_scale_tbl_info) - +		  (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); +	u8 start_action; +	u8 valid_tx_ant = mvm->nvm_data->valid_tx_ant; +	u8 tx_chains_num = num_of_ant(valid_tx_ant); +	int ret; +	u8 update_search_tbl_counter = 0; + +	start_action = tbl->action; +	while (1) { +		lq_sta->action_counter++; +		switch (tbl->action) { +		case IWL_LEGACY_SWITCH_ANTENNA1: +		case IWL_LEGACY_SWITCH_ANTENNA2: +			IWL_DEBUG_RATE(mvm, "LQ: Legacy toggle Antenna\n"); + +			if ((tbl->action == IWL_LEGACY_SWITCH_ANTENNA1 && +			     tx_chains_num <= 1) || +			    (tbl->action == IWL_LEGACY_SWITCH_ANTENNA2 && +			     tx_chains_num <= 2)) +				break; + +			/* Don't change antenna if success has been great */ +			if (window->success_ratio >= IWL_RS_GOOD_RATIO) +				break; + +			/* Set up search table to try other antenna */ +			memcpy(search_tbl, tbl, sz); + +			if (rs_toggle_antenna(valid_tx_ant, +					      &search_tbl->current_rate, +					      search_tbl)) { +				update_search_tbl_counter = 1; +				rs_set_expected_tpt_table(lq_sta, search_tbl); +				goto out; +			} +			break; +		case IWL_LEGACY_SWITCH_SISO: +			IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to SISO\n"); + +			/* Set up search table to try SISO */ +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; +			ret = rs_switch_to_siso(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) { +				lq_sta->action_counter = 0; +				goto out; +			} + +			break; +		case IWL_LEGACY_SWITCH_MIMO2_AB: +		case IWL_LEGACY_SWITCH_MIMO2_AC: +		case IWL_LEGACY_SWITCH_MIMO2_BC: +			IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to MIMO2\n"); + +			/* Set up search table to try MIMO */ +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; + +			if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AB) +				search_tbl->ant_type = ANT_AB; +			else if (tbl->action == IWL_LEGACY_SWITCH_MIMO2_AC) +				search_tbl->ant_type = ANT_AC; +			else +				search_tbl->ant_type = ANT_BC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo2(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) { +				lq_sta->action_counter = 0; +				goto out; +			} +			break; + +		case IWL_LEGACY_SWITCH_MIMO3_ABC: +			IWL_DEBUG_RATE(mvm, "LQ: Legacy switch to MIMO3\n"); + +			/* Set up search table to try MIMO3 */ +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; + +			search_tbl->ant_type = ANT_ABC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo3(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) { +				lq_sta->action_counter = 0; +				goto out; +			} +			break; +		} +		tbl->action++; +		if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) +			tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; + +		if (tbl->action == start_action) +			break; +	} +	search_tbl->lq_type = LQ_NONE; +	return 0; + +out: +	lq_sta->search_better_tbl = 1; +	tbl->action++; +	if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) +		tbl->action = IWL_LEGACY_SWITCH_ANTENNA1; +	if (update_search_tbl_counter) +		search_tbl->action = tbl->action; +	return 0; +} + +/* + * Try to switch to new modulation mode from SISO + */ +static int rs_move_siso_to_other(struct iwl_mvm *mvm, +				 struct iwl_lq_sta *lq_sta, +				 struct ieee80211_sta *sta, int index) +{ +	u8 is_green = lq_sta->is_green; +	struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +	struct iwl_scale_tbl_info *search_tbl = +				&(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); +	struct iwl_rate_scale_data *window = &(tbl->win[index]); +	struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; +	u32 sz = (sizeof(struct iwl_scale_tbl_info) - +		  (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); +	u8 start_action; +	u8 valid_tx_ant = mvm->nvm_data->valid_tx_ant; +	u8 tx_chains_num = num_of_ant(valid_tx_ant); +	u8 update_search_tbl_counter = 0; +	int ret; + +	start_action = tbl->action; +	while (1) { +		lq_sta->action_counter++; +		switch (tbl->action) { +		case IWL_SISO_SWITCH_ANTENNA1: +		case IWL_SISO_SWITCH_ANTENNA2: +			IWL_DEBUG_RATE(mvm, "LQ: SISO toggle Antenna\n"); +			if ((tbl->action == IWL_SISO_SWITCH_ANTENNA1 && +			     tx_chains_num <= 1) || +			    (tbl->action == IWL_SISO_SWITCH_ANTENNA2 && +			     tx_chains_num <= 2)) +				break; + +			if (window->success_ratio >= IWL_RS_GOOD_RATIO) +				break; + +			memcpy(search_tbl, tbl, sz); +			if (rs_toggle_antenna(valid_tx_ant, +					      &search_tbl->current_rate, +					      search_tbl)) { +				update_search_tbl_counter = 1; +				goto out; +			} +			break; +		case IWL_SISO_SWITCH_MIMO2_AB: +		case IWL_SISO_SWITCH_MIMO2_AC: +		case IWL_SISO_SWITCH_MIMO2_BC: +			IWL_DEBUG_RATE(mvm, "LQ: SISO switch to MIMO2\n"); +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; + +			if (tbl->action == IWL_SISO_SWITCH_MIMO2_AB) +				search_tbl->ant_type = ANT_AB; +			else if (tbl->action == IWL_SISO_SWITCH_MIMO2_AC) +				search_tbl->ant_type = ANT_AC; +			else +				search_tbl->ant_type = ANT_BC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo2(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) +				goto out; +			break; +		case IWL_SISO_SWITCH_GI: +			if (!tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_20)) +				break; +			if (tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_40)) +				break; + +			IWL_DEBUG_RATE(mvm, "LQ: SISO toggle SGI/NGI\n"); + +			memcpy(search_tbl, tbl, sz); +			if (is_green) { +				if (!tbl->is_SGI) +					break; +				else +					IWL_ERR(mvm, +						"SGI was set in GF+SISO\n"); +			} +			search_tbl->is_SGI = !tbl->is_SGI; +			rs_set_expected_tpt_table(lq_sta, search_tbl); +			if (tbl->is_SGI) { +				s32 tpt = lq_sta->last_tpt / 100; +				if (tpt >= search_tbl->expected_tpt[index]) +					break; +			} +			search_tbl->current_rate = +				rate_n_flags_from_tbl(mvm, search_tbl, +						      index, is_green); +			update_search_tbl_counter = 1; +			goto out; +		case IWL_SISO_SWITCH_MIMO3_ABC: +			IWL_DEBUG_RATE(mvm, "LQ: SISO switch to MIMO3\n"); +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; +			search_tbl->ant_type = ANT_ABC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo3(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) +				goto out; +			break; +		} +		tbl->action++; +		if (tbl->action > IWL_LEGACY_SWITCH_MIMO3_ABC) +			tbl->action = IWL_SISO_SWITCH_ANTENNA1; + +		if (tbl->action == start_action) +			break; +	} +	search_tbl->lq_type = LQ_NONE; +	return 0; + + out: +	lq_sta->search_better_tbl = 1; +	tbl->action++; +	if (tbl->action > IWL_SISO_SWITCH_MIMO3_ABC) +		tbl->action = IWL_SISO_SWITCH_ANTENNA1; +	if (update_search_tbl_counter) +		search_tbl->action = tbl->action; + +	return 0; +} + +/* + * Try to switch to new modulation mode from MIMO2 + */ +static int rs_move_mimo2_to_other(struct iwl_mvm *mvm, +				 struct iwl_lq_sta *lq_sta, +				 struct ieee80211_sta *sta, int index) +{ +	s8 is_green = lq_sta->is_green; +	struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +	struct iwl_scale_tbl_info *search_tbl = +				&(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); +	struct iwl_rate_scale_data *window = &(tbl->win[index]); +	struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; +	u32 sz = (sizeof(struct iwl_scale_tbl_info) - +		  (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); +	u8 start_action; +	u8 valid_tx_ant = mvm->nvm_data->valid_tx_ant; +	u8 tx_chains_num = num_of_ant(valid_tx_ant); +	u8 update_search_tbl_counter = 0; +	int ret; + +	start_action = tbl->action; +	while (1) { +		lq_sta->action_counter++; +		switch (tbl->action) { +		case IWL_MIMO2_SWITCH_ANTENNA1: +		case IWL_MIMO2_SWITCH_ANTENNA2: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO2 toggle Antennas\n"); + +			if (tx_chains_num <= 2) +				break; + +			if (window->success_ratio >= IWL_RS_GOOD_RATIO) +				break; + +			memcpy(search_tbl, tbl, sz); +			if (rs_toggle_antenna(valid_tx_ant, +					      &search_tbl->current_rate, +					      search_tbl)) { +				update_search_tbl_counter = 1; +				goto out; +			} +			break; +		case IWL_MIMO2_SWITCH_SISO_A: +		case IWL_MIMO2_SWITCH_SISO_B: +		case IWL_MIMO2_SWITCH_SISO_C: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO2 switch to SISO\n"); + +			/* Set up new search table for SISO */ +			memcpy(search_tbl, tbl, sz); + +			if (tbl->action == IWL_MIMO2_SWITCH_SISO_A) +				search_tbl->ant_type = ANT_A; +			else if (tbl->action == IWL_MIMO2_SWITCH_SISO_B) +				search_tbl->ant_type = ANT_B; +			else +				search_tbl->ant_type = ANT_C; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_siso(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) +				goto out; + +			break; + +		case IWL_MIMO2_SWITCH_GI: +			if (!tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_20)) +				break; +			if (tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_40)) +				break; + +			IWL_DEBUG_RATE(mvm, "LQ: MIMO2 toggle SGI/NGI\n"); + +			/* Set up new search table for MIMO2 */ +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = !tbl->is_SGI; +			rs_set_expected_tpt_table(lq_sta, search_tbl); +			/* +			 * If active table already uses the fastest possible +			 * modulation (dual stream with short guard interval), +			 * and it's working well, there's no need to look +			 * for a better type of modulation! +			 */ +			if (tbl->is_SGI) { +				s32 tpt = lq_sta->last_tpt / 100; +				if (tpt >= search_tbl->expected_tpt[index]) +					break; +			} +			search_tbl->current_rate = +				rate_n_flags_from_tbl(mvm, search_tbl, +						      index, is_green); +			update_search_tbl_counter = 1; +			goto out; + +		case IWL_MIMO2_SWITCH_MIMO3_ABC: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO2 switch to MIMO3\n"); +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; +			search_tbl->ant_type = ANT_ABC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo3(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) +				goto out; + +			break; +		} +		tbl->action++; +		if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) +			tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; + +		if (tbl->action == start_action) +			break; +	} +	search_tbl->lq_type = LQ_NONE; +	return 0; + out: +	lq_sta->search_better_tbl = 1; +	tbl->action++; +	if (tbl->action > IWL_MIMO2_SWITCH_MIMO3_ABC) +		tbl->action = IWL_MIMO2_SWITCH_ANTENNA1; +	if (update_search_tbl_counter) +		search_tbl->action = tbl->action; + +	return 0; +} + +/* + * Try to switch to new modulation mode from MIMO3 + */ +static int rs_move_mimo3_to_other(struct iwl_mvm *mvm, +				 struct iwl_lq_sta *lq_sta, +				 struct ieee80211_sta *sta, int index) +{ +	s8 is_green = lq_sta->is_green; +	struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +	struct iwl_scale_tbl_info *search_tbl = +				&(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); +	struct iwl_rate_scale_data *window = &(tbl->win[index]); +	struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; +	u32 sz = (sizeof(struct iwl_scale_tbl_info) - +		  (sizeof(struct iwl_rate_scale_data) * IWL_RATE_COUNT)); +	u8 start_action; +	u8 valid_tx_ant = mvm->nvm_data->valid_tx_ant; +	u8 tx_chains_num = num_of_ant(valid_tx_ant); +	int ret; +	u8 update_search_tbl_counter = 0; + +	start_action = tbl->action; +	while (1) { +		lq_sta->action_counter++; +		switch (tbl->action) { +		case IWL_MIMO3_SWITCH_ANTENNA1: +		case IWL_MIMO3_SWITCH_ANTENNA2: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO3 toggle Antennas\n"); + +			if (tx_chains_num <= 3) +				break; + +			if (window->success_ratio >= IWL_RS_GOOD_RATIO) +				break; + +			memcpy(search_tbl, tbl, sz); +			if (rs_toggle_antenna(valid_tx_ant, +					      &search_tbl->current_rate, +					      search_tbl)) +				goto out; +			break; +		case IWL_MIMO3_SWITCH_SISO_A: +		case IWL_MIMO3_SWITCH_SISO_B: +		case IWL_MIMO3_SWITCH_SISO_C: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO3 switch to SISO\n"); + +			/* Set up new search table for SISO */ +			memcpy(search_tbl, tbl, sz); + +			if (tbl->action == IWL_MIMO3_SWITCH_SISO_A) +				search_tbl->ant_type = ANT_A; +			else if (tbl->action == IWL_MIMO3_SWITCH_SISO_B) +				search_tbl->ant_type = ANT_B; +			else +				search_tbl->ant_type = ANT_C; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_siso(mvm, lq_sta, sta, +						search_tbl, index); +			if (!ret) +				goto out; + +			break; + +		case IWL_MIMO3_SWITCH_MIMO2_AB: +		case IWL_MIMO3_SWITCH_MIMO2_AC: +		case IWL_MIMO3_SWITCH_MIMO2_BC: +			IWL_DEBUG_RATE(mvm, "LQ: MIMO3 switch to MIMO2\n"); + +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = 0; +			if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AB) +				search_tbl->ant_type = ANT_AB; +			else if (tbl->action == IWL_MIMO3_SWITCH_MIMO2_AC) +				search_tbl->ant_type = ANT_AC; +			else +				search_tbl->ant_type = ANT_BC; + +			if (!rs_is_valid_ant(valid_tx_ant, +					     search_tbl->ant_type)) +				break; + +			ret = rs_switch_to_mimo2(mvm, lq_sta, sta, +						 search_tbl, index); +			if (!ret) +				goto out; + +			break; + +		case IWL_MIMO3_SWITCH_GI: +			if (!tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_20)) +				break; +			if (tbl->is_ht40 && !(ht_cap->cap & +						IEEE80211_HT_CAP_SGI_40)) +				break; + +			IWL_DEBUG_RATE(mvm, "LQ: MIMO3 toggle SGI/NGI\n"); + +			/* Set up new search table for MIMO */ +			memcpy(search_tbl, tbl, sz); +			search_tbl->is_SGI = !tbl->is_SGI; +			rs_set_expected_tpt_table(lq_sta, search_tbl); +			/* +			 * If active table already uses the fastest possible +			 * modulation (dual stream with short guard interval), +			 * and it's working well, there's no need to look +			 * for a better type of modulation! +			 */ +			if (tbl->is_SGI) { +				s32 tpt = lq_sta->last_tpt / 100; +				if (tpt >= search_tbl->expected_tpt[index]) +					break; +			} +			search_tbl->current_rate = +				rate_n_flags_from_tbl(mvm, search_tbl, +						      index, is_green); +			update_search_tbl_counter = 1; +			goto out; +		} +		tbl->action++; +		if (tbl->action > IWL_MIMO3_SWITCH_GI) +			tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; + +		if (tbl->action == start_action) +			break; +	} +	search_tbl->lq_type = LQ_NONE; +	return 0; + out: +	lq_sta->search_better_tbl = 1; +	tbl->action++; +	if (tbl->action > IWL_MIMO3_SWITCH_GI) +		tbl->action = IWL_MIMO3_SWITCH_ANTENNA1; +	if (update_search_tbl_counter) +		search_tbl->action = tbl->action; + +	return 0; +} + +/* + * Check whether we should continue using same modulation mode, or + * begin search for a new mode, based on: + * 1) # tx successes or failures while using this mode + * 2) # times calling this function + * 3) elapsed time in this mode (not used, for now) + */ +static void rs_stay_in_table(struct iwl_lq_sta *lq_sta, bool force_search) +{ +	struct iwl_scale_tbl_info *tbl; +	int i; +	int active_tbl; +	int flush_interval_passed = 0; +	struct iwl_mvm *mvm; + +	mvm = lq_sta->drv; +	active_tbl = lq_sta->active_tbl; + +	tbl = &(lq_sta->lq_info[active_tbl]); + +	/* If we've been disallowing search, see if we should now allow it */ +	if (lq_sta->stay_in_tbl) { +		/* Elapsed time using current modulation mode */ +		if (lq_sta->flush_timer) +			flush_interval_passed = +				time_after(jiffies, +					   (unsigned long)(lq_sta->flush_timer + +						IWL_RATE_SCALE_FLUSH_INTVL)); + +		/* +		 * Check if we should allow search for new modulation mode. +		 * If many frames have failed or succeeded, or we've used +		 * this same modulation for a long time, allow search, and +		 * reset history stats that keep track of whether we should +		 * allow a new search.  Also (below) reset all bitmaps and +		 * stats in active history. +		 */ +		if (force_search || +		    (lq_sta->total_failed > lq_sta->max_failure_limit) || +		    (lq_sta->total_success > lq_sta->max_success_limit) || +		    ((!lq_sta->search_better_tbl) && +		     (lq_sta->flush_timer) && (flush_interval_passed))) { +			IWL_DEBUG_RATE(mvm, +				       "LQ: stay is expired %d %d %d\n", +				     lq_sta->total_failed, +				     lq_sta->total_success, +				     flush_interval_passed); + +			/* Allow search for new mode */ +			lq_sta->stay_in_tbl = 0;	/* only place reset */ +			lq_sta->total_failed = 0; +			lq_sta->total_success = 0; +			lq_sta->flush_timer = 0; +		/* +		 * Else if we've used this modulation mode enough repetitions +		 * (regardless of elapsed time or success/failure), reset +		 * history bitmaps and rate-specific stats for all rates in +		 * active table. +		 */ +		} else { +			lq_sta->table_count++; +			if (lq_sta->table_count >= +			    lq_sta->table_count_limit) { +				lq_sta->table_count = 0; + +				IWL_DEBUG_RATE(mvm, +					       "LQ: stay in table clear win\n"); +				for (i = 0; i < IWL_RATE_COUNT; i++) +					rs_rate_scale_clear_window( +						&(tbl->win[i])); +			} +		} + +		/* If transitioning to allow "search", reset all history +		 * bitmaps and stats in active table (this will become the new +		 * "search" table). */ +		if (!lq_sta->stay_in_tbl) { +			for (i = 0; i < IWL_RATE_COUNT; i++) +				rs_rate_scale_clear_window(&(tbl->win[i])); +		} +	} +} + +/* + * setup rate table in uCode + */ +static void rs_update_rate_tbl(struct iwl_mvm *mvm, +			       struct iwl_lq_sta *lq_sta, +			       struct iwl_scale_tbl_info *tbl, +			       int index, u8 is_green) +{ +	u32 rate; + +	/* Update uCode's rate table. */ +	rate = rate_n_flags_from_tbl(mvm, tbl, index, is_green); +	rs_fill_link_cmd(mvm, lq_sta, rate); +	iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); +} + +/* + * Do rate scaling and search for new modulation mode. + */ +static void rs_rate_scale_perform(struct iwl_mvm *mvm, +				  struct sk_buff *skb, +				  struct ieee80211_sta *sta, +				  struct iwl_lq_sta *lq_sta) +{ +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	int low = IWL_RATE_INVALID; +	int high = IWL_RATE_INVALID; +	int index; +	int i; +	struct iwl_rate_scale_data *window = NULL; +	int current_tpt = IWL_INVALID_VALUE; +	int low_tpt = IWL_INVALID_VALUE; +	int high_tpt = IWL_INVALID_VALUE; +	u32 fail_count; +	s8 scale_action = 0; +	u16 rate_mask; +	u8 update_lq = 0; +	struct iwl_scale_tbl_info *tbl, *tbl1; +	u16 rate_scale_index_msk = 0; +	u8 is_green = 0; +	u8 active_tbl = 0; +	u8 done_search = 0; +	u16 high_low; +	s32 sr; +	u8 tid = IWL_MAX_TID_COUNT; +	struct iwl_mvm_sta *sta_priv = (void *)sta->drv_priv; +	struct iwl_mvm_tid_data *tid_data; + +	IWL_DEBUG_RATE(mvm, "rate scale calculate new rate for skb\n"); + +	/* Send management frames and NO_ACK data using lowest rate. */ +	/* TODO: this could probably be improved.. */ +	if (!ieee80211_is_data(hdr->frame_control) || +	    info->flags & IEEE80211_TX_CTL_NO_ACK) +		return; + +	lq_sta->supp_rates = sta->supp_rates[lq_sta->band]; + +	tid = rs_tl_add_packet(lq_sta, hdr); +	if ((tid != IWL_MAX_TID_COUNT) && +	    (lq_sta->tx_agg_tid_en & (1 << tid))) { +		tid_data = &sta_priv->tid_data[tid]; +		if (tid_data->state == IWL_AGG_OFF) +			lq_sta->is_agg = 0; +		else +			lq_sta->is_agg = 1; +	} else { +		lq_sta->is_agg = 0; +	} + +	/* +	 * Select rate-scale / modulation-mode table to work with in +	 * the rest of this function:  "search" if searching for better +	 * modulation mode, or "active" if doing rate scaling within a mode. +	 */ +	if (!lq_sta->search_better_tbl) +		active_tbl = lq_sta->active_tbl; +	else +		active_tbl = 1 - lq_sta->active_tbl; + +	tbl = &(lq_sta->lq_info[active_tbl]); +	if (is_legacy(tbl->lq_type)) +		lq_sta->is_green = 0; +	else +		lq_sta->is_green = rs_use_green(sta); +	is_green = lq_sta->is_green; + +	/* current tx rate */ +	index = lq_sta->last_txrate_idx; + +	IWL_DEBUG_RATE(mvm, "Rate scale index %d for type %d\n", index, +		       tbl->lq_type); + +	/* rates available for this association, and for modulation mode */ +	rate_mask = rs_get_supported_rates(lq_sta, hdr, tbl->lq_type); + +	IWL_DEBUG_RATE(mvm, "mask 0x%04X\n", rate_mask); + +	/* mask with station rate restriction */ +	if (is_legacy(tbl->lq_type)) { +		if (lq_sta->band == IEEE80211_BAND_5GHZ) +			/* supp_rates has no CCK bits in A mode */ +			rate_scale_index_msk = (u16) (rate_mask & +				(lq_sta->supp_rates << IWL_FIRST_OFDM_RATE)); +		else +			rate_scale_index_msk = (u16) (rate_mask & +						      lq_sta->supp_rates); + +	} else { +		rate_scale_index_msk = rate_mask; +	} + +	if (!rate_scale_index_msk) +		rate_scale_index_msk = rate_mask; + +	if (!((1 << index) & rate_scale_index_msk)) { +		IWL_ERR(mvm, "Current Rate is not valid\n"); +		if (lq_sta->search_better_tbl) { +			/* revert to active table if search table is not valid*/ +			tbl->lq_type = LQ_NONE; +			lq_sta->search_better_tbl = 0; +			tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); +			/* get "active" rate info */ +			index = iwl_hwrate_to_plcp_idx(tbl->current_rate); +			rs_update_rate_tbl(mvm, lq_sta, tbl, index, is_green); +		} +		return; +	} + +	/* Get expected throughput table and history window for current rate */ +	if (!tbl->expected_tpt) { +		IWL_ERR(mvm, "tbl->expected_tpt is NULL\n"); +		return; +	} + +	/* force user max rate if set by user */ +	if ((lq_sta->max_rate_idx != -1) && +	    (lq_sta->max_rate_idx < index)) { +		index = lq_sta->max_rate_idx; +		update_lq = 1; +		window = &(tbl->win[index]); +		goto lq_update; +	} + +	window = &(tbl->win[index]); + +	/* +	 * If there is not enough history to calculate actual average +	 * throughput, keep analyzing results of more tx frames, without +	 * changing rate or mode (bypass most of the rest of this function). +	 * Set up new rate table in uCode only if old rate is not supported +	 * in current association (use new rate found above). +	 */ +	fail_count = window->counter - window->success_counter; +	if ((fail_count < IWL_RATE_MIN_FAILURE_TH) && +	    (window->success_counter < IWL_RATE_MIN_SUCCESS_TH)) { +		IWL_DEBUG_RATE(mvm, +			       "LQ: still below TH. succ=%d total=%d for index %d\n", +			       window->success_counter, window->counter, index); + +		/* Can't calculate this yet; not enough history */ +		window->average_tpt = IWL_INVALID_VALUE; + +		/* Should we stay with this modulation mode, +		 * or search for a new one? */ +		rs_stay_in_table(lq_sta, false); + +		goto out; +	} +	/* Else we have enough samples; calculate estimate of +	 * actual average throughput */ +	if (window->average_tpt != ((window->success_ratio * +			tbl->expected_tpt[index] + 64) / 128)) { +		IWL_ERR(mvm, +			"expected_tpt should have been calculated by now\n"); +		window->average_tpt = ((window->success_ratio * +					tbl->expected_tpt[index] + 64) / 128); +	} + +	/* If we are searching for better modulation mode, check success. */ +	if (lq_sta->search_better_tbl) { +		/* If good success, continue using the "search" mode; +		 * no need to send new link quality command, since we're +		 * continuing to use the setup that we've been trying. */ +		if (window->average_tpt > lq_sta->last_tpt) { +			IWL_DEBUG_RATE(mvm, +				       "LQ: SWITCHING TO NEW TABLE suc=%d cur-tpt=%d old-tpt=%d\n", +				       window->success_ratio, +				       window->average_tpt, +				       lq_sta->last_tpt); + +			if (!is_legacy(tbl->lq_type)) +				lq_sta->enable_counter = 1; + +			/* Swap tables; "search" becomes "active" */ +			lq_sta->active_tbl = active_tbl; +			current_tpt = window->average_tpt; +		/* Else poor success; go back to mode in "active" table */ +		} else { +			IWL_DEBUG_RATE(mvm, +				       "LQ: GOING BACK TO THE OLD TABLE suc=%d cur-tpt=%d old-tpt=%d\n", +				       window->success_ratio, +				       window->average_tpt, +				       lq_sta->last_tpt); + +			/* Nullify "search" table */ +			tbl->lq_type = LQ_NONE; + +			/* Revert to "active" table */ +			active_tbl = lq_sta->active_tbl; +			tbl = &(lq_sta->lq_info[active_tbl]); + +			/* Revert to "active" rate and throughput info */ +			index = iwl_hwrate_to_plcp_idx(tbl->current_rate); +			current_tpt = lq_sta->last_tpt; + +			/* Need to set up a new rate table in uCode */ +			update_lq = 1; +		} + +		/* Either way, we've made a decision; modulation mode +		 * search is done, allow rate adjustment next time. */ +		lq_sta->search_better_tbl = 0; +		done_search = 1;	/* Don't switch modes below! */ +		goto lq_update; +	} + +	/* (Else) not in search of better modulation mode, try for better +	 * starting rate, while staying in this mode. */ +	high_low = rs_get_adjacent_rate(mvm, index, rate_scale_index_msk, +					tbl->lq_type); +	low = high_low & 0xff; +	high = (high_low >> 8) & 0xff; + +	/* If user set max rate, dont allow higher than user constrain */ +	if ((lq_sta->max_rate_idx != -1) && +	    (lq_sta->max_rate_idx < high)) +		high = IWL_RATE_INVALID; + +	sr = window->success_ratio; + +	/* Collect measured throughputs for current and adjacent rates */ +	current_tpt = window->average_tpt; +	if (low != IWL_RATE_INVALID) +		low_tpt = tbl->win[low].average_tpt; +	if (high != IWL_RATE_INVALID) +		high_tpt = tbl->win[high].average_tpt; + +	scale_action = 0; + +	/* Too many failures, decrease rate */ +	if ((sr <= IWL_RATE_DECREASE_TH) || (current_tpt == 0)) { +		IWL_DEBUG_RATE(mvm, +			       "decrease rate because of low success_ratio\n"); +		scale_action = -1; +	/* No throughput measured yet for adjacent rates; try increase. */ +	} else if ((low_tpt == IWL_INVALID_VALUE) && +		   (high_tpt == IWL_INVALID_VALUE)) { +		if (high != IWL_RATE_INVALID && sr >= IWL_RATE_INCREASE_TH) +			scale_action = 1; +		else if (low != IWL_RATE_INVALID) +			scale_action = 0; +	} + +	/* Both adjacent throughputs are measured, but neither one has better +	 * throughput; we're using the best rate, don't change it! */ +	else if ((low_tpt != IWL_INVALID_VALUE) && +		 (high_tpt != IWL_INVALID_VALUE) && +		 (low_tpt < current_tpt) && +		 (high_tpt < current_tpt)) +		scale_action = 0; + +	/* At least one adjacent rate's throughput is measured, +	 * and may have better performance. */ +	else { +		/* Higher adjacent rate's throughput is measured */ +		if (high_tpt != IWL_INVALID_VALUE) { +			/* Higher rate has better throughput */ +			if (high_tpt > current_tpt && +			    sr >= IWL_RATE_INCREASE_TH) { +				scale_action = 1; +			} else { +				scale_action = 0; +			} + +		/* Lower adjacent rate's throughput is measured */ +		} else if (low_tpt != IWL_INVALID_VALUE) { +			/* Lower rate has better throughput */ +			if (low_tpt > current_tpt) { +				IWL_DEBUG_RATE(mvm, +					       "decrease rate because of low tpt\n"); +				scale_action = -1; +			} else if (sr >= IWL_RATE_INCREASE_TH) { +				scale_action = 1; +			} +		} +	} + +	/* Sanity check; asked for decrease, but success rate or throughput +	 * has been good at old rate.  Don't change it. */ +	if ((scale_action == -1) && (low != IWL_RATE_INVALID) && +	    ((sr > IWL_RATE_HIGH_TH) || +	     (current_tpt > (100 * tbl->expected_tpt[low])))) +		scale_action = 0; + +	switch (scale_action) { +	case -1: +		/* Decrease starting rate, update uCode's rate table */ +		if (low != IWL_RATE_INVALID) { +			update_lq = 1; +			index = low; +		} + +		break; +	case 1: +		/* Increase starting rate, update uCode's rate table */ +		if (high != IWL_RATE_INVALID) { +			update_lq = 1; +			index = high; +		} + +		break; +	case 0: +		/* No change */ +	default: +		break; +	} + +	IWL_DEBUG_RATE(mvm, +		       "choose rate scale index %d action %d low %d high %d type %d\n", +		       index, scale_action, low, high, tbl->lq_type); + +lq_update: +	/* Replace uCode's rate table for the destination station. */ +	if (update_lq) +		rs_update_rate_tbl(mvm, lq_sta, tbl, index, is_green); + +	rs_stay_in_table(lq_sta, false); + +	/* +	 * Search for new modulation mode if we're: +	 * 1)  Not changing rates right now +	 * 2)  Not just finishing up a search +	 * 3)  Allowing a new search +	 */ +	if (!update_lq && !done_search && +	    !lq_sta->stay_in_tbl && window->counter) { +		/* Save current throughput to compare with "search" throughput*/ +		lq_sta->last_tpt = current_tpt; + +		/* Select a new "search" modulation mode to try. +		 * If one is found, set up the new "search" table. */ +		if (is_legacy(tbl->lq_type)) +			rs_move_legacy_other(mvm, lq_sta, sta, index); +		else if (is_siso(tbl->lq_type)) +			rs_move_siso_to_other(mvm, lq_sta, sta, index); +		else if (is_mimo2(tbl->lq_type)) +			rs_move_mimo2_to_other(mvm, lq_sta, sta, index); +		else +			rs_move_mimo3_to_other(mvm, lq_sta, sta, index); + +		/* If new "search" mode was selected, set up in uCode table */ +		if (lq_sta->search_better_tbl) { +			/* Access the "search" table, clear its history. */ +			tbl = &(lq_sta->lq_info[(1 - lq_sta->active_tbl)]); +			for (i = 0; i < IWL_RATE_COUNT; i++) +				rs_rate_scale_clear_window(&(tbl->win[i])); + +			/* Use new "search" start rate */ +			index = iwl_hwrate_to_plcp_idx(tbl->current_rate); + +			IWL_DEBUG_RATE(mvm, +				       "Switch current  mcs: %X index: %d\n", +				       tbl->current_rate, index); +			rs_fill_link_cmd(mvm, lq_sta, tbl->current_rate); +			iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_ASYNC, false); +		} else { +			done_search = 1; +		} +	} + +	if (done_search && !lq_sta->stay_in_tbl) { +		/* If the "active" (non-search) mode was legacy, +		 * and we've tried switching antennas, +		 * but we haven't been able to try HT modes (not available), +		 * stay with best antenna legacy modulation for a while +		 * before next round of mode comparisons. */ +		tbl1 = &(lq_sta->lq_info[lq_sta->active_tbl]); +		if (is_legacy(tbl1->lq_type) && !sta->ht_cap.ht_supported && +		    lq_sta->action_counter > tbl1->max_search) { +			IWL_DEBUG_RATE(mvm, "LQ: STAY in legacy table\n"); +			rs_set_stay_in_table(mvm, 1, lq_sta); +		} + +		/* If we're in an HT mode, and all 3 mode switch actions +		 * have been tried and compared, stay in this best modulation +		 * mode for a while before next round of mode comparisons. */ +		if (lq_sta->enable_counter && +		    (lq_sta->action_counter >= tbl1->max_search)) { +			if ((lq_sta->last_tpt > IWL_AGG_TPT_THREHOLD) && +			    (lq_sta->tx_agg_tid_en & (1 << tid)) && +			    (tid != IWL_MAX_TID_COUNT)) { +				tid_data = &sta_priv->tid_data[tid]; +				if (tid_data->state == IWL_AGG_OFF) { +					IWL_DEBUG_RATE(mvm, +						       "try to aggregate tid %d\n", +						       tid); +					rs_tl_turn_on_agg(mvm, tid, +							  lq_sta, sta); +				} +			} +			rs_set_stay_in_table(mvm, 0, lq_sta); +		} +	} + +out: +	tbl->current_rate = rate_n_flags_from_tbl(mvm, tbl, index, is_green); +	lq_sta->last_txrate_idx = index; +} + +/** + * rs_initialize_lq - Initialize a station's hardware rate table + * + * The uCode's station table contains a table of fallback rates + * for automatic fallback during transmission. + * + * NOTE: This sets up a default set of values.  These will be replaced later + *       if the driver's iwl-agn-rs rate scaling algorithm is used, instead of + *       rc80211_simple. + * + * NOTE: Run REPLY_ADD_STA command to set up station table entry, before + *       calling this function (which runs REPLY_TX_LINK_QUALITY_CMD, + *       which requires station table entry to exist). + */ +static void rs_initialize_lq(struct iwl_mvm *mvm, +			     struct ieee80211_sta *sta, +			     struct iwl_lq_sta *lq_sta, +			     enum ieee80211_band band) +{ +	struct iwl_scale_tbl_info *tbl; +	int rate_idx; +	int i; +	u32 rate; +	u8 use_green = rs_use_green(sta); +	u8 active_tbl = 0; +	u8 valid_tx_ant; + +	if (!sta || !lq_sta) +		return; + +	i = lq_sta->last_txrate_idx; + +	valid_tx_ant = mvm->nvm_data->valid_tx_ant; + +	if (!lq_sta->search_better_tbl) +		active_tbl = lq_sta->active_tbl; +	else +		active_tbl = 1 - lq_sta->active_tbl; + +	tbl = &(lq_sta->lq_info[active_tbl]); + +	if ((i < 0) || (i >= IWL_RATE_COUNT)) +		i = 0; + +	rate = iwl_rates[i].plcp; +	tbl->ant_type = first_antenna(valid_tx_ant); +	rate |= tbl->ant_type << RATE_MCS_ANT_POS; + +	if (i >= IWL_FIRST_CCK_RATE && i <= IWL_LAST_CCK_RATE) +		rate |= RATE_MCS_CCK_MSK; + +	rs_get_tbl_info_from_mcs(rate, band, tbl, &rate_idx); +	if (!rs_is_valid_ant(valid_tx_ant, tbl->ant_type)) +		rs_toggle_antenna(valid_tx_ant, &rate, tbl); + +	rate = rate_n_flags_from_tbl(mvm, tbl, rate_idx, use_green); +	tbl->current_rate = rate; +	rs_set_expected_tpt_table(lq_sta, tbl); +	rs_fill_link_cmd(NULL, lq_sta, rate); +	/* TODO restore station should remember the lq cmd */ +	iwl_mvm_send_lq_cmd(mvm, &lq_sta->lq, CMD_SYNC, true); +} + +static void rs_get_rate(void *mvm_r, struct ieee80211_sta *sta, void *mvm_sta, +			struct ieee80211_tx_rate_control *txrc) +{ +	struct sk_buff *skb = txrc->skb; +	struct ieee80211_supported_band *sband = txrc->sband; +	struct iwl_op_mode *op_mode __maybe_unused = +			(struct iwl_op_mode *)mvm_r; +	struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	struct iwl_lq_sta *lq_sta = mvm_sta; +	int rate_idx; + +	IWL_DEBUG_RATE_LIMIT(mvm, "rate scale calculate new rate for skb\n"); + +	/* Get max rate if user set max rate */ +	if (lq_sta) { +		lq_sta->max_rate_idx = txrc->max_rate_idx; +		if ((sband->band == IEEE80211_BAND_5GHZ) && +		    (lq_sta->max_rate_idx != -1)) +			lq_sta->max_rate_idx += IWL_FIRST_OFDM_RATE; +		if ((lq_sta->max_rate_idx < 0) || +		    (lq_sta->max_rate_idx >= IWL_RATE_COUNT)) +			lq_sta->max_rate_idx = -1; +	} + +	/* Treat uninitialized rate scaling data same as non-existing. */ +	if (lq_sta && !lq_sta->drv) { +		IWL_DEBUG_RATE(mvm, "Rate scaling not initialized yet.\n"); +		mvm_sta = NULL; +	} + +	/* Send management frames and NO_ACK data using lowest rate. */ +	if (rate_control_send_low(sta, mvm_sta, txrc)) +		return; + +	rate_idx  = lq_sta->last_txrate_idx; + +	if (lq_sta->last_rate_n_flags & RATE_MCS_HT_MSK) { +		rate_idx -= IWL_FIRST_OFDM_RATE; +		/* 6M and 9M shared same MCS index */ +		rate_idx = (rate_idx > 0) ? (rate_idx - 1) : 0; +		if (rs_extract_rate(lq_sta->last_rate_n_flags) >= +		    IWL_RATE_MIMO3_6M_PLCP) +			rate_idx = rate_idx + (2 * MCS_INDEX_PER_STREAM); +		else if (rs_extract_rate(lq_sta->last_rate_n_flags) >= +			 IWL_RATE_MIMO2_6M_PLCP) +			rate_idx = rate_idx + MCS_INDEX_PER_STREAM; +		info->control.rates[0].flags = IEEE80211_TX_RC_MCS; +		if (lq_sta->last_rate_n_flags & RATE_MCS_SGI_MSK) +			info->control.rates[0].flags |= IEEE80211_TX_RC_SHORT_GI; +		if (lq_sta->last_rate_n_flags & RATE_MCS_CHAN_WIDTH_40) /* TODO */ +			info->control.rates[0].flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; +		if (lq_sta->last_rate_n_flags & RATE_HT_MCS_GF_MSK) +			info->control.rates[0].flags |= IEEE80211_TX_RC_GREEN_FIELD; +	} else { +		/* Check for invalid rates */ +		if ((rate_idx < 0) || (rate_idx >= IWL_RATE_COUNT_LEGACY) || +		    ((sband->band == IEEE80211_BAND_5GHZ) && +		     (rate_idx < IWL_FIRST_OFDM_RATE))) +			rate_idx = rate_lowest_index(sband, sta); +		/* On valid 5 GHz rate, adjust index */ +		else if (sband->band == IEEE80211_BAND_5GHZ) +			rate_idx -= IWL_FIRST_OFDM_RATE; +		info->control.rates[0].flags = 0; +	} +	info->control.rates[0].idx = rate_idx; +} + +static void *rs_alloc_sta(void *mvm_rate, struct ieee80211_sta *sta, +			  gfp_t gfp) +{ +	struct iwl_mvm_sta *sta_priv = (struct iwl_mvm_sta *)sta->drv_priv; +	struct iwl_op_mode *op_mode __maybe_unused = +			(struct iwl_op_mode *)mvm_rate; +	struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + +	IWL_DEBUG_RATE(mvm, "create station rate scale window\n"); + +	return &sta_priv->lq_sta; +} + +/* + * Called after adding a new station to initialize rate scaling + */ +void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, struct ieee80211_sta *sta, +			  enum ieee80211_band band) +{ +	int i, j; +	struct ieee80211_hw *hw = mvm->hw; +	struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap; +	struct iwl_mvm_sta *sta_priv; +	struct iwl_lq_sta *lq_sta; +	struct ieee80211_supported_band *sband; +	unsigned long supp; /* must be unsigned long for for_each_set_bit */ + +	sta_priv = (struct iwl_mvm_sta *)sta->drv_priv; +	lq_sta = &sta_priv->lq_sta; +	sband = hw->wiphy->bands[band]; + +	lq_sta->lq.sta_id = sta_priv->sta_id; + +	for (j = 0; j < LQ_SIZE; j++) +		for (i = 0; i < IWL_RATE_COUNT; i++) +			rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + +	lq_sta->flush_timer = 0; +	lq_sta->supp_rates = sta->supp_rates[sband->band]; +	for (j = 0; j < LQ_SIZE; j++) +		for (i = 0; i < IWL_RATE_COUNT; i++) +			rs_rate_scale_clear_window(&lq_sta->lq_info[j].win[i]); + +	IWL_DEBUG_RATE(mvm, +		       "LQ: *** rate scale station global init for station %d ***\n", +		       sta_priv->sta_id); +	/* TODO: what is a good starting rate for STA? About middle? Maybe not +	 * the lowest or the highest rate.. Could consider using RSSI from +	 * previous packets? Need to have IEEE 802.1X auth succeed immediately +	 * after assoc.. */ + +	lq_sta->max_rate_idx = -1; +	lq_sta->missed_rate_counter = IWL_MISSED_RATE_MAX; +	lq_sta->is_green = rs_use_green(sta); +	lq_sta->band = sband->band; +	/* +	 * active legacy rates as per supported rates bitmap +	 */ +	supp = sta->supp_rates[sband->band]; +	lq_sta->active_legacy_rate = 0; +	for_each_set_bit(i, &supp, BITS_PER_LONG) +		lq_sta->active_legacy_rate |= BIT(sband->bitrates[i].hw_value); + +	/* +	 * active_siso_rate mask includes 9 MBits (bit 5), and CCK (bits 0-3), +	 * supp_rates[] does not; shift to convert format, force 9 MBits off. +	 */ +	lq_sta->active_siso_rate = ht_cap->mcs.rx_mask[0] << 1; +	lq_sta->active_siso_rate |= ht_cap->mcs.rx_mask[0] & 0x1; +	lq_sta->active_siso_rate &= ~((u16)0x2); +	lq_sta->active_siso_rate <<= IWL_FIRST_OFDM_RATE; + +	/* Same here */ +	lq_sta->active_mimo2_rate = ht_cap->mcs.rx_mask[1] << 1; +	lq_sta->active_mimo2_rate |= ht_cap->mcs.rx_mask[1] & 0x1; +	lq_sta->active_mimo2_rate &= ~((u16)0x2); +	lq_sta->active_mimo2_rate <<= IWL_FIRST_OFDM_RATE; + +	lq_sta->active_mimo3_rate = ht_cap->mcs.rx_mask[2] << 1; +	lq_sta->active_mimo3_rate |= ht_cap->mcs.rx_mask[2] & 0x1; +	lq_sta->active_mimo3_rate &= ~((u16)0x2); +	lq_sta->active_mimo3_rate <<= IWL_FIRST_OFDM_RATE; + +	IWL_DEBUG_RATE(mvm, +		       "SISO-RATE=%X MIMO2-RATE=%X MIMO3-RATE=%X\n", +		       lq_sta->active_siso_rate, +		       lq_sta->active_mimo2_rate, +		       lq_sta->active_mimo3_rate); + +	/* These values will be overridden later */ +	lq_sta->lq.single_stream_ant_msk = +		first_antenna(mvm->nvm_data->valid_tx_ant); +	lq_sta->lq.dual_stream_ant_msk = +		mvm->nvm_data->valid_tx_ant & +		~first_antenna(mvm->nvm_data->valid_tx_ant); +	if (!lq_sta->lq.dual_stream_ant_msk) { +		lq_sta->lq.dual_stream_ant_msk = ANT_AB; +	} else if (num_of_ant(mvm->nvm_data->valid_tx_ant) == 2) { +		lq_sta->lq.dual_stream_ant_msk = +			mvm->nvm_data->valid_tx_ant; +	} + +	/* as default allow aggregation for all tids */ +	lq_sta->tx_agg_tid_en = IWL_AGG_ALL_TID; +	lq_sta->drv = mvm; + +	/* Set last_txrate_idx to lowest rate */ +	lq_sta->last_txrate_idx = rate_lowest_index(sband, sta); +	if (sband->band == IEEE80211_BAND_5GHZ) +		lq_sta->last_txrate_idx += IWL_FIRST_OFDM_RATE; +	lq_sta->is_agg = 0; +#ifdef CONFIG_MAC80211_DEBUGFS +	lq_sta->dbg_fixed_rate = 0; +#endif + +	rs_initialize_lq(mvm, sta, lq_sta, band); +} + +static void rs_fill_link_cmd(struct iwl_mvm *mvm, +			     struct iwl_lq_sta *lq_sta, u32 new_rate) +{ +	struct iwl_scale_tbl_info tbl_type; +	int index = 0; +	int rate_idx; +	int repeat_rate = 0; +	u8 ant_toggle_cnt = 0; +	u8 use_ht_possible = 1; +	u8 valid_tx_ant = 0; +	struct iwl_lq_cmd *lq_cmd = &lq_sta->lq; + +	/* Override starting rate (index 0) if needed for debug purposes */ +	rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + +	/* Interpret new_rate (rate_n_flags) */ +	rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, +				 &tbl_type, &rate_idx); + +	/* How many times should we repeat the initial rate? */ +	if (is_legacy(tbl_type.lq_type)) { +		ant_toggle_cnt = 1; +		repeat_rate = IWL_NUMBER_TRY; +	} else { +		repeat_rate = min(IWL_HT_NUMBER_TRY, +				  LINK_QUAL_AGG_DISABLE_START_DEF - 1); +	} + +	lq_cmd->mimo_delim = is_mimo(tbl_type.lq_type) ? 1 : 0; + +	/* Fill 1st table entry (index 0) */ +	lq_cmd->rs_table[index] = cpu_to_le32(new_rate); + +	if (num_of_ant(tbl_type.ant_type) == 1) +		lq_cmd->single_stream_ant_msk = tbl_type.ant_type; +	else if (num_of_ant(tbl_type.ant_type) == 2) +		lq_cmd->dual_stream_ant_msk = tbl_type.ant_type; +	/* otherwise we don't modify the existing value */ + +	index++; +	repeat_rate--; +	if (mvm) +		valid_tx_ant = mvm->nvm_data->valid_tx_ant; + +	/* Fill rest of rate table */ +	while (index < LINK_QUAL_MAX_RETRY_NUM) { +		/* Repeat initial/next rate. +		 * For legacy IWL_NUMBER_TRY == 1, this loop will not execute. +		 * For HT IWL_HT_NUMBER_TRY == 3, this executes twice. */ +		while (repeat_rate > 0 && (index < LINK_QUAL_MAX_RETRY_NUM)) { +			if (is_legacy(tbl_type.lq_type)) { +				if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) +					ant_toggle_cnt++; +				else if (mvm && +					 rs_toggle_antenna(valid_tx_ant, +							&new_rate, &tbl_type)) +					ant_toggle_cnt = 1; +			} + +			/* Override next rate if needed for debug purposes */ +			rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + +			/* Fill next table entry */ +			lq_cmd->rs_table[index] = +					cpu_to_le32(new_rate); +			repeat_rate--; +			index++; +		} + +		rs_get_tbl_info_from_mcs(new_rate, lq_sta->band, &tbl_type, +					 &rate_idx); + + +		/* Indicate to uCode which entries might be MIMO. +		 * If initial rate was MIMO, this will finally end up +		 * as (IWL_HT_NUMBER_TRY * 2), after 2nd pass, otherwise 0. */ +		if (is_mimo(tbl_type.lq_type)) +			lq_cmd->mimo_delim = index; + +		/* Get next rate */ +		new_rate = rs_get_lower_rate(lq_sta, &tbl_type, rate_idx, +					     use_ht_possible); + +		/* How many times should we repeat the next rate? */ +		if (is_legacy(tbl_type.lq_type)) { +			if (ant_toggle_cnt < NUM_TRY_BEFORE_ANT_TOGGLE) +				ant_toggle_cnt++; +			else if (mvm && +				 rs_toggle_antenna(valid_tx_ant, +						   &new_rate, &tbl_type)) +				ant_toggle_cnt = 1; + +			repeat_rate = IWL_NUMBER_TRY; +		} else { +			repeat_rate = IWL_HT_NUMBER_TRY; +		} + +		/* Don't allow HT rates after next pass. +		 * rs_get_lower_rate() will change type to LQ_A or LQ_G. */ +		use_ht_possible = 0; + +		/* Override next rate if needed for debug purposes */ +		rs_dbgfs_set_mcs(lq_sta, &new_rate, index); + +		/* Fill next table entry */ +		lq_cmd->rs_table[index] = cpu_to_le32(new_rate); + +		index++; +		repeat_rate--; +	} + +	lq_cmd->agg_frame_cnt_limit = LINK_QUAL_AGG_FRAME_LIMIT_DEF; +	lq_cmd->agg_disable_start_th = LINK_QUAL_AGG_DISABLE_START_DEF; + +	lq_cmd->agg_time_limit = +		cpu_to_le16(LINK_QUAL_AGG_TIME_LIMIT_DEF); +} + +static void *rs_alloc(struct ieee80211_hw *hw, struct dentry *debugfsdir) +{ +	return hw->priv; +} +/* rate scale requires free function to be implemented */ +static void rs_free(void *mvm_rate) +{ +	return; +} + +static void rs_free_sta(void *mvm_r, struct ieee80211_sta *sta, +			void *mvm_sta) +{ +	struct iwl_op_mode *op_mode __maybe_unused = mvm_r; +	struct iwl_mvm *mvm __maybe_unused = IWL_OP_MODE_GET_MVM(op_mode); + +	IWL_DEBUG_RATE(mvm, "enter\n"); +	IWL_DEBUG_RATE(mvm, "leave\n"); +} + +#ifdef CONFIG_MAC80211_DEBUGFS +static void rs_dbgfs_set_mcs(struct iwl_lq_sta *lq_sta, +			     u32 *rate_n_flags, int index) +{ +	struct iwl_mvm *mvm; +	u8 valid_tx_ant; +	u8 ant_sel_tx; + +	mvm = lq_sta->drv; +	valid_tx_ant = mvm->nvm_data->valid_tx_ant; +	if (lq_sta->dbg_fixed_rate) { +		ant_sel_tx = +		  ((lq_sta->dbg_fixed_rate & RATE_MCS_ANT_ABC_MSK) +		  >> RATE_MCS_ANT_POS); +		if ((valid_tx_ant & ant_sel_tx) == ant_sel_tx) { +			*rate_n_flags = lq_sta->dbg_fixed_rate; +			IWL_DEBUG_RATE(mvm, "Fixed rate ON\n"); +		} else { +			lq_sta->dbg_fixed_rate = 0; +			IWL_ERR(mvm, +				"Invalid antenna selection 0x%X, Valid is 0x%X\n", +				ant_sel_tx, valid_tx_ant); +			IWL_DEBUG_RATE(mvm, "Fixed rate OFF\n"); +		} +	} else { +		IWL_DEBUG_RATE(mvm, "Fixed rate OFF\n"); +	} +} + +static ssize_t rs_sta_dbgfs_scale_table_write(struct file *file, +			const char __user *user_buf, size_t count, loff_t *ppos) +{ +	struct iwl_lq_sta *lq_sta = file->private_data; +	struct iwl_mvm *mvm; +	char buf[64]; +	size_t buf_size; +	u32 parsed_rate; + + +	mvm = lq_sta->drv; +	memset(buf, 0, sizeof(buf)); +	buf_size = min(count, sizeof(buf) -  1); +	if (copy_from_user(buf, user_buf, buf_size)) +		return -EFAULT; + +	if (sscanf(buf, "%x", &parsed_rate) == 1) +		lq_sta->dbg_fixed_rate = parsed_rate; +	else +		lq_sta->dbg_fixed_rate = 0; + +	rs_program_fix_rate(mvm, lq_sta); + +	return count; +} + +static ssize_t rs_sta_dbgfs_scale_table_read(struct file *file, +			char __user *user_buf, size_t count, loff_t *ppos) +{ +	char *buff; +	int desc = 0; +	int i = 0; +	int index = 0; +	ssize_t ret; + +	struct iwl_lq_sta *lq_sta = file->private_data; +	struct iwl_mvm *mvm; +	struct iwl_scale_tbl_info *tbl = &(lq_sta->lq_info[lq_sta->active_tbl]); + +	mvm = lq_sta->drv; +	buff = kmalloc(1024, GFP_KERNEL); +	if (!buff) +		return -ENOMEM; + +	desc += sprintf(buff+desc, "sta_id %d\n", lq_sta->lq.sta_id); +	desc += sprintf(buff+desc, "failed=%d success=%d rate=0%X\n", +			lq_sta->total_failed, lq_sta->total_success, +			lq_sta->active_legacy_rate); +	desc += sprintf(buff+desc, "fixed rate 0x%X\n", +			lq_sta->dbg_fixed_rate); +	desc += sprintf(buff+desc, "valid_tx_ant %s%s%s\n", +	    (mvm->nvm_data->valid_tx_ant & ANT_A) ? "ANT_A," : "", +	    (mvm->nvm_data->valid_tx_ant & ANT_B) ? "ANT_B," : "", +	    (mvm->nvm_data->valid_tx_ant & ANT_C) ? "ANT_C" : ""); +	desc += sprintf(buff+desc, "lq type %s\n", +	   (is_legacy(tbl->lq_type)) ? "legacy" : "HT"); +	if (is_Ht(tbl->lq_type)) { +		desc += sprintf(buff+desc, " %s", +		   (is_siso(tbl->lq_type)) ? "SISO" : +		   ((is_mimo2(tbl->lq_type)) ? "MIMO2" : "MIMO3")); +		   desc += sprintf(buff+desc, " %s", +		   (tbl->is_ht40) ? "40MHz" : "20MHz"); +		   desc += sprintf(buff+desc, " %s %s %s\n", +				   (tbl->is_SGI) ? "SGI" : "", +		   (lq_sta->is_green) ? "GF enabled" : "", +		   (lq_sta->is_agg) ? "AGG on" : ""); +	} +	desc += sprintf(buff+desc, "last tx rate=0x%X\n", +			lq_sta->last_rate_n_flags); +	desc += sprintf(buff+desc, +			"general: flags=0x%X mimo-d=%d s-ant0x%x d-ant=0x%x\n", +			lq_sta->lq.flags, +			lq_sta->lq.mimo_delim, +			lq_sta->lq.single_stream_ant_msk, +			lq_sta->lq.dual_stream_ant_msk); + +	desc += sprintf(buff+desc, +			"agg: time_limit=%d dist_start_th=%d frame_cnt_limit=%d\n", +			le16_to_cpu(lq_sta->lq.agg_time_limit), +			lq_sta->lq.agg_disable_start_th, +			lq_sta->lq.agg_frame_cnt_limit); + +	desc += sprintf(buff+desc, +			"Start idx [0]=0x%x [1]=0x%x [2]=0x%x [3]=0x%x\n", +			lq_sta->lq.initial_rate_index[0], +			lq_sta->lq.initial_rate_index[1], +			lq_sta->lq.initial_rate_index[2], +			lq_sta->lq.initial_rate_index[3]); + +	for (i = 0; i < LINK_QUAL_MAX_RETRY_NUM; i++) { +		index = iwl_hwrate_to_plcp_idx( +			le32_to_cpu(lq_sta->lq.rs_table[i])); +		if (is_legacy(tbl->lq_type)) { +			desc += sprintf(buff+desc, " rate[%d] 0x%X %smbps\n", +					i, le32_to_cpu(lq_sta->lq.rs_table[i]), +					iwl_rate_mcs[index].mbps); +		} else { +			desc += sprintf(buff+desc, +					" rate[%d] 0x%X %smbps (%s)\n", +					i, le32_to_cpu(lq_sta->lq.rs_table[i]), +					iwl_rate_mcs[index].mbps, +					iwl_rate_mcs[index].mcs); +		} +	} + +	ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); +	kfree(buff); +	return ret; +} + +static const struct file_operations rs_sta_dbgfs_scale_table_ops = { +	.write = rs_sta_dbgfs_scale_table_write, +	.read = rs_sta_dbgfs_scale_table_read, +	.open = simple_open, +	.llseek = default_llseek, +}; +static ssize_t rs_sta_dbgfs_stats_table_read(struct file *file, +			char __user *user_buf, size_t count, loff_t *ppos) +{ +	char *buff; +	int desc = 0; +	int i, j; +	ssize_t ret; + +	struct iwl_lq_sta *lq_sta = file->private_data; + +	buff = kmalloc(1024, GFP_KERNEL); +	if (!buff) +		return -ENOMEM; + +	for (i = 0; i < LQ_SIZE; i++) { +		desc += sprintf(buff+desc, +				"%s type=%d SGI=%d HT40=%d DUP=0 GF=%d\n" +				"rate=0x%X\n", +				lq_sta->active_tbl == i ? "*" : "x", +				lq_sta->lq_info[i].lq_type, +				lq_sta->lq_info[i].is_SGI, +				lq_sta->lq_info[i].is_ht40, +				lq_sta->is_green, +				lq_sta->lq_info[i].current_rate); +		for (j = 0; j < IWL_RATE_COUNT; j++) { +			desc += sprintf(buff+desc, +				"counter=%d success=%d %%=%d\n", +				lq_sta->lq_info[i].win[j].counter, +				lq_sta->lq_info[i].win[j].success_counter, +				lq_sta->lq_info[i].win[j].success_ratio); +		} +	} +	ret = simple_read_from_buffer(user_buf, count, ppos, buff, desc); +	kfree(buff); +	return ret; +} + +static const struct file_operations rs_sta_dbgfs_stats_table_ops = { +	.read = rs_sta_dbgfs_stats_table_read, +	.open = simple_open, +	.llseek = default_llseek, +}; + +static ssize_t rs_sta_dbgfs_rate_scale_data_read(struct file *file, +			char __user *user_buf, size_t count, loff_t *ppos) +{ +	struct iwl_lq_sta *lq_sta = file->private_data; +	struct iwl_scale_tbl_info *tbl = &lq_sta->lq_info[lq_sta->active_tbl]; +	char buff[120]; +	int desc = 0; + +	if (is_Ht(tbl->lq_type)) +		desc += sprintf(buff+desc, +				"Bit Rate= %d Mb/s\n", +				tbl->expected_tpt[lq_sta->last_txrate_idx]); +	else +		desc += sprintf(buff+desc, +				"Bit Rate= %d Mb/s\n", +				iwl_rates[lq_sta->last_txrate_idx].ieee >> 1); + +	return simple_read_from_buffer(user_buf, count, ppos, buff, desc); +} + +static const struct file_operations rs_sta_dbgfs_rate_scale_data_ops = { +	.read = rs_sta_dbgfs_rate_scale_data_read, +	.open = simple_open, +	.llseek = default_llseek, +}; + +static void rs_add_debugfs(void *mvm, void *mvm_sta, struct dentry *dir) +{ +	struct iwl_lq_sta *lq_sta = mvm_sta; +	lq_sta->rs_sta_dbgfs_scale_table_file = +		debugfs_create_file("rate_scale_table", S_IRUSR | S_IWUSR, dir, +				    lq_sta, &rs_sta_dbgfs_scale_table_ops); +	lq_sta->rs_sta_dbgfs_stats_table_file = +		debugfs_create_file("rate_stats_table", S_IRUSR, dir, +				    lq_sta, &rs_sta_dbgfs_stats_table_ops); +	lq_sta->rs_sta_dbgfs_rate_scale_data_file = +		debugfs_create_file("rate_scale_data", S_IRUSR, dir, +				    lq_sta, &rs_sta_dbgfs_rate_scale_data_ops); +	lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file = +		debugfs_create_u8("tx_agg_tid_enable", S_IRUSR | S_IWUSR, dir, +				  &lq_sta->tx_agg_tid_en); +} + +static void rs_remove_debugfs(void *mvm, void *mvm_sta) +{ +	struct iwl_lq_sta *lq_sta = mvm_sta; +	debugfs_remove(lq_sta->rs_sta_dbgfs_scale_table_file); +	debugfs_remove(lq_sta->rs_sta_dbgfs_stats_table_file); +	debugfs_remove(lq_sta->rs_sta_dbgfs_rate_scale_data_file); +	debugfs_remove(lq_sta->rs_sta_dbgfs_tx_agg_tid_en_file); +} +#endif + +/* + * Initialization of rate scaling information is done by driver after + * the station is added. Since mac80211 calls this function before a + * station is added we ignore it. + */ +static void rs_rate_init_stub(void *mvm_r, +				 struct ieee80211_supported_band *sband, +				 struct ieee80211_sta *sta, void *mvm_sta) +{ +} +static struct rate_control_ops rs_mvm_ops = { +	.module = NULL, +	.name = RS_NAME, +	.tx_status = rs_tx_status, +	.get_rate = rs_get_rate, +	.rate_init = rs_rate_init_stub, +	.alloc = rs_alloc, +	.free = rs_free, +	.alloc_sta = rs_alloc_sta, +	.free_sta = rs_free_sta, +#ifdef CONFIG_MAC80211_DEBUGFS +	.add_sta_debugfs = rs_add_debugfs, +	.remove_sta_debugfs = rs_remove_debugfs, +#endif +}; + +int iwl_mvm_rate_control_register(void) +{ +	return ieee80211_rate_control_register(&rs_mvm_ops); +} + +void iwl_mvm_rate_control_unregister(void) +{ +	ieee80211_rate_control_unregister(&rs_mvm_ops); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/rs.h b/drivers/net/wireless/iwlwifi/mvm/rs.h new file mode 100644 index 00000000000..219c6857cc0 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rs.h @@ -0,0 +1,393 @@ +/****************************************************************************** + * + * Copyright(c) 2003 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE. + * + * Contact Information: + *  Intel Linux Wireless <ilw@linux.intel.com> + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + *****************************************************************************/ + +#ifndef __rs_h__ +#define __rs_h__ + +#include <net/mac80211.h> + +#include "iwl-config.h" + +#include "fw-api.h" +#include "iwl-trans.h" + +struct iwl_rs_rate_info { +	u8 plcp;	/* uCode API:  IWL_RATE_6M_PLCP, etc. */ +	u8 plcp_siso;	/* uCode API:  IWL_RATE_SISO_6M_PLCP, etc. */ +	u8 plcp_mimo2;	/* uCode API:  IWL_RATE_MIMO2_6M_PLCP, etc. */ +	u8 plcp_mimo3;  /* uCode API:  IWL_RATE_MIMO3_6M_PLCP, etc. */ +	u8 ieee;	/* MAC header:  IWL_RATE_6M_IEEE, etc. */ +	u8 prev_ieee;    /* previous rate in IEEE speeds */ +	u8 next_ieee;    /* next rate in IEEE speeds */ +	u8 prev_rs;      /* previous rate used in rs algo */ +	u8 next_rs;      /* next rate used in rs algo */ +	u8 prev_rs_tgg;  /* previous rate used in TGG rs algo */ +	u8 next_rs_tgg;  /* next rate used in TGG rs algo */ +}; + +#define IWL_RATE_60M_PLCP 3 + +enum { +	IWL_RATE_INVM_INDEX = IWL_RATE_COUNT, +	IWL_RATE_INVALID = IWL_RATE_COUNT, +}; + +#define LINK_QUAL_MAX_RETRY_NUM 16 + +enum { +	IWL_RATE_6M_INDEX_TABLE = 0, +	IWL_RATE_9M_INDEX_TABLE, +	IWL_RATE_12M_INDEX_TABLE, +	IWL_RATE_18M_INDEX_TABLE, +	IWL_RATE_24M_INDEX_TABLE, +	IWL_RATE_36M_INDEX_TABLE, +	IWL_RATE_48M_INDEX_TABLE, +	IWL_RATE_54M_INDEX_TABLE, +	IWL_RATE_1M_INDEX_TABLE, +	IWL_RATE_2M_INDEX_TABLE, +	IWL_RATE_5M_INDEX_TABLE, +	IWL_RATE_11M_INDEX_TABLE, +	IWL_RATE_INVM_INDEX_TABLE = IWL_RATE_INVM_INDEX - 1, +}; + +/* #define vs. enum to keep from defaulting to 'large integer' */ +#define	IWL_RATE_6M_MASK   (1 << IWL_RATE_6M_INDEX) +#define	IWL_RATE_9M_MASK   (1 << IWL_RATE_9M_INDEX) +#define	IWL_RATE_12M_MASK  (1 << IWL_RATE_12M_INDEX) +#define	IWL_RATE_18M_MASK  (1 << IWL_RATE_18M_INDEX) +#define	IWL_RATE_24M_MASK  (1 << IWL_RATE_24M_INDEX) +#define	IWL_RATE_36M_MASK  (1 << IWL_RATE_36M_INDEX) +#define	IWL_RATE_48M_MASK  (1 << IWL_RATE_48M_INDEX) +#define	IWL_RATE_54M_MASK  (1 << IWL_RATE_54M_INDEX) +#define IWL_RATE_60M_MASK  (1 << IWL_RATE_60M_INDEX) +#define	IWL_RATE_1M_MASK   (1 << IWL_RATE_1M_INDEX) +#define	IWL_RATE_2M_MASK   (1 << IWL_RATE_2M_INDEX) +#define	IWL_RATE_5M_MASK   (1 << IWL_RATE_5M_INDEX) +#define	IWL_RATE_11M_MASK  (1 << IWL_RATE_11M_INDEX) + + +/* uCode API values for OFDM high-throughput (HT) bit rates */ +enum { +	IWL_RATE_SISO_6M_PLCP = 0, +	IWL_RATE_SISO_12M_PLCP = 1, +	IWL_RATE_SISO_18M_PLCP = 2, +	IWL_RATE_SISO_24M_PLCP = 3, +	IWL_RATE_SISO_36M_PLCP = 4, +	IWL_RATE_SISO_48M_PLCP = 5, +	IWL_RATE_SISO_54M_PLCP = 6, +	IWL_RATE_SISO_60M_PLCP = 7, +	IWL_RATE_MIMO2_6M_PLCP  = 0x8, +	IWL_RATE_MIMO2_12M_PLCP = 0x9, +	IWL_RATE_MIMO2_18M_PLCP = 0xa, +	IWL_RATE_MIMO2_24M_PLCP = 0xb, +	IWL_RATE_MIMO2_36M_PLCP = 0xc, +	IWL_RATE_MIMO2_48M_PLCP = 0xd, +	IWL_RATE_MIMO2_54M_PLCP = 0xe, +	IWL_RATE_MIMO2_60M_PLCP = 0xf, +	IWL_RATE_MIMO3_6M_PLCP  = 0x10, +	IWL_RATE_MIMO3_12M_PLCP = 0x11, +	IWL_RATE_MIMO3_18M_PLCP = 0x12, +	IWL_RATE_MIMO3_24M_PLCP = 0x13, +	IWL_RATE_MIMO3_36M_PLCP = 0x14, +	IWL_RATE_MIMO3_48M_PLCP = 0x15, +	IWL_RATE_MIMO3_54M_PLCP = 0x16, +	IWL_RATE_MIMO3_60M_PLCP = 0x17, +	IWL_RATE_SISO_INVM_PLCP, +	IWL_RATE_MIMO2_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, +	IWL_RATE_MIMO3_INVM_PLCP = IWL_RATE_SISO_INVM_PLCP, +}; + +/* MAC header values for bit rates */ +enum { +	IWL_RATE_6M_IEEE  = 12, +	IWL_RATE_9M_IEEE  = 18, +	IWL_RATE_12M_IEEE = 24, +	IWL_RATE_18M_IEEE = 36, +	IWL_RATE_24M_IEEE = 48, +	IWL_RATE_36M_IEEE = 72, +	IWL_RATE_48M_IEEE = 96, +	IWL_RATE_54M_IEEE = 108, +	IWL_RATE_60M_IEEE = 120, +	IWL_RATE_1M_IEEE  = 2, +	IWL_RATE_2M_IEEE  = 4, +	IWL_RATE_5M_IEEE  = 11, +	IWL_RATE_11M_IEEE = 22, +}; + +#define IWL_RATES_MASK ((1 << IWL_RATE_COUNT) - 1) + +#define IWL_INVALID_VALUE    -1 + +#define IWL_MIN_RSSI_VAL                 -100 +#define IWL_MAX_RSSI_VAL                    0 + +/* These values specify how many Tx frame attempts before + * searching for a new modulation mode */ +#define IWL_LEGACY_FAILURE_LIMIT	160 +#define IWL_LEGACY_SUCCESS_LIMIT	480 +#define IWL_LEGACY_TABLE_COUNT		160 + +#define IWL_NONE_LEGACY_FAILURE_LIMIT	400 +#define IWL_NONE_LEGACY_SUCCESS_LIMIT	4500 +#define IWL_NONE_LEGACY_TABLE_COUNT	1500 + +/* Success ratio (ACKed / attempted tx frames) values (perfect is 128 * 100) */ +#define IWL_RS_GOOD_RATIO		12800	/* 100% */ +#define IWL_RATE_SCALE_SWITCH		10880	/*  85% */ +#define IWL_RATE_HIGH_TH		10880	/*  85% */ +#define IWL_RATE_INCREASE_TH		6400	/*  50% */ +#define IWL_RATE_DECREASE_TH		1920	/*  15% */ + +/* possible actions when in legacy mode */ +#define IWL_LEGACY_SWITCH_ANTENNA1      0 +#define IWL_LEGACY_SWITCH_ANTENNA2      1 +#define IWL_LEGACY_SWITCH_SISO          2 +#define IWL_LEGACY_SWITCH_MIMO2_AB      3 +#define IWL_LEGACY_SWITCH_MIMO2_AC      4 +#define IWL_LEGACY_SWITCH_MIMO2_BC      5 +#define IWL_LEGACY_SWITCH_MIMO3_ABC     6 + +/* possible actions when in siso mode */ +#define IWL_SISO_SWITCH_ANTENNA1        0 +#define IWL_SISO_SWITCH_ANTENNA2        1 +#define IWL_SISO_SWITCH_MIMO2_AB        2 +#define IWL_SISO_SWITCH_MIMO2_AC        3 +#define IWL_SISO_SWITCH_MIMO2_BC        4 +#define IWL_SISO_SWITCH_GI              5 +#define IWL_SISO_SWITCH_MIMO3_ABC       6 + + +/* possible actions when in mimo mode */ +#define IWL_MIMO2_SWITCH_ANTENNA1       0 +#define IWL_MIMO2_SWITCH_ANTENNA2       1 +#define IWL_MIMO2_SWITCH_SISO_A         2 +#define IWL_MIMO2_SWITCH_SISO_B         3 +#define IWL_MIMO2_SWITCH_SISO_C         4 +#define IWL_MIMO2_SWITCH_GI             5 +#define IWL_MIMO2_SWITCH_MIMO3_ABC      6 + + +/* possible actions when in mimo3 mode */ +#define IWL_MIMO3_SWITCH_ANTENNA1       0 +#define IWL_MIMO3_SWITCH_ANTENNA2       1 +#define IWL_MIMO3_SWITCH_SISO_A         2 +#define IWL_MIMO3_SWITCH_SISO_B         3 +#define IWL_MIMO3_SWITCH_SISO_C         4 +#define IWL_MIMO3_SWITCH_MIMO2_AB       5 +#define IWL_MIMO3_SWITCH_MIMO2_AC       6 +#define IWL_MIMO3_SWITCH_MIMO2_BC       7 +#define IWL_MIMO3_SWITCH_GI             8 + + +#define IWL_MAX_11N_MIMO3_SEARCH IWL_MIMO3_SWITCH_GI +#define IWL_MAX_SEARCH IWL_MIMO2_SWITCH_MIMO3_ABC + +/*FIXME:RS:add possible actions for MIMO3*/ + +#define IWL_ACTION_LIMIT		3	/* # possible actions */ + +#define LINK_QUAL_AGG_TIME_LIMIT_DEF	(4000) /* 4 milliseconds */ +#define LINK_QUAL_AGG_TIME_LIMIT_MAX	(8000) +#define LINK_QUAL_AGG_TIME_LIMIT_MIN	(100) + +#define LINK_QUAL_AGG_DISABLE_START_DEF	(3) +#define LINK_QUAL_AGG_DISABLE_START_MAX	(255) +#define LINK_QUAL_AGG_DISABLE_START_MIN	(0) + +#define LINK_QUAL_AGG_FRAME_LIMIT_DEF	(63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MAX	(63) +#define LINK_QUAL_AGG_FRAME_LIMIT_MIN	(0) + +#define LQ_SIZE		2	/* 2 mode tables:  "Active" and "Search" */ + +/* load per tid defines for A-MPDU activation */ +#define IWL_AGG_TPT_THREHOLD	0 +#define IWL_AGG_LOAD_THRESHOLD	10 +#define IWL_AGG_ALL_TID		0xff +#define TID_QUEUE_CELL_SPACING	50	/*mS */ +#define TID_QUEUE_MAX_SIZE	20 +#define TID_ROUND_VALUE		5	/* mS */ + +#define TID_MAX_TIME_DIFF ((TID_QUEUE_MAX_SIZE - 1) * TID_QUEUE_CELL_SPACING) +#define TIME_WRAP_AROUND(x, y) (((y) > (x)) ? (y) - (x) : (0-(x)) + (y)) + +enum iwl_table_type { +	LQ_NONE, +	LQ_G,		/* legacy types */ +	LQ_A, +	LQ_SISO,	/* high-throughput types */ +	LQ_MIMO2, +	LQ_MIMO3, +	LQ_MAX, +}; + +#define is_legacy(tbl) (((tbl) == LQ_G) || ((tbl) == LQ_A)) +#define is_siso(tbl) ((tbl) == LQ_SISO) +#define is_mimo2(tbl) ((tbl) == LQ_MIMO2) +#define is_mimo3(tbl) ((tbl) == LQ_MIMO3) +#define is_mimo(tbl) (is_mimo2(tbl) || is_mimo3(tbl)) +#define is_Ht(tbl) (is_siso(tbl) || is_mimo(tbl)) +#define is_a_band(tbl) ((tbl) == LQ_A) +#define is_g_and(tbl) ((tbl) == LQ_G) + +#define IWL_MAX_MCS_DISPLAY_SIZE	12 + +struct iwl_rate_mcs_info { +	char	mbps[IWL_MAX_MCS_DISPLAY_SIZE]; +	char	mcs[IWL_MAX_MCS_DISPLAY_SIZE]; +}; + +/** + * struct iwl_rate_scale_data -- tx success history for one rate + */ +struct iwl_rate_scale_data { +	u64 data;		/* bitmap of successful frames */ +	s32 success_counter;	/* number of frames successful */ +	s32 success_ratio;	/* per-cent * 128  */ +	s32 counter;		/* number of frames attempted */ +	s32 average_tpt;	/* success ratio * expected throughput */ +	unsigned long stamp; +}; + +/** + * struct iwl_scale_tbl_info -- tx params and success history for all rates + * + * There are two of these in struct iwl_lq_sta, + * one for "active", and one for "search". + */ +struct iwl_scale_tbl_info { +	enum iwl_table_type lq_type; +	u8 ant_type; +	u8 is_SGI;	/* 1 = short guard interval */ +	u8 is_ht40;	/* 1 = 40 MHz channel width */ +	u8 action;	/* change modulation; IWL_[LEGACY/SISO/MIMO]_SWITCH_* */ +	u8 max_search;	/* maximun number of tables we can search */ +	s32 *expected_tpt;	/* throughput metrics; expected_tpt_G, etc. */ +	u32 current_rate;  /* rate_n_flags, uCode API format */ +	struct iwl_rate_scale_data win[IWL_RATE_COUNT]; /* rate histories */ +}; + +struct iwl_traffic_load { +	unsigned long time_stamp;	/* age of the oldest statistics */ +	u32 packet_count[TID_QUEUE_MAX_SIZE];   /* packet count in this time +						 * slice */ +	u32 total;			/* total num of packets during the +					 * last TID_MAX_TIME_DIFF */ +	u8 queue_count;			/* number of queues that has +					 * been used since the last cleanup */ +	u8 head;			/* start of the circular buffer */ +}; + +/** + * struct iwl_lq_sta -- driver's rate scaling private structure + * + * Pointer to this gets passed back and forth between driver and mac80211. + */ +struct iwl_lq_sta { +	u8 active_tbl;		/* index of active table, range 0-1 */ +	u8 enable_counter;	/* indicates HT mode */ +	u8 stay_in_tbl;		/* 1: disallow, 0: allow search for new mode */ +	u8 search_better_tbl;	/* 1: currently trying alternate mode */ +	s32 last_tpt; + +	/* The following determine when to search for a new mode */ +	u32 table_count_limit; +	u32 max_failure_limit;	/* # failed frames before new search */ +	u32 max_success_limit;	/* # successful frames before new search */ +	u32 table_count; +	u32 total_failed;	/* total failed frames, any/all rates */ +	u32 total_success;	/* total successful frames, any/all rates */ +	u64 flush_timer;	/* time staying in mode before new search */ + +	u8 action_counter;	/* # mode-switch actions tried */ +	u8 is_green; +	enum ieee80211_band band; + +	/* The following are bitmaps of rates; IWL_RATE_6M_MASK, etc. */ +	u32 supp_rates; +	u16 active_legacy_rate; +	u16 active_siso_rate; +	u16 active_mimo2_rate; +	u16 active_mimo3_rate; +	s8 max_rate_idx;     /* Max rate set by user */ +	u8 missed_rate_counter; + +	struct iwl_lq_cmd lq; +	struct iwl_scale_tbl_info lq_info[LQ_SIZE]; /* "active", "search" */ +	struct iwl_traffic_load load[IWL_MAX_TID_COUNT]; +	u8 tx_agg_tid_en; +#ifdef CONFIG_MAC80211_DEBUGFS +	struct dentry *rs_sta_dbgfs_scale_table_file; +	struct dentry *rs_sta_dbgfs_stats_table_file; +	struct dentry *rs_sta_dbgfs_rate_scale_data_file; +	struct dentry *rs_sta_dbgfs_tx_agg_tid_en_file; +	u32 dbg_fixed_rate; +#endif +	struct iwl_mvm *drv; + +	/* used to be in sta_info */ +	int last_txrate_idx; +	/* last tx rate_n_flags */ +	u32 last_rate_n_flags; +	/* packets destined for this STA are aggregated */ +	u8 is_agg; +	/* BT traffic this sta was last updated in */ +	u8 last_bt_traffic; +}; + +static inline u8 num_of_ant(u8 mask) +{ +	return  !!((mask) & ANT_A) + +		!!((mask) & ANT_B) + +		!!((mask) & ANT_C); +} + +/* Initialize station's rate scaling information after adding station */ +extern void iwl_mvm_rs_rate_init(struct iwl_mvm *mvm, +				 struct ieee80211_sta *sta, +				 enum ieee80211_band band); + +/** + * iwl_rate_control_register - Register the rate control algorithm callbacks + * + * Since the rate control algorithm is hardware specific, there is no need + * or reason to place it as a stand alone module.  The driver can call + * iwl_rate_control_register in order to register the rate control callbacks + * with the mac80211 subsystem.  This should be performed prior to calling + * ieee80211_register_hw + * + */ +extern int iwl_mvm_rate_control_register(void); + +/** + * iwl_rate_control_unregister - Unregister the rate control callbacks + * + * This should be called after calling ieee80211_unregister_hw, but before + * the driver is unloaded. + */ +extern void iwl_mvm_rate_control_unregister(void); + +#endif /* __rs__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/rx.c b/drivers/net/wireless/iwlwifi/mvm/rx.c new file mode 100644 index 00000000000..52da375e574 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/rx.c @@ -0,0 +1,355 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "iwl-trans.h" + +#include "mvm.h" +#include "fw-api.h" + +/* + * iwl_mvm_rx_rx_phy_cmd - REPLY_RX_PHY_CMD handler + * + * Copies the phy information in mvm->last_phy_info, it will be used when the + * actual data will come from the fw in the next packet. + */ +int iwl_mvm_rx_rx_phy_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); + +	memcpy(&mvm->last_phy_info, pkt->data, sizeof(mvm->last_phy_info)); +	mvm->ampdu_ref++; +	return 0; +} + +/* + * iwl_mvm_pass_packet_to_mac80211 - builds the packet for mac80211 + * + * Adds the rxb to a new skb and give it to mac80211 + */ +static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, +					    struct ieee80211_hdr *hdr, u16 len, +					    u32 ampdu_status, +					    struct iwl_rx_cmd_buffer *rxb, +					    struct ieee80211_rx_status *stats) +{ +	struct sk_buff *skb; +	unsigned int hdrlen, fraglen; + +	/* Dont use dev_alloc_skb(), we'll have enough headroom once +	 * ieee80211_hdr pulled. +	 */ +	skb = alloc_skb(128, GFP_ATOMIC); +	if (!skb) { +		IWL_ERR(mvm, "alloc_skb failed\n"); +		return; +	} +	/* If frame is small enough to fit in skb->head, pull it completely. +	 * If not, only pull ieee80211_hdr so that splice() or TCP coalesce +	 * are more efficient. +	 */ +	hdrlen = (len <= skb_tailroom(skb)) ? len : sizeof(*hdr); + +	memcpy(skb_put(skb, hdrlen), hdr, hdrlen); +	fraglen = len - hdrlen; + +	if (fraglen) { +		int offset = (void *)hdr + hdrlen - +			     rxb_addr(rxb) + rxb_offset(rxb); + +		skb_add_rx_frag(skb, 0, rxb_steal_page(rxb), offset, +				fraglen, rxb->truesize); +	} + +	memcpy(IEEE80211_SKB_RXCB(skb), stats, sizeof(*stats)); + +	ieee80211_rx(mvm->hw, skb); +} + +/* + * iwl_mvm_calc_rssi - calculate the rssi in dBm + * @phy_info: the phy information for the coming packet + */ +static int iwl_mvm_calc_rssi(struct iwl_mvm *mvm, +			     struct iwl_rx_phy_info *phy_info) +{ +	u32 rssi_a, rssi_b, rssi_c, max_rssi, agc_db; +	u32 val; + +	/* Find max rssi among 3 possible receivers. +	 * These values are measured by the Digital Signal Processor (DSP). +	 * They should stay fairly constant even as the signal strength varies, +	 * if the radio's Automatic Gain Control (AGC) is working right. +	 * AGC value (see below) will provide the "interesting" info. +	 */ +	val = le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_RSSI_AB_IDX]); +	rssi_a = (val & IWL_OFDM_RSSI_INBAND_A_MSK) >> IWL_OFDM_RSSI_A_POS; +	rssi_b = (val & IWL_OFDM_RSSI_INBAND_B_MSK) >> IWL_OFDM_RSSI_B_POS; +	val = le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_RSSI_C_IDX]); +	rssi_c = (val & IWL_OFDM_RSSI_INBAND_C_MSK) >> IWL_OFDM_RSSI_C_POS; + +	val = le32_to_cpu(phy_info->non_cfg_phy[IWL_RX_INFO_AGC_IDX]); +	agc_db = (val & IWL_OFDM_AGC_DB_MSK) >> IWL_OFDM_AGC_DB_POS; + +	max_rssi = max_t(u32, rssi_a, rssi_b); +	max_rssi = max_t(u32, max_rssi, rssi_c); + +	IWL_DEBUG_STATS(mvm, "Rssi In A %d B %d C %d Max %d AGC dB %d\n", +			rssi_a, rssi_b, rssi_c, max_rssi, agc_db); + +	/* dBm = max_rssi dB - agc dB - constant. +	 * Higher AGC (higher radio gain) means lower signal. */ +	return max_rssi - agc_db - IWL_RSSI_OFFSET; +} + +/* + * iwl_mvm_set_mac80211_rx_flag - translate fw status to mac80211 format + * @mvm: the mvm object + * @hdr: 80211 header + * @stats: status in mac80211's format + * @rx_pkt_status: status coming from fw + * + * returns non 0 value if the packet should be dropped + */ +static u32 iwl_mvm_set_mac80211_rx_flag(struct iwl_mvm *mvm, +					struct ieee80211_hdr *hdr, +					struct ieee80211_rx_status *stats, +					u32 rx_pkt_status) +{ +	if (!ieee80211_has_protected(hdr->frame_control) || +	    (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == +			     RX_MPDU_RES_STATUS_SEC_NO_ENC) +		return 0; + +	/* packet was encrypted with unknown alg */ +	if ((rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) == +					RX_MPDU_RES_STATUS_SEC_ENC_ERR) +		return 0; + +	switch (rx_pkt_status & RX_MPDU_RES_STATUS_SEC_ENC_MSK) { +	case RX_MPDU_RES_STATUS_SEC_CCM_ENC: +		/* alg is CCM: check MIC only */ +		if (!(rx_pkt_status & RX_MPDU_RES_STATUS_MIC_OK)) +			return -1; + +		stats->flag |= RX_FLAG_DECRYPTED; +		IWL_DEBUG_WEP(mvm, "hw decrypted CCMP successfully\n"); +		return 0; + +	case RX_MPDU_RES_STATUS_SEC_TKIP_ENC: +		/* Don't drop the frame and decrypt it in SW */ +		if (!(rx_pkt_status & RX_MPDU_RES_STATUS_TTAK_OK)) +			return 0; +		/* fall through if TTAK OK */ + +	case RX_MPDU_RES_STATUS_SEC_WEP_ENC: +		if (!(rx_pkt_status & RX_MPDU_RES_STATUS_ICV_OK)) +			return -1; + +		stats->flag |= RX_FLAG_DECRYPTED; +		return 0; + +	default: +		IWL_ERR(mvm, "Unhandled alg: 0x%x\n", rx_pkt_status); +	} + +	return 0; +} + +/* + * iwl_mvm_rx_rx_mpdu - REPLY_RX_MPDU_CMD handler + * + * Handles the actual data of the Rx packet from the fw + */ +int iwl_mvm_rx_rx_mpdu(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		       struct iwl_device_cmd *cmd) +{ +	struct ieee80211_hdr *hdr; +	struct ieee80211_rx_status rx_status = {}; +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_rx_phy_info *phy_info; +	struct iwl_rx_mpdu_res_start *rx_res; +	u32 len; +	u32 ampdu_status; +	u32 rate_n_flags; +	u32 rx_pkt_status; + +	phy_info = &mvm->last_phy_info; +	rx_res = (struct iwl_rx_mpdu_res_start *)pkt->data; +	hdr = (struct ieee80211_hdr *)(pkt->data + sizeof(*rx_res)); +	len = le16_to_cpu(rx_res->byte_count); +	rx_pkt_status = le32_to_cpup((__le32 *) +		(pkt->data + sizeof(*rx_res) + len)); + +	memset(&rx_status, 0, sizeof(rx_status)); + +	/* +	 * drop the packet if it has failed being decrypted by HW +	 */ +	if (iwl_mvm_set_mac80211_rx_flag(mvm, hdr, &rx_status, rx_pkt_status)) { +		IWL_DEBUG_DROP(mvm, "Bad decryption results 0x%08x\n", +			       rx_pkt_status); +		return 0; +	} + +	if ((unlikely(phy_info->cfg_phy_cnt > 20))) { +		IWL_DEBUG_DROP(mvm, "dsp size out of range [0,20]: %d\n", +			       phy_info->cfg_phy_cnt); +		return 0; +	} + +	if (!(rx_pkt_status & RX_MPDU_RES_STATUS_CRC_OK) || +	    !(rx_pkt_status & RX_MPDU_RES_STATUS_OVERRUN_OK)) { +		IWL_DEBUG_RX(mvm, "Bad CRC or FIFO: 0x%08X.\n", rx_pkt_status); +		return 0; +	} + +	/* This will be used in several places later */ +	rate_n_flags = le32_to_cpu(phy_info->rate_n_flags); + +	/* rx_status carries information about the packet to mac80211 */ +	rx_status.mactime = le64_to_cpu(phy_info->timestamp); +	rx_status.band = +		(phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_BAND_24)) ? +				IEEE80211_BAND_2GHZ : IEEE80211_BAND_5GHZ; +	rx_status.freq = +		ieee80211_channel_to_frequency(le16_to_cpu(phy_info->channel), +					       rx_status.band); +	/* +	 * TSF as indicated by the fw is at INA time, but mac80211 expects the +	 * TSF at the beginning of the MPDU. +	 */ +	/*rx_status.flag |= RX_FLAG_MACTIME_MPDU;*/ + +	/* Find max signal strength (dBm) among 3 antenna/receiver chains */ +	rx_status.signal = iwl_mvm_calc_rssi(mvm, phy_info); + +	IWL_DEBUG_STATS_LIMIT(mvm, "Rssi %d, TSF %llu\n", rx_status.signal, +			      (unsigned long long)rx_status.mactime); + +	/* +	 * "antenna number" +	 * +	 * It seems that the antenna field in the phy flags value +	 * is actually a bit field. This is undefined by radiotap, +	 * it wants an actual antenna number but I always get "7" +	 * for most legacy frames I receive indicating that the +	 * same frame was received on all three RX chains. +	 * +	 * I think this field should be removed in favor of a +	 * new 802.11n radiotap field "RX chains" that is defined +	 * as a bitmask. +	 */ +	rx_status.antenna = (le16_to_cpu(phy_info->phy_flags) & +				RX_RES_PHY_FLAGS_ANTENNA) +				>> RX_RES_PHY_FLAGS_ANTENNA_POS; + +	/* set the preamble flag if appropriate */ +	if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_SHORT_PREAMBLE)) +		rx_status.flag |= RX_FLAG_SHORTPRE; + +	if (phy_info->phy_flags & cpu_to_le16(RX_RES_PHY_FLAGS_AGG)) { +		/* +		 * We know which subframes of an A-MPDU belong +		 * together since we get a single PHY response +		 * from the firmware for all of them +		 */ +		rx_status.flag |= RX_FLAG_AMPDU_DETAILS; +		rx_status.ampdu_reference = mvm->ampdu_ref; +	} + +	/* Set up the HT phy flags */ +	switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { +	case RATE_MCS_CHAN_WIDTH_20: +		break; +	case RATE_MCS_CHAN_WIDTH_40: +		rx_status.flag |= RX_FLAG_40MHZ; +		break; +	case RATE_MCS_CHAN_WIDTH_80: +		rx_status.flag |= RX_FLAG_80MHZ; +		break; +	case RATE_MCS_CHAN_WIDTH_160: +		rx_status.flag |= RX_FLAG_160MHZ; +		break; +	} +	if (rate_n_flags & RATE_MCS_SGI_MSK) +		rx_status.flag |= RX_FLAG_SHORT_GI; +	if (rate_n_flags & RATE_HT_MCS_GF_MSK) +		rx_status.flag |= RX_FLAG_HT_GF; +	if (rate_n_flags & RATE_MCS_HT_MSK) { +		rx_status.flag |= RX_FLAG_HT; +		rx_status.rate_idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK; +	} else if (rate_n_flags & RATE_MCS_VHT_MSK) { +		rx_status.vht_nss = +			((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> +						RATE_VHT_MCS_NSS_POS) + 1; +		rx_status.rate_idx = rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK; +		rx_status.flag |= RX_FLAG_VHT; +	} else { +		rx_status.rate_idx = +			iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, +							    rx_status.band); +	} + +	iwl_mvm_pass_packet_to_mac80211(mvm, hdr, len, ampdu_status, +					rxb, &rx_status); +	return 0; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/scan.c b/drivers/net/wireless/iwlwifi/mvm/scan.c new file mode 100644 index 00000000000..406c53ad0a4 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/scan.c @@ -0,0 +1,437 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 <net/mac80211.h> + +#include "mvm.h" +#include "iwl-eeprom-parse.h" +#include "fw-api-scan.h" + +#define IWL_PLCP_QUIET_THRESH 1 +#define IWL_ACTIVE_QUIET_TIME 10 + +static inline __le16 iwl_mvm_scan_rx_chain(struct iwl_mvm *mvm) +{ +	u16 rx_chain; +	u8 rx_ant = mvm->nvm_data->valid_rx_ant; + +	rx_chain = rx_ant << PHY_RX_CHAIN_VALID_POS; +	rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_MIMO_SEL_POS; +	rx_chain |= rx_ant << PHY_RX_CHAIN_FORCE_SEL_POS; +	rx_chain |= 0x1 << PHY_RX_CHAIN_DRIVER_FORCE_POS; +	return cpu_to_le16(rx_chain); +} + +static inline __le32 iwl_mvm_scan_max_out_time(struct ieee80211_vif *vif) +{ +	if (vif->bss_conf.assoc) +		return cpu_to_le32(200 * 1024); +	else +		return 0; +} + +static inline __le32 iwl_mvm_scan_suspend_time(struct ieee80211_vif *vif) +{ +	if (vif->bss_conf.assoc) +		return cpu_to_le32(vif->bss_conf.beacon_int); +	else +		return 0; +} + +static inline __le32 +iwl_mvm_scan_rxon_flags(struct cfg80211_scan_request *req) +{ +	if (req->channels[0]->band == IEEE80211_BAND_2GHZ) +		return cpu_to_le32(PHY_BAND_24); +	else +		return cpu_to_le32(PHY_BAND_5); +} + +static inline __le32 +iwl_mvm_scan_rate_n_flags(struct iwl_mvm *mvm, enum ieee80211_band band, +			  bool no_cck) +{ +	u32 tx_ant; + +	mvm->scan_last_antenna_idx = +		iwl_mvm_next_antenna(mvm, mvm->nvm_data->valid_tx_ant, +				     mvm->scan_last_antenna_idx); +	tx_ant = BIT(mvm->scan_last_antenna_idx) << RATE_MCS_ANT_POS; + +	if (band == IEEE80211_BAND_2GHZ && !no_cck) +		return cpu_to_le32(IWL_RATE_1M_PLCP | RATE_MCS_CCK_MSK | +				   tx_ant); +	else +		return cpu_to_le32(IWL_RATE_6M_PLCP | tx_ant); +} + +/* + * We insert the SSIDs in an inverted order, because the FW will + * invert it back. The most prioritized SSID, which is first in the + * request list, is not copied here, but inserted directly to the probe + * request. + */ +static void iwl_mvm_scan_fill_ssids(struct iwl_scan_cmd *cmd, +				    struct cfg80211_scan_request *req) +{ +	int fw_idx, req_idx; + +	fw_idx = 0; +	for (req_idx = req->n_ssids - 1; req_idx > 0; req_idx--) { +		cmd->direct_scan[fw_idx].id = WLAN_EID_SSID; +		cmd->direct_scan[fw_idx].len = req->ssids[req_idx].ssid_len; +		memcpy(cmd->direct_scan[fw_idx].ssid, +		       req->ssids[req_idx].ssid, +		       req->ssids[req_idx].ssid_len); +	} +} + +/* + * If req->n_ssids > 0, it means we should do an active scan. + * In case of active scan w/o directed scan, we receive a zero-length SSID + * just to notify that this scan is active and not passive. + * In order to notify the FW of the number of SSIDs we wish to scan (including + * the zero-length one), we need to set the corresponding bits in chan->type, + * one for each SSID, and set the active bit (first). + */ +static u16 iwl_mvm_get_active_dwell(enum ieee80211_band band, int n_ssids) +{ +	if (band == IEEE80211_BAND_2GHZ) +		return 30  + 3 * (n_ssids + 1); +	return 20  + 2 * (n_ssids + 1); +} + +static u16 iwl_mvm_get_passive_dwell(enum ieee80211_band band) +{ +	return band == IEEE80211_BAND_2GHZ ? 100 + 20 : 100 + 10; +} + +static void iwl_mvm_scan_fill_channels(struct iwl_scan_cmd *cmd, +				       struct cfg80211_scan_request *req) +{ +	u16 passive_dwell = iwl_mvm_get_passive_dwell(req->channels[0]->band); +	u16 active_dwell = iwl_mvm_get_active_dwell(req->channels[0]->band, +						    req->n_ssids); +	struct iwl_scan_channel *chan = (struct iwl_scan_channel *) +		(cmd->data + le16_to_cpu(cmd->tx_cmd.len)); +	int i; +	__le32 chan_type_value; + +	if (req->n_ssids > 0) +		chan_type_value = cpu_to_le32(BIT(req->n_ssids + 1) - 1); +	else +		chan_type_value = SCAN_CHANNEL_TYPE_PASSIVE; + +	for (i = 0; i < cmd->channel_count; i++) { +		chan->channel = cpu_to_le16(req->channels[i]->hw_value); +		if (req->channels[i]->flags & IEEE80211_CHAN_PASSIVE_SCAN) +			chan->type = SCAN_CHANNEL_TYPE_PASSIVE; +		else +			chan->type = chan_type_value; +		chan->active_dwell = cpu_to_le16(active_dwell); +		chan->passive_dwell = cpu_to_le16(passive_dwell); +		chan->iteration_count = cpu_to_le16(1); +		chan++; +	} +} + +/* + * Fill in probe request with the following parameters: + * TA is our vif HW address, which mac80211 ensures we have. + * Packet is broadcasted, so this is both SA and DA. + * The probe request IE is made out of two: first comes the most prioritized + * SSID if a directed scan is requested. Second comes whatever extra + * information was given to us as the scan request IE. + */ +static u16 iwl_mvm_fill_probe_req(struct ieee80211_mgmt *frame, const u8 *ta, +				  int n_ssids, const u8 *ssid, int ssid_len, +				  const u8 *ie, int ie_len, +				  int left) +{ +	int len = 0; +	u8 *pos = NULL; + +	/* Make sure there is enough space for the probe request, +	 * two mandatory IEs and the data */ +	left -= 24; +	if (left < 0) +		return 0; + +	frame->frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); +	eth_broadcast_addr(frame->da); +	memcpy(frame->sa, ta, ETH_ALEN); +	eth_broadcast_addr(frame->bssid); +	frame->seq_ctrl = 0; + +	len += 24; + +	/* for passive scans, no need to fill anything */ +	if (n_ssids == 0) +		return (u16)len; + +	/* points to the payload of the request */ +	pos = &frame->u.probe_req.variable[0]; + +	/* fill in our SSID IE */ +	left -= ssid_len + 2; +	if (left < 0) +		return 0; +	*pos++ = WLAN_EID_SSID; +	*pos++ = ssid_len; +	if (ssid && ssid_len) { /* ssid_len may be == 0 even if ssid is valid */ +		memcpy(pos, ssid, ssid_len); +		pos += ssid_len; +	} + +	len += ssid_len + 2; + +	if (WARN_ON(left < ie_len)) +		return len; + +	if (ie && ie_len) { +		memcpy(pos, ie, ie_len); +		len += ie_len; +	} + +	return (u16)len; +} + +int iwl_mvm_scan_request(struct iwl_mvm *mvm, +			 struct ieee80211_vif *vif, +			 struct cfg80211_scan_request *req) +{ +	struct iwl_host_cmd hcmd = { +		.id = SCAN_REQUEST_CMD, +		.len = { 0, }, +		.data = { mvm->scan_cmd, }, +		.flags = CMD_SYNC, +		.dataflags = { IWL_HCMD_DFL_NOCOPY, }, +	}; +	struct iwl_scan_cmd *cmd = mvm->scan_cmd; +	int ret; +	u32 status; +	int ssid_len = 0; +	u8 *ssid = NULL; + +	lockdep_assert_held(&mvm->mutex); +	BUG_ON(mvm->scan_cmd == NULL); + +	IWL_DEBUG_SCAN(mvm, "Handling mac80211 scan request\n"); +	mvm->scan_status = IWL_MVM_SCAN_OS; +	memset(cmd, 0, sizeof(struct iwl_scan_cmd) + +	       mvm->fw->ucode_capa.max_probe_length + +	       (MAX_NUM_SCAN_CHANNELS * sizeof(struct iwl_scan_channel))); + +	cmd->channel_count = (u8)req->n_channels; +	cmd->quiet_time = cpu_to_le16(IWL_ACTIVE_QUIET_TIME); +	cmd->quiet_plcp_th = cpu_to_le16(IWL_PLCP_QUIET_THRESH); +	cmd->rxchain_sel_flags = iwl_mvm_scan_rx_chain(mvm); +	cmd->max_out_time = iwl_mvm_scan_max_out_time(vif); +	cmd->suspend_time = iwl_mvm_scan_suspend_time(vif); +	cmd->rxon_flags = iwl_mvm_scan_rxon_flags(req); +	cmd->filter_flags = cpu_to_le32(MAC_FILTER_ACCEPT_GRP | +					MAC_FILTER_IN_BEACON); +	cmd->type = SCAN_TYPE_FORCED; +	cmd->repeats = cpu_to_le32(1); + +	/* +	 * If the user asked for passive scan, don't change to active scan if +	 * you see any activity on the channel - remain passive. +	 */ +	if (req->n_ssids > 0) { +		cmd->passive2active = cpu_to_le16(1); +		ssid = req->ssids[0].ssid; +		ssid_len = req->ssids[0].ssid_len; +	} else { +		cmd->passive2active = 0; +	} + +	iwl_mvm_scan_fill_ssids(cmd, req); + +	cmd->tx_cmd.tx_flags = cpu_to_le32(TX_CMD_FLG_SEQ_CTL); +	cmd->tx_cmd.sta_id = mvm->aux_sta.sta_id; +	cmd->tx_cmd.life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); +	cmd->tx_cmd.rate_n_flags = +			iwl_mvm_scan_rate_n_flags(mvm, req->channels[0]->band, +						  req->no_cck); + +	cmd->tx_cmd.len = +		cpu_to_le16(iwl_mvm_fill_probe_req( +			    (struct ieee80211_mgmt *)cmd->data, +			    vif->addr, +			    req->n_ssids, ssid, ssid_len, +			    req->ie, req->ie_len, +			    mvm->fw->ucode_capa.max_probe_length)); + +	iwl_mvm_scan_fill_channels(cmd, req); + +	cmd->len = cpu_to_le16(sizeof(struct iwl_scan_cmd) + +		le16_to_cpu(cmd->tx_cmd.len) + +		(cmd->channel_count * sizeof(struct iwl_scan_channel))); +	hcmd.len[0] = le16_to_cpu(cmd->len); + +	status = SCAN_RESPONSE_OK; +	ret = iwl_mvm_send_cmd_status(mvm, &hcmd, &status); +	if (!ret && status == SCAN_RESPONSE_OK) { +		IWL_DEBUG_SCAN(mvm, "Scan request was sent successfully\n"); +	} else { +		/* +		 * If the scan failed, it usually means that the FW was unable +		 * to allocate the time events. Warn on it, but maybe we +		 * should try to send the command again with different params. +		 */ +		IWL_ERR(mvm, "Scan failed! status 0x%x ret %d\n", +			status, ret); +		mvm->scan_status = IWL_MVM_SCAN_NONE; +		ret = -EIO; +	} +	return ret; +} + +int iwl_mvm_rx_scan_response(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_cmd_response *resp = (void *)pkt->data; + +	IWL_DEBUG_SCAN(mvm, "Scan response received. status 0x%x\n", +		       le32_to_cpu(resp->status)); +	return 0; +} + +int iwl_mvm_rx_scan_complete(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_scan_complete_notif *notif = (void *)pkt->data; + +	IWL_DEBUG_SCAN(mvm, "Scan complete: status=0x%x scanned channels=%d\n", +		       notif->status, notif->scanned_channels); + +	mvm->scan_status = IWL_MVM_SCAN_NONE; +	ieee80211_scan_completed(mvm->hw, notif->status != SCAN_COMP_STATUS_OK); + +	return 0; +} + +static bool iwl_mvm_scan_abort_notif(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_scan_complete_notif *notif; +	u32 *resp; + +	switch (pkt->hdr.cmd) { +	case SCAN_ABORT_CMD: +		resp = (void *)pkt->data; +		if (*resp == CAN_ABORT_STATUS) { +			IWL_DEBUG_SCAN(mvm, +				       "Scan can be aborted, wait until completion\n"); +			return false; +		} + +		IWL_DEBUG_SCAN(mvm, "Scan cannot be aborted, exit now: %d\n", +			       *resp); +		return true; + +	case SCAN_COMPLETE_NOTIFICATION: +		notif = (void *)pkt->data; +		IWL_DEBUG_SCAN(mvm, "Scan aborted: status 0x%x\n", +			       notif->status); +		return true; + +	default: +		WARN_ON(1); +		return false; +	}; +} + +void iwl_mvm_cancel_scan(struct iwl_mvm *mvm) +{ +	struct iwl_notification_wait wait_scan_abort; +	static const u8 scan_abort_notif[] = { SCAN_ABORT_CMD, +					       SCAN_COMPLETE_NOTIFICATION }; +	int ret; + +	iwl_init_notification_wait(&mvm->notif_wait, &wait_scan_abort, +				   scan_abort_notif, +				   ARRAY_SIZE(scan_abort_notif), +				   iwl_mvm_scan_abort_notif, NULL); + +	ret = iwl_mvm_send_cmd_pdu(mvm, SCAN_ABORT_CMD, CMD_SYNC, 0, NULL); +	if (ret) { +		IWL_ERR(mvm, "Couldn't send SCAN_ABORT_CMD: %d\n", ret); +		goto out_remove_notif; +	} + +	ret = iwl_wait_notification(&mvm->notif_wait, &wait_scan_abort, 1 * HZ); +	if (ret) +		IWL_ERR(mvm, "%s - failed on timeout\n", __func__); + +	return; + +out_remove_notif: +	iwl_remove_notification(&mvm->notif_wait, &wait_scan_abort); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.c b/drivers/net/wireless/iwlwifi/mvm/sta.c new file mode 100644 index 00000000000..69603c3b2b3 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/sta.c @@ -0,0 +1,1211 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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" + +static int iwl_mvm_find_free_sta_id(struct iwl_mvm *mvm) +{ +	int sta_id; + +	WARN_ON_ONCE(test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)); + +	lockdep_assert_held(&mvm->mutex); + +	/* 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 (!rcu_dereference_protected(mvm->fw_id_to_mac_id[sta_id], +					       lockdep_is_held(&mvm->mutex))) +			return sta_id; +	return IWL_MVM_STATION_COUNT; +} + +/* add a NEW station to fw */ +int iwl_mvm_sta_add_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta) +{ +	struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; +	struct iwl_mvm_add_sta_cmd add_sta_cmd; +	int ret; +	u32 status; +	u32 agg_size = 0, mpdu_dens = 0; + +	memset(&add_sta_cmd, 0, sizeof(add_sta_cmd)); + +	add_sta_cmd.sta_id = mvm_sta->sta_id; +	add_sta_cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); +	add_sta_cmd.tfd_queue_msk = cpu_to_le32(mvm_sta->tfd_queue_msk); +	memcpy(&add_sta_cmd.addr, sta->addr, ETH_ALEN); + +	/* STA_FLG_FAT_EN_MSK ? */ +	/* STA_FLG_MIMO_EN_MSK ? */ + +	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; +} + +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 = (void *)sta->drv_priv; +	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); +	else +		sta_id = mvm_sta->sta_id; + +	if (WARN_ON_ONCE(sta_id == IWL_MVM_STATION_COUNT)) +		return -ENOSPC; + +	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; + +	/* HW restart, don't assume the memory has been zeroed */ +	atomic_set(&mvm_sta->pending_frames, 0); +	mvm_sta->tid_disable_agg = 0; +	mvm_sta->tfd_queue_msk = 0; +	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]); + +	if (vif->cab_queue != IEEE80211_INVAL_HW_QUEUE) +		mvm_sta->tfd_queue_msk |= BIT(vif->cab_queue); + +	/* for HW restart - need to reset the seq_number etc... */ +	memset(mvm_sta->tid_data, 0, sizeof(mvm_sta->tid_data)); + +	ret = iwl_mvm_sta_add_to_fw(mvm, sta); +	if (ret) +		return ret; + +	/* The first station added is the AP, the others are TDLS STAs */ +	if (vif->type == NL80211_IFTYPE_STATION && +	    mvmvif->ap_sta_id == IWL_MVM_STATION_COUNT) +		mvmvif->ap_sta_id = sta_id; + +	rcu_assign_pointer(mvm->fw_id_to_mac_id[sta_id], sta); + +	return 0; +} + +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, CMD_SYNC, +				   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 */ +		if (!IS_ERR(sta)) +			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_assign_pointer(mvm->fw_id_to_mac_id[sta_id], NULL); +		clear_bit(sta_id, mvm->sta_drained); +	} + +	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 = (void *)sta->drv_priv; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	if (vif->type == NL80211_IFTYPE_STATION && +	    mvmvif->ap_sta_id == mvm_sta->sta_id) { +		/* +		 * Put a non-NULL since the fw station isn't removed. +		 * It will be removed after the MAC will be set as +		 * unassoc. +		 */ +		rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], +				   ERR_PTR(-EINVAL)); + +		/* flush its queues here since we are freeing mvm_sta */ +		ret = iwl_mvm_flush_tx_path(mvm, mvm_sta->tfd_queue_msk, true); + +		/* 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; +	} + +	/* +	 * 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_sta->pending_frames)) { +		ret = iwl_mvm_drain_sta(mvm, mvm_sta, true); +		rcu_assign_pointer(mvm->fw_id_to_mac_id[mvm_sta->sta_id], +				   ERR_PTR(-EBUSY)); +	} else { +		ret = iwl_mvm_rm_sta_common(mvm, mvm_sta->sta_id); +		rcu_assign_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_assign_pointer(mvm->fw_id_to_mac_id[sta_id], NULL); +	return ret; +} + +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta, +			     u32 qmask) +{ +	if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status)) { +		sta->sta_id = iwl_mvm_find_free_sta_id(mvm); +		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; +} + +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta) +{ +	rcu_assign_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(struct iwl_mvm_add_sta_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) +{ +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	/* Add the aux station, but without any queues */ +	ret = iwl_mvm_allocate_int_sta(mvm, &mvm->aux_sta, 0); +	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; +} + +/* + * 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_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			   struct iwl_mvm_int_sta *bsta) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	static const u8 baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +	lockdep_assert_held(&mvm->mutex); + +	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 iwl_mvm_int_sta *bsta) +{ +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	ret = iwl_mvm_rm_sta_common(mvm, bsta->sta_id); +	if (ret) +		IWL_WARN(mvm, "Failed sending remove station\n"); +	return ret; +} + +/* 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_int_sta *bsta) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	static const u8 baddr[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +	u32 qmask; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	qmask = iwl_mvm_mac_get_queues_mask(mvm, vif); +	ret = iwl_mvm_allocate_int_sta(mvm, bsta, qmask); +	if (ret) +		return ret; + +	ret = iwl_mvm_add_int_sta_common(mvm, bsta, baddr, +					 mvmvif->id, mvmvif->color); + +	if (ret) +		iwl_mvm_dealloc_int_sta(mvm, bsta); +	return ret; +} + +/* + * 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 iwl_mvm_int_sta *bsta) +{ +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	ret = iwl_mvm_rm_sta_common(mvm, bsta->sta_id); +	if (ret) +		return ret; + +	iwl_mvm_dealloc_int_sta(mvm, bsta); +	return ret; +} + +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 = (void *)sta->drv_priv; +	struct iwl_mvm_add_sta_cmd cmd = {}; +	int ret; +	u32 status; + +	lockdep_assert_held(&mvm->mutex); + +	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.add_immediate_ba_tid = (u8) tid; +	cmd.add_immediate_ba_ssn = cpu_to_le16(ssn); +	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; +	} + +	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 = (void *)sta->drv_priv; +	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; +} + +static const u8 tid_to_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, +}; + +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 = (void *)sta->drv_priv; +	struct iwl_mvm_tid_data *tid_data; +	int txq_id; + +	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); + +	for (txq_id = IWL_MVM_FIRST_AGG_QUEUE; +	     txq_id <= IWL_MVM_LAST_AGG_QUEUE; txq_id++) +		if (mvm->queue_to_mac80211[txq_id] == +		    IWL_INVALID_MAC80211_QUEUE) +			break; + +	if (txq_id > IWL_MVM_LAST_AGG_QUEUE) { +		IWL_ERR(mvm, "Failed to allocate agg queue\n"); +		return -EIO; +	} + +	/* the new tx queue is still connected to the same mac80211 queue */ +	mvm->queue_to_mac80211[txq_id] = vif->hw_queue[tid_to_ac[tid]]; + +	spin_lock_bh(&mvmsta->lock); +	tid_data = &mvmsta->tid_data[tid]; +	tid_data->ssn = 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; +	} + +	spin_unlock_bh(&mvmsta->lock); + +	return 0; +} + +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 = (void *)sta->drv_priv; +	struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; +	int queue, fifo, ret; +	u16 ssn; + +	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; +	tid_data->ssn = 0xffff; +	spin_unlock_bh(&mvmsta->lock); + +	fifo = iwl_mvm_ac_to_tx_fifo[tid_to_ac[tid]]; + +	ret = iwl_mvm_sta_tx_agg(mvm, sta, tid, queue, true); +	if (ret) +		return -EIO; + +	iwl_trans_txq_enable(mvm->trans, queue, fifo, mvmsta->sta_id, tid, +			     buf_size, ssn); + +	/* +	 * 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; + +	if (mvm->cfg->ht_params->use_rts_for_aggregation) { +		/* +		 * switch to RTS/CTS if it is the prefer protection +		 * method for HT traffic +		 */ +		mvmsta->lq_sta.lq.flags |= LQ_FLAG_SET_STA_TLC_RTS_MSK; +		/* +		 * TODO: remove the TLC_RTS flag when we tear down the last +		 * AGG session (agg_tids_count in DVM) +		 */ +	} + +	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, CMD_ASYNC, 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 = (void *)sta->drv_priv; +	struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; +	u16 txq_id; +	int err; + +	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); + +	switch (tid_data->state) { +	case IWL_AGG_ON: +		tid_data->ssn = 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; +		iwl_trans_txq_disable(mvm->trans, txq_id); +		/* fall through */ +	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); +		mvm->queue_to_mac80211[txq_id] = IWL_INVALID_MAC80211_QUEUE; + +		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; +} + +static int iwl_mvm_set_fw_key_idx(struct iwl_mvm *mvm) +{ +	int i; + +	lockdep_assert_held(&mvm->mutex); + +	i = find_first_zero_bit(mvm->fw_key_table, STA_KEY_MAX_NUM); + +	if (i == STA_KEY_MAX_NUM) +		return STA_KEY_IDX_INVALID; + +	__set_bit(i, mvm->fw_key_table); + +	return i; +} + +static u8 iwl_mvm_get_key_sta_id(struct ieee80211_vif *vif, +				 struct ieee80211_sta *sta) +{ +	struct iwl_mvm_vif *mvmvif = (void *)vif->drv_priv; + +	if (sta) { +		struct iwl_mvm_sta *mvm_sta = (void *)sta->drv_priv; + +		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 && +	    mvmvif->ap_sta_id != IWL_MVM_STATION_COUNT) +		return mvmvif->ap_sta_id; + +	return IWL_INVALID_STATION; +} + +static int iwl_mvm_send_sta_key(struct iwl_mvm *mvm, +				struct iwl_mvm_sta *mvm_sta, +				struct ieee80211_key_conf *keyconf, +				u8 sta_id, u32 tkip_iv32, u16 *tkip_p1k, +				u32 cmd_flags) +{ +	__le16 key_flags; +	struct iwl_mvm_add_sta_cmd cmd = {}; +	int ret, status; +	u16 keyidx; +	int i; + +	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.key.tkip_rx_tsc_byte2 = tkip_iv32; +		for (i = 0; i < 5; i++) +			cmd.key.tkip_rx_ttak[i] = cpu_to_le16(tkip_p1k[i]); +		memcpy(cmd.key.key, keyconf->key, keyconf->keylen); +		break; +	case WLAN_CIPHER_SUITE_CCMP: +		key_flags |= cpu_to_le16(STA_KEY_FLG_CCM); +		memcpy(cmd.key.key, keyconf->key, keyconf->keylen); +		break; +	default: +		WARN_ON(1); +		return -EINVAL; +	} + +	if (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) +		key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + +	cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); +	cmd.key.key_offset = keyconf->hw_key_idx; +	cmd.key.key_flags = key_flags; +	cmd.add_modify = STA_MODE_MODIFY; +	cmd.modify_mask = STA_MODIFY_KEY; +	cmd.sta_id = sta_id; + +	status = ADD_STA_SUCCESS; +	if (cmd_flags == CMD_SYNC) +		ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, sizeof(cmd), +						  &cmd, &status); +	else +		ret = iwl_mvm_send_cmd_pdu(mvm, ADD_STA, CMD_ASYNC, +					   sizeof(cmd), &cmd); + +	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_aes_cmac_calculate_k1_k2(keyconf, +						   igtk_cmd.K1, igtk_cmd.K2); +		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, CMD_SYNC, +				    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 = (void *)vif->drv_priv; + +	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; +} + +int iwl_mvm_set_sta_key(struct iwl_mvm *mvm, +			struct ieee80211_vif *vif, +			struct ieee80211_sta *sta, +			struct ieee80211_key_conf *keyconf, +			bool have_key_offset) +{ +	struct iwl_mvm_sta *mvm_sta; +	int ret; +	u8 *addr, sta_id; +	struct ieee80211_key_seq seq; +	u16 p1k[5]; + +	lockdep_assert_held(&mvm->mutex); + +	/* Get the station id from the mvm local station table */ +	sta_id = iwl_mvm_get_key_sta_id(vif, sta); +	if (sta_id == IWL_INVALID_STATION) { +		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; +		} +	} + +	mvm_sta = (struct iwl_mvm_sta *)sta->drv_priv; +	if (WARN_ON_ONCE(mvm_sta->vif != vif)) +		return -EINVAL; + +	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) +			return -ENOSPC; +	} + +	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, sta_id, +					   seq.tkip.iv32, p1k, CMD_SYNC); +		break; +	case WLAN_CIPHER_SUITE_CCMP: +		ret = iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, sta_id, +					   0, NULL, CMD_SYNC); +		break; +	default: +		IWL_ERR(mvm, "Unknown cipher %x\n", keyconf->cipher); +		ret = -EINVAL; +	} + +	if (ret) +		__clear_bit(keyconf->hw_key_idx, mvm->fw_key_table); + +end: +	IWL_DEBUG_WEP(mvm, "key: cipher=%x len=%d idx=%d sta=%pM ret=%d\n", +		      keyconf->cipher, keyconf->keylen, keyconf->keyidx, +		      sta->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) +{ +	struct iwl_mvm_sta *mvm_sta; +	struct iwl_mvm_add_sta_cmd cmd = {}; +	__le16 key_flags; +	int ret, status; +	u8 sta_id; + +	lockdep_assert_held(&mvm->mutex); + +	/* Get the station id from the mvm local station table */ +	sta_id = iwl_mvm_get_key_sta_id(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); + +	ret = __test_and_clear_bit(keyconf->hw_key_idx, mvm->fw_key_table); +	if (!ret) { +		IWL_ERR(mvm, "offset %d not used in fw key table.\n", +			keyconf->hw_key_idx); +		return -ENOENT; +	} + +	if (sta_id == IWL_INVALID_STATION) { +		IWL_DEBUG_WEP(mvm, "station non-existent, early return.\n"); +		return 0; +	} + +	/* +	 * 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; +		} +	} + +	mvm_sta = (struct iwl_mvm_sta *)sta->drv_priv; +	if (WARN_ON_ONCE(mvm_sta->vif != vif)) +		return -EINVAL; + +	key_flags = cpu_to_le16(keyconf->keyidx & 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 (!(keyconf->flags & IEEE80211_KEY_FLAG_PAIRWISE)) +		key_flags |= cpu_to_le16(STA_KEY_MULTICAST); + +	cmd.mac_id_n_color = cpu_to_le32(mvm_sta->mac_id_n_color); +	cmd.key.key_flags = key_flags; +	cmd.key.key_offset = keyconf->hw_key_idx; +	cmd.sta_id = sta_id; + +	cmd.modify_mask = STA_MODIFY_KEY; +	cmd.add_modify = STA_MODE_MODIFY; + +	status = ADD_STA_SUCCESS; +	ret = iwl_mvm_send_cmd_pdu_status(mvm, ADD_STA, 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; +} + +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 = (void *)sta->drv_priv; +	u8 sta_id = iwl_mvm_get_key_sta_id(vif, sta); + +	if (sta_id == IWL_INVALID_STATION) +		return; + +	iwl_mvm_send_sta_key(mvm, mvm_sta, keyconf, sta_id, +			     iv32, phase1key, CMD_ASYNC); +} + +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, int sta_id) +{ +	struct iwl_mvm_add_sta_cmd cmd = { +		.add_modify = STA_MODE_MODIFY, +		.sta_id = sta_id, +		.modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT, +		.sleep_state_flags = cpu_to_le16(STA_SLEEP_STATE_AWAKE), +	}; +	int ret; + +	/* +	 * Same modify mask for sleep_tx_count and sleep_state_flags but this +	 * should be fine since if we set the STA as "awake", then +	 * sleep_tx_count is not relevant. +	 */ +	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, int sta_id, +				       enum ieee80211_frame_release_type reason, +				       u16 cnt) +{ +	u16 sleep_state_flags = +		(reason == IEEE80211_FRAME_RELEASE_UAPSD) ? +			STA_SLEEP_STATE_UAPSD : STA_SLEEP_STATE_PS_POLL; +	struct iwl_mvm_add_sta_cmd cmd = { +		.add_modify = STA_MODE_MODIFY, +		.sta_id = sta_id, +		.modify_mask = STA_MODIFY_SLEEPING_STA_TX_COUNT, +		.sleep_tx_count = cpu_to_le16(cnt), +		/* +		 * Same modify mask for sleep_tx_count and sleep_state_flags so +		 * we must set the sleep_state_flags too. +		 */ +		.sleep_state_flags = cpu_to_le16(sleep_state_flags), +	}; +	int ret; + +	/* TODO: somehow the fw doesn't seem to take PS_POLL into account */ +	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); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/sta.h b/drivers/net/wireless/iwlwifi/mvm/sta.h new file mode 100644 index 00000000000..1bf30109798 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/sta.h @@ -0,0 +1,368 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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; + +/** + * 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 coresponsding %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 the + * non-aggregation frames 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 how many frames we have in these queues so that it can + * properly handle trigger frames. + * When the a trigger frame is received, mac80211 tells the driver to send + * frames from the AMPDU queues or AC queue depending on which queue are + * delivery-enabled and what TID has frames to transmit (Note that mac80211 has + * all the knowledege 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 alseep any more. + * This is done by %iwl_mvm_sta_modify_ps_wake. + * + * TODO - EOSP handling + */ + +/** + * 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). + * @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. + * @wait_for_ba: Expect block-ack before next Tx reply + */ +struct iwl_mvm_tid_data { +	u16 seq_number; +	u16 next_reclaimed; +	/* The rest is Tx AGG related */ +	u32 rate_n_flags; +	enum iwl_mvm_agg_state state; +	u16 txq_id; +	u16 ssn; +	bool wait_for_ba; +}; + +/** + * 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 + * @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 + * @lock: lock to protect the whole struct. Since %tid_data is access from Tx + * and from Tx response flow, it needs a spinlock. + * @pending_frames: number of frames for this STA on the shared Tx queues. + * @tid_data: per tid data. Look at %iwl_mvm_tid_data. + * + * 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; +	u32 mac_id_n_color; +	u16 tid_disable_agg; +	u8 max_agg_bufsize; +	spinlock_t lock; +	atomic_t pending_frames; +	struct iwl_mvm_tid_data tid_data[IWL_MAX_TID_COUNT]; +	struct iwl_lq_sta lq_sta; +	struct ieee80211_vif *vif; + +#ifdef CONFIG_PM_SLEEP +	u16 last_seq_ctl; +#endif +}; + +/** + * 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_add_to_fw(struct iwl_mvm *mvm, struct ieee80211_sta *sta); +int iwl_mvm_add_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 *key, +			bool have_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); + +/* 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_add_aux_sta(struct iwl_mvm *mvm); +int iwl_mvm_allocate_int_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *sta, +			     u32 qmask); +void iwl_mvm_dealloc_int_sta(struct iwl_mvm *mvm, +			     struct iwl_mvm_int_sta *sta); +int iwl_mvm_send_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			   struct iwl_mvm_int_sta *bsta); +int iwl_mvm_send_rm_bcast_sta(struct iwl_mvm *mvm, +			      struct iwl_mvm_int_sta *bsta); +int iwl_mvm_add_bcast_sta(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			  struct iwl_mvm_int_sta *bsta); +int iwl_mvm_rm_bcast_sta(struct iwl_mvm *mvm, struct iwl_mvm_int_sta *bsta); +void iwl_mvm_sta_drained_wk(struct work_struct *wk); +void iwl_mvm_sta_modify_ps_wake(struct iwl_mvm *mvm, int sta_id); +void iwl_mvm_sta_modify_sleep_tx_count(struct iwl_mvm *mvm, int sta_id, +				       enum ieee80211_frame_release_type reason, +				       u16 cnt); +int iwl_mvm_drain_sta(struct iwl_mvm *mvm, struct iwl_mvm_sta *mvmsta, +		      bool drain); + +#endif /* __sta_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/time-event.c b/drivers/net/wireless/iwlwifi/mvm/time-event.c new file mode 100644 index 00000000000..b9f076f4f17 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/time-event.c @@ -0,0 +1,569 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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/jiffies.h> +#include <net/mac80211.h> + +#include "iwl-notif-wait.h" +#include "iwl-trans.h" +#include "fw-api.h" +#include "time-event.h" +#include "mvm.h" +#include "iwl-io.h" +#include "iwl-prph.h" + +/* A TimeUnit is 1024 microsecond */ +#define TU_TO_JIFFIES(_tu)	(usecs_to_jiffies((_tu) * 1024)) +#define MSEC_TO_TU(_msec)	(_msec*1000/1024) + +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, +			   struct iwl_mvm_time_event_data *te_data) +{ +	lockdep_assert_held(&mvm->time_event_lock); + +	if (te_data->id == TE_MAX) +		return; + +	list_del(&te_data->list); +	te_data->running = false; +	te_data->uid = 0; +	te_data->id = TE_MAX; +	te_data->vif = NULL; +} + +void iwl_mvm_roc_done_wk(struct work_struct *wk) +{ +	struct iwl_mvm *mvm = container_of(wk, struct iwl_mvm, roc_done_wk); + +	synchronize_net(); + +	/* +	 * Flush the offchannel queue -- this is called when the time +	 * event finishes or is cancelled, so that frames queued for it +	 * won't get stuck on the queue and be transmitted in the next +	 * time event. +	 * We have to send the command asynchronously since this cannot +	 * be under the mutex for locking reasons, but that's not an +	 * issue as it will have to complete before the next command is +	 * executed, and a new time event means a new command. +	 */ +	iwl_mvm_flush_tx_path(mvm, BIT(IWL_OFFCHANNEL_QUEUE), false); +} + +static void iwl_mvm_roc_finished(struct iwl_mvm *mvm) +{ +	/* +	 * First, clear the ROC_RUNNING status bit. This will cause the TX +	 * path to drop offchannel transmissions. That would also be done +	 * by mac80211, but it is racy, in particular in the case that the +	 * time event actually completed in the firmware (which is handled +	 * in iwl_mvm_te_handle_notif). +	 */ +	clear_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); + +	/* +	 * Of course, our status bit is just as racy as mac80211, so in +	 * addition, fire off the work struct which will drop all frames +	 * from the hardware queues that made it through the race. First +	 * it will of course synchronize the TX path to make sure that +	 * any *new* TX will be rejected. +	 */ +	schedule_work(&mvm->roc_done_wk); +} + +/* + * Handles a FW notification for an event that is known to the driver. + * + * @mvm: the mvm component + * @te_data: the time event data + * @notif: the notification data corresponding the time event data. + */ +static void iwl_mvm_te_handle_notif(struct iwl_mvm *mvm, +				    struct iwl_mvm_time_event_data *te_data, +				    struct iwl_time_event_notif *notif) +{ +	lockdep_assert_held(&mvm->time_event_lock); + +	IWL_DEBUG_TE(mvm, "Handle time event notif - UID = 0x%x action %d\n", +		     le32_to_cpu(notif->unique_id), +		     le32_to_cpu(notif->action)); + +	/* +	 * The FW sends the start/end time event notifications even for events +	 * that it fails to schedule. This is indicated in the status field of +	 * the notification. This happens in cases that the scheduler cannot +	 * find a schedule that can handle the event (for example requesting a +	 * P2P Device discoveribility, while there are other higher priority +	 * events in the system). +	 */ +	WARN_ONCE(!le32_to_cpu(notif->status), +		  "Failed to schedule time event\n"); + +	if (le32_to_cpu(notif->action) == TE_NOTIF_HOST_END) { +		IWL_DEBUG_TE(mvm, +			     "TE ended - current time %lu, estimated end %lu\n", +			     jiffies, te_data->end_jiffies); + +		if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { +			ieee80211_remain_on_channel_expired(mvm->hw); +			iwl_mvm_roc_finished(mvm); +		} + +		/* +		 * By now, we should have finished association +		 * and know the dtim period. +		 */ +		if (te_data->vif->type == NL80211_IFTYPE_STATION && +		    (!te_data->vif->bss_conf.assoc || +		     !te_data->vif->bss_conf.dtim_period)) +			IWL_ERR(mvm, +				"No assocation and the time event is over already...\n"); + +		iwl_mvm_te_clear_data(mvm, te_data); +	} else if (le32_to_cpu(notif->action) == TE_NOTIF_HOST_START) { +		te_data->running = true; +		te_data->end_jiffies = jiffies + +			TU_TO_JIFFIES(te_data->duration); + +		if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { +			set_bit(IWL_MVM_STATUS_ROC_RUNNING, &mvm->status); +			ieee80211_ready_on_channel(mvm->hw); +		} +	} else { +		IWL_WARN(mvm, "Got TE with unknown action\n"); +	} +} + +/* + * The Rx handler for time event notifications + */ +int iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, +				struct iwl_rx_cmd_buffer *rxb, +				struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_time_event_notif *notif = (void *)pkt->data; +	struct iwl_mvm_time_event_data *te_data, *tmp; + +	IWL_DEBUG_TE(mvm, "Time event notification - UID = 0x%x action %d\n", +		     le32_to_cpu(notif->unique_id), +		     le32_to_cpu(notif->action)); + +	spin_lock_bh(&mvm->time_event_lock); +	list_for_each_entry_safe(te_data, tmp, &mvm->time_event_list, list) { +		if (le32_to_cpu(notif->unique_id) == te_data->uid) +			iwl_mvm_te_handle_notif(mvm, te_data, notif); +	} +	spin_unlock_bh(&mvm->time_event_lock); + +	return 0; +} + +static bool iwl_mvm_time_event_notif(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_mvm_time_event_data *te_data = data; +	struct ieee80211_vif *vif = te_data->vif; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_time_event_notif *notif; +	struct iwl_time_event_resp *resp; + +	u32 mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + +	/* until we do something else */ +	WARN_ON(te_data->id != TE_BSS_STA_AGGRESSIVE_ASSOC); + +	switch (pkt->hdr.cmd) { +	case TIME_EVENT_CMD: +		resp = (void *)pkt->data; +		/* TODO: I can't check that since the fw is buggy - it doesn't +		 * put the right values when we remove a TE. We can be here +		 * when we remove a TE because the remove TE command is sent in +		 * ASYNC... +		 * WARN_ON(mac_id_n_color != le32_to_cpu(resp->id_and_color)); +		 */ +		te_data->uid = le32_to_cpu(resp->unique_id); +		IWL_DEBUG_TE(mvm, "Got response - UID = 0x%x\n", te_data->uid); +		return false; + +	case TIME_EVENT_NOTIFICATION: +		notif = (void *)pkt->data; +		WARN_ON(le32_to_cpu(notif->status) != 1); +		WARN_ON(mac_id_n_color != le32_to_cpu(notif->id_and_color)); +		/* check if this is our Time Event that is starting */ +		if (le32_to_cpu(notif->unique_id) != te_data->uid) +			return false; +		IWL_DEBUG_TE(mvm, "Event %d is starting - time is %d\n", +			     te_data->uid, le32_to_cpu(notif->timestamp)); + +		WARN_ONCE(!le32_to_cpu(notif->status), +			  "Failed to schedule protected session TE\n"); + +		te_data->running = true; +		te_data->end_jiffies = jiffies + +				       TU_TO_JIFFIES(te_data->duration); +		return true; + +	default: +		WARN_ON(1); +		return false; +	}; +} + +void iwl_mvm_protect_session(struct iwl_mvm *mvm, +			     struct ieee80211_vif *vif, +			     u32 duration, u32 min_duration) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; +	static const u8 time_event_notif[] = { TIME_EVENT_CMD, +					       TIME_EVENT_NOTIFICATION }; +	struct iwl_notification_wait wait_time_event; +	struct iwl_time_event_cmd time_cmd = {}; +	int ret; + +	lockdep_assert_held(&mvm->mutex); + +	if (te_data->running && +	    time_after(te_data->end_jiffies, +		       jiffies + TU_TO_JIFFIES(min_duration))) { +		IWL_DEBUG_TE(mvm, "We have enough time in the current TE: %u\n", +			     jiffies_to_msecs(te_data->end_jiffies - jiffies)); +		return; +	} + +	if (te_data->running) { +		IWL_DEBUG_TE(mvm, "extend 0x%x: only %u ms left\n", +			     te_data->uid, +			     jiffies_to_msecs(te_data->end_jiffies - jiffies)); +		/* +		 * we don't have enough time +		 * cancel the current TE and issue a new one +		 * Of course it would be better to remove the old one only +		 * when the new one is added, but we don't care if we are off +		 * channel for a bit. All we need to do, is not to return +		 * before we actually begin to be on the channel. +		 */ +		iwl_mvm_stop_session_protection(mvm, vif); +	} + +	iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, +				   time_event_notif, +				   ARRAY_SIZE(time_event_notif), +				   iwl_mvm_time_event_notif, +				   &mvmvif->time_event_data); + +	time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); +	time_cmd.id_and_color = +		cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); +	time_cmd.id = cpu_to_le32(TE_BSS_STA_AGGRESSIVE_ASSOC); + +	time_cmd.apply_time = +		cpu_to_le32(iwl_read_prph(mvm->trans, DEVICE_SYSTEM_TIME_REG)); +	time_cmd.dep_policy = TE_INDEPENDENT; +	time_cmd.is_present = cpu_to_le32(1); +	time_cmd.max_frags = cpu_to_le32(TE_FRAG_NONE); +	time_cmd.max_delay = cpu_to_le32(500); +	/* TODO: why do we need to interval = bi if it is not periodic? */ +	time_cmd.interval = cpu_to_le32(1); +	time_cmd.interval_reciprocal = cpu_to_le32(iwl_mvm_reciprocal(1)); +	time_cmd.duration = cpu_to_le32(duration); +	time_cmd.repeat = cpu_to_le32(1); +	time_cmd.notify = cpu_to_le32(TE_NOTIF_HOST_START | TE_NOTIF_HOST_END); + +	te_data->vif = vif; +	te_data->duration = duration; + +	spin_lock_bh(&mvm->time_event_lock); +	te_data->id = le32_to_cpu(time_cmd.id); +	list_add_tail(&te_data->list, &mvm->time_event_list); +	spin_unlock_bh(&mvm->time_event_lock); + +	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, CMD_SYNC, +				   sizeof(time_cmd), &time_cmd); +	if (ret) { +		IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); +		goto out_remove_notif; +	} + +	ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1 * HZ); +	if (ret) { +		IWL_ERR(mvm, "%s - failed on timeout\n", __func__); +		spin_lock_bh(&mvm->time_event_lock); +		iwl_mvm_te_clear_data(mvm, te_data); +		spin_unlock_bh(&mvm->time_event_lock); +	} + +	return; + +out_remove_notif: +	iwl_remove_notification(&mvm->notif_wait, &wait_time_event); +} + +/* + * Explicit request to remove a time event. The removal of a time event needs to + * be synchronized with the flow of a time event's end notification, which also + * removes the time event from the op mode data structures. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, +			       struct iwl_mvm_vif *mvmvif, +			       struct iwl_mvm_time_event_data *te_data) +{ +	struct iwl_time_event_cmd time_cmd = {}; +	u32 id, uid; +	int ret; + +	/* +	 * It is possible that by the time we got to this point the time +	 * event was already removed. +	 */ +	spin_lock_bh(&mvm->time_event_lock); + +	/* Save time event uid before clearing its data */ +	uid = te_data->uid; +	id = te_data->id; + +	/* +	 * The clear_data function handles time events that were already removed +	 */ +	iwl_mvm_te_clear_data(mvm, te_data); +	spin_unlock_bh(&mvm->time_event_lock); + +	/* +	 * It is possible that by the time we try to remove it, the time event +	 * has already ended and removed. In such a case there is no need to +	 * send a removal command. +	 */ +	if (id == TE_MAX) { +		IWL_DEBUG_TE(mvm, "TE 0x%x has already ended\n", uid); +		return; +	} + +	/* When we remove a TE, the UID is to be set in the id field */ +	time_cmd.id = cpu_to_le32(uid); +	time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE); +	time_cmd.id_and_color = +		cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); + +	IWL_DEBUG_TE(mvm, "Removing TE 0x%x\n", le32_to_cpu(time_cmd.id)); +	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, CMD_ASYNC, +				   sizeof(time_cmd), &time_cmd); +	if (WARN_ON(ret)) +		return; +} + +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, +				     struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; + +	lockdep_assert_held(&mvm->mutex); +	iwl_mvm_remove_time_event(mvm, mvmvif, te_data); +} + +static bool iwl_mvm_roc_te_notif(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_mvm_time_event_data *te_data = data; +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); +	struct iwl_time_event_resp *resp; + +	u32 mac_id_n_color = FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color); + +	/* until we do something else */ +	WARN_ON(te_data->id != TE_P2P_DEVICE_DISCOVERABLE); + +	switch (pkt->hdr.cmd) { +	case TIME_EVENT_CMD: +		resp = (void *)pkt->data; +		WARN_ON(mac_id_n_color != le32_to_cpu(resp->id_and_color)); +		te_data->uid = le32_to_cpu(resp->unique_id); +		IWL_DEBUG_TE(mvm, "Got response - UID = 0x%x\n", te_data->uid); +		return true; + +	default: +		WARN_ON(1); +		return false; +	}; +} + +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			  int duration) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_time_event_data *te_data = &mvmvif->time_event_data; +	static const u8 roc_te_notif[] = { TIME_EVENT_CMD }; +	struct iwl_notification_wait wait_time_event; +	struct iwl_time_event_cmd time_cmd = {}; +	int ret; + +	lockdep_assert_held(&mvm->mutex); +	if (te_data->running) { +		IWL_WARN(mvm, "P2P_DEVICE remain on channel already running\n"); +		return -EBUSY; +	} + +	/* +	 * Flush the done work, just in case it's still pending, so that +	 * the work it does can complete and we can accept new frames. +	 */ +	flush_work(&mvm->roc_done_wk); + +	iwl_init_notification_wait(&mvm->notif_wait, &wait_time_event, +				   roc_te_notif, +				   ARRAY_SIZE(roc_te_notif), +				   iwl_mvm_roc_te_notif, +				   &mvmvif->time_event_data); + +	time_cmd.action = cpu_to_le32(FW_CTXT_ACTION_ADD); +	time_cmd.id_and_color = +		cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, mvmvif->color)); +	time_cmd.id = cpu_to_le32(TE_P2P_DEVICE_DISCOVERABLE); + +	time_cmd.apply_time = cpu_to_le32(0); +	time_cmd.dep_policy = cpu_to_le32(TE_INDEPENDENT); +	time_cmd.is_present = cpu_to_le32(1); + +	time_cmd.interval = cpu_to_le32(1); + +	/* +	 * TE_P2P_DEVICE_DISCOVERABLE can have lower priority than other events +	 * that are being scheduled by the driver/fw, and thus it might not be +	 * scheduled. To improve the chances of it being scheduled, allow it to +	 * be fragmented. +	 * In addition, for the same reasons, allow to delay the scheduling of +	 * the time event. +	 */ +	time_cmd.max_frags = cpu_to_le32(MSEC_TO_TU(duration)/20); +	time_cmd.max_delay = cpu_to_le32(MSEC_TO_TU(duration/2)); +	time_cmd.duration = cpu_to_le32(MSEC_TO_TU(duration)); +	time_cmd.repeat = cpu_to_le32(1); +	time_cmd.notify = cpu_to_le32(TE_NOTIF_HOST_START | TE_NOTIF_HOST_END); + +	/* Push the te data to the tracked te list */ +	te_data->vif = vif; +	te_data->duration = MSEC_TO_TU(duration); + +	spin_lock_bh(&mvm->time_event_lock); +	te_data->id = le32_to_cpu(time_cmd.id); +	list_add_tail(&te_data->list, &mvm->time_event_list); +	spin_unlock_bh(&mvm->time_event_lock); + +	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_EVENT_CMD, CMD_SYNC, +				   sizeof(time_cmd), &time_cmd); +	if (ret) { +		IWL_ERR(mvm, "Couldn't send TIME_EVENT_CMD: %d\n", ret); +		goto out_remove_notif; +	} + +	ret = iwl_wait_notification(&mvm->notif_wait, &wait_time_event, 1 * HZ); +	if (ret) { +		IWL_ERR(mvm, "%s - failed on timeout\n", __func__); +		iwl_mvm_te_clear_data(mvm, te_data); +	} + +	return ret; + +out_remove_notif: +	iwl_remove_notification(&mvm->notif_wait, &wait_time_event); +	return ret; +} + +void iwl_mvm_stop_p2p_roc(struct iwl_mvm *mvm) +{ +	struct iwl_mvm_vif *mvmvif; +	struct iwl_mvm_time_event_data *te_data; + +	lockdep_assert_held(&mvm->mutex); + +	/* +	 * Iterate over the list of time events and find the time event that is +	 * associated with a P2P_DEVICE interface. +	 * This assumes that a P2P_DEVICE interface can have only a single time +	 * event at any given time and this time event coresponds to a ROC +	 * request +	 */ +	mvmvif = NULL; +	spin_lock_bh(&mvm->time_event_lock); +	list_for_each_entry(te_data, &mvm->time_event_list, list) { +		if (te_data->vif->type == NL80211_IFTYPE_P2P_DEVICE) { +			mvmvif = iwl_mvm_vif_from_mac80211(te_data->vif); +			break; +		} +	} +	spin_unlock_bh(&mvm->time_event_lock); + +	if (!mvmvif) { +		IWL_WARN(mvm, "P2P_DEVICE no remain on channel event\n"); +		return; +	} + +	iwl_mvm_remove_time_event(mvm, mvmvif, te_data); + +	iwl_mvm_roc_finished(mvm); +} diff --git a/drivers/net/wireless/iwlwifi/mvm/time-event.h b/drivers/net/wireless/iwlwifi/mvm/time-event.h new file mode 100644 index 00000000000..64fb57a5ab4 --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/time-event.h @@ -0,0 +1,214 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 __time_event_h__ +#define __time_event_h__ + +#include "fw-api.h" + +#include "mvm.h" + +/** + * DOC: Time Events - what is it? + * + * Time Events are a fw feature that allows the driver to control the presence + * of the device on the channel. Since the fw supports multiple channels + * concurrently, the fw may choose to jump to another channel at any time. + * In order to make sure that the fw is on a specific channel at a certain time + * and for a certain duration, the driver needs to issue a time event. + * + * The simplest example is for BSS association. The driver issues a time event, + * waits for it to start, and only then tells mac80211 that we can start the + * association. This way, we make sure that the association will be done + * smoothly and won't be interrupted by channel switch decided within the fw. + */ + + /** + * DOC: The flow against the fw + * + * When the driver needs to make sure we are in a certain channel, at a certain + * time and for a certain duration, it sends a Time Event. The flow against the + * fw goes like this: + *	1) Driver sends a TIME_EVENT_CMD to the fw + *	2) Driver gets the response for that command. This response contains the + *	   Unique ID (UID) of the event. + *	3) The fw sends notification when the event starts. + * + * Of course the API provides various options that allow to cover parameters + * of the flow. + *	What is the duration of the event? + *	What is the start time of the event? + *	Is there an end-time for the event? + *	How much can the event be delayed? + *	Can the event be split? + *	If yes what is the maximal number of chunks? + *	etc... + */ + +/** + * DOC: Abstraction to the driver + * + * In order to simplify the use of time events to the rest of the driver, + * we abstract the use of time events. This component provides the functions + * needed by the driver. + */ + +#define IWL_MVM_TE_SESSION_PROTECTION_MAX_TIME_MS 500 +#define IWL_MVM_TE_SESSION_PROTECTION_MIN_TIME_MS 400 + +/** + * iwl_mvm_protect_session - start / extend the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * @duration: the duration of the session in TU. + * @min_duration: will start a new session if the current session will end + *	in less than min_duration. + * + * This function can be used to start a session protection which means that the + * fw will stay on the channel for %duration_ms milliseconds. This function + * will block (sleep) until the session starts. This function can also be used + * to extend a currently running session. + * This function is meant to be used for BSS association for example, where we + * want to make sure that the fw stays on the channel during the association. + */ +void iwl_mvm_protect_session(struct iwl_mvm *mvm, +			     struct ieee80211_vif *vif, +			     u32 duration, u32 min_duration); + +/** + * iwl_mvm_stop_session_protection - cancel the session protection. + * @mvm: the mvm component + * @vif: the virtual interface for which the session is issued + * + * This functions cancels the session protection which is an act of good + * citizenship. If it is not needed any more it should be cancelled because + * the other bindings wait for the medium during that time. + * This funtions doesn't sleep. + */ +void iwl_mvm_stop_session_protection(struct iwl_mvm *mvm, +				      struct ieee80211_vif *vif); + +/* + * iwl_mvm_rx_time_event_notif - handles %TIME_EVENT_NOTIFICATION. + */ +int iwl_mvm_rx_time_event_notif(struct iwl_mvm *mvm, +				struct iwl_rx_cmd_buffer *rxb, +				struct iwl_device_cmd *cmd); + +/** + * iwl_mvm_start_p2p_roc - start remain on channel for p2p device functionlity + * @mvm: the mvm component + * @vif: the virtual interface for which the roc is requested. It is assumed + * that the vif type is NL80211_IFTYPE_P2P_DEVICE + * @duration: the requested duration in millisecond for the fw to be on the + * channel that is bound to the vif. + * + * This function can be used to issue a remain on channel session, + * which means that the fw will stay in the channel for the request %duration + * milliseconds. The function is async, meaning that it only issues the ROC + * request but does not wait for it to start. Once the FW is ready to serve the + * ROC request, it will issue a notification to the driver that it is on the + * requested channel. Once the FW completes the ROC request it will issue + * another notification to the driver. + */ +int iwl_mvm_start_p2p_roc(struct iwl_mvm *mvm, struct ieee80211_vif *vif, +			  int duration); + +/** + * iwl_mvm_stop_p2p_roc - stop remain on channel for p2p device functionlity + * @mvm: the mvm component + * + * This function can be used to cancel an ongoing ROC session. + * The function is async, it will instruct the FW to stop serving the ROC + * session, but will not wait for the actual stopping of the session. + */ +void iwl_mvm_stop_p2p_roc(struct iwl_mvm *mvm); + +/** + * iwl_mvm_remove_time_event - general function to clean up of time event + * @mvm: the mvm component + * @vif: the vif to which the time event belongs + * @te_data: the time event data that corresponds to that time event + * + * This function can be used to cancel a time event regardless its type. + * It is useful for cleaning up time events running before removing an + * interface. + */ +void iwl_mvm_remove_time_event(struct iwl_mvm *mvm, +			       struct iwl_mvm_vif *mvmvif, +			       struct iwl_mvm_time_event_data *te_data); + +/** + * iwl_mvm_te_clear_data - remove time event from list + * @mvm: the mvm component + * @te_data: the time event data to remove + * + * This function is mostly internal, it is made available here only + * for firmware restart purposes. + */ +void iwl_mvm_te_clear_data(struct iwl_mvm *mvm, +			   struct iwl_mvm_time_event_data *te_data); + +void iwl_mvm_roc_done_wk(struct work_struct *wk); + +#endif /* __time_event_h__ */ diff --git a/drivers/net/wireless/iwlwifi/mvm/tx.c b/drivers/net/wireless/iwlwifi/mvm/tx.c new file mode 100644 index 00000000000..cada8efe0cc --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/tx.c @@ -0,0 +1,916 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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/ieee80211.h> +#include <linux/etherdevice.h> + +#include "iwl-trans.h" +#include "iwl-eeprom-parse.h" +#include "mvm.h" +#include "sta.h" + +/* + * Sets most of the Tx cmd's fields + */ +static void iwl_mvm_set_tx_cmd(struct iwl_mvm *mvm, struct sk_buff *skb, +			       struct iwl_tx_cmd *tx_cmd, +			       struct ieee80211_tx_info *info, u8 sta_id) +{ +	struct ieee80211_hdr *hdr = (void *)skb->data; +	__le16 fc = hdr->frame_control; +	u32 tx_flags = le32_to_cpu(tx_cmd->tx_flags); +	u32 len = skb->len + FCS_LEN; + +	if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) +		tx_flags |= TX_CMD_FLG_ACK; +	else +		tx_flags &= ~TX_CMD_FLG_ACK; + +	if (ieee80211_is_probe_resp(fc)) +		tx_flags |= TX_CMD_FLG_TSF; +	else if (ieee80211_is_back_req(fc)) +		tx_flags |= TX_CMD_FLG_ACK | TX_CMD_FLG_BAR; + +	/* High prio packet (wrt. BT coex) if it is EAPOL, MCAST or MGMT */ +	if (info->band == IEEE80211_BAND_2GHZ        && +	    (skb->protocol == cpu_to_be16(ETH_P_PAE)  || +	     is_multicast_ether_addr(hdr->addr1)      || +	     ieee80211_is_back_req(fc)                || +	     ieee80211_is_mgmt(fc))) +		tx_flags |= TX_CMD_FLG_BT_DIS; + +	if (ieee80211_has_morefrags(fc)) +		tx_flags |= TX_CMD_FLG_MORE_FRAG; + +	if (ieee80211_is_data_qos(fc)) { +		u8 *qc = ieee80211_get_qos_ctl(hdr); +		tx_cmd->tid_tspec = qc[0] & 0xf; +		tx_flags &= ~TX_CMD_FLG_SEQ_CTL; +	} else { +		tx_cmd->tid_tspec = IWL_TID_NON_QOS; +		if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) +			tx_flags |= TX_CMD_FLG_SEQ_CTL; +		else +			tx_flags &= ~TX_CMD_FLG_SEQ_CTL; +	} + +	if (ieee80211_is_mgmt(fc)) { +		if (ieee80211_is_assoc_req(fc) || ieee80211_is_reassoc_req(fc)) +			tx_cmd->pm_frame_timeout = cpu_to_le16(3); +		else +			tx_cmd->pm_frame_timeout = cpu_to_le16(2); + +		/* The spec allows Action frames in A-MPDU, we don't support +		 * it +		 */ +		WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU); +	} else { +		tx_cmd->pm_frame_timeout = 0; +	} + +	if (info->flags & IEEE80211_TX_CTL_AMPDU) +		tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + +	if (ieee80211_is_data(fc) && len > mvm->rts_threshold && +	    !is_multicast_ether_addr(ieee80211_get_DA(hdr))) +		tx_flags |= TX_CMD_FLG_PROT_REQUIRE; + +	tx_cmd->driver_txop = 0; +	tx_cmd->tx_flags = cpu_to_le32(tx_flags); +	/* Total # bytes to be transmitted */ +	tx_cmd->len = cpu_to_le16((u16)skb->len); +	tx_cmd->next_frame_len = 0; +	tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE); +	tx_cmd->sta_id = sta_id; +} + +/* + * Sets the fields in the Tx cmd that are rate related + */ +static void iwl_mvm_set_tx_cmd_rate(struct iwl_mvm *mvm, +				    struct iwl_tx_cmd *tx_cmd, +				    struct ieee80211_tx_info *info, +				    struct ieee80211_sta *sta, +				    __le16 fc) +{ +	u32 rate_flags; +	int rate_idx; +	u8 rate_plcp; + +	/* Set retry limit on RTS packets */ +	tx_cmd->rts_retry_limit = IWL_RTS_DFAULT_RETRY_LIMIT; + +	/* Set retry limit on DATA packets and Probe Responses*/ +	if (ieee80211_is_probe_resp(fc)) { +		tx_cmd->data_retry_limit = IWL_MGMT_DFAULT_RETRY_LIMIT; +		tx_cmd->rts_retry_limit = +			min(tx_cmd->data_retry_limit, tx_cmd->rts_retry_limit); +	} else if (ieee80211_is_back_req(fc)) { +		tx_cmd->data_retry_limit = IWL_BAR_DFAULT_RETRY_LIMIT; +	} else { +		tx_cmd->data_retry_limit = IWL_DEFAULT_TX_RETRY; +	} + +	/* +	 * for data packets, rate info comes from the table inside he fw. This +	 * table is controlled by LINK_QUALITY commands +	 */ + +	if (ieee80211_is_data(fc)) { +		tx_cmd->initial_rate_index = 0; +		tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE); +		return; +	} else if (ieee80211_is_back_req(fc)) { +		tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_STA_RATE); +	} + +	/* HT rate doesn't make sense for a non data frame */ +	WARN_ONCE(info->control.rates[0].flags & IEEE80211_TX_RC_MCS, +		  "Got an HT rate for a non data frame 0x%x\n", +		  info->control.rates[0].flags); + +	rate_idx = info->control.rates[0].idx; +	/* if the rate isn't a well known legacy rate, take the lowest one */ +	if (rate_idx < 0 || rate_idx > IWL_RATE_COUNT_LEGACY) +		rate_idx = rate_lowest_index( +				&mvm->nvm_data->bands[info->band], sta); + +	/* For 5 GHZ band, remap mac80211 rate indices into driver indices */ +	if (info->band == IEEE80211_BAND_5GHZ) +		rate_idx += IWL_FIRST_OFDM_RATE; + +	/* For 2.4 GHZ band, check that there is no need to remap */ +	BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0); + +	/* Get PLCP rate for tx_cmd->rate_n_flags */ +	rate_plcp = iwl_mvm_mac80211_idx_to_hwrate(rate_idx); + +	mvm->mgmt_last_antenna_idx = +		iwl_mvm_next_antenna(mvm, mvm->nvm_data->valid_tx_ant, +				     mvm->mgmt_last_antenna_idx); +	rate_flags = BIT(mvm->mgmt_last_antenna_idx) << RATE_MCS_ANT_POS; + +	/* Set CCK flag as needed */ +	if ((rate_idx >= IWL_FIRST_CCK_RATE) && (rate_idx <= IWL_LAST_CCK_RATE)) +		rate_flags |= RATE_MCS_CCK_MSK; + +	/* Set the rate in the TX cmd */ +	tx_cmd->rate_n_flags = cpu_to_le32((u32)rate_plcp | rate_flags); +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +static void iwl_mvm_set_tx_cmd_crypto(struct iwl_mvm *mvm, +				      struct ieee80211_tx_info *info, +				      struct iwl_tx_cmd *tx_cmd, +				      struct sk_buff *skb_frag) +{ +	struct ieee80211_key_conf *keyconf = info->control.hw_key; + +	switch (keyconf->cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +		tx_cmd->sec_ctl = TX_CMD_SEC_CCM; +		memcpy(tx_cmd->key, keyconf->key, keyconf->keylen); +		if (info->flags & IEEE80211_TX_CTL_AMPDU) +			tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_CCMP_AGG); +		break; + +	case WLAN_CIPHER_SUITE_TKIP: +		tx_cmd->sec_ctl = TX_CMD_SEC_TKIP; +		ieee80211_get_tkip_p2k(keyconf, skb_frag, tx_cmd->key); +		break; + +	case WLAN_CIPHER_SUITE_WEP104: +		tx_cmd->sec_ctl |= TX_CMD_SEC_KEY128; +		/* fall through */ +	case WLAN_CIPHER_SUITE_WEP40: +		tx_cmd->sec_ctl |= TX_CMD_SEC_WEP | +			((keyconf->keyidx << TX_CMD_SEC_WEP_KEY_IDX_POS) & +			  TX_CMD_SEC_WEP_KEY_IDX_MSK); + +		memcpy(&tx_cmd->key[3], keyconf->key, keyconf->keylen); +		break; +	default: +		IWL_ERR(mvm, "Unknown encode cipher %x\n", keyconf->cipher); +		break; +	} +} + +/* + * Allocates and sets the Tx cmd the driver data pointers in the skb + */ +static struct iwl_device_cmd * +iwl_mvm_set_tx_params(struct iwl_mvm *mvm, struct sk_buff *skb, +		      struct ieee80211_sta *sta, u8 sta_id) +{ +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	struct iwl_device_cmd *dev_cmd; +	struct iwl_tx_cmd *tx_cmd; + +	dev_cmd = iwl_trans_alloc_tx_cmd(mvm->trans); + +	if (unlikely(!dev_cmd)) +		return NULL; + +	memset(dev_cmd, 0, sizeof(*dev_cmd)); +	tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + +	if (info->control.hw_key) +		iwl_mvm_set_tx_cmd_crypto(mvm, info, tx_cmd, skb); + +	iwl_mvm_set_tx_cmd(mvm, skb, tx_cmd, info, sta_id); + +	iwl_mvm_set_tx_cmd_rate(mvm, tx_cmd, info, sta, hdr->frame_control); + +	memset(&info->status, 0, sizeof(info->status)); + +	info->driver_data[0] = NULL; +	info->driver_data[1] = dev_cmd; + +	return dev_cmd; +} + +int iwl_mvm_tx_skb_non_sta(struct iwl_mvm *mvm, struct sk_buff *skb) +{ +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	struct iwl_device_cmd *dev_cmd; +	struct iwl_tx_cmd *tx_cmd; +	u8 sta_id; + +	if (WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_AMPDU)) +		return -1; + +	if (WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM && +			 (!info->control.vif || +			  info->hw_queue != info->control.vif->cab_queue))) +		return -1; + +	/* +	 * If the interface on which frame is sent is the P2P_DEVICE +	 * or an AP/GO interface use the broadcast station associated +	 * with it; otherwise use the AUX station. +	 */ +	if (info->control.vif && +	    (info->control.vif->type == NL80211_IFTYPE_P2P_DEVICE || +	     info->control.vif->type == NL80211_IFTYPE_AP)) { +		struct iwl_mvm_vif *mvmvif = +			iwl_mvm_vif_from_mac80211(info->control.vif); +		sta_id = mvmvif->bcast_sta.sta_id; +	} else { +		sta_id = mvm->aux_sta.sta_id; +	} + +	IWL_DEBUG_TX(mvm, "station Id %d, queue=%d\n", sta_id, info->hw_queue); + +	dev_cmd = iwl_mvm_set_tx_params(mvm, skb, NULL, sta_id); +	if (!dev_cmd) +		return -1; + +	/* From now on, we cannot access info->control */ +	tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; + +	/* Copy MAC header from skb into command buffer */ +	memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(hdr->frame_control)); + +	if (iwl_trans_tx(mvm->trans, skb, dev_cmd, info->hw_queue)) { +		iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); +		return -1; +	} + +	return 0; +} + +/* + * Sets the fields in the Tx cmd that are crypto related + */ +int iwl_mvm_tx_skb(struct iwl_mvm *mvm, struct sk_buff *skb, +		   struct ieee80211_sta *sta) +{ +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	struct iwl_mvm_sta *mvmsta; +	struct iwl_device_cmd *dev_cmd; +	struct iwl_tx_cmd *tx_cmd; +	__le16 fc; +	u16 seq_number = 0; +	u8 tid = IWL_MAX_TID_COUNT; +	u8 txq_id = info->hw_queue; +	bool is_data_qos = false, is_ampdu = false; + +	mvmsta = (void *)sta->drv_priv; +	fc = hdr->frame_control; + +	if (WARN_ON_ONCE(!mvmsta)) +		return -1; + +	if (WARN_ON_ONCE(mvmsta->sta_id == IWL_INVALID_STATION)) +		return -1; + +	dev_cmd = iwl_mvm_set_tx_params(mvm, skb, sta, mvmsta->sta_id); +	if (!dev_cmd) +		goto drop; + +	tx_cmd = (struct iwl_tx_cmd *)dev_cmd->payload; +	/* From now on, we cannot access info->control */ + +	spin_lock(&mvmsta->lock); + +	if (ieee80211_is_data_qos(fc) && !ieee80211_is_qos_nullfunc(fc)) { +		u8 *qc = NULL; +		qc = ieee80211_get_qos_ctl(hdr); +		tid = qc[0] & IEEE80211_QOS_CTL_TID_MASK; +		if (WARN_ON_ONCE(tid >= IWL_MAX_TID_COUNT)) +			goto drop_unlock_sta; + +		seq_number = mvmsta->tid_data[tid].seq_number; +		seq_number &= IEEE80211_SCTL_SEQ; +		hdr->seq_ctrl &= cpu_to_le16(IEEE80211_SCTL_FRAG); +		hdr->seq_ctrl |= cpu_to_le16(seq_number); +		seq_number += 0x10; +		is_data_qos = true; +		is_ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; +	} + +	/* Copy MAC header from skb into command buffer */ +	memcpy(tx_cmd->hdr, hdr, ieee80211_hdrlen(fc)); + +	WARN_ON_ONCE(info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM); + +	if (is_ampdu) { +		if (WARN_ON_ONCE(mvmsta->tid_data[tid].state != IWL_AGG_ON)) +			goto drop_unlock_sta; +		txq_id = mvmsta->tid_data[tid].txq_id; +	} + +	IWL_DEBUG_TX(mvm, "TX to [%d|%d] Q:%d - seq: 0x%x\n", mvmsta->sta_id, +		     tid, txq_id, seq_number); + +	/* NOTE: aggregation will need changes here (for txq id) */ +	if (iwl_trans_tx(mvm->trans, skb, dev_cmd, txq_id)) +		goto drop_unlock_sta; + +	if (is_data_qos && !ieee80211_has_morefrags(fc)) +		mvmsta->tid_data[tid].seq_number = seq_number; + +	spin_unlock(&mvmsta->lock); + +	if (mvmsta->vif->type == NL80211_IFTYPE_AP && +	    txq_id < IWL_FIRST_AMPDU_QUEUE) +		atomic_inc(&mvmsta->pending_frames); + +	return 0; + +drop_unlock_sta: +	iwl_trans_free_tx_cmd(mvm->trans, dev_cmd); +	spin_unlock(&mvmsta->lock); +drop: +	return -1; +} + +static void iwl_mvm_check_ratid_empty(struct iwl_mvm *mvm, +				      struct ieee80211_sta *sta, u8 tid) +{ +	struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; +	struct iwl_mvm_tid_data *tid_data = &mvmsta->tid_data[tid]; +	struct ieee80211_vif *vif = mvmsta->vif; + +	lockdep_assert_held(&mvmsta->lock); + +	if (tid_data->ssn != tid_data->next_reclaimed) +		return; + +	switch (tid_data->state) { +	case IWL_EMPTYING_HW_QUEUE_ADDBA: +		IWL_DEBUG_TX_QUEUES(mvm, +				    "Can continue addBA flow ssn = next_recl = %d\n", +				    tid_data->next_reclaimed); +		tid_data->state = IWL_AGG_STARTING; +		ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); +		break; + +	case IWL_EMPTYING_HW_QUEUE_DELBA: +		IWL_DEBUG_TX_QUEUES(mvm, +				    "Can continue DELBA flow ssn = next_recl = %d\n", +				    tid_data->next_reclaimed); +		iwl_trans_txq_disable(mvm->trans, tid_data->txq_id); +		tid_data->state = IWL_AGG_OFF; +		/* +		 * we can't hold the mutex - but since we are after a sequence +		 * point (call to iwl_trans_txq_disable), so we don't even need +		 * a memory barrier. +		 */ +		mvm->queue_to_mac80211[tid_data->txq_id] = +					IWL_INVALID_MAC80211_QUEUE; +		ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); +		break; + +	default: +		break; +	} +} + +#ifdef CONFIG_IWLWIFI_DEBUG +const char *iwl_mvm_get_tx_fail_reason(u32 status) +{ +#define TX_STATUS_FAIL(x) case TX_STATUS_FAIL_ ## x: return #x +#define TX_STATUS_POSTPONE(x) case TX_STATUS_POSTPONE_ ## x: return #x + +	switch (status & TX_STATUS_MSK) { +	case TX_STATUS_SUCCESS: +		return "SUCCESS"; +	TX_STATUS_POSTPONE(DELAY); +	TX_STATUS_POSTPONE(FEW_BYTES); +	TX_STATUS_POSTPONE(BT_PRIO); +	TX_STATUS_POSTPONE(QUIET_PERIOD); +	TX_STATUS_POSTPONE(CALC_TTAK); +	TX_STATUS_FAIL(INTERNAL_CROSSED_RETRY); +	TX_STATUS_FAIL(SHORT_LIMIT); +	TX_STATUS_FAIL(LONG_LIMIT); +	TX_STATUS_FAIL(UNDERRUN); +	TX_STATUS_FAIL(DRAIN_FLOW); +	TX_STATUS_FAIL(RFKILL_FLUSH); +	TX_STATUS_FAIL(LIFE_EXPIRE); +	TX_STATUS_FAIL(DEST_PS); +	TX_STATUS_FAIL(HOST_ABORTED); +	TX_STATUS_FAIL(BT_RETRY); +	TX_STATUS_FAIL(STA_INVALID); +	TX_STATUS_FAIL(FRAG_DROPPED); +	TX_STATUS_FAIL(TID_DISABLE); +	TX_STATUS_FAIL(FIFO_FLUSHED); +	TX_STATUS_FAIL(SMALL_CF_POLL); +	TX_STATUS_FAIL(FW_DROP); +	TX_STATUS_FAIL(STA_COLOR_MISMATCH); +	} + +	return "UNKNOWN"; + +#undef TX_STATUS_FAIL +#undef TX_STATUS_POSTPONE +} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +/** + * translate ucode response to mac80211 tx status control values + */ +static void iwl_mvm_hwrate_to_tx_control(u32 rate_n_flags, +					 struct ieee80211_tx_info *info) +{ +	struct ieee80211_tx_rate *r = &info->status.rates[0]; + +	info->status.antenna = +		((rate_n_flags & RATE_MCS_ANT_ABC_MSK) >> RATE_MCS_ANT_POS); +	if (rate_n_flags & RATE_HT_MCS_GF_MSK) +		r->flags |= IEEE80211_TX_RC_GREEN_FIELD; +	switch (rate_n_flags & RATE_MCS_CHAN_WIDTH_MSK) { +	case RATE_MCS_CHAN_WIDTH_20: +		break; +	case RATE_MCS_CHAN_WIDTH_40: +		r->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH; +		break; +	case RATE_MCS_CHAN_WIDTH_80: +		r->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH; +		break; +	case RATE_MCS_CHAN_WIDTH_160: +		r->flags |= IEEE80211_TX_RC_160_MHZ_WIDTH; +		break; +	} +	if (rate_n_flags & RATE_MCS_SGI_MSK) +		r->flags |= IEEE80211_TX_RC_SHORT_GI; +	if (rate_n_flags & RATE_MCS_HT_MSK) { +		r->flags |= IEEE80211_TX_RC_MCS; +		r->idx = rate_n_flags & RATE_HT_MCS_INDEX_MSK; +	} else if (rate_n_flags & RATE_MCS_VHT_MSK) { +		ieee80211_rate_set_vht( +			r, rate_n_flags & RATE_VHT_MCS_RATE_CODE_MSK, +			((rate_n_flags & RATE_VHT_MCS_NSS_MSK) >> +						RATE_VHT_MCS_NSS_POS) + 1); +		r->flags |= IEEE80211_TX_RC_VHT_MCS; +	} else { +		r->idx = iwl_mvm_legacy_rate_to_mac80211_idx(rate_n_flags, +							     info->band); +	} +} + +static void iwl_mvm_rx_tx_cmd_single(struct iwl_mvm *mvm, +				     struct iwl_rx_packet *pkt) +{ +	struct ieee80211_sta *sta; +	u16 sequence = le16_to_cpu(pkt->hdr.sequence); +	int txq_id = SEQ_TO_QUEUE(sequence); +	struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; +	int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); +	int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); +	u32 status = le16_to_cpu(tx_resp->status.status); +	u16 ssn = iwl_mvm_get_scd_ssn(tx_resp); +	struct iwl_mvm_sta *mvmsta; +	struct sk_buff_head skbs; +	u8 skb_freed = 0; +	u16 next_reclaimed, seq_ctl; + +	__skb_queue_head_init(&skbs); + +	seq_ctl = le16_to_cpu(tx_resp->seq_ctl); + +	/* we can free until ssn % q.n_bd not inclusive */ +	iwl_trans_reclaim(mvm->trans, txq_id, ssn, &skbs); + +	while (!skb_queue_empty(&skbs)) { +		struct sk_buff *skb = __skb_dequeue(&skbs); +		struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + +		skb_freed++; + +		iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + +		memset(&info->status, 0, sizeof(info->status)); + +		info->flags &= ~IEEE80211_TX_CTL_AMPDU; + +		/* inform mac80211 about what happened with the frame */ +		switch (status & TX_STATUS_MSK) { +		case TX_STATUS_SUCCESS: +		case TX_STATUS_DIRECT_DONE: +			info->flags |= IEEE80211_TX_STAT_ACK; +			break; +		case TX_STATUS_FAIL_DEST_PS: +			info->flags |= IEEE80211_TX_STAT_TX_FILTERED; +			break; +		default: +			break; +		} + +		info->status.rates[0].count = tx_resp->failure_frame + 1; +		iwl_mvm_hwrate_to_tx_control(le32_to_cpu(tx_resp->initial_rate), +					     info); + +		/* Single frame failure in an AMPDU queue => send BAR */ +		if (txq_id >= IWL_FIRST_AMPDU_QUEUE && +		    !(info->flags & IEEE80211_TX_STAT_ACK)) { +			/* there must be only one skb in the skb_list */ +			WARN_ON_ONCE(skb_freed > 1 || +				     !skb_queue_empty(&skbs)); +			info->flags |= IEEE80211_TX_STAT_AMPDU_NO_BACK; +		} + +		/* W/A FW bug: seq_ctl is wrong when the queue is flushed */ +		if (status == TX_STATUS_FAIL_FIFO_FLUSHED) { +			struct ieee80211_hdr *hdr = (void *)skb->data; +			seq_ctl = le16_to_cpu(hdr->seq_ctrl); +		} + +		ieee80211_tx_status(mvm->hw, skb); +	} + +	if (txq_id >= IWL_FIRST_AMPDU_QUEUE) { +		/* If this is an aggregation queue, we use the ssn since: +		 * ssn = wifi seq_num % 256. +		 * The seq_ctl is the sequence control of the packet to which +		 * this Tx response relates. But if there is a hole in the +		 * bitmap of the BA we received, this Tx response may allow to +		 * reclaim the hole and all the subsequent packets that were +		 * already acked. In that case, seq_ctl != ssn, and the next +		 * packet to be reclaimed will be ssn and not seq_ctl. In that +		 * case, several packets will be reclaimed even if +		 * frame_count = 1. +		 * +		 * The ssn is the index (% 256) of the latest packet that has +		 * treated (acked / dropped) + 1. +		 */ +		next_reclaimed = ssn; +	} else { +		/* The next packet to be reclaimed is the one after this one */ +		next_reclaimed = SEQ_TO_SN(seq_ctl + 0x10); +	} + +	IWL_DEBUG_TX_REPLY(mvm, +			   "TXQ %d status %s (0x%08x)\n\t\t\t\tinitial_rate 0x%x " +			    "retries %d, idx=%d ssn=%d next_reclaimed=0x%x seq_ctl=0x%x\n", +			   txq_id, iwl_mvm_get_tx_fail_reason(status), +			   status, le32_to_cpu(tx_resp->initial_rate), +			   tx_resp->failure_frame, SEQ_TO_INDEX(sequence), +			   ssn, next_reclaimed, seq_ctl); + +	rcu_read_lock(); + +	sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + +	if (!IS_ERR_OR_NULL(sta)) { +		mvmsta = (void *)sta->drv_priv; + +		if (tid != IWL_TID_NON_QOS) { +			struct iwl_mvm_tid_data *tid_data = +				&mvmsta->tid_data[tid]; + +			spin_lock(&mvmsta->lock); +			tid_data->next_reclaimed = next_reclaimed; +			IWL_DEBUG_TX_REPLY(mvm, "Next reclaimed packet:%d\n", +					   next_reclaimed); +			iwl_mvm_check_ratid_empty(mvm, sta, tid); +			spin_unlock(&mvmsta->lock); +		} + +#ifdef CONFIG_PM_SLEEP +		mvmsta->last_seq_ctl = seq_ctl; +#endif +	} else { +		sta = NULL; +		mvmsta = NULL; +	} + +	/* +	 * If the txq is not an AMPDU queue, there is no chance we freed +	 * several skbs. Check that out... +	 * If there are no pending frames for this STA, notify mac80211 that +	 * this station can go to sleep in its STA table. +	 */ +	if (txq_id < IWL_FIRST_AMPDU_QUEUE && mvmsta && +	    !WARN_ON(skb_freed > 1) && +	    mvmsta->vif->type == NL80211_IFTYPE_AP && +	    atomic_sub_and_test(skb_freed, &mvmsta->pending_frames)) { +		ieee80211_sta_block_awake(mvm->hw, sta, false); +		set_bit(sta_id, mvm->sta_drained); +		schedule_work(&mvm->sta_drained_wk); +	} + +	rcu_read_unlock(); +} + +#ifdef CONFIG_IWLWIFI_DEBUG +#define AGG_TX_STATE_(x) case AGG_TX_STATE_ ## x: return #x +static const char *iwl_get_agg_tx_status(u16 status) +{ +	switch (status & AGG_TX_STATE_STATUS_MSK) { +	AGG_TX_STATE_(TRANSMITTED); +	AGG_TX_STATE_(UNDERRUN); +	AGG_TX_STATE_(BT_PRIO); +	AGG_TX_STATE_(FEW_BYTES); +	AGG_TX_STATE_(ABORT); +	AGG_TX_STATE_(LAST_SENT_TTL); +	AGG_TX_STATE_(LAST_SENT_TRY_CNT); +	AGG_TX_STATE_(LAST_SENT_BT_KILL); +	AGG_TX_STATE_(SCD_QUERY); +	AGG_TX_STATE_(TEST_BAD_CRC32); +	AGG_TX_STATE_(RESPONSE); +	AGG_TX_STATE_(DUMP_TX); +	AGG_TX_STATE_(DELAY_TX); +	} + +	return "UNKNOWN"; +} + +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, +				      struct iwl_rx_packet *pkt) +{ +	struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; +	struct agg_tx_status *frame_status = &tx_resp->status; +	int i; + +	for (i = 0; i < tx_resp->frame_count; i++) { +		u16 fstatus = le16_to_cpu(frame_status[i].status); + +		IWL_DEBUG_TX_REPLY(mvm, +				   "status %s (0x%04x), try-count (%d) seq (0x%x)\n", +				   iwl_get_agg_tx_status(fstatus), +				   fstatus & AGG_TX_STATE_STATUS_MSK, +				   (fstatus & AGG_TX_STATE_TRY_CNT_MSK) >> +					AGG_TX_STATE_TRY_CNT_POS, +				   le16_to_cpu(frame_status[i].sequence)); +	} +} +#else +static void iwl_mvm_rx_tx_cmd_agg_dbg(struct iwl_mvm *mvm, +				      struct iwl_rx_packet *pkt) +{} +#endif /* CONFIG_IWLWIFI_DEBUG */ + +static void iwl_mvm_rx_tx_cmd_agg(struct iwl_mvm *mvm, +				  struct iwl_rx_packet *pkt) +{ +	struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; +	int sta_id = IWL_MVM_TX_RES_GET_RA(tx_resp->ra_tid); +	int tid = IWL_MVM_TX_RES_GET_TID(tx_resp->ra_tid); +	u16 sequence = le16_to_cpu(pkt->hdr.sequence); +	struct ieee80211_sta *sta; + +	if (WARN_ON_ONCE(SEQ_TO_QUEUE(sequence) < IWL_FIRST_AMPDU_QUEUE)) +		return; + +	if (WARN_ON_ONCE(tid == IWL_TID_NON_QOS)) +		return; + +	iwl_mvm_rx_tx_cmd_agg_dbg(mvm, pkt); + +	rcu_read_lock(); + +	sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + +	if (!WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { +		struct iwl_mvm_sta *mvmsta = (void *)sta->drv_priv; +		mvmsta->tid_data[tid].rate_n_flags = +			le32_to_cpu(tx_resp->initial_rate); +	} + +	rcu_read_unlock(); +} + +int iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +		      struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_mvm_tx_resp *tx_resp = (void *)pkt->data; + +	if (tx_resp->frame_count == 1) +		iwl_mvm_rx_tx_cmd_single(mvm, pkt); +	else +		iwl_mvm_rx_tx_cmd_agg(mvm, pkt); + +	return 0; +} + +int iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_mvm_ba_notif *ba_notif = (void *)pkt->data; +	struct sk_buff_head reclaimed_skbs; +	struct iwl_mvm_tid_data *tid_data; +	struct ieee80211_tx_info *info; +	struct ieee80211_sta *sta; +	struct iwl_mvm_sta *mvmsta; +	struct ieee80211_hdr *hdr; +	struct sk_buff *skb; +	int sta_id, tid, freed; + +	/* "flow" corresponds to Tx queue */ +	u16 scd_flow = le16_to_cpu(ba_notif->scd_flow); + +	/* "ssn" is start of block-ack Tx window, corresponds to index +	 * (in Tx queue's circular buffer) of first TFD/frame in window */ +	u16 ba_resp_scd_ssn = le16_to_cpu(ba_notif->scd_ssn); + +	sta_id = ba_notif->sta_id; +	tid = ba_notif->tid; + +	rcu_read_lock(); + +	sta = rcu_dereference(mvm->fw_id_to_mac_id[sta_id]); + +	/* Reclaiming frames for a station that has been deleted ? */ +	if (WARN_ON_ONCE(IS_ERR_OR_NULL(sta))) { +		rcu_read_unlock(); +		return 0; +	} + +	mvmsta = (void *)sta->drv_priv; +	tid_data = &mvmsta->tid_data[tid]; + +	if (WARN_ONCE(tid_data->txq_id != scd_flow, "Q %d, tid %d, flow %d", +		      tid_data->txq_id, tid, scd_flow)) { +		rcu_read_unlock(); +		return 0; +	} + +	spin_lock(&mvmsta->lock); + +	__skb_queue_head_init(&reclaimed_skbs); + +	/* +	 * Release all TFDs before the SSN, i.e. all TFDs in front of +	 * block-ack window (we assume that they've been successfully +	 * transmitted ... if not, it's too late anyway). +	 */ +	iwl_trans_reclaim(mvm->trans, scd_flow, ba_resp_scd_ssn, +			  &reclaimed_skbs); + +	IWL_DEBUG_TX_REPLY(mvm, +			   "BA_NOTIFICATION Received from %pM, sta_id = %d\n", +			   (u8 *)&ba_notif->sta_addr_lo32, +			   ba_notif->sta_id); +	IWL_DEBUG_TX_REPLY(mvm, +			   "TID = %d, SeqCtl = %d, bitmap = 0x%llx, scd_flow = %d, scd_ssn = %d sent:%d, acked:%d\n", +			   ba_notif->tid, le16_to_cpu(ba_notif->seq_ctl), +			   (unsigned long long)le64_to_cpu(ba_notif->bitmap), +			   scd_flow, ba_resp_scd_ssn, ba_notif->txed, +			   ba_notif->txed_2_done); + +	tid_data->next_reclaimed = ba_resp_scd_ssn; + +	iwl_mvm_check_ratid_empty(mvm, sta, tid); + +	freed = 0; + +	skb_queue_walk(&reclaimed_skbs, skb) { +		hdr = (struct ieee80211_hdr *)skb->data; + +		if (ieee80211_is_data_qos(hdr->frame_control)) +			freed++; +		else +			WARN_ON_ONCE(1); + +		info = IEEE80211_SKB_CB(skb); +		iwl_trans_free_tx_cmd(mvm->trans, info->driver_data[1]); + +		if (freed == 1) { +			/* this is the first skb we deliver in this batch */ +			/* put the rate scaling data there */ +			info = IEEE80211_SKB_CB(skb); +			memset(&info->status, 0, sizeof(info->status)); +			info->flags |= IEEE80211_TX_STAT_ACK; +			info->flags |= IEEE80211_TX_STAT_AMPDU; +			info->status.ampdu_ack_len = ba_notif->txed_2_done; +			info->status.ampdu_len = ba_notif->txed; +			iwl_mvm_hwrate_to_tx_control(tid_data->rate_n_flags, +						     info); +		} +	} + +	spin_unlock(&mvmsta->lock); + +	rcu_read_unlock(); + +	while (!skb_queue_empty(&reclaimed_skbs)) { +		skb = __skb_dequeue(&reclaimed_skbs); +		ieee80211_tx_status(mvm->hw, skb); +	} + +	return 0; +} + +int iwl_mvm_flush_tx_path(struct iwl_mvm *mvm, u32 tfd_msk, bool sync) +{ +	int ret; +	struct iwl_tx_path_flush_cmd flush_cmd = { +		.queues_ctl = cpu_to_le32(tfd_msk), +		.flush_ctl = cpu_to_le16(DUMP_TX_FIFO_FLUSH), +	}; + +	u32 flags = sync ? CMD_SYNC : CMD_ASYNC; + +	ret = iwl_mvm_send_cmd_pdu(mvm, TXPATH_FLUSH, flags, +				   sizeof(flush_cmd), &flush_cmd); +	if (ret) +		IWL_ERR(mvm, "Failed to send flush command (%d)\n", ret); +	return ret; +} diff --git a/drivers/net/wireless/iwlwifi/mvm/utils.c b/drivers/net/wireless/iwlwifi/mvm/utils.c new file mode 100644 index 00000000000..000e842c2ed --- /dev/null +++ b/drivers/net/wireless/iwlwifi/mvm/utils.c @@ -0,0 +1,472 @@ +/****************************************************************************** + * + * 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 - 2013 Intel Corporation. All rights reserved. + * + * 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 LICENSE.GPL. + * + * 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 - 2013 Intel Corporation. All rights reserved. + * 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 "iwl-debug.h" +#include "iwl-io.h" + +#include "mvm.h" +#include "fw-api-rs.h" + +/* + * Will return 0 even if the cmd failed when RFKILL is asserted unless + * CMD_WANT_SKB is set in cmd->flags. + */ +int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) +{ +	int ret; + +	/* +	 * Synchronous commands from this op-mode must hold +	 * the mutex, this ensures we don't try to send two +	 * (or more) synchronous commands at a time. +	 */ +	if (!(cmd->flags & CMD_ASYNC)) +		lockdep_assert_held(&mvm->mutex); + +	ret = iwl_trans_send_cmd(mvm->trans, cmd); + +	/* +	 * If the caller wants the SKB, then don't hide any problems, the +	 * caller might access the response buffer which will be NULL if +	 * the command failed. +	 */ +	if (cmd->flags & CMD_WANT_SKB) +		return ret; + +	/* Silently ignore failures if RFKILL is asserted */ +	if (!ret || ret == -ERFKILL) +		return 0; +	return ret; +} + +int iwl_mvm_send_cmd_pdu(struct iwl_mvm *mvm, u8 id, +			 u32 flags, u16 len, const void *data) +{ +	struct iwl_host_cmd cmd = { +		.id = id, +		.len = { len, }, +		.data = { data, }, +		.flags = flags, +	}; + +	return iwl_mvm_send_cmd(mvm, &cmd); +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, +			    u32 *status) +{ +	struct iwl_rx_packet *pkt; +	struct iwl_cmd_response *resp; +	int ret, resp_len; + +	lockdep_assert_held(&mvm->mutex); + +	/* +	 * Only synchronous commands can wait for status, +	 * we use WANT_SKB so the caller can't. +	 */ +	if (WARN_ONCE(cmd->flags & (CMD_ASYNC | CMD_WANT_SKB), +		      "cmd flags %x", cmd->flags)) +		return -EINVAL; + +	cmd->flags |= CMD_SYNC | CMD_WANT_SKB; + +	ret = iwl_trans_send_cmd(mvm->trans, cmd); +	if (ret == -ERFKILL) { +		/* +		 * The command failed because of RFKILL, don't update +		 * the status, leave it as success and return 0. +		 */ +		return 0; +	} else if (ret) { +		return ret; +	} + +	pkt = cmd->resp_pkt; +	/* Can happen if RFKILL is asserted */ +	if (!pkt) { +		ret = 0; +		goto out_free_resp; +	} + +	if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { +		ret = -EIO; +		goto out_free_resp; +	} + +	resp_len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; +	if (WARN_ON_ONCE(resp_len != sizeof(pkt->hdr) + sizeof(*resp))) { +		ret = -EIO; +		goto out_free_resp; +	} + +	resp = (void *)pkt->data; +	*status = le32_to_cpu(resp->status); + out_free_resp: +	iwl_free_resp(cmd); +	return ret; +} + +/* + * We assume that the caller set the status to the sucess value + */ +int iwl_mvm_send_cmd_pdu_status(struct iwl_mvm *mvm, u8 id, u16 len, +				const void *data, u32 *status) +{ +	struct iwl_host_cmd cmd = { +		.id = id, +		.len = { len, }, +		.data = { data, }, +	}; + +	return iwl_mvm_send_cmd_status(mvm, &cmd, status); +} + +#define IWL_DECLARE_RATE_INFO(r) \ +	[IWL_RATE_##r##M_INDEX] = IWL_RATE_##r##M_PLCP + +/* + * Translate from fw_rate_index (IWL_RATE_XXM_INDEX) to PLCP + */ +static const u8 fw_rate_idx_to_plcp[IWL_RATE_COUNT] = { +	IWL_DECLARE_RATE_INFO(1), +	IWL_DECLARE_RATE_INFO(2), +	IWL_DECLARE_RATE_INFO(5), +	IWL_DECLARE_RATE_INFO(11), +	IWL_DECLARE_RATE_INFO(6), +	IWL_DECLARE_RATE_INFO(9), +	IWL_DECLARE_RATE_INFO(12), +	IWL_DECLARE_RATE_INFO(18), +	IWL_DECLARE_RATE_INFO(24), +	IWL_DECLARE_RATE_INFO(36), +	IWL_DECLARE_RATE_INFO(48), +	IWL_DECLARE_RATE_INFO(54), +}; + +int iwl_mvm_legacy_rate_to_mac80211_idx(u32 rate_n_flags, +					enum ieee80211_band band) +{ +	int rate = rate_n_flags & RATE_LEGACY_RATE_MSK; +	int idx; +	int band_offset = 0; + +	/* Legacy rate format, search for match in table */ +	if (band == IEEE80211_BAND_5GHZ) +		band_offset = IWL_FIRST_OFDM_RATE; +	for (idx = band_offset; idx < IWL_RATE_COUNT_LEGACY; idx++) +		if (fw_rate_idx_to_plcp[idx] == rate) +			return idx - band_offset; + +	return -1; +} + +u8 iwl_mvm_mac80211_idx_to_hwrate(int rate_idx) +{ +	/* Get PLCP rate for tx_cmd->rate_n_flags */ +	return fw_rate_idx_to_plcp[rate_idx]; +} + +int iwl_mvm_rx_fw_error(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb, +			  struct iwl_device_cmd *cmd) +{ +	struct iwl_rx_packet *pkt = rxb_addr(rxb); +	struct iwl_error_resp *err_resp = (void *)pkt->data; + +	IWL_ERR(mvm, "FW Error notification: type 0x%08X cmd_id 0x%02X\n", +		le32_to_cpu(err_resp->error_type), err_resp->cmd_id); +	IWL_ERR(mvm, "FW Error notification: seq 0x%04X service 0x%08X\n", +		le16_to_cpu(err_resp->bad_cmd_seq_num), +		le32_to_cpu(err_resp->error_service)); +	IWL_ERR(mvm, "FW Error notification: timestamp 0x%16llX\n", +		le64_to_cpu(err_resp->timestamp)); +	return 0; +} + +/* + * Returns the first antenna as ANT_[ABC], as defined in iwl-config.h. + * The parameter should also be a combination of ANT_[ABC]. + */ +u8 first_antenna(u8 mask) +{ +	BUILD_BUG_ON(ANT_A != BIT(0)); /* using ffs is wrong if not */ +	WARN_ON_ONCE(!mask); /* ffs will return 0 if mask is zeroed */ +	return (u8)(BIT(ffs(mask))); +} + +/* + * Toggles between TX antennas to send the probe request on. + * Receives the bitmask of valid TX antennas and the *index* used + * for the last TX, and returns the next valid *index* to use. + * In order to set it in the tx_cmd, must do BIT(idx). + */ +u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx) +{ +	u8 ind = last_idx; +	int i; + +	for (i = 0; i < RATE_MCS_ANT_NUM; i++) { +		ind = (ind + 1) % RATE_MCS_ANT_NUM; +		if (valid & BIT(ind)) +			return ind; +	} + +	WARN_ONCE(1, "Failed to toggle between antennas 0x%x", valid); +	return last_idx; +} + +static struct { +	char *name; +	u8 num; +} advanced_lookup[] = { +	{ "NMI_INTERRUPT_WDG", 0x34 }, +	{ "SYSASSERT", 0x35 }, +	{ "UCODE_VERSION_MISMATCH", 0x37 }, +	{ "BAD_COMMAND", 0x38 }, +	{ "NMI_INTERRUPT_DATA_ACTION_PT", 0x3C }, +	{ "FATAL_ERROR", 0x3D }, +	{ "NMI_TRM_HW_ERR", 0x46 }, +	{ "NMI_INTERRUPT_TRM", 0x4C }, +	{ "NMI_INTERRUPT_BREAK_POINT", 0x54 }, +	{ "NMI_INTERRUPT_WDG_RXF_FULL", 0x5C }, +	{ "NMI_INTERRUPT_WDG_NO_RBD_RXF_FULL", 0x64 }, +	{ "NMI_INTERRUPT_HOST", 0x66 }, +	{ "NMI_INTERRUPT_ACTION_PT", 0x7C }, +	{ "NMI_INTERRUPT_UNKNOWN", 0x84 }, +	{ "NMI_INTERRUPT_INST_ACTION_PT", 0x86 }, +	{ "ADVANCED_SYSASSERT", 0 }, +}; + +static const char *desc_lookup(u32 num) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(advanced_lookup) - 1; i++) +		if (advanced_lookup[i].num == num) +			return advanced_lookup[i].name; + +	/* No entry matches 'num', so it is the last: ADVANCED_SYSASSERT */ +	return advanced_lookup[i].name; +} + +/* + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_error_event_table { +	u32 valid;		/* (nonzero) valid, (0) log is empty */ +	u32 error_id;		/* type of error */ +	u32 pc;			/* program counter */ +	u32 blink1;		/* branch link */ +	u32 blink2;		/* branch link */ +	u32 ilink1;		/* interrupt link */ +	u32 ilink2;		/* interrupt link */ +	u32 data1;		/* error-specific data */ +	u32 data2;		/* error-specific data */ +	u32 data3;		/* error-specific data */ +	u32 bcon_time;		/* beacon timer */ +	u32 tsf_low;		/* network timestamp function timer */ +	u32 tsf_hi;		/* network timestamp function timer */ +	u32 gp1;		/* GP1 timer register */ +	u32 gp2;		/* GP2 timer register */ +	u32 gp3;		/* GP3 timer register */ +	u32 ucode_ver;		/* uCode version */ +	u32 hw_ver;		/* HW Silicon version */ +	u32 brd_ver;		/* HW board version */ +	u32 log_pc;		/* log program counter */ +	u32 frame_ptr;		/* frame pointer */ +	u32 stack_ptr;		/* stack pointer */ +	u32 hcmd;		/* last host command header */ +	u32 isr0;		/* isr status register LMPM_NIC_ISR0: +				 * rxtx_flag */ +	u32 isr1;		/* isr status register LMPM_NIC_ISR1: +				 * host_flag */ +	u32 isr2;		/* isr status register LMPM_NIC_ISR2: +				 * enc_flag */ +	u32 isr3;		/* isr status register LMPM_NIC_ISR3: +				 * time_flag */ +	u32 isr4;		/* isr status register LMPM_NIC_ISR4: +				 * wico interrupt */ +	u32 isr_pref;		/* isr status register LMPM_NIC_PREF_STAT */ +	u32 wait_event;		/* wait event() caller address */ +	u32 l2p_control;	/* L2pControlField */ +	u32 l2p_duration;	/* L2pDurationField */ +	u32 l2p_mhvalid;	/* L2pMhValidBits */ +	u32 l2p_addr_match;	/* L2pAddrMatchStat */ +	u32 lmpm_pmg_sel;	/* indicate which clocks are turned on +				 * (LMPM_PMG_SEL) */ +	u32 u_timestamp;	/* indicate when the date and time of the +				 * compilation */ +	u32 flow_handler;	/* FH read/write pointers, RX credit */ +} __packed; + +#define ERROR_START_OFFSET  (1 * sizeof(u32)) +#define ERROR_ELEM_SIZE     (7 * sizeof(u32)) + +void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) +{ +	struct iwl_trans *trans = mvm->trans; +	struct iwl_error_event_table table; +	u32 base; + +	base = mvm->error_event_table; +	if (mvm->cur_ucode == IWL_UCODE_INIT) { +		if (!base) +			base = mvm->fw->init_errlog_ptr; +	} else { +		if (!base) +			base = mvm->fw->inst_errlog_ptr; +	} + +	if (base < 0x800000 || base >= 0x80C000) { +		IWL_ERR(mvm, +			"Not valid error log pointer 0x%08X for %s uCode\n", +			base, +			(mvm->cur_ucode == IWL_UCODE_INIT) +					? "Init" : "RT"); +		return; +	} + +	iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + +	if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { +		IWL_ERR(trans, "Start IWL Error Log Dump:\n"); +		IWL_ERR(trans, "Status: 0x%08lX, count: %d\n", +			mvm->status, table.valid); +	} + +	trace_iwlwifi_dev_ucode_error(trans->dev, table.error_id, table.tsf_low, +				      table.data1, table.data2, table.data3, +				      table.blink1, table.blink2, table.ilink1, +				      table.ilink2, table.bcon_time, table.gp1, +				      table.gp2, table.gp3, table.ucode_ver, +				      table.hw_ver, table.brd_ver); +	IWL_ERR(mvm, "0x%08X | %-28s\n", table.error_id, +		desc_lookup(table.error_id)); +	IWL_ERR(mvm, "0x%08X | uPc\n", table.pc); +	IWL_ERR(mvm, "0x%08X | branchlink1\n", table.blink1); +	IWL_ERR(mvm, "0x%08X | branchlink2\n", table.blink2); +	IWL_ERR(mvm, "0x%08X | interruptlink1\n", table.ilink1); +	IWL_ERR(mvm, "0x%08X | interruptlink2\n", table.ilink2); +	IWL_ERR(mvm, "0x%08X | data1\n", table.data1); +	IWL_ERR(mvm, "0x%08X | data2\n", table.data2); +	IWL_ERR(mvm, "0x%08X | data3\n", table.data3); +	IWL_ERR(mvm, "0x%08X | beacon time\n", table.bcon_time); +	IWL_ERR(mvm, "0x%08X | tsf low\n", table.tsf_low); +	IWL_ERR(mvm, "0x%08X | tsf hi\n", table.tsf_hi); +	IWL_ERR(mvm, "0x%08X | time gp1\n", table.gp1); +	IWL_ERR(mvm, "0x%08X | time gp2\n", table.gp2); +	IWL_ERR(mvm, "0x%08X | time gp3\n", table.gp3); +	IWL_ERR(mvm, "0x%08X | uCode version\n", table.ucode_ver); +	IWL_ERR(mvm, "0x%08X | hw version\n", table.hw_ver); +	IWL_ERR(mvm, "0x%08X | board version\n", table.brd_ver); +	IWL_ERR(mvm, "0x%08X | hcmd\n", table.hcmd); +	IWL_ERR(mvm, "0x%08X | isr0\n", table.isr0); +	IWL_ERR(mvm, "0x%08X | isr1\n", table.isr1); +	IWL_ERR(mvm, "0x%08X | isr2\n", table.isr2); +	IWL_ERR(mvm, "0x%08X | isr3\n", table.isr3); +	IWL_ERR(mvm, "0x%08X | isr4\n", table.isr4); +	IWL_ERR(mvm, "0x%08X | isr_pref\n", table.isr_pref); +	IWL_ERR(mvm, "0x%08X | wait_event\n", table.wait_event); +	IWL_ERR(mvm, "0x%08X | l2p_control\n", table.l2p_control); +	IWL_ERR(mvm, "0x%08X | l2p_duration\n", table.l2p_duration); +	IWL_ERR(mvm, "0x%08X | l2p_mhvalid\n", table.l2p_mhvalid); +	IWL_ERR(mvm, "0x%08X | l2p_addr_match\n", table.l2p_addr_match); +	IWL_ERR(mvm, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); +	IWL_ERR(mvm, "0x%08X | timestamp\n", table.u_timestamp); +	IWL_ERR(mvm, "0x%08X | flow_handler\n", table.flow_handler); +} + +/** + * iwl_mvm_send_lq_cmd() - Send link quality command + * @init: This command is sent as part of station initialization right + *        after station has been added. + * + * The link quality command is sent as the last step of station creation. + * This is the special case in which init is set and we call a callback in + * this case to clear the state indicating that station creation is in + * progress. + */ +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, +			u8 flags, bool init) +{ +	struct iwl_host_cmd cmd = { +		.id = LQ_CMD, +		.len = { sizeof(struct iwl_lq_cmd), }, +		.flags = flags, +		.data = { lq, }, +	}; + +	if (WARN_ON(lq->sta_id == IWL_INVALID_STATION)) +		return -EINVAL; + +	if (WARN_ON(init && (cmd.flags & CMD_ASYNC))) +		return -EINVAL; + +	return iwl_mvm_send_cmd(mvm, &cmd); +}  |