Commit | Line | Data |
---|---|---|
8e99ea8d JB |
1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* | |
12bacfc2 | 3 | * Copyright (C) 2012-2014, 2018-2023 Intel Corporation |
8e99ea8d JB |
4 | * Copyright (C) 2013-2014 Intel Mobile Communications GmbH |
5 | * Copyright (C) 2017 Intel Deutschland GmbH | |
6 | */ | |
8ca151b5 JB |
7 | #include <net/mac80211.h> |
8 | #include "fw-api.h" | |
9 | #include "mvm.h" | |
10 | ||
0d365ae5 | 11 | /* Maps the driver specific channel width definition to the fw values */ |
6ce73e65 | 12 | u8 iwl_mvm_get_channel_width(struct cfg80211_chan_def *chandef) |
8ca151b5 JB |
13 | { |
14 | switch (chandef->width) { | |
15 | case NL80211_CHAN_WIDTH_20_NOHT: | |
16 | case NL80211_CHAN_WIDTH_20: | |
7ac87575 | 17 | return IWL_PHY_CHANNEL_MODE20; |
8ca151b5 | 18 | case NL80211_CHAN_WIDTH_40: |
7ac87575 | 19 | return IWL_PHY_CHANNEL_MODE40; |
8ca151b5 | 20 | case NL80211_CHAN_WIDTH_80: |
7ac87575 | 21 | return IWL_PHY_CHANNEL_MODE80; |
8ca151b5 | 22 | case NL80211_CHAN_WIDTH_160: |
7ac87575 JB |
23 | return IWL_PHY_CHANNEL_MODE160; |
24 | case NL80211_CHAN_WIDTH_320: | |
25 | return IWL_PHY_CHANNEL_MODE320; | |
8ca151b5 JB |
26 | default: |
27 | WARN(1, "Invalid channel width=%u", chandef->width); | |
7ac87575 | 28 | return IWL_PHY_CHANNEL_MODE20; |
8ca151b5 JB |
29 | } |
30 | } | |
31 | ||
32 | /* | |
33 | * Maps the driver specific control channel position (relative to the center | |
34 | * freq) definitions to the the fw values | |
35 | */ | |
6ce73e65 | 36 | u8 iwl_mvm_get_ctrl_pos(struct cfg80211_chan_def *chandef) |
8ca151b5 | 37 | { |
7ac87575 JB |
38 | int offs = chandef->chan->center_freq - chandef->center_freq1; |
39 | int abs_offs = abs(offs); | |
40 | u8 ret; | |
41 | ||
42 | if (offs == 0) { | |
8ca151b5 JB |
43 | /* |
44 | * The FW is expected to check the control channel position only | |
45 | * when in HT/VHT and the channel width is not 20MHz. Return | |
46 | * this value as the default one. | |
47 | */ | |
7ac87575 | 48 | return 0; |
8ca151b5 | 49 | } |
7ac87575 JB |
50 | |
51 | /* this results in a value 0-7, i.e. fitting into 0b0111 */ | |
52 | ret = (abs_offs - 10) / 20; | |
53 | /* | |
54 | * But we need the value to be in 0b1011 because 0b0100 is | |
55 | * IWL_PHY_CTRL_POS_ABOVE, so shift bit 2 up to land in | |
56 | * IWL_PHY_CTRL_POS_OFFS_EXT (0b1000) | |
57 | */ | |
58 | ret = (ret & IWL_PHY_CTRL_POS_OFFS_MSK) | | |
59 | ((ret & BIT(2)) << 1); | |
60 | /* and add the above bit */ | |
61 | ret |= (offs > 0) * IWL_PHY_CTRL_POS_ABOVE; | |
62 | ||
63 | return ret; | |
8ca151b5 JB |
64 | } |
65 | ||
66 | /* | |
67 | * Construct the generic fields of the PHY context command | |
68 | */ | |
69 | static void iwl_mvm_phy_ctxt_cmd_hdr(struct iwl_mvm_phy_ctxt *ctxt, | |
70 | struct iwl_phy_context_cmd *cmd, | |
a8682106 | 71 | u32 action) |
8ca151b5 | 72 | { |
8ca151b5 JB |
73 | cmd->id_and_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(ctxt->id, |
74 | ctxt->color)); | |
75 | cmd->action = cpu_to_le32(action); | |
8ca151b5 JB |
76 | } |
77 | ||
a8682106 | 78 | static void iwl_mvm_phy_ctxt_set_rxchain(struct iwl_mvm *mvm, |
a171399f | 79 | struct iwl_mvm_phy_ctxt *ctxt, |
a8682106 MG |
80 | __le32 *rxchain_info, |
81 | u8 chains_static, | |
82 | u8 chains_dynamic) | |
8ca151b5 | 83 | { |
33223542 | 84 | u8 active_cnt, idle_cnt; |
8ca151b5 JB |
85 | |
86 | /* Set rx the chains */ | |
8ca151b5 JB |
87 | idle_cnt = chains_static; |
88 | active_cnt = chains_dynamic; | |
89 | ||
1a095d30 JB |
90 | /* In scenarios where we only ever use a single-stream rates, |
91 | * i.e. legacy 11b/g/a associations, single-stream APs or even | |
92 | * static SMPS, enable both chains to get diversity, improving | |
93 | * the case where we're far enough from the AP that attenuation | |
94 | * between the two antennas is sufficiently different to impact | |
95 | * performance. | |
96 | */ | |
a171399f | 97 | if (active_cnt == 1 && iwl_mvm_rx_diversity_allowed(mvm, ctxt)) { |
1a095d30 JB |
98 | idle_cnt = 2; |
99 | active_cnt = 2; | |
100 | } | |
101 | ||
a8682106 | 102 | *rxchain_info = cpu_to_le32(iwl_mvm_get_valid_rx_ant(mvm) << |
8ca151b5 | 103 | PHY_RX_CHAIN_VALID_POS); |
a8682106 MG |
104 | *rxchain_info |= cpu_to_le32(idle_cnt << PHY_RX_CHAIN_CNT_POS); |
105 | *rxchain_info |= cpu_to_le32(active_cnt << | |
8ca151b5 | 106 | PHY_RX_CHAIN_MIMO_CNT_POS); |
aa5e1832 | 107 | #ifdef CONFIG_IWLWIFI_DEBUGFS |
ddf89ab1 | 108 | if (unlikely(mvm->dbgfs_rx_phyinfo)) |
a8682106 | 109 | *rxchain_info = cpu_to_le32(mvm->dbgfs_rx_phyinfo); |
aa5e1832 | 110 | #endif |
a8682106 MG |
111 | } |
112 | ||
113 | /* | |
114 | * Add the phy configuration to the PHY context command | |
115 | */ | |
116 | static void iwl_mvm_phy_ctxt_cmd_data_v1(struct iwl_mvm *mvm, | |
a171399f | 117 | struct iwl_mvm_phy_ctxt *ctxt, |
a8682106 MG |
118 | struct iwl_phy_context_cmd_v1 *cmd, |
119 | struct cfg80211_chan_def *chandef, | |
120 | u8 chains_static, u8 chains_dynamic) | |
121 | { | |
122 | struct iwl_phy_context_cmd_tail *tail = | |
123 | iwl_mvm_chan_info_cmd_tail(mvm, &cmd->ci); | |
124 | ||
125 | /* Set the channel info data */ | |
126 | iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); | |
127 | ||
a171399f | 128 | iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &tail->rxchain_info, |
a8682106 | 129 | chains_static, chains_dynamic); |
8ca151b5 | 130 | |
57e861d9 | 131 | tail->txchain_info = cpu_to_le32(iwl_mvm_get_valid_tx_ant(mvm)); |
8ca151b5 JB |
132 | } |
133 | ||
a8682106 MG |
134 | /* |
135 | * Add the phy configuration to the PHY context command | |
136 | */ | |
137 | static void iwl_mvm_phy_ctxt_cmd_data(struct iwl_mvm *mvm, | |
a171399f | 138 | struct iwl_mvm_phy_ctxt *ctxt, |
a8682106 MG |
139 | struct iwl_phy_context_cmd *cmd, |
140 | struct cfg80211_chan_def *chandef, | |
141 | u8 chains_static, u8 chains_dynamic) | |
142 | { | |
d51439a6 | 143 | cmd->lmac_id = cpu_to_le32(iwl_mvm_get_lmac_id(mvm, |
aa4936b1 | 144 | chandef->chan->band)); |
a8682106 MG |
145 | |
146 | /* Set the channel info data */ | |
147 | iwl_mvm_set_chan_info_chandef(mvm, &cmd->ci, chandef); | |
148 | ||
c48e93a6 | 149 | /* we only support RLC command version 2 */ |
971cbe50 | 150 | if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, RLC_CONFIG_CMD), 0) < 2) |
c48e93a6 JB |
151 | iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &cmd->rxchain_info, |
152 | chains_static, chains_dynamic); | |
153 | } | |
154 | ||
12bacfc2 MK |
155 | int iwl_mvm_phy_send_rlc(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, |
156 | u8 chains_static, u8 chains_dynamic) | |
c48e93a6 JB |
157 | { |
158 | struct iwl_rlc_config_cmd cmd = { | |
159 | .phy_id = cpu_to_le32(ctxt->id), | |
160 | }; | |
161 | ||
12bacfc2 MK |
162 | if (ctxt->rlc_disabled) |
163 | return 0; | |
164 | ||
165 | if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, | |
166 | RLC_CONFIG_CMD), 0) < 2) | |
c48e93a6 JB |
167 | return 0; |
168 | ||
169 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_DRIVER_FORCE != | |
170 | PHY_RX_CHAIN_DRIVER_FORCE_MSK); | |
171 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_VALID != | |
172 | PHY_RX_CHAIN_VALID_MSK); | |
173 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_FORCE != | |
174 | PHY_RX_CHAIN_FORCE_SEL_MSK); | |
175 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_FORCE_MIMO != | |
176 | PHY_RX_CHAIN_FORCE_MIMO_SEL_MSK); | |
177 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_COUNT != PHY_RX_CHAIN_CNT_MSK); | |
178 | BUILD_BUG_ON(IWL_RLC_CHAIN_INFO_MIMO_COUNT != | |
179 | PHY_RX_CHAIN_MIMO_CNT_MSK); | |
180 | ||
181 | iwl_mvm_phy_ctxt_set_rxchain(mvm, ctxt, &cmd.rlc.rx_chain_info, | |
a8682106 | 182 | chains_static, chains_dynamic); |
c48e93a6 | 183 | |
48a25b5d MK |
184 | IWL_DEBUG_FW(mvm, "Send RLC command: phy=%d, rx_chain_info=0x%x\n", |
185 | ctxt->id, cmd.rlc.rx_chain_info); | |
186 | ||
c48e93a6 JB |
187 | return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(RLC_CONFIG_CMD, |
188 | DATA_PATH_GROUP, 2), | |
189 | 0, sizeof(cmd), &cmd); | |
a8682106 MG |
190 | } |
191 | ||
8ca151b5 JB |
192 | /* |
193 | * Send a command to apply the current phy configuration. The command is send | |
194 | * only if something in the configuration changed: in case that this is the | |
195 | * first time that the phy configuration is applied or in case that the phy | |
196 | * configuration changed from the previous apply. | |
197 | */ | |
198 | static int iwl_mvm_phy_ctxt_apply(struct iwl_mvm *mvm, | |
199 | struct iwl_mvm_phy_ctxt *ctxt, | |
200 | struct cfg80211_chan_def *chandef, | |
201 | u8 chains_static, u8 chains_dynamic, | |
a8682106 | 202 | u32 action) |
8ca151b5 | 203 | { |
8ca151b5 | 204 | int ret; |
971cbe50 | 205 | int ver = iwl_fw_lookup_cmd_ver(mvm->fw, PHY_CONTEXT_CMD, 1); |
a8682106 | 206 | |
26e9ccb3 | 207 | if (ver == 3 || ver == 4) { |
a8682106 | 208 | struct iwl_phy_context_cmd cmd = {}; |
8ca151b5 | 209 | |
a8682106 MG |
210 | /* Set the command header fields */ |
211 | iwl_mvm_phy_ctxt_cmd_hdr(ctxt, &cmd, action); | |
212 | ||
213 | /* Set the command data */ | |
a171399f | 214 | iwl_mvm_phy_ctxt_cmd_data(mvm, ctxt, &cmd, chandef, |
a8682106 MG |
215 | chains_static, |
216 | chains_dynamic); | |
217 | ||
218 | ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, | |
219 | 0, sizeof(cmd), &cmd); | |
220 | } else if (ver < 3) { | |
221 | struct iwl_phy_context_cmd_v1 cmd = {}; | |
222 | u16 len = sizeof(cmd) - iwl_mvm_chan_info_padding(mvm); | |
223 | ||
224 | /* Set the command header fields */ | |
225 | iwl_mvm_phy_ctxt_cmd_hdr(ctxt, | |
226 | (struct iwl_phy_context_cmd *)&cmd, | |
227 | action); | |
228 | ||
229 | /* Set the command data */ | |
a171399f | 230 | iwl_mvm_phy_ctxt_cmd_data_v1(mvm, ctxt, &cmd, chandef, |
a8682106 MG |
231 | chains_static, |
232 | chains_dynamic); | |
233 | ret = iwl_mvm_send_cmd_pdu(mvm, PHY_CONTEXT_CMD, | |
234 | 0, len, &cmd); | |
235 | } else { | |
236 | IWL_ERR(mvm, "PHY ctxt cmd error ver %d not supported\n", ver); | |
237 | return -EOPNOTSUPP; | |
238 | } | |
8ca151b5 | 239 | |
8ca151b5 | 240 | |
c48e93a6 | 241 | if (ret) { |
8ca151b5 | 242 | IWL_ERR(mvm, "PHY ctxt cmd error. ret=%d\n", ret); |
c48e93a6 JB |
243 | return ret; |
244 | } | |
245 | ||
246 | if (action != FW_CTXT_ACTION_REMOVE) | |
247 | return iwl_mvm_phy_send_rlc(mvm, ctxt, chains_static, | |
248 | chains_dynamic); | |
249 | ||
250 | return 0; | |
8ca151b5 JB |
251 | } |
252 | ||
8ca151b5 JB |
253 | /* |
254 | * Send a command to add a PHY context based on the current HW configuration. | |
255 | */ | |
256 | int iwl_mvm_phy_ctxt_add(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, | |
257 | struct cfg80211_chan_def *chandef, | |
258 | u8 chains_static, u8 chains_dynamic) | |
259 | { | |
a32a8494 EG |
260 | int ret; |
261 | ||
debff618 JB |
262 | WARN_ON(!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status) && |
263 | ctxt->ref); | |
8ca151b5 | 264 | lockdep_assert_held(&mvm->mutex); |
8ca151b5 | 265 | |
fe0f2de3 | 266 | ctxt->channel = chandef->chan; |
c48e93a6 JB |
267 | ctxt->width = chandef->width; |
268 | ctxt->center_freq1 = chandef->center_freq1; | |
8ca151b5 | 269 | |
a32a8494 EG |
270 | ret = iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, |
271 | chains_static, chains_dynamic, | |
272 | FW_CTXT_ACTION_ADD); | |
273 | ||
274 | if (ret) | |
275 | return ret; | |
276 | ||
277 | ctxt->ref++; | |
278 | ||
279 | return 0; | |
fe0f2de3 | 280 | } |
8ca151b5 | 281 | |
fe0f2de3 IP |
282 | /* |
283 | * Update the number of references to the given PHY context. This is valid only | |
284 | * in case the PHY context was already created, i.e., its reference count > 0. | |
285 | */ | |
286 | void iwl_mvm_phy_ctxt_ref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) | |
287 | { | |
fe0f2de3 | 288 | lockdep_assert_held(&mvm->mutex); |
a32a8494 EG |
289 | |
290 | /* If we were taking the first ref, we should have | |
291 | * called iwl_mvm_phy_ctxt_add. | |
292 | */ | |
293 | WARN_ON(!ctxt->ref); | |
fe0f2de3 | 294 | ctxt->ref++; |
8ca151b5 JB |
295 | } |
296 | ||
297 | /* | |
298 | * Send a command to modify the PHY context based on the current HW | |
299 | * configuration. Note that the function does not check that the configuration | |
300 | * changed. | |
301 | */ | |
302 | int iwl_mvm_phy_ctxt_changed(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt, | |
303 | struct cfg80211_chan_def *chandef, | |
304 | u8 chains_static, u8 chains_dynamic) | |
305 | { | |
d172a5ef | 306 | enum iwl_ctxt_action action = FW_CTXT_ACTION_MODIFY; |
730a1891 | 307 | |
8ca151b5 JB |
308 | lockdep_assert_held(&mvm->mutex); |
309 | ||
f3276ff0 EG |
310 | if (WARN_ON_ONCE(!ctxt->ref)) |
311 | return -EINVAL; | |
312 | ||
313 | if (iwl_fw_lookup_cmd_ver(mvm->fw, WIDE_ID(DATA_PATH_GROUP, | |
314 | RLC_CONFIG_CMD), 0) >= 2 && | |
c48e93a6 JB |
315 | ctxt->channel == chandef->chan && |
316 | ctxt->width == chandef->width && | |
317 | ctxt->center_freq1 == chandef->center_freq1) | |
318 | return iwl_mvm_phy_send_rlc(mvm, ctxt, chains_static, | |
319 | chains_dynamic); | |
320 | ||
91109f42 JB |
321 | if (fw_has_capa(&mvm->fw->ucode_capa, |
322 | IWL_UCODE_TLV_CAPA_BINDING_CDB_SUPPORT) && | |
730a1891 SS |
323 | ctxt->channel->band != chandef->chan->band) { |
324 | int ret; | |
325 | ||
326 | /* ... remove it here ...*/ | |
327 | ret = iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, | |
328 | chains_static, chains_dynamic, | |
a8682106 | 329 | FW_CTXT_ACTION_REMOVE); |
730a1891 SS |
330 | if (ret) |
331 | return ret; | |
332 | ||
333 | /* ... and proceed to add it again */ | |
334 | action = FW_CTXT_ACTION_ADD; | |
335 | } | |
336 | ||
8ca151b5 | 337 | ctxt->channel = chandef->chan; |
7a20bcce | 338 | ctxt->width = chandef->width; |
c48e93a6 JB |
339 | ctxt->center_freq1 = chandef->center_freq1; |
340 | ||
8ca151b5 JB |
341 | return iwl_mvm_phy_ctxt_apply(mvm, ctxt, chandef, |
342 | chains_static, chains_dynamic, | |
a8682106 | 343 | action); |
8ca151b5 JB |
344 | } |
345 | ||
fe0f2de3 IP |
346 | void iwl_mvm_phy_ctxt_unref(struct iwl_mvm *mvm, struct iwl_mvm_phy_ctxt *ctxt) |
347 | { | |
f3276ff0 | 348 | struct cfg80211_chan_def chandef; |
fe0f2de3 | 349 | lockdep_assert_held(&mvm->mutex); |
e2821011 JB |
350 | |
351 | if (WARN_ON_ONCE(!ctxt)) | |
352 | return; | |
353 | ||
fe0f2de3 | 354 | ctxt->ref--; |
9f4ef1d7 | 355 | |
f3276ff0 EG |
356 | if (ctxt->ref) |
357 | return; | |
358 | ||
359 | cfg80211_chandef_create(&chandef, ctxt->channel, NL80211_CHAN_NO_HT); | |
360 | ||
361 | iwl_mvm_phy_ctxt_apply(mvm, ctxt, &chandef, 1, 1, | |
362 | FW_CTXT_ACTION_REMOVE); | |
fe0f2de3 | 363 | } |
cf7b491d AN |
364 | |
365 | static void iwl_mvm_binding_iterator(void *_data, u8 *mac, | |
366 | struct ieee80211_vif *vif) | |
367 | { | |
368 | unsigned long *data = _data; | |
369 | struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); | |
370 | ||
650cadb7 | 371 | if (!mvmvif->deflink.phy_ctxt) |
cf7b491d AN |
372 | return; |
373 | ||
374 | if (vif->type == NL80211_IFTYPE_STATION || | |
375 | vif->type == NL80211_IFTYPE_AP) | |
650cadb7 | 376 | __set_bit(mvmvif->deflink.phy_ctxt->id, data); |
cf7b491d AN |
377 | } |
378 | ||
379 | int iwl_mvm_phy_ctx_count(struct iwl_mvm *mvm) | |
380 | { | |
381 | unsigned long phy_ctxt_counter = 0; | |
382 | ||
383 | ieee80211_iterate_active_interfaces_atomic(mvm->hw, | |
384 | IEEE80211_IFACE_ITER_NORMAL, | |
385 | iwl_mvm_binding_iterator, | |
386 | &phy_ctxt_counter); | |
387 | ||
388 | return hweight8(phy_ctxt_counter); | |
389 | } |