1027 lines
25 KiB
C
1027 lines
25 KiB
C
/*
|
|
* 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 <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#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.reg_cap;
|
|
|
|
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 void
|
|
ath9k_regd_get_wmodes_nreg(struct ath_hal *ah,
|
|
struct country_code_to_enum_rd *country,
|
|
struct regDomain *rd5GHz,
|
|
unsigned long *modes_allowed)
|
|
{
|
|
bitmap_copy(modes_allowed, ah->ah_caps.wireless_modes, ATH9K_MODE_MAX);
|
|
|
|
if (test_bit(ATH9K_MODE_11G, ah->ah_caps.wireless_modes) &&
|
|
(!country->allow11g))
|
|
clear_bit(ATH9K_MODE_11G, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11A, ah->ah_caps.wireless_modes) &&
|
|
(ath9k_regd_is_chan_bm_zero(rd5GHz->chan11a)))
|
|
clear_bit(ATH9K_MODE_11A, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NG_HT20, ah->ah_caps.wireless_modes)
|
|
&& (!country->allow11ng20))
|
|
clear_bit(ATH9K_MODE_11NG_HT20, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NA_HT20, ah->ah_caps.wireless_modes)
|
|
&& (!country->allow11na20))
|
|
clear_bit(ATH9K_MODE_11NA_HT20, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NG_HT40PLUS, ah->ah_caps.wireless_modes) &&
|
|
(!country->allow11ng40))
|
|
clear_bit(ATH9K_MODE_11NG_HT40PLUS, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NG_HT40MINUS, ah->ah_caps.wireless_modes) &&
|
|
(!country->allow11ng40))
|
|
clear_bit(ATH9K_MODE_11NG_HT40MINUS, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NA_HT40PLUS, ah->ah_caps.wireless_modes) &&
|
|
(!country->allow11na40))
|
|
clear_bit(ATH9K_MODE_11NA_HT40PLUS, modes_allowed);
|
|
|
|
if (test_bit(ATH9K_MODE_11NA_HT40MINUS, ah->ah_caps.wireless_modes) &&
|
|
(!country->allow11na40))
|
|
clear_bit(ATH9K_MODE_11NA_HT40MINUS, modes_allowed);
|
|
}
|
|
|
|
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.hw_caps & ATH9K_HW_CAP_CHAN_HALFRATE)) {
|
|
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.hw_caps & ATH9K_HW_CAP_CHAN_QUARTERRATE)) {
|
|
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.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) ? 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.reg_cap;
|
|
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,
|
|
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;
|
|
}
|
|
|
|
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];
|
|
}
|