xref: /linux/drivers/net/wireless/intel/iwlwifi/mvm/link.c (revision 9410645520e9b820069761f3450ef6661418e279)
155eb1c5fSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
255eb1c5fSMiri Korenblit /*
3b82730bfSJohannes Berg  * Copyright (C) 2022 - 2024 Intel Corporation
455eb1c5fSMiri Korenblit  */
555eb1c5fSMiri Korenblit #include "mvm.h"
6bf976c81SJohannes Berg #include "time-event.h"
755eb1c5fSMiri Korenblit 
8966a4d9bSDaniel Gabay #define HANDLE_ESR_REASONS(HOW)		\
9966a4d9bSDaniel Gabay 	HOW(BLOCKED_PREVENTION)		\
10966a4d9bSDaniel Gabay 	HOW(BLOCKED_WOWLAN)		\
11966a4d9bSDaniel Gabay 	HOW(BLOCKED_TPT)		\
12966a4d9bSDaniel Gabay 	HOW(BLOCKED_FW)			\
13966a4d9bSDaniel Gabay 	HOW(BLOCKED_NON_BSS)		\
145fdbde79SJohannes Berg 	HOW(BLOCKED_ROC)		\
15966a4d9bSDaniel Gabay 	HOW(EXIT_MISSED_BEACON)		\
16966a4d9bSDaniel Gabay 	HOW(EXIT_LOW_RSSI)		\
17966a4d9bSDaniel Gabay 	HOW(EXIT_COEX)			\
18966a4d9bSDaniel Gabay 	HOW(EXIT_BANDWIDTH)		\
19df966c93SMiri Korenblit 	HOW(EXIT_CSA)			\
20a08cf019SEmmanuel Grumbach 	HOW(EXIT_LINK_USAGE)		\
21a08cf019SEmmanuel Grumbach 	HOW(EXIT_FAIL_ENTRY)
22966a4d9bSDaniel Gabay 
23966a4d9bSDaniel Gabay static const char *const iwl_mvm_esr_states_names[] = {
24966a4d9bSDaniel Gabay #define NAME_ENTRY(x) [ilog2(IWL_MVM_ESR_##x)] = #x,
25966a4d9bSDaniel Gabay 	HANDLE_ESR_REASONS(NAME_ENTRY)
26966a4d9bSDaniel Gabay };
27966a4d9bSDaniel Gabay 
iwl_get_esr_state_string(enum iwl_mvm_esr_state state)28ff907d97SYedidya Benshimol const char *iwl_get_esr_state_string(enum iwl_mvm_esr_state state)
29966a4d9bSDaniel Gabay {
30966a4d9bSDaniel Gabay 	int offs = ilog2(state);
31966a4d9bSDaniel Gabay 
32966a4d9bSDaniel Gabay 	if (offs >= ARRAY_SIZE(iwl_mvm_esr_states_names) ||
33966a4d9bSDaniel Gabay 	    !iwl_mvm_esr_states_names[offs])
34966a4d9bSDaniel Gabay 		return "UNKNOWN";
35966a4d9bSDaniel Gabay 
36966a4d9bSDaniel Gabay 	return iwl_mvm_esr_states_names[offs];
37966a4d9bSDaniel Gabay }
38966a4d9bSDaniel Gabay 
iwl_mvm_print_esr_state(struct iwl_mvm * mvm,u32 mask)3905f10dadSYedidya Benshimol static void iwl_mvm_print_esr_state(struct iwl_mvm *mvm, u32 mask)
4005f10dadSYedidya Benshimol {
4105f10dadSYedidya Benshimol #define NAME_FMT(x) "%s"
4205f10dadSYedidya Benshimol #define NAME_PR(x) (mask & IWL_MVM_ESR_##x) ? "[" #x "]" : "",
4305f10dadSYedidya Benshimol 	IWL_DEBUG_INFO(mvm,
4405f10dadSYedidya Benshimol 		       "EMLSR state = " HANDLE_ESR_REASONS(NAME_FMT)
4505f10dadSYedidya Benshimol 		       " (0x%x)\n",
4605f10dadSYedidya Benshimol 		       HANDLE_ESR_REASONS(NAME_PR)
4705f10dadSYedidya Benshimol 		       mask);
4805f10dadSYedidya Benshimol #undef NAME_FMT
4905f10dadSYedidya Benshimol #undef NAME_PR
5005f10dadSYedidya Benshimol }
5105f10dadSYedidya Benshimol 
iwl_mvm_get_free_fw_link_id(struct iwl_mvm * mvm,struct iwl_mvm_vif * mvm_vif)52d6f6b0d8SGregory Greenman static u32 iwl_mvm_get_free_fw_link_id(struct iwl_mvm *mvm,
53d6f6b0d8SGregory Greenman 				       struct iwl_mvm_vif *mvm_vif)
54d6f6b0d8SGregory Greenman {
55ed93faf0SJohannes Berg 	u32 i;
56d6f6b0d8SGregory Greenman 
57d6f6b0d8SGregory Greenman 	lockdep_assert_held(&mvm->mutex);
58d6f6b0d8SGregory Greenman 
59ed93faf0SJohannes Berg 	for (i = 0; i < ARRAY_SIZE(mvm->link_id_to_link_conf); i++)
60ed93faf0SJohannes Berg 		if (!rcu_access_pointer(mvm->link_id_to_link_conf[i]))
61ed93faf0SJohannes Berg 			return i;
62d6f6b0d8SGregory Greenman 
63d6f6b0d8SGregory Greenman 	return IWL_MVM_FW_LINK_ID_INVALID;
64d6f6b0d8SGregory Greenman }
65d6f6b0d8SGregory Greenman 
iwl_mvm_link_cmd_send(struct iwl_mvm * mvm,struct iwl_link_config_cmd * cmd,enum iwl_ctxt_action action)6655eb1c5fSMiri Korenblit static int iwl_mvm_link_cmd_send(struct iwl_mvm *mvm,
6755eb1c5fSMiri Korenblit 				 struct iwl_link_config_cmd *cmd,
6855eb1c5fSMiri Korenblit 				 enum iwl_ctxt_action action)
6955eb1c5fSMiri Korenblit {
7055eb1c5fSMiri Korenblit 	int ret;
7155eb1c5fSMiri Korenblit 
7255eb1c5fSMiri Korenblit 	cmd->action = cpu_to_le32(action);
7355eb1c5fSMiri Korenblit 	ret = iwl_mvm_send_cmd_pdu(mvm,
7455eb1c5fSMiri Korenblit 				   WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD), 0,
7555eb1c5fSMiri Korenblit 				   sizeof(*cmd), cmd);
7655eb1c5fSMiri Korenblit 	if (ret)
7755eb1c5fSMiri Korenblit 		IWL_ERR(mvm, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n",
7855eb1c5fSMiri Korenblit 			action, ret);
7955eb1c5fSMiri Korenblit 	return ret;
8055eb1c5fSMiri Korenblit }
8155eb1c5fSMiri Korenblit 
iwl_mvm_set_link_mapping(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf)82a8b5d480SIlan Peer int iwl_mvm_set_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
83a8b5d480SIlan Peer 			     struct ieee80211_bss_conf *link_conf)
84a8b5d480SIlan Peer {
85a8b5d480SIlan Peer 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
86a8b5d480SIlan Peer 	struct iwl_mvm_vif_link_info *link_info =
87a8b5d480SIlan Peer 		mvmvif->link[link_conf->link_id];
88a8b5d480SIlan Peer 
89a8b5d480SIlan Peer 	if (link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID) {
90a8b5d480SIlan Peer 		link_info->fw_link_id = iwl_mvm_get_free_fw_link_id(mvm,
91a8b5d480SIlan Peer 								    mvmvif);
92a8b5d480SIlan Peer 		if (link_info->fw_link_id >=
93a8b5d480SIlan Peer 		    ARRAY_SIZE(mvm->link_id_to_link_conf))
94a8b5d480SIlan Peer 			return -EINVAL;
95a8b5d480SIlan Peer 
96a8b5d480SIlan Peer 		rcu_assign_pointer(mvm->link_id_to_link_conf[link_info->fw_link_id],
97a8b5d480SIlan Peer 				   link_conf);
98a8b5d480SIlan Peer 	}
99a8b5d480SIlan Peer 
100a8b5d480SIlan Peer 	return 0;
101a8b5d480SIlan Peer }
102a8b5d480SIlan Peer 
iwl_mvm_add_link(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf)103cacc1d42SGregory Greenman int iwl_mvm_add_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
104cacc1d42SGregory Greenman 		     struct ieee80211_bss_conf *link_conf)
10555eb1c5fSMiri Korenblit {
10655eb1c5fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
107cacc1d42SGregory Greenman 	unsigned int link_id = link_conf->link_id;
108d6f6b0d8SGregory Greenman 	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];
10955eb1c5fSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
1104dde4ff0SShaul Triebitz 	unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);
1114dde4ff0SShaul Triebitz 	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);
112a8b5d480SIlan Peer 	int ret;
113cacc1d42SGregory Greenman 
114d6f6b0d8SGregory Greenman 	if (WARN_ON_ONCE(!link_info))
115cacc1d42SGregory Greenman 		return -EINVAL;
11655eb1c5fSMiri Korenblit 
117a8b5d480SIlan Peer 	ret = iwl_mvm_set_link_mapping(mvm, vif, link_conf);
118a8b5d480SIlan Peer 	if (ret)
119a8b5d480SIlan Peer 		return ret;
120d6f6b0d8SGregory Greenman 
12155eb1c5fSMiri Korenblit 	/* Update SF - Disable if needed. if this fails, SF might still be on
12255eb1c5fSMiri Korenblit 	 * while many macs are bound, which is forbidden - so fail the binding.
12355eb1c5fSMiri Korenblit 	 */
12455eb1c5fSMiri Korenblit 	if (iwl_mvm_sf_update(mvm, vif, false))
12555eb1c5fSMiri Korenblit 		return -EINVAL;
12655eb1c5fSMiri Korenblit 
127d6f6b0d8SGregory Greenman 	cmd.link_id = cpu_to_le32(link_info->fw_link_id);
12855eb1c5fSMiri Korenblit 	cmd.mac_id = cpu_to_le32(mvmvif->id);
1293f302269SEmmanuel Grumbach 	cmd.spec_link_id = link_conf->link_id;
13084ef7cbeSIlan Peer 	WARN_ON_ONCE(link_info->phy_ctxt);
131cb145863SJohannes Berg 	cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);
13255eb1c5fSMiri Korenblit 
133cacc1d42SGregory Greenman 	memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);
13455eb1c5fSMiri Korenblit 
135cacc1d42SGregory Greenman 	if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)
136cacc1d42SGregory Greenman 		memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);
1376b5a87dfSMiri Korenblit 
1384dde4ff0SShaul Triebitz 	if (cmd_ver < 2)
13912bacfc2SMiri Korenblit 		cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);
14012bacfc2SMiri Korenblit 
14155eb1c5fSMiri Korenblit 	return iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_ADD);
14255eb1c5fSMiri Korenblit }
14355eb1c5fSMiri Korenblit 
144a1efeb82SYedidya Benshimol struct iwl_mvm_esr_iter_data {
145a1efeb82SYedidya Benshimol 	struct ieee80211_vif *vif;
146a1efeb82SYedidya Benshimol 	unsigned int link_id;
147a1efeb82SYedidya Benshimol 	bool lift_block;
148a1efeb82SYedidya Benshimol };
149a1efeb82SYedidya Benshimol 
iwl_mvm_esr_vif_iterator(void * _data,u8 * mac,struct ieee80211_vif * vif)150a1efeb82SYedidya Benshimol static void iwl_mvm_esr_vif_iterator(void *_data, u8 *mac,
151a1efeb82SYedidya Benshimol 				     struct ieee80211_vif *vif)
152a1efeb82SYedidya Benshimol {
153a1efeb82SYedidya Benshimol 	struct iwl_mvm_esr_iter_data *data = _data;
154a1efeb82SYedidya Benshimol 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
155a1efeb82SYedidya Benshimol 	int link_id;
156a1efeb82SYedidya Benshimol 
157a1efeb82SYedidya Benshimol 	if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION)
158a1efeb82SYedidya Benshimol 		return;
159a1efeb82SYedidya Benshimol 
160a1efeb82SYedidya Benshimol 	for_each_mvm_vif_valid_link(mvmvif, link_id) {
161a1efeb82SYedidya Benshimol 		struct iwl_mvm_vif_link_info *link_info =
162a1efeb82SYedidya Benshimol 			mvmvif->link[link_id];
163a1efeb82SYedidya Benshimol 		if (vif == data->vif && link_id == data->link_id)
164a1efeb82SYedidya Benshimol 			continue;
165a1efeb82SYedidya Benshimol 		if (link_info->active)
166a1efeb82SYedidya Benshimol 			data->lift_block = false;
167a1efeb82SYedidya Benshimol 	}
168a1efeb82SYedidya Benshimol }
169a1efeb82SYedidya Benshimol 
iwl_mvm_esr_non_bss_link(struct iwl_mvm * mvm,struct ieee80211_vif * vif,unsigned int link_id,bool active)170a1efeb82SYedidya Benshimol int iwl_mvm_esr_non_bss_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
171a1efeb82SYedidya Benshimol 			     unsigned int link_id, bool active)
172a1efeb82SYedidya Benshimol {
173a1efeb82SYedidya Benshimol 	/* An active link of a non-station vif blocks EMLSR. Upon activation
174a1efeb82SYedidya Benshimol 	 * block EMLSR on the bss vif. Upon deactivation, check if this link
175a1efeb82SYedidya Benshimol 	 * was the last non-station link active, and if so unblock the bss vif
176a1efeb82SYedidya Benshimol 	 */
177a1efeb82SYedidya Benshimol 	struct ieee80211_vif *bss_vif = iwl_mvm_get_bss_vif(mvm);
178a1efeb82SYedidya Benshimol 	struct iwl_mvm_esr_iter_data data = {
179a1efeb82SYedidya Benshimol 		.vif = vif,
180a1efeb82SYedidya Benshimol 		.link_id = link_id,
181a1efeb82SYedidya Benshimol 		.lift_block = true,
182a1efeb82SYedidya Benshimol 	};
183a1efeb82SYedidya Benshimol 
184a1efeb82SYedidya Benshimol 	if (IS_ERR_OR_NULL(bss_vif))
185a1efeb82SYedidya Benshimol 		return 0;
186a1efeb82SYedidya Benshimol 
187a1efeb82SYedidya Benshimol 	if (active)
188a1efeb82SYedidya Benshimol 		return iwl_mvm_block_esr_sync(mvm, bss_vif,
189a1efeb82SYedidya Benshimol 					      IWL_MVM_ESR_BLOCKED_NON_BSS);
190a1efeb82SYedidya Benshimol 
191a1efeb82SYedidya Benshimol 	ieee80211_iterate_active_interfaces(mvm->hw,
192a1efeb82SYedidya Benshimol 					    IEEE80211_IFACE_ITER_NORMAL,
193a1efeb82SYedidya Benshimol 					    iwl_mvm_esr_vif_iterator, &data);
194a1efeb82SYedidya Benshimol 	if (data.lift_block) {
195a1efeb82SYedidya Benshimol 		mutex_lock(&mvm->mutex);
196a1efeb82SYedidya Benshimol 		iwl_mvm_unblock_esr(mvm, bss_vif, IWL_MVM_ESR_BLOCKED_NON_BSS);
197a1efeb82SYedidya Benshimol 		mutex_unlock(&mvm->mutex);
198a1efeb82SYedidya Benshimol 	}
199a1efeb82SYedidya Benshimol 
200a1efeb82SYedidya Benshimol 	return 0;
201a1efeb82SYedidya Benshimol }
202a1efeb82SYedidya Benshimol 
iwl_mvm_link_changed(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf,u32 changes,bool active)20355eb1c5fSMiri Korenblit int iwl_mvm_link_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
204cacc1d42SGregory Greenman 			 struct ieee80211_bss_conf *link_conf,
20555eb1c5fSMiri Korenblit 			 u32 changes, bool active)
20655eb1c5fSMiri Korenblit {
20755eb1c5fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
208cacc1d42SGregory Greenman 	unsigned int link_id = link_conf->link_id;
209d6f6b0d8SGregory Greenman 	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];
210cacc1d42SGregory Greenman 	struct iwl_mvm_phy_ctxt *phyctxt;
21155eb1c5fSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
21255eb1c5fSMiri Korenblit 	u32 ht_flag, flags = 0, flags_mask = 0;
213d6f6b0d8SGregory Greenman 	int ret;
2144dde4ff0SShaul Triebitz 	unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);
2154dde4ff0SShaul Triebitz 	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);
21655eb1c5fSMiri Korenblit 
217d6f6b0d8SGregory Greenman 	if (WARN_ON_ONCE(!link_info ||
218d6f6b0d8SGregory Greenman 			 link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID))
219cacc1d42SGregory Greenman 		return -EINVAL;
220cacc1d42SGregory Greenman 
221bf976c81SJohannes Berg 	if (changes & LINK_CONTEXT_MODIFY_ACTIVE) {
222bf976c81SJohannes Berg 		/* When activating a link, phy context should be valid;
223bf976c81SJohannes Berg 		 * when deactivating a link, it also should be valid since
224bf976c81SJohannes Berg 		 * the link was active before. So, do nothing in this case.
225bf976c81SJohannes Berg 		 * Since a link is added first with FW_CTXT_INVALID, then we
226bf976c81SJohannes Berg 		 * can get here in case it's removed before it was activated.
227bf976c81SJohannes Berg 		 */
228bf976c81SJohannes Berg 		if (!link_info->phy_ctxt)
229bf976c81SJohannes Berg 			return 0;
230bf976c81SJohannes Berg 
231bf976c81SJohannes Berg 		/* Catch early if driver tries to activate or deactivate a link
232bf976c81SJohannes Berg 		 * twice.
233bf976c81SJohannes Berg 		 */
234bf976c81SJohannes Berg 		WARN_ON_ONCE(active == link_info->active);
235bf976c81SJohannes Berg 
236bf976c81SJohannes Berg 		/* When deactivating a link session protection should
23707fb5378SEmmanuel Grumbach 		 * be stopped. Also let the firmware know if we can't Tx.
238bf976c81SJohannes Berg 		 */
23907fb5378SEmmanuel Grumbach 		if (!active && vif->type == NL80211_IFTYPE_STATION) {
240bf976c81SJohannes Berg 			iwl_mvm_stop_session_protection(mvm, vif);
24107fb5378SEmmanuel Grumbach 			if (link_info->csa_block_tx) {
24207fb5378SEmmanuel Grumbach 				cmd.block_tx = 1;
24307fb5378SEmmanuel Grumbach 				link_info->csa_block_tx = false;
24407fb5378SEmmanuel Grumbach 			}
24507fb5378SEmmanuel Grumbach 		}
246bf976c81SJohannes Berg 	}
247bf976c81SJohannes Berg 
248d6f6b0d8SGregory Greenman 	cmd.link_id = cpu_to_le32(link_info->fw_link_id);
24955eb1c5fSMiri Korenblit 
25055eb1c5fSMiri Korenblit 	/* The phy_id, link address and listen_lmac can be modified only until
25155eb1c5fSMiri Korenblit 	 * the link becomes active, otherwise they will be ignored.
25255eb1c5fSMiri Korenblit 	 */
253d6f6b0d8SGregory Greenman 	phyctxt = link_info->phy_ctxt;
254cb145863SJohannes Berg 	if (phyctxt)
25555eb1c5fSMiri Korenblit 		cmd.phy_id = cpu_to_le32(phyctxt->id);
256cb145863SJohannes Berg 	else
257cb145863SJohannes Berg 		cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);
25855eb1c5fSMiri Korenblit 	cmd.mac_id = cpu_to_le32(mvmvif->id);
25955eb1c5fSMiri Korenblit 
260cacc1d42SGregory Greenman 	memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);
26155eb1c5fSMiri Korenblit 
26255eb1c5fSMiri Korenblit 	cmd.active = cpu_to_le32(active);
26355eb1c5fSMiri Korenblit 
264cacc1d42SGregory Greenman 	if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)
265cacc1d42SGregory Greenman 		memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);
2666b5a87dfSMiri Korenblit 
267*a032b5fcSMiri Korenblit 	iwl_mvm_set_fw_basic_rates(mvm, vif, link_info,
268cacc1d42SGregory Greenman 				   &cmd.cck_rates, &cmd.ofdm_rates);
26955eb1c5fSMiri Korenblit 
270cacc1d42SGregory Greenman 	cmd.cck_short_preamble = cpu_to_le32(link_conf->use_short_preamble);
271cacc1d42SGregory Greenman 	cmd.short_slot = cpu_to_le32(link_conf->use_short_slot);
27255eb1c5fSMiri Korenblit 
27355eb1c5fSMiri Korenblit 	/* The fw does not distinguish between ht and fat */
27455eb1c5fSMiri Korenblit 	ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT;
275cacc1d42SGregory Greenman 	iwl_mvm_set_fw_protection_flags(mvm, vif, link_conf,
276cacc1d42SGregory Greenman 					&cmd.protection_flags,
27755eb1c5fSMiri Korenblit 					ht_flag, LINK_PROT_FLG_TGG_PROTECT);
27855eb1c5fSMiri Korenblit 
27977e1f3f3SJohannes Berg 	iwl_mvm_set_fw_qos_params(mvm, vif, link_conf, cmd.ac,
280cacc1d42SGregory Greenman 				  &cmd.qos_flags);
28155eb1c5fSMiri Korenblit 
28255eb1c5fSMiri Korenblit 
283cacc1d42SGregory Greenman 	cmd.bi = cpu_to_le32(link_conf->beacon_int);
284cacc1d42SGregory Greenman 	cmd.dtim_interval = cpu_to_le32(link_conf->beacon_int *
285cacc1d42SGregory Greenman 					link_conf->dtim_period);
28655eb1c5fSMiri Korenblit 
287cacc1d42SGregory Greenman 	if (!link_conf->he_support || iwlwifi_mod_params.disable_11ax ||
28822c58834SGregory Greenman 	    (vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) {
28955eb1c5fSMiri Korenblit 		changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS;
29055eb1c5fSMiri Korenblit 		goto send_cmd;
29155eb1c5fSMiri Korenblit 	}
29255eb1c5fSMiri Korenblit 
293cacc1d42SGregory Greenman 	cmd.htc_trig_based_pkt_ext = link_conf->htc_trig_based_pkt_ext;
29455eb1c5fSMiri Korenblit 
295cacc1d42SGregory Greenman 	if (link_conf->uora_exists) {
29655eb1c5fSMiri Korenblit 		cmd.rand_alloc_ecwmin =
297cacc1d42SGregory Greenman 			link_conf->uora_ocw_range & 0x7;
29855eb1c5fSMiri Korenblit 		cmd.rand_alloc_ecwmax =
299cacc1d42SGregory Greenman 			(link_conf->uora_ocw_range >> 3) & 0x7;
30055eb1c5fSMiri Korenblit 	}
30155eb1c5fSMiri Korenblit 
3026d1b52ccSEmmanuel Grumbach 	/* ap_sta may be NULL if we're disconnecting */
3036d1b52ccSEmmanuel Grumbach 	if (changes & LINK_CONTEXT_MODIFY_HE_PARAMS && mvmvif->ap_sta) {
3046d1b52ccSEmmanuel Grumbach 		struct ieee80211_link_sta *link_sta =
3056d1b52ccSEmmanuel Grumbach 			link_sta_dereference_check(mvmvif->ap_sta, link_id);
3066d1b52ccSEmmanuel Grumbach 
3076d1b52ccSEmmanuel Grumbach 		if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&
3086d1b52ccSEmmanuel Grumbach 		    link_sta->he_cap.he_cap_elem.mac_cap_info[5] &
3096d1b52ccSEmmanuel Grumbach 		    IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)
3106d1b52ccSEmmanuel Grumbach 			cmd.ul_mu_data_disable = 1;
3116d1b52ccSEmmanuel Grumbach 	}
3126d1b52ccSEmmanuel Grumbach 
31355eb1c5fSMiri Korenblit 	/* TODO  how to set ndp_fdbk_buff_th_exp? */
31455eb1c5fSMiri Korenblit 
315e119e740SEmmanuel Grumbach 	if (iwl_mvm_set_fw_mu_edca_params(mvm, mvmvif->link[link_id],
31655eb1c5fSMiri Korenblit 					  &cmd.trig_based_txf[0])) {
31755eb1c5fSMiri Korenblit 		flags |= LINK_FLG_MU_EDCA_CW;
31855eb1c5fSMiri Korenblit 		flags_mask |= LINK_FLG_MU_EDCA_CW;
31955eb1c5fSMiri Korenblit 	}
32055eb1c5fSMiri Korenblit 
32135b9281fSIlan Peer 	if (changes & LINK_CONTEXT_MODIFY_EHT_PARAMS) {
322b82730bfSJohannes Berg 		struct ieee80211_chanctx_conf *ctx;
323b82730bfSJohannes Berg 		struct cfg80211_chan_def *def = NULL;
324b82730bfSJohannes Berg 
325b82730bfSJohannes Berg 		rcu_read_lock();
326b82730bfSJohannes Berg 		ctx = rcu_dereference(link_conf->chanctx_conf);
327b82730bfSJohannes Berg 		if (ctx)
328b82730bfSJohannes Berg 			def = iwl_mvm_chanctx_def(mvm, ctx);
329b82730bfSJohannes Berg 
33035b9281fSIlan Peer 		if (iwlwifi_mod_params.disable_11be ||
3319a43c190SJohannes Berg 		    !link_conf->eht_support || !def ||
3329a43c190SJohannes Berg 		    iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) >= 6)
33355eb1c5fSMiri Korenblit 			changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;
33435b9281fSIlan Peer 		else
335b82730bfSJohannes Berg 			cmd.puncture_mask = cpu_to_le16(def->punctured);
336b82730bfSJohannes Berg 		rcu_read_unlock();
33735b9281fSIlan Peer 	}
33855eb1c5fSMiri Korenblit 
339cacc1d42SGregory Greenman 	cmd.bss_color = link_conf->he_bss_color.color;
34055eb1c5fSMiri Korenblit 
341cacc1d42SGregory Greenman 	if (!link_conf->he_bss_color.enabled) {
34255eb1c5fSMiri Korenblit 		flags |= LINK_FLG_BSS_COLOR_DIS;
34355eb1c5fSMiri Korenblit 		flags_mask |= LINK_FLG_BSS_COLOR_DIS;
34455eb1c5fSMiri Korenblit 	}
34555eb1c5fSMiri Korenblit 
346cacc1d42SGregory Greenman 	cmd.frame_time_rts_th = cpu_to_le16(link_conf->frame_time_rts_th);
34755eb1c5fSMiri Korenblit 
34855eb1c5fSMiri Korenblit 	/* Block 26-tone RU OFDMA transmissions */
349d6f6b0d8SGregory Greenman 	if (link_info->he_ru_2mhz_block) {
35055eb1c5fSMiri Korenblit 		flags |= LINK_FLG_RU_2MHZ_BLOCK;
35155eb1c5fSMiri Korenblit 		flags_mask |= LINK_FLG_RU_2MHZ_BLOCK;
35255eb1c5fSMiri Korenblit 	}
35355eb1c5fSMiri Korenblit 
354cacc1d42SGregory Greenman 	if (link_conf->nontransmitted) {
35555eb1c5fSMiri Korenblit 		ether_addr_copy(cmd.ref_bssid_addr,
356cacc1d42SGregory Greenman 				link_conf->transmitter_bssid);
357cacc1d42SGregory Greenman 		cmd.bssid_index = link_conf->bssid_index;
35855eb1c5fSMiri Korenblit 	}
35955eb1c5fSMiri Korenblit 
36055eb1c5fSMiri Korenblit send_cmd:
36155eb1c5fSMiri Korenblit 	cmd.modify_mask = cpu_to_le32(changes);
36255eb1c5fSMiri Korenblit 	cmd.flags = cpu_to_le32(flags);
36355eb1c5fSMiri Korenblit 	cmd.flags_mask = cpu_to_le32(flags_mask);
364ed0c3433SJohannes Berg 	cmd.spec_link_id = link_conf->link_id;
3654dde4ff0SShaul Triebitz 	if (cmd_ver < 2)
36612bacfc2SMiri Korenblit 		cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);
36755eb1c5fSMiri Korenblit 
368d6f6b0d8SGregory Greenman 	ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_MODIFY);
3699deccfcdSGregory Greenman 	if (!ret && (changes & LINK_CONTEXT_MODIFY_ACTIVE))
370d6f6b0d8SGregory Greenman 		link_info->active = active;
371d6f6b0d8SGregory Greenman 
372d6f6b0d8SGregory Greenman 	return ret;
37355eb1c5fSMiri Korenblit }
37455eb1c5fSMiri Korenblit 
iwl_mvm_unset_link_mapping(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf)375a8b5d480SIlan Peer int iwl_mvm_unset_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
376a8b5d480SIlan Peer 			       struct ieee80211_bss_conf *link_conf)
377a8b5d480SIlan Peer {
378a8b5d480SIlan Peer 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
379a8b5d480SIlan Peer 	struct iwl_mvm_vif_link_info *link_info =
380a8b5d480SIlan Peer 		mvmvif->link[link_conf->link_id];
381a8b5d480SIlan Peer 
382a8b5d480SIlan Peer 	/* mac80211 thought we have the link, but it was never configured */
383a8b5d480SIlan Peer 	if (WARN_ON(!link_info ||
384a8b5d480SIlan Peer 		    link_info->fw_link_id >=
385a8b5d480SIlan Peer 		    ARRAY_SIZE(mvm->link_id_to_link_conf)))
386a8b5d480SIlan Peer 		return -EINVAL;
387a8b5d480SIlan Peer 
388a8b5d480SIlan Peer 	RCU_INIT_POINTER(mvm->link_id_to_link_conf[link_info->fw_link_id],
389a8b5d480SIlan Peer 			 NULL);
390a8b5d480SIlan Peer 	return 0;
391a8b5d480SIlan Peer }
392a8b5d480SIlan Peer 
iwl_mvm_remove_link(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf)393cacc1d42SGregory Greenman int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
394cacc1d42SGregory Greenman 			struct ieee80211_bss_conf *link_conf)
39555eb1c5fSMiri Korenblit {
39655eb1c5fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
397d6f6b0d8SGregory Greenman 	unsigned int link_id = link_conf->link_id;
398d6f6b0d8SGregory Greenman 	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];
39955eb1c5fSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
40055eb1c5fSMiri Korenblit 	int ret;
40155eb1c5fSMiri Korenblit 
402a8b5d480SIlan Peer 	ret = iwl_mvm_unset_link_mapping(mvm, vif, link_conf);
403a8b5d480SIlan Peer 	if (ret)
404ac139aa3SBenjamin Berg 		return 0;
405d6f6b0d8SGregory Greenman 
406d6f6b0d8SGregory Greenman 	cmd.link_id = cpu_to_le32(link_info->fw_link_id);
407d6f6b0d8SGregory Greenman 	link_info->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID;
408ed0c3433SJohannes Berg 	cmd.spec_link_id = link_conf->link_id;
409f3276ff0SEmmanuel Grumbach 	cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);
410d6f6b0d8SGregory Greenman 
41155eb1c5fSMiri Korenblit 	ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_REMOVE);
41255eb1c5fSMiri Korenblit 
41355eb1c5fSMiri Korenblit 	if (!ret)
41455eb1c5fSMiri Korenblit 		if (iwl_mvm_sf_update(mvm, vif, true))
41555eb1c5fSMiri Korenblit 			IWL_ERR(mvm, "Failed to update SF state\n");
41655eb1c5fSMiri Korenblit 
41755eb1c5fSMiri Korenblit 	return ret;
41855eb1c5fSMiri Korenblit }
419bf976c81SJohannes Berg 
420bf976c81SJohannes Berg /* link should be deactivated before removal, so in most cases we need to
421bf976c81SJohannes Berg  * perform these two operations together
422bf976c81SJohannes Berg  */
iwl_mvm_disable_link(struct iwl_mvm * mvm,struct ieee80211_vif * vif,struct ieee80211_bss_conf * link_conf)423bf976c81SJohannes Berg int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
424bf976c81SJohannes Berg 			 struct ieee80211_bss_conf *link_conf)
425bf976c81SJohannes Berg {
426bf976c81SJohannes Berg 	int ret;
427bf976c81SJohannes Berg 
428bf976c81SJohannes Berg 	ret = iwl_mvm_link_changed(mvm, vif, link_conf,
429bf976c81SJohannes Berg 				   LINK_CONTEXT_MODIFY_ACTIVE, false);
430bf976c81SJohannes Berg 	if (ret)
431bf976c81SJohannes Berg 		return ret;
432bf976c81SJohannes Berg 
433bf976c81SJohannes Berg 	ret = iwl_mvm_remove_link(mvm, vif, link_conf);
434bf976c81SJohannes Berg 	if (ret)
435bf976c81SJohannes Berg 		return ret;
436bf976c81SJohannes Berg 
437bf976c81SJohannes Berg 	return ret;
438bf976c81SJohannes Berg }
4392887af4dSMiri Korenblit 
4402887af4dSMiri Korenblit struct iwl_mvm_rssi_to_grade {
4412887af4dSMiri Korenblit 	s8 rssi[2];
4422887af4dSMiri Korenblit 	u16 grade;
4432887af4dSMiri Korenblit };
4442887af4dSMiri Korenblit 
4452887af4dSMiri Korenblit #define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \
4462887af4dSMiri Korenblit 	{ \
4472887af4dSMiri Korenblit 		.rssi = {_lb, _hb_uhb}, \
4482887af4dSMiri Korenblit 		.grade = _grade \
4492887af4dSMiri Korenblit 	}
4502887af4dSMiri Korenblit 
4512887af4dSMiri Korenblit /*
4522887af4dSMiri Korenblit  * This array must be sorted by increasing RSSI for proper functionality.
4532887af4dSMiri Korenblit  * The grades are actually estimated throughput, represented as fixed-point
4542887af4dSMiri Korenblit  * with a scale factor of 1/10.
4552887af4dSMiri Korenblit  */
4562887af4dSMiri Korenblit static const struct iwl_mvm_rssi_to_grade rssi_to_grade_map[] = {
4572887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-85, -89, 177),
4582887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-83, -86, 344),
4592887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-82, -85, 516),
4602887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-80, -83, 688),
4612887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-77, -79, 1032),
4622887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-73, -76, 1376),
4632887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-70, -74, 1548),
4642887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-69, -72, 1750),
4652887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-65, -68, 2064),
4662887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-61, -66, 2294),
4672887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-58, -61, 2580),
4682887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-55, -58, 2868),
4692887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-46, -55, 3098),
4702887af4dSMiri Korenblit 	RSSI_TO_GRADE_LINE(-43, -54, 3442)
4712887af4dSMiri Korenblit };
4722887af4dSMiri Korenblit 
4732887af4dSMiri Korenblit #define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)
4742887af4dSMiri Korenblit 
4752887af4dSMiri Korenblit #define DEFAULT_CHAN_LOAD_LB	30
4762887af4dSMiri Korenblit #define DEFAULT_CHAN_LOAD_HB	15
4772887af4dSMiri Korenblit #define DEFAULT_CHAN_LOAD_UHB	0
4782887af4dSMiri Korenblit 
4792887af4dSMiri Korenblit /* Factors calculation is done with fixed-point with a scaling factor of 1/256 */
4802887af4dSMiri Korenblit #define SCALE_FACTOR 256
4812887af4dSMiri Korenblit 
4822887af4dSMiri Korenblit /* Convert a percentage from [0,100] to [0,255] */
4832887af4dSMiri Korenblit #define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * SCALE_FACTOR / 100)
4842887af4dSMiri Korenblit 
4852887af4dSMiri Korenblit static unsigned int
iwl_mvm_get_puncturing_factor(const struct ieee80211_bss_conf * link_conf)4862887af4dSMiri Korenblit iwl_mvm_get_puncturing_factor(const struct ieee80211_bss_conf *link_conf)
4872887af4dSMiri Korenblit {
4882887af4dSMiri Korenblit 	enum nl80211_chan_width chan_width =
4892887af4dSMiri Korenblit 		link_conf->chanreq.oper.width;
4902887af4dSMiri Korenblit 	int mhz = nl80211_chan_width_to_mhz(chan_width);
4912887af4dSMiri Korenblit 	unsigned int n_subchannels, n_punctured, puncturing_penalty;
4922887af4dSMiri Korenblit 
4932887af4dSMiri Korenblit 	if (WARN_ONCE(mhz < 20 || mhz > 320,
4942887af4dSMiri Korenblit 		      "Invalid channel width : (%d)\n", mhz))
4952887af4dSMiri Korenblit 		return SCALE_FACTOR;
4962887af4dSMiri Korenblit 
4972887af4dSMiri Korenblit 	/* No puncturing, no penalty */
4982887af4dSMiri Korenblit 	if (mhz < 80)
4992887af4dSMiri Korenblit 		return SCALE_FACTOR;
5002887af4dSMiri Korenblit 
5012887af4dSMiri Korenblit 	/* total number of subchannels */
5022887af4dSMiri Korenblit 	n_subchannels = mhz / 20;
5032887af4dSMiri Korenblit 	/* how many of these are punctured */
5042887af4dSMiri Korenblit 	n_punctured = hweight16(link_conf->chanreq.oper.punctured);
5052887af4dSMiri Korenblit 
5062887af4dSMiri Korenblit 	puncturing_penalty = n_punctured * SCALE_FACTOR / n_subchannels;
5072887af4dSMiri Korenblit 	return SCALE_FACTOR - puncturing_penalty;
5082887af4dSMiri Korenblit }
5092887af4dSMiri Korenblit 
5102887af4dSMiri Korenblit static unsigned int
iwl_mvm_get_chan_load(struct ieee80211_bss_conf * link_conf)51107bf5297SMiri Korenblit iwl_mvm_get_chan_load(struct ieee80211_bss_conf *link_conf)
5122887af4dSMiri Korenblit {
513d9b7531fSJohannes Berg 	struct ieee80211_vif *vif = link_conf->vif;
5142887af4dSMiri Korenblit 	struct iwl_mvm_vif_link_info *mvm_link =
5152887af4dSMiri Korenblit 		iwl_mvm_vif_from_mac80211(link_conf->vif)->link[link_conf->link_id];
51607bf5297SMiri Korenblit 	const struct element *bss_load_elem;
5172887af4dSMiri Korenblit 	const struct ieee80211_bss_load_elem *bss_load;
5182887af4dSMiri Korenblit 	enum nl80211_band band = link_conf->chanreq.oper.chan->band;
519d9b7531fSJohannes Berg 	const struct cfg80211_bss_ies *ies;
5202887af4dSMiri Korenblit 	unsigned int chan_load;
5212887af4dSMiri Korenblit 	u32 chan_load_by_us;
5222887af4dSMiri Korenblit 
52307bf5297SMiri Korenblit 	rcu_read_lock();
524d9b7531fSJohannes Berg 	if (ieee80211_vif_link_active(vif, link_conf->link_id))
525d9b7531fSJohannes Berg 		ies = rcu_dereference(link_conf->bss->beacon_ies);
526d9b7531fSJohannes Berg 	else
527d9b7531fSJohannes Berg 		ies = rcu_dereference(link_conf->bss->ies);
528d9b7531fSJohannes Berg 
529d9b7531fSJohannes Berg 	if (ies)
530d9b7531fSJohannes Berg 		bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD,
531d9b7531fSJohannes Berg 						   ies->data, ies->len);
532d9b7531fSJohannes Berg 	else
533d9b7531fSJohannes Berg 		bss_load_elem = NULL;
53407bf5297SMiri Korenblit 
5352887af4dSMiri Korenblit 	/* If there isn't BSS Load element, take the defaults */
5362887af4dSMiri Korenblit 	if (!bss_load_elem ||
5372887af4dSMiri Korenblit 	    bss_load_elem->datalen != sizeof(*bss_load)) {
53807bf5297SMiri Korenblit 		rcu_read_unlock();
5392887af4dSMiri Korenblit 		switch (band) {
5402887af4dSMiri Korenblit 		case NL80211_BAND_2GHZ:
5412887af4dSMiri Korenblit 			chan_load = DEFAULT_CHAN_LOAD_LB;
5422887af4dSMiri Korenblit 			break;
5432887af4dSMiri Korenblit 		case NL80211_BAND_5GHZ:
5442887af4dSMiri Korenblit 			chan_load = DEFAULT_CHAN_LOAD_HB;
5452887af4dSMiri Korenblit 			break;
5462887af4dSMiri Korenblit 		case NL80211_BAND_6GHZ:
5472887af4dSMiri Korenblit 			chan_load = DEFAULT_CHAN_LOAD_UHB;
5482887af4dSMiri Korenblit 			break;
5492887af4dSMiri Korenblit 		default:
5502887af4dSMiri Korenblit 			chan_load = 0;
5512887af4dSMiri Korenblit 			break;
5522887af4dSMiri Korenblit 		}
5532887af4dSMiri Korenblit 		/* The defaults are given in percentage */
55407bf5297SMiri Korenblit 		return NORMALIZE_PERCENT_TO_255(chan_load);
5552887af4dSMiri Korenblit 	}
5562887af4dSMiri Korenblit 
5572887af4dSMiri Korenblit 	bss_load = (const void *)bss_load_elem->data;
5582887af4dSMiri Korenblit 	/* Channel util is in range 0-255 */
5592887af4dSMiri Korenblit 	chan_load = bss_load->channel_util;
56007bf5297SMiri Korenblit 	rcu_read_unlock();
5612887af4dSMiri Korenblit 
5622887af4dSMiri Korenblit 	if (!mvm_link || !mvm_link->active)
56307bf5297SMiri Korenblit 		return chan_load;
5642887af4dSMiri Korenblit 
5652887af4dSMiri Korenblit 	if (WARN_ONCE(!mvm_link->phy_ctxt,
5662887af4dSMiri Korenblit 		      "Active link (%u) without phy ctxt assigned!\n",
5672887af4dSMiri Korenblit 		      link_conf->link_id))
56807bf5297SMiri Korenblit 		return chan_load;
5692887af4dSMiri Korenblit 
5702887af4dSMiri Korenblit 	/* channel load by us is given in percentage */
5712887af4dSMiri Korenblit 	chan_load_by_us =
5722887af4dSMiri Korenblit 		NORMALIZE_PERCENT_TO_255(mvm_link->phy_ctxt->channel_load_by_us);
5732887af4dSMiri Korenblit 
5742887af4dSMiri Korenblit 	/* Use only values that firmware sends that can possibly be valid */
5752887af4dSMiri Korenblit 	if (chan_load_by_us <= chan_load)
5762887af4dSMiri Korenblit 		chan_load -= chan_load_by_us;
57707bf5297SMiri Korenblit 
57807bf5297SMiri Korenblit 	return chan_load;
57907bf5297SMiri Korenblit }
58007bf5297SMiri Korenblit 
58107bf5297SMiri Korenblit static unsigned int
iwl_mvm_get_chan_load_factor(struct ieee80211_bss_conf * link_conf)58207bf5297SMiri Korenblit iwl_mvm_get_chan_load_factor(struct ieee80211_bss_conf *link_conf)
58307bf5297SMiri Korenblit {
58407bf5297SMiri Korenblit 	return SCALE_FACTOR - iwl_mvm_get_chan_load(link_conf);
5852887af4dSMiri Korenblit }
5862887af4dSMiri Korenblit 
5872887af4dSMiri Korenblit /* This function calculates the grade of a link. Returns 0 in error case */
58807bf5297SMiri Korenblit VISIBLE_IF_IWLWIFI_KUNIT
iwl_mvm_get_link_grade(struct ieee80211_bss_conf * link_conf)5892887af4dSMiri Korenblit unsigned int iwl_mvm_get_link_grade(struct ieee80211_bss_conf *link_conf)
5902887af4dSMiri Korenblit {
5912887af4dSMiri Korenblit 	enum nl80211_band band;
5922887af4dSMiri Korenblit 	int i, rssi_idx;
5932887af4dSMiri Korenblit 	s32 link_rssi;
5942887af4dSMiri Korenblit 	unsigned int grade = MAX_GRADE;
5952887af4dSMiri Korenblit 
5962887af4dSMiri Korenblit 	if (WARN_ON_ONCE(!link_conf))
5972887af4dSMiri Korenblit 		return 0;
5982887af4dSMiri Korenblit 
5992887af4dSMiri Korenblit 	band = link_conf->chanreq.oper.chan->band;
6002887af4dSMiri Korenblit 	if (WARN_ONCE(band != NL80211_BAND_2GHZ &&
6012887af4dSMiri Korenblit 		      band != NL80211_BAND_5GHZ &&
6022887af4dSMiri Korenblit 		      band != NL80211_BAND_6GHZ,
6032887af4dSMiri Korenblit 		      "Invalid band (%u)\n", band))
6042887af4dSMiri Korenblit 		return 0;
6052887af4dSMiri Korenblit 
6062887af4dSMiri Korenblit 	link_rssi = MBM_TO_DBM(link_conf->bss->signal);
6072887af4dSMiri Korenblit 	/*
6082887af4dSMiri Korenblit 	 * For 6 GHz the RSSI of the beacons is lower than
6092887af4dSMiri Korenblit 	 * the RSSI of the data.
6102887af4dSMiri Korenblit 	 */
6112887af4dSMiri Korenblit 	if (band == NL80211_BAND_6GHZ)
6122887af4dSMiri Korenblit 		link_rssi += 4;
6132887af4dSMiri Korenblit 
6142887af4dSMiri Korenblit 	rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;
6152887af4dSMiri Korenblit 
61607bf5297SMiri Korenblit 	/* No valid RSSI - take the lowest grade */
61707bf5297SMiri Korenblit 	if (!link_rssi)
61807bf5297SMiri Korenblit 		link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];
61907bf5297SMiri Korenblit 
6202887af4dSMiri Korenblit 	/* Get grade based on RSSI */
6212887af4dSMiri Korenblit 	for (i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {
6222887af4dSMiri Korenblit 		const struct iwl_mvm_rssi_to_grade *line =
6232887af4dSMiri Korenblit 			&rssi_to_grade_map[i];
6242887af4dSMiri Korenblit 
6252887af4dSMiri Korenblit 		if (link_rssi > line->rssi[rssi_idx])
6262887af4dSMiri Korenblit 			continue;
6272887af4dSMiri Korenblit 		grade = line->grade;
6282887af4dSMiri Korenblit 		break;
6292887af4dSMiri Korenblit 	}
6302887af4dSMiri Korenblit 
6312887af4dSMiri Korenblit 	/* apply the channel load and puncturing factors */
6322887af4dSMiri Korenblit 	grade = grade * iwl_mvm_get_chan_load_factor(link_conf) / SCALE_FACTOR;
6332887af4dSMiri Korenblit 	grade = grade * iwl_mvm_get_puncturing_factor(link_conf) / SCALE_FACTOR;
6342887af4dSMiri Korenblit 	return grade;
6352887af4dSMiri Korenblit }
6362887af4dSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_get_link_grade);
6371b9b7d37SMiri Korenblit 
638a9bf72d8SMiri Korenblit static
iwl_mvm_set_link_selection_data(struct ieee80211_vif * vif,struct iwl_mvm_link_sel_data * data,unsigned long usable_links,u8 * best_link_idx)63907bf5297SMiri Korenblit u8 iwl_mvm_set_link_selection_data(struct ieee80211_vif *vif,
64007bf5297SMiri Korenblit 				   struct iwl_mvm_link_sel_data *data,
64107bf5297SMiri Korenblit 				   unsigned long usable_links,
64207bf5297SMiri Korenblit 				   u8 *best_link_idx)
6431b9b7d37SMiri Korenblit {
64407bf5297SMiri Korenblit 	u8 n_data = 0;
64507bf5297SMiri Korenblit 	u16 max_grade = 0;
64607bf5297SMiri Korenblit 	unsigned long link_id;
6471b9b7d37SMiri Korenblit 
64848ac6c8eSMiri Korenblit 	/* TODO: don't select links that weren't discovered in the last scan */
6491b9b7d37SMiri Korenblit 	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) {
6501b9b7d37SMiri Korenblit 		struct ieee80211_bss_conf *link_conf =
6511b9b7d37SMiri Korenblit 			link_conf_dereference_protected(vif, link_id);
6521b9b7d37SMiri Korenblit 
6531b9b7d37SMiri Korenblit 		if (WARN_ON_ONCE(!link_conf))
6541b9b7d37SMiri Korenblit 			continue;
6551b9b7d37SMiri Korenblit 
6561b9b7d37SMiri Korenblit 		data[n_data].link_id = link_id;
65730ce0390SMiri Korenblit 		data[n_data].chandef = &link_conf->chanreq.oper;
65830ce0390SMiri Korenblit 		data[n_data].signal = link_conf->bss->signal / 100;
65907bf5297SMiri Korenblit 		data[n_data].grade = iwl_mvm_get_link_grade(link_conf);
66007bf5297SMiri Korenblit 
66107bf5297SMiri Korenblit 		if (data[n_data].grade > max_grade) {
66207bf5297SMiri Korenblit 			max_grade = data[n_data].grade;
66307bf5297SMiri Korenblit 			*best_link_idx = n_data;
66407bf5297SMiri Korenblit 		}
6651b9b7d37SMiri Korenblit 		n_data++;
6661b9b7d37SMiri Korenblit 	}
6671b9b7d37SMiri Korenblit 
6681b9b7d37SMiri Korenblit 	return n_data;
6691b9b7d37SMiri Korenblit }
6701b9b7d37SMiri Korenblit 
67130ce0390SMiri Korenblit struct iwl_mvm_bw_to_rssi_threshs {
67230ce0390SMiri Korenblit 	s8 low;
67330ce0390SMiri Korenblit 	s8 high;
67430ce0390SMiri Korenblit };
67530ce0390SMiri Korenblit 
67630ce0390SMiri Korenblit #define BW_TO_RSSI_THRESHOLDS(_bw)				\
67730ce0390SMiri Korenblit 	[IWL_PHY_CHANNEL_MODE ## _bw] = {			\
67830ce0390SMiri Korenblit 		.low = IWL_MVM_LOW_RSSI_THRESH_##_bw##MHZ,	\
67930ce0390SMiri Korenblit 		.high = IWL_MVM_HIGH_RSSI_THRESH_##_bw##MHZ	\
68030ce0390SMiri Korenblit 	}
68130ce0390SMiri Korenblit 
iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm * mvm,const struct cfg80211_chan_def * chandef,bool low)68230ce0390SMiri Korenblit s8 iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm *mvm,
68330ce0390SMiri Korenblit 			       const struct cfg80211_chan_def *chandef,
68430ce0390SMiri Korenblit 			       bool low)
68530ce0390SMiri Korenblit {
68630ce0390SMiri Korenblit 	const struct iwl_mvm_bw_to_rssi_threshs bw_to_rssi_threshs_map[] = {
68730ce0390SMiri Korenblit 		BW_TO_RSSI_THRESHOLDS(20),
68830ce0390SMiri Korenblit 		BW_TO_RSSI_THRESHOLDS(40),
68930ce0390SMiri Korenblit 		BW_TO_RSSI_THRESHOLDS(80),
69030ce0390SMiri Korenblit 		BW_TO_RSSI_THRESHOLDS(160)
69130ce0390SMiri Korenblit 		/* 320 MHz has the same thresholds as 20 MHz */
69230ce0390SMiri Korenblit 	};
69330ce0390SMiri Korenblit 	const struct iwl_mvm_bw_to_rssi_threshs *threshs;
69430ce0390SMiri Korenblit 	u8 chan_width = iwl_mvm_get_channel_width(chandef);
69530ce0390SMiri Korenblit 
69630ce0390SMiri Korenblit 	if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ &&
69730ce0390SMiri Korenblit 		    chandef->chan->band != NL80211_BAND_5GHZ &&
69830ce0390SMiri Korenblit 		    chandef->chan->band != NL80211_BAND_6GHZ))
69930ce0390SMiri Korenblit 		return S8_MAX;
70030ce0390SMiri Korenblit 
70130ce0390SMiri Korenblit 	/* 6 GHz will always use 20 MHz thresholds, regardless of the BW */
70230ce0390SMiri Korenblit 	if (chan_width == IWL_PHY_CHANNEL_MODE320)
70330ce0390SMiri Korenblit 		chan_width = IWL_PHY_CHANNEL_MODE20;
70430ce0390SMiri Korenblit 
70530ce0390SMiri Korenblit 	threshs = &bw_to_rssi_threshs_map[chan_width];
70630ce0390SMiri Korenblit 
70730ce0390SMiri Korenblit 	return low ? threshs->low : threshs->high;
70830ce0390SMiri Korenblit }
70930ce0390SMiri Korenblit 
71030ce0390SMiri Korenblit static u32
iwl_mvm_esr_disallowed_with_link(struct iwl_mvm * mvm,struct ieee80211_vif * vif,const struct iwl_mvm_link_sel_data * link,bool primary)71105fe9606SYedidya Benshimol iwl_mvm_esr_disallowed_with_link(struct iwl_mvm *mvm,
71205fe9606SYedidya Benshimol 				 struct ieee80211_vif *vif,
71372c19df2SMiri Korenblit 				 const struct iwl_mvm_link_sel_data *link,
71472c19df2SMiri Korenblit 				 bool primary)
71530ce0390SMiri Korenblit {
7162f876f91SJohannes Berg 	struct wiphy *wiphy = mvm->hw->wiphy;
7172f876f91SJohannes Berg 	struct ieee80211_bss_conf *conf;
71830ce0390SMiri Korenblit 	enum iwl_mvm_esr_state ret = 0;
71930ce0390SMiri Korenblit 	s8 thresh;
72030ce0390SMiri Korenblit 
7212f876f91SJohannes Berg 	conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]);
7222f876f91SJohannes Berg 	if (WARN_ON_ONCE(!conf))
7232f876f91SJohannes Berg 		return false;
7242f876f91SJohannes Berg 
72530ce0390SMiri Korenblit 	/* BT Coex effects eSR mode only if one of the links is on LB */
72630ce0390SMiri Korenblit 	if (link->chandef->chan->band == NL80211_BAND_2GHZ &&
72772c19df2SMiri Korenblit 	    (!iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link->signal,
72872c19df2SMiri Korenblit 						 primary)))
72972c19df2SMiri Korenblit 		ret |= IWL_MVM_ESR_EXIT_COEX;
73072c19df2SMiri Korenblit 
73130ce0390SMiri Korenblit 	thresh = iwl_mvm_get_esr_rssi_thresh(mvm, link->chandef,
73230ce0390SMiri Korenblit 					     false);
73330ce0390SMiri Korenblit 
73430ce0390SMiri Korenblit 	if (link->signal < thresh)
73530ce0390SMiri Korenblit 		ret |= IWL_MVM_ESR_EXIT_LOW_RSSI;
73630ce0390SMiri Korenblit 
7372f876f91SJohannes Berg 	if (conf->csa_active)
7382f876f91SJohannes Berg 		ret |= IWL_MVM_ESR_EXIT_CSA;
7392f876f91SJohannes Berg 
74005f10dadSYedidya Benshimol 	if (ret) {
74130ce0390SMiri Korenblit 		IWL_DEBUG_INFO(mvm,
74205f10dadSYedidya Benshimol 			       "Link %d is not allowed for esr\n",
74305f10dadSYedidya Benshimol 			       link->link_id);
74405f10dadSYedidya Benshimol 		iwl_mvm_print_esr_state(mvm, ret);
74505f10dadSYedidya Benshimol 	}
74630ce0390SMiri Korenblit 	return ret;
74730ce0390SMiri Korenblit }
74830ce0390SMiri Korenblit 
74907bf5297SMiri Korenblit VISIBLE_IF_IWLWIFI_KUNIT
iwl_mvm_mld_valid_link_pair(struct ieee80211_vif * vif,const struct iwl_mvm_link_sel_data * a,const struct iwl_mvm_link_sel_data * b)7501b9b7d37SMiri Korenblit bool iwl_mvm_mld_valid_link_pair(struct ieee80211_vif *vif,
75107bf5297SMiri Korenblit 				 const struct iwl_mvm_link_sel_data *a,
75207bf5297SMiri Korenblit 				 const struct iwl_mvm_link_sel_data *b)
7531b9b7d37SMiri Korenblit {
75405fe9606SYedidya Benshimol 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
75505fe9606SYedidya Benshimol 	struct iwl_mvm *mvm = mvmvif->mvm;
75605fe9606SYedidya Benshimol 	enum iwl_mvm_esr_state ret = 0;
75705fe9606SYedidya Benshimol 
75830ce0390SMiri Korenblit 	/* Per-link considerations */
75905fe9606SYedidya Benshimol 	if (iwl_mvm_esr_disallowed_with_link(mvm, vif, a, true) ||
76005fe9606SYedidya Benshimol 	    iwl_mvm_esr_disallowed_with_link(mvm, vif, b, false))
7611b9b7d37SMiri Korenblit 		return false;
7621b9b7d37SMiri Korenblit 
76305fe9606SYedidya Benshimol 	if (a->chandef->width != b->chandef->width ||
76405fe9606SYedidya Benshimol 	    !(a->chandef->chan->band == NL80211_BAND_6GHZ &&
765ae7fe563SYedidya Benshimol 	      b->chandef->chan->band == NL80211_BAND_5GHZ))
76605fe9606SYedidya Benshimol 		ret |= IWL_MVM_ESR_EXIT_BANDWIDTH;
767ae7fe563SYedidya Benshimol 
76805fe9606SYedidya Benshimol 	if (ret) {
76905fe9606SYedidya Benshimol 		IWL_DEBUG_INFO(mvm,
77005fe9606SYedidya Benshimol 			       "Links %d and %d are not a valid pair for EMLSR\n",
77105fe9606SYedidya Benshimol 			       a->link_id, b->link_id);
77205fe9606SYedidya Benshimol 		iwl_mvm_print_esr_state(mvm, ret);
77305fe9606SYedidya Benshimol 		return false;
77405fe9606SYedidya Benshimol 	}
77505fe9606SYedidya Benshimol 
77605fe9606SYedidya Benshimol 	return true;
77705fe9606SYedidya Benshimol 
7781b9b7d37SMiri Korenblit }
77907bf5297SMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_mld_valid_link_pair);
7801b9b7d37SMiri Korenblit 
78107bf5297SMiri Korenblit /*
78207bf5297SMiri Korenblit  * Returns the combined eSR grade of two given links.
78307bf5297SMiri Korenblit  * Returns 0 if eSR is not allowed with these 2 links.
78407bf5297SMiri Korenblit  */
78507bf5297SMiri Korenblit static
iwl_mvm_get_esr_grade(struct ieee80211_vif * vif,const struct iwl_mvm_link_sel_data * a,const struct iwl_mvm_link_sel_data * b,u8 * primary_id)78607bf5297SMiri Korenblit unsigned int iwl_mvm_get_esr_grade(struct ieee80211_vif *vif,
78707bf5297SMiri Korenblit 				   const struct iwl_mvm_link_sel_data *a,
78807bf5297SMiri Korenblit 				   const struct iwl_mvm_link_sel_data *b,
78907bf5297SMiri Korenblit 				   u8 *primary_id)
79007bf5297SMiri Korenblit {
79107bf5297SMiri Korenblit 	struct ieee80211_bss_conf *primary_conf;
79207bf5297SMiri Korenblit 	struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy;
79307bf5297SMiri Korenblit 	unsigned int primary_load;
79407bf5297SMiri Korenblit 
79507bf5297SMiri Korenblit 	lockdep_assert_wiphy(wiphy);
79607bf5297SMiri Korenblit 
79707bf5297SMiri Korenblit 	/* a is always primary, b is always secondary */
79807bf5297SMiri Korenblit 	if (b->grade > a->grade)
79907bf5297SMiri Korenblit 		swap(a, b);
80007bf5297SMiri Korenblit 
80107bf5297SMiri Korenblit 	*primary_id = a->link_id;
80207bf5297SMiri Korenblit 
80307bf5297SMiri Korenblit 	if (!iwl_mvm_mld_valid_link_pair(vif, a, b))
80407bf5297SMiri Korenblit 		return 0;
80507bf5297SMiri Korenblit 
80607bf5297SMiri Korenblit 	primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]);
80707bf5297SMiri Korenblit 
80807bf5297SMiri Korenblit 	if (WARN_ON_ONCE(!primary_conf))
80907bf5297SMiri Korenblit 		return 0;
81007bf5297SMiri Korenblit 
81107bf5297SMiri Korenblit 	primary_load = iwl_mvm_get_chan_load(primary_conf);
81207bf5297SMiri Korenblit 
81307bf5297SMiri Korenblit 	return a->grade +
81407bf5297SMiri Korenblit 		((b->grade * primary_load) / SCALE_FACTOR);
81507bf5297SMiri Korenblit }
81607bf5297SMiri Korenblit 
iwl_mvm_select_links(struct iwl_mvm * mvm,struct ieee80211_vif * vif)81707bf5297SMiri Korenblit void iwl_mvm_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif)
8181b9b7d37SMiri Korenblit {
8191b9b7d37SMiri Korenblit 	struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS];
82007bf5297SMiri Korenblit 	struct iwl_mvm_link_sel_data *best_link;
82107bf5297SMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
8221b9b7d37SMiri Korenblit 	u32 max_active_links = iwl_mvm_max_active_links(mvm, vif);
82307bf5297SMiri Korenblit 	u16 usable_links = ieee80211_vif_usable_links(vif);
82407bf5297SMiri Korenblit 	u8 best, primary_link, best_in_pair, n_data;
82507bf5297SMiri Korenblit 	u16 max_esr_grade = 0, new_active_links;
82607bf5297SMiri Korenblit 
82707bf5297SMiri Korenblit 	lockdep_assert_wiphy(mvm->hw->wiphy);
82807bf5297SMiri Korenblit 
82907bf5297SMiri Korenblit 	if (!mvmvif->authorized || !ieee80211_vif_is_mld(vif))
83007bf5297SMiri Korenblit 		return;
8311b9b7d37SMiri Korenblit 
8321b9b7d37SMiri Korenblit 	if (!IWL_MVM_AUTO_EML_ENABLE)
8331b9b7d37SMiri Korenblit 		return;
8341b9b7d37SMiri Korenblit 
8351b9b7d37SMiri Korenblit 	/* The logic below is a simple version that doesn't suit more than 2
8361b9b7d37SMiri Korenblit 	 * links
8371b9b7d37SMiri Korenblit 	 */
8381b9b7d37SMiri Korenblit 	WARN_ON_ONCE(max_active_links > 2);
8391b9b7d37SMiri Korenblit 
84007bf5297SMiri Korenblit 	n_data = iwl_mvm_set_link_selection_data(vif, data, usable_links,
84107bf5297SMiri Korenblit 						 &best);
84207bf5297SMiri Korenblit 
84307bf5297SMiri Korenblit 	if (WARN(!n_data, "Couldn't find a valid grade for any link!\n"))
8441b9b7d37SMiri Korenblit 		return;
8451b9b7d37SMiri Korenblit 
84607bf5297SMiri Korenblit 	best_link = &data[best];
84707bf5297SMiri Korenblit 	primary_link = best_link->link_id;
84807bf5297SMiri Korenblit 	new_active_links = BIT(best_link->link_id);
8491b9b7d37SMiri Korenblit 
850f23caa39SMiri Korenblit 	/* eSR is not supported/blocked, or only one usable link */
851f23caa39SMiri Korenblit 	if (max_active_links == 1 || !iwl_mvm_vif_has_esr_cap(mvm, vif) ||
852f23caa39SMiri Korenblit 	    mvmvif->esr_disable_reason || n_data == 1)
85307bf5297SMiri Korenblit 		goto set_active;
8541b9b7d37SMiri Korenblit 
85507bf5297SMiri Korenblit 	for (u8 a = 0; a < n_data; a++)
85607bf5297SMiri Korenblit 		for (u8 b = a + 1; b < n_data; b++) {
85707bf5297SMiri Korenblit 			u16 esr_grade = iwl_mvm_get_esr_grade(vif, &data[a],
85807bf5297SMiri Korenblit 							      &data[b],
85907bf5297SMiri Korenblit 							      &best_in_pair);
8601b9b7d37SMiri Korenblit 
86107bf5297SMiri Korenblit 			if (esr_grade <= max_esr_grade)
8621b9b7d37SMiri Korenblit 				continue;
8631b9b7d37SMiri Korenblit 
86407bf5297SMiri Korenblit 			max_esr_grade = esr_grade;
86507bf5297SMiri Korenblit 			primary_link = best_in_pair;
86607bf5297SMiri Korenblit 			new_active_links = BIT(data[a].link_id) |
86707bf5297SMiri Korenblit 					   BIT(data[b].link_id);
8681b9b7d37SMiri Korenblit 		}
8691b9b7d37SMiri Korenblit 
87007bf5297SMiri Korenblit 	/* No valid pair was found, go with the best link */
87107bf5297SMiri Korenblit 	if (hweight16(new_active_links) <= 1)
87207bf5297SMiri Korenblit 		goto set_active;
8731b9b7d37SMiri Korenblit 
8748ecdc570SMiri Korenblit 	/* For equal grade - prefer EMLSR */
8758ecdc570SMiri Korenblit 	if (best_link->grade > max_esr_grade) {
87607bf5297SMiri Korenblit 		primary_link = best_link->link_id;
87707bf5297SMiri Korenblit 		new_active_links = BIT(best_link->link_id);
8781b9b7d37SMiri Korenblit 	}
87907bf5297SMiri Korenblit set_active:
88007bf5297SMiri Korenblit 	IWL_DEBUG_INFO(mvm, "Link selection result: 0x%x. Primary = %d\n",
88107bf5297SMiri Korenblit 		       new_active_links, primary_link);
8821b9b7d37SMiri Korenblit 	ieee80211_set_active_links_async(vif, new_active_links);
88307bf5297SMiri Korenblit 	mvmvif->link_selection_res = new_active_links;
88407bf5297SMiri Korenblit 	mvmvif->link_selection_primary = primary_link;
88507bf5297SMiri Korenblit }
88607bf5297SMiri Korenblit 
iwl_mvm_get_primary_link(struct ieee80211_vif * vif)88707bf5297SMiri Korenblit u8 iwl_mvm_get_primary_link(struct ieee80211_vif *vif)
88807bf5297SMiri Korenblit {
88907bf5297SMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
89007bf5297SMiri Korenblit 
89154fa45ddSYedidya Benshimol 	/* relevant data is written with both locks held, so read with either */
89254fa45ddSYedidya Benshimol 	lockdep_assert(lockdep_is_held(&mvmvif->mvm->mutex) ||
89354fa45ddSYedidya Benshimol 		       lockdep_is_held(&mvmvif->mvm->hw->wiphy->mtx));
89407bf5297SMiri Korenblit 
89507bf5297SMiri Korenblit 	if (!ieee80211_vif_is_mld(vif))
89607bf5297SMiri Korenblit 		return 0;
89707bf5297SMiri Korenblit 
89807bf5297SMiri Korenblit 	/* In AP mode, there is no primary link */
89907bf5297SMiri Korenblit 	if (vif->type == NL80211_IFTYPE_AP)
90007bf5297SMiri Korenblit 		return __ffs(vif->active_links);
90107bf5297SMiri Korenblit 
90207bf5297SMiri Korenblit 	if (mvmvif->esr_active &&
90307bf5297SMiri Korenblit 	    !WARN_ON(!(BIT(mvmvif->primary_link) & vif->active_links)))
90407bf5297SMiri Korenblit 		return mvmvif->primary_link;
90507bf5297SMiri Korenblit 
90607bf5297SMiri Korenblit 	return __ffs(vif->active_links);
9071b9b7d37SMiri Korenblit }
9086cf7df9fSMiri Korenblit 
90948ac6c8eSMiri Korenblit /*
91048ac6c8eSMiri Korenblit  * For non-MLO/single link, this will return the deflink/single active link,
91148ac6c8eSMiri Korenblit  * respectively
91248ac6c8eSMiri Korenblit  */
iwl_mvm_get_other_link(struct ieee80211_vif * vif,u8 link_id)91348ac6c8eSMiri Korenblit u8 iwl_mvm_get_other_link(struct ieee80211_vif *vif, u8 link_id)
91448ac6c8eSMiri Korenblit {
91548ac6c8eSMiri Korenblit 	switch (hweight16(vif->active_links)) {
91648ac6c8eSMiri Korenblit 	case 0:
91748ac6c8eSMiri Korenblit 		return 0;
91848ac6c8eSMiri Korenblit 	default:
91948ac6c8eSMiri Korenblit 		WARN_ON(1);
92048ac6c8eSMiri Korenblit 		fallthrough;
92148ac6c8eSMiri Korenblit 	case 1:
92248ac6c8eSMiri Korenblit 		return __ffs(vif->active_links);
92348ac6c8eSMiri Korenblit 	case 2:
92448ac6c8eSMiri Korenblit 		return __ffs(vif->active_links & ~BIT(link_id));
92548ac6c8eSMiri Korenblit 	}
92648ac6c8eSMiri Korenblit }
92748ac6c8eSMiri Korenblit 
928492bc4e4SMiri Korenblit /* Reasons that can cause esr prevention */
929492bc4e4SMiri Korenblit #define IWL_MVM_ESR_PREVENT_REASONS	IWL_MVM_ESR_EXIT_MISSED_BEACON
930492bc4e4SMiri Korenblit #define IWL_MVM_PREVENT_ESR_TIMEOUT	(HZ * 400)
931492bc4e4SMiri Korenblit #define IWL_MVM_ESR_PREVENT_SHORT	(HZ * 300)
932492bc4e4SMiri Korenblit #define IWL_MVM_ESR_PREVENT_LONG	(HZ * 600)
933492bc4e4SMiri Korenblit 
iwl_mvm_check_esr_prevention(struct iwl_mvm * mvm,struct iwl_mvm_vif * mvmvif,enum iwl_mvm_esr_state reason)9342f33561eSMiri Korenblit static bool iwl_mvm_check_esr_prevention(struct iwl_mvm *mvm,
935492bc4e4SMiri Korenblit 					 struct iwl_mvm_vif *mvmvif,
936492bc4e4SMiri Korenblit 					 enum iwl_mvm_esr_state reason)
937492bc4e4SMiri Korenblit {
9382f33561eSMiri Korenblit 	bool timeout_expired = time_after(jiffies,
9392f33561eSMiri Korenblit 					  mvmvif->last_esr_exit.ts +
940492bc4e4SMiri Korenblit 					  IWL_MVM_PREVENT_ESR_TIMEOUT);
9412f33561eSMiri Korenblit 	unsigned long delay;
942492bc4e4SMiri Korenblit 
943492bc4e4SMiri Korenblit 	lockdep_assert_held(&mvm->mutex);
944492bc4e4SMiri Korenblit 
9452f33561eSMiri Korenblit 	/* Only handle reasons that can cause prevention */
9462f33561eSMiri Korenblit 	if (!(reason & IWL_MVM_ESR_PREVENT_REASONS))
9472f33561eSMiri Korenblit 		return false;
948492bc4e4SMiri Korenblit 
9492f33561eSMiri Korenblit 	/*
9502f33561eSMiri Korenblit 	 * Reset the counter if more than 400 seconds have passed between one
9512f33561eSMiri Korenblit 	 * exit and the other, or if we exited due to a different reason.
9522f33561eSMiri Korenblit 	 * Will also reset the counter after the long prevention is done.
9532f33561eSMiri Korenblit 	 */
9542f33561eSMiri Korenblit 	if (timeout_expired || mvmvif->last_esr_exit.reason != reason) {
955492bc4e4SMiri Korenblit 		mvmvif->exit_same_reason_count = 1;
9562f33561eSMiri Korenblit 		return false;
957492bc4e4SMiri Korenblit 	}
958492bc4e4SMiri Korenblit 
959492bc4e4SMiri Korenblit 	mvmvif->exit_same_reason_count++;
960492bc4e4SMiri Korenblit 	if (WARN_ON(mvmvif->exit_same_reason_count < 2 ||
961492bc4e4SMiri Korenblit 		    mvmvif->exit_same_reason_count > 3))
9622f33561eSMiri Korenblit 		return false;
963492bc4e4SMiri Korenblit 
964492bc4e4SMiri Korenblit 	mvmvif->esr_disable_reason |= IWL_MVM_ESR_BLOCKED_PREVENTION;
965492bc4e4SMiri Korenblit 
9662f33561eSMiri Korenblit 	/*
9672f33561eSMiri Korenblit 	 * For the second exit, use a short prevention, and for the third one,
9682f33561eSMiri Korenblit 	 * use a long prevention.
9692f33561eSMiri Korenblit 	 */
970492bc4e4SMiri Korenblit 	delay = mvmvif->exit_same_reason_count == 2 ?
971492bc4e4SMiri Korenblit 		IWL_MVM_ESR_PREVENT_SHORT :
972492bc4e4SMiri Korenblit 		IWL_MVM_ESR_PREVENT_LONG;
973492bc4e4SMiri Korenblit 
974492bc4e4SMiri Korenblit 	IWL_DEBUG_INFO(mvm,
975966a4d9bSDaniel Gabay 		       "Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n",
976966a4d9bSDaniel Gabay 		       delay / HZ, mvmvif->exit_same_reason_count,
977966a4d9bSDaniel Gabay 		       iwl_get_esr_state_string(reason), reason);
978492bc4e4SMiri Korenblit 
979492bc4e4SMiri Korenblit 	wiphy_delayed_work_queue(mvm->hw->wiphy,
980492bc4e4SMiri Korenblit 				 &mvmvif->prevent_esr_done_wk, delay);
9812f33561eSMiri Korenblit 	return true;
982492bc4e4SMiri Korenblit }
983492bc4e4SMiri Korenblit 
9842f33561eSMiri Korenblit #define IWL_MVM_TRIGGER_LINK_SEL_TIME (IWL_MVM_TRIGGER_LINK_SEL_TIME_SEC * HZ)
9852f33561eSMiri Korenblit 
9866cf7df9fSMiri Korenblit /* API to exit eSR mode */
iwl_mvm_exit_esr(struct iwl_mvm * mvm,struct ieee80211_vif * vif,enum iwl_mvm_esr_state reason,u8 link_to_keep)9876cf7df9fSMiri Korenblit void iwl_mvm_exit_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
9886cf7df9fSMiri Korenblit 		      enum iwl_mvm_esr_state reason,
9896cf7df9fSMiri Korenblit 		      u8 link_to_keep)
9906cf7df9fSMiri Korenblit {
9916cf7df9fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
9926cf7df9fSMiri Korenblit 	u16 new_active_links;
9932f33561eSMiri Korenblit 	bool prevented;
9946cf7df9fSMiri Korenblit 
9956cf7df9fSMiri Korenblit 	lockdep_assert_held(&mvm->mutex);
9966cf7df9fSMiri Korenblit 
9979574c759SMiri Korenblit 	if (!IWL_MVM_AUTO_EML_ENABLE)
9989574c759SMiri Korenblit 		return;
9999574c759SMiri Korenblit 
10006cf7df9fSMiri Korenblit 	/* Nothing to do */
10016cf7df9fSMiri Korenblit 	if (!mvmvif->esr_active)
10026cf7df9fSMiri Korenblit 		return;
10036cf7df9fSMiri Korenblit 
10046cf7df9fSMiri Korenblit 	if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mvmvif->authorized))
10056cf7df9fSMiri Korenblit 		return;
10066cf7df9fSMiri Korenblit 
10076cf7df9fSMiri Korenblit 	if (WARN_ON(!(vif->active_links & BIT(link_to_keep))))
10086cf7df9fSMiri Korenblit 		link_to_keep = __ffs(vif->active_links);
10096cf7df9fSMiri Korenblit 
10106cf7df9fSMiri Korenblit 	new_active_links = BIT(link_to_keep);
10116cf7df9fSMiri Korenblit 	IWL_DEBUG_INFO(mvm,
1012966a4d9bSDaniel Gabay 		       "Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n",
1013966a4d9bSDaniel Gabay 		       iwl_get_esr_state_string(reason), reason,
1014966a4d9bSDaniel Gabay 		       vif->active_links, new_active_links);
10156cf7df9fSMiri Korenblit 
10166cf7df9fSMiri Korenblit 	ieee80211_set_active_links_async(vif, new_active_links);
1017492bc4e4SMiri Korenblit 
10182f33561eSMiri Korenblit 	/* Prevent EMLSR if needed */
10192f33561eSMiri Korenblit 	prevented = iwl_mvm_check_esr_prevention(mvm, mvmvif, reason);
10202f33561eSMiri Korenblit 
10212f33561eSMiri Korenblit 	/* Remember why and when we exited EMLSR */
10222f33561eSMiri Korenblit 	mvmvif->last_esr_exit.ts = jiffies;
10232f33561eSMiri Korenblit 	mvmvif->last_esr_exit.reason = reason;
10242f33561eSMiri Korenblit 
10252f33561eSMiri Korenblit 	/*
10262f33561eSMiri Korenblit 	 * If EMLSR is prevented now - don't try to get back to EMLSR.
10272f33561eSMiri Korenblit 	 * If we exited due to a blocking event, we will try to get back to
10282f33561eSMiri Korenblit 	 * EMLSR when the corresponding unblocking event will happen.
10292f33561eSMiri Korenblit 	 */
10302f33561eSMiri Korenblit 	if (prevented || reason & IWL_MVM_BLOCK_ESR_REASONS)
10312f33561eSMiri Korenblit 		return;
10322f33561eSMiri Korenblit 
10332f33561eSMiri Korenblit 	/* If EMLSR is not blocked - try enabling it again in 30 seconds */
10342f33561eSMiri Korenblit 	wiphy_delayed_work_queue(mvm->hw->wiphy,
10352f33561eSMiri Korenblit 				 &mvmvif->mlo_int_scan_wk,
10362f33561eSMiri Korenblit 				 round_jiffies_relative(IWL_MVM_TRIGGER_LINK_SEL_TIME));
10376cf7df9fSMiri Korenblit }
10386cf7df9fSMiri Korenblit 
iwl_mvm_block_esr(struct iwl_mvm * mvm,struct ieee80211_vif * vif,enum iwl_mvm_esr_state reason,u8 link_to_keep)10396cf7df9fSMiri Korenblit void iwl_mvm_block_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
10406cf7df9fSMiri Korenblit 		       enum iwl_mvm_esr_state reason,
10416cf7df9fSMiri Korenblit 		       u8 link_to_keep)
10426cf7df9fSMiri Korenblit {
10436cf7df9fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
10446cf7df9fSMiri Korenblit 
10456cf7df9fSMiri Korenblit 	lockdep_assert_held(&mvm->mutex);
10466cf7df9fSMiri Korenblit 
10479574c759SMiri Korenblit 	if (!IWL_MVM_AUTO_EML_ENABLE)
10489574c759SMiri Korenblit 		return;
10499574c759SMiri Korenblit 
10506cf7df9fSMiri Korenblit 	/* This should be called only with disable reasons */
10516cf7df9fSMiri Korenblit 	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))
10526cf7df9fSMiri Korenblit 		return;
10536cf7df9fSMiri Korenblit 
1054f27579ffSJohannes Berg 	if (mvmvif->esr_disable_reason & reason)
1055f27579ffSJohannes Berg 		return;
1056f27579ffSJohannes Berg 
1057966a4d9bSDaniel Gabay 	IWL_DEBUG_INFO(mvm,
1058966a4d9bSDaniel Gabay 		       "Blocking EMLSR mode. reason = %s (0x%x)\n",
1059966a4d9bSDaniel Gabay 		       iwl_get_esr_state_string(reason), reason);
10606cf7df9fSMiri Korenblit 
10616cf7df9fSMiri Korenblit 	mvmvif->esr_disable_reason |= reason;
10626cf7df9fSMiri Korenblit 
1063f27579ffSJohannes Berg 	iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);
1064f27579ffSJohannes Berg 
10656cf7df9fSMiri Korenblit 	iwl_mvm_exit_esr(mvm, vif, reason, link_to_keep);
10666cf7df9fSMiri Korenblit }
10676cf7df9fSMiri Korenblit 
iwl_mvm_block_esr_sync(struct iwl_mvm * mvm,struct ieee80211_vif * vif,enum iwl_mvm_esr_state reason)1068a1efeb82SYedidya Benshimol int iwl_mvm_block_esr_sync(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
1069a1efeb82SYedidya Benshimol 			   enum iwl_mvm_esr_state reason)
1070a1efeb82SYedidya Benshimol {
1071a1efeb82SYedidya Benshimol 	int primary_link = iwl_mvm_get_primary_link(vif);
1072a1efeb82SYedidya Benshimol 	int ret;
1073a1efeb82SYedidya Benshimol 
1074a1efeb82SYedidya Benshimol 	if (!IWL_MVM_AUTO_EML_ENABLE || !ieee80211_vif_is_mld(vif))
1075a1efeb82SYedidya Benshimol 		return 0;
1076a1efeb82SYedidya Benshimol 
1077a1efeb82SYedidya Benshimol 	/* This should be called only with blocking reasons */
1078a1efeb82SYedidya Benshimol 	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))
1079a1efeb82SYedidya Benshimol 		return 0;
1080a1efeb82SYedidya Benshimol 
1081a1efeb82SYedidya Benshimol 	/* leave ESR immediately, not only async with iwl_mvm_block_esr() */
1082a1efeb82SYedidya Benshimol 	ret = ieee80211_set_active_links(vif, BIT(primary_link));
1083a1efeb82SYedidya Benshimol 	if (ret)
1084a1efeb82SYedidya Benshimol 		return ret;
1085a1efeb82SYedidya Benshimol 
1086a1efeb82SYedidya Benshimol 	mutex_lock(&mvm->mutex);
1087a1efeb82SYedidya Benshimol 	/* only additionally block for consistency and to avoid concurrency */
1088a1efeb82SYedidya Benshimol 	iwl_mvm_block_esr(mvm, vif, reason, primary_link);
1089a1efeb82SYedidya Benshimol 	mutex_unlock(&mvm->mutex);
1090a1efeb82SYedidya Benshimol 
1091a1efeb82SYedidya Benshimol 	return 0;
1092a1efeb82SYedidya Benshimol }
1093a1efeb82SYedidya Benshimol 
iwl_mvm_esr_unblocked(struct iwl_mvm * mvm,struct ieee80211_vif * vif)10942f33561eSMiri Korenblit static void iwl_mvm_esr_unblocked(struct iwl_mvm *mvm,
10952f33561eSMiri Korenblit 				  struct ieee80211_vif *vif)
10962f33561eSMiri Korenblit {
10972f33561eSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
10982f33561eSMiri Korenblit 	bool need_new_sel = time_after(jiffies, mvmvif->last_esr_exit.ts +
10992f33561eSMiri Korenblit 						IWL_MVM_TRIGGER_LINK_SEL_TIME);
11002f33561eSMiri Korenblit 
11012f33561eSMiri Korenblit 	lockdep_assert_held(&mvm->mutex);
11022f33561eSMiri Korenblit 
11032f33561eSMiri Korenblit 	if (!ieee80211_vif_is_mld(vif) || !mvmvif->authorized ||
11042f33561eSMiri Korenblit 	    mvmvif->esr_active)
11052f33561eSMiri Korenblit 		return;
11062f33561eSMiri Korenblit 
11072f33561eSMiri Korenblit 	IWL_DEBUG_INFO(mvm, "EMLSR is unblocked\n");
11082f33561eSMiri Korenblit 
1109bd40215bSMiri Korenblit 	/* If we exited due to an EXIT reason, and the exit was in less than
1110bd40215bSMiri Korenblit 	 * 30 seconds, then a MLO scan was scheduled already.
1111bd40215bSMiri Korenblit 	 */
1112bd40215bSMiri Korenblit 	if (!need_new_sel &&
111346144103SMiri Korenblit 	    !(mvmvif->last_esr_exit.reason & IWL_MVM_BLOCK_ESR_REASONS)) {
111446144103SMiri Korenblit 		IWL_DEBUG_INFO(mvm, "Wait for MLO scan\n");
111546144103SMiri Korenblit 		return;
111646144103SMiri Korenblit 	}
111746144103SMiri Korenblit 
11182f33561eSMiri Korenblit 	/*
11192f33561eSMiri Korenblit 	 * If EMLSR was blocked for more than 30 seconds, or the last link
11202f33561eSMiri Korenblit 	 * selection decided to not enter EMLSR, trigger a new scan.
11212f33561eSMiri Korenblit 	 */
11222f33561eSMiri Korenblit 	if (need_new_sel || hweight16(mvmvif->link_selection_res) < 2) {
11232f33561eSMiri Korenblit 		IWL_DEBUG_INFO(mvm, "Trigger MLO scan\n");
11242f33561eSMiri Korenblit 		wiphy_delayed_work_queue(mvm->hw->wiphy,
11252f33561eSMiri Korenblit 					 &mvmvif->mlo_int_scan_wk, 0);
11262f33561eSMiri Korenblit 	/*
11272f33561eSMiri Korenblit 	 * If EMLSR was blocked for less than 30 seconds, and the last link
11282f33561eSMiri Korenblit 	 * selection decided to use EMLSR, activate EMLSR using the previous
11292f33561eSMiri Korenblit 	 * link selection result.
11302f33561eSMiri Korenblit 	 */
11312f33561eSMiri Korenblit 	} else {
11322f33561eSMiri Korenblit 		IWL_DEBUG_INFO(mvm,
11332f33561eSMiri Korenblit 			       "Use the latest link selection result: 0x%x\n",
11342f33561eSMiri Korenblit 			       mvmvif->link_selection_res);
11352f33561eSMiri Korenblit 		ieee80211_set_active_links_async(vif,
11362f33561eSMiri Korenblit 						 mvmvif->link_selection_res);
11372f33561eSMiri Korenblit 	}
11382f33561eSMiri Korenblit }
11392f33561eSMiri Korenblit 
iwl_mvm_unblock_esr(struct iwl_mvm * mvm,struct ieee80211_vif * vif,enum iwl_mvm_esr_state reason)11406cf7df9fSMiri Korenblit void iwl_mvm_unblock_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,
11416cf7df9fSMiri Korenblit 			 enum iwl_mvm_esr_state reason)
11426cf7df9fSMiri Korenblit {
11436cf7df9fSMiri Korenblit 	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
11446cf7df9fSMiri Korenblit 
11456cf7df9fSMiri Korenblit 	lockdep_assert_held(&mvm->mutex);
11466cf7df9fSMiri Korenblit 
11479574c759SMiri Korenblit 	if (!IWL_MVM_AUTO_EML_ENABLE)
11489574c759SMiri Korenblit 		return;
11499574c759SMiri Korenblit 
11506cf7df9fSMiri Korenblit 	/* This should be called only with disable reasons */
11516cf7df9fSMiri Korenblit 	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))
11526cf7df9fSMiri Korenblit 		return;
11536cf7df9fSMiri Korenblit 
11541d52e8caSMiri Korenblit 	/* No Change */
11551d52e8caSMiri Korenblit 	if (!(mvmvif->esr_disable_reason & reason))
11561d52e8caSMiri Korenblit 		return;
11571d52e8caSMiri Korenblit 
115805f10dadSYedidya Benshimol 	mvmvif->esr_disable_reason &= ~reason;
115905f10dadSYedidya Benshimol 
1160966a4d9bSDaniel Gabay 	IWL_DEBUG_INFO(mvm,
1161966a4d9bSDaniel Gabay 		       "Unblocking EMLSR mode. reason = %s (0x%x)\n",
1162966a4d9bSDaniel Gabay 		       iwl_get_esr_state_string(reason), reason);
116305f10dadSYedidya Benshimol 	iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);
11642f33561eSMiri Korenblit 
11652f33561eSMiri Korenblit 	if (!mvmvif->esr_disable_reason)
11662f33561eSMiri Korenblit 		iwl_mvm_esr_unblocked(mvm, vif);
11676cf7df9fSMiri Korenblit }
1168