1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3 * Copyright (C) 2024 Intel Corporation
4 */
5
6 #include <net/cfg80211.h>
7 #include <net/mac80211.h>
8
9 #include <fw/dbg.h>
10 #include <iwl-nvm-parse.h>
11
12 #include "mld.h"
13 #include "hcmd.h"
14 #include "mcc.h"
15
16 /* It is the caller's responsibility to free the pointer returned here */
17 static struct iwl_mcc_update_resp_v8 *
iwl_mld_parse_mcc_update_resp_v8(const struct iwl_rx_packet * pkt)18 iwl_mld_parse_mcc_update_resp_v8(const struct iwl_rx_packet *pkt)
19 {
20 const struct iwl_mcc_update_resp_v8 *mcc_resp_v8 = (const void *)pkt->data;
21 int n_channels = __le32_to_cpu(mcc_resp_v8->n_channels);
22 struct iwl_mcc_update_resp_v8 *resp_cp;
23 int notif_len = struct_size(resp_cp, channels, n_channels);
24
25 if (iwl_rx_packet_payload_len(pkt) != notif_len)
26 return ERR_PTR(-EINVAL);
27
28 resp_cp = kmemdup(mcc_resp_v8, notif_len, GFP_KERNEL);
29 if (!resp_cp)
30 return ERR_PTR(-ENOMEM);
31
32 return resp_cp;
33 }
34
35 /* It is the caller's responsibility to free the pointer returned here */
36 static struct iwl_mcc_update_resp_v8 *
iwl_mld_parse_mcc_update_resp_v5_v6(const struct iwl_rx_packet * pkt)37 iwl_mld_parse_mcc_update_resp_v5_v6(const struct iwl_rx_packet *pkt)
38 {
39 const struct iwl_mcc_update_resp_v4 *mcc_resp_v4 = (const void *)pkt->data;
40 struct iwl_mcc_update_resp_v8 *resp_cp;
41 int n_channels = __le32_to_cpu(mcc_resp_v4->n_channels);
42 int resp_len;
43
44 if (iwl_rx_packet_payload_len(pkt) !=
45 struct_size(mcc_resp_v4, channels, n_channels))
46 return ERR_PTR(-EINVAL);
47
48 resp_len = struct_size(resp_cp, channels, n_channels);
49 resp_cp = kzalloc(resp_len, GFP_KERNEL);
50 if (!resp_cp)
51 return ERR_PTR(-ENOMEM);
52
53 resp_cp->status = mcc_resp_v4->status;
54 resp_cp->mcc = mcc_resp_v4->mcc;
55 resp_cp->cap = cpu_to_le32(le16_to_cpu(mcc_resp_v4->cap));
56 resp_cp->source_id = mcc_resp_v4->source_id;
57 resp_cp->geo_info = mcc_resp_v4->geo_info;
58 resp_cp->n_channels = mcc_resp_v4->n_channels;
59 memcpy(resp_cp->channels, mcc_resp_v4->channels,
60 n_channels * sizeof(__le32));
61
62 return resp_cp;
63 }
64
65 /* It is the caller's responsibility to free the pointer returned here */
66 static struct iwl_mcc_update_resp_v8 *
iwl_mld_update_mcc(struct iwl_mld * mld,const char * alpha2,enum iwl_mcc_source src_id)67 iwl_mld_update_mcc(struct iwl_mld *mld, const char *alpha2,
68 enum iwl_mcc_source src_id)
69 {
70 int resp_ver = iwl_fw_lookup_notif_ver(mld->fw, LONG_GROUP,
71 MCC_UPDATE_CMD, 0);
72 struct iwl_mcc_update_cmd mcc_update_cmd = {
73 .mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
74 .source_id = (u8)src_id,
75 };
76 struct iwl_mcc_update_resp_v8 *resp_cp;
77 struct iwl_rx_packet *pkt;
78 struct iwl_host_cmd cmd = {
79 .id = MCC_UPDATE_CMD,
80 .flags = CMD_WANT_SKB,
81 .data = { &mcc_update_cmd },
82 .len[0] = sizeof(mcc_update_cmd),
83 };
84 int ret;
85 u16 mcc;
86
87 IWL_DEBUG_LAR(mld, "send MCC update to FW with '%c%c' src = %d\n",
88 alpha2[0], alpha2[1], src_id);
89
90 ret = iwl_mld_send_cmd(mld, &cmd);
91 if (ret)
92 return ERR_PTR(ret);
93
94 pkt = cmd.resp_pkt;
95
96 /* For Wifi-7 radios, we get version 8
97 * For Wifi-6E radios, we get version 6
98 * For Wifi-6 radios, we get version 5, but 5, 6, and 4 are compatible.
99 */
100 switch (resp_ver) {
101 case 5:
102 case 6:
103 resp_cp = iwl_mld_parse_mcc_update_resp_v5_v6(pkt);
104 break;
105 case 8:
106 resp_cp = iwl_mld_parse_mcc_update_resp_v8(pkt);
107 break;
108 default:
109 IWL_FW_CHECK_FAILED(mld, "Unknown MCC_UPDATE_CMD version %d\n", resp_ver);
110 resp_cp = ERR_PTR(-EINVAL);
111 }
112
113 if (IS_ERR(resp_cp))
114 goto exit;
115
116 mcc = le16_to_cpu(resp_cp->mcc);
117
118 IWL_FW_CHECK(mld, !mcc, "mcc can't be 0: %d\n", mcc);
119
120 IWL_DEBUG_LAR(mld,
121 "MCC response status: 0x%x. new MCC: 0x%x ('%c%c')\n",
122 le32_to_cpu(resp_cp->status), mcc, mcc >> 8, mcc & 0xff);
123
124 exit:
125 iwl_free_resp(&cmd);
126 return resp_cp;
127 }
128
129 /* It is the caller's responsibility to free the pointer returned here */
130 struct ieee80211_regdomain *
iwl_mld_get_regdomain(struct iwl_mld * mld,const char * alpha2,enum iwl_mcc_source src_id,bool * changed)131 iwl_mld_get_regdomain(struct iwl_mld *mld,
132 const char *alpha2,
133 enum iwl_mcc_source src_id,
134 bool *changed)
135 {
136 struct ieee80211_regdomain *regd = NULL;
137 struct iwl_mcc_update_resp_v8 *resp;
138 u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP,
139 MCC_UPDATE_CMD, 0);
140
141 IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2);
142
143 lockdep_assert_wiphy(mld->wiphy);
144
145 resp = iwl_mld_update_mcc(mld, alpha2, src_id);
146 if (IS_ERR(resp)) {
147 IWL_DEBUG_LAR(mld, "Could not get update from FW %ld\n",
148 PTR_ERR(resp));
149 resp = NULL;
150 goto out;
151 }
152
153 if (changed) {
154 u32 status = le32_to_cpu(resp->status);
155
156 *changed = (status == MCC_RESP_NEW_CHAN_PROFILE ||
157 status == MCC_RESP_ILLEGAL);
158 }
159 IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver);
160
161 regd = iwl_parse_nvm_mcc_info(mld->trans->dev, mld->cfg,
162 __le32_to_cpu(resp->n_channels),
163 resp->channels,
164 __le16_to_cpu(resp->mcc),
165 __le16_to_cpu(resp->geo_info),
166 le32_to_cpu(resp->cap), resp_ver);
167
168 if (IS_ERR(regd)) {
169 IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n",
170 PTR_ERR(regd));
171 goto out;
172 }
173
174 IWL_DEBUG_LAR(mld, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n",
175 regd->alpha2, regd->alpha2[0],
176 regd->alpha2[1], resp->source_id);
177
178 mld->mcc_src = resp->source_id;
179
180 if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing,
181 le16_to_cpu(resp->mcc)))
182 ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING);
183 else
184 __clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING, mld->hw->flags);
185
186 out:
187 kfree(resp);
188 return regd;
189 }
190
191 /* It is the caller's responsibility to free the pointer returned here */
192 static struct ieee80211_regdomain *
iwl_mld_get_current_regdomain(struct iwl_mld * mld,bool * changed)193 iwl_mld_get_current_regdomain(struct iwl_mld *mld,
194 bool *changed)
195 {
196 return iwl_mld_get_regdomain(mld, "ZZ",
197 MCC_SOURCE_GET_CURRENT, changed);
198 }
199
iwl_mld_update_changed_regdomain(struct iwl_mld * mld)200 void iwl_mld_update_changed_regdomain(struct iwl_mld *mld)
201 {
202 struct ieee80211_regdomain *regd;
203 bool changed;
204
205 regd = iwl_mld_get_current_regdomain(mld, &changed);
206
207 if (IS_ERR_OR_NULL(regd))
208 return;
209
210 if (changed)
211 regulatory_set_wiphy_regd(mld->wiphy, regd);
212 kfree(regd);
213 }
214
iwl_mld_apply_last_mcc(struct iwl_mld * mld,const char * alpha2)215 static int iwl_mld_apply_last_mcc(struct iwl_mld *mld,
216 const char *alpha2)
217 {
218 struct ieee80211_regdomain *regd;
219 u32 used_src;
220 bool changed;
221 int ret;
222
223 /* save the last source in case we overwrite it below */
224 used_src = mld->mcc_src;
225
226 /* Notify the firmware we support wifi location updates */
227 regd = iwl_mld_get_current_regdomain(mld, NULL);
228 if (!IS_ERR_OR_NULL(regd))
229 kfree(regd);
230
231 /* Now set our last stored MCC and source */
232 regd = iwl_mld_get_regdomain(mld, alpha2, used_src,
233 &changed);
234 if (IS_ERR_OR_NULL(regd))
235 return -EIO;
236
237 /* update cfg80211 if the regdomain was changed */
238 if (changed)
239 ret = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
240 else
241 ret = 0;
242
243 kfree(regd);
244 return ret;
245 }
246
iwl_mld_init_mcc(struct iwl_mld * mld)247 int iwl_mld_init_mcc(struct iwl_mld *mld)
248 {
249 const struct ieee80211_regdomain *r;
250 struct ieee80211_regdomain *regd;
251 char mcc[3];
252 int retval;
253
254 /* try to replay the last set MCC to FW */
255 r = wiphy_dereference(mld->wiphy, mld->wiphy->regd);
256
257 if (r)
258 return iwl_mld_apply_last_mcc(mld, r->alpha2);
259
260 regd = iwl_mld_get_current_regdomain(mld, NULL);
261 if (IS_ERR_OR_NULL(regd))
262 return -EIO;
263
264 if (!iwl_bios_get_mcc(&mld->fwrt, mcc)) {
265 kfree(regd);
266 regd = iwl_mld_get_regdomain(mld, mcc, MCC_SOURCE_BIOS, NULL);
267 if (IS_ERR_OR_NULL(regd))
268 return -EIO;
269 }
270
271 retval = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
272
273 kfree(regd);
274 return retval;
275 }
276
iwl_mld_find_assoc_vif_iterator(void * data,u8 * mac,struct ieee80211_vif * vif)277 static void iwl_mld_find_assoc_vif_iterator(void *data, u8 *mac,
278 struct ieee80211_vif *vif)
279 {
280 bool *assoc = data;
281
282 if (vif->type == NL80211_IFTYPE_STATION &&
283 vif->cfg.assoc)
284 *assoc = true;
285 }
286
iwl_mld_is_a_vif_assoc(struct iwl_mld * mld)287 static bool iwl_mld_is_a_vif_assoc(struct iwl_mld *mld)
288 {
289 bool assoc = false;
290
291 ieee80211_iterate_active_interfaces_atomic(mld->hw,
292 IEEE80211_IFACE_ITER_NORMAL,
293 iwl_mld_find_assoc_vif_iterator,
294 &assoc);
295 return assoc;
296 }
297
iwl_mld_handle_update_mcc(struct iwl_mld * mld,struct iwl_rx_packet * pkt)298 void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt)
299 {
300 struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
301 enum iwl_mcc_source src;
302 char mcc[3];
303 struct ieee80211_regdomain *regd;
304 bool changed;
305
306 lockdep_assert_wiphy(mld->wiphy);
307
308 if (iwl_mld_is_a_vif_assoc(mld) &&
309 notif->source_id == MCC_SOURCE_WIFI) {
310 IWL_DEBUG_LAR(mld, "Ignore mcc update while associated\n");
311 return;
312 }
313
314 mcc[0] = le16_to_cpu(notif->mcc) >> 8;
315 mcc[1] = le16_to_cpu(notif->mcc) & 0xff;
316 mcc[2] = '\0';
317 src = notif->source_id;
318
319 IWL_DEBUG_LAR(mld,
320 "RX: received chub update mcc cmd (mcc '%s' src %d)\n",
321 mcc, src);
322 regd = iwl_mld_get_regdomain(mld, mcc, src, &changed);
323 if (IS_ERR_OR_NULL(regd))
324 return;
325
326 if (changed)
327 regulatory_set_wiphy_regd(mld->hw->wiphy, regd);
328 kfree(regd);
329 }
330