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