/* * Copyright (c) 2008 Atheros Communications Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "core.h" #include "hw.h" #include "regd.h" #include "regd_common.h" static int ath9k_regd_chansort(const void *a, const void *b) { const struct ath9k_channel *ca = a; const struct ath9k_channel *cb = b; return (ca->channel == cb->channel) ? (ca->channelFlags & CHAN_FLAGS) - (cb->channelFlags & CHAN_FLAGS) : ca->channel - cb->channel; } static void ath9k_regd_sort(void *a, u32 n, u32 size, ath_hal_cmp_t *cmp) { u8 *aa = a; u8 *ai, *t; for (ai = aa + size; --n >= 1; ai += size) for (t = ai; t > aa; t -= size) { u8 *u = t - size; if (cmp(u, t) <= 0) break; swap(u, t, size); } } static u16 ath9k_regd_get_eepromRD(struct ath_hal *ah) { return ah->ah_currentRD & ~WORLDWIDE_ROAMING_FLAG; } static bool ath9k_regd_is_chan_bm_zero(u64 *bitmask) { int i; for (i = 0; i < BMLEN; i++) { if (bitmask[i] != 0) return false; } return true; } static bool ath9k_regd_is_eeprom_valid(struct ath_hal *ah) { u16 rd = ath9k_regd_get_eepromRD(ah); int i; if (rd & COUNTRY_ERD_FLAG) { u16 cc = rd & ~COUNTRY_ERD_FLAG; for (i = 0; i < ARRAY_SIZE(allCountries); i++) if (allCountries[i].countryCode == cc) return true; } else { for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) if (regDomainPairs[i].regDmnEnum == rd) return true; } DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: invalid regulatory domain/country code 0x%x\n", __func__, rd); return false; } static bool ath9k_regd_is_fcc_midband_supported(struct ath_hal *ah) { u32 regcap; regcap = ah->ah_caps.halRegCap; if (regcap & AR_EEPROM_EEREGCAP_EN_FCC_MIDBAND) return true; else return false; } static bool ath9k_regd_is_ccode_valid(struct ath_hal *ah, u16 cc) { u16 rd; int i; if (cc == CTRY_DEFAULT) return true; if (cc == CTRY_DEBUG) return true; rd = ath9k_regd_get_eepromRD(ah); DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: EEPROM regdomain 0x%x\n", __func__, rd); if (rd & COUNTRY_ERD_FLAG) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: EEPROM setting is country code %u\n", __func__, rd & ~COUNTRY_ERD_FLAG); return cc == (rd & ~COUNTRY_ERD_FLAG); } for (i = 0; i < ARRAY_SIZE(allCountries); i++) { if (cc == allCountries[i].countryCode) { #ifdef AH_SUPPORT_11D if ((rd & WORLD_SKU_MASK) == WORLD_SKU_PREFIX) return true; #endif if (allCountries[i].regDmnEnum == rd || rd == DEBUG_REG_DMN || rd == NO_ENUMRD) return true; } } return false; } static u32 ath9k_regd_get_wmodes_nreg(struct ath_hal *ah, struct country_code_to_enum_rd *country, struct regDomain *rd5GHz) { u32 modesAvail; modesAvail = ah->ah_caps.halWirelessModes; if ((modesAvail & ATH9K_MODE_SEL_11G) && (!country->allow11g)) modesAvail &= ~ATH9K_MODE_SEL_11G; if ((modesAvail & ATH9K_MODE_SEL_11A) && (ath9k_regd_is_chan_bm_zero(rd5GHz->chan11a))) modesAvail &= ~ATH9K_MODE_SEL_11A; if ((modesAvail & ATH9K_MODE_SEL_11NG_HT20) && (!country->allow11ng20)) modesAvail &= ~ATH9K_MODE_SEL_11NG_HT20; if ((modesAvail & ATH9K_MODE_SEL_11NA_HT20) && (!country->allow11na20)) modesAvail &= ~ATH9K_MODE_SEL_11NA_HT20; if ((modesAvail & ATH9K_MODE_SEL_11NG_HT40PLUS) && (!country->allow11ng40)) modesAvail &= ~ATH9K_MODE_SEL_11NG_HT40PLUS; if ((modesAvail & ATH9K_MODE_SEL_11NG_HT40MINUS) && (!country->allow11ng40)) modesAvail &= ~ATH9K_MODE_SEL_11NG_HT40MINUS; if ((modesAvail & ATH9K_MODE_SEL_11NA_HT40PLUS) && (!country->allow11na40)) modesAvail &= ~ATH9K_MODE_SEL_11NA_HT40PLUS; if ((modesAvail & ATH9K_MODE_SEL_11NA_HT40MINUS) && (!country->allow11na40)) modesAvail &= ~ATH9K_MODE_SEL_11NA_HT40MINUS; return modesAvail; } bool ath9k_regd_is_public_safety_sku(struct ath_hal *ah) { u16 rd; rd = ath9k_regd_get_eepromRD(ah); switch (rd) { case FCC4_FCCA: case (CTRY_UNITED_STATES_FCC49 | COUNTRY_ERD_FLAG): return true; case DEBUG_REG_DMN: case NO_ENUMRD: if (ah->ah_countryCode == CTRY_UNITED_STATES_FCC49) return true; break; } return false; } static struct country_code_to_enum_rd* ath9k_regd_find_country(u16 countryCode) { int i; for (i = 0; i < ARRAY_SIZE(allCountries); i++) { if (allCountries[i].countryCode == countryCode) return &allCountries[i]; } return NULL; } static u16 ath9k_regd_get_default_country(struct ath_hal *ah) { u16 rd; int i; rd = ath9k_regd_get_eepromRD(ah); if (rd & COUNTRY_ERD_FLAG) { struct country_code_to_enum_rd *country = NULL; u16 cc = rd & ~COUNTRY_ERD_FLAG; country = ath9k_regd_find_country(cc); if (country != NULL) return cc; } for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) if (regDomainPairs[i].regDmnEnum == rd) { if (regDomainPairs[i].singleCC != 0) return regDomainPairs[i].singleCC; else i = ARRAY_SIZE(regDomainPairs); } return CTRY_DEFAULT; } static bool ath9k_regd_is_valid_reg_domain(int regDmn, struct regDomain *rd) { int i; for (i = 0; i < ARRAY_SIZE(regDomains); i++) { if (regDomains[i].regDmnEnum == regDmn) { if (rd != NULL) { memcpy(rd, ®Domains[i], sizeof(struct regDomain)); } return true; } } return false; } static bool ath9k_regd_is_valid_reg_domainPair(int regDmnPair) { int i; if (regDmnPair == NO_ENUMRD) return false; for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { if (regDomainPairs[i].regDmnEnum == regDmnPair) return true; } return false; } static bool ath9k_regd_get_wmode_regdomain(struct ath_hal *ah, int regDmn, u16 channelFlag, struct regDomain *rd) { int i, found; u64 flags = NO_REQ; struct reg_dmn_pair_mapping *regPair = NULL; int regOrg; regOrg = regDmn; if (regDmn == CTRY_DEFAULT) { u16 rdnum; rdnum = ath9k_regd_get_eepromRD(ah); if (!(rdnum & COUNTRY_ERD_FLAG)) { if (ath9k_regd_is_valid_reg_domain(rdnum, NULL) || ath9k_regd_is_valid_reg_domainPair(rdnum)) { regDmn = rdnum; } } } if ((regDmn & MULTI_DOMAIN_MASK) == 0) { for (i = 0, found = 0; (i < ARRAY_SIZE(regDomainPairs)) && (!found); i++) { if (regDomainPairs[i].regDmnEnum == regDmn) { regPair = ®DomainPairs[i]; found = 1; } } if (!found) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Failed to find reg domain pair %u\n", __func__, regDmn); return false; } if (!(channelFlag & CHANNEL_2GHZ)) { regDmn = regPair->regDmn5GHz; flags = regPair->flags5GHz; } if (channelFlag & CHANNEL_2GHZ) { regDmn = regPair->regDmn2GHz; flags = regPair->flags2GHz; } } found = ath9k_regd_is_valid_reg_domain(regDmn, rd); if (!found) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Failed to find unitary reg domain %u\n", __func__, regDmn); return false; } else { rd->pscan &= regPair->pscanMask; if (((regOrg & MULTI_DOMAIN_MASK) == 0) && (flags != NO_REQ)) { rd->flags = flags; } rd->flags &= (channelFlag & CHANNEL_2GHZ) ? REG_DOMAIN_2GHZ_MASK : REG_DOMAIN_5GHZ_MASK; return true; } } static bool ath9k_regd_is_bit_set(int bit, u64 *bitmask) { int byteOffset, bitnum; u64 val; byteOffset = bit / 64; bitnum = bit - byteOffset * 64; val = ((u64) 1) << bitnum; if (bitmask[byteOffset] & val) return true; else return false; } static void ath9k_regd_add_reg_classid(u8 *regclassids, u32 maxregids, u32 *nregids, u8 regclassid) { int i; if (regclassid == 0) return; for (i = 0; i < maxregids; i++) { if (regclassids[i] == regclassid) return; if (regclassids[i] == 0) break; } if (i == maxregids) return; else { regclassids[i] = regclassid; *nregids += 1; } return; } static bool ath9k_regd_get_eeprom_reg_ext_bits(struct ath_hal *ah, enum reg_ext_bitmap bit) { return (ah->ah_currentRDExt & (1 << bit)) ? true : false; } #ifdef ATH_NF_PER_CHAN static void ath9k_regd_init_rf_buffer(struct ath9k_channel *ichans, int nchans) { int i, j, next; for (next = 0; next < nchans; next++) { for (i = 0; i < NUM_NF_READINGS; i++) { ichans[next].nfCalHist[i].currIndex = 0; ichans[next].nfCalHist[i].privNF = AR_PHY_CCA_MAX_GOOD_VALUE; ichans[next].nfCalHist[i].invalidNFcount = AR_PHY_CCA_FILTERWINDOW_LENGTH; for (j = 0; j < ATH9K_NF_CAL_HIST_MAX; j++) { ichans[next].nfCalHist[i].nfCalBuffer[j] = AR_PHY_CCA_MAX_GOOD_VALUE; } } } } #endif static int ath9k_regd_is_chan_present(struct ath_hal *ah, u16 c) { int i; for (i = 0; i < 150; i++) { if (!ah->ah_channels[i].channel) return -1; else if (ah->ah_channels[i].channel == c) return i; } return -1; } static bool ath9k_regd_add_channel(struct ath_hal *ah, u16 c, u16 c_lo, u16 c_hi, u16 maxChan, u8 ctl, int pos, struct regDomain rd5GHz, struct RegDmnFreqBand *fband, struct regDomain *rd, const struct cmode *cm, struct ath9k_channel *ichans, bool enableExtendedChannels) { struct ath9k_channel *chan; int ret; u32 channelFlags = 0; u8 privFlags = 0; if (!(c_lo <= c && c <= c_hi)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: c %u out of range [%u..%u]\n", __func__, c, c_lo, c_hi); return false; } if ((fband->channelBW == CHANNEL_HALF_BW) && !ah->ah_caps.halChanHalfRate) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Skipping %u half rate channel\n", __func__, c); return false; } if ((fband->channelBW == CHANNEL_QUARTER_BW) && !ah->ah_caps.halChanQuarterRate) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Skipping %u quarter rate channel\n", __func__, c); return false; } if (((c + fband->channelSep) / 2) > (maxChan + HALF_MAXCHANBW)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: c %u > maxChan %u\n", __func__, c, maxChan); return false; } if ((fband->usePassScan & IS_ECM_CHAN) && !enableExtendedChannels) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Skipping ecm channel\n"); return false; } if ((rd->flags & NO_HOSTAP) && (ah->ah_opmode == ATH9K_M_HOSTAP)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Skipping HOSTAP channel\n"); return false; } if (IS_HT40_MODE(cm->mode) && !(ath9k_regd_get_eeprom_reg_ext_bits(ah, REG_EXT_FCC_DFS_HT40)) && (fband->useDfs) && (rd->conformanceTestLimit != MKK)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Skipping HT40 channel (en_fcc_dfs_ht40 = 0)\n"); return false; } if (IS_HT40_MODE(cm->mode) && !(ath9k_regd_get_eeprom_reg_ext_bits(ah, REG_EXT_JAPAN_NONDFS_HT40)) && !(fband->useDfs) && (rd->conformanceTestLimit == MKK)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Skipping HT40 channel (en_jap_ht40 = 0)\n"); return false; } if (IS_HT40_MODE(cm->mode) && !(ath9k_regd_get_eeprom_reg_ext_bits(ah, REG_EXT_JAPAN_DFS_HT40)) && (fband->useDfs) && (rd->conformanceTestLimit == MKK)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Skipping HT40 channel (en_jap_dfs_ht40 = 0)\n"); return false; } /* Calculate channel flags */ channelFlags = cm->flags; switch (fband->channelBW) { case CHANNEL_HALF_BW: channelFlags |= CHANNEL_HALF; break; case CHANNEL_QUARTER_BW: channelFlags |= CHANNEL_QUARTER; break; } if (fband->usePassScan & rd->pscan) channelFlags |= CHANNEL_PASSIVE; else channelFlags &= ~CHANNEL_PASSIVE; if (fband->useDfs & rd->dfsMask) privFlags = CHANNEL_DFS; else privFlags = 0; if (rd->flags & LIMIT_FRAME_4MS) privFlags |= CHANNEL_4MS_LIMIT; if (privFlags & CHANNEL_DFS) privFlags |= CHANNEL_DISALLOW_ADHOC; if (rd->flags & ADHOC_PER_11D) privFlags |= CHANNEL_PER_11D_ADHOC; if (channelFlags & CHANNEL_PASSIVE) { if ((c < 2412) || (c > 2462)) { if (rd5GHz.regDmnEnum == MKK1 || rd5GHz.regDmnEnum == MKK2) { u32 regcap = ah->ah_caps.halRegCap; if (!(regcap & (AR_EEPROM_EEREGCAP_EN_KK_U1_EVEN | AR_EEPROM_EEREGCAP_EN_KK_U2 | AR_EEPROM_EEREGCAP_EN_KK_MIDBAND)) && isUNII1OddChan(c)) { channelFlags &= ~CHANNEL_PASSIVE; } else { privFlags |= CHANNEL_DISALLOW_ADHOC; } } else { privFlags |= CHANNEL_DISALLOW_ADHOC; } } } if (cm->mode & (ATH9K_MODE_SEL_11A | ATH9K_MODE_SEL_11NA_HT20 | ATH9K_MODE_SEL_11NA_HT40PLUS | ATH9K_MODE_SEL_11NA_HT40MINUS)) { if (rd->flags & (ADHOC_NO_11A | DISALLOW_ADHOC_11A)) privFlags |= CHANNEL_DISALLOW_ADHOC; } /* Fill in channel details */ ret = ath9k_regd_is_chan_present(ah, c); if (ret == -1) { chan = &ah->ah_channels[pos]; chan->channel = c; chan->maxRegTxPower = fband->powerDfs; chan->antennaMax = fband->antennaMax; chan->regDmnFlags = rd->flags; chan->maxTxPower = AR5416_MAX_RATE_POWER; chan->minTxPower = AR5416_MAX_RATE_POWER; chan->channelFlags = channelFlags; chan->privFlags = privFlags; } else { chan = &ah->ah_channels[ret]; chan->channelFlags |= channelFlags; chan->privFlags |= privFlags; } /* Set CTLs */ if ((cm->flags & CHANNEL_ALL) == CHANNEL_A) chan->conformanceTestLimit[0] = ctl; else if ((cm->flags & CHANNEL_ALL) == CHANNEL_B) chan->conformanceTestLimit[1] = ctl; else if ((cm->flags & CHANNEL_ALL) == CHANNEL_G) chan->conformanceTestLimit[2] = ctl; return (ret == -1) ? true : false; } static bool ath9k_regd_japan_check(struct ath_hal *ah, int b, struct regDomain *rd5GHz) { bool skipband = false; int i; u32 regcap; for (i = 0; i < ARRAY_SIZE(j_bandcheck); i++) { if (j_bandcheck[i].freqbandbit == b) { regcap = ah->ah_caps.halRegCap; if ((j_bandcheck[i].eepromflagtocheck & regcap) == 0) { skipband = true; } else if ((regcap & AR_EEPROM_EEREGCAP_EN_KK_U2) || (regcap & AR_EEPROM_EEREGCAP_EN_KK_MIDBAND)) { rd5GHz->dfsMask |= DFS_MKK4; rd5GHz->pscan |= PSCAN_MKK3; } break; } } DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Skipping %d freq band\n", __func__, j_bandcheck[i].freqbandbit); return skipband; } bool ath9k_regd_init_channels(struct ath_hal *ah, u32 maxchans, u32 *nchans, u8 *regclassids, u32 maxregids, u32 *nregids, u16 cc, u32 modeSelect, bool enableOutdoor, bool enableExtendedChannels) { u32 modesAvail; u16 maxChan = 7000; struct country_code_to_enum_rd *country = NULL; struct regDomain rd5GHz, rd2GHz; const struct cmode *cm; struct ath9k_channel *ichans = &ah->ah_channels[0]; int next = 0, b; u8 ctl; int regdmn; u16 chanSep; DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: cc %u mode 0x%x%s%s\n", __func__, cc, modeSelect, enableOutdoor ? " Enable outdoor" : " ", enableExtendedChannels ? " Enable ecm" : ""); if (!ath9k_regd_is_ccode_valid(ah, cc)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: invalid country code %d\n", __func__, cc); return false; } if (!ath9k_regd_is_eeprom_valid(ah)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: invalid EEPROM contents\n", __func__); return false; } ah->ah_countryCode = ath9k_regd_get_default_country(ah); if (ah->ah_countryCode == CTRY_DEFAULT) { ah->ah_countryCode = cc & COUNTRY_CODE_MASK; if ((ah->ah_countryCode == CTRY_DEFAULT) && (ath9k_regd_get_eepromRD(ah) == CTRY_DEFAULT)) { ah->ah_countryCode = CTRY_UNITED_STATES; } } #ifdef AH_SUPPORT_11D if (ah->ah_countryCode == CTRY_DEFAULT) { regdmn = ath9k_regd_get_eepromRD(ah); country = NULL; } else { #endif country = ath9k_regd_find_country(ah->ah_countryCode); if (country == NULL) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Country is NULL!!!!, cc= %d\n", ah->ah_countryCode); return false; } else { regdmn = country->regDmnEnum; #ifdef AH_SUPPORT_11D if (((ath9k_regd_get_eepromRD(ah) & WORLD_SKU_MASK) == WORLD_SKU_PREFIX) && (cc == CTRY_UNITED_STATES)) { if (!isWwrSKU_NoMidband(ah) && ath9k_regd_is_fcc_midband_supported(ah)) regdmn = FCC3_FCCA; else regdmn = FCC1_FCCA; } #endif } #ifdef AH_SUPPORT_11D } #endif if (!ath9k_regd_get_wmode_regdomain(ah, regdmn, ~CHANNEL_2GHZ, &rd5GHz)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: couldn't find unitary " "5GHz reg domain for country %u\n", __func__, ah->ah_countryCode); return false; } if (!ath9k_regd_get_wmode_regdomain(ah, regdmn, CHANNEL_2GHZ, &rd2GHz)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: couldn't find unitary 2GHz " "reg domain for country %u\n", __func__, ah->ah_countryCode); return false; } if (!isWwrSKU(ah) && ((rd5GHz.regDmnEnum == FCC1) || (rd5GHz.regDmnEnum == FCC2))) { if (ath9k_regd_is_fcc_midband_supported(ah)) { if (!ath9k_regd_get_wmode_regdomain(ah, FCC3_FCCA, ~CHANNEL_2GHZ, &rd5GHz)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: couldn't find unitary 5GHz " "reg domain for country %u\n", __func__, ah->ah_countryCode); return false; } } } if (country == NULL) { modesAvail = ah->ah_caps.halWirelessModes; } else { modesAvail = ath9k_regd_get_wmodes_nreg(ah, country, &rd5GHz); if (!enableOutdoor) maxChan = country->outdoorChanStart; } next = 0; if (maxchans > ARRAY_SIZE(ah->ah_channels)) maxchans = ARRAY_SIZE(ah->ah_channels); for (cm = modes; cm < &modes[ARRAY_SIZE(modes)]; cm++) { u16 c, c_hi, c_lo; u64 *channelBM = NULL; struct regDomain *rd = NULL; struct RegDmnFreqBand *fband = NULL, *freqs; int8_t low_adj = 0, hi_adj = 0; if ((cm->mode & modeSelect) == 0) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: skip mode 0x%x flags 0x%x\n", __func__, cm->mode, cm->flags); continue; } if ((cm->mode & modesAvail) == 0) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: !avail mode 0x%x (0x%x) flags 0x%x\n", __func__, modesAvail, cm->mode, cm->flags); continue; } if (!ath9k_get_channel_edges(ah, cm->flags, &c_lo, &c_hi)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: channels 0x%x not supported " "by hardware\n", __func__, cm->flags); continue; } switch (cm->mode) { case ATH9K_MODE_SEL_11A: case ATH9K_MODE_SEL_11NA_HT20: case ATH9K_MODE_SEL_11NA_HT40PLUS: case ATH9K_MODE_SEL_11NA_HT40MINUS: rd = &rd5GHz; channelBM = rd->chan11a; freqs = ®Dmn5GhzFreq[0]; ctl = rd->conformanceTestLimit; break; case ATH9K_MODE_SEL_11B: rd = &rd2GHz; channelBM = rd->chan11b; freqs = ®Dmn2GhzFreq[0]; ctl = rd->conformanceTestLimit | CTL_11B; break; case ATH9K_MODE_SEL_11G: case ATH9K_MODE_SEL_11NG_HT20: case ATH9K_MODE_SEL_11NG_HT40PLUS: case ATH9K_MODE_SEL_11NG_HT40MINUS: rd = &rd2GHz; channelBM = rd->chan11g; freqs = ®Dmn2Ghz11gFreq[0]; ctl = rd->conformanceTestLimit | CTL_11G; break; default: DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: Unknown HAL mode 0x%x\n", __func__, cm->mode); continue; } if (ath9k_regd_is_chan_bm_zero(channelBM)) continue; if ((cm->mode == ATH9K_MODE_SEL_11NA_HT40PLUS) || (cm->mode == ATH9K_MODE_SEL_11NG_HT40PLUS)) { hi_adj = -20; } if ((cm->mode == ATH9K_MODE_SEL_11NA_HT40MINUS) || (cm->mode == ATH9K_MODE_SEL_11NG_HT40MINUS)) { low_adj = 20; } /* XXX: Add a helper here instead */ for (b = 0; b < 64 * BMLEN; b++) { if (ath9k_regd_is_bit_set(b, channelBM)) { fband = &freqs[b]; if (rd5GHz.regDmnEnum == MKK1 || rd5GHz.regDmnEnum == MKK2) { if (ath9k_regd_japan_check(ah, b, &rd5GHz)) continue; } ath9k_regd_add_reg_classid(regclassids, maxregids, nregids, fband-> regClassId); if (IS_HT40_MODE(cm->mode) && (rd == &rd5GHz)) { chanSep = 40; if (fband->lowChannel == 5280) low_adj += 20; if (fband->lowChannel == 5170) continue; } else chanSep = fband->channelSep; for (c = fband->lowChannel + low_adj; ((c <= (fband->highChannel + hi_adj)) && (c >= (fband->lowChannel + low_adj))); c += chanSep) { if (next >= maxchans) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: too many channels " "for channel table\n", __func__); goto done; } if (ath9k_regd_add_channel(ah, c, c_lo, c_hi, maxChan, ctl, next, rd5GHz, fband, rd, cm, ichans, enableExtendedChannels)) next++; } if (IS_HT40_MODE(cm->mode) && (fband->lowChannel == 5280)) { low_adj -= 20; } } } } done: if (next != 0) { int i; if (next > ARRAY_SIZE(ah->ah_channels)) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: too many channels %u; truncating to %u\n", __func__, next, (int) ARRAY_SIZE(ah->ah_channels)); next = ARRAY_SIZE(ah->ah_channels); } #ifdef ATH_NF_PER_CHAN ath9k_regd_init_rf_buffer(ichans, next); #endif ath9k_regd_sort(ichans, next, sizeof(struct ath9k_channel), ath9k_regd_chansort); ah->ah_nchan = next; DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "Channel list:\n"); for (i = 0; i < next; i++) { DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "chan: %d flags: 0x%x\n", ah->ah_channels[i].channel, ah->ah_channels[i].channelFlags); } } *nchans = next; ah->ah_countryCode = ah->ah_countryCode; ah->ah_currentRDInUse = regdmn; ah->ah_currentRD5G = rd5GHz.regDmnEnum; ah->ah_currentRD2G = rd2GHz.regDmnEnum; if (country == NULL) { ah->ah_iso[0] = 0; ah->ah_iso[1] = 0; } else { ah->ah_iso[0] = country->isoName[0]; ah->ah_iso[1] = country->isoName[1]; } return next != 0; } struct ath9k_channel* ath9k_regd_check_channel(struct ath_hal *ah, const struct ath9k_channel *c) { struct ath9k_channel *base, *cc; int flags = c->channelFlags & CHAN_FLAGS; int n, lim; DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: channel %u/0x%x (0x%x) requested\n", __func__, c->channel, c->channelFlags, flags); cc = ah->ah_curchan; if (cc != NULL && cc->channel == c->channel && (cc->channelFlags & CHAN_FLAGS) == flags) { if ((cc->privFlags & CHANNEL_INTERFERENCE) && (cc->privFlags & CHANNEL_DFS)) return NULL; else return cc; } base = ah->ah_channels; n = ah->ah_nchan; for (lim = n; lim != 0; lim >>= 1) { int d; cc = &base[lim >> 1]; d = c->channel - cc->channel; if (d == 0) { if ((cc->channelFlags & CHAN_FLAGS) == flags) { if ((cc->privFlags & CHANNEL_INTERFERENCE) && (cc->privFlags & CHANNEL_DFS)) return NULL; else return cc; } d = flags - (cc->channelFlags & CHAN_FLAGS); } DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: channel %u/0x%x d %d\n", __func__, cc->channel, cc->channelFlags, d); if (d > 0) { base = cc + 1; lim--; } } DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: no match for %u/0x%x\n", __func__, c->channel, c->channelFlags); return NULL; } u32 ath9k_regd_get_antenna_allowed(struct ath_hal *ah, struct ath9k_channel *chan) { struct ath9k_channel *ichan = NULL; ichan = ath9k_regd_check_channel(ah, chan); if (!ichan) return 0; return ichan->antennaMax; } u32 ath9k_regd_get_ctl(struct ath_hal *ah, struct ath9k_channel *chan) { u32 ctl = NO_CTL; struct ath9k_channel *ichan; if (ah->ah_countryCode == CTRY_DEFAULT && isWwrSKU(ah)) { if (IS_CHAN_B(chan)) ctl = SD_NO_CTL | CTL_11B; else if (IS_CHAN_G(chan)) ctl = SD_NO_CTL | CTL_11G; else ctl = SD_NO_CTL | CTL_11A; } else { ichan = ath9k_regd_check_channel(ah, chan); if (ichan != NULL) { /* FIXME */ if (IS_CHAN_A(ichan)) ctl = ichan->conformanceTestLimit[0]; else if (IS_CHAN_B(ichan)) ctl = ichan->conformanceTestLimit[1]; else if (IS_CHAN_G(ichan)) ctl = ichan->conformanceTestLimit[2]; if (IS_CHAN_G(chan) && (ctl & 0xf) == CTL_11B) ctl = (ctl & ~0xf) | CTL_11G; } } return ctl; } void ath9k_regd_get_current_country(struct ath_hal *ah, struct ath9k_country_entry *ctry) { u16 rd = ath9k_regd_get_eepromRD(ah); ctry->isMultidomain = false; if (rd == CTRY_DEFAULT) ctry->isMultidomain = true; else if (!(rd & COUNTRY_ERD_FLAG)) ctry->isMultidomain = isWwrSKU(ah); ctry->countryCode = ah->ah_countryCode; ctry->regDmnEnum = ah->ah_currentRD; ctry->regDmn5G = ah->ah_currentRD5G; ctry->regDmn2G = ah->ah_currentRD2G; ctry->iso[0] = ah->ah_iso[0]; ctry->iso[1] = ah->ah_iso[1]; ctry->iso[2] = ah->ah_iso[2]; }