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 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; }
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; }
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]; }
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, "invalid regulatory domain/country code 0x%x\n", rd); return false; }
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_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); }
bool ath9k_regd_init_channels(struct ath_hal *ah, u32 maxchans, u32 *nchans, u8 *regclassids, u32 maxregids, u32 *nregids, u16 cc, bool enableOutdoor, bool enableExtendedChannels) { 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; unsigned long *modes_avail; DECLARE_BITMAP(modes_allowed, ATH9K_MODE_MAX); DPRINTF(ah->ah_sc, ATH_DBG_REGULATORY, "%s: cc %u %s %s\n", __func__, cc, 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) { 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++) { 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 (!test_bit(cm->mode, modes_avail)) { DPRINTF(ah->ah_sc, ATH_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)) { 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_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: 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_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) { 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; }
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; } }