1297 lines
34 KiB
C
Executable File
1297 lines
34 KiB
C
Executable File
/*
|
|
* Copyright (c) 2004-2013 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 "core.h"
|
|
#include "debug.h"
|
|
|
|
/* Default Regulatory for invalid reg_code. */
|
|
static struct ieee80211_regdomain ath6kl_regd_NA = {
|
|
.n_reg_rules = 1,
|
|
.alpha2 = "NA",
|
|
.reg_rules = {
|
|
REG_RULE(2412 - 10, 2462 + 10, 20, 3, 20, 0),
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Naming from ISO 3166
|
|
*/
|
|
static const struct reg_code_to_isoname ath6kl_country_code_to_iso_name[] = {
|
|
/* Country Code */
|
|
{COUNTRY_ALBANIA, "AL", NULL},
|
|
{COUNTRY_ALGERIA, "DZ", NULL},
|
|
{COUNTRY_ARGENTINA, "AR", NULL},
|
|
{COUNTRY_ARMENIA, "AM", NULL},
|
|
{COUNTRY_ARUBA, "AW", NULL},
|
|
{COUNTRY_AUSTRALIA, "AU", NULL},
|
|
{COUNTRY_AUSTRALIA_AP, "A2", NULL},
|
|
{COUNTRY_AUSTRIA, "AT", NULL},
|
|
{COUNTRY_AZERBAIJAN, "AZ", NULL},
|
|
{COUNTRY_BAHRAIN, "BH", NULL},
|
|
{COUNTRY_BANGLADESH, "BD", NULL},
|
|
{COUNTRY_BARBADOS, "BB", NULL},
|
|
{COUNTRY_BELARUS, "BY", NULL},
|
|
{COUNTRY_BELGIUM, "BE", NULL},
|
|
{COUNTRY_BELIZE, "BZ", NULL},
|
|
{COUNTRY_BOLIVIA, "BO", NULL},
|
|
{COUNTRY_BOSNIA_HERZEGOWANIA, "BA", NULL},
|
|
{COUNTRY_BRAZIL, "BR", NULL},
|
|
{COUNTRY_BRUNEI_DARUSSALAM, "BN", NULL},
|
|
{COUNTRY_BULGARIA, "BG", NULL},
|
|
{COUNTRY_CAMBODIA, "KH", NULL},
|
|
{COUNTRY_CANADA, "CA", NULL},
|
|
{COUNTRY_CANADA_AP, "C2", NULL},
|
|
{COUNTRY_CHILE, "CL", NULL},
|
|
{COUNTRY_CHINA, "CN", NULL},
|
|
{COUNTRY_COLOMBIA, "CO", NULL},
|
|
{COUNTRY_COSTA_RICA, "CR", NULL},
|
|
{COUNTRY_CROATIA, "HR", NULL},
|
|
{COUNTRY_CYPRUS, "CY", NULL},
|
|
{COUNTRY_CZECH, "CZ", NULL},
|
|
{COUNTRY_DENMARK, "DK", NULL},
|
|
{COUNTRY_DOMINICAN_REPUBLIC, "DO", NULL},
|
|
{COUNTRY_ECUADOR, "EC", NULL},
|
|
{COUNTRY_EGYPT, "EG", NULL},
|
|
{COUNTRY_EL_SALVADOR, "SV", NULL},
|
|
{COUNTRY_ESTONIA, "EE", NULL},
|
|
{COUNTRY_FAEROE_ISLANDS, "FO", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_FINLAND, "FI", NULL},
|
|
{COUNTRY_FRANCE, "FR", NULL},
|
|
{COUNTRY_FRANCE2, "F2", NULL},
|
|
{COUNTRY_GEORGIA, "GE", NULL},
|
|
{COUNTRY_GERMANY, "DE", NULL},
|
|
{COUNTRY_GREECE, "GR", NULL},
|
|
{COUNTRY_GREENLAND, "GL", NULL},
|
|
{COUNTRY_GRENADA, "GD", NULL},
|
|
{COUNTRY_GUAM, "GU", NULL},
|
|
{COUNTRY_GUATEMALA, "GT", NULL},
|
|
{COUNTRY_HAITI, "HT", NULL},
|
|
{COUNTRY_HONDURAS, "HN", NULL},
|
|
{COUNTRY_HONG_KONG, "KH", NULL},
|
|
{COUNTRY_HUNGARY, "HU", NULL},
|
|
{COUNTRY_ICELAND, "IS", NULL},
|
|
{COUNTRY_INDIA, "IN", NULL},
|
|
{COUNTRY_INDONESIA, "ID", NULL},
|
|
{COUNTRY_IRAN, "IR", NULL},
|
|
{COUNTRY_IRAQ, "IQ", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_IRELAND, "IE", NULL},
|
|
{COUNTRY_ISRAEL, "IL", NULL},
|
|
{COUNTRY_ITALY, "IT", NULL},
|
|
{COUNTRY_JAMAICA, "JM", NULL},
|
|
{COUNTRY_JAPAN, "JP", NULL},
|
|
{COUNTRY_JAPAN1, "J2", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JAPAN2, "J3", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JAPAN3, "J4", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JAPAN4, "J5", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JAPAN5, "J6", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JAPAN6, "J7", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_JORDAN, "JO", NULL},
|
|
{COUNTRY_KAZAKHSTAN, "KZ", NULL},
|
|
{COUNTRY_KENYA, "KE", NULL},
|
|
{COUNTRY_KOREA_NORTH, "KP", NULL},
|
|
{COUNTRY_KOREA_ROC, "KR", NULL},
|
|
{COUNTRY_KOREA_ROC2, "K2", NULL},
|
|
{COUNTRY_KOREA_ROC3, "K3", NULL},
|
|
{COUNTRY_KUWAIT, "KW", NULL},
|
|
{COUNTRY_LATVIA, "LV", NULL},
|
|
{COUNTRY_LEBANON, "LE", NULL},
|
|
{COUNTRY_LIBYA, "LY", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_LIECHTENSTEIN, "LI", NULL},
|
|
{COUNTRY_LITHUANIA, "LT", NULL},
|
|
{COUNTRY_LUXEMBOURG, "LU", NULL},
|
|
{COUNTRY_MACAU, "MO", NULL},
|
|
{COUNTRY_MACEDONIA, "MK", NULL},
|
|
{COUNTRY_MALAYSIA, "MY", NULL},
|
|
{COUNTRY_MALTA, "MT", NULL},
|
|
{COUNTRY_MEXICO, "MX", NULL},
|
|
{COUNTRY_MONACO, "MC", NULL},
|
|
{COUNTRY_MOROCCO, "MA", NULL},
|
|
{COUNTRY_NEPAL, "NP", NULL},
|
|
{COUNTRY_NETHERLANDS, "NL", NULL},
|
|
{COUNTRY_NETHERLAND_ANTILLES, "AN", NULL},
|
|
{COUNTRY_NEW_ZEALAND, "NZ", NULL},
|
|
{COUNTRY_NICARAGUA, "NI", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_NORWAY, "NO", NULL},
|
|
{COUNTRY_OMAN, "OM", NULL},
|
|
{COUNTRY_PAKISTAN, "PK", NULL},
|
|
{COUNTRY_PANAMA, "PA", NULL},
|
|
{COUNTRY_PARAGUAY, "PY", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_PERU, "PE", NULL},
|
|
{COUNTRY_PHILIPPINES, "PH", NULL},
|
|
{COUNTRY_POLAND, "PL", NULL},
|
|
{COUNTRY_PORTUGAL, "PT", NULL},
|
|
{COUNTRY_PUERTO_RICO, "PR", NULL},
|
|
{COUNTRY_QATAR, "QA", NULL},
|
|
{COUNTRY_ROMANIA, "RO", NULL},
|
|
{COUNTRY_RUSSIA, "RU", NULL},
|
|
{COUNTRY_RWANDA, "RW", NULL},
|
|
{COUNTRY_SAUDI_ARABIA, "SA", NULL},
|
|
{COUNTRY_SERBIA, "RS", NULL}, /* NOT SUPPORT */
|
|
{COUNTRY_MONTENEGRO, "ME", NULL},
|
|
{COUNTRY_SINGAPORE, "SG", NULL},
|
|
{COUNTRY_SLOVAKIA, "SK", NULL},
|
|
{COUNTRY_SLOVENIA, "SI", NULL},
|
|
{COUNTRY_SOUTH_AFRICA, "ZA", NULL},
|
|
{COUNTRY_SPAIN, "ES", NULL},
|
|
{COUNTRY_SRILANKA, "LK", NULL},
|
|
{COUNTRY_SWEDEN, "SE", NULL},
|
|
{COUNTRY_SWITZERLAND, "CH", NULL},
|
|
{COUNTRY_SYRIA, "SY", NULL},
|
|
{COUNTRY_TAIWAN, "TW", NULL},
|
|
{COUNTRY_THAILAND, "TH", NULL},
|
|
{COUNTRY_TRINIDAD_Y_TOBAGO, "TT", NULL},
|
|
{COUNTRY_TUNISIA, "TN", NULL},
|
|
{COUNTRY_TURKEY, "TR", NULL},
|
|
{COUNTRY_UAE, "AE", NULL},
|
|
{COUNTRY_UGANDA, "UG", NULL},
|
|
{COUNTRY_UKRAINE, "UA", NULL},
|
|
{COUNTRY_UNITED_KINGDOM, "GB", NULL},
|
|
{COUNTRY_UNITED_STATES, "US", NULL},
|
|
{COUNTRY_UNITED_STATES_AP, "U2", NULL},
|
|
{COUNTRY_UNITED_STATES_PS, "PS", NULL},
|
|
{COUNTRY_URUGUAY, "UY", NULL},
|
|
{COUNTRY_UZBEKISTAN, "UZ", NULL},
|
|
{COUNTRY_VENEZUELA, "VE", NULL},
|
|
{COUNTRY_VIET_NAM, "VN", NULL},
|
|
{COUNTRY_YEMEN, "YE", NULL},
|
|
{COUNTRY_ZIMBABWE, "ZW", NULL},
|
|
/* keep last */
|
|
{NULL_REG_CODE, NULL},
|
|
};
|
|
|
|
static const struct reg_code_to_isoname ath6kl_region_code_to_name[] = {
|
|
/* Region Code */
|
|
{REGION_NO_ENUMRD, "00", NULL}, /* DEBUG */
|
|
{REGION_NULL1_WORLD, "03", "AL"},
|
|
{REGION_NULL1_ETSIB, "07", NULL},
|
|
{REGION_NULL1_ETSIC, "08", NULL},
|
|
{REGION_FCC1_FCCA, "10", "CO"},
|
|
{REGION_FCC1_WORLD, "11", "CR"},
|
|
{REGION_FCC2_FCCA, "20", NULL},
|
|
{REGION_FCC2_WORLD, "21", "BB"},
|
|
{REGION_FCC2_ETSIC, "22", NULL},
|
|
{REGION_FCC3_FCCA, "3a", "CA"},
|
|
{REGION_FCC3_WORLD, "3b", "AR"},
|
|
{REGION_FCC3_ETSIC, "3f", "NZ"},
|
|
{REGION_FCC4_FCCA, "12", "PS"},
|
|
{REGION_FCC5_FCCA, "13", NULL},
|
|
{REGION_FCC5_WORLD, "16", NULL},
|
|
{REGION_FCC6_FCCA, "14", "CA"},
|
|
{REGION_FCC6_WORLD, "23", "AU"},
|
|
{REGION_ETSI1_WORLD, "37", "AW"},
|
|
{REGION_ETSI2_WORLD, "35", "JO"},
|
|
{REGION_ETSI3_WORLD, "36", "CY"},
|
|
{REGION_ETSI4_WORLD, "30", "AM"},
|
|
{REGION_ETSI4_ETSIC, "38", NULL}, /* NOT SUPPORT */
|
|
{REGION_ETSI5_WORLD, "39", NULL},
|
|
{REGION_ETSI6_WORLD, "34", NULL},
|
|
{REGION_ETSI8_WORLD, "3d", "RU"},
|
|
{REGION_ETSI9_WORLD, "3e", "UA"},
|
|
{REGION_ETSI_RESERVED, "33", NULL}, /* NOT SUPPORT */
|
|
{REGION_FRANCE_RES, "31", NULL},
|
|
{REGION_APL6_WORLD, "5b", "BH"},
|
|
{REGION_APL4_WORLD, "42", "MA"},
|
|
{REGION_APL3_FCCA, "50", NULL},
|
|
{REGION_APL_RESERVED, "44", NULL}, /* NOT SUPPORT */
|
|
{REGION_APL2_WORLD, "45", "ID"},
|
|
{REGION_APL2_APLC, "46", NULL}, /* NOT SUPPORT */
|
|
{REGION_APL3_WORLD, "47", NULL},
|
|
{REGION_APL2_APLD, "49", "KR"},
|
|
{REGION_APL2_FCCA, "4d", NULL},
|
|
{REGION_APL1_WORLD, "52", "CN"},
|
|
{REGION_APL1_FCCA, "53", NULL}, /* NOT SUPPORT */
|
|
{REGION_APL1_ETSIC, "55", "BZ"},
|
|
{REGION_APL2_ETSIC, "56", NULL},
|
|
{REGION_APL5_WORLD, "58", NULL},
|
|
{REGION_APL7_FCCA, "5c", "TW"},
|
|
{REGION_APL8_WORLD, "5d", NULL},
|
|
{REGION_APL9_WORLD, "5e", "KP"},
|
|
{REGION_APL10_WORLD, "5f", "KR"},
|
|
{REGION_MKK5_MKKA, "99", NULL},
|
|
{REGION_MKK5_FCCA, "9a", NULL},
|
|
{REGION_MKK5_MKKC, "88", "JP"},
|
|
{REGION_MKK11_MKKA, "d4", NULL},
|
|
{REGION_MKK11_FCCA, "d5", NULL},
|
|
{REGION_MKK11_MKKC, "d7", NULL},
|
|
{REGION_WOR0_WORLD, "60", "00"},
|
|
{REGION_WOR1_WORLD, "61", "00"},
|
|
{REGION_WOR2_WORLD, "62", "00"},
|
|
{REGION_WOR3_WORLD, "63", "00"},
|
|
{REGION_WOR4_WORLD, "64", "00"},
|
|
{REGION_WOR5_ETSIC, "65", "00"},
|
|
{REGION_WOR01_WORLD, "66", "00"},
|
|
{REGION_WOR02_WORLD, "67", "00"},
|
|
{REGION_EU1_WORLD, "68", "00"},
|
|
{REGION_WOR9_WORLD, "69", "00"},
|
|
{REGION_WORA_WORLD, "6a", "00"}, /* Default region code */
|
|
{REGION_WORB_WORLD, "6b", "00"},
|
|
{REGION_WORC_WORLD, "6c", "00"},
|
|
|
|
/* keep last */
|
|
{NULL_REG_CODE, NULL},
|
|
};
|
|
|
|
static inline bool __is_ht40_not_allowed(struct ieee80211_channel *chan)
|
|
{
|
|
if (!chan)
|
|
return true;
|
|
|
|
/* HT40 is not allowed in CH12, CH13 and CH14. */
|
|
if ((chan->center_freq == 2467) ||
|
|
(chan->center_freq == 2472) ||
|
|
(chan->center_freq == 2484))
|
|
return true;
|
|
|
|
if (chan->flags & IEEE80211_CHAN_DISABLED)
|
|
return true;
|
|
|
|
if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void _reg_process_chan(struct ieee80211_supported_band *sband,
|
|
int chan_idx)
|
|
{
|
|
struct ieee80211_channel *channel;
|
|
struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
|
|
unsigned int i;
|
|
u16 start_freq = 0;
|
|
|
|
channel = &sband->channels[chan_idx];
|
|
|
|
if (__is_ht40_not_allowed(channel)) {
|
|
channel->flags |= IEEE80211_CHAN_NO_HT40;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
struct ieee80211_channel *c = &sband->channels[i];
|
|
|
|
if (c->center_freq == (channel->center_freq - 20))
|
|
channel_before = c;
|
|
if (c->center_freq == (channel->center_freq + 20))
|
|
channel_after = c;
|
|
}
|
|
|
|
/* Only update it the channel still not yet marked as HT40+/HT40-.*/
|
|
if (channel->center_freq < 4000) {
|
|
if (!(channel->flags & IEEE80211_CHAN_NO_HT40MINUS)) {
|
|
if (__is_ht40_not_allowed(channel_before))
|
|
channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
|
|
else
|
|
channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
|
|
}
|
|
|
|
if (!(channel->flags & IEEE80211_CHAN_NO_HT40PLUS)) {
|
|
if (__is_ht40_not_allowed(channel_after))
|
|
channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
|
|
else
|
|
channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
|
|
}
|
|
} else { /* No overlap HT40 in 5G. */
|
|
/*
|
|
* Don't care 5170(CH34) - 5230(CH46) because no HT40
|
|
* channel in it.
|
|
*/
|
|
if (channel->center_freq >= 5745)
|
|
start_freq = 5745; /* Upper U-NII */
|
|
else
|
|
start_freq = 5180;
|
|
|
|
if (((channel->center_freq - start_freq) % 40) == 0) {
|
|
/* HT40+ only channel */
|
|
channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
|
|
|
|
if (!(channel->flags & IEEE80211_CHAN_NO_HT40PLUS)) {
|
|
if (__is_ht40_not_allowed(channel_after))
|
|
channel->flags |=
|
|
IEEE80211_CHAN_NO_HT40PLUS;
|
|
else
|
|
channel->flags &=
|
|
~IEEE80211_CHAN_NO_HT40PLUS;
|
|
}
|
|
} else {
|
|
/* HT40- only channel */
|
|
channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
|
|
|
|
if (!(channel->flags & IEEE80211_CHAN_NO_HT40MINUS)) {
|
|
if (__is_ht40_not_allowed(channel_before))
|
|
channel->flags |=
|
|
IEEE80211_CHAN_NO_HT40MINUS;
|
|
else
|
|
channel->flags &=
|
|
~IEEE80211_CHAN_NO_HT40MINUS;
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void ath6kl_reg_chan_flags_update(struct reg_info *reg)
|
|
{
|
|
struct wiphy *wiphy = reg->wiphy;
|
|
struct ieee80211_supported_band *sband;
|
|
enum ieee80211_band band;
|
|
int i;
|
|
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
|
|
if (wiphy->bands[band]) {
|
|
sband = wiphy->bands[band];
|
|
for (i = 0; i < sband->n_channels; i++)
|
|
_reg_process_chan(sband, i);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static const char *_reg_find_iso_name(u16 reg_code,
|
|
bool isCountry,
|
|
bool reg2Ctry)
|
|
{
|
|
const struct reg_code_to_isoname *name;
|
|
|
|
if (isCountry)
|
|
reg2Ctry = false;
|
|
|
|
if (isCountry)
|
|
name = &ath6kl_country_code_to_iso_name[0];
|
|
else
|
|
name = &ath6kl_region_code_to_name[0];
|
|
while (name) {
|
|
if (name->reg_code == NULL_REG_CODE)
|
|
break;
|
|
|
|
if ((name->reg_code == reg_code) &&
|
|
(name->iso_name)) {
|
|
if (reg2Ctry)
|
|
return name->reg_to_ctry_iso_name;
|
|
else
|
|
return name->iso_name;
|
|
}
|
|
|
|
name++;
|
|
}
|
|
|
|
ath6kl_err("reg iosname search fail! code is 0x%x\n", reg_code);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct ieee80211_regdomain *_reg_find_regd(u16 code,
|
|
bool isCountry)
|
|
{
|
|
struct ieee80211_regdomain *regd;
|
|
struct ieee80211_regdomain **reg_regdb;
|
|
const char *alpha2 = _reg_find_iso_name(code, isCountry, false);
|
|
|
|
if (alpha2 == NULL)
|
|
return NULL;
|
|
|
|
if (isCountry)
|
|
reg_regdb = (struct ieee80211_regdomain **)
|
|
ath6kl_reg_regdb_country;
|
|
else
|
|
reg_regdb = (struct ieee80211_regdomain **)
|
|
ath6kl_reg_regdb_region;
|
|
|
|
while (*reg_regdb) {
|
|
regd = *reg_regdb;
|
|
if ((regd->alpha2[0] == alpha2[0]) &&
|
|
(regd->alpha2[1] == alpha2[1]))
|
|
return regd;
|
|
|
|
reg_regdb++;
|
|
}
|
|
|
|
ath6kl_err("reg regd search fail! code is 0x%x\n", code);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct ieee80211_regdomain *ath6kl_reg_get_regd(struct reg_info *reg,
|
|
u32 reg_code)
|
|
{
|
|
struct ieee80211_regdomain *regd = NULL;
|
|
u16 code, code_type;
|
|
|
|
/*
|
|
* If it's a country code and search country code list first,
|
|
* then region domain list. And, assume it must be a valid code.
|
|
* If return is NULL and back to use the default's.
|
|
*/
|
|
code = (u16)(reg_code & ATH6KL_REG_CODE_MASK);
|
|
code_type = (u16)(reg_code >> ATH6KL_COUNTRY_RD_SHIFT);
|
|
if (code_type & ATH6KL_COUNTRY_ERD_FLAG)
|
|
regd = _reg_find_regd(code, true);
|
|
else
|
|
regd = _reg_find_regd(code, false);
|
|
|
|
if (regd == NULL) {
|
|
regd = &ath6kl_regd_NA;
|
|
|
|
ath6kl_err("reg get regd fail, using default NA.\n");
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg code 0x%0x%s, %sCode, WWR %s --> %c%c, %d rules\n",
|
|
reg_code,
|
|
(reg ? "" : " from user"),
|
|
(code_type & ATH6KL_COUNTRY_ERD_FLAG) ?
|
|
"Country" : "Region",
|
|
(code_type & ATH6KL_WORLDWIDE_ROAMING_FLAG) ?
|
|
"ON" : "OFF",
|
|
regd->alpha2[0], regd->alpha2[1],
|
|
regd->n_reg_rules);
|
|
|
|
return regd;
|
|
}
|
|
|
|
static int __reg_freq_reg_info_regd(u32 center_freq,
|
|
u32 desired_bw_khz,
|
|
const struct ieee80211_reg_rule **reg_rule,
|
|
const struct ieee80211_regdomain *custom_regd,
|
|
bool *is_freq_start,
|
|
bool *is_freq_end)
|
|
{
|
|
#define ONE_GHZ_IN_KHZ 1000000
|
|
int i;
|
|
bool band_rule_found = false;
|
|
bool bw_fits = false;
|
|
u32 start_freq_khz, end_freq_khz;
|
|
|
|
if (!desired_bw_khz)
|
|
desired_bw_khz = MHZ_TO_KHZ(20);
|
|
|
|
if (!custom_regd)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < custom_regd->n_reg_rules; i++) {
|
|
const struct ieee80211_reg_rule *rr;
|
|
const struct ieee80211_freq_range *fr = NULL;
|
|
|
|
rr = &custom_regd->reg_rules[i];
|
|
fr = &rr->freq_range;
|
|
|
|
if (!band_rule_found) {
|
|
if (abs(center_freq - fr->start_freq_khz) <=
|
|
(2 * ONE_GHZ_IN_KHZ))
|
|
band_rule_found = true;
|
|
if (abs(center_freq - fr->end_freq_khz) <=
|
|
(2 * ONE_GHZ_IN_KHZ))
|
|
band_rule_found = true;
|
|
}
|
|
|
|
start_freq_khz = center_freq - (desired_bw_khz/2);
|
|
end_freq_khz = center_freq + (desired_bw_khz/2);
|
|
|
|
if (band_rule_found &&
|
|
fr->start_freq_khz > MHZ_TO_KHZ(4000) &&
|
|
start_freq_khz >= fr->start_freq_khz &&
|
|
end_freq_khz <= fr->end_freq_khz &&
|
|
((center_freq - fr->start_freq_khz) % MHZ_TO_KHZ(20)) == 0)
|
|
return -ERANGE;
|
|
|
|
if (start_freq_khz >= fr->start_freq_khz &&
|
|
end_freq_khz <= fr->end_freq_khz)
|
|
bw_fits = true;
|
|
else
|
|
bw_fits = false;
|
|
|
|
if (band_rule_found && bw_fits) {
|
|
if (center_freq > MHZ_TO_KHZ(4000)) {
|
|
if (start_freq_khz == fr->start_freq_khz)
|
|
*is_freq_start = true;
|
|
if (end_freq_khz == fr->end_freq_khz)
|
|
*is_freq_end = true;
|
|
}
|
|
*reg_rule = rr;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!band_rule_found)
|
|
return -ERANGE;
|
|
|
|
return -EINVAL;
|
|
#undef ONE_GHZ_IN_KHZ
|
|
}
|
|
|
|
static void _reg_handle_channel(struct ieee80211_channel *chan,
|
|
const struct ieee80211_regdomain *regd)
|
|
{
|
|
int r;
|
|
u32 desired_bw_khz = MHZ_TO_KHZ(20);
|
|
u32 bw_flags = 0;
|
|
u32 channel_flags = 0;
|
|
const struct ieee80211_reg_rule *reg_rule = NULL;
|
|
const struct ieee80211_power_rule *power_rule = NULL;
|
|
const struct ieee80211_freq_range *freq_range = NULL;
|
|
bool is_freq_start = false, is_freq_end = false;
|
|
|
|
r = __reg_freq_reg_info_regd(MHZ_TO_KHZ(chan->center_freq),
|
|
desired_bw_khz,
|
|
®_rule,
|
|
regd,
|
|
&is_freq_start,
|
|
&is_freq_end);
|
|
|
|
if (r) {
|
|
chan->flags =
|
|
chan->orig_flags = IEEE80211_CHAN_DISABLED;
|
|
return;
|
|
}
|
|
|
|
power_rule = ®_rule->power_rule;
|
|
freq_range = ®_rule->freq_range;
|
|
|
|
if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
|
|
bw_flags = IEEE80211_CHAN_NO_HT40;
|
|
else {
|
|
/*
|
|
* A hint to let _reg_process_chan() know that this channel
|
|
* is a boundry channel and HT40+/HT40- already known.
|
|
*/
|
|
if (is_freq_start)
|
|
bw_flags |= IEEE80211_CHAN_NO_HT40MINUS;
|
|
|
|
if (is_freq_end)
|
|
bw_flags |= IEEE80211_CHAN_NO_HT40PLUS;
|
|
}
|
|
|
|
if (reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)
|
|
channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN;
|
|
if (reg_rule->flags & NL80211_RRF_NO_IBSS)
|
|
channel_flags |= IEEE80211_CHAN_NO_IBSS;
|
|
if (reg_rule->flags & NL80211_RRF_DFS)
|
|
channel_flags |= IEEE80211_CHAN_RADAR;
|
|
|
|
chan->flags = (channel_flags | bw_flags);
|
|
chan->orig_flags = chan->flags;
|
|
chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
|
|
chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
|
|
|
|
return;
|
|
}
|
|
|
|
static void ath6kl_reg_apply_regulatory(struct reg_info *reg,
|
|
const struct ieee80211_regdomain *regd)
|
|
{
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_channel *chan;
|
|
enum ieee80211_band band;
|
|
int i;
|
|
|
|
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
|
|
if (!reg->wiphy->bands[band])
|
|
continue;
|
|
|
|
sband = reg->wiphy->bands[band];
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
chan = &sband->channels[i];
|
|
_reg_handle_channel(chan, regd);
|
|
|
|
/*
|
|
* If this channel is P2P allowed and not marked
|
|
* as PASSIVE/IBSS to let wpa_supplicant use it.
|
|
*/
|
|
if ((reg->flags & ATH6KL_REG_FALGS_P2P_IN_PASV_CHAN) &&
|
|
(!(chan->flags & IEEE80211_CHAN_RADAR)) &&
|
|
ath6kl_p2p_is_p2p_channel(chan->center_freq)) {
|
|
chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN;
|
|
chan->flags &= ~IEEE80211_CHAN_NO_IBSS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void _reg_update_report(struct wiphy *wiphy, const char *alpha2)
|
|
{
|
|
char remap_alpha2[2];
|
|
static u32 flip;
|
|
|
|
#ifdef CONFIG_ATH6KL_REGDB_AS_CFG80211_REGDB
|
|
/*
|
|
* If cfg80211-internal-regdb uses our regdb.c and just pass
|
|
* our alpha2 code to cfg80211.
|
|
*/
|
|
remap_alpha2[0] = alpha2[0];
|
|
remap_alpha2[1] = alpha2[1];
|
|
#else
|
|
/*
|
|
* HACK: Here just kick the different alpha2 to force to trigger
|
|
* regdb update to user.
|
|
*/
|
|
if (flip++ & 0x1) {
|
|
remap_alpha2[0] = 'U';
|
|
remap_alpha2[1] = 'S';
|
|
} else {
|
|
remap_alpha2[0] = '0';
|
|
remap_alpha2[1] = '0';
|
|
}
|
|
#endif
|
|
|
|
/* Update to cfg80211 or CRDA */
|
|
regulatory_hint(wiphy, remap_alpha2);
|
|
|
|
return;
|
|
}
|
|
|
|
static struct ieee80211_regdomain *ath6kl_reg_update(struct reg_info *reg,
|
|
u32 reg_code,
|
|
bool target_update)
|
|
{
|
|
struct ieee80211_regdomain *regd;
|
|
|
|
BUG_ON(!reg);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg update reg_code %x %sfrom target\n",
|
|
reg_code,
|
|
target_update ? "" : "not ");
|
|
|
|
|
|
/* Query the local regulatory database from alpha2 words. */
|
|
regd = ath6kl_reg_get_regd(reg, reg_code);
|
|
|
|
/* HACK: Fetch local regulatory database to support channel list. */
|
|
ath6kl_reg_apply_regulatory(reg, regd);
|
|
|
|
/* Update the channel flags. */
|
|
ath6kl_reg_chan_flags_update(reg);
|
|
|
|
if (target_update && regd)
|
|
_reg_update_report(reg->wiphy, regd->alpha2);
|
|
|
|
/* Notify to update the channel record. */
|
|
ath6kl_p2p_rc_fetch_chan(reg->ar);
|
|
|
|
return regd;
|
|
}
|
|
|
|
static void ath6kl_reg_cfg80211_update(struct reg_info *reg,
|
|
u32 reg_code,
|
|
bool target_update)
|
|
{
|
|
u16 code;
|
|
const char *iso_name;
|
|
char alpha2[2];
|
|
|
|
BUG_ON(!reg);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg cfg80211_update reg_code %x %sfrom target\n",
|
|
reg_code,
|
|
target_update ? "" : "not ");
|
|
|
|
code = (u16)(reg_code & ATH6KL_REG_CODE_MASK);
|
|
if ((reg_code >> ATH6KL_COUNTRY_RD_SHIFT) &
|
|
ATH6KL_COUNTRY_ERD_FLAG)
|
|
iso_name = _reg_find_iso_name(reg_code, true, false);
|
|
else
|
|
iso_name = _reg_find_iso_name(reg_code, false, true);
|
|
|
|
if (iso_name) {
|
|
alpha2[0] = iso_name[0];
|
|
alpha2[1] = iso_name[1];
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"Alpha2 string being used: %c%c\n",
|
|
alpha2[0], alpha2[1]);
|
|
|
|
/* Update to cfg80211 & CRDA */
|
|
regulatory_hint(reg->wiphy, alpha2);
|
|
}
|
|
|
|
/* Notify to update the channel record. */
|
|
ath6kl_p2p_rc_fetch_chan(reg->ar);
|
|
|
|
return;
|
|
}
|
|
|
|
static int _reg_cfg80211_notify(struct wiphy *wiphy,
|
|
struct regulatory_request *request)
|
|
{
|
|
char initiatorString[4][16] = {
|
|
"driver",
|
|
"core",
|
|
"user",
|
|
"country-ie",
|
|
};
|
|
struct ath6kl *ar = wiphy_priv(wiphy);
|
|
int ret = 0;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"cfg cfg80211_notify %c%c%s%s initiator %s\n",
|
|
request->alpha2[0], request->alpha2[1],
|
|
request->intersect ? " intersect" : "",
|
|
request->processed ? " processed" : "",
|
|
initiatorString[request->initiator]);
|
|
|
|
ret = ath6kl_reg_set_country(ar, request->alpha2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _reg_notifier(struct wiphy *wiphy,
|
|
struct regulatory_request *request)
|
|
{
|
|
char initiatorString[4][16] = {
|
|
"driver",
|
|
"core",
|
|
"user",
|
|
"country-ie",
|
|
};
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct reg_info *reg = ar->reg_ctx;
|
|
struct ieee80211_regdomain *regd = NULL;
|
|
|
|
switch (request->initiator) {
|
|
case NL80211_REGDOM_SET_BY_DRIVER:
|
|
case NL80211_REGDOM_SET_BY_CORE:
|
|
case NL80211_REGDOM_SET_BY_USER:
|
|
case NL80211_REGDOM_SET_BY_COUNTRY_IE:
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg notify by %s %c%c\n",
|
|
initiatorString[request->initiator],
|
|
request->alpha2[0],
|
|
request->alpha2[1]);
|
|
|
|
/* HACK: always use ours */
|
|
regd = ath6kl_reg_update(reg, reg->current_reg_code, false);
|
|
if (regd != reg->current_regd) {
|
|
ath6kl_err("reg notifier fail, %x %p %p\n",
|
|
reg->current_reg_code,
|
|
reg->current_regd,
|
|
regd);
|
|
WARN_ON(1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ath6kl_reg_notifier(struct wiphy *wiphy,
|
|
struct regulatory_request *request)
|
|
{
|
|
struct ath6kl *ar = (struct ath6kl *)wiphy_priv(wiphy);
|
|
struct reg_info *reg = ar->reg_ctx;
|
|
|
|
if (reg == NULL)
|
|
return 0;
|
|
|
|
if (reg->flags & ATH6KL_REG_FALGS_INTERNAL_REGDB)
|
|
_reg_notifier(wiphy, request);
|
|
else if (reg->flags & ATH6KL_REG_FALGS_CFG80211_REGDB)
|
|
_reg_cfg80211_notify(wiphy, request);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ath6kl_reg_notifier2(struct wiphy *wiphy,
|
|
struct regulatory_request *request)
|
|
{
|
|
ath6kl_reg_notifier(wiphy, request);
|
|
|
|
return;
|
|
}
|
|
|
|
int ath6kl_reg_target_notify(struct ath6kl *ar, u32 reg_code)
|
|
{
|
|
struct reg_info *reg = ar->reg_ctx;
|
|
struct ieee80211_regdomain *regd = NULL;
|
|
|
|
BUG_ON(!reg);
|
|
|
|
if ((reg_code & ATH6KL_REG_CODE_MASK) == NULL_REG_CODE) {
|
|
ath6kl_err("reg unknown code 0x%x, ignore it\n", reg_code);
|
|
|
|
/* Looks set country was rejected by the target. */
|
|
if (((reg->flags & ATH6KL_REG_FALGS_INTERNAL_REGDB) ||
|
|
(reg->flags & ATH6KL_REG_FALGS_CFG80211_REGDB)) &&
|
|
test_bit(REG_COUNTRY_UPDATE, &ar->flag)) {
|
|
clear_bit(REG_COUNTRY_UPDATE, &ar->flag);
|
|
wake_up(&ar->event_wq);
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (reg->flags & ATH6KL_REG_FALGS_INTERNAL_REGDB)
|
|
regd = ath6kl_reg_update(reg, reg_code, true);
|
|
else if (reg->flags & ATH6KL_REG_FALGS_CFG80211_REGDB)
|
|
ath6kl_reg_cfg80211_update(reg, reg_code, true);
|
|
else {
|
|
u16 code;
|
|
const char *iso_name;
|
|
char alpha2[2];
|
|
|
|
code = (u16)(reg_code & ATH6KL_REG_CODE_MASK);
|
|
if ((reg_code >> ATH6KL_COUNTRY_RD_SHIFT) &
|
|
ATH6KL_COUNTRY_ERD_FLAG)
|
|
iso_name = _reg_find_iso_name(reg_code, true, false);
|
|
else
|
|
iso_name = _reg_find_iso_name(reg_code, false, true);
|
|
|
|
if (iso_name) {
|
|
alpha2[0] = iso_name[0];
|
|
alpha2[1] = iso_name[1];
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"Country alpha2 being used: %c%c\n",
|
|
alpha2[0], alpha2[1]);
|
|
|
|
/* Update to cfg80211 & CRDA */
|
|
regulatory_hint(ar->wiphy, alpha2);
|
|
}
|
|
}
|
|
|
|
/* Cache the latest regulatory */
|
|
reg->current_regd = regd;
|
|
reg->current_reg_code = reg_code;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ath6kl_reg_is_init_done(struct ath6kl *ar)
|
|
{
|
|
struct reg_info *reg = ar->reg_ctx;
|
|
|
|
if (!((reg->current_reg_code >> ATH6KL_COUNTRY_RD_SHIFT) &
|
|
ATH6KL_WORLDWIDE_ROAMING_FLAG))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ath6kl_reg_is_p2p_channel(struct ath6kl *ar, u32 freq)
|
|
{
|
|
#define _REG_CHAN_P2P_NOT_ALLOWED ( \
|
|
IEEE80211_CHAN_RADAR | \
|
|
IEEE80211_CHAN_PASSIVE_SCAN | \
|
|
IEEE80211_CHAN_NO_IBSS)
|
|
if (ath6kl_p2p_is_p2p_channel(freq)) {
|
|
struct wiphy *wiphy = ar->wiphy;
|
|
enum ieee80211_band band = NL80211_BAND_2GHZ;
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_channel *chan;
|
|
int i;
|
|
|
|
if (freq > 4000)
|
|
band = NL80211_BAND_5GHZ;
|
|
|
|
sband = wiphy->bands[band];
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
chan = &sband->channels[i];
|
|
if (chan->center_freq == freq) {
|
|
if (chan->flags & _REG_CHAN_P2P_NOT_ALLOWED)
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else
|
|
return false;
|
|
#undef _REG_CHAN_P2P_NOT_ALLOWED
|
|
}
|
|
|
|
bool ath6kl_reg_is_dfs_channel(struct ath6kl *ar, u32 freq)
|
|
{
|
|
struct wiphy *wiphy = ar->wiphy;
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_channel *chan;
|
|
int i;
|
|
|
|
if (freq < 4000)
|
|
return false;
|
|
|
|
sband = wiphy->bands[NL80211_BAND_5GHZ];
|
|
for (i = 0; i < sband->n_channels; i++) {
|
|
chan = &sband->channels[i];
|
|
if (chan->center_freq == freq) {
|
|
if (chan->flags & IEEE80211_CHAN_RADAR)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ath6kl_reg_is_lte_channel(struct ath6kl *ar, u32 freq)
|
|
{
|
|
#define _LTE_BAND7_START_FREQ (2457) /* Not used after Ch10 */
|
|
|
|
/*
|
|
* FDD LTE band 7 uplink (2500Mhz ~ 2570MHz)
|
|
* TDD LTE band 40 (2300Mhz ~ 2400MHz)
|
|
*/
|
|
|
|
if (freq >= _LTE_BAND7_START_FREQ)
|
|
return true;
|
|
else
|
|
return false;
|
|
#undef _LTE_BAND7_START_FREQ
|
|
}
|
|
|
|
struct reg_info *ath6kl_reg_init(struct ath6kl *ar,
|
|
bool intRegdb,
|
|
bool cfgRegdb,
|
|
bool p2pInPasvCh)
|
|
{
|
|
struct reg_info *reg;
|
|
|
|
reg = kzalloc(sizeof(struct reg_info), GFP_KERNEL);
|
|
if (!reg) {
|
|
ath6kl_err("failed to alloc memory for reg\n");
|
|
return NULL;
|
|
}
|
|
|
|
BUG_ON((intRegdb && cfgRegdb));
|
|
|
|
reg->ar = ar;
|
|
reg->wiphy = ar->wiphy;
|
|
if (intRegdb) {
|
|
reg->flags |= ATH6KL_REG_FALGS_INTERNAL_REGDB;
|
|
if (p2pInPasvCh)
|
|
reg->flags |= ATH6KL_REG_FALGS_P2P_IN_PASV_CHAN;
|
|
|
|
ath6kl_info("Using driver's regdb%s.\n",
|
|
(p2pInPasvCh ? " & p2p-in-passive-chan" : ""));
|
|
} else if (cfgRegdb) {
|
|
reg->flags |= ATH6KL_REG_FALGS_CFG80211_REGDB;
|
|
|
|
ath6kl_info("Using cfg80211's regdb.\n");
|
|
}
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg init flags %x\n",
|
|
reg->flags);
|
|
|
|
return reg;
|
|
}
|
|
|
|
void ath6kl_reg_deinit(struct ath6kl *ar)
|
|
{
|
|
kfree(ar->reg_ctx);
|
|
ar->reg_ctx = NULL;
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg deinit");
|
|
|
|
return;
|
|
}
|
|
|
|
static bool _reg_dump_country_ie(u8 *ies, int ies_len, u8 *alpha2)
|
|
{
|
|
u8 *pos;
|
|
bool found = false;
|
|
|
|
pos = ies;
|
|
if (ies && ies_len) {
|
|
while (pos + 1 < ies + ies_len) {
|
|
if (pos + 2 + pos[1] > ies + ies_len)
|
|
break;
|
|
if (pos[0] == WLAN_EID_COUNTRY) {
|
|
found = true;
|
|
memcpy(alpha2, (pos + 2), 2);
|
|
break;
|
|
}
|
|
pos += 2 + pos[1];
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
void ath6kl_reg_bss_info(struct ath6kl *ar,
|
|
struct ieee80211_mgmt *mgmt,
|
|
int len,
|
|
u8 snr,
|
|
struct ieee80211_channel *channel)
|
|
{
|
|
if (!ath6kl_reg_is_init_done(ar)) {
|
|
char alpha[2];
|
|
bool found = false;
|
|
|
|
if ((mgmt->frame_control == IEEE80211_STYPE_BEACON) ||
|
|
(mgmt->frame_control == IEEE80211_STYPE_PROBE_RESP))
|
|
found = _reg_dump_country_ie(mgmt->u.beacon.variable,
|
|
(len - 36),
|
|
(u8 *)alpha);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg bssinfo BSSID %02x:%02x:%02x:%02x:%02x:%02x "
|
|
"fc %02x freq %d snr %3d %c%c\n",
|
|
mgmt->bssid[0], mgmt->bssid[1], mgmt->bssid[2],
|
|
mgmt->bssid[3], mgmt->bssid[4], mgmt->bssid[5],
|
|
mgmt->frame_control,
|
|
(channel ? channel->center_freq : 0),
|
|
snr,
|
|
(found ? alpha[0] : ' '),
|
|
(found ? alpha[1] : ' '));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void _reg_set_country(struct ath6kl *ar)
|
|
{
|
|
struct ath6kl_vif *vif;
|
|
/* These 11 channels are always active channel */
|
|
u16 ch_list[11] = {2412, 2417, 2422, 2427,
|
|
2432, 2437, 2442, 2447,
|
|
2452, 2457, 2462};
|
|
bool scan_on_going = false;
|
|
int i;
|
|
|
|
/* Any scan on-going? */
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if (vif && vif->scan_req)
|
|
scan_on_going = true;
|
|
}
|
|
|
|
/* Start a quick scan to kick it works */
|
|
if (scan_on_going == false)
|
|
ath6kl_wmi_startscan_cmd(ar->wmi,
|
|
0, WMI_LONG_SCAN,
|
|
true, false, 0, 0,
|
|
11,
|
|
ch_list);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg set country done, scan_on_going %d\n",
|
|
scan_on_going);
|
|
|
|
return;
|
|
}
|
|
|
|
int ath6kl_reg_set_country(struct ath6kl *ar, char *isoName)
|
|
{
|
|
#define WAIT_REG_RESULT (HZ / 5) /* 200 ms. */
|
|
struct ath6kl_vif *vif;
|
|
struct reg_info *reg = ar->reg_ctx;
|
|
long left;
|
|
int i;
|
|
|
|
BUG_ON(!reg);
|
|
|
|
if (!(reg->flags & (ATH6KL_REG_FALGS_INTERNAL_REGDB |
|
|
ATH6KL_REG_FALGS_CFG80211_REGDB)))
|
|
return -EPERM;
|
|
|
|
for (i = 0; i < ar->vif_max; i++) {
|
|
vif = ath6kl_get_vif_by_index(ar, i);
|
|
if ((vif) &&
|
|
(test_bit(CONNECTED, &vif->flags) ||
|
|
test_bit(CONNECT_PEND, &vif->flags))) {
|
|
ath6kl_err("reg not allow to change now\n");
|
|
|
|
return -EPERM;
|
|
}
|
|
}
|
|
|
|
if (down_interruptible(&ar->sem))
|
|
return -EBUSY;
|
|
|
|
set_bit(REG_COUNTRY_UPDATE, &ar->flag);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg set country %c%c\n",
|
|
isoName[0], isoName[1]);
|
|
|
|
if (ath6kl_wmi_set_regdomain_cmd(ar->wmi, isoName)) {
|
|
clear_bit(REG_COUNTRY_UPDATE, &ar->flag);
|
|
up(&ar->sem);
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
left = wait_event_interruptible_timeout(ar->event_wq,
|
|
!test_bit(REG_COUNTRY_UPDATE,
|
|
&ar->flag),
|
|
WAIT_REG_RESULT);
|
|
up(&ar->sem);
|
|
|
|
if (test_bit(REG_COUNTRY_UPDATE, &ar->flag)) {
|
|
clear_bit(REG_COUNTRY_UPDATE, &ar->flag);
|
|
_reg_set_country(ar);
|
|
} else {
|
|
ath6kl_err("reg set country failed\n");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
#undef WAIT_REG_RESULT
|
|
}
|
|
|
|
#define AR6004_BOARD_DATA_ADDR 0x00400854
|
|
#define AR6004_BOARD_DATA_OFFSET 4
|
|
#define AR6004_RD_OFFSET 20
|
|
|
|
#define AR6006_BOARD_DATA_ADDR 0x00428854
|
|
#define AR6006_BOARD_DATA_OFFSET 4
|
|
#define AR6006_RD_OFFSET 20
|
|
|
|
static void _reg_remap_rdcode(unsigned short *rdcode_used)
|
|
{
|
|
unsigned short country_code_ori, country_code_remap;
|
|
|
|
/*
|
|
* If the customer want to change the channel-set
|
|
* for some country and target channel-set already
|
|
* exist in certain country and here provide a possible
|
|
* recode re-map to avoid firmware patch.
|
|
*
|
|
* WARN: You moust know what you want to do when insert
|
|
* any code to this function.
|
|
*/
|
|
|
|
country_code_ori = *rdcode_used;
|
|
|
|
/* Only support Country-Code w/o WWR remap */
|
|
if ((country_code_ori & ATH6KL_COUNTRY_ERD_FLAG) &&
|
|
!(country_code_ori & ATH6KL_WORLDWIDE_ROAMING_FLAG)) {
|
|
country_code_ori &= ~(ATH6KL_COUNTRY_ERD_FLAG |
|
|
ATH6KL_WORLDWIDE_ROAMING_FLAG);
|
|
country_code_remap = country_code_ori;
|
|
|
|
/* TODO : remap country code here */
|
|
if (country_code_ori == COUNTRY_CHINA) {
|
|
/* China allow 5150-5350 after 2013/01/31. */
|
|
country_code_remap = COUNTRY_INDIA;
|
|
}
|
|
|
|
if (country_code_ori != country_code_remap) {
|
|
*rdcode_used = (ATH6KL_COUNTRY_ERD_FLAG |
|
|
country_code_remap);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg remap from 0x%x to 0x%x, use 0x%x\n",
|
|
country_code_ori,
|
|
country_code_remap,
|
|
*rdcode_used);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
int ath6kl_reg_set_rdcode(struct ath6kl *ar,
|
|
unsigned short rdcode,
|
|
unsigned short *rdcode_used)
|
|
{
|
|
u8 buf[32];
|
|
u16 o_sum, o_ver, o_rd, o_rd_next;
|
|
u32 n_rd, n_sum;
|
|
u32 bd_addr = 0;
|
|
int ret;
|
|
u32 rd_offset, bd_offset;
|
|
struct ieee80211_regdomain *regd;
|
|
u32 reg_code;
|
|
|
|
if (rdcode == NULL_REG_CODE)
|
|
return -EINVAL;
|
|
|
|
*rdcode_used = NULL_REG_CODE;
|
|
|
|
/* check the rdcode is valid or not */
|
|
reg_code = ((rdcode & 0xf000) << 16) | (rdcode & 0x0fff);
|
|
regd = ath6kl_reg_get_regd(NULL, reg_code);
|
|
if (regd == &ath6kl_regd_NA) {
|
|
ath6kl_err("Non-support code 0x%x, use tgt default code\n",
|
|
rdcode);
|
|
return 0;
|
|
} else {
|
|
/* update final used rdcode */
|
|
*rdcode_used = rdcode;
|
|
|
|
/* Remap the rdcode only if internal-regdb support */
|
|
if ((ar->reg_ctx) &&
|
|
(ar->reg_ctx->flags & ATH6KL_REG_FALGS_INTERNAL_REGDB))
|
|
_reg_remap_rdcode(rdcode_used);
|
|
}
|
|
|
|
switch (ar->target_type) {
|
|
case TARGET_TYPE_AR6004:
|
|
rd_offset = AR6004_RD_OFFSET;
|
|
bd_offset = AR6004_BOARD_DATA_OFFSET;
|
|
ret = ath6kl_bmi_read(ar,
|
|
AR6004_BOARD_DATA_ADDR,
|
|
(u8 *)&bd_addr,
|
|
4);
|
|
break;
|
|
case TARGET_TYPE_AR6006:
|
|
rd_offset = AR6006_RD_OFFSET;
|
|
bd_offset = AR6006_BOARD_DATA_OFFSET;
|
|
ret = ath6kl_bmi_read(ar,
|
|
AR6006_BOARD_DATA_ADDR,
|
|
(u8 *)&bd_addr,
|
|
4);
|
|
break;
|
|
default:
|
|
ath6kl_err("No support rdcode overwrite! target_type %d\n",
|
|
ar->target_type);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
ret = ath6kl_bmi_read(ar, bd_addr, buf, sizeof(buf));
|
|
if (ret)
|
|
return ret;
|
|
|
|
memcpy((u8 *)&o_sum, buf + bd_offset, 2);
|
|
memcpy((u8 *)&o_ver, buf + bd_offset + 2, 2);
|
|
memcpy((u8 *)&o_rd, buf + rd_offset, 2);
|
|
memcpy((u8 *)&o_rd_next, buf + rd_offset + 2, 2);
|
|
|
|
ath6kl_dbg(ATH6KL_DBG_REGDB,
|
|
"reg set rdcode/used 0x%x/0x%x ver 0x%x ori 0x%x-%x\n",
|
|
rdcode,
|
|
(*rdcode_used),
|
|
o_ver,
|
|
o_rd_next,
|
|
o_rd);
|
|
|
|
n_rd = (o_rd_next << 16) + (*rdcode_used);
|
|
ret = ath6kl_bmi_write(ar,
|
|
bd_addr + rd_offset,
|
|
(u8 *)&n_rd,
|
|
4);
|
|
if (ret)
|
|
return ret;
|
|
|
|
n_sum = (o_ver << 16) + (o_sum ^ o_rd ^ (*rdcode_used));
|
|
ret = ath6kl_bmi_write(ar,
|
|
bd_addr + bd_offset,
|
|
(u8 *)&n_sum,
|
|
4);
|
|
|
|
return ret;
|
|
}
|
|
|