diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/d3.c')
| -rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/d3.c | 642 | 
1 files changed, 462 insertions, 180 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/d3.c b/drivers/net/wireless/iwlwifi/mvm/d3.c index 417639f77b0..645b3cfc29a 100644 --- a/drivers/net/wireless/iwlwifi/mvm/d3.c +++ b/drivers/net/wireless/iwlwifi/mvm/d3.c @@ -5,7 +5,7 @@   *   * GPL LICENSE SUMMARY   * - * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * Copyright(c) 2012 - 2014 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 @@ -30,7 +30,7 @@   *   * BSD LICENSE   * - * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.   * All rights reserved.   *   * Redistribution and use in source and binary forms, with or without @@ -67,6 +67,7 @@  #include <net/cfg80211.h>  #include <net/ipv6.h>  #include <net/tcp.h> +#include <net/addrconf.h>  #include "iwl-modparams.h"  #include "fw-api.h"  #include "mvm.h" @@ -192,8 +193,7 @@ static void iwl_mvm_wowlan_program_keys(struct ieee80211_hw *hw,  			wkc.wep_key.key_offset = data->wep_key_idx;  		} -		ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, CMD_SYNC, -					   sizeof(wkc), &wkc); +		ret = iwl_mvm_send_cmd_pdu(mvm, WEP_KEY, 0, sizeof(wkc), &wkc);  		data->error = ret != 0;  		mvm->ptk_ivlen = key->iv_len; @@ -340,7 +340,6 @@ static int iwl_mvm_send_patterns(struct iwl_mvm *mvm,  	struct iwl_host_cmd cmd = {  		.id = WOWLAN_PATTERNS,  		.dataflags[0] = IWL_HCMD_DFL_NOCOPY, -		.flags = CMD_SYNC,  	};  	int i, err; @@ -375,73 +374,6 @@ static int iwl_mvm_send_patterns(struct iwl_mvm *mvm,  	return err;  } -static int iwl_mvm_send_proto_offload(struct iwl_mvm *mvm, -				      struct ieee80211_vif *vif) -{ -	union { -		struct iwl_proto_offload_cmd_v1 v1; -		struct iwl_proto_offload_cmd_v2 v2; -	} cmd = {}; -	struct iwl_proto_offload_cmd_common *common; -	u32 enabled = 0, size; -#if IS_ENABLED(CONFIG_IPV6) -	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); -	int i; - -	if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { -		if (mvmvif->num_target_ipv6_addrs) { -			enabled |= IWL_D3_PROTO_OFFLOAD_NS; -			memcpy(cmd.v2.ndp_mac_addr, vif->addr, ETH_ALEN); -		} - -		BUILD_BUG_ON(sizeof(cmd.v2.target_ipv6_addr[0]) != -			     sizeof(mvmvif->target_ipv6_addrs[0])); - -		for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, -				    IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V2); i++) -			memcpy(cmd.v2.target_ipv6_addr[i], -			       &mvmvif->target_ipv6_addrs[i], -			       sizeof(cmd.v2.target_ipv6_addr[i])); -	} else { -		if (mvmvif->num_target_ipv6_addrs) { -			enabled |= IWL_D3_PROTO_OFFLOAD_NS; -			memcpy(cmd.v1.ndp_mac_addr, vif->addr, ETH_ALEN); -		} - -		BUILD_BUG_ON(sizeof(cmd.v1.target_ipv6_addr[0]) != -			     sizeof(mvmvif->target_ipv6_addrs[0])); - -		for (i = 0; i < min(mvmvif->num_target_ipv6_addrs, -				    IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V1); i++) -			memcpy(cmd.v1.target_ipv6_addr[i], -			       &mvmvif->target_ipv6_addrs[i], -			       sizeof(cmd.v1.target_ipv6_addr[i])); -	} -#endif - -	if (mvm->fw->ucode_capa.flags & IWL_UCODE_TLV_FLAGS_D3_6_IPV6_ADDRS) { -		common = &cmd.v2.common; -		size = sizeof(cmd.v2); -	} else { -		common = &cmd.v1.common; -		size = sizeof(cmd.v1); -	} - -	if (vif->bss_conf.arp_addr_cnt) { -		enabled |= IWL_D3_PROTO_OFFLOAD_ARP; -		common->host_ipv4_addr = vif->bss_conf.arp_addr_list[0]; -		memcpy(common->arp_mac_addr, vif->addr, ETH_ALEN); -	} - -	if (!enabled) -		return 0; - -	common->enabled = cpu_to_le32(enabled); - -	return iwl_mvm_send_cmd_pdu(mvm, PROT_OFFLOAD_CONFIG_CMD, CMD_SYNC, -				    size, &cmd); -} -  enum iwl_mvm_tcp_packet_type {  	MVM_TCP_TX_SYN,  	MVM_TCP_RX_SYNACK, @@ -584,7 +516,6 @@ static int iwl_mvm_send_remote_wake_cfg(struct iwl_mvm *mvm,  		.id = REMOTE_WAKE_CONFIG_CMD,  		.len = { sizeof(*cfg), },  		.dataflags = { IWL_HCMD_DFL_NOCOPY, }, -		.flags = CMD_SYNC,  	};  	int ret; @@ -732,10 +663,8 @@ static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	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; @@ -771,7 +700,7 @@ static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  		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); +	ret = iwl_mvm_mac_ctxt_changed(mvm, vif, false);  	if (ret)  		return ret; @@ -779,13 +708,13 @@ static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	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); +	quota_cmd.quotas[0].quota = cpu_to_le32(IWL_MVM_MAX_QUOTA); +	quota_cmd.quotas[0].max_duration = cpu_to_le32(IWL_MVM_MAX_QUOTA);  	for (i = 1; i < MAX_BINDINGS; i++)  		quota_cmd.quotas[i].id_and_color = cpu_to_le32(FW_CTXT_INVALID); -	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, CMD_SYNC, +	ret = iwl_mvm_send_cmd_pdu(mvm, TIME_QUOTA_CMD, 0,  				   sizeof(quota_cmd), "a_cmd);  	if (ret)  		IWL_ERR(mvm, "Failed to send quota: %d\n", ret); @@ -793,6 +722,79 @@ static int iwl_mvm_d3_reprogram(struct iwl_mvm *mvm, struct ieee80211_vif *vif,  	return 0;  } +static int iwl_mvm_get_last_nonqos_seq(struct iwl_mvm *mvm, +				       struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_nonqos_seq_query_cmd query_cmd = { +		.get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_GET), +		.mac_id_n_color = +			cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							mvmvif->color)), +	}; +	struct iwl_host_cmd cmd = { +		.id = NON_QOS_TX_COUNTER_CMD, +		.flags = CMD_WANT_SKB, +	}; +	int err; +	u32 size; + +	cmd.data[0] = &query_cmd; +	cmd.len[0] = sizeof(query_cmd); + +	err = iwl_mvm_send_cmd(mvm, &cmd); +	if (err) +		return err; + +	size = iwl_rx_packet_payload_len(cmd.resp_pkt); +	if (size < sizeof(__le16)) { +		err = -EINVAL; +	} else { +		err = le16_to_cpup((__le16 *)cmd.resp_pkt->data); +		/* firmware returns next, not last-used seqno */ +		err = (u16) (err - 0x10); +	} + +	iwl_free_resp(&cmd); +	return err; +} + +void iwl_mvm_set_last_nonqos_seq(struct iwl_mvm *mvm, struct ieee80211_vif *vif) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_nonqos_seq_query_cmd query_cmd = { +		.get_set_flag = cpu_to_le32(IWL_NONQOS_SEQ_SET), +		.mac_id_n_color = +			cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id, +							mvmvif->color)), +		.value = cpu_to_le16(mvmvif->seqno), +	}; + +	/* return if called during restart, not resume from D3 */ +	if (!mvmvif->seqno_valid) +		return; + +	mvmvif->seqno_valid = false; + +	if (iwl_mvm_send_cmd_pdu(mvm, NON_QOS_TX_COUNTER_CMD, 0, +				 sizeof(query_cmd), &query_cmd)) +		IWL_ERR(mvm, "failed to set non-QoS seqno\n"); +} + +static int +iwl_mvm_send_wowlan_config_cmd(struct iwl_mvm *mvm, +			       const struct iwl_wowlan_config_cmd_v3 *cmd) +{ +	/* start only with the v2 part of the command */ +	u16 cmd_len = sizeof(cmd->common); + +	if (mvm->fw->ucode_capa.api[0] & IWL_UCODE_TLV_API_WOWLAN_CONFIG_TID) +		cmd_len = sizeof(*cmd); + +	return iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, 0, +				    cmd_len, cmd); +} +  static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  			     struct cfg80211_wowlan *wowlan,  			     bool test) @@ -805,7 +807,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	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_config_cmd_v3 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_data = { @@ -818,7 +820,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	};  	struct iwl_host_cmd d3_cfg_cmd = {  		.id = D3_CONFIG_CMD, -		.flags = CMD_SYNC | CMD_WANT_SKB, +		.flags = CMD_WANT_SKB,  		.data[0] = &d3_cfg_cmd_data,  		.len[0] = sizeof(d3_cfg_cmd_data),  	}; @@ -827,10 +829,8 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		.tkip = &tkip_cmd,  		.use_tkip = false,  	}; -	int ret, i; +	int ret;  	int len __maybe_unused; -	u16 seq; -	u8 old_aux_sta_id, old_ap_sta_id = IWL_MVM_STATION_COUNT;  	if (!wowlan) {  		/* @@ -847,8 +847,6 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	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, @@ -872,60 +870,41 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	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 */ +	/* TODO: wowlan_config_cmd.common.wowlan_ba_teardown_tids */ -	wowlan_config_cmd.is_11n_connection = ap_sta->ht_cap.ht_supported; +	wowlan_config_cmd.common.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); +	/* Query the last used seqno and set it */ +	ret = iwl_mvm_get_last_nonqos_seq(mvm, vif); +	if (ret < 0) +		goto out_noreset; +	wowlan_config_cmd.common.non_qos_seq = cpu_to_le16(ret); -	/* -	 * 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); -	} +	iwl_mvm_set_wowlan_qos_seq(mvm_ap_sta, &wowlan_config_cmd.common);  	if (wowlan->disconnect) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS |  				    IWL_WOWLAN_WAKEUP_LINK_CHANGE);  	if (wowlan->magic_pkt) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET);  	if (wowlan->gtk_rekey_failure) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL);  	if (wowlan->eap_identity_req) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ);  	if (wowlan->four_way_handshake) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE);  	if (wowlan->n_patterns) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH);  	if (wowlan->rfkill_release) -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT);  	if (wowlan->tcp) { @@ -933,7 +912,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		 * Set the "link change" (really "link lost") flag as well  		 * since that implies losing the TCP connection.  		 */ -		wowlan_config_cmd.wakeup_filter |= +		wowlan_config_cmd.common.wakeup_filter |=  			cpu_to_le32(IWL_WOWLAN_WAKEUP_REMOTE_LINK_LOSS |  				    IWL_WOWLAN_WAKEUP_REMOTE_SIGNATURE_TABLE |  				    IWL_WOWLAN_WAKEUP_REMOTE_WAKEUP_PACKET | @@ -964,16 +943,6 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	mvm->ptk_ivlen = 0;  	mvm->ptk_icvlen = 0; -	/* -	 * 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; @@ -1001,7 +970,6 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		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), @@ -1015,7 +983,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		if (key_data.use_tkip) {  			ret = iwl_mvm_send_cmd_pdu(mvm,  						   WOWLAN_TKIP_PARAM, -						   CMD_SYNC, sizeof(tkip_cmd), +						   0, sizeof(tkip_cmd),  						   &tkip_cmd);  			if (ret)  				goto out; @@ -1032,8 +1000,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  			kek_kck_cmd.replay_ctr = mvmvif->rekey_data.replay_ctr;  			ret = iwl_mvm_send_cmd_pdu(mvm, -						   WOWLAN_KEK_KCK_MATERIAL, -						   CMD_SYNC, +						   WOWLAN_KEK_KCK_MATERIAL, 0,  						   sizeof(kek_kck_cmd),  						   &kek_kck_cmd);  			if (ret) @@ -1041,9 +1008,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  		}  	} -	ret = iwl_mvm_send_cmd_pdu(mvm, WOWLAN_CONFIGURATION, -				   CMD_SYNC, sizeof(wowlan_config_cmd), -				   &wowlan_config_cmd); +	ret = iwl_mvm_send_wowlan_config_cmd(mvm, &wowlan_config_cmd);  	if (ret)  		goto out; @@ -1051,7 +1016,7 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	if (ret)  		goto out; -	ret = iwl_mvm_send_proto_offload(mvm, vif); +	ret = iwl_mvm_send_proto_offload(mvm, vif, false, 0);  	if (ret)  		goto out; @@ -1059,7 +1024,11 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	if (ret)  		goto out; -	ret = iwl_mvm_power_update_mode(mvm, vif); +	ret = iwl_mvm_power_update_device(mvm); +	if (ret) +		goto out; + +	ret = iwl_mvm_power_update_mac(mvm);  	if (ret)  		goto out; @@ -1074,15 +1043,10 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	if (ret)  		goto out;  #ifdef CONFIG_IWLWIFI_DEBUGFS -	len = le32_to_cpu(d3_cfg_cmd.resp_pkt->len_n_flags) & -		FH_RSCSR_FRAME_SIZE_MSK; -	if (len >= sizeof(u32) * 2) { +	len = iwl_rx_packet_payload_len(d3_cfg_cmd.resp_pkt); +	if (len >= sizeof(u32)) {  		mvm->d3_test_pme_ptr =  			le32_to_cpup((__le32 *)d3_cfg_cmd.resp_pkt->data); -	} else if (test) { -		/* in test mode we require the pointer */ -		ret = -EIO; -		goto out;  	}  #endif  	iwl_free_resp(&d3_cfg_cmd); @@ -1091,13 +1055,10 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  	iwl_trans_d3_suspend(mvm->trans, test);   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); + out_noreset: +	kfree(key_data.rsc_tsc);  	mutex_unlock(&mvm->mutex); @@ -1106,19 +1067,38 @@ static int __iwl_mvm_suspend(struct ieee80211_hw *hw,  int iwl_mvm_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)  { +	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); + +	if (iwl_mvm_is_d0i3_supported(mvm)) { +		mutex_lock(&mvm->d0i3_suspend_mutex); +		__set_bit(D0I3_DEFER_WAKEUP, &mvm->d0i3_suspend_flags); +		mutex_unlock(&mvm->d0i3_suspend_mutex); +		return 0; +	} +  	return __iwl_mvm_suspend(hw, wowlan, false);  } +/* converted data from the different status responses */ +struct iwl_wowlan_status_data { +	u16 pattern_number; +	u16 qos_seq_ctr[8]; +	u32 wakeup_reasons; +	u32 wake_packet_length; +	u32 wake_packet_bufsize; +	const u8 *wake_packet; +}; +  static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm,  					  struct ieee80211_vif *vif, -					  struct iwl_wowlan_status *status) +					  struct iwl_wowlan_status_data *status)  {  	struct sk_buff *pkt = NULL;  	struct cfg80211_wowlan_wakeup wakeup = {  		.pattern_idx = -1,  	};  	struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup; -	u32 reasons = le32_to_cpu(status->wakeup_reasons); +	u32 reasons = status->wakeup_reasons;  	if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) {  		wakeup_report = NULL; @@ -1130,7 +1110,7 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm,  	if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN)  		wakeup.pattern_idx = -			le16_to_cpu(status->pattern_number); +			status->pattern_number;  	if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON |  		       IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH)) @@ -1158,8 +1138,8 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm,  		wakeup.tcp_match = true;  	if (status->wake_packet_bufsize) { -		int pktsize = le32_to_cpu(status->wake_packet_bufsize); -		int pktlen = le32_to_cpu(status->wake_packet_length); +		int pktsize = status->wake_packet_bufsize; +		int pktlen = status->wake_packet_length;  		const u8 *pktdata = status->wake_packet;  		struct ieee80211_hdr *hdr = (void *)pktdata;  		int truncated = pktlen - pktsize; @@ -1239,8 +1219,236 @@ static void iwl_mvm_report_wakeup_reasons(struct iwl_mvm *mvm,  	kfree_skb(pkt);  } +static void iwl_mvm_aes_sc_to_seq(struct aes_sc *sc, +				  struct ieee80211_key_seq *seq) +{ +	u64 pn; + +	pn = le64_to_cpu(sc->pn); +	seq->ccmp.pn[0] = pn >> 40; +	seq->ccmp.pn[1] = pn >> 32; +	seq->ccmp.pn[2] = pn >> 24; +	seq->ccmp.pn[3] = pn >> 16; +	seq->ccmp.pn[4] = pn >> 8; +	seq->ccmp.pn[5] = pn; +} + +static void iwl_mvm_tkip_sc_to_seq(struct tkip_sc *sc, +				   struct ieee80211_key_seq *seq) +{ +	seq->tkip.iv32 = le32_to_cpu(sc->iv32); +	seq->tkip.iv16 = le16_to_cpu(sc->iv16); +} + +static void iwl_mvm_set_aes_rx_seq(struct aes_sc *scs, +				   struct ieee80211_key_conf *key) +{ +	int tid; + +	BUILD_BUG_ON(IWL_NUM_RSC != IEEE80211_NUM_TIDS); + +	for (tid = 0; tid < IWL_NUM_RSC; tid++) { +		struct ieee80211_key_seq seq = {}; + +		iwl_mvm_aes_sc_to_seq(&scs[tid], &seq); +		ieee80211_set_key_rx_seq(key, tid, &seq); +	} +} + +static void iwl_mvm_set_tkip_rx_seq(struct tkip_sc *scs, +				    struct ieee80211_key_conf *key) +{ +	int tid; + +	BUILD_BUG_ON(IWL_NUM_RSC != IEEE80211_NUM_TIDS); + +	for (tid = 0; tid < IWL_NUM_RSC; tid++) { +		struct ieee80211_key_seq seq = {}; + +		iwl_mvm_tkip_sc_to_seq(&scs[tid], &seq); +		ieee80211_set_key_rx_seq(key, tid, &seq); +	} +} + +static void iwl_mvm_set_key_rx_seq(struct ieee80211_key_conf *key, +				   struct iwl_wowlan_status *status) +{ +	union iwl_all_tsc_rsc *rsc = &status->gtk.rsc.all_tsc_rsc; + +	switch (key->cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +		iwl_mvm_set_aes_rx_seq(rsc->aes.multicast_rsc, key); +		break; +	case WLAN_CIPHER_SUITE_TKIP: +		iwl_mvm_set_tkip_rx_seq(rsc->tkip.multicast_rsc, key); +		break; +	default: +		WARN_ON(1); +	} +} + +struct iwl_mvm_d3_gtk_iter_data { +	struct iwl_wowlan_status *status; +	void *last_gtk; +	u32 cipher; +	bool find_phase, unhandled_cipher; +	int num_keys; +}; + +static void iwl_mvm_d3_update_gtks(struct ieee80211_hw *hw, +				   struct ieee80211_vif *vif, +				   struct ieee80211_sta *sta, +				   struct ieee80211_key_conf *key, +				   void *_data) +{ +	struct iwl_mvm_d3_gtk_iter_data *data = _data; + +	if (data->unhandled_cipher) +		return; + +	switch (key->cipher) { +	case WLAN_CIPHER_SUITE_WEP40: +	case WLAN_CIPHER_SUITE_WEP104: +		/* ignore WEP completely, nothing to do */ +		return; +	case WLAN_CIPHER_SUITE_CCMP: +	case WLAN_CIPHER_SUITE_TKIP: +		/* we support these */ +		break; +	default: +		/* everything else (even CMAC for MFP) - disconnect from AP */ +		data->unhandled_cipher = true; +		return; +	} + +	data->num_keys++; + +	/* +	 * pairwise key - update sequence counters only; +	 * note that this assumes no TDLS sessions are active +	 */ +	if (sta) { +		struct ieee80211_key_seq seq = {}; +		union iwl_all_tsc_rsc *sc = &data->status->gtk.rsc.all_tsc_rsc; + +		if (data->find_phase) +			return; + +		switch (key->cipher) { +		case WLAN_CIPHER_SUITE_CCMP: +			iwl_mvm_aes_sc_to_seq(&sc->aes.tsc, &seq); +			iwl_mvm_set_aes_rx_seq(sc->aes.unicast_rsc, key); +			break; +		case WLAN_CIPHER_SUITE_TKIP: +			iwl_mvm_tkip_sc_to_seq(&sc->tkip.tsc, &seq); +			iwl_mvm_set_tkip_rx_seq(sc->tkip.unicast_rsc, key); +			break; +		} +		ieee80211_set_key_tx_seq(key, &seq); + +		/* that's it for this key */ +		return; +	} + +	if (data->find_phase) { +		data->last_gtk = key; +		data->cipher = key->cipher; +		return; +	} + +	if (data->status->num_of_gtk_rekeys) +		ieee80211_remove_key(key); +	else if (data->last_gtk == key) +		iwl_mvm_set_key_rx_seq(key, data->status); +} + +static bool iwl_mvm_setup_connection_keep(struct iwl_mvm *mvm, +					  struct ieee80211_vif *vif, +					  struct iwl_wowlan_status *status) +{ +	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); +	struct iwl_mvm_d3_gtk_iter_data gtkdata = { +		.status = status, +	}; +	u32 disconnection_reasons = +		IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | +		IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH; + +	if (!status || !vif->bss_conf.bssid) +		return false; + +	if (le32_to_cpu(status->wakeup_reasons) & disconnection_reasons) +		return false; + +	/* find last GTK that we used initially, if any */ +	gtkdata.find_phase = true; +	ieee80211_iter_keys(mvm->hw, vif, +			    iwl_mvm_d3_update_gtks, >kdata); +	/* not trying to keep connections with MFP/unhandled ciphers */ +	if (gtkdata.unhandled_cipher) +		return false; +	if (!gtkdata.num_keys) +		goto out; +	if (!gtkdata.last_gtk) +		return false; + +	/* +	 * invalidate all other GTKs that might still exist and update +	 * the one that we used +	 */ +	gtkdata.find_phase = false; +	ieee80211_iter_keys(mvm->hw, vif, +			    iwl_mvm_d3_update_gtks, >kdata); + +	if (status->num_of_gtk_rekeys) { +		struct ieee80211_key_conf *key; +		struct { +			struct ieee80211_key_conf conf; +			u8 key[32]; +		} conf = { +			.conf.cipher = gtkdata.cipher, +			.conf.keyidx = status->gtk.key_index, +		}; + +		switch (gtkdata.cipher) { +		case WLAN_CIPHER_SUITE_CCMP: +			conf.conf.keylen = WLAN_KEY_LEN_CCMP; +			memcpy(conf.conf.key, status->gtk.decrypt_key, +			       WLAN_KEY_LEN_CCMP); +			break; +		case WLAN_CIPHER_SUITE_TKIP: +			conf.conf.keylen = WLAN_KEY_LEN_TKIP; +			memcpy(conf.conf.key, status->gtk.decrypt_key, 16); +			/* leave TX MIC key zeroed, we don't use it anyway */ +			memcpy(conf.conf.key + +			       NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, +			       status->gtk.tkip_mic_key, 8); +			break; +		} + +		key = ieee80211_gtk_rekey_add(vif, &conf.conf); +		if (IS_ERR(key)) +			return false; +		iwl_mvm_set_key_rx_seq(key, status); +	} + +	if (status->num_of_gtk_rekeys) { +		__be64 replay_ctr = +			cpu_to_be64(le64_to_cpu(status->replay_ctr)); +		ieee80211_gtk_rekey_notify(vif, vif->bss_conf.bssid, +					   (void *)&replay_ctr, GFP_KERNEL); +	} + +out: +	mvmvif->seqno_valid = true; +	/* +0x10 because the set API expects next-to-use, not last-used */ +	mvmvif->seqno = le16_to_cpu(status->non_qos_seq_ctr) + 0x10; + +	return true; +} +  /* releases the MVM mutex */ -static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm, +static bool iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  					 struct ieee80211_vif *vif)  {  	u32 base = mvm->error_event_table; @@ -1251,10 +1459,14 @@ static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  	} err_info;  	struct iwl_host_cmd cmd = {  		.id = WOWLAN_GET_STATUSES, -		.flags = CMD_SYNC | CMD_WANT_SKB, +		.flags = CMD_WANT_SKB,  	}; -	struct iwl_wowlan_status *status; -	int ret, len; +	struct iwl_wowlan_status_data status; +	struct iwl_wowlan_status *fw_status; +	int ret, len, status_size, i; +	bool keep; +	struct ieee80211_sta *ap_sta; +	struct iwl_mvm_sta *mvm_ap_sta;  	iwl_trans_read_mem_bytes(mvm->trans, base,  				 &err_info, sizeof(err_info)); @@ -1273,7 +1485,7 @@ static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  	}  	/* only for tracing for now */ -	ret = iwl_mvm_send_cmd_pdu(mvm, OFFLOADS_QUERY_CMD, CMD_SYNC, 0, NULL); +	ret = iwl_mvm_send_cmd_pdu(mvm, OFFLOADS_QUERY_CMD, 0, 0, NULL);  	if (ret)  		IWL_ERR(mvm, "failed to query offload statistics (%d)\n", ret); @@ -1287,32 +1499,62 @@ static void iwl_mvm_query_wakeup_reasons(struct iwl_mvm *mvm,  	if (!cmd.resp_pkt)  		goto out_unlock; -	len = le32_to_cpu(cmd.resp_pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; -	if (len - sizeof(struct iwl_cmd_header) < sizeof(*status)) { +	status_size = sizeof(*fw_status); + +	len = iwl_rx_packet_payload_len(cmd.resp_pkt); +	if (len < status_size) {  		IWL_ERR(mvm, "Invalid WoWLAN status response!\n");  		goto out_free_resp;  	} -	status = (void *)cmd.resp_pkt->data; +	fw_status = (void *)cmd.resp_pkt->data; -	if (len - sizeof(struct iwl_cmd_header) != -	    sizeof(*status) + -	    ALIGN(le32_to_cpu(status->wake_packet_bufsize), 4)) { +	status.pattern_number = le16_to_cpu(fw_status->pattern_number); +	for (i = 0; i < 8; i++) +		status.qos_seq_ctr[i] = +			le16_to_cpu(fw_status->qos_seq_ctr[i]); +	status.wakeup_reasons = le32_to_cpu(fw_status->wakeup_reasons); +	status.wake_packet_length = +		le32_to_cpu(fw_status->wake_packet_length); +	status.wake_packet_bufsize = +		le32_to_cpu(fw_status->wake_packet_bufsize); +	status.wake_packet = fw_status->wake_packet; + +	if (len != status_size + ALIGN(status.wake_packet_bufsize, 4)) {  		IWL_ERR(mvm, "Invalid WoWLAN status response!\n");  		goto out_free_resp;  	} +	/* still at hard-coded place 0 for D3 image */ +	ap_sta = rcu_dereference_protected( +			mvm->fw_id_to_mac_id[0], +			lockdep_is_held(&mvm->mutex)); +	if (IS_ERR_OR_NULL(ap_sta)) +		goto out_free_resp; + +	mvm_ap_sta = (struct iwl_mvm_sta *)ap_sta->drv_priv; +	for (i = 0; i < IWL_MAX_TID_COUNT; i++) { +		u16 seq = status.qos_seq_ctr[i]; +		/* firmware stores last-used value, we store next value */ +		seq += 0x10; +		mvm_ap_sta->tid_data[i].seq_number = seq; +	} +  	/* now we have all the data we need, unlock to avoid mac80211 issues */  	mutex_unlock(&mvm->mutex); -	iwl_mvm_report_wakeup_reasons(mvm, vif, status); +	iwl_mvm_report_wakeup_reasons(mvm, vif, &status); + +	keep = iwl_mvm_setup_connection_keep(mvm, vif, fw_status); +  	iwl_free_resp(&cmd); -	return; +	return keep;   out_free_resp:  	iwl_free_resp(&cmd);   out_unlock:  	mutex_unlock(&mvm->mutex); +	return false;  }  static void iwl_mvm_read_d3_sram(struct iwl_mvm *mvm) @@ -1335,6 +1577,17 @@ static void iwl_mvm_read_d3_sram(struct iwl_mvm *mvm)  #endif  } +static void iwl_mvm_d3_disconnect_iter(void *data, u8 *mac, +				       struct ieee80211_vif *vif) +{ +	/* skip the one we keep connection on */ +	if (data == vif) +		return; + +	if (vif->type == NL80211_IFTYPE_STATION) +		ieee80211_resume_disconnect(vif); +} +  static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)  {  	struct iwl_d3_iter_data resume_iter_data = { @@ -1343,6 +1596,7 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)  	struct ieee80211_vif *vif = NULL;  	int ret;  	enum iwl_d3_status d3_status; +	bool keep = false;  	mutex_lock(&mvm->mutex); @@ -1368,7 +1622,11 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)  	/* query SRAM first in case we want event logging */  	iwl_mvm_read_d3_sram(mvm); -	iwl_mvm_query_wakeup_reasons(mvm, vif); +	keep = iwl_mvm_query_wakeup_reasons(mvm, vif); +#ifdef CONFIG_IWLWIFI_DEBUGFS +	if (keep) +		mvm->keep_vif = vif; +#endif  	/* has unlocked the mutex, so skip that */  	goto out; @@ -1376,8 +1634,10 @@ static int __iwl_mvm_resume(struct iwl_mvm *mvm, bool test)  	mutex_unlock(&mvm->mutex);   out: -	if (!test && vif) -		ieee80211_resume_disconnect(vif); +	if (!test) +		ieee80211_iterate_active_interfaces_rtnl(mvm->hw, +			IEEE80211_IFACE_ITER_NORMAL, +			iwl_mvm_d3_disconnect_iter, keep ? vif : NULL);  	/* return 1 to reconfigure the device */  	set_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status); @@ -1388,6 +1648,19 @@ int iwl_mvm_resume(struct ieee80211_hw *hw)  {  	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw); +	if (iwl_mvm_is_d0i3_supported(mvm)) { +		bool exit_now; + +		mutex_lock(&mvm->d0i3_suspend_mutex); +		__clear_bit(D0I3_DEFER_WAKEUP, &mvm->d0i3_suspend_flags); +		exit_now = __test_and_clear_bit(D0I3_PENDING_WAKEUP, +						&mvm->d0i3_suspend_flags); +		mutex_unlock(&mvm->d0i3_suspend_mutex); +		if (exit_now) +			_iwl_mvm_exit_d0i3(mvm); +		return 0; +	} +  	return __iwl_mvm_resume(mvm, false);  } @@ -1423,6 +1696,7 @@ static int iwl_mvm_d3_test_open(struct inode *inode, struct file *file)  		return err;  	}  	mvm->d3_test_active = true; +	mvm->keep_vif = NULL;  	return 0;  } @@ -1433,10 +1707,14 @@ static ssize_t iwl_mvm_d3_test_read(struct file *file, char __user *user_buf,  	u32 pme_asserted;  	while (true) { -		pme_asserted = iwl_trans_read_mem32(mvm->trans, -						    mvm->d3_test_pme_ptr); -		if (pme_asserted) -			break; +		/* read pme_ptr if available */ +		if (mvm->d3_test_pme_ptr) { +			pme_asserted = iwl_trans_read_mem32(mvm->trans, +						mvm->d3_test_pme_ptr); +			if (pme_asserted) +				break; +		} +  		if (msleep_interruptible(100))  			break;  	} @@ -1447,6 +1725,10 @@ static ssize_t iwl_mvm_d3_test_read(struct file *file, char __user *user_buf,  static void iwl_mvm_d3_test_disconn_work_iter(void *_data, u8 *mac,  					      struct ieee80211_vif *vif)  { +	/* skip the one we keep connection on */ +	if (_data == vif) +		return; +  	if (vif->type == NL80211_IFTYPE_STATION)  		ieee80211_connection_loss(vif);  } @@ -1473,7 +1755,7 @@ static int iwl_mvm_d3_test_release(struct inode *inode, struct file *file)  	ieee80211_iterate_active_interfaces_atomic(  		mvm->hw, IEEE80211_IFACE_ITER_NORMAL, -		iwl_mvm_d3_test_disconn_work_iter, NULL); +		iwl_mvm_d3_test_disconn_work_iter, mvm->keep_vif);  	ieee80211_wake_queues(mvm->hw);  | 
