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