xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/mcc.c (revision ea045a0de3b9de8f917f0149783bae8dc14fcbb2)
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright (C) 2024-2025 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 *
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 *
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 *
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 *
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,
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 	/* FM is the earliest supported and later always do puncturing */
181 	if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) {
182 		if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing,
183 						       le16_to_cpu(resp->mcc)))
184 			ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING);
185 		else
186 			__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING,
187 				    mld->hw->flags);
188 	}
189 
190 out:
191 	kfree(resp);
192 	return regd;
193 }
194 
195 /* It is the caller's responsibility to free the pointer returned here */
196 static struct ieee80211_regdomain *
197 iwl_mld_get_current_regdomain(struct iwl_mld *mld,
198 			      bool *changed)
199 {
200 	return iwl_mld_get_regdomain(mld, "ZZ",
201 				     MCC_SOURCE_GET_CURRENT, changed);
202 }
203 
204 void iwl_mld_update_changed_regdomain(struct iwl_mld *mld)
205 {
206 	struct ieee80211_regdomain *regd;
207 	bool changed;
208 
209 	regd = iwl_mld_get_current_regdomain(mld, &changed);
210 
211 	if (IS_ERR_OR_NULL(regd))
212 		return;
213 
214 	if (changed)
215 		regulatory_set_wiphy_regd(mld->wiphy, regd);
216 	kfree(regd);
217 }
218 
219 static int iwl_mld_apply_last_mcc(struct iwl_mld *mld,
220 				  const char *alpha2)
221 {
222 	struct ieee80211_regdomain *regd;
223 	u32 used_src;
224 	bool changed;
225 	int ret;
226 
227 	/* save the last source in case we overwrite it below */
228 	used_src = mld->mcc_src;
229 
230 	/* Notify the firmware we support wifi location updates */
231 	regd = iwl_mld_get_current_regdomain(mld, NULL);
232 	if (!IS_ERR_OR_NULL(regd))
233 		kfree(regd);
234 
235 	/* Now set our last stored MCC and source */
236 	regd = iwl_mld_get_regdomain(mld, alpha2, used_src,
237 				     &changed);
238 	if (IS_ERR_OR_NULL(regd))
239 		return -EIO;
240 
241 	/* update cfg80211 if the regdomain was changed */
242 	if (changed)
243 		ret = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
244 	else
245 		ret = 0;
246 
247 	kfree(regd);
248 	return ret;
249 }
250 
251 int iwl_mld_init_mcc(struct iwl_mld *mld)
252 {
253 	const struct ieee80211_regdomain *r;
254 	struct ieee80211_regdomain *regd;
255 	char mcc[3];
256 	int retval;
257 
258 	/* try to replay the last set MCC to FW */
259 	r = wiphy_dereference(mld->wiphy, mld->wiphy->regd);
260 
261 	if (r)
262 		return iwl_mld_apply_last_mcc(mld, r->alpha2);
263 
264 	regd = iwl_mld_get_current_regdomain(mld, NULL);
265 	if (IS_ERR_OR_NULL(regd))
266 		return -EIO;
267 
268 	if (!iwl_bios_get_mcc(&mld->fwrt, mcc)) {
269 		kfree(regd);
270 		regd = iwl_mld_get_regdomain(mld, mcc, MCC_SOURCE_BIOS, NULL);
271 		if (IS_ERR_OR_NULL(regd))
272 			return -EIO;
273 	}
274 
275 	retval = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
276 
277 	kfree(regd);
278 	return retval;
279 }
280 
281 static void iwl_mld_find_assoc_vif_iterator(void *data, u8 *mac,
282 					    struct ieee80211_vif *vif)
283 {
284 	bool *assoc = data;
285 
286 	if (vif->type == NL80211_IFTYPE_STATION &&
287 	    vif->cfg.assoc)
288 		*assoc = true;
289 }
290 
291 static bool iwl_mld_is_a_vif_assoc(struct iwl_mld *mld)
292 {
293 	bool assoc = false;
294 
295 	ieee80211_iterate_active_interfaces_atomic(mld->hw,
296 						   IEEE80211_IFACE_ITER_NORMAL,
297 						   iwl_mld_find_assoc_vif_iterator,
298 						   &assoc);
299 	return assoc;
300 }
301 
302 void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt)
303 {
304 	struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
305 	enum iwl_mcc_source src;
306 	char mcc[3];
307 	struct ieee80211_regdomain *regd;
308 	bool changed;
309 
310 	lockdep_assert_wiphy(mld->wiphy);
311 
312 	if (iwl_mld_is_a_vif_assoc(mld) &&
313 	    notif->source_id == MCC_SOURCE_WIFI) {
314 		IWL_DEBUG_LAR(mld, "Ignore mcc update while associated\n");
315 		return;
316 	}
317 
318 	mcc[0] = le16_to_cpu(notif->mcc) >> 8;
319 	mcc[1] = le16_to_cpu(notif->mcc) & 0xff;
320 	mcc[2] = '\0';
321 	src = notif->source_id;
322 
323 	IWL_DEBUG_LAR(mld,
324 		      "RX: received chub update mcc cmd (mcc '%s' src %d)\n",
325 		      mcc, src);
326 	regd = iwl_mld_get_regdomain(mld, mcc, src, &changed);
327 	if (IS_ERR_OR_NULL(regd))
328 		return;
329 
330 	if (changed)
331 		regulatory_set_wiphy_regd(mld->hw->wiphy, regd);
332 	kfree(regd);
333 }
334