1 /* 2 * Copyright (c) 2011-2018, 2020 The Linux Foundation. All rights reserved. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for 5 * any purpose with or without fee is hereby granted, provided that the 6 * above copyright notice and this permission notice appear in all 7 * copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 * PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * DOC: contains scan 11d api and functionality 21 */ 22 #include <qdf_status.h> 23 #include <wlan_objmgr_psoc_obj.h> 24 #include <wlan_objmgr_pdev_obj.h> 25 #include <wlan_objmgr_vdev_obj.h> 26 #include <wlan_scan_public_structs.h> 27 #include <wlan_scan_utils_api.h> 28 #include "wlan_scan_main.h" 29 #include "wlan_scan_11d.h" 30 #include "wlan_reg_services_api.h" 31 #include "wlan_reg_ucfg_api.h" 32 33 /** 34 * wlan_pdevid_get_cc_db() - private API to get cc db from pdev id 35 * @psoc: psoc object 36 * @pdev_id: pdev id 37 * 38 * Return: cc db for the pdev id 39 */ 40 static struct scan_country_code_db * 41 wlan_pdevid_get_cc_db(struct wlan_objmgr_psoc *psoc, uint8_t pdev_id) 42 { 43 struct wlan_scan_obj *scan_obj; 44 45 if (pdev_id > WLAN_UMAC_MAX_PDEVS) { 46 scm_err("invalid pdev_id %d", pdev_id); 47 return NULL; 48 } 49 50 scan_obj = wlan_psoc_get_scan_obj(psoc); 51 if (!scan_obj) 52 return NULL; 53 54 return &scan_obj->cc_db[pdev_id]; 55 } 56 57 /** 58 * wlan_pdev_get_cc_db() - private API to get cc db from pdev 59 * @psoc: psoc object 60 * @pdev: Pdev object 61 * 62 * Return: cc db for the pdev 63 */ 64 static struct scan_country_code_db * 65 wlan_pdev_get_cc_db(struct wlan_objmgr_psoc *psoc, 66 struct wlan_objmgr_pdev *pdev) 67 { 68 uint8_t pdev_id; 69 70 if (!pdev) { 71 scm_err("pdev is NULL"); 72 return NULL; 73 } 74 pdev_id = wlan_objmgr_pdev_get_pdev_id(pdev); 75 76 return wlan_pdevid_get_cc_db(psoc, pdev_id); 77 } 78 79 /** 80 * scm_11d_elected_country_algo_fcc - private api to get cc per fcc algo 81 * @cc_db: scan country code db 82 * 83 * Return: true or false 84 */ 85 static bool 86 scm_11d_elected_country_algo_fcc(struct scan_country_code_db *cc_db) 87 { 88 uint8_t i; 89 uint8_t country_idx; 90 uint16_t max_votes; 91 bool found = false; 92 93 if (!cc_db->num_country_codes) { 94 scm_err("No AP with 11d Country code is present in scan list"); 95 return false; 96 } 97 98 max_votes = cc_db->votes[0].votes; 99 if (wlan_reg_is_us(cc_db->votes[0].cc)) { 100 found = true; 101 country_idx = 0; 102 goto algo_done; 103 } else if (max_votes >= MIN_11D_AP_COUNT) { 104 found = true; 105 country_idx = 0; 106 } 107 108 for (i = 1; i < cc_db->num_country_codes; i++) { 109 if (wlan_reg_is_us(cc_db->votes[i].cc)) { 110 found = true; 111 country_idx = i; 112 goto algo_done; 113 } 114 115 if ((max_votes < cc_db->votes[i].votes) && 116 (cc_db->votes[i].votes >= MIN_11D_AP_COUNT)) { 117 scm_debug("Votes for Country %c%c : %d", 118 cc_db->votes[i].cc[0], 119 cc_db->votes[i].cc[1], 120 cc_db->votes[i].votes); 121 max_votes = cc_db->votes[i].votes; 122 country_idx = i; 123 found = true; 124 } 125 } 126 127 algo_done: 128 if (found) { 129 qdf_mem_copy(cc_db->elected_cc, 130 cc_db->votes[country_idx].cc, 131 REG_ALPHA2_LEN + 1); 132 133 scm_debug("Selected Country is %c%c With count %d", 134 cc_db->votes[country_idx].cc[0], 135 cc_db->votes[country_idx].cc[1], 136 cc_db->votes[country_idx].votes); 137 } 138 139 return found; 140 } 141 142 /** 143 * scm_11d_elected_country_info - private api to get cc 144 * @cc_db: scan country code db 145 * 146 * Return: true or false 147 */ 148 static bool 149 scm_11d_elected_country_info(struct scan_country_code_db *cc_db) 150 { 151 uint8_t i, j = 0; 152 uint8_t max_votes; 153 154 if (!cc_db->num_country_codes) { 155 scm_err("No AP with 11d Country code is present in scan list"); 156 return false; 157 } 158 159 max_votes = cc_db->votes[0].votes; 160 161 for (i = 1; i < cc_db->num_country_codes; i++) { 162 /* 163 * If we have a tie for max votes for 2 different country codes, 164 * pick random. 165 */ 166 if (max_votes < cc_db->votes[i].votes) { 167 scm_debug("Votes for Country %c%c : %d", 168 cc_db->votes[i].cc[0], 169 cc_db->votes[i].cc[1], 170 cc_db->votes[i].votes); 171 172 max_votes = cc_db->votes[i].votes; 173 j = i; 174 } 175 } 176 177 qdf_mem_copy(cc_db->elected_cc, cc_db->votes[j].cc, 178 REG_ALPHA2_LEN + 1); 179 180 scm_debug("Selected Country is %c%c With count %d", 181 cc_db->votes[j].cc[0], 182 cc_db->votes[j].cc[1], 183 cc_db->votes[j].votes); 184 185 return true; 186 } 187 188 /** 189 * scm_11d_set_country_code - private api to set cc per 11d learning 190 * @pdev: pdev object 191 * @elected_cc: elected country code 192 * @current_cc: current country code 193 * 194 * Return: true or false 195 */ 196 static bool 197 scm_11d_set_country_code(struct wlan_objmgr_pdev *pdev, 198 uint8_t *elected_cc, uint8_t *current_cc) 199 { 200 scm_debug("elected country %c%c, current country %c%c", 201 elected_cc[0], elected_cc[1], current_cc[0], current_cc[1]); 202 203 if (!qdf_mem_cmp(elected_cc, current_cc, REG_ALPHA2_LEN + 1)) 204 return true; 205 206 wlan_reg_set_11d_country(pdev, elected_cc); 207 return true; 208 } 209 210 /** 211 * scm_11d_reset_cc_db - reset the country code db 212 * @cc_db: the pointer of country code db 213 * 214 * Return: void 215 */ 216 static void scm_11d_reset_cc_db(struct scan_country_code_db *cc_db) 217 { 218 qdf_mem_zero(cc_db->votes, sizeof(cc_db->votes)); 219 qdf_mem_zero(cc_db->elected_cc, sizeof(cc_db->elected_cc)); 220 cc_db->num_country_codes = 0; 221 } 222 223 QDF_STATUS scm_11d_cc_db_init(struct wlan_objmgr_psoc *psoc) 224 { 225 struct scan_country_code_db *cc_db; 226 struct wlan_scan_obj *scan_obj; 227 228 if (!psoc) { 229 scm_err("psoc is NULL"); 230 return QDF_STATUS_E_INVAL; 231 } 232 233 scan_obj = wlan_psoc_get_scan_obj(psoc); 234 if (!scan_obj) { 235 scm_err("scan_obj is NULL"); 236 return QDF_STATUS_E_INVAL; 237 } 238 239 cc_db = (struct scan_country_code_db *)qdf_mem_malloc_atomic( 240 sizeof(struct scan_country_code_db) * WLAN_UMAC_MAX_PDEVS); 241 if (!cc_db) { 242 scm_err("alloc country code db error"); 243 return QDF_STATUS_E_INVAL; 244 } 245 246 qdf_mem_zero(cc_db, 247 sizeof(struct scan_country_code_db) * 248 WLAN_UMAC_MAX_PDEVS); 249 250 scan_obj->cc_db = cc_db; 251 return QDF_STATUS_SUCCESS; 252 } 253 254 QDF_STATUS scm_11d_cc_db_deinit(struct wlan_objmgr_psoc *psoc) 255 { 256 struct wlan_scan_obj *scan_obj; 257 258 if (!psoc) { 259 scm_err("psoc is NULL"); 260 return QDF_STATUS_E_INVAL; 261 } 262 263 scan_obj = wlan_psoc_get_scan_obj(psoc); 264 if (!scan_obj) { 265 scm_err("scan_obj is NULL"); 266 return QDF_STATUS_E_INVAL; 267 } 268 269 qdf_mem_free(scan_obj->cc_db); 270 return QDF_STATUS_SUCCESS; 271 } 272 273 /** 274 * scm_11d_handle_country_info() - API to handle 11d country info 275 * @arg: pointer to country code db 276 * @scan_entry: the pointer to scan entry 277 * 278 * Update the country code database per the country code from country IE 279 * 280 * Return: QDF_STATUS 281 */ 282 static QDF_STATUS 283 scm_11d_handle_country_info(void *arg, 284 struct scan_cache_entry *scan_entry) 285 { 286 uint8_t i; 287 bool match = false; 288 uint8_t num_country_codes; 289 struct wlan_country_ie *cc_ie; 290 struct scan_country_code_db *cc_db; 291 292 cc_ie = util_scan_entry_country(scan_entry); 293 cc_db = (struct scan_country_code_db *)arg; 294 295 if (!cc_db) 296 return QDF_STATUS_E_INVAL; 297 298 /* return success to continue iterate */ 299 if (!cc_ie) 300 return QDF_STATUS_SUCCESS; 301 302 /* just to be sure, convert to UPPER case here */ 303 for (i = 0; i < 3; i++) 304 cc_ie->cc[i] = qdf_toupper(cc_ie->cc[i]); 305 306 num_country_codes = cc_db->num_country_codes; 307 for (i = 0; i < num_country_codes; i++) { 308 match = !qdf_mem_cmp(cc_db->votes[i].cc, cc_ie->cc, 309 REG_ALPHA2_LEN); 310 if (match) 311 break; 312 } 313 314 if (match) { 315 cc_db->votes[i].votes++; 316 return QDF_STATUS_SUCCESS; 317 } 318 319 if (num_country_codes >= SCAN_MAX_NUM_COUNTRY_CODE) { 320 scm_debug("country code db already full: %d", 321 num_country_codes); 322 return QDF_STATUS_SUCCESS; 323 } 324 325 /* add country code to end of the list */ 326 qdf_mem_copy(cc_db->votes[num_country_codes].cc, cc_ie->cc, 327 REG_ALPHA2_LEN + 1); 328 cc_db->votes[num_country_codes].votes = 1; 329 cc_db->num_country_codes++; 330 return QDF_STATUS_SUCCESS; 331 } 332 333 #define CC_VOTE_CHAR_LEN 10 334 335 /** 336 * scm_11d_dump_cc_db() - Function to dump country db info 337 * @cc_db: pointer to country code db 338 * 339 * Return: None 340 */ 341 static void scm_11d_dump_cc_db(struct scan_country_code_db *cc_db) 342 { 343 uint32_t i, buf_len, num = 0; 344 uint8_t *cc, *cc_buf; 345 346 if (!cc_db || !cc_db->num_country_codes) 347 return; 348 349 buf_len = QDF_MIN(cc_db->num_country_codes, SCAN_MAX_NUM_COUNTRY_CODE); 350 buf_len = buf_len * CC_VOTE_CHAR_LEN + 1; 351 cc_buf = qdf_mem_malloc(buf_len); 352 if (!cc_buf) 353 return; 354 355 for (i = 0; i < cc_db->num_country_codes; i++) { 356 cc = cc_db->votes[i].cc; 357 num += qdf_scnprintf(cc_buf + num, buf_len - num, " %c%c:%d", 358 cc[0], cc[1], cc_db->votes[i].votes); 359 } 360 scm_debug("%s", cc_buf); 361 qdf_mem_free(cc_buf); 362 } 363 364 void scm_11d_decide_country_code(struct wlan_objmgr_vdev *vdev) 365 { 366 uint8_t current_cc[REG_ALPHA2_LEN + 1]; 367 bool found; 368 struct scan_country_code_db *cc_db; 369 struct wlan_objmgr_pdev *pdev = wlan_vdev_get_pdev(vdev); 370 struct wlan_objmgr_psoc *psoc = wlan_pdev_get_psoc(pdev); 371 372 if (!wlan_reg_11d_enabled_on_host(psoc)) 373 return; 374 375 if (SOURCE_UNKNOWN == ucfg_reg_get_cc_and_src(psoc, current_cc)) { 376 scm_err("fail to get current country code"); 377 return; 378 } 379 380 cc_db = wlan_pdev_get_cc_db(psoc, pdev); 381 if (!cc_db) { 382 scm_err("scan_db is NULL"); 383 return; 384 } 385 386 scm_iterate_scan_db(pdev, scm_11d_handle_country_info, cc_db); 387 388 scm_11d_dump_cc_db(cc_db); 389 390 if (wlan_reg_is_us(current_cc) || wlan_reg_is_world(current_cc)) 391 found = scm_11d_elected_country_algo_fcc(cc_db); 392 else 393 found = scm_11d_elected_country_info(cc_db); 394 395 if (found) 396 scm_11d_set_country_code(pdev, cc_db->elected_cc, 397 current_cc); 398 scm_11d_reset_cc_db(cc_db); 399 } 400