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