Commit | Line | Data |
---|---|---|
55eb1c5f MK |
1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* | |
b82730bf | 3 | * Copyright (C) 2022 - 2024 Intel Corporation |
55eb1c5f MK |
4 | */ |
5 | #include "mvm.h" | |
bf976c81 | 6 | #include "time-event.h" |
55eb1c5f | 7 | |
d6f6b0d8 GG |
8 | static u32 iwl_mvm_get_free_fw_link_id(struct iwl_mvm *mvm, |
9 | struct iwl_mvm_vif *mvm_vif) | |
10 | { | |
11 | u32 link_id; | |
12 | ||
13 | lockdep_assert_held(&mvm->mutex); | |
14 | ||
15 | link_id = ffz(mvm->fw_link_ids_map); | |
16 | ||
17 | /* this case can happen if there're deactivated but not removed links */ | |
18 | if (link_id > IWL_MVM_FW_MAX_LINK_ID) | |
19 | return IWL_MVM_FW_LINK_ID_INVALID; | |
20 | ||
21 | mvm->fw_link_ids_map |= BIT(link_id); | |
22 | return link_id; | |
23 | } | |
24 | ||
25 | static void iwl_mvm_release_fw_link_id(struct iwl_mvm *mvm, u32 link_id) | |
26 | { | |
27 | lockdep_assert_held(&mvm->mutex); | |
28 | ||
29 | if (!WARN_ON(link_id > IWL_MVM_FW_MAX_LINK_ID)) | |
30 | mvm->fw_link_ids_map &= ~BIT(link_id); | |
31 | } | |
32 | ||
55eb1c5f MK |
33 | static int iwl_mvm_link_cmd_send(struct iwl_mvm *mvm, |
34 | struct iwl_link_config_cmd *cmd, | |
35 | enum iwl_ctxt_action action) | |
36 | { | |
37 | int ret; | |
38 | ||
39 | cmd->action = cpu_to_le32(action); | |
40 | ret = iwl_mvm_send_cmd_pdu(mvm, | |
41 | WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD), 0, | |
42 | sizeof(*cmd), cmd); | |
43 | if (ret) | |
44 | IWL_ERR(mvm, "Failed to send LINK_CONFIG_CMD (action:%d): %d\n", | |
45 | action, ret); | |
46 | return ret; | |
47 | } | |
48 | ||
a8b5d480 IP |
49 | int iwl_mvm_set_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif, |
50 | struct ieee80211_bss_conf *link_conf) | |
51 | { | |
52 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
53 | struct iwl_mvm_vif_link_info *link_info = | |
54 | mvmvif->link[link_conf->link_id]; | |
55 | ||
56 | if (link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID) { | |
57 | link_info->fw_link_id = iwl_mvm_get_free_fw_link_id(mvm, | |
58 | mvmvif); | |
59 | if (link_info->fw_link_id >= | |
60 | ARRAY_SIZE(mvm->link_id_to_link_conf)) | |
61 | return -EINVAL; | |
62 | ||
63 | rcu_assign_pointer(mvm->link_id_to_link_conf[link_info->fw_link_id], | |
64 | link_conf); | |
65 | } | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
cacc1d42 GG |
70 | int iwl_mvm_add_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif, |
71 | struct ieee80211_bss_conf *link_conf) | |
55eb1c5f MK |
72 | { |
73 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
cacc1d42 | 74 | unsigned int link_id = link_conf->link_id; |
d6f6b0d8 | 75 | struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id]; |
55eb1c5f | 76 | struct iwl_link_config_cmd cmd = {}; |
4dde4ff0 ST |
77 | unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD); |
78 | u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1); | |
a8b5d480 | 79 | int ret; |
cacc1d42 | 80 | |
d6f6b0d8 | 81 | if (WARN_ON_ONCE(!link_info)) |
cacc1d42 | 82 | return -EINVAL; |
55eb1c5f | 83 | |
a8b5d480 IP |
84 | ret = iwl_mvm_set_link_mapping(mvm, vif, link_conf); |
85 | if (ret) | |
86 | return ret; | |
d6f6b0d8 | 87 | |
55eb1c5f MK |
88 | /* Update SF - Disable if needed. if this fails, SF might still be on |
89 | * while many macs are bound, which is forbidden - so fail the binding. | |
90 | */ | |
91 | if (iwl_mvm_sf_update(mvm, vif, false)) | |
92 | return -EINVAL; | |
93 | ||
d6f6b0d8 | 94 | cmd.link_id = cpu_to_le32(link_info->fw_link_id); |
55eb1c5f | 95 | cmd.mac_id = cpu_to_le32(mvmvif->id); |
3f302269 | 96 | cmd.spec_link_id = link_conf->link_id; |
84ef7cbe IP |
97 | WARN_ON_ONCE(link_info->phy_ctxt); |
98 | cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID); | |
55eb1c5f | 99 | |
cacc1d42 | 100 | memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN); |
55eb1c5f | 101 | |
cacc1d42 GG |
102 | if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid) |
103 | memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN); | |
6b5a87df | 104 | |
4dde4ff0 ST |
105 | if (cmd_ver < 2) |
106 | cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac); | |
12bacfc2 | 107 | |
55eb1c5f MK |
108 | return iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_ADD); |
109 | } | |
110 | ||
111 | int iwl_mvm_link_changed(struct iwl_mvm *mvm, struct ieee80211_vif *vif, | |
cacc1d42 | 112 | struct ieee80211_bss_conf *link_conf, |
55eb1c5f MK |
113 | u32 changes, bool active) |
114 | { | |
115 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
cacc1d42 | 116 | unsigned int link_id = link_conf->link_id; |
d6f6b0d8 | 117 | struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id]; |
cacc1d42 | 118 | struct iwl_mvm_phy_ctxt *phyctxt; |
55eb1c5f MK |
119 | struct iwl_link_config_cmd cmd = {}; |
120 | u32 ht_flag, flags = 0, flags_mask = 0; | |
d6f6b0d8 | 121 | int ret; |
4dde4ff0 ST |
122 | unsigned int cmd_id = WIDE_ID(MAC_CONF_GROUP, LINK_CONFIG_CMD); |
123 | u8 cmd_ver = iwl_fw_lookup_cmd_ver(mvm->fw, cmd_id, 1); | |
d6f6b0d8 GG |
124 | |
125 | if (WARN_ON_ONCE(!link_info || | |
126 | link_info->fw_link_id == IWL_MVM_FW_LINK_ID_INVALID)) | |
127 | return -EINVAL; | |
55eb1c5f | 128 | |
bf976c81 JB |
129 | if (changes & LINK_CONTEXT_MODIFY_ACTIVE) { |
130 | /* When activating a link, phy context should be valid; | |
131 | * when deactivating a link, it also should be valid since | |
132 | * the link was active before. So, do nothing in this case. | |
133 | * Since a link is added first with FW_CTXT_INVALID, then we | |
134 | * can get here in case it's removed before it was activated. | |
135 | */ | |
136 | if (!link_info->phy_ctxt) | |
137 | return 0; | |
138 | ||
139 | /* Catch early if driver tries to activate or deactivate a link | |
140 | * twice. | |
141 | */ | |
142 | WARN_ON_ONCE(active == link_info->active); | |
143 | ||
144 | /* When deactivating a link session protection should | |
145 | * be stopped | |
146 | */ | |
147 | if (!active && vif->type == NL80211_IFTYPE_STATION) | |
148 | iwl_mvm_stop_session_protection(mvm, vif); | |
149 | } | |
150 | ||
d6f6b0d8 | 151 | cmd.link_id = cpu_to_le32(link_info->fw_link_id); |
55eb1c5f MK |
152 | |
153 | /* The phy_id, link address and listen_lmac can be modified only until | |
154 | * the link becomes active, otherwise they will be ignored. | |
155 | */ | |
d6f6b0d8 | 156 | phyctxt = link_info->phy_ctxt; |
cb145863 JB |
157 | if (phyctxt) |
158 | cmd.phy_id = cpu_to_le32(phyctxt->id); | |
159 | else | |
160 | cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID); | |
55eb1c5f MK |
161 | cmd.mac_id = cpu_to_le32(mvmvif->id); |
162 | ||
cacc1d42 | 163 | memcpy(cmd.local_link_addr, link_conf->addr, ETH_ALEN); |
55eb1c5f MK |
164 | |
165 | cmd.active = cpu_to_le32(active); | |
166 | ||
cacc1d42 GG |
167 | if (vif->type == NL80211_IFTYPE_ADHOC && link_conf->bssid) |
168 | memcpy(cmd.ibss_bssid_addr, link_conf->bssid, ETH_ALEN); | |
6b5a87df | 169 | |
cacc1d42 GG |
170 | iwl_mvm_set_fw_basic_rates(mvm, vif, link_conf, |
171 | &cmd.cck_rates, &cmd.ofdm_rates); | |
55eb1c5f | 172 | |
cacc1d42 GG |
173 | cmd.cck_short_preamble = cpu_to_le32(link_conf->use_short_preamble); |
174 | cmd.short_slot = cpu_to_le32(link_conf->use_short_slot); | |
55eb1c5f MK |
175 | |
176 | /* The fw does not distinguish between ht and fat */ | |
177 | ht_flag = LINK_PROT_FLG_HT_PROT | LINK_PROT_FLG_FAT_PROT; | |
cacc1d42 GG |
178 | iwl_mvm_set_fw_protection_flags(mvm, vif, link_conf, |
179 | &cmd.protection_flags, | |
55eb1c5f MK |
180 | ht_flag, LINK_PROT_FLG_TGG_PROTECT); |
181 | ||
77e1f3f3 | 182 | iwl_mvm_set_fw_qos_params(mvm, vif, link_conf, cmd.ac, |
cacc1d42 | 183 | &cmd.qos_flags); |
55eb1c5f | 184 | |
55eb1c5f | 185 | |
cacc1d42 GG |
186 | cmd.bi = cpu_to_le32(link_conf->beacon_int); |
187 | cmd.dtim_interval = cpu_to_le32(link_conf->beacon_int * | |
188 | link_conf->dtim_period); | |
55eb1c5f | 189 | |
cacc1d42 | 190 | if (!link_conf->he_support || iwlwifi_mod_params.disable_11ax || |
22c58834 | 191 | (vif->type == NL80211_IFTYPE_STATION && !vif->cfg.assoc)) { |
55eb1c5f MK |
192 | changes &= ~LINK_CONTEXT_MODIFY_HE_PARAMS; |
193 | goto send_cmd; | |
194 | } | |
195 | ||
cacc1d42 | 196 | cmd.htc_trig_based_pkt_ext = link_conf->htc_trig_based_pkt_ext; |
55eb1c5f | 197 | |
cacc1d42 | 198 | if (link_conf->uora_exists) { |
55eb1c5f | 199 | cmd.rand_alloc_ecwmin = |
cacc1d42 | 200 | link_conf->uora_ocw_range & 0x7; |
55eb1c5f | 201 | cmd.rand_alloc_ecwmax = |
cacc1d42 | 202 | (link_conf->uora_ocw_range >> 3) & 0x7; |
55eb1c5f MK |
203 | } |
204 | ||
205 | /* TODO how to set ndp_fdbk_buff_th_exp? */ | |
206 | ||
e119e740 | 207 | if (iwl_mvm_set_fw_mu_edca_params(mvm, mvmvif->link[link_id], |
55eb1c5f MK |
208 | &cmd.trig_based_txf[0])) { |
209 | flags |= LINK_FLG_MU_EDCA_CW; | |
210 | flags_mask |= LINK_FLG_MU_EDCA_CW; | |
211 | } | |
212 | ||
35b9281f | 213 | if (changes & LINK_CONTEXT_MODIFY_EHT_PARAMS) { |
b82730bf JB |
214 | struct ieee80211_chanctx_conf *ctx; |
215 | struct cfg80211_chan_def *def = NULL; | |
216 | ||
217 | rcu_read_lock(); | |
218 | ctx = rcu_dereference(link_conf->chanctx_conf); | |
219 | if (ctx) | |
220 | def = iwl_mvm_chanctx_def(mvm, ctx); | |
221 | ||
35b9281f | 222 | if (iwlwifi_mod_params.disable_11be || |
9a43c190 JB |
223 | !link_conf->eht_support || !def || |
224 | iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1) >= 6) | |
35b9281f IP |
225 | changes &= ~LINK_CONTEXT_MODIFY_EHT_PARAMS; |
226 | else | |
b82730bf JB |
227 | cmd.puncture_mask = cpu_to_le16(def->punctured); |
228 | rcu_read_unlock(); | |
35b9281f | 229 | } |
55eb1c5f | 230 | |
cacc1d42 | 231 | cmd.bss_color = link_conf->he_bss_color.color; |
55eb1c5f | 232 | |
cacc1d42 | 233 | if (!link_conf->he_bss_color.enabled) { |
55eb1c5f MK |
234 | flags |= LINK_FLG_BSS_COLOR_DIS; |
235 | flags_mask |= LINK_FLG_BSS_COLOR_DIS; | |
236 | } | |
237 | ||
cacc1d42 | 238 | cmd.frame_time_rts_th = cpu_to_le16(link_conf->frame_time_rts_th); |
55eb1c5f MK |
239 | |
240 | /* Block 26-tone RU OFDMA transmissions */ | |
d6f6b0d8 | 241 | if (link_info->he_ru_2mhz_block) { |
55eb1c5f MK |
242 | flags |= LINK_FLG_RU_2MHZ_BLOCK; |
243 | flags_mask |= LINK_FLG_RU_2MHZ_BLOCK; | |
244 | } | |
245 | ||
cacc1d42 | 246 | if (link_conf->nontransmitted) { |
55eb1c5f | 247 | ether_addr_copy(cmd.ref_bssid_addr, |
cacc1d42 GG |
248 | link_conf->transmitter_bssid); |
249 | cmd.bssid_index = link_conf->bssid_index; | |
55eb1c5f MK |
250 | } |
251 | ||
252 | send_cmd: | |
253 | cmd.modify_mask = cpu_to_le32(changes); | |
254 | cmd.flags = cpu_to_le32(flags); | |
255 | cmd.flags_mask = cpu_to_le32(flags_mask); | |
ed0c3433 | 256 | cmd.spec_link_id = link_conf->link_id; |
4dde4ff0 ST |
257 | if (cmd_ver < 2) |
258 | cmd.listen_lmac = cpu_to_le32(link_info->listen_lmac); | |
55eb1c5f | 259 | |
d6f6b0d8 | 260 | ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_MODIFY); |
9deccfcd | 261 | if (!ret && (changes & LINK_CONTEXT_MODIFY_ACTIVE)) |
d6f6b0d8 | 262 | link_info->active = active; |
d6f6b0d8 GG |
263 | |
264 | return ret; | |
55eb1c5f MK |
265 | } |
266 | ||
a8b5d480 IP |
267 | int iwl_mvm_unset_link_mapping(struct iwl_mvm *mvm, struct ieee80211_vif *vif, |
268 | struct ieee80211_bss_conf *link_conf) | |
269 | { | |
270 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
271 | struct iwl_mvm_vif_link_info *link_info = | |
272 | mvmvif->link[link_conf->link_id]; | |
273 | ||
274 | /* mac80211 thought we have the link, but it was never configured */ | |
275 | if (WARN_ON(!link_info || | |
276 | link_info->fw_link_id >= | |
277 | ARRAY_SIZE(mvm->link_id_to_link_conf))) | |
278 | return -EINVAL; | |
279 | ||
280 | RCU_INIT_POINTER(mvm->link_id_to_link_conf[link_info->fw_link_id], | |
281 | NULL); | |
282 | return 0; | |
283 | } | |
284 | ||
cacc1d42 GG |
285 | int iwl_mvm_remove_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif, |
286 | struct ieee80211_bss_conf *link_conf) | |
55eb1c5f MK |
287 | { |
288 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
d6f6b0d8 GG |
289 | unsigned int link_id = link_conf->link_id; |
290 | struct iwl_mvm_vif_link_info *link_info = mvmvif->link[link_id]; | |
55eb1c5f MK |
291 | struct iwl_link_config_cmd cmd = {}; |
292 | int ret; | |
293 | ||
a8b5d480 IP |
294 | ret = iwl_mvm_unset_link_mapping(mvm, vif, link_conf); |
295 | if (ret) | |
ac139aa3 | 296 | return 0; |
d6f6b0d8 GG |
297 | |
298 | cmd.link_id = cpu_to_le32(link_info->fw_link_id); | |
299 | iwl_mvm_release_fw_link_id(mvm, link_info->fw_link_id); | |
300 | link_info->fw_link_id = IWL_MVM_FW_LINK_ID_INVALID; | |
ed0c3433 | 301 | cmd.spec_link_id = link_conf->link_id; |
f3276ff0 | 302 | cmd.phy_id = cpu_to_le32(FW_CTXT_INVALID); |
d6f6b0d8 | 303 | |
55eb1c5f MK |
304 | ret = iwl_mvm_link_cmd_send(mvm, &cmd, FW_CTXT_ACTION_REMOVE); |
305 | ||
306 | if (!ret) | |
307 | if (iwl_mvm_sf_update(mvm, vif, true)) | |
308 | IWL_ERR(mvm, "Failed to update SF state\n"); | |
309 | ||
310 | return ret; | |
311 | } | |
bf976c81 JB |
312 | |
313 | /* link should be deactivated before removal, so in most cases we need to | |
314 | * perform these two operations together | |
315 | */ | |
316 | int iwl_mvm_disable_link(struct iwl_mvm *mvm, struct ieee80211_vif *vif, | |
317 | struct ieee80211_bss_conf *link_conf) | |
318 | { | |
319 | int ret; | |
320 | ||
321 | ret = iwl_mvm_link_changed(mvm, vif, link_conf, | |
322 | LINK_CONTEXT_MODIFY_ACTIVE, false); | |
323 | if (ret) | |
324 | return ret; | |
325 | ||
326 | ret = iwl_mvm_remove_link(mvm, vif, link_conf); | |
327 | if (ret) | |
328 | return ret; | |
329 | ||
330 | return ret; | |
331 | } |