Commit | Line | Data |
---|---|---|
f078f209 LR |
1 | /* |
2 | * Copyright (c) 2008 Atheros Communications Inc. | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
17 | #include <linux/kernel.h> | |
18 | #include <linux/slab.h> | |
19 | #include "core.h" | |
20 | #include "hw.h" | |
21 | #include "regd.h" | |
22 | #include "regd_common.h" | |
23 | ||
24 | static int ath9k_regd_chansort(const void *a, const void *b) | |
25 | { | |
26 | const struct ath9k_channel *ca = a; | |
27 | const struct ath9k_channel *cb = b; | |
28 | ||
29 | return (ca->channel == cb->channel) ? | |
30 | (ca->channelFlags & CHAN_FLAGS) - | |
31 | (cb->channelFlags & CHAN_FLAGS) : ca->channel - cb->channel; | |
32 | } | |
33 | ||
34 | static void | |
35 | ath9k_regd_sort(void *a, u32 n, u32 size, ath_hal_cmp_t *cmp) | |
36 | { | |
37 | u8 *aa = a; | |
38 | u8 *ai, *t; | |
39 | ||
40 | for (ai = aa + size; --n >= 1; ai += size) | |
41 | for (t = ai; t > aa; t -= size) { | |
42 | u8 *u = t - size; | |
43 | if (cmp(u, t) <= 0) | |
44 | break; | |
45 | swap(u, t, size); | |
46 | } | |
47 | } | |
48 | ||
49 | static u16 ath9k_regd_get_eepromRD(struct ath_hal *ah) | |
50 | { | |
51 | return ah->ah_currentRD & ~WORLDWIDE_ROAMING_FLAG; | |
52 | } | |
53 | ||
54 | static bool ath9k_regd_is_chan_bm_zero(u64 *bitmask) | |
55 | { | |
56 | int i; | |
57 | ||
58 | for (i = 0; i < BMLEN; i++) { | |
59 | if (bitmask[i] != 0) | |
60 | return false; | |
61 | } | |
62 | return true; | |
63 | } | |
64 | ||
65 | static bool ath9k_regd_is_eeprom_valid(struct ath_hal *ah) | |
66 | { | |
67 | u16 rd = ath9k_regd_get_eepromRD(ah); | |
68 | int i; | |
69 | ||
70 | if (rd & COUNTRY_ERD_FLAG) { | |
71 | u16 cc = rd & ~COUNTRY_ERD_FLAG; | |
72 | for (i = 0; i < ARRAY_SIZE(allCountries); i++) | |
73 | if (allCountries[i].countryCode == cc) | |
74 | return true; | |
75 | } else { | |
76 | for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) | |
77 | if (regDomainPairs[i].regDmnEnum == rd) | |
78 | return true; | |
79 | } | |
80 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
81 | "%s: invalid regulatory domain/country code 0x%x\n", | |
82 | __func__, rd); | |
83 | return false; | |
84 | } | |
85 | ||
86 | static bool ath9k_regd_is_fcc_midband_supported(struct ath_hal *ah) | |
87 | { | |
88 | u32 regcap; | |
89 | ||
90 | regcap = ah->ah_caps.halRegCap; | |
91 | ||
92 | if (regcap & AR_EEPROM_EEREGCAP_EN_FCC_MIDBAND) | |
93 | return true; | |
94 | else | |
95 | return false; | |
96 | } | |
97 | ||
98 | static bool ath9k_regd_is_ccode_valid(struct ath_hal *ah, | |
99 | u16 cc) | |
100 | { | |
101 | u16 rd; | |
102 | int i; | |
103 | ||
104 | if (cc == CTRY_DEFAULT) | |
105 | return true; | |
106 | if (cc == CTRY_DEBUG) | |
107 | return true; | |
108 | ||
109 | rd = ath9k_regd_get_eepromRD(ah); | |
110 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: EEPROM regdomain 0x%x\n", | |
111 | __func__, rd); | |
112 | ||
113 | if (rd & COUNTRY_ERD_FLAG) { | |
114 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
115 | "%s: EEPROM setting is country code %u\n", | |
116 | __func__, rd & ~COUNTRY_ERD_FLAG); | |
117 | return cc == (rd & ~COUNTRY_ERD_FLAG); | |
118 | } | |
119 | ||
120 | for (i = 0; i < ARRAY_SIZE(allCountries); i++) { | |
121 | if (cc == allCountries[i].countryCode) { | |
122 | #ifdef AH_SUPPORT_11D | |
123 | if ((rd & WORLD_SKU_MASK) == WORLD_SKU_PREFIX) | |
124 | return true; | |
125 | #endif | |
126 | if (allCountries[i].regDmnEnum == rd || | |
127 | rd == DEBUG_REG_DMN || rd == NO_ENUMRD) | |
128 | return true; | |
129 | } | |
130 | } | |
131 | return false; | |
132 | } | |
133 | ||
134 | static u32 | |
135 | ath9k_regd_get_wmodes_nreg(struct ath_hal *ah, | |
136 | struct country_code_to_enum_rd *country, | |
137 | struct regDomain *rd5GHz) | |
138 | { | |
139 | u32 modesAvail; | |
140 | ||
141 | modesAvail = ah->ah_caps.halWirelessModes; | |
142 | ||
143 | if ((modesAvail & ATH9K_MODE_SEL_11G) && (!country->allow11g)) | |
144 | modesAvail &= ~ATH9K_MODE_SEL_11G; | |
145 | if ((modesAvail & ATH9K_MODE_SEL_11A) && | |
146 | (ath9k_regd_is_chan_bm_zero(rd5GHz->chan11a))) | |
147 | modesAvail &= ~ATH9K_MODE_SEL_11A; | |
148 | ||
149 | if ((modesAvail & ATH9K_MODE_SEL_11NG_HT20) | |
150 | && (!country->allow11ng20)) | |
151 | modesAvail &= ~ATH9K_MODE_SEL_11NG_HT20; | |
152 | ||
153 | if ((modesAvail & ATH9K_MODE_SEL_11NA_HT20) | |
154 | && (!country->allow11na20)) | |
155 | modesAvail &= ~ATH9K_MODE_SEL_11NA_HT20; | |
156 | ||
157 | if ((modesAvail & ATH9K_MODE_SEL_11NG_HT40PLUS) && | |
158 | (!country->allow11ng40)) | |
159 | modesAvail &= ~ATH9K_MODE_SEL_11NG_HT40PLUS; | |
160 | ||
161 | if ((modesAvail & ATH9K_MODE_SEL_11NG_HT40MINUS) && | |
162 | (!country->allow11ng40)) | |
163 | modesAvail &= ~ATH9K_MODE_SEL_11NG_HT40MINUS; | |
164 | ||
165 | if ((modesAvail & ATH9K_MODE_SEL_11NA_HT40PLUS) && | |
166 | (!country->allow11na40)) | |
167 | modesAvail &= ~ATH9K_MODE_SEL_11NA_HT40PLUS; | |
168 | ||
169 | if ((modesAvail & ATH9K_MODE_SEL_11NA_HT40MINUS) && | |
170 | (!country->allow11na40)) | |
171 | modesAvail &= ~ATH9K_MODE_SEL_11NA_HT40MINUS; | |
172 | ||
173 | return modesAvail; | |
174 | } | |
175 | ||
176 | bool ath9k_regd_is_public_safety_sku(struct ath_hal *ah) | |
177 | { | |
178 | u16 rd; | |
179 | ||
180 | rd = ath9k_regd_get_eepromRD(ah); | |
181 | ||
182 | switch (rd) { | |
183 | case FCC4_FCCA: | |
184 | case (CTRY_UNITED_STATES_FCC49 | COUNTRY_ERD_FLAG): | |
185 | return true; | |
186 | case DEBUG_REG_DMN: | |
187 | case NO_ENUMRD: | |
188 | if (ah->ah_countryCode == CTRY_UNITED_STATES_FCC49) | |
189 | return true; | |
190 | break; | |
191 | } | |
192 | return false; | |
193 | } | |
194 | ||
195 | static struct country_code_to_enum_rd* | |
196 | ath9k_regd_find_country(u16 countryCode) | |
197 | { | |
198 | int i; | |
199 | ||
200 | for (i = 0; i < ARRAY_SIZE(allCountries); i++) { | |
201 | if (allCountries[i].countryCode == countryCode) | |
202 | return &allCountries[i]; | |
203 | } | |
204 | return NULL; | |
205 | } | |
206 | ||
207 | static u16 ath9k_regd_get_default_country(struct ath_hal *ah) | |
208 | { | |
209 | u16 rd; | |
210 | int i; | |
211 | ||
212 | rd = ath9k_regd_get_eepromRD(ah); | |
213 | if (rd & COUNTRY_ERD_FLAG) { | |
214 | struct country_code_to_enum_rd *country = NULL; | |
215 | u16 cc = rd & ~COUNTRY_ERD_FLAG; | |
216 | ||
217 | country = ath9k_regd_find_country(cc); | |
218 | if (country != NULL) | |
219 | return cc; | |
220 | } | |
221 | ||
222 | for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) | |
223 | if (regDomainPairs[i].regDmnEnum == rd) { | |
224 | if (regDomainPairs[i].singleCC != 0) | |
225 | return regDomainPairs[i].singleCC; | |
226 | else | |
227 | i = ARRAY_SIZE(regDomainPairs); | |
228 | } | |
229 | return CTRY_DEFAULT; | |
230 | } | |
231 | ||
232 | static bool ath9k_regd_is_valid_reg_domain(int regDmn, | |
233 | struct regDomain *rd) | |
234 | { | |
235 | int i; | |
236 | ||
237 | for (i = 0; i < ARRAY_SIZE(regDomains); i++) { | |
238 | if (regDomains[i].regDmnEnum == regDmn) { | |
239 | if (rd != NULL) { | |
240 | memcpy(rd, ®Domains[i], | |
241 | sizeof(struct regDomain)); | |
242 | } | |
243 | return true; | |
244 | } | |
245 | } | |
246 | return false; | |
247 | } | |
248 | ||
249 | static bool ath9k_regd_is_valid_reg_domainPair(int regDmnPair) | |
250 | { | |
251 | int i; | |
252 | ||
253 | if (regDmnPair == NO_ENUMRD) | |
254 | return false; | |
255 | for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { | |
256 | if (regDomainPairs[i].regDmnEnum == regDmnPair) | |
257 | return true; | |
258 | } | |
259 | return false; | |
260 | } | |
261 | ||
262 | static bool | |
263 | ath9k_regd_get_wmode_regdomain(struct ath_hal *ah, int regDmn, | |
264 | u16 channelFlag, struct regDomain *rd) | |
265 | { | |
266 | int i, found; | |
267 | u64 flags = NO_REQ; | |
268 | struct reg_dmn_pair_mapping *regPair = NULL; | |
269 | int regOrg; | |
270 | ||
271 | regOrg = regDmn; | |
272 | if (regDmn == CTRY_DEFAULT) { | |
273 | u16 rdnum; | |
274 | rdnum = ath9k_regd_get_eepromRD(ah); | |
275 | ||
276 | if (!(rdnum & COUNTRY_ERD_FLAG)) { | |
277 | if (ath9k_regd_is_valid_reg_domain(rdnum, NULL) || | |
278 | ath9k_regd_is_valid_reg_domainPair(rdnum)) { | |
279 | regDmn = rdnum; | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
284 | if ((regDmn & MULTI_DOMAIN_MASK) == 0) { | |
285 | for (i = 0, found = 0; | |
286 | (i < ARRAY_SIZE(regDomainPairs)) && (!found); i++) { | |
287 | if (regDomainPairs[i].regDmnEnum == regDmn) { | |
288 | regPair = ®DomainPairs[i]; | |
289 | found = 1; | |
290 | } | |
291 | } | |
292 | if (!found) { | |
293 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
294 | "%s: Failed to find reg domain pair %u\n", | |
295 | __func__, regDmn); | |
296 | return false; | |
297 | } | |
298 | if (!(channelFlag & CHANNEL_2GHZ)) { | |
299 | regDmn = regPair->regDmn5GHz; | |
300 | flags = regPair->flags5GHz; | |
301 | } | |
302 | if (channelFlag & CHANNEL_2GHZ) { | |
303 | regDmn = regPair->regDmn2GHz; | |
304 | flags = regPair->flags2GHz; | |
305 | } | |
306 | } | |
307 | ||
308 | found = ath9k_regd_is_valid_reg_domain(regDmn, rd); | |
309 | if (!found) { | |
310 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
311 | "%s: Failed to find unitary reg domain %u\n", | |
312 | __func__, regDmn); | |
313 | return false; | |
314 | } else { | |
315 | rd->pscan &= regPair->pscanMask; | |
316 | if (((regOrg & MULTI_DOMAIN_MASK) == 0) && | |
317 | (flags != NO_REQ)) { | |
318 | rd->flags = flags; | |
319 | } | |
320 | ||
321 | rd->flags &= (channelFlag & CHANNEL_2GHZ) ? | |
322 | REG_DOMAIN_2GHZ_MASK : REG_DOMAIN_5GHZ_MASK; | |
323 | return true; | |
324 | } | |
325 | } | |
326 | ||
327 | static bool ath9k_regd_is_bit_set(int bit, u64 *bitmask) | |
328 | { | |
329 | int byteOffset, bitnum; | |
330 | u64 val; | |
331 | ||
332 | byteOffset = bit / 64; | |
333 | bitnum = bit - byteOffset * 64; | |
334 | val = ((u64) 1) << bitnum; | |
335 | if (bitmask[byteOffset] & val) | |
336 | return true; | |
337 | else | |
338 | return false; | |
339 | } | |
340 | ||
341 | static void | |
342 | ath9k_regd_add_reg_classid(u8 *regclassids, u32 maxregids, | |
343 | u32 *nregids, u8 regclassid) | |
344 | { | |
345 | int i; | |
346 | ||
347 | if (regclassid == 0) | |
348 | return; | |
349 | ||
350 | for (i = 0; i < maxregids; i++) { | |
351 | if (regclassids[i] == regclassid) | |
352 | return; | |
353 | if (regclassids[i] == 0) | |
354 | break; | |
355 | } | |
356 | ||
357 | if (i == maxregids) | |
358 | return; | |
359 | else { | |
360 | regclassids[i] = regclassid; | |
361 | *nregids += 1; | |
362 | } | |
363 | ||
364 | return; | |
365 | } | |
366 | ||
367 | static bool | |
368 | ath9k_regd_get_eeprom_reg_ext_bits(struct ath_hal *ah, | |
369 | enum reg_ext_bitmap bit) | |
370 | { | |
371 | return (ah->ah_currentRDExt & (1 << bit)) ? true : false; | |
372 | } | |
373 | ||
374 | #ifdef ATH_NF_PER_CHAN | |
375 | ||
376 | static void ath9k_regd_init_rf_buffer(struct ath9k_channel *ichans, | |
377 | int nchans) | |
378 | { | |
379 | int i, j, next; | |
380 | ||
381 | for (next = 0; next < nchans; next++) { | |
382 | for (i = 0; i < NUM_NF_READINGS; i++) { | |
383 | ichans[next].nfCalHist[i].currIndex = 0; | |
384 | ichans[next].nfCalHist[i].privNF = | |
385 | AR_PHY_CCA_MAX_GOOD_VALUE; | |
386 | ichans[next].nfCalHist[i].invalidNFcount = | |
387 | AR_PHY_CCA_FILTERWINDOW_LENGTH; | |
388 | for (j = 0; j < ATH9K_NF_CAL_HIST_MAX; j++) { | |
389 | ichans[next].nfCalHist[i].nfCalBuffer[j] = | |
390 | AR_PHY_CCA_MAX_GOOD_VALUE; | |
391 | } | |
392 | } | |
393 | } | |
394 | } | |
395 | #endif | |
396 | ||
397 | static int ath9k_regd_is_chan_present(struct ath_hal *ah, | |
398 | u16 c) | |
399 | { | |
400 | int i; | |
401 | ||
402 | for (i = 0; i < 150; i++) { | |
403 | if (!ah->ah_channels[i].channel) | |
404 | return -1; | |
405 | else if (ah->ah_channels[i].channel == c) | |
406 | return i; | |
407 | } | |
408 | ||
409 | return -1; | |
410 | } | |
411 | ||
412 | static bool | |
413 | ath9k_regd_add_channel(struct ath_hal *ah, | |
414 | u16 c, | |
415 | u16 c_lo, | |
416 | u16 c_hi, | |
417 | u16 maxChan, | |
418 | u8 ctl, | |
419 | int pos, | |
420 | struct regDomain rd5GHz, | |
421 | struct RegDmnFreqBand *fband, | |
422 | struct regDomain *rd, | |
423 | const struct cmode *cm, | |
424 | struct ath9k_channel *ichans, | |
425 | bool enableExtendedChannels) | |
426 | { | |
427 | struct ath9k_channel *chan; | |
428 | int ret; | |
429 | u32 channelFlags = 0; | |
430 | u8 privFlags = 0; | |
431 | ||
432 | if (!(c_lo <= c && c <= c_hi)) { | |
433 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
434 | "%s: c %u out of range [%u..%u]\n", | |
435 | __func__, c, c_lo, c_hi); | |
436 | return false; | |
437 | } | |
438 | if ((fband->channelBW == CHANNEL_HALF_BW) && | |
439 | !ah->ah_caps.halChanHalfRate) { | |
440 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
441 | "%s: Skipping %u half rate channel\n", | |
442 | __func__, c); | |
443 | return false; | |
444 | } | |
445 | ||
446 | if ((fband->channelBW == CHANNEL_QUARTER_BW) && | |
447 | !ah->ah_caps.halChanQuarterRate) { | |
448 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
449 | "%s: Skipping %u quarter rate channel\n", | |
450 | __func__, c); | |
451 | return false; | |
452 | } | |
453 | ||
454 | if (((c + fband->channelSep) / 2) > (maxChan + HALF_MAXCHANBW)) { | |
455 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
456 | "%s: c %u > maxChan %u\n", | |
457 | __func__, c, maxChan); | |
458 | return false; | |
459 | } | |
460 | ||
461 | if ((fband->usePassScan & IS_ECM_CHAN) && !enableExtendedChannels) { | |
462 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
463 | "Skipping ecm channel\n"); | |
464 | return false; | |
465 | } | |
466 | ||
467 | if ((rd->flags & NO_HOSTAP) && (ah->ah_opmode == ATH9K_M_HOSTAP)) { | |
468 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
469 | "Skipping HOSTAP channel\n"); | |
470 | return false; | |
471 | } | |
472 | ||
473 | if (IS_HT40_MODE(cm->mode) && | |
474 | !(ath9k_regd_get_eeprom_reg_ext_bits(ah, REG_EXT_FCC_DFS_HT40)) && | |
475 | (fband->useDfs) && | |
476 | (rd->conformanceTestLimit != MKK)) { | |
477 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
478 | "Skipping HT40 channel (en_fcc_dfs_ht40 = 0)\n"); | |
479 | return false; | |
480 | } | |
481 | ||
482 | if (IS_HT40_MODE(cm->mode) && | |
483 | !(ath9k_regd_get_eeprom_reg_ext_bits(ah, | |
484 | REG_EXT_JAPAN_NONDFS_HT40)) && | |
485 | !(fband->useDfs) && (rd->conformanceTestLimit == MKK)) { | |
486 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
487 | "Skipping HT40 channel (en_jap_ht40 = 0)\n"); | |
488 | return false; | |
489 | } | |
490 | ||
491 | if (IS_HT40_MODE(cm->mode) && | |
492 | !(ath9k_regd_get_eeprom_reg_ext_bits(ah, REG_EXT_JAPAN_DFS_HT40)) && | |
493 | (fband->useDfs) && | |
494 | (rd->conformanceTestLimit == MKK)) { | |
495 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
496 | "Skipping HT40 channel (en_jap_dfs_ht40 = 0)\n"); | |
497 | return false; | |
498 | } | |
499 | ||
500 | /* Calculate channel flags */ | |
501 | ||
502 | channelFlags = cm->flags; | |
503 | ||
504 | switch (fband->channelBW) { | |
505 | case CHANNEL_HALF_BW: | |
506 | channelFlags |= CHANNEL_HALF; | |
507 | break; | |
508 | case CHANNEL_QUARTER_BW: | |
509 | channelFlags |= CHANNEL_QUARTER; | |
510 | break; | |
511 | } | |
512 | ||
513 | if (fband->usePassScan & rd->pscan) | |
514 | channelFlags |= CHANNEL_PASSIVE; | |
515 | else | |
516 | channelFlags &= ~CHANNEL_PASSIVE; | |
517 | if (fband->useDfs & rd->dfsMask) | |
518 | privFlags = CHANNEL_DFS; | |
519 | else | |
520 | privFlags = 0; | |
521 | if (rd->flags & LIMIT_FRAME_4MS) | |
522 | privFlags |= CHANNEL_4MS_LIMIT; | |
523 | if (privFlags & CHANNEL_DFS) | |
524 | privFlags |= CHANNEL_DISALLOW_ADHOC; | |
525 | if (rd->flags & ADHOC_PER_11D) | |
526 | privFlags |= CHANNEL_PER_11D_ADHOC; | |
527 | ||
528 | if (channelFlags & CHANNEL_PASSIVE) { | |
529 | if ((c < 2412) || (c > 2462)) { | |
530 | if (rd5GHz.regDmnEnum == MKK1 || | |
531 | rd5GHz.regDmnEnum == MKK2) { | |
532 | u32 regcap = ah->ah_caps.halRegCap; | |
533 | if (!(regcap & | |
534 | (AR_EEPROM_EEREGCAP_EN_KK_U1_EVEN | | |
535 | AR_EEPROM_EEREGCAP_EN_KK_U2 | | |
536 | AR_EEPROM_EEREGCAP_EN_KK_MIDBAND)) && | |
537 | isUNII1OddChan(c)) { | |
538 | channelFlags &= ~CHANNEL_PASSIVE; | |
539 | } else { | |
540 | privFlags |= CHANNEL_DISALLOW_ADHOC; | |
541 | } | |
542 | } else { | |
543 | privFlags |= CHANNEL_DISALLOW_ADHOC; | |
544 | } | |
545 | } | |
546 | } | |
547 | ||
548 | if (cm->mode & (ATH9K_MODE_SEL_11A | | |
549 | ATH9K_MODE_SEL_11NA_HT20 | | |
550 | ATH9K_MODE_SEL_11NA_HT40PLUS | | |
551 | ATH9K_MODE_SEL_11NA_HT40MINUS)) { | |
552 | if (rd->flags & (ADHOC_NO_11A | DISALLOW_ADHOC_11A)) | |
553 | privFlags |= CHANNEL_DISALLOW_ADHOC; | |
554 | } | |
555 | ||
556 | /* Fill in channel details */ | |
557 | ||
558 | ret = ath9k_regd_is_chan_present(ah, c); | |
559 | if (ret == -1) { | |
560 | chan = &ah->ah_channels[pos]; | |
561 | chan->channel = c; | |
562 | chan->maxRegTxPower = fband->powerDfs; | |
563 | chan->antennaMax = fband->antennaMax; | |
564 | chan->regDmnFlags = rd->flags; | |
565 | chan->maxTxPower = AR5416_MAX_RATE_POWER; | |
566 | chan->minTxPower = AR5416_MAX_RATE_POWER; | |
567 | chan->channelFlags = channelFlags; | |
568 | chan->privFlags = privFlags; | |
569 | } else { | |
570 | chan = &ah->ah_channels[ret]; | |
571 | chan->channelFlags |= channelFlags; | |
572 | chan->privFlags |= privFlags; | |
573 | } | |
574 | ||
575 | /* Set CTLs */ | |
576 | ||
577 | if ((cm->flags & CHANNEL_ALL) == CHANNEL_A) | |
578 | chan->conformanceTestLimit[0] = ctl; | |
579 | else if ((cm->flags & CHANNEL_ALL) == CHANNEL_B) | |
580 | chan->conformanceTestLimit[1] = ctl; | |
581 | else if ((cm->flags & CHANNEL_ALL) == CHANNEL_G) | |
582 | chan->conformanceTestLimit[2] = ctl; | |
583 | ||
584 | return (ret == -1) ? true : false; | |
585 | } | |
586 | ||
587 | static bool ath9k_regd_japan_check(struct ath_hal *ah, | |
588 | int b, | |
589 | struct regDomain *rd5GHz) | |
590 | { | |
591 | bool skipband = false; | |
592 | int i; | |
593 | u32 regcap; | |
594 | ||
595 | for (i = 0; i < ARRAY_SIZE(j_bandcheck); i++) { | |
596 | if (j_bandcheck[i].freqbandbit == b) { | |
597 | regcap = ah->ah_caps.halRegCap; | |
598 | if ((j_bandcheck[i].eepromflagtocheck & regcap) == 0) { | |
599 | skipband = true; | |
600 | } else if ((regcap & AR_EEPROM_EEREGCAP_EN_KK_U2) || | |
601 | (regcap & AR_EEPROM_EEREGCAP_EN_KK_MIDBAND)) { | |
602 | rd5GHz->dfsMask |= DFS_MKK4; | |
603 | rd5GHz->pscan |= PSCAN_MKK3; | |
604 | } | |
605 | break; | |
606 | } | |
607 | } | |
608 | ||
609 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
610 | "%s: Skipping %d freq band\n", | |
611 | __func__, j_bandcheck[i].freqbandbit); | |
612 | ||
613 | return skipband; | |
614 | } | |
615 | ||
616 | bool | |
617 | ath9k_regd_init_channels(struct ath_hal *ah, | |
618 | u32 maxchans, | |
619 | u32 *nchans, u8 *regclassids, | |
620 | u32 maxregids, u32 *nregids, u16 cc, | |
621 | u32 modeSelect, bool enableOutdoor, | |
622 | bool enableExtendedChannels) | |
623 | { | |
624 | u32 modesAvail; | |
625 | u16 maxChan = 7000; | |
626 | struct country_code_to_enum_rd *country = NULL; | |
627 | struct regDomain rd5GHz, rd2GHz; | |
628 | const struct cmode *cm; | |
629 | struct ath9k_channel *ichans = &ah->ah_channels[0]; | |
630 | int next = 0, b; | |
631 | u8 ctl; | |
632 | int regdmn; | |
633 | u16 chanSep; | |
634 | ||
635 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: cc %u mode 0x%x%s%s\n", | |
636 | __func__, cc, modeSelect, | |
637 | enableOutdoor ? " Enable outdoor" : " ", | |
638 | enableExtendedChannels ? " Enable ecm" : ""); | |
639 | ||
640 | if (!ath9k_regd_is_ccode_valid(ah, cc)) { | |
641 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
642 | "%s: invalid country code %d\n", __func__, cc); | |
643 | return false; | |
644 | } | |
645 | ||
646 | if (!ath9k_regd_is_eeprom_valid(ah)) { | |
647 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
648 | "%s: invalid EEPROM contents\n", __func__); | |
649 | return false; | |
650 | } | |
651 | ||
652 | ah->ah_countryCode = ath9k_regd_get_default_country(ah); | |
653 | ||
654 | if (ah->ah_countryCode == CTRY_DEFAULT) { | |
655 | ah->ah_countryCode = cc & COUNTRY_CODE_MASK; | |
656 | if ((ah->ah_countryCode == CTRY_DEFAULT) && | |
657 | (ath9k_regd_get_eepromRD(ah) == CTRY_DEFAULT)) { | |
658 | ah->ah_countryCode = CTRY_UNITED_STATES; | |
659 | } | |
660 | } | |
661 | ||
662 | #ifdef AH_SUPPORT_11D | |
663 | if (ah->ah_countryCode == CTRY_DEFAULT) { | |
664 | regdmn = ath9k_regd_get_eepromRD(ah); | |
665 | country = NULL; | |
666 | } else { | |
667 | #endif | |
668 | country = ath9k_regd_find_country(ah->ah_countryCode); | |
669 | if (country == NULL) { | |
670 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
671 | "Country is NULL!!!!, cc= %d\n", | |
672 | ah->ah_countryCode); | |
673 | return false; | |
674 | } else { | |
675 | regdmn = country->regDmnEnum; | |
676 | #ifdef AH_SUPPORT_11D | |
677 | if (((ath9k_regd_get_eepromRD(ah) & | |
678 | WORLD_SKU_MASK) == WORLD_SKU_PREFIX) && | |
679 | (cc == CTRY_UNITED_STATES)) { | |
680 | if (!isWwrSKU_NoMidband(ah) | |
681 | && ath9k_regd_is_fcc_midband_supported(ah)) | |
682 | regdmn = FCC3_FCCA; | |
683 | else | |
684 | regdmn = FCC1_FCCA; | |
685 | } | |
686 | #endif | |
687 | } | |
688 | #ifdef AH_SUPPORT_11D | |
689 | } | |
690 | #endif | |
691 | if (!ath9k_regd_get_wmode_regdomain(ah, | |
692 | regdmn, | |
693 | ~CHANNEL_2GHZ, | |
694 | &rd5GHz)) { | |
695 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
696 | "%s: couldn't find unitary " | |
697 | "5GHz reg domain for country %u\n", | |
698 | __func__, ah->ah_countryCode); | |
699 | return false; | |
700 | } | |
701 | if (!ath9k_regd_get_wmode_regdomain(ah, | |
702 | regdmn, | |
703 | CHANNEL_2GHZ, | |
704 | &rd2GHz)) { | |
705 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
706 | "%s: couldn't find unitary 2GHz " | |
707 | "reg domain for country %u\n", | |
708 | __func__, ah->ah_countryCode); | |
709 | return false; | |
710 | } | |
711 | ||
712 | if (!isWwrSKU(ah) && ((rd5GHz.regDmnEnum == FCC1) || | |
713 | (rd5GHz.regDmnEnum == FCC2))) { | |
714 | if (ath9k_regd_is_fcc_midband_supported(ah)) { | |
715 | if (!ath9k_regd_get_wmode_regdomain(ah, | |
716 | FCC3_FCCA, | |
717 | ~CHANNEL_2GHZ, | |
718 | &rd5GHz)) { | |
719 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
720 | "%s: couldn't find unitary 5GHz " | |
721 | "reg domain for country %u\n", | |
722 | __func__, ah->ah_countryCode); | |
723 | return false; | |
724 | } | |
725 | } | |
726 | } | |
727 | ||
728 | if (country == NULL) { | |
729 | modesAvail = ah->ah_caps.halWirelessModes; | |
730 | } else { | |
731 | modesAvail = ath9k_regd_get_wmodes_nreg(ah, country, &rd5GHz); | |
732 | if (!enableOutdoor) | |
733 | maxChan = country->outdoorChanStart; | |
734 | } | |
735 | ||
736 | next = 0; | |
737 | ||
738 | if (maxchans > ARRAY_SIZE(ah->ah_channels)) | |
739 | maxchans = ARRAY_SIZE(ah->ah_channels); | |
740 | ||
741 | for (cm = modes; cm < &modes[ARRAY_SIZE(modes)]; cm++) { | |
742 | u16 c, c_hi, c_lo; | |
743 | u64 *channelBM = NULL; | |
744 | struct regDomain *rd = NULL; | |
745 | struct RegDmnFreqBand *fband = NULL, *freqs; | |
746 | int8_t low_adj = 0, hi_adj = 0; | |
747 | ||
748 | if ((cm->mode & modeSelect) == 0) { | |
749 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
750 | "%s: skip mode 0x%x flags 0x%x\n", | |
751 | __func__, cm->mode, cm->flags); | |
752 | continue; | |
753 | } | |
754 | if ((cm->mode & modesAvail) == 0) { | |
755 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
756 | "%s: !avail mode 0x%x (0x%x) flags 0x%x\n", | |
757 | __func__, modesAvail, cm->mode, | |
758 | cm->flags); | |
759 | continue; | |
760 | } | |
761 | if (!ath9k_get_channel_edges(ah, cm->flags, &c_lo, &c_hi)) { | |
762 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
763 | "%s: channels 0x%x not supported " | |
764 | "by hardware\n", | |
765 | __func__, cm->flags); | |
766 | continue; | |
767 | } | |
768 | ||
769 | switch (cm->mode) { | |
770 | case ATH9K_MODE_SEL_11A: | |
771 | case ATH9K_MODE_SEL_11NA_HT20: | |
772 | case ATH9K_MODE_SEL_11NA_HT40PLUS: | |
773 | case ATH9K_MODE_SEL_11NA_HT40MINUS: | |
774 | rd = &rd5GHz; | |
775 | channelBM = rd->chan11a; | |
776 | freqs = ®Dmn5GhzFreq[0]; | |
777 | ctl = rd->conformanceTestLimit; | |
778 | break; | |
779 | case ATH9K_MODE_SEL_11B: | |
780 | rd = &rd2GHz; | |
781 | channelBM = rd->chan11b; | |
782 | freqs = ®Dmn2GhzFreq[0]; | |
783 | ctl = rd->conformanceTestLimit | CTL_11B; | |
784 | break; | |
785 | case ATH9K_MODE_SEL_11G: | |
786 | case ATH9K_MODE_SEL_11NG_HT20: | |
787 | case ATH9K_MODE_SEL_11NG_HT40PLUS: | |
788 | case ATH9K_MODE_SEL_11NG_HT40MINUS: | |
789 | rd = &rd2GHz; | |
790 | channelBM = rd->chan11g; | |
791 | freqs = ®Dmn2Ghz11gFreq[0]; | |
792 | ctl = rd->conformanceTestLimit | CTL_11G; | |
793 | break; | |
794 | default: | |
795 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
796 | "%s: Unknown HAL mode 0x%x\n", __func__, | |
797 | cm->mode); | |
798 | continue; | |
799 | } | |
800 | ||
801 | if (ath9k_regd_is_chan_bm_zero(channelBM)) | |
802 | continue; | |
803 | ||
804 | if ((cm->mode == ATH9K_MODE_SEL_11NA_HT40PLUS) || | |
805 | (cm->mode == ATH9K_MODE_SEL_11NG_HT40PLUS)) { | |
806 | hi_adj = -20; | |
807 | } | |
808 | ||
809 | if ((cm->mode == ATH9K_MODE_SEL_11NA_HT40MINUS) || | |
810 | (cm->mode == ATH9K_MODE_SEL_11NG_HT40MINUS)) { | |
811 | low_adj = 20; | |
812 | } | |
813 | ||
814 | /* XXX: Add a helper here instead */ | |
815 | for (b = 0; b < 64 * BMLEN; b++) { | |
816 | if (ath9k_regd_is_bit_set(b, channelBM)) { | |
817 | fband = &freqs[b]; | |
818 | if (rd5GHz.regDmnEnum == MKK1 | |
819 | || rd5GHz.regDmnEnum == MKK2) { | |
820 | if (ath9k_regd_japan_check(ah, | |
821 | b, | |
822 | &rd5GHz)) | |
823 | continue; | |
824 | } | |
825 | ||
826 | ath9k_regd_add_reg_classid(regclassids, | |
827 | maxregids, | |
828 | nregids, | |
829 | fband-> | |
830 | regClassId); | |
831 | ||
832 | if (IS_HT40_MODE(cm->mode) && (rd == &rd5GHz)) { | |
833 | chanSep = 40; | |
834 | if (fband->lowChannel == 5280) | |
835 | low_adj += 20; | |
836 | ||
837 | if (fband->lowChannel == 5170) | |
838 | continue; | |
839 | } else | |
840 | chanSep = fband->channelSep; | |
841 | ||
842 | for (c = fband->lowChannel + low_adj; | |
843 | ((c <= (fband->highChannel + hi_adj)) && | |
844 | (c >= (fband->lowChannel + low_adj))); | |
845 | c += chanSep) { | |
846 | if (next >= maxchans) { | |
847 | DPRINTF(ah->ah_sc, | |
848 | ATH_DBG_REGULATORY, | |
849 | "%s: too many channels " | |
850 | "for channel table\n", | |
851 | __func__); | |
852 | goto done; | |
853 | } | |
854 | if (ath9k_regd_add_channel(ah, | |
855 | c, c_lo, c_hi, | |
856 | maxChan, ctl, | |
857 | next, | |
858 | rd5GHz, | |
859 | fband, rd, cm, | |
860 | ichans, | |
861 | enableExtendedChannels)) | |
862 | next++; | |
863 | } | |
864 | if (IS_HT40_MODE(cm->mode) && | |
865 | (fband->lowChannel == 5280)) { | |
866 | low_adj -= 20; | |
867 | } | |
868 | } | |
869 | } | |
870 | } | |
871 | done: | |
872 | if (next != 0) { | |
873 | int i; | |
874 | ||
875 | if (next > ARRAY_SIZE(ah->ah_channels)) { | |
876 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
877 | "%s: too many channels %u; truncating to %u\n", | |
878 | __func__, next, | |
879 | (int) ARRAY_SIZE(ah->ah_channels)); | |
880 | next = ARRAY_SIZE(ah->ah_channels); | |
881 | } | |
882 | #ifdef ATH_NF_PER_CHAN | |
883 | ath9k_regd_init_rf_buffer(ichans, next); | |
884 | #endif | |
885 | ath9k_regd_sort(ichans, next, | |
886 | sizeof(struct ath9k_channel), | |
887 | ath9k_regd_chansort); | |
888 | ||
889 | ah->ah_nchan = next; | |
890 | ||
891 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Channel list:\n"); | |
892 | for (i = 0; i < next; i++) { | |
893 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
894 | "chan: %d flags: 0x%x\n", | |
895 | ah->ah_channels[i].channel, | |
896 | ah->ah_channels[i].channelFlags); | |
897 | } | |
898 | } | |
899 | *nchans = next; | |
900 | ||
901 | ah->ah_countryCode = ah->ah_countryCode; | |
902 | ||
903 | ah->ah_currentRDInUse = regdmn; | |
904 | ah->ah_currentRD5G = rd5GHz.regDmnEnum; | |
905 | ah->ah_currentRD2G = rd2GHz.regDmnEnum; | |
906 | if (country == NULL) { | |
907 | ah->ah_iso[0] = 0; | |
908 | ah->ah_iso[1] = 0; | |
909 | } else { | |
910 | ah->ah_iso[0] = country->isoName[0]; | |
911 | ah->ah_iso[1] = country->isoName[1]; | |
912 | } | |
913 | ||
914 | return next != 0; | |
915 | } | |
916 | ||
917 | struct ath9k_channel* | |
918 | ath9k_regd_check_channel(struct ath_hal *ah, | |
919 | const struct ath9k_channel *c) | |
920 | { | |
921 | struct ath9k_channel *base, *cc; | |
922 | ||
923 | int flags = c->channelFlags & CHAN_FLAGS; | |
924 | int n, lim; | |
925 | ||
926 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
927 | "%s: channel %u/0x%x (0x%x) requested\n", __func__, | |
928 | c->channel, c->channelFlags, flags); | |
929 | ||
930 | cc = ah->ah_curchan; | |
931 | if (cc != NULL && cc->channel == c->channel && | |
932 | (cc->channelFlags & CHAN_FLAGS) == flags) { | |
933 | if ((cc->privFlags & CHANNEL_INTERFERENCE) && | |
934 | (cc->privFlags & CHANNEL_DFS)) | |
935 | return NULL; | |
936 | else | |
937 | return cc; | |
938 | } | |
939 | ||
940 | base = ah->ah_channels; | |
941 | n = ah->ah_nchan; | |
942 | ||
943 | for (lim = n; lim != 0; lim >>= 1) { | |
944 | int d; | |
945 | cc = &base[lim >> 1]; | |
946 | d = c->channel - cc->channel; | |
947 | if (d == 0) { | |
948 | if ((cc->channelFlags & CHAN_FLAGS) == flags) { | |
949 | if ((cc->privFlags & CHANNEL_INTERFERENCE) && | |
950 | (cc->privFlags & CHANNEL_DFS)) | |
951 | return NULL; | |
952 | else | |
953 | return cc; | |
954 | } | |
955 | d = flags - (cc->channelFlags & CHAN_FLAGS); | |
956 | } | |
957 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, | |
958 | "%s: channel %u/0x%x d %d\n", __func__, | |
959 | cc->channel, cc->channelFlags, d); | |
960 | if (d > 0) { | |
961 | base = cc + 1; | |
962 | lim--; | |
963 | } | |
964 | } | |
965 | DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: no match for %u/0x%x\n", | |
966 | __func__, c->channel, c->channelFlags); | |
967 | return NULL; | |
968 | } | |
969 | ||
970 | u32 | |
971 | ath9k_regd_get_antenna_allowed(struct ath_hal *ah, | |
972 | struct ath9k_channel *chan) | |
973 | { | |
974 | struct ath9k_channel *ichan = NULL; | |
975 | ||
976 | ichan = ath9k_regd_check_channel(ah, chan); | |
977 | if (!ichan) | |
978 | return 0; | |
979 | ||
980 | return ichan->antennaMax; | |
981 | } | |
982 | ||
983 | u32 ath9k_regd_get_ctl(struct ath_hal *ah, struct ath9k_channel *chan) | |
984 | { | |
985 | u32 ctl = NO_CTL; | |
986 | struct ath9k_channel *ichan; | |
987 | ||
988 | if (ah->ah_countryCode == CTRY_DEFAULT && isWwrSKU(ah)) { | |
989 | if (IS_CHAN_B(chan)) | |
990 | ctl = SD_NO_CTL | CTL_11B; | |
991 | else if (IS_CHAN_G(chan)) | |
992 | ctl = SD_NO_CTL | CTL_11G; | |
993 | else | |
994 | ctl = SD_NO_CTL | CTL_11A; | |
995 | } else { | |
996 | ichan = ath9k_regd_check_channel(ah, chan); | |
997 | if (ichan != NULL) { | |
998 | /* FIXME */ | |
999 | if (IS_CHAN_A(ichan)) | |
1000 | ctl = ichan->conformanceTestLimit[0]; | |
1001 | else if (IS_CHAN_B(ichan)) | |
1002 | ctl = ichan->conformanceTestLimit[1]; | |
1003 | else if (IS_CHAN_G(ichan)) | |
1004 | ctl = ichan->conformanceTestLimit[2]; | |
1005 | ||
1006 | if (IS_CHAN_G(chan) && (ctl & 0xf) == CTL_11B) | |
1007 | ctl = (ctl & ~0xf) | CTL_11G; | |
1008 | } | |
1009 | } | |
1010 | return ctl; | |
1011 | } | |
1012 | ||
1013 | void ath9k_regd_get_current_country(struct ath_hal *ah, | |
1014 | struct ath9k_country_entry *ctry) | |
1015 | { | |
1016 | u16 rd = ath9k_regd_get_eepromRD(ah); | |
1017 | ||
1018 | ctry->isMultidomain = false; | |
1019 | if (rd == CTRY_DEFAULT) | |
1020 | ctry->isMultidomain = true; | |
1021 | else if (!(rd & COUNTRY_ERD_FLAG)) | |
1022 | ctry->isMultidomain = isWwrSKU(ah); | |
1023 | ||
1024 | ctry->countryCode = ah->ah_countryCode; | |
1025 | ctry->regDmnEnum = ah->ah_currentRD; | |
1026 | ctry->regDmn5G = ah->ah_currentRD5G; | |
1027 | ctry->regDmn2G = ah->ah_currentRD2G; | |
1028 | ctry->iso[0] = ah->ah_iso[0]; | |
1029 | ctry->iso[1] = ah->ah_iso[1]; | |
1030 | ctry->iso[2] = ah->ah_iso[2]; | |
1031 | } |