File Index
Files and directories
Sign in
FILE

mcc.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/intel/iwlwifi/mld/mcc.c C
  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 */
 17static struct iwl_mcc_update_resp_v8 *
 18iwl_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 */
 36static struct iwl_mcc_update_resp_v8 *
 37iwl_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
 76exit:
 77	iwl_free_resp(&cmd);
 78	return resp_cp;
 79}
 80
 81/* It is the caller's responsibility to free the pointer returned here */
 82struct ieee80211_regdomain *
 83iwl_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
142out:
143	kfree(resp);
144	return regd;
145}
146
147/* It is the caller's responsibility to free the pointer returned here */
148static struct ieee80211_regdomain *
149iwl_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
156void 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
171static 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
203int 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
233static 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
243static 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
254void 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}
~/ Read-only file preview.