brintos

brintos / linux-shallow public Read only

0
0
Text · 32.4 KiB · 2b06521 Raw
1168 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2/*3 * Copyright (C) 2022 - 2024 Intel Corporation4 */5#include "mvm.h"6#include "time-event.h"7 8#define HANDLE_ESR_REASONS(HOW)		\9	HOW(BLOCKED_PREVENTION)		\10	HOW(BLOCKED_WOWLAN)		\11	HOW(BLOCKED_TPT)		\12	HOW(BLOCKED_FW)			\13	HOW(BLOCKED_NON_BSS)		\14	HOW(BLOCKED_ROC)		\15	HOW(EXIT_MISSED_BEACON)		\16	HOW(EXIT_LOW_RSSI)		\17	HOW(EXIT_COEX)			\18	HOW(EXIT_BANDWIDTH)		\19	HOW(EXIT_CSA)			\20	HOW(EXIT_LINK_USAGE)		\21	HOW(EXIT_FAIL_ENTRY)22 23static const char *const iwl_mvm_esr_states_names[] = {24#define NAME_ENTRY(x) [ilog2(IWL_MVM_ESR_##x)] = #x,25	HANDLE_ESR_REASONS(NAME_ENTRY)26};27 28const char *iwl_get_esr_state_string(enum iwl_mvm_esr_state state)29{30	int offs = ilog2(state);31 32	if (offs >= ARRAY_SIZE(iwl_mvm_esr_states_names) ||33	    !iwl_mvm_esr_states_names[offs])34		return "UNKNOWN";35 36	return iwl_mvm_esr_states_names[offs];37}38 39static void iwl_mvm_print_esr_state(struct iwl_mvm *mvm, u32 mask)40{41#define NAME_FMT(x) "%s"42#define NAME_PR(x) (mask & IWL_MVM_ESR_##x) ? "[" #x "]" : "",43	IWL_DEBUG_INFO(mvm,44		       "EMLSR state = " HANDLE_ESR_REASONS(NAME_FMT)45		       " (0x%x)\n",46		       HANDLE_ESR_REASONS(NAME_PR)47		       mask);48#undef NAME_FMT49#undef NAME_PR50}51 52static u32 iwl_mvm_get_free_fw_link_id(struct iwl_mvm *mvm,53				       struct iwl_mvm_vif *mvm_vif)54{55	u32 i;56 57	lockdep_assert_held(&mvm->mutex);58 59	for (i = 0; i < ARRAY_SIZE(mvm->link_id_to_link_conf); i++)60		if (!rcu_access_pointer(mvm->link_id_to_link_conf[i]))61			return i;62 63	return IWL_MVM_FW_LINK_ID_INVALID;64}65 66static int iwl_mvm_link_cmd_send(struct iwl_mvm *mvm,67				 struct iwl_link_config_cmd *cmd,68				 enum iwl_ctxt_action action)69{70	int ret;71 72	cmd->action = cpu_to_le32(action);73	ret = iwl_mvm_send_cmd_pdu(mvm,74				   WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD), 0,75				   sizeof(*cmd), cmd);76	if (ret)77		IWL_ERR(mvm, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n",78			action, ret);79	return ret;80}81 82int iwl_mvm_set_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif,83			     struct ieee80211_bss_conf *link_conf)84{85	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);86	struct iwl_mvm_vif_link_info *link_info =87		mvmvif->link[link_conf->link_id];88 89	if (link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID) {90		link_info->fw_link_id = iwl_mvm_get_free_fw_link_id(mvm,91								    mvmvif);92		if (link_info->fw_link_id >=93		    ARRAY_SIZE(mvm->link_id_to_link_conf))94			return -EINVAL;95 96		rcu_assign_pointer(mvm->link_id_to_link_conf[link_info->fw_link_id],97				   link_conf);98	}99 100	return 0;101}102 103int iwl_mvm_add_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,104		     struct ieee80211_bss_conf *link_conf)105{106	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);107	unsigned int link_id = link_conf->link_id;108	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];109	struct iwl_link_config_cmd cmd = {};110	unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);111	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);112	int ret;113 114	if (WARN_ON_ONCE(!link_info))115		return -EINVAL;116 117	ret = iwl_mvm_set_link_mapping(mvm, vif, link_conf);118	if (ret)119		return ret;120 121	/* Update SF - Disable if needed. if this fails, SF might still be on122	 * while many macs are bound, which is forbidden - so fail the binding.123	 */124	if (iwl_mvm_sf_update(mvm, vif, false))125		return -EINVAL;126 127	cmd.link_id = cpu_to_le32(link_info->fw_link_id);128	cmd.mac_id = cpu_to_le32(mvmvif->id);129	cmd.spec_link_id = link_conf->link_id;130	WARN_ON_ONCE(link_info->phy_ctxt);131	cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);132 133	memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);134 135	if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)136		memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);137 138	if (cmd_ver < 2)139		cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);140 141	return iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_ADD);142}143 144struct iwl_mvm_esr_iter_data {145	struct ieee80211_vif *vif;146	unsigned int link_id;147	bool lift_block;148};149 150static void iwl_mvm_esr_vif_iterator(void *_data, u8 *mac,151				     struct ieee80211_vif *vif)152{153	struct iwl_mvm_esr_iter_data *data = _data;154	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);155	int link_id;156 157	if (ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_STATION)158		return;159 160	for_each_mvm_vif_valid_link(mvmvif, link_id) {161		struct iwl_mvm_vif_link_info *link_info =162			mvmvif->link[link_id];163		if (vif == data->vif && link_id == data->link_id)164			continue;165		if (link_info->active)166			data->lift_block = false;167	}168}169 170int iwl_mvm_esr_non_bss_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,171			     unsigned int link_id, bool active)172{173	/* An active link of a non-station vif blocks EMLSR. Upon activation174	 * block EMLSR on the bss vif. Upon deactivation, check if this link175	 * was the last non-station link active, and if so unblock the bss vif176	 */177	struct ieee80211_vif *bss_vif = iwl_mvm_get_bss_vif(mvm);178	struct iwl_mvm_esr_iter_data data = {179		.vif = vif,180		.link_id = link_id,181		.lift_block = true,182	};183 184	if (IS_ERR_OR_NULL(bss_vif))185		return 0;186 187	if (active)188		return iwl_mvm_block_esr_sync(mvm, bss_vif,189					      IWL_MVM_ESR_BLOCKED_NON_BSS);190 191	ieee80211_iterate_active_interfaces(mvm->hw,192					    IEEE80211_IFACE_ITER_NORMAL,193					    iwl_mvm_esr_vif_iterator, &data);194	if (data.lift_block) {195		mutex_lock(&mvm->mutex);196		iwl_mvm_unblock_esr(mvm, bss_vif, IWL_MVM_ESR_BLOCKED_NON_BSS);197		mutex_unlock(&mvm->mutex);198	}199 200	return 0;201}202 203int iwl_mvm_link_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif,204			 struct ieee80211_bss_conf *link_conf,205			 u32 changes, bool active)206{207	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);208	unsigned int link_id = link_conf->link_id;209	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];210	struct iwl_mvm_phy_ctxt *phyctxt;211	struct iwl_link_config_cmd cmd = {};212	u32 ht_flag, flags = 0, flags_mask = 0;213	int ret;214	unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD);215	u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1);216 217	if (WARN_ON_ONCE(!link_info ||218			 link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID))219		return -EINVAL;220 221	if (changes & LINK_CONTEXT_MODIFY_ACTIVE) {222		/* When activating a link, phy context should be valid;223		 * when deactivating a link, it also should be valid since224		 * the link was active before. So, do nothing in this case.225		 * Since a link is added first with FW_CTXT_INVALID, then we226		 * can get here in case it's removed before it was activated.227		 */228		if (!link_info->phy_ctxt)229			return 0;230 231		/* Catch early if driver tries to activate or deactivate a link232		 * twice.233		 */234		WARN_ON_ONCE(active == link_info->active);235 236		/* When deactivating a link session protection should237		 * be stopped. Also let the firmware know if we can't Tx.238		 */239		if (!active && vif->type == NL80211_IFTYPE_STATION) {240			iwl_mvm_stop_session_protection(mvm, vif);241			if (link_info->csa_block_tx) {242				cmd.block_tx = 1;243				link_info->csa_block_tx = false;244			}245		}246	}247 248	cmd.link_id = cpu_to_le32(link_info->fw_link_id);249 250	/* The phy_id, link address and listen_lmac can be modified only until251	 * the link becomes active, otherwise they will be ignored.252	 */253	phyctxt = link_info->phy_ctxt;254	if (phyctxt)255		cmd.phy_id = cpu_to_le32(phyctxt->id);256	else257		cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);258	cmd.mac_id = cpu_to_le32(mvmvif->id);259 260	memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN);261 262	cmd.active = cpu_to_le32(active);263 264	if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)265		memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN);266 267	iwl_mvm_set_fw_basic_rates(mvm, vif, link_info,268				   &cmd.cck_rates, &cmd.ofdm_rates);269 270	cmd.cck_short_preamble = cpu_to_le32(link_conf->use_short_preamble);271	cmd.short_slot = cpu_to_le32(link_conf->use_short_slot);272 273	/* The fw does not distinguish between ht and fat */274	ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT;275	iwl_mvm_set_fw_protection_flags(mvm, vif, link_conf,276					&cmd.protection_flags,277					ht_flag, LINK_PROT_FLG_TGG_PROTECT);278 279	iwl_mvm_set_fw_qos_params(mvm, vif, link_conf, cmd.ac,280				  &cmd.qos_flags);281 282 283	cmd.bi = cpu_to_le32(link_conf->beacon_int);284	cmd.dtim_interval = cpu_to_le32(link_conf->beacon_int *285					link_conf->dtim_period);286 287	if (!link_conf->he_support || iwlwifi_mod_params.disable_11ax ||288	    (vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) {289		changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS;290		goto send_cmd;291	}292 293	cmd.htc_trig_based_pkt_ext = link_conf->htc_trig_based_pkt_ext;294 295	if (link_conf->uora_exists) {296		cmd.rand_alloc_ecwmin =297			link_conf->uora_ocw_range & 0x7;298		cmd.rand_alloc_ecwmax =299			(link_conf->uora_ocw_range >> 3) & 0x7;300	}301 302	/* ap_sta may be NULL if we're disconnecting */303	if (changes & LINK_CONTEXT_MODIFY_HE_PARAMS && mvmvif->ap_sta) {304		struct ieee80211_link_sta *link_sta =305			link_sta_dereference_check(mvmvif->ap_sta, link_id);306 307		if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&308		    link_sta->he_cap.he_cap_elem.mac_cap_info[5] &309		    IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)310			cmd.ul_mu_data_disable = 1;311	}312 313	/* TODO  how to set ndp_fdbk_buff_th_exp? */314 315	if (iwl_mvm_set_fw_mu_edca_params(mvm, mvmvif->link[link_id],316					  &cmd.trig_based_txf[0])) {317		flags |= LINK_FLG_MU_EDCA_CW;318		flags_mask |= LINK_FLG_MU_EDCA_CW;319	}320 321	if (changes & LINK_CONTEXT_MODIFY_EHT_PARAMS) {322		struct ieee80211_chanctx_conf *ctx;323		struct cfg80211_chan_def *def = NULL;324 325		rcu_read_lock();326		ctx = rcu_dereference(link_conf->chanctx_conf);327		if (ctx)328			def = iwl_mvm_chanctx_def(mvm, ctx);329 330		if (iwlwifi_mod_params.disable_11be ||331		    !link_conf->eht_support || !def ||332		    iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) >= 6)333			changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;334		else335			cmd.puncture_mask = cpu_to_le16(def->punctured);336		rcu_read_unlock();337	}338 339	cmd.bss_color = link_conf->he_bss_color.color;340 341	if (!link_conf->he_bss_color.enabled) {342		flags |= LINK_FLG_BSS_COLOR_DIS;343		flags_mask |= LINK_FLG_BSS_COLOR_DIS;344	}345 346	cmd.frame_time_rts_th = cpu_to_le16(link_conf->frame_time_rts_th);347 348	/* Block 26-tone RU OFDMA transmissions */349	if (link_info->he_ru_2mhz_block) {350		flags |= LINK_FLG_RU_2MHZ_BLOCK;351		flags_mask |= LINK_FLG_RU_2MHZ_BLOCK;352	}353 354	if (link_conf->nontransmitted) {355		ether_addr_copy(cmd.ref_bssid_addr,356				link_conf->transmitter_bssid);357		cmd.bssid_index = link_conf->bssid_index;358	}359 360send_cmd:361	cmd.modify_mask = cpu_to_le32(changes);362	cmd.flags = cpu_to_le32(flags);363	cmd.flags_mask = cpu_to_le32(flags_mask);364	cmd.spec_link_id = link_conf->link_id;365	if (cmd_ver < 2)366		cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac);367 368	ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_MODIFY);369	if (!ret && (changes & LINK_CONTEXT_MODIFY_ACTIVE))370		link_info->active = active;371 372	return ret;373}374 375int iwl_mvm_unset_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif,376			       struct ieee80211_bss_conf *link_conf)377{378	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);379	struct iwl_mvm_vif_link_info *link_info =380		mvmvif->link[link_conf->link_id];381 382	/* mac80211 thought we have the link, but it was never configured */383	if (WARN_ON(!link_info ||384		    link_info->fw_link_id >=385		    ARRAY_SIZE(mvm->link_id_to_link_conf)))386		return -EINVAL;387 388	RCU_INIT_POINTER(mvm->link_id_to_link_conf[link_info->fw_link_id],389			 NULL);390	return 0;391}392 393int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,394			struct ieee80211_bss_conf *link_conf)395{396	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);397	unsigned int link_id = link_conf->link_id;398	struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id];399	struct iwl_link_config_cmd cmd = {};400	int ret;401 402	ret = iwl_mvm_unset_link_mapping(mvm, vif, link_conf);403	if (ret)404		return 0;405 406	cmd.link_id = cpu_to_le32(link_info->fw_link_id);407	link_info->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID;408	cmd.spec_link_id = link_conf->link_id;409	cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID);410 411	ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_REMOVE);412 413	if (!ret)414		if (iwl_mvm_sf_update(mvm, vif, true))415			IWL_ERR(mvm, "Failed to update SF state\n");416 417	return ret;418}419 420/* link should be deactivated before removal, so in most cases we need to421 * perform these two operations together422 */423int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif,424			 struct ieee80211_bss_conf *link_conf)425{426	int ret;427 428	ret = iwl_mvm_link_changed(mvm, vif, link_conf,429				   LINK_CONTEXT_MODIFY_ACTIVE, false);430	if (ret)431		return ret;432 433	ret = iwl_mvm_remove_link(mvm, vif, link_conf);434	if (ret)435		return ret;436 437	return ret;438}439 440struct iwl_mvm_rssi_to_grade {441	s8 rssi[2];442	u16 grade;443};444 445#define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \446	{ \447		.rssi = {_lb, _hb_uhb}, \448		.grade = _grade \449	}450 451/*452 * This array must be sorted by increasing RSSI for proper functionality.453 * The grades are actually estimated throughput, represented as fixed-point454 * with a scale factor of 1/10.455 */456static const struct iwl_mvm_rssi_to_grade rssi_to_grade_map[] = {457	RSSI_TO_GRADE_LINE(-85, -89, 177),458	RSSI_TO_GRADE_LINE(-83, -86, 344),459	RSSI_TO_GRADE_LINE(-82, -85, 516),460	RSSI_TO_GRADE_LINE(-80, -83, 688),461	RSSI_TO_GRADE_LINE(-77, -79, 1032),462	RSSI_TO_GRADE_LINE(-73, -76, 1376),463	RSSI_TO_GRADE_LINE(-70, -74, 1548),464	RSSI_TO_GRADE_LINE(-69, -72, 1750),465	RSSI_TO_GRADE_LINE(-65, -68, 2064),466	RSSI_TO_GRADE_LINE(-61, -66, 2294),467	RSSI_TO_GRADE_LINE(-58, -61, 2580),468	RSSI_TO_GRADE_LINE(-55, -58, 2868),469	RSSI_TO_GRADE_LINE(-46, -55, 3098),470	RSSI_TO_GRADE_LINE(-43, -54, 3442)471};472 473#define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)474 475#define DEFAULT_CHAN_LOAD_LB	30476#define DEFAULT_CHAN_LOAD_HB	15477#define DEFAULT_CHAN_LOAD_UHB	0478 479/* Factors calculation is done with fixed-point with a scaling factor of 1/256 */480#define SCALE_FACTOR 256481 482/* Convert a percentage from [0,100] to [0,255] */483#define NORMALIZE_PERCENT_TO_255(percentage) ((percentage) * SCALE_FACTOR / 100)484 485static unsigned int486iwl_mvm_get_puncturing_factor(const struct ieee80211_bss_conf *link_conf)487{488	enum nl80211_chan_width chan_width =489		link_conf->chanreq.oper.width;490	int mhz = nl80211_chan_width_to_mhz(chan_width);491	unsigned int n_subchannels, n_punctured, puncturing_penalty;492 493	if (WARN_ONCE(mhz < 20 || mhz > 320,494		      "Invalid channel width : (%d)\n", mhz))495		return SCALE_FACTOR;496 497	/* No puncturing, no penalty */498	if (mhz < 80)499		return SCALE_FACTOR;500 501	/* total number of subchannels */502	n_subchannels = mhz / 20;503	/* how many of these are punctured */504	n_punctured = hweight16(link_conf->chanreq.oper.punctured);505 506	puncturing_penalty = n_punctured * SCALE_FACTOR / n_subchannels;507	return SCALE_FACTOR - puncturing_penalty;508}509 510static unsigned int511iwl_mvm_get_chan_load(struct ieee80211_bss_conf *link_conf)512{513	struct ieee80211_vif *vif = link_conf->vif;514	struct iwl_mvm_vif_link_info *mvm_link =515		iwl_mvm_vif_from_mac80211(link_conf->vif)->link[link_conf->link_id];516	const struct element *bss_load_elem;517	const struct ieee80211_bss_load_elem *bss_load;518	enum nl80211_band band = link_conf->chanreq.oper.chan->band;519	const struct cfg80211_bss_ies *ies;520	unsigned int chan_load;521	u32 chan_load_by_us;522 523	rcu_read_lock();524	if (ieee80211_vif_link_active(vif, link_conf->link_id))525		ies = rcu_dereference(link_conf->bss->beacon_ies);526	else527		ies = rcu_dereference(link_conf->bss->ies);528 529	if (ies)530		bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD,531						   ies->data, ies->len);532	else533		bss_load_elem = NULL;534 535	/* If there isn't BSS Load element, take the defaults */536	if (!bss_load_elem ||537	    bss_load_elem->datalen != sizeof(*bss_load)) {538		rcu_read_unlock();539		switch (band) {540		case NL80211_BAND_2GHZ:541			chan_load = DEFAULT_CHAN_LOAD_LB;542			break;543		case NL80211_BAND_5GHZ:544			chan_load = DEFAULT_CHAN_LOAD_HB;545			break;546		case NL80211_BAND_6GHZ:547			chan_load = DEFAULT_CHAN_LOAD_UHB;548			break;549		default:550			chan_load = 0;551			break;552		}553		/* The defaults are given in percentage */554		return NORMALIZE_PERCENT_TO_255(chan_load);555	}556 557	bss_load = (const void *)bss_load_elem->data;558	/* Channel util is in range 0-255 */559	chan_load = bss_load->channel_util;560	rcu_read_unlock();561 562	if (!mvm_link || !mvm_link->active)563		return chan_load;564 565	if (WARN_ONCE(!mvm_link->phy_ctxt,566		      "Active link (%u) without phy ctxt assigned!\n",567		      link_conf->link_id))568		return chan_load;569 570	/* channel load by us is given in percentage */571	chan_load_by_us =572		NORMALIZE_PERCENT_TO_255(mvm_link->phy_ctxt->channel_load_by_us);573 574	/* Use only values that firmware sends that can possibly be valid */575	if (chan_load_by_us <= chan_load)576		chan_load -= chan_load_by_us;577 578	return chan_load;579}580 581static unsigned int582iwl_mvm_get_chan_load_factor(struct ieee80211_bss_conf *link_conf)583{584	return SCALE_FACTOR - iwl_mvm_get_chan_load(link_conf);585}586 587/* This function calculates the grade of a link. Returns 0 in error case */588VISIBLE_IF_IWLWIFI_KUNIT589unsigned int iwl_mvm_get_link_grade(struct ieee80211_bss_conf *link_conf)590{591	enum nl80211_band band;592	int i, rssi_idx;593	s32 link_rssi;594	unsigned int grade = MAX_GRADE;595 596	if (WARN_ON_ONCE(!link_conf))597		return 0;598 599	band = link_conf->chanreq.oper.chan->band;600	if (WARN_ONCE(band != NL80211_BAND_2GHZ &&601		      band != NL80211_BAND_5GHZ &&602		      band != NL80211_BAND_6GHZ,603		      "Invalid band (%u)\n", band))604		return 0;605 606	link_rssi = MBM_TO_DBM(link_conf->bss->signal);607	/*608	 * For 6 GHz the RSSI of the beacons is lower than609	 * the RSSI of the data.610	 */611	if (band == NL80211_BAND_6GHZ)612		link_rssi += 4;613 614	rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;615 616	/* No valid RSSI - take the lowest grade */617	if (!link_rssi)618		link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];619 620	/* Get grade based on RSSI */621	for (i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {622		const struct iwl_mvm_rssi_to_grade *line =623			&rssi_to_grade_map[i];624 625		if (link_rssi > line->rssi[rssi_idx])626			continue;627		grade = line->grade;628		break;629	}630 631	/* apply the channel load and puncturing factors */632	grade = grade * iwl_mvm_get_chan_load_factor(link_conf) / SCALE_FACTOR;633	grade = grade * iwl_mvm_get_puncturing_factor(link_conf) / SCALE_FACTOR;634	return grade;635}636EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_get_link_grade);637 638static639u8 iwl_mvm_set_link_selection_data(struct ieee80211_vif *vif,640				   struct iwl_mvm_link_sel_data *data,641				   unsigned long usable_links,642				   u8 *best_link_idx)643{644	u8 n_data = 0;645	u16 max_grade = 0;646	unsigned long link_id;647 648	/* TODO: don't select links that weren't discovered in the last scan */649	for_each_set_bit(link_id, &usable_links, IEEE80211_MLD_MAX_NUM_LINKS) {650		struct ieee80211_bss_conf *link_conf =651			link_conf_dereference_protected(vif, link_id);652 653		if (WARN_ON_ONCE(!link_conf))654			continue;655 656		data[n_data].link_id = link_id;657		data[n_data].chandef = &link_conf->chanreq.oper;658		data[n_data].signal = link_conf->bss->signal / 100;659		data[n_data].grade = iwl_mvm_get_link_grade(link_conf);660 661		if (data[n_data].grade > max_grade) {662			max_grade = data[n_data].grade;663			*best_link_idx = n_data;664		}665		n_data++;666	}667 668	return n_data;669}670 671struct iwl_mvm_bw_to_rssi_threshs {672	s8 low;673	s8 high;674};675 676#define BW_TO_RSSI_THRESHOLDS(_bw)				\677	[IWL_PHY_CHANNEL_MODE ## _bw] = {			\678		.low = IWL_MVM_LOW_RSSI_THRESH_##_bw##MHZ,	\679		.high = IWL_MVM_HIGH_RSSI_THRESH_##_bw##MHZ	\680	}681 682s8 iwl_mvm_get_esr_rssi_thresh(struct iwl_mvm *mvm,683			       const struct cfg80211_chan_def *chandef,684			       bool low)685{686	const struct iwl_mvm_bw_to_rssi_threshs bw_to_rssi_threshs_map[] = {687		BW_TO_RSSI_THRESHOLDS(20),688		BW_TO_RSSI_THRESHOLDS(40),689		BW_TO_RSSI_THRESHOLDS(80),690		BW_TO_RSSI_THRESHOLDS(160)691		/* 320 MHz has the same thresholds as 20 MHz */692	};693	const struct iwl_mvm_bw_to_rssi_threshs *threshs;694	u8 chan_width = iwl_mvm_get_channel_width(chandef);695 696	if (WARN_ON(chandef->chan->band != NL80211_BAND_2GHZ &&697		    chandef->chan->band != NL80211_BAND_5GHZ &&698		    chandef->chan->band != NL80211_BAND_6GHZ))699		return S8_MAX;700 701	/* 6 GHz will always use 20 MHz thresholds, regardless of the BW */702	if (chan_width == IWL_PHY_CHANNEL_MODE320)703		chan_width = IWL_PHY_CHANNEL_MODE20;704 705	threshs = &bw_to_rssi_threshs_map[chan_width];706 707	return low ? threshs->low : threshs->high;708}709 710static u32711iwl_mvm_esr_disallowed_with_link(struct iwl_mvm *mvm,712				 struct ieee80211_vif *vif,713				 const struct iwl_mvm_link_sel_data *link,714				 bool primary)715{716	struct wiphy *wiphy = mvm->hw->wiphy;717	struct ieee80211_bss_conf *conf;718	enum iwl_mvm_esr_state ret = 0;719	s8 thresh;720 721	conf = wiphy_dereference(wiphy, vif->link_conf[link->link_id]);722	if (WARN_ON_ONCE(!conf))723		return false;724 725	/* BT Coex effects eSR mode only if one of the links is on LB */726	if (link->chandef->chan->band == NL80211_BAND_2GHZ &&727	    (!iwl_mvm_bt_coex_calculate_esr_mode(mvm, vif, link->signal,728						 primary)))729		ret |= IWL_MVM_ESR_EXIT_COEX;730 731	thresh = iwl_mvm_get_esr_rssi_thresh(mvm, link->chandef,732					     false);733 734	if (link->signal < thresh)735		ret |= IWL_MVM_ESR_EXIT_LOW_RSSI;736 737	if (conf->csa_active)738		ret |= IWL_MVM_ESR_EXIT_CSA;739 740	if (ret) {741		IWL_DEBUG_INFO(mvm,742			       "Link %d is not allowed for esr\n",743			       link->link_id);744		iwl_mvm_print_esr_state(mvm, ret);745	}746	return ret;747}748 749VISIBLE_IF_IWLWIFI_KUNIT750bool iwl_mvm_mld_valid_link_pair(struct ieee80211_vif *vif,751				 const struct iwl_mvm_link_sel_data *a,752				 const struct iwl_mvm_link_sel_data *b)753{754	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);755	struct iwl_mvm *mvm = mvmvif->mvm;756	enum iwl_mvm_esr_state ret = 0;757 758	/* Per-link considerations */759	if (iwl_mvm_esr_disallowed_with_link(mvm, vif, a, true) ||760	    iwl_mvm_esr_disallowed_with_link(mvm, vif, b, false))761		return false;762 763	if (a->chandef->width != b->chandef->width ||764	    !(a->chandef->chan->band == NL80211_BAND_6GHZ &&765	      b->chandef->chan->band == NL80211_BAND_5GHZ))766		ret |= IWL_MVM_ESR_EXIT_BANDWIDTH;767 768	if (ret) {769		IWL_DEBUG_INFO(mvm,770			       "Links %d and %d are not a valid pair for EMLSR\n",771			       a->link_id, b->link_id);772		iwl_mvm_print_esr_state(mvm, ret);773		return false;774	}775 776	return true;777 778}779EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mvm_mld_valid_link_pair);780 781/*782 * Returns the combined eSR grade of two given links.783 * Returns 0 if eSR is not allowed with these 2 links.784 */785static786unsigned int iwl_mvm_get_esr_grade(struct ieee80211_vif *vif,787				   const struct iwl_mvm_link_sel_data *a,788				   const struct iwl_mvm_link_sel_data *b,789				   u8 *primary_id)790{791	struct ieee80211_bss_conf *primary_conf;792	struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy;793	unsigned int primary_load;794 795	lockdep_assert_wiphy(wiphy);796 797	/* a is always primary, b is always secondary */798	if (b->grade > a->grade)799		swap(a, b);800 801	*primary_id = a->link_id;802 803	if (!iwl_mvm_mld_valid_link_pair(vif, a, b))804		return 0;805 806	primary_conf = wiphy_dereference(wiphy, vif->link_conf[*primary_id]);807 808	if (WARN_ON_ONCE(!primary_conf))809		return 0;810 811	primary_load = iwl_mvm_get_chan_load(primary_conf);812 813	return a->grade +814		((b->grade * primary_load) / SCALE_FACTOR);815}816 817void iwl_mvm_select_links(struct iwl_mvm *mvm, struct ieee80211_vif *vif)818{819	struct iwl_mvm_link_sel_data data[IEEE80211_MLD_MAX_NUM_LINKS];820	struct iwl_mvm_link_sel_data *best_link;821	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);822	u32 max_active_links = iwl_mvm_max_active_links(mvm, vif);823	u16 usable_links = ieee80211_vif_usable_links(vif);824	u8 best, primary_link, best_in_pair, n_data;825	u16 max_esr_grade = 0, new_active_links;826 827	lockdep_assert_wiphy(mvm->hw->wiphy);828 829	if (!mvmvif->authorized || !ieee80211_vif_is_mld(vif))830		return;831 832	if (!IWL_MVM_AUTO_EML_ENABLE)833		return;834 835	/* The logic below is a simple version that doesn't suit more than 2836	 * links837	 */838	WARN_ON_ONCE(max_active_links > 2);839 840	n_data = iwl_mvm_set_link_selection_data(vif, data, usable_links,841						 &best);842 843	if (WARN(!n_data, "Couldn't find a valid grade for any link!\n"))844		return;845 846	best_link = &data[best];847	primary_link = best_link->link_id;848	new_active_links = BIT(best_link->link_id);849 850	/* eSR is not supported/blocked, or only one usable link */851	if (max_active_links == 1 || !iwl_mvm_vif_has_esr_cap(mvm, vif) ||852	    mvmvif->esr_disable_reason || n_data == 1)853		goto set_active;854 855	for (u8 a = 0; a < n_data; a++)856		for (u8 b = a + 1; b < n_data; b++) {857			u16 esr_grade = iwl_mvm_get_esr_grade(vif, &data[a],858							      &data[b],859							      &best_in_pair);860 861			if (esr_grade <= max_esr_grade)862				continue;863 864			max_esr_grade = esr_grade;865			primary_link = best_in_pair;866			new_active_links = BIT(data[a].link_id) |867					   BIT(data[b].link_id);868		}869 870	/* No valid pair was found, go with the best link */871	if (hweight16(new_active_links) <= 1)872		goto set_active;873 874	/* For equal grade - prefer EMLSR */875	if (best_link->grade > max_esr_grade) {876		primary_link = best_link->link_id;877		new_active_links = BIT(best_link->link_id);878	}879set_active:880	IWL_DEBUG_INFO(mvm, "Link selection result: 0x%x. Primary = %d\n",881		       new_active_links, primary_link);882	ieee80211_set_active_links_async(vif, new_active_links);883	mvmvif->link_selection_res = new_active_links;884	mvmvif->link_selection_primary = primary_link;885}886 887u8 iwl_mvm_get_primary_link(struct ieee80211_vif *vif)888{889	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);890 891	/* relevant data is written with both locks held, so read with either */892	lockdep_assert(lockdep_is_held(&mvmvif->mvm->mutex) ||893		       lockdep_is_held(&mvmvif->mvm->hw->wiphy->mtx));894 895	if (!ieee80211_vif_is_mld(vif))896		return 0;897 898	/* In AP mode, there is no primary link */899	if (vif->type == NL80211_IFTYPE_AP)900		return __ffs(vif->active_links);901 902	if (mvmvif->esr_active &&903	    !WARN_ON(!(BIT(mvmvif->primary_link) & vif->active_links)))904		return mvmvif->primary_link;905 906	return __ffs(vif->active_links);907}908 909/*910 * For non-MLO/single link, this will return the deflink/single active link,911 * respectively912 */913u8 iwl_mvm_get_other_link(struct ieee80211_vif *vif, u8 link_id)914{915	switch (hweight16(vif->active_links)) {916	case 0:917		return 0;918	default:919		WARN_ON(1);920		fallthrough;921	case 1:922		return __ffs(vif->active_links);923	case 2:924		return __ffs(vif->active_links & ~BIT(link_id));925	}926}927 928/* Reasons that can cause esr prevention */929#define IWL_MVM_ESR_PREVENT_REASONS	IWL_MVM_ESR_EXIT_MISSED_BEACON930#define IWL_MVM_PREVENT_ESR_TIMEOUT	(HZ * 400)931#define IWL_MVM_ESR_PREVENT_SHORT	(HZ * 300)932#define IWL_MVM_ESR_PREVENT_LONG	(HZ * 600)933 934static bool iwl_mvm_check_esr_prevention(struct iwl_mvm *mvm,935					 struct iwl_mvm_vif *mvmvif,936					 enum iwl_mvm_esr_state reason)937{938	bool timeout_expired = time_after(jiffies,939					  mvmvif->last_esr_exit.ts +940					  IWL_MVM_PREVENT_ESR_TIMEOUT);941	unsigned long delay;942 943	lockdep_assert_held(&mvm->mutex);944 945	/* Only handle reasons that can cause prevention */946	if (!(reason & IWL_MVM_ESR_PREVENT_REASONS))947		return false;948 949	/*950	 * Reset the counter if more than 400 seconds have passed between one951	 * exit and the other, or if we exited due to a different reason.952	 * Will also reset the counter after the long prevention is done.953	 */954	if (timeout_expired || mvmvif->last_esr_exit.reason != reason) {955		mvmvif->exit_same_reason_count = 1;956		return false;957	}958 959	mvmvif->exit_same_reason_count++;960	if (WARN_ON(mvmvif->exit_same_reason_count < 2 ||961		    mvmvif->exit_same_reason_count > 3))962		return false;963 964	mvmvif->esr_disable_reason |= IWL_MVM_ESR_BLOCKED_PREVENTION;965 966	/*967	 * For the second exit, use a short prevention, and for the third one,968	 * use a long prevention.969	 */970	delay = mvmvif->exit_same_reason_count == 2 ?971		IWL_MVM_ESR_PREVENT_SHORT :972		IWL_MVM_ESR_PREVENT_LONG;973 974	IWL_DEBUG_INFO(mvm,975		       "Preventing EMLSR for %ld seconds due to %u exits with the reason = %s (0x%x)\n",976		       delay / HZ, mvmvif->exit_same_reason_count,977		       iwl_get_esr_state_string(reason), reason);978 979	wiphy_delayed_work_queue(mvm->hw->wiphy,980				 &mvmvif->prevent_esr_done_wk, delay);981	return true;982}983 984#define IWL_MVM_TRIGGER_LINK_SEL_TIME (IWL_MVM_TRIGGER_LINK_SEL_TIME_SEC * HZ)985 986/* API to exit eSR mode */987void iwl_mvm_exit_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,988		      enum iwl_mvm_esr_state reason,989		      u8 link_to_keep)990{991	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);992	u16 new_active_links;993	bool prevented;994 995	lockdep_assert_held(&mvm->mutex);996 997	if (!IWL_MVM_AUTO_EML_ENABLE)998		return;999 1000	/* Nothing to do */1001	if (!mvmvif->esr_active)1002		return;1003 1004	if (WARN_ON(!ieee80211_vif_is_mld(vif) || !mvmvif->authorized))1005		return;1006 1007	if (WARN_ON(!(vif->active_links & BIT(link_to_keep))))1008		link_to_keep = __ffs(vif->active_links);1009 1010	new_active_links = BIT(link_to_keep);1011	IWL_DEBUG_INFO(mvm,1012		       "Exiting EMLSR. reason = %s (0x%x). Current active links=0x%x, new active links = 0x%x\n",1013		       iwl_get_esr_state_string(reason), reason,1014		       vif->active_links, new_active_links);1015 1016	ieee80211_set_active_links_async(vif, new_active_links);1017 1018	/* Prevent EMLSR if needed */1019	prevented = iwl_mvm_check_esr_prevention(mvm, mvmvif, reason);1020 1021	/* Remember why and when we exited EMLSR */1022	mvmvif->last_esr_exit.ts = jiffies;1023	mvmvif->last_esr_exit.reason = reason;1024 1025	/*1026	 * If EMLSR is prevented now - don't try to get back to EMLSR.1027	 * If we exited due to a blocking event, we will try to get back to1028	 * EMLSR when the corresponding unblocking event will happen.1029	 */1030	if (prevented || reason & IWL_MVM_BLOCK_ESR_REASONS)1031		return;1032 1033	/* If EMLSR is not blocked - try enabling it again in 30 seconds */1034	wiphy_delayed_work_queue(mvm->hw->wiphy,1035				 &mvmvif->mlo_int_scan_wk,1036				 round_jiffies_relative(IWL_MVM_TRIGGER_LINK_SEL_TIME));1037}1038 1039void iwl_mvm_block_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,1040		       enum iwl_mvm_esr_state reason,1041		       u8 link_to_keep)1042{1043	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);1044 1045	lockdep_assert_held(&mvm->mutex);1046 1047	if (!IWL_MVM_AUTO_EML_ENABLE)1048		return;1049 1050	/* This should be called only with disable reasons */1051	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1052		return;1053 1054	if (mvmvif->esr_disable_reason & reason)1055		return;1056 1057	IWL_DEBUG_INFO(mvm,1058		       "Blocking EMLSR mode. reason = %s (0x%x)\n",1059		       iwl_get_esr_state_string(reason), reason);1060 1061	mvmvif->esr_disable_reason |= reason;1062 1063	iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);1064 1065	iwl_mvm_exit_esr(mvm, vif, reason, link_to_keep);1066}1067 1068int iwl_mvm_block_esr_sync(struct iwl_mvm *mvm, struct ieee80211_vif *vif,1069			   enum iwl_mvm_esr_state reason)1070{1071	int primary_link = iwl_mvm_get_primary_link(vif);1072	int ret;1073 1074	if (!IWL_MVM_AUTO_EML_ENABLE || !ieee80211_vif_is_mld(vif))1075		return 0;1076 1077	/* This should be called only with blocking reasons */1078	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1079		return 0;1080 1081	/* leave ESR immediately, not only async with iwl_mvm_block_esr() */1082	ret = ieee80211_set_active_links(vif, BIT(primary_link));1083	if (ret)1084		return ret;1085 1086	mutex_lock(&mvm->mutex);1087	/* only additionally block for consistency and to avoid concurrency */1088	iwl_mvm_block_esr(mvm, vif, reason, primary_link);1089	mutex_unlock(&mvm->mutex);1090 1091	return 0;1092}1093 1094static void iwl_mvm_esr_unblocked(struct iwl_mvm *mvm,1095				  struct ieee80211_vif *vif)1096{1097	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);1098	bool need_new_sel = time_after(jiffies, mvmvif->last_esr_exit.ts +1099						IWL_MVM_TRIGGER_LINK_SEL_TIME);1100 1101	lockdep_assert_held(&mvm->mutex);1102 1103	if (!ieee80211_vif_is_mld(vif) || !mvmvif->authorized ||1104	    mvmvif->esr_active)1105		return;1106 1107	IWL_DEBUG_INFO(mvm, "EMLSR is unblocked\n");1108 1109	/* If we exited due to an EXIT reason, and the exit was in less than1110	 * 30 seconds, then a MLO scan was scheduled already.1111	 */1112	if (!need_new_sel &&1113	    !(mvmvif->last_esr_exit.reason & IWL_MVM_BLOCK_ESR_REASONS)) {1114		IWL_DEBUG_INFO(mvm, "Wait for MLO scan\n");1115		return;1116	}1117 1118	/*1119	 * If EMLSR was blocked for more than 30 seconds, or the last link1120	 * selection decided to not enter EMLSR, trigger a new scan.1121	 */1122	if (need_new_sel || hweight16(mvmvif->link_selection_res) < 2) {1123		IWL_DEBUG_INFO(mvm, "Trigger MLO scan\n");1124		wiphy_delayed_work_queue(mvm->hw->wiphy,1125					 &mvmvif->mlo_int_scan_wk, 0);1126	/*1127	 * If EMLSR was blocked for less than 30 seconds, and the last link1128	 * selection decided to use EMLSR, activate EMLSR using the previous1129	 * link selection result.1130	 */1131	} else {1132		IWL_DEBUG_INFO(mvm,1133			       "Use the latest link selection result: 0x%x\n",1134			       mvmvif->link_selection_res);1135		ieee80211_set_active_links_async(vif,1136						 mvmvif->link_selection_res);1137	}1138}1139 1140void iwl_mvm_unblock_esr(struct iwl_mvm *mvm, struct ieee80211_vif *vif,1141			 enum iwl_mvm_esr_state reason)1142{1143	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);1144 1145	lockdep_assert_held(&mvm->mutex);1146 1147	if (!IWL_MVM_AUTO_EML_ENABLE)1148		return;1149 1150	/* This should be called only with disable reasons */1151	if (WARN_ON(!(reason & IWL_MVM_BLOCK_ESR_REASONS)))1152		return;1153 1154	/* No Change */1155	if (!(mvmvif->esr_disable_reason & reason))1156		return;1157 1158	mvmvif->esr_disable_reason &= ~reason;1159 1160	IWL_DEBUG_INFO(mvm,1161		       "Unblocking EMLSR mode. reason = %s (0x%x)\n",1162		       iwl_get_esr_state_string(reason), reason);1163	iwl_mvm_print_esr_state(mvm, mvmvif->esr_disable_reason);1164 1165	if (!mvmvif->esr_disable_reason)1166		iwl_mvm_esr_unblocked(mvm, vif);1167}1168