xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/link.c (revision 4f9786035f9e519db41375818e1d0b5f20da2f10)
1d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2d1e879ecSMiri Korenblit /*
3d1e879ecSMiri Korenblit  * Copyright (C) 2024-2025 Intel Corporation
4d1e879ecSMiri Korenblit  */
5d1e879ecSMiri Korenblit 
6d1e879ecSMiri Korenblit #include "constants.h"
7d1e879ecSMiri Korenblit #include "link.h"
8d1e879ecSMiri Korenblit #include "iface.h"
9d1e879ecSMiri Korenblit #include "mlo.h"
10d1e879ecSMiri Korenblit #include "hcmd.h"
11d1e879ecSMiri Korenblit #include "phy.h"
12d1e879ecSMiri Korenblit #include "fw/api/rs.h"
13d1e879ecSMiri Korenblit #include "fw/api/txq.h"
14d1e879ecSMiri Korenblit #include "fw/api/mac.h"
15d1e879ecSMiri Korenblit 
16d1e879ecSMiri Korenblit #include "fw/api/context.h"
17d1e879ecSMiri Korenblit #include "fw/dbg.h"
18d1e879ecSMiri Korenblit 
19d1e879ecSMiri Korenblit static int iwl_mld_send_link_cmd(struct iwl_mld *mld,
20d1e879ecSMiri Korenblit 				 struct iwl_link_config_cmd *cmd,
21d1e879ecSMiri Korenblit 				 enum iwl_ctxt_action action)
22d1e879ecSMiri Korenblit {
23d1e879ecSMiri Korenblit 	int ret;
24d1e879ecSMiri Korenblit 
25d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
26d1e879ecSMiri Korenblit 
27d1e879ecSMiri Korenblit 	cmd->action = cpu_to_le32(action);
28d1e879ecSMiri Korenblit 	ret = iwl_mld_send_cmd_pdu(mld,
29d1e879ecSMiri Korenblit 				   WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD),
30d1e879ecSMiri Korenblit 				   cmd);
31d1e879ecSMiri Korenblit 	if (ret)
32d1e879ecSMiri Korenblit 		IWL_ERR(mld, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n",
33d1e879ecSMiri Korenblit 			action, ret);
34d1e879ecSMiri Korenblit 	return ret;
35d1e879ecSMiri Korenblit }
36d1e879ecSMiri Korenblit 
37d1e879ecSMiri Korenblit static int iwl_mld_add_link_to_fw(struct iwl_mld *mld,
38d1e879ecSMiri Korenblit 				  struct ieee80211_bss_conf *link_conf)
39d1e879ecSMiri Korenblit {
40d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = link_conf->vif;
41d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
42d1e879ecSMiri Korenblit 	struct iwl_mld_link *link = iwl_mld_link_from_mac80211(link_conf);
43d1e879ecSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
44d1e879ecSMiri Korenblit 
45d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
46d1e879ecSMiri Korenblit 
47d1e879ecSMiri Korenblit 	if (WARN_ON(!link))
48d1e879ecSMiri Korenblit 		return -EINVAL;
49d1e879ecSMiri Korenblit 
50d1e879ecSMiri Korenblit 	cmd.link_id = cpu_to_le32(link->fw_id);
51d1e879ecSMiri Korenblit 	cmd.mac_id = cpu_to_le32(mld_vif->fw_id);
52d1e879ecSMiri Korenblit 	cmd.spec_link_id = link_conf->link_id;
53d1e879ecSMiri Korenblit 	cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);
54d1e879ecSMiri Korenblit 
55d1e879ecSMiri Korenblit 	ether_addr_copy(cmd.local_link_addr, link_conf->addr);
56d1e879ecSMiri Korenblit 
57d1e879ecSMiri Korenblit 	if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid)
58d1e879ecSMiri Korenblit 		ether_addr_copy(cmd.ibss_bssid_addr, link_conf->bssid);
59d1e879ecSMiri Korenblit 
60d1e879ecSMiri Korenblit 	return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_ADD);
61d1e879ecSMiri Korenblit }
62d1e879ecSMiri Korenblit 
63d1e879ecSMiri Korenblit /* Get the basic rates of the used band and add the mandatory ones */
64d1e879ecSMiri Korenblit static void iwl_mld_fill_rates(struct iwl_mld *mld,
65d1e879ecSMiri Korenblit 			       struct ieee80211_bss_conf *link,
66d1e879ecSMiri Korenblit 			       struct ieee80211_chanctx_conf *chan_ctx,
67d1e879ecSMiri Korenblit 			       __le32 *cck_rates, __le32 *ofdm_rates)
68d1e879ecSMiri Korenblit {
69d1e879ecSMiri Korenblit 	struct cfg80211_chan_def *chandef =
70fd04fbeeSJohannes Berg 		iwl_mld_get_chandef_from_chanctx(mld, chan_ctx);
71d1e879ecSMiri Korenblit 	struct ieee80211_supported_band *sband =
72d1e879ecSMiri Korenblit 		mld->hw->wiphy->bands[chandef->chan->band];
73d1e879ecSMiri Korenblit 	unsigned long basic = link->basic_rates;
74d1e879ecSMiri Korenblit 	int lowest_present_ofdm = 100;
75d1e879ecSMiri Korenblit 	int lowest_present_cck = 100;
76d1e879ecSMiri Korenblit 	u32 cck = 0;
77d1e879ecSMiri Korenblit 	u32 ofdm = 0;
78d1e879ecSMiri Korenblit 	int i;
79d1e879ecSMiri Korenblit 
80d1e879ecSMiri Korenblit 	for_each_set_bit(i, &basic, BITS_PER_LONG) {
81d1e879ecSMiri Korenblit 		int hw = sband->bitrates[i].hw_value;
82d1e879ecSMiri Korenblit 
83d1e879ecSMiri Korenblit 		if (hw >= IWL_FIRST_OFDM_RATE) {
84d1e879ecSMiri Korenblit 			ofdm |= BIT(hw - IWL_FIRST_OFDM_RATE);
85d1e879ecSMiri Korenblit 			if (lowest_present_ofdm > hw)
86d1e879ecSMiri Korenblit 				lowest_present_ofdm = hw;
87d1e879ecSMiri Korenblit 		} else {
88d1e879ecSMiri Korenblit 			BUILD_BUG_ON(IWL_FIRST_CCK_RATE != 0);
89d1e879ecSMiri Korenblit 
90d1e879ecSMiri Korenblit 			cck |= BIT(hw);
91d1e879ecSMiri Korenblit 			if (lowest_present_cck > hw)
92d1e879ecSMiri Korenblit 				lowest_present_cck = hw;
93d1e879ecSMiri Korenblit 		}
94d1e879ecSMiri Korenblit 	}
95d1e879ecSMiri Korenblit 
96d1e879ecSMiri Korenblit 	/* Now we've got the basic rates as bitmaps in the ofdm and cck
97d1e879ecSMiri Korenblit 	 * variables. This isn't sufficient though, as there might not
98d1e879ecSMiri Korenblit 	 * be all the right rates in the bitmap. E.g. if the only basic
99d1e879ecSMiri Korenblit 	 * rates are 5.5 Mbps and 11 Mbps, we still need to add 1 Mbps
100d1e879ecSMiri Korenblit 	 * and 6 Mbps because the 802.11-2007 standard says in 9.6:
101d1e879ecSMiri Korenblit 	 *
102d1e879ecSMiri Korenblit 	 *    [...] a STA responding to a received frame shall transmit
103d1e879ecSMiri Korenblit 	 *    its Control Response frame [...] at the highest rate in the
104d1e879ecSMiri Korenblit 	 *    BSSBasicRateSet parameter that is less than or equal to the
105d1e879ecSMiri Korenblit 	 *    rate of the immediately previous frame in the frame exchange
106d1e879ecSMiri Korenblit 	 *    sequence ([...]) and that is of the same modulation class
107d1e879ecSMiri Korenblit 	 *    ([...]) as the received frame. If no rate contained in the
108d1e879ecSMiri Korenblit 	 *    BSSBasicRateSet parameter meets these conditions, then the
109d1e879ecSMiri Korenblit 	 *    control frame sent in response to a received frame shall be
110d1e879ecSMiri Korenblit 	 *    transmitted at the highest mandatory rate of the PHY that is
111d1e879ecSMiri Korenblit 	 *    less than or equal to the rate of the received frame, and
112d1e879ecSMiri Korenblit 	 *    that is of the same modulation class as the received frame.
113d1e879ecSMiri Korenblit 	 *
114d1e879ecSMiri Korenblit 	 * As a consequence, we need to add all mandatory rates that are
115d1e879ecSMiri Korenblit 	 * lower than all of the basic rates to these bitmaps.
116d1e879ecSMiri Korenblit 	 */
117d1e879ecSMiri Korenblit 
118d1e879ecSMiri Korenblit 	if (lowest_present_ofdm > IWL_RATE_24M_INDEX)
119d1e879ecSMiri Korenblit 		ofdm |= IWL_RATE_BIT_MSK(24) >> IWL_FIRST_OFDM_RATE;
120d1e879ecSMiri Korenblit 	if (lowest_present_ofdm > IWL_RATE_12M_INDEX)
121d1e879ecSMiri Korenblit 		ofdm |= IWL_RATE_BIT_MSK(12) >> IWL_FIRST_OFDM_RATE;
122d1e879ecSMiri Korenblit 	/* 6M already there or needed so always add */
123d1e879ecSMiri Korenblit 	ofdm |= IWL_RATE_BIT_MSK(6) >> IWL_FIRST_OFDM_RATE;
124d1e879ecSMiri Korenblit 
125d1e879ecSMiri Korenblit 	/* CCK is a bit more complex with DSSS vs. HR/DSSS vs. ERP.
126d1e879ecSMiri Korenblit 	 * Note, however:
127d1e879ecSMiri Korenblit 	 *  - if no CCK rates are basic, it must be ERP since there must
128d1e879ecSMiri Korenblit 	 *    be some basic rates at all, so they're OFDM => ERP PHY
129d1e879ecSMiri Korenblit 	 *    (or we're in 5 GHz, and the cck bitmap will never be used)
130d1e879ecSMiri Korenblit 	 *  - if 11M is a basic rate, it must be ERP as well, so add 5.5M
131d1e879ecSMiri Korenblit 	 *  - if 5.5M is basic, 1M and 2M are mandatory
132d1e879ecSMiri Korenblit 	 *  - if 2M is basic, 1M is mandatory
133d1e879ecSMiri Korenblit 	 *  - if 1M is basic, that's the only valid ACK rate.
134d1e879ecSMiri Korenblit 	 * As a consequence, it's not as complicated as it sounds, just add
135d1e879ecSMiri Korenblit 	 * any lower rates to the ACK rate bitmap.
136d1e879ecSMiri Korenblit 	 */
137d1e879ecSMiri Korenblit 	if (lowest_present_cck > IWL_RATE_11M_INDEX)
138d1e879ecSMiri Korenblit 		cck |= IWL_RATE_BIT_MSK(11) >> IWL_FIRST_CCK_RATE;
139d1e879ecSMiri Korenblit 	if (lowest_present_cck > IWL_RATE_5M_INDEX)
140d1e879ecSMiri Korenblit 		cck |= IWL_RATE_BIT_MSK(5) >> IWL_FIRST_CCK_RATE;
141d1e879ecSMiri Korenblit 	if (lowest_present_cck > IWL_RATE_2M_INDEX)
142d1e879ecSMiri Korenblit 		cck |= IWL_RATE_BIT_MSK(2) >> IWL_FIRST_CCK_RATE;
143d1e879ecSMiri Korenblit 	/* 1M already there or needed so always add */
144d1e879ecSMiri Korenblit 	cck |= IWL_RATE_BIT_MSK(1) >> IWL_FIRST_CCK_RATE;
145d1e879ecSMiri Korenblit 
146d1e879ecSMiri Korenblit 	*cck_rates = cpu_to_le32((u32)cck);
147d1e879ecSMiri Korenblit 	*ofdm_rates = cpu_to_le32((u32)ofdm);
148d1e879ecSMiri Korenblit }
149d1e879ecSMiri Korenblit 
150d1e879ecSMiri Korenblit static void iwl_mld_fill_protection_flags(struct iwl_mld *mld,
151d1e879ecSMiri Korenblit 					  struct ieee80211_bss_conf *link,
152d1e879ecSMiri Korenblit 					  __le32 *protection_flags)
153d1e879ecSMiri Korenblit {
154d1e879ecSMiri Korenblit 	u8 protection_mode = link->ht_operation_mode &
155d1e879ecSMiri Korenblit 				IEEE80211_HT_OP_MODE_PROTECTION;
156d1e879ecSMiri Korenblit 	u8 ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT;
157d1e879ecSMiri Korenblit 
158d1e879ecSMiri Korenblit 	IWL_DEBUG_RATE(mld, "HT protection mode: %d\n", protection_mode);
159d1e879ecSMiri Korenblit 
160d1e879ecSMiri Korenblit 	if (link->use_cts_prot)
161d1e879ecSMiri Korenblit 		*protection_flags |= cpu_to_le32(LINK_PROT_FLG_TGG_PROTECT);
162d1e879ecSMiri Korenblit 
163d1e879ecSMiri Korenblit 	/* See section 9.23.3.1 of IEEE 80211-2012.
164d1e879ecSMiri Korenblit 	 * Nongreenfield HT STAs Present is not supported.
165d1e879ecSMiri Korenblit 	 */
166d1e879ecSMiri Korenblit 	switch (protection_mode) {
167d1e879ecSMiri Korenblit 	case IEEE80211_HT_OP_MODE_PROTECTION_NONE:
168d1e879ecSMiri Korenblit 		break;
169d1e879ecSMiri Korenblit 	case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER:
170d1e879ecSMiri Korenblit 	case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED:
171d1e879ecSMiri Korenblit 		*protection_flags |= cpu_to_le32(ht_flag);
172d1e879ecSMiri Korenblit 		break;
173d1e879ecSMiri Korenblit 	case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ:
174d1e879ecSMiri Korenblit 		/* Protect when channel wider than 20MHz */
175d1e879ecSMiri Korenblit 		if (link->chanreq.oper.width > NL80211_CHAN_WIDTH_20)
176d1e879ecSMiri Korenblit 			*protection_flags |= cpu_to_le32(ht_flag);
177d1e879ecSMiri Korenblit 		break;
178d1e879ecSMiri Korenblit 	}
179d1e879ecSMiri Korenblit }
180d1e879ecSMiri Korenblit 
181d1e879ecSMiri Korenblit static u8 iwl_mld_mac80211_ac_to_fw_ac(enum ieee80211_ac_numbers ac)
182d1e879ecSMiri Korenblit {
183d1e879ecSMiri Korenblit 	static const u8 mac80211_ac_to_fw[] = {
184d1e879ecSMiri Korenblit 		AC_VO,
185d1e879ecSMiri Korenblit 		AC_VI,
186d1e879ecSMiri Korenblit 		AC_BE,
187d1e879ecSMiri Korenblit 		AC_BK
188d1e879ecSMiri Korenblit 	};
189d1e879ecSMiri Korenblit 
190d1e879ecSMiri Korenblit 	return mac80211_ac_to_fw[ac];
191d1e879ecSMiri Korenblit }
192d1e879ecSMiri Korenblit 
193d1e879ecSMiri Korenblit static void iwl_mld_fill_qos_params(struct ieee80211_bss_conf *link,
194d1e879ecSMiri Korenblit 				    struct iwl_ac_qos *ac, __le32 *qos_flags)
195d1e879ecSMiri Korenblit {
196d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
197d1e879ecSMiri Korenblit 
198d1e879ecSMiri Korenblit 	/* no need to check mld_link since it is done in the caller */
199d1e879ecSMiri Korenblit 
200d1e879ecSMiri Korenblit 	for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) {
201d1e879ecSMiri Korenblit 		u8 txf = iwl_mld_mac80211_ac_to_fw_tx_fifo(mac_ac);
202d1e879ecSMiri Korenblit 		u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac);
203d1e879ecSMiri Korenblit 
204d1e879ecSMiri Korenblit 		ac[fw_ac].cw_min =
205d1e879ecSMiri Korenblit 			cpu_to_le16(mld_link->queue_params[mac_ac].cw_min);
206d1e879ecSMiri Korenblit 		ac[fw_ac].cw_max =
207d1e879ecSMiri Korenblit 			cpu_to_le16(mld_link->queue_params[mac_ac].cw_max);
208d1e879ecSMiri Korenblit 		ac[fw_ac].edca_txop =
209d1e879ecSMiri Korenblit 			cpu_to_le16(mld_link->queue_params[mac_ac].txop * 32);
210d1e879ecSMiri Korenblit 		ac[fw_ac].aifsn = mld_link->queue_params[mac_ac].aifs;
211d1e879ecSMiri Korenblit 		ac[fw_ac].fifos_mask = BIT(txf);
212d1e879ecSMiri Korenblit 	}
213d1e879ecSMiri Korenblit 
214d1e879ecSMiri Korenblit 	if (link->qos)
215d1e879ecSMiri Korenblit 		*qos_flags |= cpu_to_le32(MAC_QOS_FLG_UPDATE_EDCA);
216d1e879ecSMiri Korenblit 
217d1e879ecSMiri Korenblit 	if (link->chanreq.oper.width != NL80211_CHAN_WIDTH_20_NOHT)
218d1e879ecSMiri Korenblit 		*qos_flags |= cpu_to_le32(MAC_QOS_FLG_TGN);
219d1e879ecSMiri Korenblit }
220d1e879ecSMiri Korenblit 
221d1e879ecSMiri Korenblit static bool iwl_mld_fill_mu_edca(struct iwl_mld *mld,
222d1e879ecSMiri Korenblit 				 const struct iwl_mld_link *mld_link,
223d1e879ecSMiri Korenblit 				 struct iwl_he_backoff_conf *trig_based_txf)
224d1e879ecSMiri Korenblit {
225d1e879ecSMiri Korenblit 	for (int mac_ac = 0; mac_ac < IEEE80211_NUM_ACS; mac_ac++) {
226d1e879ecSMiri Korenblit 		const struct ieee80211_he_mu_edca_param_ac_rec *mu_edca =
227d1e879ecSMiri Korenblit 			&mld_link->queue_params[mac_ac].mu_edca_param_rec;
228d1e879ecSMiri Korenblit 		u8 fw_ac = iwl_mld_mac80211_ac_to_fw_ac(mac_ac);
229d1e879ecSMiri Korenblit 
230d1e879ecSMiri Korenblit 		if (!mld_link->queue_params[mac_ac].mu_edca)
231d1e879ecSMiri Korenblit 			return false;
232d1e879ecSMiri Korenblit 
233d1e879ecSMiri Korenblit 		trig_based_txf[fw_ac].cwmin =
234d1e879ecSMiri Korenblit 			cpu_to_le16(mu_edca->ecw_min_max & 0xf);
235d1e879ecSMiri Korenblit 		trig_based_txf[fw_ac].cwmax =
236d1e879ecSMiri Korenblit 			cpu_to_le16((mu_edca->ecw_min_max & 0xf0) >> 4);
237d1e879ecSMiri Korenblit 		trig_based_txf[fw_ac].aifsn =
238d1e879ecSMiri Korenblit 			cpu_to_le16(mu_edca->aifsn & 0xf);
239d1e879ecSMiri Korenblit 		trig_based_txf[fw_ac].mu_time =
240d1e879ecSMiri Korenblit 			cpu_to_le16(mu_edca->mu_edca_timer);
241d1e879ecSMiri Korenblit 	}
242d1e879ecSMiri Korenblit 	return true;
243d1e879ecSMiri Korenblit }
244d1e879ecSMiri Korenblit 
245d1e879ecSMiri Korenblit static u8 iwl_mld_sta_rx_bw_to_fw(enum ieee80211_sta_rx_bandwidth bw)
246d1e879ecSMiri Korenblit {
247d1e879ecSMiri Korenblit 	switch (bw) {
248d1e879ecSMiri Korenblit 	default: /* potential future values not supported by this hw/driver */
249d1e879ecSMiri Korenblit 	case IEEE80211_STA_RX_BW_20:
250d1e879ecSMiri Korenblit 		return IWL_LINK_MODIFY_BW_20;
251d1e879ecSMiri Korenblit 	case IEEE80211_STA_RX_BW_40:
252d1e879ecSMiri Korenblit 		return IWL_LINK_MODIFY_BW_40;
253d1e879ecSMiri Korenblit 	case IEEE80211_STA_RX_BW_80:
254d1e879ecSMiri Korenblit 		return IWL_LINK_MODIFY_BW_80;
255d1e879ecSMiri Korenblit 	case IEEE80211_STA_RX_BW_160:
256d1e879ecSMiri Korenblit 		return IWL_LINK_MODIFY_BW_160;
257d1e879ecSMiri Korenblit 	case IEEE80211_STA_RX_BW_320:
258d1e879ecSMiri Korenblit 		return IWL_LINK_MODIFY_BW_320;
259d1e879ecSMiri Korenblit 	}
260d1e879ecSMiri Korenblit }
261d1e879ecSMiri Korenblit 
262d1e879ecSMiri Korenblit static int _iwl_mld_change_link_in_fw(struct iwl_mld *mld,
263d1e879ecSMiri Korenblit 				      struct ieee80211_bss_conf *link,
264d1e879ecSMiri Korenblit 				      enum ieee80211_sta_rx_bandwidth bw,
265d1e879ecSMiri Korenblit 				      u32 changes)
266d1e879ecSMiri Korenblit {
267d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
268d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = link->vif;
269d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
270d1e879ecSMiri Korenblit 	struct ieee80211_chanctx_conf *chan_ctx;
271d1e879ecSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
272d1e879ecSMiri Korenblit 	u32 flags = 0;
273d1e879ecSMiri Korenblit 
274d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
275d1e879ecSMiri Korenblit 
276d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link))
277d1e879ecSMiri Korenblit 		return -EINVAL;
278d1e879ecSMiri Korenblit 
279d1e879ecSMiri Korenblit 	cmd.link_id = cpu_to_le32(mld_link->fw_id);
280d1e879ecSMiri Korenblit 	cmd.spec_link_id = link->link_id;
281d1e879ecSMiri Korenblit 	cmd.mac_id = cpu_to_le32(mld_vif->fw_id);
282d1e879ecSMiri Korenblit 
283d1e879ecSMiri Korenblit 	chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx);
284d1e879ecSMiri Korenblit 
285d1e879ecSMiri Korenblit 	cmd.phy_id = cpu_to_le32(chan_ctx ?
286d1e879ecSMiri Korenblit 		iwl_mld_phy_from_mac80211(chan_ctx)->fw_id :
287d1e879ecSMiri Korenblit 		FW_CTXT_ID_INVALID);
288d1e879ecSMiri Korenblit 
289d1e879ecSMiri Korenblit 	ether_addr_copy(cmd.local_link_addr, link->addr);
290d1e879ecSMiri Korenblit 
291d1e879ecSMiri Korenblit 	cmd.active = cpu_to_le32(mld_link->active);
292d1e879ecSMiri Korenblit 
293d1e879ecSMiri Korenblit 	if ((changes & LINK_CONTEXT_MODIFY_ACTIVE) && !mld_link->active &&
294d1e879ecSMiri Korenblit 	    mld_link->silent_deactivation) {
295d1e879ecSMiri Korenblit 		/* We are de-activating a link that is having CSA with
296d1e879ecSMiri Korenblit 		 * immediate quiet in EMLSR. Tell the firmware not to send any
297d1e879ecSMiri Korenblit 		 * frame.
298d1e879ecSMiri Korenblit 		 */
299d1e879ecSMiri Korenblit 		cmd.block_tx = 1;
300d1e879ecSMiri Korenblit 		mld_link->silent_deactivation = false;
301d1e879ecSMiri Korenblit 	}
302d1e879ecSMiri Korenblit 
303d1e879ecSMiri Korenblit 	if (vif->type == NL80211_IFTYPE_ADHOC && link->bssid)
304d1e879ecSMiri Korenblit 		ether_addr_copy(cmd.ibss_bssid_addr, link->bssid);
305d1e879ecSMiri Korenblit 
306d1e879ecSMiri Korenblit 	/* Channel context is needed to get the rates */
307d1e879ecSMiri Korenblit 	if (chan_ctx)
308d1e879ecSMiri Korenblit 		iwl_mld_fill_rates(mld, link, chan_ctx, &cmd.cck_rates,
309d1e879ecSMiri Korenblit 				   &cmd.ofdm_rates);
310d1e879ecSMiri Korenblit 
311d1e879ecSMiri Korenblit 	cmd.cck_short_preamble = cpu_to_le32(link->use_short_preamble);
312d1e879ecSMiri Korenblit 	cmd.short_slot = cpu_to_le32(link->use_short_slot);
313d1e879ecSMiri Korenblit 
314d1e879ecSMiri Korenblit 	iwl_mld_fill_protection_flags(mld, link, &cmd.protection_flags);
315d1e879ecSMiri Korenblit 
316d1e879ecSMiri Korenblit 	iwl_mld_fill_qos_params(link, cmd.ac, &cmd.qos_flags);
317d1e879ecSMiri Korenblit 
318d1e879ecSMiri Korenblit 	cmd.bi = cpu_to_le32(link->beacon_int);
319d1e879ecSMiri Korenblit 	cmd.dtim_interval = cpu_to_le32(link->beacon_int * link->dtim_period);
320d1e879ecSMiri Korenblit 
321d1e879ecSMiri Korenblit 	if (changes & LINK_CONTEXT_MODIFY_BANDWIDTH)
322d1e879ecSMiri Korenblit 		cmd.modify_bandwidth = iwl_mld_sta_rx_bw_to_fw(bw);
323d1e879ecSMiri Korenblit 
324d1e879ecSMiri Korenblit 	/* Configure HE parameters only if HE is supported, and only after
325d1e879ecSMiri Korenblit 	 * the parameters are set in mac80211 (meaning after assoc)
326d1e879ecSMiri Korenblit 	 */
327d1e879ecSMiri Korenblit 	if (!link->he_support || iwlwifi_mod_params.disable_11ax ||
328d1e879ecSMiri Korenblit 	    (vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) {
329d1e879ecSMiri Korenblit 		changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS;
330d1e879ecSMiri Korenblit 		goto send_cmd;
331d1e879ecSMiri Korenblit 	}
332d1e879ecSMiri Korenblit 
333d1e879ecSMiri Korenblit 	/* ap_sta may be NULL if we're disconnecting */
334d1e879ecSMiri Korenblit 	if (mld_vif->ap_sta) {
335d1e879ecSMiri Korenblit 		struct ieee80211_link_sta *link_sta =
336d1e879ecSMiri Korenblit 			link_sta_dereference_check(mld_vif->ap_sta,
337d1e879ecSMiri Korenblit 						   link->link_id);
338d1e879ecSMiri Korenblit 
339d1e879ecSMiri Korenblit 		if (!WARN_ON(!link_sta) && link_sta->he_cap.has_he &&
340d1e879ecSMiri Korenblit 		    link_sta->he_cap.he_cap_elem.mac_cap_info[5] &
341d1e879ecSMiri Korenblit 		    IEEE80211_HE_MAC_CAP5_OM_CTRL_UL_MU_DATA_DIS_RX)
342d1e879ecSMiri Korenblit 			cmd.ul_mu_data_disable = 1;
343d1e879ecSMiri Korenblit 	}
344d1e879ecSMiri Korenblit 
345d1e879ecSMiri Korenblit 	cmd.htc_trig_based_pkt_ext = link->htc_trig_based_pkt_ext;
346d1e879ecSMiri Korenblit 
347d1e879ecSMiri Korenblit 	if (link->uora_exists) {
348d1e879ecSMiri Korenblit 		cmd.rand_alloc_ecwmin = link->uora_ocw_range & 0x7;
349d1e879ecSMiri Korenblit 		cmd.rand_alloc_ecwmax = (link->uora_ocw_range >> 3) & 0x7;
350d1e879ecSMiri Korenblit 	}
351d1e879ecSMiri Korenblit 
352d1e879ecSMiri Korenblit 	if (iwl_mld_fill_mu_edca(mld, mld_link, cmd.trig_based_txf))
353d1e879ecSMiri Korenblit 		flags |= LINK_FLG_MU_EDCA_CW;
354d1e879ecSMiri Korenblit 
355d1e879ecSMiri Korenblit 	cmd.bss_color = link->he_bss_color.color;
356d1e879ecSMiri Korenblit 
357d1e879ecSMiri Korenblit 	if (!link->he_bss_color.enabled)
358d1e879ecSMiri Korenblit 		flags |= LINK_FLG_BSS_COLOR_DIS;
359d1e879ecSMiri Korenblit 
360d1e879ecSMiri Korenblit 	cmd.frame_time_rts_th = cpu_to_le16(link->frame_time_rts_th);
361d1e879ecSMiri Korenblit 
362d1e879ecSMiri Korenblit 	/* Block 26-tone RU OFDMA transmissions */
363d1e879ecSMiri Korenblit 	if (mld_link->he_ru_2mhz_block)
364d1e879ecSMiri Korenblit 		flags |= LINK_FLG_RU_2MHZ_BLOCK;
365d1e879ecSMiri Korenblit 
366d1e879ecSMiri Korenblit 	if (link->nontransmitted) {
367d1e879ecSMiri Korenblit 		ether_addr_copy(cmd.ref_bssid_addr, link->transmitter_bssid);
368d1e879ecSMiri Korenblit 		cmd.bssid_index = link->bssid_index;
369d1e879ecSMiri Korenblit 	}
370d1e879ecSMiri Korenblit 
371d1e879ecSMiri Korenblit 	/* The only EHT parameter is puncturing, and starting from PHY cmd
372d1e879ecSMiri Korenblit 	 * version 6 - it is sent there. For older versions of the PHY cmd,
373d1e879ecSMiri Korenblit 	 * puncturing is not needed at all.
374d1e879ecSMiri Korenblit 	 */
375d1e879ecSMiri Korenblit 	if (WARN_ON(changes & LINK_CONTEXT_MODIFY_EHT_PARAMS))
376d1e879ecSMiri Korenblit 		changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS;
377d1e879ecSMiri Korenblit 
378d1e879ecSMiri Korenblit send_cmd:
379d1e879ecSMiri Korenblit 	cmd.modify_mask = cpu_to_le32(changes);
380d1e879ecSMiri Korenblit 	cmd.flags = cpu_to_le32(flags);
381d1e879ecSMiri Korenblit 
382d1e879ecSMiri Korenblit 	return iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_MODIFY);
383d1e879ecSMiri Korenblit }
384d1e879ecSMiri Korenblit 
385d1e879ecSMiri Korenblit int iwl_mld_change_link_in_fw(struct iwl_mld *mld,
386d1e879ecSMiri Korenblit 			      struct ieee80211_bss_conf *link,
387d1e879ecSMiri Korenblit 			      u32 changes)
388d1e879ecSMiri Korenblit {
389d1e879ecSMiri Korenblit 	if (WARN_ON(changes & LINK_CONTEXT_MODIFY_BANDWIDTH))
390d1e879ecSMiri Korenblit 		changes &= ~LINK_CONTEXT_MODIFY_BANDWIDTH;
391d1e879ecSMiri Korenblit 
392d1e879ecSMiri Korenblit 	return _iwl_mld_change_link_in_fw(mld, link, 0, changes);
393d1e879ecSMiri Korenblit }
394d1e879ecSMiri Korenblit 
395d1e879ecSMiri Korenblit int iwl_mld_change_link_omi_bw(struct iwl_mld *mld,
396d1e879ecSMiri Korenblit 			       struct ieee80211_bss_conf *link,
397d1e879ecSMiri Korenblit 			       enum ieee80211_sta_rx_bandwidth bw)
398d1e879ecSMiri Korenblit {
399d1e879ecSMiri Korenblit 	return _iwl_mld_change_link_in_fw(mld, link, bw,
400d1e879ecSMiri Korenblit 					  LINK_CONTEXT_MODIFY_BANDWIDTH);
401d1e879ecSMiri Korenblit }
402d1e879ecSMiri Korenblit 
403d1e879ecSMiri Korenblit int iwl_mld_activate_link(struct iwl_mld *mld,
404d1e879ecSMiri Korenblit 			  struct ieee80211_bss_conf *link)
405d1e879ecSMiri Korenblit {
406d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
407d1e879ecSMiri Korenblit 	int ret;
408d1e879ecSMiri Korenblit 
409d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
410d1e879ecSMiri Korenblit 
411d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link || mld_link->active))
412d1e879ecSMiri Korenblit 		return -EINVAL;
413d1e879ecSMiri Korenblit 
414d1e879ecSMiri Korenblit 	mld_link->rx_omi.exit_ts = jiffies;
415d1e879ecSMiri Korenblit 	mld_link->active = true;
416d1e879ecSMiri Korenblit 
417d1e879ecSMiri Korenblit 	ret = iwl_mld_change_link_in_fw(mld, link,
418d1e879ecSMiri Korenblit 					LINK_CONTEXT_MODIFY_ACTIVE);
419d1e879ecSMiri Korenblit 	if (ret)
420d1e879ecSMiri Korenblit 		mld_link->active = false;
421d1e879ecSMiri Korenblit 
422d1e879ecSMiri Korenblit 	return ret;
423d1e879ecSMiri Korenblit }
424d1e879ecSMiri Korenblit 
425d1e879ecSMiri Korenblit void iwl_mld_deactivate_link(struct iwl_mld *mld,
426d1e879ecSMiri Korenblit 			     struct ieee80211_bss_conf *link)
427d1e879ecSMiri Korenblit {
428d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
429d1e879ecSMiri Korenblit 	struct iwl_probe_resp_data *probe_data;
430d1e879ecSMiri Korenblit 
431d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
432d1e879ecSMiri Korenblit 
433d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link || !mld_link->active))
434d1e879ecSMiri Korenblit 		return;
435d1e879ecSMiri Korenblit 
436d1e879ecSMiri Korenblit 	iwl_mld_cancel_session_protection(mld, link->vif, link->link_id);
437d1e879ecSMiri Korenblit 
438d1e879ecSMiri Korenblit 	/* If we deactivate the link, we will probably remove it, or switch
439d1e879ecSMiri Korenblit 	 * channel. In both cases, the CSA or Notice of Absence information is
440d1e879ecSMiri Korenblit 	 * now irrelevant. Remove the data here.
441d1e879ecSMiri Korenblit 	 */
442d1e879ecSMiri Korenblit 	probe_data = wiphy_dereference(mld->wiphy, mld_link->probe_resp_data);
443d1e879ecSMiri Korenblit 	RCU_INIT_POINTER(mld_link->probe_resp_data, NULL);
444d1e879ecSMiri Korenblit 	if (probe_data)
445d1e879ecSMiri Korenblit 		kfree_rcu(probe_data, rcu_head);
446d1e879ecSMiri Korenblit 
447d1e879ecSMiri Korenblit 	mld_link->active = false;
448d1e879ecSMiri Korenblit 
449d1e879ecSMiri Korenblit 	iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ACTIVE);
450d1e879ecSMiri Korenblit 
451d1e879ecSMiri Korenblit 	/* Now that the link is not active in FW, we don't expect any new
452d1e879ecSMiri Korenblit 	 * notifications for it. Cancel the ones that are already pending
453d1e879ecSMiri Korenblit 	 */
454d1e879ecSMiri Korenblit 	iwl_mld_cancel_notifications_of_object(mld, IWL_MLD_OBJECT_TYPE_LINK,
455d1e879ecSMiri Korenblit 					       mld_link->fw_id);
456d1e879ecSMiri Korenblit }
457d1e879ecSMiri Korenblit 
458*5789f791SMiri Korenblit static void
459d1e879ecSMiri Korenblit iwl_mld_rm_link_from_fw(struct iwl_mld *mld, struct ieee80211_bss_conf *link)
460d1e879ecSMiri Korenblit {
461d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link);
462d1e879ecSMiri Korenblit 	struct iwl_link_config_cmd cmd = {};
463d1e879ecSMiri Korenblit 
464d1e879ecSMiri Korenblit 	lockdep_assert_wiphy(mld->wiphy);
465d1e879ecSMiri Korenblit 
466d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link))
467*5789f791SMiri Korenblit 		return;
468d1e879ecSMiri Korenblit 
469d1e879ecSMiri Korenblit 	cmd.link_id = cpu_to_le32(mld_link->fw_id);
470d1e879ecSMiri Korenblit 	cmd.spec_link_id = link->link_id;
471d1e879ecSMiri Korenblit 	cmd.phy_id = cpu_to_le32(FW_CTXT_ID_INVALID);
472d1e879ecSMiri Korenblit 
473*5789f791SMiri Korenblit 	iwl_mld_send_link_cmd(mld, &cmd, FW_CTXT_ACTION_REMOVE);
474d1e879ecSMiri Korenblit }
475d1e879ecSMiri Korenblit 
476d1e879ecSMiri Korenblit static void iwl_mld_omi_bw_update(struct iwl_mld *mld,
477d1e879ecSMiri Korenblit 				  struct ieee80211_bss_conf *link_conf,
478d1e879ecSMiri Korenblit 				  struct iwl_mld_link *mld_link,
479d1e879ecSMiri Korenblit 				  struct ieee80211_link_sta *link_sta,
480d1e879ecSMiri Korenblit 				  enum ieee80211_sta_rx_bandwidth bw,
481d1e879ecSMiri Korenblit 				  bool ap_update)
482d1e879ecSMiri Korenblit {
483d1e879ecSMiri Korenblit 	enum ieee80211_sta_rx_bandwidth apply_bw;
484d1e879ecSMiri Korenblit 
485d1e879ecSMiri Korenblit 	mld_link->rx_omi.desired_bw = bw;
486d1e879ecSMiri Korenblit 
487d1e879ecSMiri Korenblit 	/* Can't update OMI while already in progress, desired_bw was
488d1e879ecSMiri Korenblit 	 * set so on FW notification the worker will see the change
489d1e879ecSMiri Korenblit 	 * and apply new the new desired bw.
490d1e879ecSMiri Korenblit 	 */
491d1e879ecSMiri Korenblit 	if (mld_link->rx_omi.bw_in_progress)
492d1e879ecSMiri Korenblit 		return;
493d1e879ecSMiri Korenblit 
494d1e879ecSMiri Korenblit 	if (bw == IEEE80211_STA_RX_BW_MAX)
495d1e879ecSMiri Korenblit 		apply_bw = ieee80211_chan_width_to_rx_bw(link_conf->chanreq.oper.width);
496d1e879ecSMiri Korenblit 	else
497d1e879ecSMiri Korenblit 		apply_bw = bw;
498d1e879ecSMiri Korenblit 
499d1e879ecSMiri Korenblit 	if (!ap_update) {
500d1e879ecSMiri Korenblit 		/* The update isn't due to AP tracking after leaving OMI,
501d1e879ecSMiri Korenblit 		 * where the AP could increase BW and then we must tell
502d1e879ecSMiri Korenblit 		 * it that we can do the increased BW as well, if we did
503d1e879ecSMiri Korenblit 		 * update the chandef.
504d1e879ecSMiri Korenblit 		 * In this case, if we want MAX, then we will need to send
505d1e879ecSMiri Korenblit 		 * a new OMI to the AP if it increases its own bandwidth as
506d1e879ecSMiri Korenblit 		 * we can (due to internal and FW limitations, and being
507d1e879ecSMiri Korenblit 		 * worried the AP might break) only send to what we're doing
508d1e879ecSMiri Korenblit 		 * at the moment. In this case, set last_max_bw; otherwise
509d1e879ecSMiri Korenblit 		 * if we really want to decrease our bandwidth set it to 0
510d1e879ecSMiri Korenblit 		 * to indicate no updates are needed if the AP changes.
511d1e879ecSMiri Korenblit 		 */
512d1e879ecSMiri Korenblit 		if (bw != IEEE80211_STA_RX_BW_MAX)
513d1e879ecSMiri Korenblit 			mld_link->rx_omi.last_max_bw = apply_bw;
514d1e879ecSMiri Korenblit 		else
515d1e879ecSMiri Korenblit 			mld_link->rx_omi.last_max_bw = 0;
516d1e879ecSMiri Korenblit 	} else {
517d1e879ecSMiri Korenblit 		/* Otherwise, if we're already trying to do maximum and
518d1e879ecSMiri Korenblit 		 * the AP is changing, set last_max_bw to the new max the
519d1e879ecSMiri Korenblit 		 * AP is using, we'll only get to this code path if the
520d1e879ecSMiri Korenblit 		 * new bandwidth of the AP is bigger than what we sent it
521d1e879ecSMiri Korenblit 		 * previously. This avoids repeatedly sending updates if
522d1e879ecSMiri Korenblit 		 * it changes bandwidth, only doing it once on an increase.
523d1e879ecSMiri Korenblit 		 */
524d1e879ecSMiri Korenblit 		mld_link->rx_omi.last_max_bw = apply_bw;
525d1e879ecSMiri Korenblit 	}
526d1e879ecSMiri Korenblit 
527d1e879ecSMiri Korenblit 	if (ieee80211_prepare_rx_omi_bw(link_sta, bw)) {
528d1e879ecSMiri Korenblit 		mld_link->rx_omi.bw_in_progress = apply_bw;
529d1e879ecSMiri Korenblit 		iwl_mld_change_link_omi_bw(mld, link_conf, apply_bw);
530d1e879ecSMiri Korenblit 	}
531d1e879ecSMiri Korenblit }
532d1e879ecSMiri Korenblit 
533d1e879ecSMiri Korenblit static void iwl_mld_omi_bw_finished_work(struct wiphy *wiphy,
534d1e879ecSMiri Korenblit 					 struct wiphy_work *work)
535d1e879ecSMiri Korenblit {
536d1e879ecSMiri Korenblit 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
537d1e879ecSMiri Korenblit 	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
538d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link =
539d1e879ecSMiri Korenblit 		container_of(work, typeof(*mld_link), rx_omi.finished_work.work);
540d1e879ecSMiri Korenblit 	enum ieee80211_sta_rx_bandwidth desired_bw, switched_to_bw;
541d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = mld_link->vif;
542d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
543d1e879ecSMiri Korenblit 	struct ieee80211_bss_conf *link_conf;
544d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta;
545d1e879ecSMiri Korenblit 
546d1e879ecSMiri Korenblit 	if (!mld_vif->ap_sta)
547d1e879ecSMiri Korenblit 		return;
548d1e879ecSMiri Korenblit 
549d1e879ecSMiri Korenblit 	link_sta = wiphy_dereference(mld->wiphy,
550d1e879ecSMiri Korenblit 				     mld_vif->ap_sta->link[mld_link->link_id]);
551d1e879ecSMiri Korenblit 	if (WARN_ON_ONCE(!link_sta))
552d1e879ecSMiri Korenblit 		return;
553d1e879ecSMiri Korenblit 
554d1e879ecSMiri Korenblit 	link_conf = link_conf_dereference_protected(vif, link_sta->link_id);
555d1e879ecSMiri Korenblit 	if (WARN_ON_ONCE(!link_conf))
556d1e879ecSMiri Korenblit 		return;
557d1e879ecSMiri Korenblit 
558d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link->rx_omi.bw_in_progress))
559d1e879ecSMiri Korenblit 		return;
560d1e879ecSMiri Korenblit 
561d1e879ecSMiri Korenblit 	desired_bw = mld_link->rx_omi.desired_bw;
562d1e879ecSMiri Korenblit 	switched_to_bw = mld_link->rx_omi.bw_in_progress;
563d1e879ecSMiri Korenblit 
564d1e879ecSMiri Korenblit 	ieee80211_finalize_rx_omi_bw(link_sta);
565d1e879ecSMiri Korenblit 	mld_link->rx_omi.bw_in_progress = 0;
566d1e879ecSMiri Korenblit 
567d1e879ecSMiri Korenblit 	if (desired_bw != switched_to_bw)
568d1e879ecSMiri Korenblit 		iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta,
569d1e879ecSMiri Korenblit 				      desired_bw, false);
570d1e879ecSMiri Korenblit }
571d1e879ecSMiri Korenblit 
572d1e879ecSMiri Korenblit static struct ieee80211_vif *
573d1e879ecSMiri Korenblit iwl_mld_get_omi_bw_reduction_pointers(struct iwl_mld *mld,
574d1e879ecSMiri Korenblit 				      struct ieee80211_link_sta **link_sta,
575d1e879ecSMiri Korenblit 				      struct iwl_mld_link **mld_link)
576d1e879ecSMiri Korenblit {
577d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif;
578d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
579d1e879ecSMiri Korenblit 	int n_link_stas = 0;
580d1e879ecSMiri Korenblit 
581d1e879ecSMiri Korenblit 	*link_sta = NULL;
582d1e879ecSMiri Korenblit 
583d1e879ecSMiri Korenblit 	if (mld->trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_SC)
584d1e879ecSMiri Korenblit 		return NULL;
585d1e879ecSMiri Korenblit 
586d1e879ecSMiri Korenblit 	vif = iwl_mld_get_bss_vif(mld);
587d1e879ecSMiri Korenblit 	if (!vif)
588d1e879ecSMiri Korenblit 		return NULL;
589d1e879ecSMiri Korenblit 
590d1e879ecSMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(mld->fw_id_to_link_sta); i++) {
591d1e879ecSMiri Korenblit 		struct ieee80211_link_sta *tmp;
592d1e879ecSMiri Korenblit 
593d1e879ecSMiri Korenblit 		tmp = wiphy_dereference(mld->wiphy, mld->fw_id_to_link_sta[i]);
594d1e879ecSMiri Korenblit 		if (IS_ERR_OR_NULL(tmp))
595d1e879ecSMiri Korenblit 			continue;
596d1e879ecSMiri Korenblit 
597d1e879ecSMiri Korenblit 		n_link_stas++;
598d1e879ecSMiri Korenblit 		*link_sta = tmp;
599d1e879ecSMiri Korenblit 	}
600d1e879ecSMiri Korenblit 
601d1e879ecSMiri Korenblit 	/* can't do anything if we have TDLS peers or EMLSR */
602d1e879ecSMiri Korenblit 	if (n_link_stas != 1)
603d1e879ecSMiri Korenblit 		return NULL;
604d1e879ecSMiri Korenblit 
605d1e879ecSMiri Korenblit 	mld_vif = iwl_mld_vif_from_mac80211(vif);
606d1e879ecSMiri Korenblit 	*mld_link = iwl_mld_link_dereference_check(mld_vif,
607d1e879ecSMiri Korenblit 						   (*link_sta)->link_id);
608d1e879ecSMiri Korenblit 	if (WARN_ON(!*mld_link))
609d1e879ecSMiri Korenblit 		return NULL;
610d1e879ecSMiri Korenblit 
611d1e879ecSMiri Korenblit 	return vif;
612d1e879ecSMiri Korenblit }
613d1e879ecSMiri Korenblit 
614d1e879ecSMiri Korenblit void iwl_mld_omi_ap_changed_bw(struct iwl_mld *mld,
615d1e879ecSMiri Korenblit 			       struct ieee80211_bss_conf *link_conf,
616d1e879ecSMiri Korenblit 			       enum ieee80211_sta_rx_bandwidth bw)
617d1e879ecSMiri Korenblit {
618d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta;
619d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link;
620d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
621d1e879ecSMiri Korenblit 
622d1e879ecSMiri Korenblit 	vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link);
623d1e879ecSMiri Korenblit 	if (!vif)
624d1e879ecSMiri Korenblit 		return;
625d1e879ecSMiri Korenblit 
626d1e879ecSMiri Korenblit 	if (WARN_ON(link_conf->vif != vif))
627d1e879ecSMiri Korenblit 		return;
628d1e879ecSMiri Korenblit 
629d1e879ecSMiri Korenblit 	/* This is 0 if we requested an OMI BW reduction and don't want to
630d1e879ecSMiri Korenblit 	 * be sending an OMI when the AP's bandwidth changes.
631d1e879ecSMiri Korenblit 	 */
632d1e879ecSMiri Korenblit 	if (!mld_link->rx_omi.last_max_bw)
633d1e879ecSMiri Korenblit 		return;
634d1e879ecSMiri Korenblit 
635d1e879ecSMiri Korenblit 	/* We only need to tell the AP if it increases BW over what we last
636d1e879ecSMiri Korenblit 	 * told it we were using, if it reduces then our last OMI to it will
637d1e879ecSMiri Korenblit 	 * not get used anyway (e.g. we said we want 160 but it's doing 80.)
638d1e879ecSMiri Korenblit 	 */
639d1e879ecSMiri Korenblit 	if (bw < mld_link->rx_omi.last_max_bw)
640d1e879ecSMiri Korenblit 		return;
641d1e879ecSMiri Korenblit 
642d1e879ecSMiri Korenblit 	iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, bw, true);
643d1e879ecSMiri Korenblit }
644d1e879ecSMiri Korenblit 
645d1e879ecSMiri Korenblit void iwl_mld_handle_omi_status_notif(struct iwl_mld *mld,
646d1e879ecSMiri Korenblit 				     struct iwl_rx_packet *pkt)
647d1e879ecSMiri Korenblit {
648d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta;
649d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link;
650d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
651d1e879ecSMiri Korenblit 
652d1e879ecSMiri Korenblit 	vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link);
653d1e879ecSMiri Korenblit 	if (IWL_FW_CHECK(mld, !vif, "unexpected OMI notification\n"))
654d1e879ecSMiri Korenblit 		return;
655d1e879ecSMiri Korenblit 
656d1e879ecSMiri Korenblit 	if (IWL_FW_CHECK(mld, !mld_link->rx_omi.bw_in_progress,
657d1e879ecSMiri Korenblit 			 "OMI notification when not requested\n"))
658d1e879ecSMiri Korenblit 		return;
659d1e879ecSMiri Korenblit 
660d1e879ecSMiri Korenblit 	wiphy_delayed_work_queue(mld->hw->wiphy,
661d1e879ecSMiri Korenblit 				 &mld_link->rx_omi.finished_work,
662d1e879ecSMiri Korenblit 				 msecs_to_jiffies(IWL_MLD_OMI_AP_SETTLE_DELAY));
663d1e879ecSMiri Korenblit }
664d1e879ecSMiri Korenblit 
665d1e879ecSMiri Korenblit void iwl_mld_leave_omi_bw_reduction(struct iwl_mld *mld)
666d1e879ecSMiri Korenblit {
667d1e879ecSMiri Korenblit 	struct ieee80211_bss_conf *link_conf;
668d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta;
669d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link;
670d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
671d1e879ecSMiri Korenblit 
672d1e879ecSMiri Korenblit 	vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link);
673d1e879ecSMiri Korenblit 	if (!vif)
674d1e879ecSMiri Korenblit 		return;
675d1e879ecSMiri Korenblit 
676d1e879ecSMiri Korenblit 	link_conf = link_conf_dereference_protected(vif, link_sta->link_id);
677d1e879ecSMiri Korenblit 	if (WARN_ON_ONCE(!link_conf))
678d1e879ecSMiri Korenblit 		return;
679d1e879ecSMiri Korenblit 
680d1e879ecSMiri Korenblit 	if (!link_conf->he_support)
681d1e879ecSMiri Korenblit 		return;
682d1e879ecSMiri Korenblit 
683d1e879ecSMiri Korenblit 	mld_link->rx_omi.exit_ts = jiffies;
684d1e879ecSMiri Korenblit 
685d1e879ecSMiri Korenblit 	iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta,
686d1e879ecSMiri Korenblit 			      IEEE80211_STA_RX_BW_MAX, false);
687d1e879ecSMiri Korenblit }
688d1e879ecSMiri Korenblit 
689d1e879ecSMiri Korenblit void iwl_mld_check_omi_bw_reduction(struct iwl_mld *mld)
690d1e879ecSMiri Korenblit {
691d1e879ecSMiri Korenblit 	enum ieee80211_sta_rx_bandwidth bw = IEEE80211_STA_RX_BW_MAX;
692d1e879ecSMiri Korenblit 	struct ieee80211_chanctx_conf *chanctx;
693d1e879ecSMiri Korenblit 	struct ieee80211_bss_conf *link_conf;
694d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta;
695d1e879ecSMiri Korenblit 	struct cfg80211_chan_def chandef;
696d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link;
697d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif;
698d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
699d1e879ecSMiri Korenblit 	struct iwl_mld_phy *phy;
700d1e879ecSMiri Korenblit 	u16 punctured;
701d1e879ecSMiri Korenblit 	int exit_thr;
702d1e879ecSMiri Korenblit 
703d1e879ecSMiri Korenblit 	/* not allowed in CAM mode */
704d1e879ecSMiri Korenblit 	if (iwlmld_mod_params.power_scheme == IWL_POWER_SCHEME_CAM)
705d1e879ecSMiri Korenblit 		return;
706d1e879ecSMiri Korenblit 
707d1e879ecSMiri Korenblit 	/* must have one BSS connection (no P2P), no TDLS, nor EMLSR */
708d1e879ecSMiri Korenblit 	vif = iwl_mld_get_omi_bw_reduction_pointers(mld, &link_sta, &mld_link);
709d1e879ecSMiri Korenblit 	if (!vif)
710d1e879ecSMiri Korenblit 		return;
711d1e879ecSMiri Korenblit 
712d1e879ecSMiri Korenblit 	link_conf = link_conf_dereference_protected(vif, link_sta->link_id);
713d1e879ecSMiri Korenblit 	if (WARN_ON_ONCE(!link_conf))
714d1e879ecSMiri Korenblit 		return;
715d1e879ecSMiri Korenblit 
716d1e879ecSMiri Korenblit 	if (!link_conf->he_support)
717d1e879ecSMiri Korenblit 		return;
718d1e879ecSMiri Korenblit 
719d1e879ecSMiri Korenblit 	chanctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx);
720d1e879ecSMiri Korenblit 	if (WARN_ON(!chanctx))
721d1e879ecSMiri Korenblit 		return;
722d1e879ecSMiri Korenblit 
723d1e879ecSMiri Korenblit 	mld_vif = iwl_mld_vif_from_mac80211(vif);
724d1e879ecSMiri Korenblit 	if (!mld_vif->authorized)
725d1e879ecSMiri Korenblit 		goto apply;
726d1e879ecSMiri Korenblit 
727d1e879ecSMiri Korenblit 	/* must not be in low-latency mode */
728d1e879ecSMiri Korenblit 	if (iwl_mld_vif_low_latency(mld_vif))
729d1e879ecSMiri Korenblit 		goto apply;
730d1e879ecSMiri Korenblit 
731d1e879ecSMiri Korenblit 	chandef = link_conf->chanreq.oper;
732d1e879ecSMiri Korenblit 
733d1e879ecSMiri Korenblit 	switch (chandef.width) {
734d1e879ecSMiri Korenblit 	case NL80211_CHAN_WIDTH_320:
735d1e879ecSMiri Korenblit 		exit_thr = IWL_MLD_OMI_EXIT_CHAN_LOAD_320;
736d1e879ecSMiri Korenblit 		break;
737d1e879ecSMiri Korenblit 	case NL80211_CHAN_WIDTH_160:
738d1e879ecSMiri Korenblit 		exit_thr = IWL_MLD_OMI_EXIT_CHAN_LOAD_160;
739d1e879ecSMiri Korenblit 		break;
740d1e879ecSMiri Korenblit 	default:
741d1e879ecSMiri Korenblit 		/* since we reduce to 80 MHz, must have more to start with */
742d1e879ecSMiri Korenblit 		goto apply;
743d1e879ecSMiri Korenblit 	}
744d1e879ecSMiri Korenblit 
745d1e879ecSMiri Korenblit 	/* not to be done if primary 80 MHz is punctured */
746d1e879ecSMiri Korenblit 	if (cfg80211_chandef_primary(&chandef, NL80211_CHAN_WIDTH_80,
747d1e879ecSMiri Korenblit 				     &punctured) < 0 ||
748d1e879ecSMiri Korenblit 	    punctured != 0)
749d1e879ecSMiri Korenblit 		goto apply;
750d1e879ecSMiri Korenblit 
751d1e879ecSMiri Korenblit 	phy = iwl_mld_phy_from_mac80211(chanctx);
752d1e879ecSMiri Korenblit 
753d1e879ecSMiri Korenblit 	if (phy->channel_load_by_us > exit_thr) {
754d1e879ecSMiri Korenblit 		/* send OMI for max bandwidth */
755d1e879ecSMiri Korenblit 		goto apply;
756d1e879ecSMiri Korenblit 	}
757d1e879ecSMiri Korenblit 
758d1e879ecSMiri Korenblit 	if (phy->channel_load_by_us > IWL_MLD_OMI_ENTER_CHAN_LOAD) {
759d1e879ecSMiri Korenblit 		/* no changes between enter/exit thresholds */
760d1e879ecSMiri Korenblit 		return;
761d1e879ecSMiri Korenblit 	}
762d1e879ecSMiri Korenblit 
763b807dec3SJohannes Berg 	if (time_is_after_jiffies(mld_link->rx_omi.exit_ts +
764d1e879ecSMiri Korenblit 				  msecs_to_jiffies(IWL_MLD_OMI_EXIT_PROTECTION)))
765d1e879ecSMiri Korenblit 		return;
766d1e879ecSMiri Korenblit 
767d1e879ecSMiri Korenblit 	/* reduce bandwidth to 80 MHz to save power */
768d1e879ecSMiri Korenblit 	bw = IEEE80211_STA_RX_BW_80;
769d1e879ecSMiri Korenblit apply:
770d1e879ecSMiri Korenblit 	iwl_mld_omi_bw_update(mld, link_conf, mld_link, link_sta, bw, false);
771d1e879ecSMiri Korenblit }
772d1e879ecSMiri Korenblit 
773d1e879ecSMiri Korenblit IWL_MLD_ALLOC_FN(link, bss_conf)
774d1e879ecSMiri Korenblit 
775d1e879ecSMiri Korenblit /* Constructor function for struct iwl_mld_link */
776d1e879ecSMiri Korenblit static int
777d1e879ecSMiri Korenblit iwl_mld_init_link(struct iwl_mld *mld, struct ieee80211_bss_conf *link,
778d1e879ecSMiri Korenblit 		  struct iwl_mld_link *mld_link)
779d1e879ecSMiri Korenblit {
780d1e879ecSMiri Korenblit 	mld_link->vif = link->vif;
781d1e879ecSMiri Korenblit 	mld_link->link_id = link->link_id;
782d1e879ecSMiri Korenblit 
783d1e879ecSMiri Korenblit 	iwl_mld_init_internal_sta(&mld_link->bcast_sta);
784d1e879ecSMiri Korenblit 	iwl_mld_init_internal_sta(&mld_link->mcast_sta);
785d1e879ecSMiri Korenblit 	iwl_mld_init_internal_sta(&mld_link->aux_sta);
786d1e879ecSMiri Korenblit 
787d1e879ecSMiri Korenblit 	wiphy_delayed_work_init(&mld_link->rx_omi.finished_work,
788d1e879ecSMiri Korenblit 				iwl_mld_omi_bw_finished_work);
789d1e879ecSMiri Korenblit 
790d1e879ecSMiri Korenblit 	return iwl_mld_allocate_link_fw_id(mld, &mld_link->fw_id, link);
791d1e879ecSMiri Korenblit }
792d1e879ecSMiri Korenblit 
793d1e879ecSMiri Korenblit /* Initializes the link structure, maps fw id to the ieee80211_bss_conf, and
794d1e879ecSMiri Korenblit  * adds a link to the fw
795d1e879ecSMiri Korenblit  */
796d1e879ecSMiri Korenblit int iwl_mld_add_link(struct iwl_mld *mld,
797d1e879ecSMiri Korenblit 		     struct ieee80211_bss_conf *bss_conf)
798d1e879ecSMiri Korenblit {
799d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif);
800d1e879ecSMiri Korenblit 	struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf);
801d1e879ecSMiri Korenblit 	bool is_deflink = bss_conf == &bss_conf->vif->bss_conf;
802d1e879ecSMiri Korenblit 	int ret;
803d1e879ecSMiri Korenblit 
804d1e879ecSMiri Korenblit 	if (!link) {
805d1e879ecSMiri Korenblit 		if (is_deflink)
806d1e879ecSMiri Korenblit 			link = &mld_vif->deflink;
807d1e879ecSMiri Korenblit 		else
808d1e879ecSMiri Korenblit 			link = kzalloc(sizeof(*link), GFP_KERNEL);
809d1e879ecSMiri Korenblit 	} else {
810d1e879ecSMiri Korenblit 		WARN_ON(!mld->fw_status.in_hw_restart);
811d1e879ecSMiri Korenblit 	}
812d1e879ecSMiri Korenblit 
813d1e879ecSMiri Korenblit 	ret = iwl_mld_init_link(mld, bss_conf, link);
814d1e879ecSMiri Korenblit 	if (ret)
815d1e879ecSMiri Korenblit 		goto free;
816d1e879ecSMiri Korenblit 
817d1e879ecSMiri Korenblit 	rcu_assign_pointer(mld_vif->link[bss_conf->link_id], link);
818d1e879ecSMiri Korenblit 
819d1e879ecSMiri Korenblit 	ret = iwl_mld_add_link_to_fw(mld, bss_conf);
820d1e879ecSMiri Korenblit 	if (ret) {
821d1e879ecSMiri Korenblit 		RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL);
822d1e879ecSMiri Korenblit 		RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL);
823d1e879ecSMiri Korenblit 		goto free;
824d1e879ecSMiri Korenblit 	}
825d1e879ecSMiri Korenblit 
826d1e879ecSMiri Korenblit 	return ret;
827d1e879ecSMiri Korenblit 
828d1e879ecSMiri Korenblit free:
829d1e879ecSMiri Korenblit 	if (!is_deflink)
830d1e879ecSMiri Korenblit 		kfree(link);
831d1e879ecSMiri Korenblit 	return ret;
832d1e879ecSMiri Korenblit }
833d1e879ecSMiri Korenblit 
834d1e879ecSMiri Korenblit /* Remove link from fw, unmap the bss_conf, and destroy the link structure */
835*5789f791SMiri Korenblit void iwl_mld_remove_link(struct iwl_mld *mld,
836d1e879ecSMiri Korenblit 			 struct ieee80211_bss_conf *bss_conf)
837d1e879ecSMiri Korenblit {
838d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(bss_conf->vif);
839d1e879ecSMiri Korenblit 	struct iwl_mld_link *link = iwl_mld_link_from_mac80211(bss_conf);
840d1e879ecSMiri Korenblit 	bool is_deflink = link == &mld_vif->deflink;
841d1e879ecSMiri Korenblit 
842d1e879ecSMiri Korenblit 	if (WARN_ON(!link || link->active))
843*5789f791SMiri Korenblit 		return;
844d1e879ecSMiri Korenblit 
845*5789f791SMiri Korenblit 	iwl_mld_rm_link_from_fw(mld, bss_conf);
846d1e879ecSMiri Korenblit 	/* Continue cleanup on failure */
847d1e879ecSMiri Korenblit 
848d1e879ecSMiri Korenblit 	if (!is_deflink)
849d1e879ecSMiri Korenblit 		kfree_rcu(link, rcu_head);
850d1e879ecSMiri Korenblit 
851d1e879ecSMiri Korenblit 	RCU_INIT_POINTER(mld_vif->link[bss_conf->link_id], NULL);
852d1e879ecSMiri Korenblit 
853d1e879ecSMiri Korenblit 	wiphy_delayed_work_cancel(mld->wiphy, &link->rx_omi.finished_work);
854d1e879ecSMiri Korenblit 
855d1e879ecSMiri Korenblit 	if (WARN_ON(link->fw_id >= mld->fw->ucode_capa.num_links))
856*5789f791SMiri Korenblit 		return;
857d1e879ecSMiri Korenblit 
858d1e879ecSMiri Korenblit 	RCU_INIT_POINTER(mld->fw_id_to_bss_conf[link->fw_id], NULL);
859d1e879ecSMiri Korenblit }
860d1e879ecSMiri Korenblit 
861d1e879ecSMiri Korenblit void iwl_mld_handle_missed_beacon_notif(struct iwl_mld *mld,
862d1e879ecSMiri Korenblit 					struct iwl_rx_packet *pkt)
863d1e879ecSMiri Korenblit {
864d1e879ecSMiri Korenblit 	const struct iwl_missed_beacons_notif *notif = (const void *)pkt->data;
865d1e879ecSMiri Korenblit 	union iwl_dbg_tlv_tp_data tp_data = { .fw_pkt = pkt };
866d1e879ecSMiri Korenblit 	u32 link_id = le32_to_cpu(notif->link_id);
867d1e879ecSMiri Korenblit 	u32 missed_bcon = le32_to_cpu(notif->consec_missed_beacons);
868d1e879ecSMiri Korenblit 	u32 missed_bcon_since_rx =
869d1e879ecSMiri Korenblit 		le32_to_cpu(notif->consec_missed_beacons_since_last_rx);
870d1e879ecSMiri Korenblit 	u32 scnd_lnk_bcn_lost =
871d1e879ecSMiri Korenblit 		le32_to_cpu(notif->consec_missed_beacons_other_link);
872d1e879ecSMiri Korenblit 	struct ieee80211_bss_conf *link_conf =
873d1e879ecSMiri Korenblit 		iwl_mld_fw_id_to_link_conf(mld, link_id);
874d1e879ecSMiri Korenblit 	u32 bss_param_ch_cnt_link_id;
875d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif;
876d1e879ecSMiri Korenblit 
877d1e879ecSMiri Korenblit 	if (WARN_ON(!link_conf))
878d1e879ecSMiri Korenblit 		return;
879d1e879ecSMiri Korenblit 
880d1e879ecSMiri Korenblit 	vif = link_conf->vif;
881d1e879ecSMiri Korenblit 	bss_param_ch_cnt_link_id = link_conf->bss_param_ch_cnt_link_id;
882d1e879ecSMiri Korenblit 
883d1e879ecSMiri Korenblit 	IWL_DEBUG_INFO(mld,
884d1e879ecSMiri Korenblit 		       "missed bcn link_id=%u, %u consecutive=%u\n",
885d1e879ecSMiri Korenblit 		       link_id, missed_bcon, missed_bcon_since_rx);
886d1e879ecSMiri Korenblit 
887d1e879ecSMiri Korenblit 	if (WARN_ON(!vif))
888d1e879ecSMiri Korenblit 		return;
889d1e879ecSMiri Korenblit 
890d1e879ecSMiri Korenblit 	mld->trans->dbg.dump_file_name_ext_valid = true;
891d1e879ecSMiri Korenblit 	snprintf(mld->trans->dbg.dump_file_name_ext, IWL_FW_INI_MAX_NAME,
892d1e879ecSMiri Korenblit 		 "LinkId_%d_MacType_%d", link_id,
893d1e879ecSMiri Korenblit 		 iwl_mld_mac80211_iftype_to_fw(vif));
894d1e879ecSMiri Korenblit 
895d1e879ecSMiri Korenblit 	iwl_dbg_tlv_time_point(&mld->fwrt,
896d1e879ecSMiri Korenblit 			       IWL_FW_INI_TIME_POINT_MISSED_BEACONS, &tp_data);
897d1e879ecSMiri Korenblit 
898d1e879ecSMiri Korenblit 	if (missed_bcon >= IWL_MLD_MISSED_BEACONS_THRESHOLD_LONG) {
899d1e879ecSMiri Korenblit 		if (missed_bcon_since_rx >=
900d1e879ecSMiri Korenblit 		    IWL_MLD_MISSED_BEACONS_SINCE_RX_THOLD) {
901d1e879ecSMiri Korenblit 			ieee80211_connection_loss(vif);
902d1e879ecSMiri Korenblit 			return;
903d1e879ecSMiri Korenblit 		}
904d1e879ecSMiri Korenblit 		IWL_WARN(mld,
905d1e879ecSMiri Korenblit 			 "missed beacons exceeds threshold, but receiving data. Stay connected, Expect bugs.\n");
906d1e879ecSMiri Korenblit 		return;
907d1e879ecSMiri Korenblit 	}
908d1e879ecSMiri Korenblit 
909d1e879ecSMiri Korenblit 	if (missed_bcon_since_rx > IWL_MLD_MISSED_BEACONS_THRESHOLD) {
910d1e879ecSMiri Korenblit 		ieee80211_cqm_beacon_loss_notify(vif, GFP_ATOMIC);
911d1e879ecSMiri Korenblit 
912d1e879ecSMiri Korenblit 		/* try to switch links, no-op if we don't have MLO */
913f31d666fSMiri Korenblit 		iwl_mld_int_mlo_scan(mld, vif);
914d1e879ecSMiri Korenblit 	}
915d1e879ecSMiri Korenblit 
916d1e879ecSMiri Korenblit 	/* no more logic if we're not in EMLSR */
917d1e879ecSMiri Korenblit 	if (hweight16(vif->active_links) <= 1)
918d1e879ecSMiri Korenblit 		return;
919d1e879ecSMiri Korenblit 
920d1e879ecSMiri Korenblit 	/* We are processing a notification before link activation */
921d1e879ecSMiri Korenblit 	if (le32_to_cpu(notif->other_link_id) == FW_CTXT_ID_INVALID)
922d1e879ecSMiri Korenblit 		return;
923d1e879ecSMiri Korenblit 
924d1e879ecSMiri Korenblit 	/* Exit EMLSR if we lost more than
925d1e879ecSMiri Korenblit 	 * IWL_MLD_MISSED_BEACONS_EXIT_ESR_THRESH beacons on boths links
926d1e879ecSMiri Korenblit 	 * OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH on current link.
927d1e879ecSMiri Korenblit 	 * OR more than IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED
928d1e879ecSMiri Korenblit 	 * on current link and the link's bss_param_ch_count has changed on
929d1e879ecSMiri Korenblit 	 * the other link's beacon.
930d1e879ecSMiri Korenblit 	 */
931d1e879ecSMiri Korenblit 	if ((missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS &&
932d1e879ecSMiri Korenblit 	     scnd_lnk_bcn_lost >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_2_LINKS) ||
933d1e879ecSMiri Korenblit 	    missed_bcon >= IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH ||
934d1e879ecSMiri Korenblit 	    (bss_param_ch_cnt_link_id != link_id &&
935d1e879ecSMiri Korenblit 	     missed_bcon >=
936d1e879ecSMiri Korenblit 	     IWL_MLD_BCN_LOSS_EXIT_ESR_THRESH_BSS_PARAM_CHANGED)) {
937d1e879ecSMiri Korenblit 		iwl_mld_exit_emlsr(mld, vif, IWL_MLD_EMLSR_EXIT_MISSED_BEACON,
938d1e879ecSMiri Korenblit 				   iwl_mld_get_primary_link(vif));
939d1e879ecSMiri Korenblit 	}
940d1e879ecSMiri Korenblit }
941d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_handle_missed_beacon_notif);
942d1e879ecSMiri Korenblit 
943d1e879ecSMiri Korenblit bool iwl_mld_cancel_missed_beacon_notif(struct iwl_mld *mld,
944d1e879ecSMiri Korenblit 					struct iwl_rx_packet *pkt,
945d1e879ecSMiri Korenblit 					u32 removed_link_id)
946d1e879ecSMiri Korenblit {
947d1e879ecSMiri Korenblit 	struct iwl_missed_beacons_notif *notif = (void *)pkt->data;
948d1e879ecSMiri Korenblit 
949d1e879ecSMiri Korenblit 	if (le32_to_cpu(notif->other_link_id) == removed_link_id) {
950d1e879ecSMiri Korenblit 		/* Second link is being removed. Don't cancel the notification,
951d1e879ecSMiri Korenblit 		 * but mark second link as invalid.
952d1e879ecSMiri Korenblit 		 */
953d1e879ecSMiri Korenblit 		notif->other_link_id = cpu_to_le32(FW_CTXT_ID_INVALID);
954d1e879ecSMiri Korenblit 	}
955d1e879ecSMiri Korenblit 
956d1e879ecSMiri Korenblit 	/* If the primary link is removed, cancel the notification */
957d1e879ecSMiri Korenblit 	return le32_to_cpu(notif->link_id) == removed_link_id;
958d1e879ecSMiri Korenblit }
959d1e879ecSMiri Korenblit 
960d1e879ecSMiri Korenblit int iwl_mld_link_set_associated(struct iwl_mld *mld, struct ieee80211_vif *vif,
961d1e879ecSMiri Korenblit 				struct ieee80211_bss_conf *link)
962d1e879ecSMiri Korenblit {
963d1e879ecSMiri Korenblit 	return iwl_mld_change_link_in_fw(mld, link, LINK_CONTEXT_MODIFY_ALL &
964d1e879ecSMiri Korenblit 					 ~(LINK_CONTEXT_MODIFY_ACTIVE |
965d1e879ecSMiri Korenblit 					   LINK_CONTEXT_MODIFY_EHT_PARAMS));
966d1e879ecSMiri Korenblit }
967d1e879ecSMiri Korenblit 
968d1e879ecSMiri Korenblit struct iwl_mld_rssi_to_grade {
969d1e879ecSMiri Korenblit 	s8 rssi[2];
970d1e879ecSMiri Korenblit 	u16 grade;
971d1e879ecSMiri Korenblit };
972d1e879ecSMiri Korenblit 
973d1e879ecSMiri Korenblit #define RSSI_TO_GRADE_LINE(_lb, _hb_uhb, _grade) \
974d1e879ecSMiri Korenblit 	{ \
975d1e879ecSMiri Korenblit 		.rssi = {_lb, _hb_uhb}, \
976d1e879ecSMiri Korenblit 		.grade = _grade \
977d1e879ecSMiri Korenblit 	}
978d1e879ecSMiri Korenblit 
979d1e879ecSMiri Korenblit /*
980d1e879ecSMiri Korenblit  * This array must be sorted by increasing RSSI for proper functionality.
981d1e879ecSMiri Korenblit  * The grades are actually estimated throughput, represented as fixed-point
982d1e879ecSMiri Korenblit  * with a scale factor of 1/10.
983d1e879ecSMiri Korenblit  */
984d1e879ecSMiri Korenblit static const struct iwl_mld_rssi_to_grade rssi_to_grade_map[] = {
985d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-85, -89, 172),
986d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-83, -86, 344),
987d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-82, -85, 516),
988d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-80, -83, 688),
989d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-77, -79, 1032),
990d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-73, -76, 1376),
991d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-70, -74, 1548),
992d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-69, -72, 1720),
993d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-65, -68, 2064),
994d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-61, -66, 2294),
995d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-58, -61, 2580),
996d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-55, -58, 2868),
997d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-46, -55, 3098),
998d1e879ecSMiri Korenblit 	RSSI_TO_GRADE_LINE(-43, -54, 3442)
999d1e879ecSMiri Korenblit };
1000d1e879ecSMiri Korenblit 
1001d1e879ecSMiri Korenblit #define MAX_GRADE (rssi_to_grade_map[ARRAY_SIZE(rssi_to_grade_map) - 1].grade)
1002d1e879ecSMiri Korenblit 
1003d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_2GHZ	30
1004d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_5GHZ	15
1005d1e879ecSMiri Korenblit #define DEFAULT_CHAN_LOAD_6GHZ	0
1006d1e879ecSMiri Korenblit 
1007d1e879ecSMiri Korenblit /* Factors calculation is done with fixed-point with a scaling factor of 1/256 */
1008d1e879ecSMiri Korenblit #define SCALE_FACTOR 256
1009d1e879ecSMiri Korenblit #define MAX_CHAN_LOAD 256
1010d1e879ecSMiri Korenblit 
1011d1e879ecSMiri Korenblit static unsigned int
1012d1e879ecSMiri Korenblit iwl_mld_get_n_subchannels(const struct ieee80211_bss_conf *link_conf)
1013d1e879ecSMiri Korenblit {
1014d1e879ecSMiri Korenblit 	enum nl80211_chan_width chan_width =
1015d1e879ecSMiri Korenblit 		link_conf->chanreq.oper.width;
1016d1e879ecSMiri Korenblit 	int mhz = nl80211_chan_width_to_mhz(chan_width);
1017d1e879ecSMiri Korenblit 	unsigned int n_subchannels;
1018d1e879ecSMiri Korenblit 
1019d1e879ecSMiri Korenblit 	if (WARN_ONCE(mhz < 20 || mhz > 320,
1020d1e879ecSMiri Korenblit 		      "Invalid channel width : (%d)\n", mhz))
1021d1e879ecSMiri Korenblit 		return 1;
1022d1e879ecSMiri Korenblit 
1023d1e879ecSMiri Korenblit 	/* total number of subchannels */
1024d1e879ecSMiri Korenblit 	n_subchannels = mhz / 20;
1025d1e879ecSMiri Korenblit 
1026d1e879ecSMiri Korenblit 	/* No puncturing if less than 80 MHz */
1027d1e879ecSMiri Korenblit 	if (mhz >= 80)
1028d1e879ecSMiri Korenblit 		n_subchannels -= hweight16(link_conf->chanreq.oper.punctured);
1029d1e879ecSMiri Korenblit 
1030d1e879ecSMiri Korenblit 	return n_subchannels;
1031d1e879ecSMiri Korenblit }
1032d1e879ecSMiri Korenblit 
1033d1e879ecSMiri Korenblit static int
1034d1e879ecSMiri Korenblit iwl_mld_get_chan_load_from_element(struct iwl_mld *mld,
1035d1e879ecSMiri Korenblit 				   struct ieee80211_bss_conf *link_conf)
1036d1e879ecSMiri Korenblit {
1037d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = link_conf->vif;
1038d1e879ecSMiri Korenblit 	const struct cfg80211_bss_ies *ies;
1039d1e879ecSMiri Korenblit 	const struct element *bss_load_elem = NULL;
1040d1e879ecSMiri Korenblit 	const struct ieee80211_bss_load_elem *bss_load;
1041d1e879ecSMiri Korenblit 
1042d1e879ecSMiri Korenblit 	guard(rcu)();
1043d1e879ecSMiri Korenblit 
1044d1e879ecSMiri Korenblit 	if (ieee80211_vif_link_active(vif, link_conf->link_id))
1045d1e879ecSMiri Korenblit 		ies = rcu_dereference(link_conf->bss->beacon_ies);
1046d1e879ecSMiri Korenblit 	else
1047d1e879ecSMiri Korenblit 		ies = rcu_dereference(link_conf->bss->ies);
1048d1e879ecSMiri Korenblit 
1049d1e879ecSMiri Korenblit 	if (ies)
1050d1e879ecSMiri Korenblit 		bss_load_elem = cfg80211_find_elem(WLAN_EID_QBSS_LOAD,
1051d1e879ecSMiri Korenblit 						   ies->data, ies->len);
1052d1e879ecSMiri Korenblit 
1053d1e879ecSMiri Korenblit 	if (!bss_load_elem ||
1054d1e879ecSMiri Korenblit 	    bss_load_elem->datalen != sizeof(*bss_load))
1055d1e879ecSMiri Korenblit 		return -EINVAL;
1056d1e879ecSMiri Korenblit 
1057d1e879ecSMiri Korenblit 	bss_load = (const void *)bss_load_elem->data;
1058d1e879ecSMiri Korenblit 
1059d1e879ecSMiri Korenblit 	return bss_load->channel_util;
1060d1e879ecSMiri Korenblit }
1061d1e879ecSMiri Korenblit 
1062d1e879ecSMiri Korenblit static unsigned int
1063d1e879ecSMiri Korenblit iwl_mld_get_chan_load_by_us(struct iwl_mld *mld,
1064d1e879ecSMiri Korenblit 			    struct ieee80211_bss_conf *link_conf,
1065d1e879ecSMiri Korenblit 			    bool expect_active_link)
1066d1e879ecSMiri Korenblit {
1067d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_from_mac80211(link_conf);
1068d1e879ecSMiri Korenblit 	struct ieee80211_chanctx_conf *chan_ctx;
1069d1e879ecSMiri Korenblit 	struct iwl_mld_phy *phy;
1070d1e879ecSMiri Korenblit 
1071d1e879ecSMiri Korenblit 	if (!mld_link || !mld_link->active) {
1072d1e879ecSMiri Korenblit 		WARN_ON(expect_active_link);
1073d1e879ecSMiri Korenblit 		return 0;
1074d1e879ecSMiri Korenblit 	}
1075d1e879ecSMiri Korenblit 
1076d1e879ecSMiri Korenblit 	if (WARN_ONCE(!rcu_access_pointer(mld_link->chan_ctx),
1077d1e879ecSMiri Korenblit 		      "Active link (%u) without channel ctxt assigned!\n",
1078d1e879ecSMiri Korenblit 		      link_conf->link_id))
1079d1e879ecSMiri Korenblit 		return 0;
1080d1e879ecSMiri Korenblit 
1081d1e879ecSMiri Korenblit 	chan_ctx = wiphy_dereference(mld->wiphy, mld_link->chan_ctx);
1082d1e879ecSMiri Korenblit 	phy = iwl_mld_phy_from_mac80211(chan_ctx);
1083d1e879ecSMiri Korenblit 
1084d1e879ecSMiri Korenblit 	return phy->channel_load_by_us;
1085d1e879ecSMiri Korenblit }
1086d1e879ecSMiri Korenblit 
1087d1e879ecSMiri Korenblit /* Returns error if the channel utilization element is invalid/unavailable */
1088d1e879ecSMiri Korenblit int iwl_mld_get_chan_load_by_others(struct iwl_mld *mld,
1089d1e879ecSMiri Korenblit 				    struct ieee80211_bss_conf *link_conf,
1090d1e879ecSMiri Korenblit 				    bool expect_active_link)
1091d1e879ecSMiri Korenblit {
1092d1e879ecSMiri Korenblit 	int chan_load;
1093d1e879ecSMiri Korenblit 	unsigned int chan_load_by_us;
1094d1e879ecSMiri Korenblit 
1095d1e879ecSMiri Korenblit 	/* get overall load */
1096d1e879ecSMiri Korenblit 	chan_load = iwl_mld_get_chan_load_from_element(mld, link_conf);
1097d1e879ecSMiri Korenblit 	if (chan_load < 0)
1098d1e879ecSMiri Korenblit 		return chan_load;
1099d1e879ecSMiri Korenblit 
1100d1e879ecSMiri Korenblit 	chan_load_by_us = iwl_mld_get_chan_load_by_us(mld, link_conf,
1101d1e879ecSMiri Korenblit 						      expect_active_link);
1102d1e879ecSMiri Korenblit 
1103d1e879ecSMiri Korenblit 	/* channel load by us is given in percentage */
1104d1e879ecSMiri Korenblit 	chan_load_by_us =
1105d1e879ecSMiri Korenblit 		NORMALIZE_PERCENT_TO_255(chan_load_by_us);
1106d1e879ecSMiri Korenblit 
1107d1e879ecSMiri Korenblit 	/* Use only values that firmware sends that can possibly be valid */
1108d1e879ecSMiri Korenblit 	if (chan_load_by_us <= chan_load)
1109d1e879ecSMiri Korenblit 		chan_load -= chan_load_by_us;
1110d1e879ecSMiri Korenblit 
1111d1e879ecSMiri Korenblit 	return chan_load;
1112d1e879ecSMiri Korenblit }
1113d1e879ecSMiri Korenblit 
1114d1e879ecSMiri Korenblit static unsigned int
1115d1e879ecSMiri Korenblit iwl_mld_get_default_chan_load(struct ieee80211_bss_conf *link_conf)
1116d1e879ecSMiri Korenblit {
1117d1e879ecSMiri Korenblit 	enum nl80211_band band = link_conf->chanreq.oper.chan->band;
1118d1e879ecSMiri Korenblit 
1119d1e879ecSMiri Korenblit 	switch (band) {
1120d1e879ecSMiri Korenblit 	case NL80211_BAND_2GHZ:
1121d1e879ecSMiri Korenblit 		return DEFAULT_CHAN_LOAD_2GHZ;
1122d1e879ecSMiri Korenblit 	case NL80211_BAND_5GHZ:
1123d1e879ecSMiri Korenblit 		return DEFAULT_CHAN_LOAD_5GHZ;
1124d1e879ecSMiri Korenblit 	case NL80211_BAND_6GHZ:
1125d1e879ecSMiri Korenblit 		return DEFAULT_CHAN_LOAD_6GHZ;
1126d1e879ecSMiri Korenblit 	default:
1127d1e879ecSMiri Korenblit 		WARN_ON(1);
1128d1e879ecSMiri Korenblit 		return 0;
1129d1e879ecSMiri Korenblit 	}
1130d1e879ecSMiri Korenblit }
1131d1e879ecSMiri Korenblit 
1132d1e879ecSMiri Korenblit unsigned int iwl_mld_get_chan_load(struct iwl_mld *mld,
1133d1e879ecSMiri Korenblit 				   struct ieee80211_bss_conf *link_conf)
1134d1e879ecSMiri Korenblit {
1135d1e879ecSMiri Korenblit 	int chan_load;
1136d1e879ecSMiri Korenblit 
1137d1e879ecSMiri Korenblit 	chan_load = iwl_mld_get_chan_load_by_others(mld, link_conf, false);
1138d1e879ecSMiri Korenblit 	if (chan_load >= 0)
1139d1e879ecSMiri Korenblit 		return chan_load;
1140d1e879ecSMiri Korenblit 
1141d1e879ecSMiri Korenblit 	/* No information from the element, take the defaults */
1142d1e879ecSMiri Korenblit 	chan_load = iwl_mld_get_default_chan_load(link_conf);
1143d1e879ecSMiri Korenblit 
1144d1e879ecSMiri Korenblit 	/* The defaults are given in percentage */
1145d1e879ecSMiri Korenblit 	return NORMALIZE_PERCENT_TO_255(chan_load);
1146d1e879ecSMiri Korenblit }
1147d1e879ecSMiri Korenblit 
1148d1e879ecSMiri Korenblit static unsigned int
1149d1e879ecSMiri Korenblit iwl_mld_get_avail_chan_load(struct iwl_mld *mld,
1150d1e879ecSMiri Korenblit 			    struct ieee80211_bss_conf *link_conf)
1151d1e879ecSMiri Korenblit {
1152d1e879ecSMiri Korenblit 	return MAX_CHAN_LOAD - iwl_mld_get_chan_load(mld, link_conf);
1153d1e879ecSMiri Korenblit }
1154d1e879ecSMiri Korenblit 
1155d1e879ecSMiri Korenblit /* This function calculates the grade of a link. Returns 0 in error case */
1156d1e879ecSMiri Korenblit unsigned int iwl_mld_get_link_grade(struct iwl_mld *mld,
1157d1e879ecSMiri Korenblit 				    struct ieee80211_bss_conf *link_conf)
1158d1e879ecSMiri Korenblit {
1159d1e879ecSMiri Korenblit 	enum nl80211_band band;
1160d1e879ecSMiri Korenblit 	int rssi_idx;
1161d1e879ecSMiri Korenblit 	s32 link_rssi;
1162d1e879ecSMiri Korenblit 	unsigned int grade = MAX_GRADE;
1163d1e879ecSMiri Korenblit 
1164d1e879ecSMiri Korenblit 	if (WARN_ON_ONCE(!link_conf))
1165d1e879ecSMiri Korenblit 		return 0;
1166d1e879ecSMiri Korenblit 
1167d1e879ecSMiri Korenblit 	band = link_conf->chanreq.oper.chan->band;
1168d1e879ecSMiri Korenblit 	if (WARN_ONCE(band != NL80211_BAND_2GHZ &&
1169d1e879ecSMiri Korenblit 		      band != NL80211_BAND_5GHZ &&
1170d1e879ecSMiri Korenblit 		      band != NL80211_BAND_6GHZ,
1171d1e879ecSMiri Korenblit 		      "Invalid band (%u)\n", band))
1172d1e879ecSMiri Korenblit 		return 0;
1173d1e879ecSMiri Korenblit 
1174d1e879ecSMiri Korenblit 	link_rssi = MBM_TO_DBM(link_conf->bss->signal);
1175d1e879ecSMiri Korenblit 	/*
1176d1e879ecSMiri Korenblit 	 * For 6 GHz the RSSI of the beacons is lower than
1177d1e879ecSMiri Korenblit 	 * the RSSI of the data.
1178d1e879ecSMiri Korenblit 	 */
1179d1e879ecSMiri Korenblit 	if (band == NL80211_BAND_6GHZ && link_rssi)
1180d1e879ecSMiri Korenblit 		link_rssi += 4;
1181d1e879ecSMiri Korenblit 
1182d1e879ecSMiri Korenblit 	rssi_idx = band == NL80211_BAND_2GHZ ? 0 : 1;
1183d1e879ecSMiri Korenblit 
1184d1e879ecSMiri Korenblit 	/* No valid RSSI - take the lowest grade */
1185d1e879ecSMiri Korenblit 	if (!link_rssi)
1186d1e879ecSMiri Korenblit 		link_rssi = rssi_to_grade_map[0].rssi[rssi_idx];
1187d1e879ecSMiri Korenblit 
1188d1e879ecSMiri Korenblit 	IWL_DEBUG_EHT(mld,
1189d1e879ecSMiri Korenblit 		      "Calculating grade of link %d: band = %d, bandwidth = %d, punctured subchannels =0x%x RSSI = %d\n",
1190d1e879ecSMiri Korenblit 		      link_conf->link_id, band,
1191d1e879ecSMiri Korenblit 		      link_conf->chanreq.oper.width,
1192d1e879ecSMiri Korenblit 		      link_conf->chanreq.oper.punctured, link_rssi);
1193d1e879ecSMiri Korenblit 
1194d1e879ecSMiri Korenblit 	/* Get grade based on RSSI */
1195d1e879ecSMiri Korenblit 	for (int i = 0; i < ARRAY_SIZE(rssi_to_grade_map); i++) {
1196d1e879ecSMiri Korenblit 		const struct iwl_mld_rssi_to_grade *line =
1197d1e879ecSMiri Korenblit 			&rssi_to_grade_map[i];
1198d1e879ecSMiri Korenblit 
1199d1e879ecSMiri Korenblit 		if (link_rssi > line->rssi[rssi_idx])
1200d1e879ecSMiri Korenblit 			continue;
1201d1e879ecSMiri Korenblit 		grade = line->grade;
1202d1e879ecSMiri Korenblit 		break;
1203d1e879ecSMiri Korenblit 	}
1204d1e879ecSMiri Korenblit 
1205d1e879ecSMiri Korenblit 	/* Apply the channel load and puncturing factors */
1206d1e879ecSMiri Korenblit 	grade = grade * iwl_mld_get_avail_chan_load(mld, link_conf) / SCALE_FACTOR;
1207d1e879ecSMiri Korenblit 	grade = grade * iwl_mld_get_n_subchannels(link_conf);
1208d1e879ecSMiri Korenblit 
1209d1e879ecSMiri Korenblit 	IWL_DEBUG_EHT(mld, "Link %d's grade: %d\n", link_conf->link_id, grade);
1210d1e879ecSMiri Korenblit 
1211d1e879ecSMiri Korenblit 	return grade;
1212d1e879ecSMiri Korenblit }
1213d1e879ecSMiri Korenblit EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_get_link_grade);
1214