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_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 * 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 * 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 * 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 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 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 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 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 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 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