diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/d3.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/d3.c | 1906 | 
1 files changed, 1906 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/d3.c b/sys/contrib/dev/iwlwifi/mld/d3.c new file mode 100644 index 000000000000..ed0a0f76f1c5 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/d3.c @@ -0,0 +1,1906 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2024-2025 Intel Corporation + */ +#include "mld.h" + +#include "d3.h" +#include "power.h" +#include "hcmd.h" +#include "iface.h" +#include "mcc.h" +#include "sta.h" +#include "mlo.h" + +#include "fw/api/d3.h" +#include "fw/api/offload.h" +#include "fw/api/sta.h" +#include "fw/dbg.h" + +#include <net/ipv6.h> +#include <net/addrconf.h> +#include <linux/bitops.h> + +/** + * enum iwl_mld_d3_notif - d3 notifications + * @IWL_D3_NOTIF_WOWLAN_INFO: WOWLAN_INFO_NOTIF is expected/was received + * @IWL_D3_NOTIF_WOWLAN_WAKE_PKT: WOWLAN_WAKE_PKT_NOTIF is expected/was received + * @IWL_D3_NOTIF_PROT_OFFLOAD: PROT_OFFLOAD_NOTIF is expected/was received + * @IWL_D3_ND_MATCH_INFO: OFFLOAD_MATCH_INFO_NOTIF is expected/was received + * @IWL_D3_NOTIF_D3_END_NOTIF: D3_END_NOTIF is expected/was received + */ +enum iwl_mld_d3_notif { +	IWL_D3_NOTIF_WOWLAN_INFO =	BIT(0), +	IWL_D3_NOTIF_WOWLAN_WAKE_PKT =	BIT(1), +	IWL_D3_NOTIF_PROT_OFFLOAD =	BIT(2), +	IWL_D3_ND_MATCH_INFO      =     BIT(3), +	IWL_D3_NOTIF_D3_END_NOTIF =	BIT(4) +}; + +struct iwl_mld_resume_key_iter_data { +	struct iwl_mld *mld; +	struct iwl_mld_wowlan_status *wowlan_status; +	u32 num_keys, gtk_cipher, igtk_cipher, bigtk_cipher; +	bool unhandled_cipher; +}; + +struct iwl_mld_suspend_key_iter_data { +	struct iwl_wowlan_rsc_tsc_params_cmd *rsc; +	bool have_rsc; +	int gtks; +	int found_gtk_idx[4]; +	__le32 gtk_cipher; +	__le32 igtk_cipher; +	__le32 bigtk_cipher; +}; + +struct iwl_mld_mcast_key_data { +	u8 key[WOWLAN_KEY_MAX_SIZE]; +	u8 len; +	u8 flags; +	u8 id; +	union { +		struct { +			struct ieee80211_key_seq aes_seq[IWL_MAX_TID_COUNT]; +			struct ieee80211_key_seq tkip_seq[IWL_MAX_TID_COUNT]; +		} gtk; +		struct { +			struct ieee80211_key_seq cmac_gmac_seq; +		} igtk_bigtk; +	}; + +}; + +/** + * struct iwl_mld_wowlan_status - contains wowlan status data from + * all wowlan notifications + * @wakeup_reasons: wakeup reasons, see &enum iwl_wowlan_wakeup_reason + * @replay_ctr: GTK rekey replay counter + * @pattern_number: number of the matched patterns on packets + * @last_qos_seq: QoS sequence counter of offloaded tid + * @num_of_gtk_rekeys: number of GTK rekeys during D3 + * @tid_offloaded_tx: tid used by the firmware to transmit data packets + *	while in wowlan + * @wake_packet: wakeup packet received + * @wake_packet_length: wake packet length + * @wake_packet_bufsize: wake packet bufsize + * @gtk: data of the last two used gtk's by the FW upon resume + * @igtk: data of the last used igtk by the FW upon resume + * @bigtk: data of the last two used gtk's by the FW upon resume + * @ptk: last seq numbers per tid passed by the FW, + *	holds both in tkip and aes formats + */ +struct iwl_mld_wowlan_status { +	u32 wakeup_reasons; +	u64 replay_ctr; +	u16 pattern_number; +	u16 last_qos_seq; +	u32 num_of_gtk_rekeys; +	u8 tid_offloaded_tx; +	u8 *wake_packet; +	u32 wake_packet_length; +	u32 wake_packet_bufsize; +	struct iwl_mld_mcast_key_data gtk[WOWLAN_GTK_KEYS_NUM]; +	struct iwl_mld_mcast_key_data igtk; +	struct iwl_mld_mcast_key_data bigtk[WOWLAN_BIGTK_KEYS_NUM]; +	struct { +		struct ieee80211_key_seq aes_seq[IWL_MAX_TID_COUNT]; +		struct ieee80211_key_seq tkip_seq[IWL_MAX_TID_COUNT]; + +	} ptk; +}; + +#define NETDETECT_QUERY_BUF_LEN \ +	(sizeof(struct iwl_scan_offload_profile_match) * \ +	 IWL_SCAN_MAX_PROFILES_V2) + +/** + * struct iwl_mld_netdetect_res - contains netdetect results from + * match_info_notif + * @matched_profiles: bitmap of matched profiles, referencing the + *	matches passed in the scan offload request + * @matches: array of match information, one for each match + */ +struct iwl_mld_netdetect_res { +	u32 matched_profiles; +	u8 matches[NETDETECT_QUERY_BUF_LEN]; +}; + +/** + * struct iwl_mld_resume_data - d3 resume flow data + * @notifs_expected: bitmap of expected notifications from fw, + *	see &enum iwl_mld_d3_notif + * @notifs_received: bitmap of received notifications from fw, + *	see &enum iwl_mld_d3_notif + * @d3_end_flags: bitmap of flags from d3_end_notif + * @notif_handling_err: error handling one of the resume notifications + * @wowlan_status: wowlan status data from all wowlan notifications + * @netdetect_res: contains netdetect results from match_info_notif + */ +struct iwl_mld_resume_data { +	u32 notifs_expected; +	u32 notifs_received; +	u32 d3_end_flags; +	bool notif_handling_err; +	struct iwl_mld_wowlan_status *wowlan_status; +	struct iwl_mld_netdetect_res *netdetect_res; +}; + +#define IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT \ +	(IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET | \ +	IWL_WOWLAN_WAKEUP_BY_PATTERN | \ +	IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN |\ +	IWL_WAKEUP_BY_PATTERN_IPV4_TCP_SYN_WILDCARD |\ +	IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN |\ +	IWL_WAKEUP_BY_PATTERN_IPV6_TCP_SYN_WILDCARD) + +#define IWL_WOWLAN_OFFLOAD_TID 0 + +void iwl_mld_set_rekey_data(struct ieee80211_hw *hw, +			    struct ieee80211_vif *vif, +			    struct cfg80211_gtk_rekey_data *data) +{ +	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data; + +	lockdep_assert_wiphy(mld->wiphy); + +	wowlan_data->rekey_data.kek_len = data->kek_len; +	wowlan_data->rekey_data.kck_len = data->kck_len; +	memcpy(wowlan_data->rekey_data.kek, data->kek, data->kek_len); +	memcpy(wowlan_data->rekey_data.kck, data->kck, data->kck_len); +	wowlan_data->rekey_data.akm = data->akm & 0xFF; +	wowlan_data->rekey_data.replay_ctr = +		cpu_to_le64(be64_to_cpup((const __be64 *)data->replay_ctr)); +	wowlan_data->rekey_data.valid = true; +} + +#if IS_ENABLED(CONFIG_IPV6) +void iwl_mld_ipv6_addr_change(struct ieee80211_hw *hw, +			      struct ieee80211_vif *vif, +			      struct inet6_dev *idev) +{ +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data; +	struct inet6_ifaddr *ifa; +	int idx = 0; + +	memset(wowlan_data->tentative_addrs, 0, +	       sizeof(wowlan_data->tentative_addrs)); + +	read_lock_bh(&idev->lock); +	list_for_each_entry(ifa, &idev->addr_list, if_list) { +		wowlan_data->target_ipv6_addrs[idx] = ifa->addr; +		if (ifa->flags & IFA_F_TENTATIVE) +			__set_bit(idx, wowlan_data->tentative_addrs); +		idx++; +		if (idx >= IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_MAX) +			break; +	} +	read_unlock_bh(&idev->lock); + +	wowlan_data->num_target_ipv6_addrs = idx; +} +#endif + +static int +iwl_mld_netdetect_config(struct iwl_mld *mld, +			 struct ieee80211_vif *vif, +			 const struct cfg80211_wowlan *wowlan) +{ +	int ret; +	struct cfg80211_sched_scan_request *netdetect_cfg = +		wowlan->nd_config; +	struct ieee80211_scan_ies ies = {}; + +	ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_SCHED, true); +	if (ret) +		return ret; + +	ret = iwl_mld_sched_scan_start(mld, vif, netdetect_cfg, &ies, +				       IWL_MLD_SCAN_NETDETECT); +	return ret; +} + +static void +iwl_mld_le64_to_tkip_seq(__le64 le_pn, struct ieee80211_key_seq *seq) +{ +	u64 pn = le64_to_cpu(le_pn); + +	seq->tkip.iv16 = (u16)pn; +	seq->tkip.iv32 = (u32)(pn >> 16); +} + +static void +iwl_mld_le64_to_aes_seq(__le64 le_pn, struct ieee80211_key_seq *seq) +{ +	u64 pn = le64_to_cpu(le_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_mld_convert_gtk_resume_seq(struct iwl_mld_mcast_key_data *gtk_data, +			       const struct iwl_wowlan_all_rsc_tsc_v5 *sc, +			       int rsc_idx) +{ +	struct ieee80211_key_seq *aes_seq = gtk_data->gtk.aes_seq; +	struct ieee80211_key_seq *tkip_seq = gtk_data->gtk.tkip_seq; + +	if (rsc_idx >= ARRAY_SIZE(sc->mcast_rsc)) +		return; + +	/* We store both the TKIP and AES representations coming from the +	 * FW because we decode the data from there before we iterate +	 * the keys and know which type is used. +	 */ +	for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { +		iwl_mld_le64_to_tkip_seq(sc->mcast_rsc[rsc_idx][tid], +					 &tkip_seq[tid]); +		iwl_mld_le64_to_aes_seq(sc->mcast_rsc[rsc_idx][tid], +					&aes_seq[tid]); +	} +} + +static void +iwl_mld_convert_gtk_resume_data(struct iwl_mld *mld, +				struct iwl_mld_wowlan_status *wowlan_status, +				const struct iwl_wowlan_gtk_status_v3 *gtk_data, +				const struct iwl_wowlan_all_rsc_tsc_v5 *sc) +{ +	int status_idx = 0; + +	BUILD_BUG_ON(sizeof(wowlan_status->gtk[0].key) < +		     sizeof(gtk_data[0].key)); +	BUILD_BUG_ON(ARRAY_SIZE(wowlan_status->gtk) < WOWLAN_GTK_KEYS_NUM); + +	for (int notif_idx = 0; notif_idx < ARRAY_SIZE(wowlan_status->gtk); +	     notif_idx++) { +		int rsc_idx; + +		if (!(gtk_data[notif_idx].key_len)) +			continue; + +		wowlan_status->gtk[status_idx].len = +			gtk_data[notif_idx].key_len; +		wowlan_status->gtk[status_idx].flags = +			gtk_data[notif_idx].key_flags; +		wowlan_status->gtk[status_idx].id = +			wowlan_status->gtk[status_idx].flags & +			IWL_WOWLAN_GTK_IDX_MASK; +		memcpy(wowlan_status->gtk[status_idx].key, +		       gtk_data[notif_idx].key, +		       sizeof(gtk_data[notif_idx].key)); + +		/* The rsc for both gtk keys are stored in gtk[0]->sc->mcast_rsc +		 * The gtk ids can be any two numbers between 0 and 3, +		 * the id_map maps between the key id and the index in sc->mcast +		 */ +		rsc_idx = +			sc->mcast_key_id_map[wowlan_status->gtk[status_idx].id]; +		iwl_mld_convert_gtk_resume_seq(&wowlan_status->gtk[status_idx], +					       sc, rsc_idx); + +		/* if it's as long as the TKIP encryption key, copy MIC key */ +		if (wowlan_status->gtk[status_idx].len == +		    NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY) +			memcpy(wowlan_status->gtk[status_idx].key + +			       NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY, +			       gtk_data[notif_idx].tkip_mic_key, +			       sizeof(gtk_data[notif_idx].tkip_mic_key)); +		status_idx++; +	} +} + +static void +iwl_mld_convert_ptk_resume_seq(struct iwl_mld *mld, +			       struct iwl_mld_wowlan_status *wowlan_status, +			       const struct iwl_wowlan_all_rsc_tsc_v5 *sc) +{ +	struct ieee80211_key_seq *aes_seq = wowlan_status->ptk.aes_seq; +	struct ieee80211_key_seq *tkip_seq = wowlan_status->ptk.tkip_seq; + +	BUILD_BUG_ON(ARRAY_SIZE(sc->ucast_rsc) != IWL_MAX_TID_COUNT); + +	for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { +		iwl_mld_le64_to_aes_seq(sc->ucast_rsc[tid], &aes_seq[tid]); +		iwl_mld_le64_to_tkip_seq(sc->ucast_rsc[tid], &tkip_seq[tid]); +	} +} + +static void +iwl_mld_convert_mcast_ipn(struct iwl_mld_mcast_key_data *key_status, +			  const struct iwl_wowlan_igtk_status *key) +{ +	struct ieee80211_key_seq *seq = +		&key_status->igtk_bigtk.cmac_gmac_seq; +	u8 ipn_len = ARRAY_SIZE(key->ipn); + +	BUILD_BUG_ON(ipn_len != ARRAY_SIZE(seq->aes_gmac.pn)); +	BUILD_BUG_ON(ipn_len != ARRAY_SIZE(seq->aes_cmac.pn)); +	BUILD_BUG_ON(offsetof(struct ieee80211_key_seq, aes_gmac) != +		     offsetof(struct ieee80211_key_seq, aes_cmac)); + +	/* mac80211 expects big endian for memcmp() to work, convert. +	 * We don't have the key cipher yet so copy to both to cmac and gmac +	 */ +	for (int i = 0; i < ipn_len; i++) { +		seq->aes_gmac.pn[i] = key->ipn[ipn_len - i - 1]; +		seq->aes_cmac.pn[i] = key->ipn[ipn_len - i - 1]; +	} +} + +static void +iwl_mld_convert_igtk_resume_data(struct iwl_mld_wowlan_status *wowlan_status, +				 const struct iwl_wowlan_igtk_status *igtk) +{ +	BUILD_BUG_ON(sizeof(wowlan_status->igtk.key) < sizeof(igtk->key)); + +	if (!igtk->key_len) +		return; + +	wowlan_status->igtk.len = igtk->key_len; +	wowlan_status->igtk.flags = igtk->key_flags; +	wowlan_status->igtk.id = +		u32_get_bits(igtk->key_flags, +			     IWL_WOWLAN_IGTK_BIGTK_IDX_MASK) + +		WOWLAN_IGTK_MIN_INDEX; + +	memcpy(wowlan_status->igtk.key, igtk->key, sizeof(igtk->key)); +	iwl_mld_convert_mcast_ipn(&wowlan_status->igtk, igtk); +} + +static void +iwl_mld_convert_bigtk_resume_data(struct iwl_mld_wowlan_status *wowlan_status, +				  const struct iwl_wowlan_igtk_status *bigtk) +{ +	int status_idx = 0; + +	BUILD_BUG_ON(ARRAY_SIZE(wowlan_status->bigtk) < WOWLAN_BIGTK_KEYS_NUM); + +	for (int notif_idx = 0; notif_idx < WOWLAN_BIGTK_KEYS_NUM; +	     notif_idx++) { +		if (!bigtk[notif_idx].key_len) +			continue; + +		wowlan_status->bigtk[status_idx].len = bigtk[notif_idx].key_len; +		wowlan_status->bigtk[status_idx].flags = +			bigtk[notif_idx].key_flags; +		wowlan_status->bigtk[status_idx].id = +			u32_get_bits(bigtk[notif_idx].key_flags, +				     IWL_WOWLAN_IGTK_BIGTK_IDX_MASK) +			+ WOWLAN_BIGTK_MIN_INDEX; + +		BUILD_BUG_ON(sizeof(wowlan_status->bigtk[status_idx].key) < +			     sizeof(bigtk[notif_idx].key)); +		memcpy(wowlan_status->bigtk[status_idx].key, +		       bigtk[notif_idx].key, sizeof(bigtk[notif_idx].key)); +		iwl_mld_convert_mcast_ipn(&wowlan_status->bigtk[status_idx], +					  &bigtk[notif_idx]); +		status_idx++; +	} +} + +static bool +iwl_mld_handle_wowlan_info_notif(struct iwl_mld *mld, +				 struct iwl_mld_wowlan_status *wowlan_status, +				 struct iwl_rx_packet *pkt) +{ +	const struct iwl_wowlan_info_notif *notif = (void *)pkt->data; +	u32 expected_len, len = iwl_rx_packet_payload_len(pkt); + +	expected_len = sizeof(*notif); + +	if (IWL_FW_CHECK(mld, len < expected_len, +			 "Invalid wowlan_info_notif (expected=%ud got=%ud)\n", +			 expected_len, len)) +		return true; + +	if (IWL_FW_CHECK(mld, notif->tid_offloaded_tx != IWL_WOWLAN_OFFLOAD_TID, +			 "Invalid tid_offloaded_tx %d\n", +			 wowlan_status->tid_offloaded_tx)) +		return true; + +	iwl_mld_convert_gtk_resume_data(mld, wowlan_status, notif->gtk, +					¬if->gtk[0].sc); +	iwl_mld_convert_ptk_resume_seq(mld, wowlan_status, ¬if->gtk[0].sc); +	/* only one igtk is passed by FW */ +	iwl_mld_convert_igtk_resume_data(wowlan_status, ¬if->igtk[0]); +	iwl_mld_convert_bigtk_resume_data(wowlan_status, notif->bigtk); + +	wowlan_status->replay_ctr = le64_to_cpu(notif->replay_ctr); +	wowlan_status->pattern_number = le16_to_cpu(notif->pattern_number); + +	wowlan_status->tid_offloaded_tx = notif->tid_offloaded_tx; +	wowlan_status->last_qos_seq = le16_to_cpu(notif->qos_seq_ctr); +	wowlan_status->num_of_gtk_rekeys = +		le32_to_cpu(notif->num_of_gtk_rekeys); +	wowlan_status->wakeup_reasons = le32_to_cpu(notif->wakeup_reasons); +	return false; +	/* TODO: mlo_links (task=MLO)*/ +} + +static bool +iwl_mld_handle_wake_pkt_notif(struct iwl_mld *mld, +			      struct iwl_mld_wowlan_status *wowlan_status, +			      struct iwl_rx_packet *pkt) +{ +	const struct iwl_wowlan_wake_pkt_notif *notif = (void *)pkt->data; +	u32 actual_size, len = iwl_rx_packet_payload_len(pkt); +	u32 expected_size = le32_to_cpu(notif->wake_packet_length); + +	if (IWL_FW_CHECK(mld, len < sizeof(*notif), +			 "Invalid WoWLAN wake packet notification (expected size=%zu got=%u)\n", +			 sizeof(*notif), len)) +		return true; + +	if (IWL_FW_CHECK(mld, !(wowlan_status->wakeup_reasons & +				IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT), +			 "Got wake packet but wakeup reason is %x\n", +			 wowlan_status->wakeup_reasons)) +		return true; + +	actual_size = len - offsetof(struct iwl_wowlan_wake_pkt_notif, +				     wake_packet); + +	/* actual_size got the padding from the notification, remove it. */ +	if (expected_size < actual_size) +		actual_size = expected_size; +	wowlan_status->wake_packet = kmemdup(notif->wake_packet, actual_size, +					     GFP_ATOMIC); +	if (!wowlan_status->wake_packet) +		return true; + +	wowlan_status->wake_packet_length = expected_size; +	wowlan_status->wake_packet_bufsize = actual_size; + +	return false; +} + +static void +iwl_mld_set_wake_packet(struct iwl_mld *mld, +			struct ieee80211_vif *vif, +			const struct iwl_mld_wowlan_status *wowlan_status, +			struct cfg80211_wowlan_wakeup *wakeup, +			struct sk_buff **_pkt) +{ +	int pkt_bufsize = wowlan_status->wake_packet_bufsize; +	int expected_pktlen = wowlan_status->wake_packet_length; +	const u8 *pktdata = wowlan_status->wake_packet; +	const struct ieee80211_hdr *hdr = (const void *)pktdata; +	int truncated = expected_pktlen - pkt_bufsize; + +	if (ieee80211_is_data(hdr->frame_control)) { +		int hdrlen = ieee80211_hdrlen(hdr->frame_control); +		int ivlen = 0, icvlen = 4; /* also FCS */ + +		struct sk_buff *pkt = alloc_skb(pkt_bufsize, GFP_KERNEL); +		*_pkt = pkt; +		if (!pkt) +			return; + +		skb_put_data(pkt, pktdata, hdrlen); +		pktdata += hdrlen; +		pkt_bufsize -= hdrlen; + +		/* if truncated, FCS/ICV is (partially) gone */ +		if (truncated >= icvlen) { +			truncated -= icvlen; +			icvlen = 0; +		} else { +			icvlen -= truncated; +			truncated = 0; +		} + +		pkt_bufsize -= ivlen + icvlen; +		pktdata += ivlen; + +		skb_put_data(pkt, pktdata, pkt_bufsize); + +		if (ieee80211_data_to_8023(pkt, vif->addr, vif->type)) +			return; +		wakeup->packet = pkt->data; +		wakeup->packet_present_len = pkt->len; +		wakeup->packet_len = pkt->len - truncated; +		wakeup->packet_80211 = false; +	} else { +		int fcslen = 4; + +		if (truncated >= 4) { +			truncated -= 4; +			fcslen = 0; +		} else { +			fcslen -= truncated; +			truncated = 0; +		} +		pkt_bufsize -= fcslen; +		wakeup->packet = wowlan_status->wake_packet; +		wakeup->packet_present_len = pkt_bufsize; +		wakeup->packet_len = expected_pktlen - truncated; +		wakeup->packet_80211 = true; +	} +} + +static void +iwl_mld_report_wowlan_wakeup(struct iwl_mld *mld, +			     struct ieee80211_vif *vif, +			     struct iwl_mld_wowlan_status *wowlan_status) +{ +	struct sk_buff *pkt = NULL; +	struct cfg80211_wowlan_wakeup wakeup = { +		.pattern_idx = -1, +	}; +	u32 reasons = wowlan_status->wakeup_reasons; + +	if (reasons == IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) { +		ieee80211_report_wowlan_wakeup(vif, NULL, GFP_KERNEL); +		return; +	} + +	pm_wakeup_event(mld->dev, 0); + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_MAGIC_PACKET) +		wakeup.magic_pkt = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_PATTERN) +		wakeup.pattern_idx = +			wowlan_status->pattern_number; + +	if (reasons & (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | +		       IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH | +		       IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE)) +		wakeup.disconnect = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE) +		wakeup.gtk_rekey_failure = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED) +		wakeup.rfkill_release = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_EAPOL_REQUEST) +		wakeup.eap_identity_req = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_FOUR_WAY_HANDSHAKE) +		wakeup.four_way_handshake = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_LINK_LOSS) +		wakeup.tcp_connlost = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_SIGNATURE_TABLE) +		wakeup.tcp_nomoretokens = true; + +	if (reasons & IWL_WOWLAN_WAKEUP_BY_REM_WAKE_WAKEUP_PACKET) +		wakeup.tcp_match = true; + +	if (reasons & IWL_WAKEUP_BY_11W_UNPROTECTED_DEAUTH_OR_DISASSOC) +		wakeup.unprot_deauth_disassoc = true; + +	if (wowlan_status->wake_packet) +		iwl_mld_set_wake_packet(mld, vif, wowlan_status, &wakeup, &pkt); + +	ieee80211_report_wowlan_wakeup(vif, &wakeup, GFP_KERNEL); +	kfree_skb(pkt); +} + +static void +iwl_mld_set_key_rx_seq_tids(struct ieee80211_key_conf *key, +			    struct ieee80211_key_seq *seq) +{ +	int tid; + +	for (tid = 0; tid < IWL_MAX_TID_COUNT; tid++) +		ieee80211_set_key_rx_seq(key, tid, &seq[tid]); +} + +static void +iwl_mld_set_key_rx_seq(struct ieee80211_key_conf *key, +		       struct iwl_mld_mcast_key_data *key_data) +{ +	switch (key->cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +	case WLAN_CIPHER_SUITE_GCMP: +	case WLAN_CIPHER_SUITE_GCMP_256: +		iwl_mld_set_key_rx_seq_tids(key, +					    key_data->gtk.aes_seq); +		break; +	case WLAN_CIPHER_SUITE_TKIP: +		iwl_mld_set_key_rx_seq_tids(key, +					    key_data->gtk.tkip_seq); +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_128: +	case WLAN_CIPHER_SUITE_BIP_GMAC_256: +	case WLAN_CIPHER_SUITE_BIP_CMAC_256: +	case WLAN_CIPHER_SUITE_AES_CMAC: +		/* igtk/bigtk ciphers*/ +		ieee80211_set_key_rx_seq(key, 0, +					 &key_data->igtk_bigtk.cmac_gmac_seq); +		break; +	default: +		WARN_ON(1); +	} +} + +static void +iwl_mld_update_ptk_rx_seq(struct iwl_mld *mld, +			  struct iwl_mld_wowlan_status *wowlan_status, +			  struct ieee80211_sta *sta, +			  struct ieee80211_key_conf *key, +			  bool is_tkip) +{ +	struct iwl_mld_sta *mld_sta = +		iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld_ptk_pn *mld_ptk_pn = +		wiphy_dereference(mld->wiphy, +				  mld_sta->ptk_pn[key->keyidx]); + +	iwl_mld_set_key_rx_seq_tids(key, is_tkip ? +				    wowlan_status->ptk.tkip_seq : +				    wowlan_status->ptk.aes_seq); +	if (is_tkip) +		return; + +	if (WARN_ON(!mld_ptk_pn)) +		return; + +	for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { +		for (int i = 1; i < mld->trans->info.num_rxqs; i++) +			memcpy(mld_ptk_pn->q[i].pn[tid], +			       wowlan_status->ptk.aes_seq[tid].ccmp.pn, +			       IEEE80211_CCMP_PN_LEN); +	} +} + +static void +iwl_mld_resume_keys_iter(struct ieee80211_hw *hw, +			 struct ieee80211_vif *vif, +			 struct ieee80211_sta *sta, +			 struct ieee80211_key_conf *key, +			 void *_data) +{ +	struct iwl_mld_resume_key_iter_data *data = _data; +	struct iwl_mld_wowlan_status *wowlan_status = data->wowlan_status; +	u8 status_idx; + +	/* TODO: check key link id (task=MLO) */ +	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_GCMP: +	case WLAN_CIPHER_SUITE_GCMP_256: +	case WLAN_CIPHER_SUITE_TKIP: +		if (sta) { +			iwl_mld_update_ptk_rx_seq(data->mld, wowlan_status, +						  sta, key, +						  key->cipher == +						  WLAN_CIPHER_SUITE_TKIP); +			return; +		} + +		if (WARN_ON(data->gtk_cipher && +			    data->gtk_cipher != key->cipher)) +			return; + +		data->gtk_cipher = key->cipher; +		status_idx = key->keyidx == wowlan_status->gtk[1].id; +		iwl_mld_set_key_rx_seq(key, &wowlan_status->gtk[status_idx]); +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_128: +	case WLAN_CIPHER_SUITE_BIP_GMAC_256: +	case WLAN_CIPHER_SUITE_BIP_CMAC_256: +	case WLAN_CIPHER_SUITE_AES_CMAC: +		if (key->keyidx == 4 || key->keyidx == 5) { +			if (WARN_ON(data->igtk_cipher && +				    data->igtk_cipher != key->cipher)) +				return; + +			data->igtk_cipher = key->cipher; +			if (key->keyidx == wowlan_status->igtk.id) +				iwl_mld_set_key_rx_seq(key, &wowlan_status->igtk); +		} +		if (key->keyidx == 6 || key->keyidx == 7) { +			if (WARN_ON(data->bigtk_cipher && +				    data->bigtk_cipher != key->cipher)) +				return; + +			data->bigtk_cipher = key->cipher; +			status_idx = key->keyidx == wowlan_status->bigtk[1].id; +			iwl_mld_set_key_rx_seq(key, &wowlan_status->bigtk[status_idx]); +		} +		break; +	default: +		data->unhandled_cipher = true; +		return; +	} +	data->num_keys++; +} + +static void +iwl_mld_add_mcast_rekey(struct ieee80211_vif *vif, +			struct iwl_mld *mld, +			struct iwl_mld_mcast_key_data *key_data, +			struct ieee80211_bss_conf *link_conf, +			u32 cipher) +{ +	struct ieee80211_key_conf *key_config; +	struct { +		struct ieee80211_key_conf conf; +		u8 key[WOWLAN_KEY_MAX_SIZE]; +	} conf = { +		.conf.cipher = cipher, +		.conf.keyidx = key_data->id, +	}; +	int link_id = vif->active_links ? __ffs(vif->active_links) : -1; +	u8 key[WOWLAN_KEY_MAX_SIZE]; + +	BUILD_BUG_ON(WLAN_KEY_LEN_CCMP != WLAN_KEY_LEN_GCMP); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_CCMP); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_GCMP_256); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_TKIP); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_BIP_GMAC_128); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_BIP_GMAC_256); +	BUILD_BUG_ON(sizeof(conf.key) < WLAN_KEY_LEN_AES_CMAC); +	BUILD_BUG_ON(sizeof(conf.key) < sizeof(key_data->key)); + +	if (!key_data->len) +		return; + +	switch (cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +	case WLAN_CIPHER_SUITE_GCMP: +		conf.conf.keylen = WLAN_KEY_LEN_CCMP; +		break; +	case WLAN_CIPHER_SUITE_GCMP_256: +		conf.conf.keylen = WLAN_KEY_LEN_GCMP_256; +		break; +	case WLAN_CIPHER_SUITE_TKIP: +		conf.conf.keylen = WLAN_KEY_LEN_TKIP; +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_128: +		conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_128; +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_256: +		conf.conf.keylen = WLAN_KEY_LEN_BIP_GMAC_256; +		break; +	case WLAN_CIPHER_SUITE_AES_CMAC: +		conf.conf.keylen = WLAN_KEY_LEN_AES_CMAC; +		break; +	case WLAN_CIPHER_SUITE_BIP_CMAC_256: +		conf.conf.keylen = WLAN_KEY_LEN_BIP_CMAC_256; +		break; +	default: +		WARN_ON(1); +	} + +	memcpy(conf.conf.key, key_data->key, conf.conf.keylen); + +	memcpy(key, key_data->key, sizeof(key_data->key)); + +	key_config = ieee80211_gtk_rekey_add(vif, key_data->id, key, +					     sizeof(key), link_id); +	if (IS_ERR(key_config)) +		return; + +	iwl_mld_set_key_rx_seq(key_config, key_data); + +	/* The FW holds only one igtk so we keep track of the valid one */ +	if (key_config->keyidx == 4 || key_config->keyidx == 5) { +		struct iwl_mld_link *mld_link = +			iwl_mld_link_from_mac80211(link_conf); + +		/* If we had more than one rekey, mac80211 will tell us to +		 * remove the old and add the new so we will update the IGTK in +		 * drv_set_key +		 */ +		if (mld_link->igtk && mld_link->igtk != key_config) { +			/* mark the old IGTK as not in FW */ +			mld_link->igtk->hw_key_idx = STA_KEY_IDX_INVALID; +			mld_link->igtk = key_config; +		} +	} + +	/* Also keep track of the new BIGTK */ +	if ((key_config->keyidx == 6 || key_config->keyidx == 7) && +	    vif->type == NL80211_IFTYPE_STATION) { +		struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); + +		rcu_assign_pointer(mld_vif->bigtks[key_config->keyidx - 6], key_config); +	} +} + +static void +iwl_mld_add_all_rekeys(struct ieee80211_vif *vif, +		       struct iwl_mld_wowlan_status *wowlan_status, +		       struct iwl_mld_resume_key_iter_data *key_iter_data, +		       struct ieee80211_bss_conf *link_conf) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(wowlan_status->gtk); i++) +		iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, +					&wowlan_status->gtk[i], +					link_conf, +					key_iter_data->gtk_cipher); + +	iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, +				&wowlan_status->igtk, +				link_conf, key_iter_data->igtk_cipher); + +	for (i = 0; i < ARRAY_SIZE(wowlan_status->bigtk); i++) +		iwl_mld_add_mcast_rekey(vif, key_iter_data->mld, +					&wowlan_status->bigtk[i], +					link_conf, +					key_iter_data->bigtk_cipher); +} + +static bool +iwl_mld_update_sec_keys(struct iwl_mld *mld, +			struct ieee80211_vif *vif, +			struct iwl_mld_wowlan_status *wowlan_status) +{ +	int link_id = vif->active_links ? __ffs(vif->active_links) : 0; +	struct ieee80211_bss_conf *link_conf = +		link_conf_dereference_protected(vif, link_id); +	__be64 replay_ctr = cpu_to_be64(wowlan_status->replay_ctr); +	struct iwl_mld_resume_key_iter_data key_iter_data = { +		.mld = mld, +		.wowlan_status = wowlan_status, +	}; + +	if (WARN_ON(!link_conf)) +		return false; + +	ieee80211_iter_keys(mld->hw, vif, iwl_mld_resume_keys_iter, +			    &key_iter_data); + +	if (key_iter_data.unhandled_cipher) +		return false; + +	IWL_DEBUG_WOWLAN(mld, +			 "Number of installed keys: %d, Number of rekeys: %d\n", +			 key_iter_data.num_keys, +			 wowlan_status->num_of_gtk_rekeys); + +	if (!key_iter_data.num_keys || !wowlan_status->num_of_gtk_rekeys) +		return true; + +	iwl_mld_add_all_rekeys(vif, wowlan_status, &key_iter_data, +			       link_conf); + +	ieee80211_gtk_rekey_notify(vif, link_conf->bssid, +				   (void *)&replay_ctr, GFP_KERNEL); +	/* TODO: MLO rekey (task=MLO) */ +	return true; +} + +static bool +iwl_mld_process_wowlan_status(struct iwl_mld *mld, +			      struct ieee80211_vif *vif, +			      struct iwl_mld_wowlan_status *wowlan_status) +{ +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	struct ieee80211_sta *ap_sta = mld_vif->ap_sta; +	struct iwl_mld_txq *mld_txq; + +	iwl_mld_report_wowlan_wakeup(mld, vif, wowlan_status); + +	if (WARN_ON(!ap_sta)) +		return false; + +	mld_txq = +		iwl_mld_txq_from_mac80211(ap_sta->txq[wowlan_status->tid_offloaded_tx]); + +	/* Update the pointers of the Tx queue that may have moved during +	 * suspend if the firmware sent frames. +	 * The firmware stores last-used value, we store next value. +	 */ +	WARN_ON(!mld_txq->status.allocated); +	iwl_trans_set_q_ptrs(mld->trans, mld_txq->fw_id, +			     (wowlan_status->last_qos_seq + +			     0x10) >> 4); + +	if (!iwl_mld_update_sec_keys(mld, vif, wowlan_status)) +		return false; + +	if (wowlan_status->wakeup_reasons & +	    (IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_MISSED_BEACON | +	     IWL_WOWLAN_WAKEUP_BY_DISCONNECTION_ON_DEAUTH | +	     IWL_WOWLAN_WAKEUP_BY_GTK_REKEY_FAILURE)) +		return false; + +	return true; +} + +static bool +iwl_mld_netdetect_match_info_handler(struct iwl_mld *mld, +				     struct iwl_mld_resume_data *resume_data, +				     struct iwl_rx_packet *pkt) +{ +	struct iwl_mld_netdetect_res *results = resume_data->netdetect_res; +	const struct iwl_scan_offload_match_info *notif = (void *)pkt->data; +	u32 len = iwl_rx_packet_payload_len(pkt); + +	if (IWL_FW_CHECK(mld, !mld->netdetect, +			 "Got scan match info notif when mld->netdetect==%d\n", +			 mld->netdetect)) +		return true; + +	if (IWL_FW_CHECK(mld, len < sizeof(*notif), +			 "Invalid scan offload match notif of length: %d\n", +			 len)) +		return true; + +	if (IWL_FW_CHECK(mld, resume_data->wowlan_status->wakeup_reasons != +			 IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS, +			 "Ignore scan match info: unexpected wakeup reason (expected=0x%x got=0x%x)\n", +			 IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS, +			 resume_data->wowlan_status->wakeup_reasons)) +		return true; + +	results->matched_profiles = le32_to_cpu(notif->matched_profiles); +	IWL_DEBUG_WOWLAN(mld, "number of matched profiles=%u\n", +			 results->matched_profiles); + +	if (results->matched_profiles) +		memcpy(results->matches, notif->matches, +		       NETDETECT_QUERY_BUF_LEN); + +	/* No scan should be active at this point */ +	mld->scan.status = 0; +	memset(mld->scan.uid_status, 0, sizeof(mld->scan.uid_status)); +	return false; +} + +static void +iwl_mld_set_netdetect_info(struct iwl_mld *mld, +			   const struct cfg80211_sched_scan_request *netdetect_cfg, +			   struct cfg80211_wowlan_nd_info *netdetect_info, +			   struct iwl_mld_netdetect_res *netdetect_res, +			   unsigned long matched_profiles) +{ +	int i; + +	for_each_set_bit(i, &matched_profiles, netdetect_cfg->n_match_sets) { +		struct cfg80211_wowlan_nd_match *match; +		int idx, j, n_channels = 0; +		struct iwl_scan_offload_profile_match *matches = +			(void *)netdetect_res->matches; + +		for (int k = 0; k < SCAN_OFFLOAD_MATCHING_CHANNELS_LEN; k++) +			n_channels += +				hweight8(matches[i].matching_channels[k]); +		match = kzalloc(struct_size(match, channels, n_channels), +				GFP_KERNEL); +		if (!match) +			return; + +		netdetect_info->matches[netdetect_info->n_matches] = match; +		netdetect_info->n_matches++; + +		/* We inverted the order of the SSIDs in the scan +		 * request, so invert the index here. +		 */ +		idx = netdetect_cfg->n_match_sets - i - 1; +		match->ssid.ssid_len = +			netdetect_cfg->match_sets[idx].ssid.ssid_len; +		memcpy(match->ssid.ssid, +		       netdetect_cfg->match_sets[idx].ssid.ssid, +		       match->ssid.ssid_len); + +		if (netdetect_cfg->n_channels < n_channels) +			continue; + +		for_each_set_bit(j, +				 (unsigned long *)&matches[i].matching_channels[0], +				 sizeof(matches[i].matching_channels)) { +			match->channels[match->n_channels] = +				netdetect_cfg->channels[j]->center_freq; +			match->n_channels++; +		} +	} +} + +static void +iwl_mld_process_netdetect_res(struct iwl_mld *mld, +			      struct ieee80211_vif *vif, +			      struct iwl_mld_resume_data *resume_data) +{ +	struct cfg80211_wowlan_nd_info *netdetect_info = NULL; +	const struct cfg80211_sched_scan_request *netdetect_cfg; +	struct cfg80211_wowlan_wakeup wakeup = { +		.pattern_idx = -1, +	}; +	struct cfg80211_wowlan_wakeup *wakeup_report = &wakeup; +	unsigned long matched_profiles; +	u32 wakeup_reasons; +	int n_matches; + +	lockdep_assert_wiphy(mld->wiphy); + +	if (WARN_ON(!mld->wiphy->wowlan_config || +		    !mld->wiphy->wowlan_config->nd_config)) { +		IWL_DEBUG_WOWLAN(mld, +				 "Netdetect isn't configured on resume flow\n"); +		goto out; +	} + +	netdetect_cfg = mld->wiphy->wowlan_config->nd_config; +	wakeup_reasons = resume_data->wowlan_status->wakeup_reasons; + +	if (wakeup_reasons & IWL_WOWLAN_WAKEUP_BY_RFKILL_DEASSERTED) +		wakeup.rfkill_release = true; + +	if (wakeup_reasons != IWL_WOWLAN_WAKEUP_BY_NON_WIRELESS) +		goto out; + +	if (!resume_data->netdetect_res->matched_profiles) { +		IWL_DEBUG_WOWLAN(mld, +				 "Netdetect results aren't valid\n"); +		wakeup_report = NULL; +		goto out; +	} + +	matched_profiles = resume_data->netdetect_res->matched_profiles; +	if (!netdetect_cfg->n_match_sets) { +		IWL_DEBUG_WOWLAN(mld, +				 "No netdetect match sets are configured\n"); +		goto out; +	} +	n_matches = hweight_long(matched_profiles); +	netdetect_info = kzalloc(struct_size(netdetect_info, matches, +					     n_matches), GFP_KERNEL); +	if (netdetect_info) +		iwl_mld_set_netdetect_info(mld, netdetect_cfg, netdetect_info, +					   resume_data->netdetect_res, +					   matched_profiles); + +	wakeup.net_detect = netdetect_info; + out: +	ieee80211_report_wowlan_wakeup(vif, wakeup_report, GFP_KERNEL); +	if (netdetect_info) { +		for (int i = 0; i < netdetect_info->n_matches; i++) +			kfree(netdetect_info->matches[i]); +		kfree(netdetect_info); +	} +} + +static bool iwl_mld_handle_d3_notif(struct iwl_notif_wait_data *notif_wait, +				    struct iwl_rx_packet *pkt, void *data) +{ +	struct iwl_mld_resume_data *resume_data = data; +	struct iwl_mld *mld = +		container_of(notif_wait, struct iwl_mld, notif_wait); + +	switch (WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) { +	case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION): { +		if (resume_data->notifs_received & IWL_D3_NOTIF_WOWLAN_INFO) { +			IWL_DEBUG_WOWLAN(mld, +					 "got additional wowlan_info notif\n"); +			break; +		} +		resume_data->notif_handling_err = +			iwl_mld_handle_wowlan_info_notif(mld, +							 resume_data->wowlan_status, +							 pkt); +		resume_data->notifs_received |= IWL_D3_NOTIF_WOWLAN_INFO; + +		if (resume_data->wowlan_status->wakeup_reasons & +		    IWL_WOWLAN_WAKEUP_REASON_HAS_WAKEUP_PKT) +			resume_data->notifs_expected |= +				IWL_D3_NOTIF_WOWLAN_WAKE_PKT; +		break; +	} +	case WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION): { +		if (resume_data->notifs_received & +		    IWL_D3_NOTIF_WOWLAN_WAKE_PKT) { +			/* We shouldn't get two wake packet notifications */ +			IWL_DEBUG_WOWLAN(mld, +					 "Got additional wowlan wake packet notification\n"); +			break; +		} +		resume_data->notif_handling_err = +			iwl_mld_handle_wake_pkt_notif(mld, +						      resume_data->wowlan_status, +						      pkt); +		resume_data->notifs_received |= IWL_D3_NOTIF_WOWLAN_WAKE_PKT; +		break; +	} +	case WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF): { +		if (resume_data->notifs_received & IWL_D3_ND_MATCH_INFO) { +			IWL_ERR(mld, +				"Got additional netdetect match info\n"); +			break; +		} + +		resume_data->notif_handling_err = +			iwl_mld_netdetect_match_info_handler(mld, resume_data, +							     pkt); +		resume_data->notifs_received |= IWL_D3_ND_MATCH_INFO; +		break; +	} +	case WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION): { +		struct iwl_d3_end_notif *notif = (void *)pkt->data; + +		resume_data->d3_end_flags = le32_to_cpu(notif->flags); +		resume_data->notifs_received |= IWL_D3_NOTIF_D3_END_NOTIF; +		break; +	} +	default: +		WARN_ON(1); +	} + +	return resume_data->notifs_received == resume_data->notifs_expected; +} + +#define IWL_MLD_D3_NOTIF_TIMEOUT (HZ / 3) + +static int iwl_mld_wait_d3_notif(struct iwl_mld *mld, +				 struct iwl_mld_resume_data *resume_data, +				 bool with_wowlan) +{ +	static const u16 wowlan_resume_notif[] = { +		WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_INFO_NOTIFICATION), +		WIDE_ID(PROT_OFFLOAD_GROUP, WOWLAN_WAKE_PKT_NOTIFICATION), +		WIDE_ID(SCAN_GROUP, OFFLOAD_MATCH_INFO_NOTIF), +		WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION) +	}; +	static const u16 d3_resume_notif[] = { +		WIDE_ID(PROT_OFFLOAD_GROUP, D3_END_NOTIFICATION) +	}; +	struct iwl_notification_wait wait_d3_notif; +	enum iwl_d3_status d3_status; +	int ret; + +	if (with_wowlan) +		iwl_init_notification_wait(&mld->notif_wait, &wait_d3_notif, +					   wowlan_resume_notif, +					   ARRAY_SIZE(wowlan_resume_notif), +					   iwl_mld_handle_d3_notif, +					   resume_data); +	else +		iwl_init_notification_wait(&mld->notif_wait, &wait_d3_notif, +					   d3_resume_notif, +					   ARRAY_SIZE(d3_resume_notif), +					   iwl_mld_handle_d3_notif, +					   resume_data); + +	ret = iwl_trans_d3_resume(mld->trans, &d3_status, false, false); +	if (ret || d3_status != IWL_D3_STATUS_ALIVE) { +		if (d3_status != IWL_D3_STATUS_ALIVE) { +			IWL_INFO(mld, "Device was reset during suspend\n"); +			ret = -ENOENT; +		} else { +			IWL_ERR(mld, "Transport resume failed\n"); +		} +		iwl_remove_notification(&mld->notif_wait, &wait_d3_notif); +		return ret; +	} + +	ret = iwl_wait_notification(&mld->notif_wait, &wait_d3_notif, +				    IWL_MLD_D3_NOTIF_TIMEOUT); +	if (ret) +		IWL_ERR(mld, "Couldn't get the d3 notif %d\n", ret); + +	if (resume_data->notif_handling_err) +		ret = -EIO; + +	return ret; +} + +int iwl_mld_no_wowlan_suspend(struct iwl_mld *mld) +{ +	struct iwl_d3_manager_config d3_cfg_cmd_data = {}; +	int ret; + +	if (mld->debug_max_sleep) { +		d3_cfg_cmd_data.wakeup_host_timer = +			cpu_to_le32(mld->debug_max_sleep); +		d3_cfg_cmd_data.wakeup_flags = +			cpu_to_le32(IWL_WAKEUP_D3_HOST_TIMER); +	} + +	lockdep_assert_wiphy(mld->wiphy); + +	IWL_DEBUG_WOWLAN(mld, "Starting the no wowlan suspend flow\n"); + +	iwl_mld_low_latency_stop(mld); + +	/* This will happen if iwl_mld_supsend failed with FW error */ +	if (mld->trans->state == IWL_TRANS_NO_FW && +	    test_bit(STATUS_FW_ERROR, &mld->trans->status)) +		return -ENODEV; + +	ret = iwl_mld_update_device_power(mld, true); +	if (ret) { +		IWL_ERR(mld, +			"d3 suspend: couldn't send power_device %d\n", ret); +		goto out; +	} + +	ret = iwl_mld_send_cmd_pdu(mld, D3_CONFIG_CMD, +				   &d3_cfg_cmd_data); +	if (ret) { +		IWL_ERR(mld, +			"d3 suspend: couldn't send D3_CONFIG_CMD %d\n", ret); +		goto out; +	} + +	ret = iwl_trans_d3_suspend(mld->trans, false, false); +	if (ret) { +		IWL_ERR(mld, "d3 suspend: trans_d3_suspend failed %d\n", ret); +	} else { +		/* Async notification might send hcmds, which is not allowed in suspend */ +		iwl_mld_cancel_async_notifications(mld); +		mld->fw_status.in_d3 = true; +	} + + out: +	if (ret) { +		mld->trans->state = IWL_TRANS_NO_FW; +		set_bit(STATUS_FW_ERROR, &mld->trans->status); +	} + +	return ret; +} + +int iwl_mld_no_wowlan_resume(struct iwl_mld *mld) +{ +	struct iwl_mld_resume_data resume_data = { +		.notifs_expected = +			IWL_D3_NOTIF_D3_END_NOTIF, +	}; +	int ret; + +	lockdep_assert_wiphy(mld->wiphy); + +	IWL_DEBUG_WOWLAN(mld, "Starting the no wowlan resume flow\n"); + +	mld->fw_status.in_d3 = false; +	iwl_fw_dbg_read_d3_debug_data(&mld->fwrt); + +	ret = iwl_mld_wait_d3_notif(mld, &resume_data, false); + +	if (!ret && (resume_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE)) +		return -ENODEV; + +	if (ret) { +		mld->trans->state = IWL_TRANS_NO_FW; +		set_bit(STATUS_FW_ERROR, &mld->trans->status); +		return ret; +	} +	iwl_mld_low_latency_restart(mld); + +	return iwl_mld_update_device_power(mld, false); +} + +static void +iwl_mld_aes_seq_to_le64_pn(struct ieee80211_key_conf *key, +			   __le64 *key_rsc) +{ +	for (int i = 0; i < IWL_MAX_TID_COUNT; i++) { +		struct ieee80211_key_seq seq; +		u8 *pn = key->cipher == WLAN_CIPHER_SUITE_CCMP ? seq.ccmp.pn : +			seq.gcmp.pn; + +		ieee80211_get_key_rx_seq(key, i, &seq); +		key_rsc[i] = 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)); +	} +} + +static void +iwl_mld_suspend_set_ucast_pn(struct iwl_mld *mld, struct ieee80211_sta *sta, +			     struct ieee80211_key_conf *key, __le64 *key_rsc) +{ +	struct iwl_mld_sta *mld_sta = +		iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld_ptk_pn *mld_ptk_pn; + +	if (WARN_ON(key->keyidx >= ARRAY_SIZE(mld_sta->ptk_pn))) +		return; + +	mld_ptk_pn = wiphy_dereference(mld->wiphy, +				       mld_sta->ptk_pn[key->keyidx]); +	if (WARN_ON(!mld_ptk_pn)) +		return; + +	for (int tid = 0; tid < IWL_MAX_TID_COUNT; tid++) { +		struct ieee80211_key_seq seq; +		u8 *max_pn = seq.ccmp.pn; + +		/* get the PN from mac80211, used on the default queue */ +		ieee80211_get_key_rx_seq(key, tid, &seq); + +		/* and use the internal data for all queues */ +		for (int que = 1; que < mld->trans->info.num_rxqs; que++) { +			u8 *cur_pn = mld_ptk_pn->q[que].pn[tid]; + +			if (memcmp(max_pn, cur_pn, IEEE80211_CCMP_PN_LEN) < 0) +				max_pn = cur_pn; +		} +		key_rsc[tid] = cpu_to_le64((u64)max_pn[5] | +					   ((u64)max_pn[4] << 8) | +					   ((u64)max_pn[3] << 16) | +					   ((u64)max_pn[2] << 24) | +					   ((u64)max_pn[1] << 32) | +					   ((u64)max_pn[0] << 40)); +	} +} + +static void +iwl_mld_suspend_convert_tkip_ipn(struct ieee80211_key_conf *key, +				 __le64 *rsc) +{ +	struct ieee80211_key_seq seq; + +	for (int i = 0; i < IWL_MAX_TID_COUNT; i++) { +		ieee80211_get_key_rx_seq(key, i, &seq); +		rsc[i] = +			cpu_to_le64(((u64)seq.tkip.iv32 << 16) | +				    seq.tkip.iv16); +	} +} + +static void +iwl_mld_suspend_key_data_iter(struct ieee80211_hw *hw, +			      struct ieee80211_vif *vif, +			      struct ieee80211_sta *sta, +			      struct ieee80211_key_conf *key, +			      void *_data) +{ +	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw); +	struct iwl_mld_suspend_key_iter_data *data = _data; +	__le64 *key_rsc; +	__le32 cipher = 0; + +	switch (key->cipher) { +	case WLAN_CIPHER_SUITE_CCMP: +		cipher = cpu_to_le32(STA_KEY_FLG_CCM); +		fallthrough; +	case WLAN_CIPHER_SUITE_GCMP: +	case WLAN_CIPHER_SUITE_GCMP_256: +		if (!cipher) +			cipher = cpu_to_le32(STA_KEY_FLG_GCMP); +		fallthrough; +	case WLAN_CIPHER_SUITE_TKIP: +		if (!cipher) +			cipher = cpu_to_le32(STA_KEY_FLG_TKIP); +		if (sta) { +			key_rsc = data->rsc->ucast_rsc; +			if (key->cipher == WLAN_CIPHER_SUITE_TKIP) +				iwl_mld_suspend_convert_tkip_ipn(key, key_rsc); +			else +				iwl_mld_suspend_set_ucast_pn(mld, sta, key, +							     key_rsc); + +			data->have_rsc = true; +			return; +		} +		/* We're iterating from old to new, there're 4 possible +		 * gtk ids, and only the last two keys matter +		 */ +		if (WARN_ON(data->gtks >= +				ARRAY_SIZE(data->found_gtk_idx))) +			return; + +		if (WARN_ON(key->keyidx >= +				ARRAY_SIZE(data->rsc->mcast_key_id_map))) +			return; +		data->gtk_cipher = cipher; +		data->found_gtk_idx[data->gtks] = key->keyidx; +		key_rsc = data->rsc->mcast_rsc[data->gtks % 2]; +		data->rsc->mcast_key_id_map[key->keyidx] = +			data->gtks % 2; + +		if (data->gtks >= 2) { +			int prev = data->gtks % 2; +			int prev_idx = data->found_gtk_idx[prev]; + +			data->rsc->mcast_key_id_map[prev_idx] = +				IWL_MCAST_KEY_MAP_INVALID; +		} + +		if (key->cipher == WLAN_CIPHER_SUITE_TKIP) +			iwl_mld_suspend_convert_tkip_ipn(key, key_rsc); +		else +			iwl_mld_aes_seq_to_le64_pn(key, key_rsc); + +		data->gtks++; +		data->have_rsc = true; +		break; +	case WLAN_CIPHER_SUITE_BIP_GMAC_128: +	case WLAN_CIPHER_SUITE_BIP_GMAC_256: +		cipher = cpu_to_le32(STA_KEY_FLG_GCMP); +		fallthrough; +	case WLAN_CIPHER_SUITE_BIP_CMAC_256: +	case WLAN_CIPHER_SUITE_AES_CMAC: +		if (!cipher) +			cipher = cpu_to_le32(STA_KEY_FLG_CCM); +		if (key->keyidx == 4 || key->keyidx == 5) +			data->igtk_cipher = cipher; + +		if (key->keyidx == 6 || key->keyidx == 7) +			data->bigtk_cipher = cipher; + +		break; +	} +} + +static int +iwl_mld_send_kek_kck_cmd(struct iwl_mld *mld, +			 struct iwl_mld_vif *mld_vif, +			 struct iwl_mld_suspend_key_iter_data data, +			 int ap_sta_id) +{ +	struct iwl_wowlan_kek_kck_material_cmd_v4 kek_kck_cmd = {}; +	struct iwl_mld_rekey_data *rekey_data = +		&mld_vif->wowlan_data.rekey_data; + +	memcpy(kek_kck_cmd.kck, rekey_data->kck, +	       rekey_data->kck_len); +	kek_kck_cmd.kck_len = cpu_to_le16(rekey_data->kck_len); +	memcpy(kek_kck_cmd.kek, rekey_data->kek, +	       rekey_data->kek_len); +	kek_kck_cmd.kek_len = cpu_to_le16(rekey_data->kek_len); +	kek_kck_cmd.replay_ctr = rekey_data->replay_ctr; +	kek_kck_cmd.akm = cpu_to_le32(rekey_data->akm); +	kek_kck_cmd.sta_id = cpu_to_le32(ap_sta_id); +	kek_kck_cmd.gtk_cipher = data.gtk_cipher; +	kek_kck_cmd.igtk_cipher = data.igtk_cipher; +	kek_kck_cmd.bigtk_cipher = data.bigtk_cipher; + +	IWL_DEBUG_WOWLAN(mld, "setting akm %d\n", +			 rekey_data->akm); + +	return iwl_mld_send_cmd_pdu(mld, WOWLAN_KEK_KCK_MATERIAL, +				    &kek_kck_cmd); +} + +static int +iwl_mld_suspend_send_security_cmds(struct iwl_mld *mld, +				   struct ieee80211_vif *vif, +				   struct iwl_mld_vif *mld_vif, +				   int ap_sta_id) +{ +	struct iwl_mld_suspend_key_iter_data data = {}; +	int ret; + +	data.rsc = kzalloc(sizeof(*data.rsc), GFP_KERNEL); +	if (!data.rsc) +		return -ENOMEM; + +	memset(data.rsc->mcast_key_id_map, IWL_MCAST_KEY_MAP_INVALID, +	       ARRAY_SIZE(data.rsc->mcast_key_id_map)); + +	data.rsc->sta_id = cpu_to_le32(ap_sta_id); +	ieee80211_iter_keys(mld->hw, vif, +			    iwl_mld_suspend_key_data_iter, +			    &data); + +	if (data.have_rsc) +		ret = iwl_mld_send_cmd_pdu(mld, WOWLAN_TSC_RSC_PARAM, +					   data.rsc); +	else +		ret = 0; + +	if (!ret && mld_vif->wowlan_data.rekey_data.valid) +		ret = iwl_mld_send_kek_kck_cmd(mld, mld_vif, data, ap_sta_id); + +	kfree(data.rsc); + +	return ret; +} + +static void +iwl_mld_set_wowlan_config_cmd(struct iwl_mld *mld, +			      struct cfg80211_wowlan *wowlan, +			      struct iwl_wowlan_config_cmd *wowlan_config_cmd, +			      struct ieee80211_sta *ap_sta) +{ +	wowlan_config_cmd->is_11n_connection = +					ap_sta->deflink.ht_cap.ht_supported; +	wowlan_config_cmd->flags = ENABLE_L3_FILTERING | +		ENABLE_NBNS_FILTERING | ENABLE_DHCP_FILTERING; + +	if (ap_sta->mfp) +		wowlan_config_cmd->flags |= IS_11W_ASSOC; + +	if (wowlan->disconnect) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS | +				    IWL_WOWLAN_WAKEUP_LINK_CHANGE); +	if (wowlan->magic_pkt) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_MAGIC_PACKET); +	if (wowlan->gtk_rekey_failure) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_GTK_REKEY_FAIL); +	if (wowlan->eap_identity_req) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_EAP_IDENT_REQ); +	if (wowlan->four_way_handshake) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_4WAY_HANDSHAKE); +	if (wowlan->n_patterns) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_PATTERN_MATCH); + +	if (wowlan->rfkill_release) +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_RF_KILL_DEASSERT); + +	if (wowlan->any) { +		wowlan_config_cmd->wakeup_filter |= +			cpu_to_le32(IWL_WOWLAN_WAKEUP_BEACON_MISS | +				    IWL_WOWLAN_WAKEUP_LINK_CHANGE | +				    IWL_WOWLAN_WAKEUP_RX_FRAME | +				    IWL_WOWLAN_WAKEUP_BCN_FILTERING); +	} +} + +static int iwl_mld_send_patterns(struct iwl_mld *mld, +				 struct cfg80211_wowlan *wowlan, +				 int ap_sta_id) +{ +	struct iwl_wowlan_patterns_cmd *pattern_cmd; +	struct iwl_host_cmd cmd = { +		.id = WOWLAN_PATTERNS, +		.dataflags[0] = IWL_HCMD_DFL_NOCOPY, +	}; +	int ret; + +	if (!wowlan->n_patterns) +		return 0; + +	cmd.len[0] = struct_size(pattern_cmd, patterns, wowlan->n_patterns); + +	pattern_cmd = kzalloc(cmd.len[0], GFP_KERNEL); +	if (!pattern_cmd) +		return -ENOMEM; + +	pattern_cmd->n_patterns = wowlan->n_patterns; +	pattern_cmd->sta_id = ap_sta_id; + +	for (int i = 0; i < wowlan->n_patterns; i++) { +		int mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); + +		pattern_cmd->patterns[i].pattern_type = +			WOWLAN_PATTERN_TYPE_BITMASK; + +		memcpy(&pattern_cmd->patterns[i].u.bitmask.mask, +		       wowlan->patterns[i].mask, mask_len); +		memcpy(&pattern_cmd->patterns[i].u.bitmask.pattern, +		       wowlan->patterns[i].pattern, +		       wowlan->patterns[i].pattern_len); +		pattern_cmd->patterns[i].u.bitmask.mask_size = mask_len; +		pattern_cmd->patterns[i].u.bitmask.pattern_size = +			wowlan->patterns[i].pattern_len; +	} + +	cmd.data[0] = pattern_cmd; +	ret = iwl_mld_send_cmd(mld, &cmd); +	kfree(pattern_cmd); +	return ret; +} + +static int +iwl_mld_send_proto_offload(struct iwl_mld *mld, +			   struct ieee80211_vif *vif, +			   u8 ap_sta_id) +{ +	struct iwl_proto_offload_cmd_v4 *cmd __free(kfree); +	struct iwl_host_cmd hcmd = { +		.id = PROT_OFFLOAD_CONFIG_CMD, +		.dataflags[0] = IWL_HCMD_DFL_NOCOPY, +		.len[0] = sizeof(*cmd), +	}; +	u32 enabled = 0; + +	cmd = kzalloc(hcmd.len[0], GFP_KERNEL); + +#if IS_ENABLED(CONFIG_IPV6) +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif); +	struct iwl_mld_wowlan_data *wowlan_data = &mld_vif->wowlan_data; +	struct iwl_ns_config *nsc; +	struct iwl_targ_addr *addrs; +	int n_nsc, n_addrs; +	int i, c; +	int num_skipped = 0; + +	nsc = cmd->ns_config; +	n_nsc = IWL_PROTO_OFFLOAD_NUM_NS_CONFIG_V3L; +	addrs = cmd->targ_addrs; +	n_addrs = IWL_PROTO_OFFLOAD_NUM_IPV6_ADDRS_V3L; + +	/* For each address we have (and that will fit) fill a target +	 * address struct and combine for NS offload structs with the +	 * solicited node addresses. +	 */ +	for (i = 0, c = 0; +		i < wowlan_data->num_target_ipv6_addrs && +		i < n_addrs && c < n_nsc; i++) { +		int j; +		struct in6_addr solicited_addr; + +		/* Because ns is offloaded skip tentative address to avoid +		 * violating RFC4862. +		 */ +		if (test_bit(i, wowlan_data->tentative_addrs)) { +			num_skipped++; +			continue; +		} + +		addrconf_addr_solict_mult(&wowlan_data->target_ipv6_addrs[i], +					  &solicited_addr); +		for (j = 0; j < n_nsc && j < c; j++) +			if (ipv6_addr_cmp(&nsc[j].dest_ipv6_addr, +					  &solicited_addr) == 0) +				break; +		if (j == c) +			c++; +		addrs[i].addr = wowlan_data->target_ipv6_addrs[i]; +		addrs[i].config_num = cpu_to_le32(j); +		nsc[j].dest_ipv6_addr = solicited_addr; +		memcpy(nsc[j].target_mac_addr, vif->addr, ETH_ALEN); +	} + +	if (wowlan_data->num_target_ipv6_addrs - num_skipped) +		enabled |= IWL_D3_PROTO_IPV6_VALID; + +	cmd->num_valid_ipv6_addrs = cpu_to_le32(i - num_skipped); +	if (enabled & IWL_D3_PROTO_IPV6_VALID) +		enabled |= IWL_D3_PROTO_OFFLOAD_NS; +#endif + +	if (vif->cfg.arp_addr_cnt) { +		enabled |= IWL_D3_PROTO_OFFLOAD_ARP | IWL_D3_PROTO_IPV4_VALID; +		cmd->common.host_ipv4_addr = vif->cfg.arp_addr_list[0]; +		ether_addr_copy(cmd->common.arp_mac_addr, vif->addr); +	} + +	enabled |= IWL_D3_PROTO_OFFLOAD_BTM; +	cmd->common.enabled = cpu_to_le32(enabled); +	cmd->sta_id = cpu_to_le32(ap_sta_id); +	hcmd.data[0] = cmd; +	return iwl_mld_send_cmd(mld, &hcmd); +} + +static int +iwl_mld_wowlan_config(struct iwl_mld *mld, struct ieee80211_vif *bss_vif, +		      struct cfg80211_wowlan *wowlan) +{ +	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_vif); +	struct ieee80211_sta *ap_sta = mld_vif->ap_sta; +	struct iwl_wowlan_config_cmd wowlan_config_cmd = { +			.offloading_tid = IWL_WOWLAN_OFFLOAD_TID, +	}; +	u32 sta_id_mask; +	int ap_sta_id, ret; +	int link_id = iwl_mld_get_primary_link(bss_vif); +	struct ieee80211_bss_conf *link_conf; + +	ret = iwl_mld_block_emlsr_sync(mld, bss_vif, +				       IWL_MLD_EMLSR_BLOCKED_WOWLAN, link_id); +	if (ret) +		return ret; + +	link_conf = link_conf_dereference_protected(bss_vif, link_id); + +	if (WARN_ON(!ap_sta || !link_conf)) +		return -EINVAL; + +	sta_id_mask = iwl_mld_fw_sta_id_mask(mld, ap_sta); +	if (WARN_ON(hweight32(sta_id_mask) != 1)) +		return -EINVAL; + +	ap_sta_id = __ffs(sta_id_mask); +	wowlan_config_cmd.sta_id = ap_sta_id; + +	ret = iwl_mld_ensure_queue(mld, +				   ap_sta->txq[wowlan_config_cmd.offloading_tid]); +	if (ret) +		return ret; + +	iwl_mld_set_wowlan_config_cmd(mld, wowlan, +				      &wowlan_config_cmd, ap_sta); +	ret = iwl_mld_send_cmd_pdu(mld, WOWLAN_CONFIGURATION, +				   &wowlan_config_cmd); +	if (ret) +		return ret; + +	ret = iwl_mld_suspend_send_security_cmds(mld, bss_vif, mld_vif, +						 ap_sta_id); +	if (ret) +		return ret; + +	ret = iwl_mld_send_patterns(mld, wowlan, ap_sta_id); +	if (ret) +		return ret; + +	ret = iwl_mld_send_proto_offload(mld, bss_vif, ap_sta_id); +	if (ret) +		return ret; + +	iwl_mld_enable_beacon_filter(mld, link_conf, true); +	return iwl_mld_update_mac_power(mld, bss_vif, true); +} + +int iwl_mld_wowlan_suspend(struct iwl_mld *mld, struct cfg80211_wowlan *wowlan) +{ +	struct ieee80211_vif *bss_vif; + +	lockdep_assert_wiphy(mld->wiphy); + +	if (WARN_ON(!wowlan)) +		return 1; + +	IWL_DEBUG_WOWLAN(mld, "Starting the wowlan suspend flow\n"); + +	bss_vif = iwl_mld_get_bss_vif(mld); +	if (WARN_ON(!bss_vif)) +		return 1; + +	if (!bss_vif->cfg.assoc) { +		int ret; +		/* If we're not associated, this must be netdetect */ +		if (WARN_ON(!wowlan->nd_config)) +			return 1; + +		ret = iwl_mld_netdetect_config(mld, bss_vif, wowlan); +		if (!ret) +			mld->netdetect = true; + +		return ret; +	} + +	return iwl_mld_wowlan_config(mld, bss_vif, wowlan); +} + +/* Returns 0 on success, 1 if an error occurred in firmware during d3, + * A negative value is expected only in unrecovreable cases. + */ +int iwl_mld_wowlan_resume(struct iwl_mld *mld) +{ +	struct ieee80211_vif *bss_vif; +	struct ieee80211_bss_conf *link_conf; +	struct iwl_mld_netdetect_res netdetect_res; +	struct iwl_mld_resume_data resume_data = { +		.notifs_expected = +			IWL_D3_NOTIF_WOWLAN_INFO | +			IWL_D3_NOTIF_D3_END_NOTIF, +		.netdetect_res = &netdetect_res, +	}; +	int link_id; +	int ret; +	bool fw_err = false; + +	lockdep_assert_wiphy(mld->wiphy); + +	IWL_DEBUG_WOWLAN(mld, "Starting the wowlan resume flow\n"); + +	if (!mld->fw_status.in_d3) { +		IWL_DEBUG_WOWLAN(mld, +				 "Device_powered_off() was called during wowlan\n"); +		goto err; +	} + +	mld->fw_status.resuming = true; +	mld->fw_status.in_d3 = false; +	mld->scan.last_start_time_jiffies = jiffies; + +	bss_vif = iwl_mld_get_bss_vif(mld); +	if (WARN_ON(!bss_vif)) +		goto err; + +	/* We can't have several links upon wowlan entry, +	 * this is enforced in the suspend flow. +	 */ +	WARN_ON(hweight16(bss_vif->active_links) > 1); +	link_id = bss_vif->active_links ? __ffs(bss_vif->active_links) : 0; +	link_conf = link_conf_dereference_protected(bss_vif, link_id); + +	if (WARN_ON(!link_conf)) +		goto err; + +	iwl_fw_dbg_read_d3_debug_data(&mld->fwrt); + +	resume_data.wowlan_status = kzalloc(sizeof(*resume_data.wowlan_status), +					    GFP_KERNEL); +	if (!resume_data.wowlan_status) +		return -ENOMEM; + +	if (mld->netdetect) +		resume_data.notifs_expected |= IWL_D3_ND_MATCH_INFO; + +	ret = iwl_mld_wait_d3_notif(mld, &resume_data, true); +	if (ret) { +		IWL_ERR(mld, "Couldn't get the d3 notifs %d\n", ret); +		fw_err = true; +		goto err; +	} + +	if (resume_data.d3_end_flags & IWL_D0I3_RESET_REQUIRE) { +		mld->fw_status.in_hw_restart = true; +		goto process_wakeup_results; +	} + +	iwl_mld_update_changed_regdomain(mld); +	iwl_mld_update_mac_power(mld, bss_vif, false); +	iwl_mld_enable_beacon_filter(mld, link_conf, false); +	iwl_mld_update_device_power(mld, false); + +	if (mld->netdetect) +		ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_NETDETECT, false); + + process_wakeup_results: +	if (mld->netdetect) { +		iwl_mld_process_netdetect_res(mld, bss_vif, &resume_data); +		mld->netdetect = false; +	} else { +		bool keep_connection = +			iwl_mld_process_wowlan_status(mld, bss_vif, +						      resume_data.wowlan_status); + +		/* EMLSR state will be cleared if the connection is not kept */ +		if (keep_connection) +			iwl_mld_unblock_emlsr(mld, bss_vif, +					      IWL_MLD_EMLSR_BLOCKED_WOWLAN); +		else +			ieee80211_resume_disconnect(bss_vif); +	} + +	goto out; + + err: +	if (fw_err) { +		mld->trans->state = IWL_TRANS_NO_FW; +		set_bit(STATUS_FW_ERROR, &mld->trans->status); +	} + +	mld->fw_status.in_hw_restart = true; +	ret = 1; + out: +	mld->fw_status.resuming = false; + +	if (resume_data.wowlan_status) { +		kfree(resume_data.wowlan_status->wake_packet); +		kfree(resume_data.wowlan_status); +	} + +	return ret; +} | 
