xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/mcc.c (revision ab93e0dd72c37d378dd936f031ffb83ff2bd87ce)
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 *
iwl_mld_copy_mcc_resp(const struct iwl_rx_packet * pkt)18 iwl_mld_copy_mcc_resp(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_update_mcc(struct iwl_mld * mld,const char * alpha2,enum iwl_mcc_source src_id)37 iwl_mld_update_mcc(struct iwl_mld *mld, const char *alpha2,
38 		   enum iwl_mcc_source src_id)
39 {
40 	struct iwl_mcc_update_cmd mcc_update_cmd = {
41 		.mcc = cpu_to_le16(alpha2[0] << 8 | alpha2[1]),
42 		.source_id = (u8)src_id,
43 	};
44 	struct iwl_mcc_update_resp_v8 *resp_cp;
45 	struct iwl_rx_packet *pkt;
46 	struct iwl_host_cmd cmd = {
47 		.id = MCC_UPDATE_CMD,
48 		.flags = CMD_WANT_SKB,
49 		.data = { &mcc_update_cmd },
50 		.len[0] = sizeof(mcc_update_cmd),
51 	};
52 	int ret;
53 	u16 mcc;
54 
55 	IWL_DEBUG_LAR(mld, "send MCC update to FW with '%c%c' src = %d\n",
56 		      alpha2[0], alpha2[1], src_id);
57 
58 	ret = iwl_mld_send_cmd(mld, &cmd);
59 	if (ret)
60 		return ERR_PTR(ret);
61 
62 	pkt = cmd.resp_pkt;
63 
64 	resp_cp = iwl_mld_copy_mcc_resp(pkt);
65 	if (IS_ERR(resp_cp))
66 		goto exit;
67 
68 	mcc = le16_to_cpu(resp_cp->mcc);
69 
70 	IWL_FW_CHECK(mld, !mcc, "mcc can't be 0: %d\n", mcc);
71 
72 	IWL_DEBUG_LAR(mld,
73 		      "MCC response status: 0x%x. new MCC: 0x%x ('%c%c')\n",
74 		      le32_to_cpu(resp_cp->status), mcc, mcc >> 8, mcc & 0xff);
75 
76 exit:
77 	iwl_free_resp(&cmd);
78 	return resp_cp;
79 }
80 
81 /* It is the caller's responsibility to free the pointer returned here */
82 struct ieee80211_regdomain *
iwl_mld_get_regdomain(struct iwl_mld * mld,const char * alpha2,enum iwl_mcc_source src_id,bool * changed)83 iwl_mld_get_regdomain(struct iwl_mld *mld,
84 		      const char *alpha2,
85 		      enum iwl_mcc_source src_id,
86 		      bool *changed)
87 {
88 	struct ieee80211_regdomain *regd = NULL;
89 	struct iwl_mcc_update_resp_v8 *resp;
90 	u8 resp_ver = iwl_fw_lookup_notif_ver(mld->fw, IWL_ALWAYS_LONG_GROUP,
91 					      MCC_UPDATE_CMD, 0);
92 
93 	IWL_DEBUG_LAR(mld, "Getting regdomain data for %s from FW\n", alpha2);
94 
95 	lockdep_assert_wiphy(mld->wiphy);
96 
97 	resp = iwl_mld_update_mcc(mld, alpha2, src_id);
98 	if (IS_ERR(resp)) {
99 		IWL_DEBUG_LAR(mld, "Could not get update from FW %ld\n",
100 			      PTR_ERR(resp));
101 		resp = NULL;
102 		goto out;
103 	}
104 
105 	if (changed) {
106 		u32 status = le32_to_cpu(resp->status);
107 
108 		*changed = (status == MCC_RESP_NEW_CHAN_PROFILE ||
109 			    status == MCC_RESP_ILLEGAL);
110 	}
111 	IWL_DEBUG_LAR(mld, "MCC update response version: %d\n", resp_ver);
112 
113 	regd = iwl_parse_nvm_mcc_info(mld->trans,
114 				      __le32_to_cpu(resp->n_channels),
115 				      resp->channels,
116 				      __le16_to_cpu(resp->mcc),
117 				      __le16_to_cpu(resp->geo_info),
118 				      le32_to_cpu(resp->cap), resp_ver);
119 
120 	if (IS_ERR(regd)) {
121 		IWL_DEBUG_LAR(mld, "Could not get parse update from FW %ld\n",
122 			      PTR_ERR(regd));
123 		goto out;
124 	}
125 
126 	IWL_DEBUG_LAR(mld, "setting alpha2 from FW to %s (0x%x, 0x%x) src=%d\n",
127 		      regd->alpha2, regd->alpha2[0],
128 		      regd->alpha2[1], resp->source_id);
129 
130 	mld->mcc_src = resp->source_id;
131 
132 	/* FM is the earliest supported and later always do puncturing */
133 	if (CSR_HW_RFID_TYPE(mld->trans->info.hw_rf_id) == IWL_CFG_RF_TYPE_FM) {
134 		if (!iwl_puncturing_is_allowed_in_bios(mld->bios_enable_puncturing,
135 						       le16_to_cpu(resp->mcc)))
136 			ieee80211_hw_set(mld->hw, DISALLOW_PUNCTURING);
137 		else
138 			__clear_bit(IEEE80211_HW_DISALLOW_PUNCTURING,
139 				    mld->hw->flags);
140 	}
141 
142 out:
143 	kfree(resp);
144 	return regd;
145 }
146 
147 /* It is the caller's responsibility to free the pointer returned here */
148 static struct ieee80211_regdomain *
iwl_mld_get_current_regdomain(struct iwl_mld * mld,bool * changed)149 iwl_mld_get_current_regdomain(struct iwl_mld *mld,
150 			      bool *changed)
151 {
152 	return iwl_mld_get_regdomain(mld, "ZZ",
153 				     MCC_SOURCE_GET_CURRENT, changed);
154 }
155 
iwl_mld_update_changed_regdomain(struct iwl_mld * mld)156 void iwl_mld_update_changed_regdomain(struct iwl_mld *mld)
157 {
158 	struct ieee80211_regdomain *regd;
159 	bool changed;
160 
161 	regd = iwl_mld_get_current_regdomain(mld, &changed);
162 
163 	if (IS_ERR_OR_NULL(regd))
164 		return;
165 
166 	if (changed)
167 		regulatory_set_wiphy_regd(mld->wiphy, regd);
168 	kfree(regd);
169 }
170 
iwl_mld_apply_last_mcc(struct iwl_mld * mld,const char * alpha2)171 static int iwl_mld_apply_last_mcc(struct iwl_mld *mld,
172 				  const char *alpha2)
173 {
174 	struct ieee80211_regdomain *regd;
175 	u32 used_src;
176 	bool changed;
177 	int ret;
178 
179 	/* save the last source in case we overwrite it below */
180 	used_src = mld->mcc_src;
181 
182 	/* Notify the firmware we support wifi location updates */
183 	regd = iwl_mld_get_current_regdomain(mld, NULL);
184 	if (!IS_ERR_OR_NULL(regd))
185 		kfree(regd);
186 
187 	/* Now set our last stored MCC and source */
188 	regd = iwl_mld_get_regdomain(mld, alpha2, used_src,
189 				     &changed);
190 	if (IS_ERR_OR_NULL(regd))
191 		return -EIO;
192 
193 	/* update cfg80211 if the regdomain was changed */
194 	if (changed)
195 		ret = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
196 	else
197 		ret = 0;
198 
199 	kfree(regd);
200 	return ret;
201 }
202 
iwl_mld_init_mcc(struct iwl_mld * mld)203 int iwl_mld_init_mcc(struct iwl_mld *mld)
204 {
205 	const struct ieee80211_regdomain *r;
206 	struct ieee80211_regdomain *regd;
207 	char mcc[3];
208 	int retval;
209 
210 	/* try to replay the last set MCC to FW */
211 	r = wiphy_dereference(mld->wiphy, mld->wiphy->regd);
212 
213 	if (r)
214 		return iwl_mld_apply_last_mcc(mld, r->alpha2);
215 
216 	regd = iwl_mld_get_current_regdomain(mld, NULL);
217 	if (IS_ERR_OR_NULL(regd))
218 		return -EIO;
219 
220 	if (!iwl_bios_get_mcc(&mld->fwrt, mcc)) {
221 		kfree(regd);
222 		regd = iwl_mld_get_regdomain(mld, mcc, MCC_SOURCE_BIOS, NULL);
223 		if (IS_ERR_OR_NULL(regd))
224 			return -EIO;
225 	}
226 
227 	retval = regulatory_set_wiphy_regd_sync(mld->wiphy, regd);
228 
229 	kfree(regd);
230 	return retval;
231 }
232 
iwl_mld_find_assoc_vif_iterator(void * data,u8 * mac,struct ieee80211_vif * vif)233 static void iwl_mld_find_assoc_vif_iterator(void *data, u8 *mac,
234 					    struct ieee80211_vif *vif)
235 {
236 	bool *assoc = data;
237 
238 	if (vif->type == NL80211_IFTYPE_STATION &&
239 	    vif->cfg.assoc)
240 		*assoc = true;
241 }
242 
iwl_mld_is_a_vif_assoc(struct iwl_mld * mld)243 static bool iwl_mld_is_a_vif_assoc(struct iwl_mld *mld)
244 {
245 	bool assoc = false;
246 
247 	ieee80211_iterate_active_interfaces_atomic(mld->hw,
248 						   IEEE80211_IFACE_ITER_NORMAL,
249 						   iwl_mld_find_assoc_vif_iterator,
250 						   &assoc);
251 	return assoc;
252 }
253 
iwl_mld_handle_update_mcc(struct iwl_mld * mld,struct iwl_rx_packet * pkt)254 void iwl_mld_handle_update_mcc(struct iwl_mld *mld, struct iwl_rx_packet *pkt)
255 {
256 	struct iwl_mcc_chub_notif *notif = (void *)pkt->data;
257 	enum iwl_mcc_source src;
258 	char mcc[3];
259 	struct ieee80211_regdomain *regd;
260 	bool changed;
261 
262 	lockdep_assert_wiphy(mld->wiphy);
263 
264 	if (iwl_mld_is_a_vif_assoc(mld) &&
265 	    notif->source_id == MCC_SOURCE_WIFI) {
266 		IWL_DEBUG_LAR(mld, "Ignore mcc update while associated\n");
267 		return;
268 	}
269 
270 	mcc[0] = le16_to_cpu(notif->mcc) >> 8;
271 	mcc[1] = le16_to_cpu(notif->mcc) & 0xff;
272 	mcc[2] = '\0';
273 	src = notif->source_id;
274 
275 	IWL_DEBUG_LAR(mld,
276 		      "RX: received chub update mcc cmd (mcc '%s' src %d)\n",
277 		      mcc, src);
278 	regd = iwl_mld_get_regdomain(mld, mcc, src, &changed);
279 	if (IS_ERR_OR_NULL(regd))
280 		return;
281 
282 	if (changed)
283 		regulatory_set_wiphy_regd(mld->hw->wiphy, regd);
284 	kfree(regd);
285 }
286