/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 "arn_core.h" #include "arn_hw.h" #include "arn_regd.h" #include "arn_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, uint32_t n, uint32_t size, ath_hal_cmp_t *cmp) { uint8_t *aa = a; uint8_t *ai, *t; for (ai = aa + size; --n >= 1; ai += size) for (t = ai; t > aa; t -= size) { uint8_t *u = t - size; if (cmp(u, t) <= 0) break; swap(u, t, size); } } static uint16_t ath9k_regd_get_eepromRD(struct ath_hal *ah) { return (ah->ah_currentRD & ~WORLDWIDE_ROAMING_FLAG); } static boolean_t ath9k_regd_is_chan_bm_zero(uint64_t *bitmask) { int i; for (i = 0; i < BMLEN; i++) { if (bitmask[i] != 0) return (B_FALSE); } return (B_TRUE); } static boolean_t ath9k_regd_is_eeprom_valid(struct ath_hal *ah) { uint16_t rd = ath9k_regd_get_eepromRD(ah); int i; if (rd & COUNTRY_ERD_FLAG) { uint16_t cc = rd & ~COUNTRY_ERD_FLAG; for (i = 0; i < ARRAY_SIZE(allCountries); i++) if (allCountries[i].countryCode == cc) return (B_TRUE); } else { for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) if (regDomainPairs[i].regDmnEnum == rd) return (B_TRUE); } ARN_DBG((ARN_DBG_REGULATORY, "%s: invalid regulatory domain/country code 0x%x\n", __func__, rd)); return (B_FALSE); } static boolean_t ath9k_regd_is_fcc_midband_supported(struct ath_hal *ah) { uint32_t regcap; regcap = ah->ah_caps.reg_cap; if (regcap & AR_EEPROM_EEREGCAP_EN_FCC_MIDBAND) return (B_TRUE); else return (B_FALSE); } static boolean_t ath9k_regd_is_ccode_valid(struct ath_hal *ah, uint16_t cc) { uint16_t rd; int i; if (cc == CTRY_DEFAULT) return (B_TRUE); if (cc == CTRY_DEBUG) return (B_TRUE); rd = ath9k_regd_get_eepromRD(ah); ARN_DBG((ARN_DBG_REGULATORY, "%s: EEPROM regdomain 0x%x\n", __func__, rd)); if (rd & COUNTRY_ERD_FLAG) { ARN_DBG((ARN_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 ARN_SUPPORT_11D if ((rd & WORLD_SKU_MASK) == WORLD_SKU_PREFIX) return (B_TRUE); #endif if (allCountries[i].regDmnEnum == rd || rd == DEBUG_REG_DMN || rd == NO_ENUMRD) return (B_TRUE); } } return (B_FALSE); } static void ath9k_regd_get_wmodes_nreg(struct ath_hal *ah, struct country_code_to_enum_rd *country, struct regDomain *rd5GHz, uint8_t *modes_allowed) { bcopy(ah->ah_caps.wireless_modes, modes_allowed, sizeof (ah->ah_caps.wireless_modes)); if (is_set(ATH9K_MODE_11G, ah->ah_caps.wireless_modes) && (!country->allow11g)) clr_bit(ATH9K_MODE_11G, modes_allowed); if (is_set(ATH9K_MODE_11A, ah->ah_caps.wireless_modes) && (ath9k_regd_is_chan_bm_zero(rd5GHz->chan11a))) clr_bit(ATH9K_MODE_11A, modes_allowed); if (is_set(ATH9K_MODE_11NG_HT20, ah->ah_caps.wireless_modes) && (!country->allow11ng20)) clr_bit(ATH9K_MODE_11NG_HT20, modes_allowed); if (is_set(ATH9K_MODE_11NA_HT20, ah->ah_caps.wireless_modes) && (!country->allow11na20)) clr_bit(ATH9K_MODE_11NA_HT20, modes_allowed); if (is_set(ATH9K_MODE_11NG_HT40PLUS, ah->ah_caps.wireless_modes) && (!country->allow11ng40)) clr_bit(ATH9K_MODE_11NG_HT40PLUS, modes_allowed); if (is_set(ATH9K_MODE_11NG_HT40MINUS, ah->ah_caps.wireless_modes) && (!country->allow11ng40)) clr_bit(ATH9K_MODE_11NG_HT40MINUS, modes_allowed); if (is_set(ATH9K_MODE_11NA_HT40PLUS, ah->ah_caps.wireless_modes) && (!country->allow11na40)) clr_bit(ATH9K_MODE_11NA_HT40PLUS, modes_allowed); if (is_set(ATH9K_MODE_11NA_HT40MINUS, ah->ah_caps.wireless_modes) && (!country->allow11na40)) clr_bit(ATH9K_MODE_11NA_HT40MINUS, modes_allowed); } boolean_t ath9k_regd_is_public_safety_sku(struct ath_hal *ah) { uint16_t rd; rd = ath9k_regd_get_eepromRD(ah); switch (rd) { case FCC4_FCCA: case (CTRY_UNITED_STATES_FCC49 | COUNTRY_ERD_FLAG): return (B_TRUE); case DEBUG_REG_DMN: case NO_ENUMRD: if (ah->ah_countryCode == CTRY_UNITED_STATES_FCC49) return (B_TRUE); break; } return (B_FALSE); } static struct country_code_to_enum_rd * ath9k_regd_find_country(uint16_t countryCode) { int i; for (i = 0; i < ARRAY_SIZE(allCountries); i++) { if (allCountries[i].countryCode == countryCode) return (&allCountries[i]); } return (NULL); } static uint16_t ath9k_regd_get_default_country(struct ath_hal *ah) { uint16_t rd; int i; rd = ath9k_regd_get_eepromRD(ah); if (rd & COUNTRY_ERD_FLAG) { struct country_code_to_enum_rd *country = NULL; uint16_t 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 boolean_t 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) { (void) memcpy(rd, ®Domains[i], sizeof (struct regDomain)); } return (B_TRUE); } } return (B_FALSE); } static boolean_t ath9k_regd_is_valid_reg_domainPair(int regDmnPair) { int i; if (regDmnPair == NO_ENUMRD) return (B_FALSE); for (i = 0; i < ARRAY_SIZE(regDomainPairs); i++) { if (regDomainPairs[i].regDmnEnum == regDmnPair) return (B_TRUE); } return (B_FALSE); } static boolean_t ath9k_regd_get_wmode_regdomain(struct ath_hal *ah, int regDmn, uint16_t channelFlag, struct regDomain *rd) { int i, found; uint64_t flags = NO_REQ; struct reg_dmn_pair_mapping *regPair = NULL; int regOrg; regOrg = regDmn; if (regDmn == CTRY_DEFAULT) { uint16_t 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) { ARN_DBG((ARN_DBG_REGULATORY, "%s: Failed to find reg domain pair %u\n", __func__, regDmn)); return (B_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) { ARN_DBG((ARN_DBG_REGULATORY, "%s: Failed to find unitary reg domain %u\n", __func__, regDmn)); return (B_FALSE); } else { rd->pscan &= regPair->pscanMask; if (((regOrg & MULTI_DOMAIN_MASK) == 0) && (flags != NO_REQ)) { rd->flags = (uint32_t)flags; /* LINT */ } rd->flags &= (channelFlag & CHANNEL_2GHZ) ? REG_DOMAIN_2GHZ_MASK : REG_DOMAIN_5GHZ_MASK; return (B_TRUE); } } static boolean_t ath9k_regd_is_bit_set(int bit, uint64_t *bitmask) { int byteOffset, bitnum; uint64_t val; byteOffset = bit / 64; bitnum = bit - byteOffset * 64; val = ((uint64_t)1) << bitnum; if (bitmask[byteOffset] & val) return (B_TRUE); else return (B_FALSE); } static void ath9k_regd_add_reg_classid(uint8_t *regclassids, uint32_t maxregids, uint32_t *nregids, uint8_t 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; } } static boolean_t ath9k_regd_get_eeprom_reg_ext_bits(struct ath_hal *ah, enum reg_ext_bitmap bit) { return ((ah->ah_currentRDExt & (1 << bit)) ? B_TRUE : B_FALSE); } #ifdef ARN_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, uint16_t 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); } /* ARGSUSED */ static boolean_t ath9k_regd_add_channel( struct ath_hal *ah, uint16_t c, uint16_t c_lo, uint16_t c_hi, uint16_t maxChan, uint8_t ctl, int pos, struct regDomain rd5GHz, struct RegDmnFreqBand *fband, struct regDomain *rd, const struct cmode *cm, struct ath9k_channel *ichans, boolean_t enableExtendedChannels) { struct ath9k_channel *chan; int ret; uint32_t channelFlags = 0; uint8_t privFlags = 0; if (!(c_lo <= c && c <= c_hi)) { ARN_DBG((ARN_DBG_REGULATORY, "%s: c %u out of range [%u..%u]\n", __func__, c, c_lo, c_hi)); return (B_FALSE); } if ((fband->channelBW == CHANNEL_HALF_BW) && !(ah->ah_caps.hw_caps & ATH9K_HW_CAP_CHAN_HALFRATE)) { ARN_DBG((ARN_DBG_REGULATORY, "%s: Skipping %u half rate channel\n", __func__, c)); return (B_FALSE); } if ((fband->channelBW == CHANNEL_QUARTER_BW) && !(ah->ah_caps.hw_caps & ATH9K_HW_CAP_CHAN_QUARTERRATE)) { ARN_DBG((ARN_DBG_REGULATORY, "%s: Skipping %u quarter rate channel\n", __func__, c)); return (B_FALSE); } if (((c + fband->channelSep) / 2) > (maxChan + HALF_MAXCHANBW)) { ARN_DBG((ARN_DBG_REGULATORY, "%s: c %u > maxChan %u\n", __func__, c, maxChan)); return (B_FALSE); } if ((fband->usePassScan & IS_ECM_CHAN) && !enableExtendedChannels) { ARN_DBG((ARN_DBG_REGULATORY, "Skipping ecm channel\n")); return (B_FALSE); } if ((rd->flags & NO_HOSTAP) && (ah->ah_opmode == ATH9K_M_HOSTAP)) { ARN_DBG((ARN_DBG_REGULATORY, "Skipping HOSTAP channel\n")); return (B_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)) { ARN_DBG((ARN_DBG_REGULATORY, "Skipping HT40 channel (en_fcc_dfs_ht40 = 0)\n")); return (B_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)) { ARN_DBG((ARN_DBG_REGULATORY, "Skipping HT40 channel (en_jap_ht40 = 0)\n")); return (B_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)) { ARN_DBG((ARN_DBG_REGULATORY, "Skipping HT40 channel (en_jap_dfs_ht40 = 0)\n")); return (B_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) { uint32_t regcap = ah->ah_caps.reg_cap; 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_11A) || (cm->mode == ATH9K_MODE_11NA_HT20) || (cm->mode == ATH9K_MODE_11NA_HT40PLUS) || (cm->mode == ATH9K_MODE_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) ? B_TRUE : B_FALSE); } static boolean_t ath9k_regd_japan_check(struct ath_hal *ah, int b, struct regDomain *rd5GHz) { boolean_t skipband = B_FALSE; int i; uint32_t regcap; for (i = 0; i < ARRAY_SIZE(j_bandcheck); i++) { if (j_bandcheck[i].freqbandbit == b) { regcap = ah->ah_caps.reg_cap; if ((j_bandcheck[i].eepromflagtocheck & regcap) == 0) { skipband = B_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; } } ARN_DBG((ARN_DBG_REGULATORY, "%s: Skipping %d freq band\n", __func__, j_bandcheck[i].freqbandbit)); return (skipband); } boolean_t ath9k_regd_init_channels( struct ath_hal *ah, uint32_t maxchans, uint32_t *nchans, uint8_t *regclassids, uint32_t maxregids, uint32_t *nregids, uint16_t cc, boolean_t enableOutdoor, boolean_t enableExtendedChannels) { uint16_t 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; uint8_t ctl; int regdmn; uint16_t chanSep; uint8_t *modes_avail; uint8_t modes_allowed[4]; (void) memset(modes_allowed, 0, sizeof (modes_allowed)); ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "cc %u %s %s\n", cc, enableOutdoor ? "Enable outdoor" : "", enableExtendedChannels ? "Enable ecm" : "")); if (!ath9k_regd_is_ccode_valid(ah, cc)) { ARN_DBG((ARN_DBG_XMIT, "arn: ath9k_regd_init_channels(): " "invalid country code %d\n", cc)); return (B_FALSE); } if (!ath9k_regd_is_eeprom_valid(ah)) { ARN_DBG((ARN_DBG_ANY, "arn: ath9k_regd_init_channels(): " "invalid EEPROM contents\n")); return (B_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 ARN_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) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "Country is NULL!!!!, cc= %d\n", ah->ah_countryCode)); return (B_FALSE); } else { regdmn = country->regDmnEnum; #ifdef ARN_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 ARN_SUPPORT_11D } #endif if (!ath9k_regd_get_wmode_regdomain(ah, regdmn, ~CHANNEL_2GHZ, &rd5GHz)) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "couldn't find unitary " "5GHz reg domain for country %u\n", ah->ah_countryCode)); return (B_FALSE); } if (!ath9k_regd_get_wmode_regdomain(ah, regdmn, CHANNEL_2GHZ, &rd2GHz)) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "couldn't find unitary 2GHz " "reg domain for country %u\n", ah->ah_countryCode)); return (B_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)) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "couldn't find unitary 5GHz " "reg domain for country %u\n", ah->ah_countryCode)); return (B_FALSE); } } } if (country == NULL) { modes_avail = ah->ah_caps.wireless_modes; } else { ath9k_regd_get_wmodes_nreg(ah, country, &rd5GHz, modes_allowed); modes_avail = modes_allowed; 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++) { uint16_t c, c_hi, c_lo; uint64_t *channelBM = NULL; struct regDomain *rd = NULL; struct RegDmnFreqBand *fband = NULL, *freqs; int8_t low_adj = 0, hi_adj = 0; if (!is_set(cm->mode, modes_avail)) { ARN_DBG((ARN_DBG_REGULATORY, "%s: !avail mode %d flags 0x%x\n", __func__, cm->mode, cm->flags)); continue; } if (!ath9k_get_channel_edges(ah, cm->flags, &c_lo, &c_hi)) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "channels 0x%x not supported " "by hardware\n", cm->flags)); continue; } switch (cm->mode) { case ATH9K_MODE_11A: case ATH9K_MODE_11NA_HT20: case ATH9K_MODE_11NA_HT40PLUS: case ATH9K_MODE_11NA_HT40MINUS: rd = &rd5GHz; channelBM = rd->chan11a; freqs = ®Dmn5GhzFreq[0]; ctl = rd->conformanceTestLimit; break; case ATH9K_MODE_11B: rd = &rd2GHz; channelBM = rd->chan11b; freqs = ®Dmn2GhzFreq[0]; ctl = rd->conformanceTestLimit | CTL_11B; break; case ATH9K_MODE_11G: case ATH9K_MODE_11NG_HT20: case ATH9K_MODE_11NG_HT40PLUS: case ATH9K_MODE_11NG_HT40MINUS: rd = &rd2GHz; channelBM = rd->chan11g; freqs = ®Dmn2Ghz11gFreq[0]; ctl = rd->conformanceTestLimit | CTL_11G; break; default: ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "Unknown HAL mode 0x%x\n", cm->mode)); continue; } if (ath9k_regd_is_chan_bm_zero(channelBM)) continue; if ((cm->mode == ATH9K_MODE_11NA_HT40PLUS) || (cm->mode == ATH9K_MODE_11NG_HT40PLUS)) { hi_adj = -20; } if ((cm->mode == ATH9K_MODE_11NA_HT40MINUS) || (cm->mode == ATH9K_MODE_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) { ARN_DBG((ARN_DBG_REGULATORY, "too many channels " "for channel table\n")); 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)) { ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "too many channels %u; truncating to %u\n", next, (int)ARRAY_SIZE(ah->ah_channels))); next = ARRAY_SIZE(ah->ah_channels); } #ifdef ARN_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; ARN_DBG((ARN_DBG_REGULATORY, "arn: ath9k_regd_init_channels(): " "Channel list:\n")); for (i = 0; i < next; i++) { ARN_DBG((ARN_DBG_REGULATORY, "arn: " "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 = (uint16_t)regdmn; /* LINT */ 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; ARN_DBG((ARN_DBG_REGULATORY, "arn: " "%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); } ARN_DBG((ARN_DBG_REGULATORY, "arn: " "%s: channel %u/0x%x d %d\n", __func__, cc->channel, cc->channelFlags, d)); if (d > 0) { base = cc + 1; lim--; } } ARN_DBG((ARN_DBG_REGULATORY, "arn: " "%s: no match for %u/0x%x\n", __func__, c->channel, c->channelFlags)); return (NULL); } uint32_t 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); } uint32_t ath9k_regd_get_ctl(struct ath_hal *ah, struct ath9k_channel *chan) { uint32_t 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) { uint16_t rd = ath9k_regd_get_eepromRD(ah); ctry->isMultidomain = B_FALSE; if (rd == CTRY_DEFAULT) ctry->isMultidomain = B_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]; }