| /****************************************************************************** | 
 |  * | 
 |  * Copyright(c) 2009-2010  Realtek Corporation. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify it | 
 |  * under the terms of version 2 of the GNU General Public License as | 
 |  * published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, but WITHOUT | 
 |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
 |  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
 |  * more details. | 
 |  * | 
 |  * You should have received a copy of the GNU General Public License along with | 
 |  * this program; if not, write to the Free Software Foundation, Inc., | 
 |  * 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA | 
 |  * | 
 |  * The full GNU General Public License is included in this distribution in the | 
 |  * file called LICENSE. | 
 |  * | 
 |  * Contact Information: | 
 |  * wlanfae <wlanfae@realtek.com> | 
 |  * Realtek Corporation, No. 2, Innovation Road II, Hsinchu Science Park, | 
 |  * Hsinchu 300, Taiwan. | 
 |  * | 
 |  * Larry Finger <Larry.Finger@lwfinger.net> | 
 |  * | 
 |  *****************************************************************************/ | 
 |  | 
 | #include "wifi.h" | 
 | #include "regd.h" | 
 |  | 
 | static struct country_code_to_enum_rd allCountries[] = { | 
 | 	{COUNTRY_CODE_FCC, "US"}, | 
 | 	{COUNTRY_CODE_IC, "US"}, | 
 | 	{COUNTRY_CODE_ETSI, "EC"}, | 
 | 	{COUNTRY_CODE_SPAIN, "EC"}, | 
 | 	{COUNTRY_CODE_FRANCE, "EC"}, | 
 | 	{COUNTRY_CODE_MKK, "JP"}, | 
 | 	{COUNTRY_CODE_MKK1, "JP"}, | 
 | 	{COUNTRY_CODE_ISRAEL, "EC"}, | 
 | 	{COUNTRY_CODE_TELEC, "JP"}, | 
 | 	{COUNTRY_CODE_MIC, "JP"}, | 
 | 	{COUNTRY_CODE_GLOBAL_DOMAIN, "JP"}, | 
 | 	{COUNTRY_CODE_WORLD_WIDE_13, "EC"}, | 
 | 	{COUNTRY_CODE_TELEC_NETGEAR, "EC"}, | 
 | }; | 
 |  | 
 | /* | 
 |  *Only these channels all allow active | 
 |  *scan on all world regulatory domains | 
 |  */ | 
 | #define RTL819x_2GHZ_CH01_11	\ | 
 | 	REG_RULE(2412-10, 2462+10, 40, 0, 20, 0) | 
 |  | 
 | /* | 
 |  *We enable active scan on these a case | 
 |  *by case basis by regulatory domain | 
 |  */ | 
 | #define RTL819x_2GHZ_CH12_13	\ | 
 | 	REG_RULE(2467-10, 2472+10, 40, 0, 20,\ | 
 | 	NL80211_RRF_PASSIVE_SCAN) | 
 |  | 
 | #define RTL819x_2GHZ_CH14	\ | 
 | 	REG_RULE(2484-10, 2484+10, 40, 0, 20, \ | 
 | 	NL80211_RRF_PASSIVE_SCAN | \ | 
 | 	NL80211_RRF_NO_OFDM) | 
 |  | 
 | /* 5G chan 36 - chan 64*/ | 
 | #define RTL819x_5GHZ_5150_5350	\ | 
 | 	REG_RULE(5150-10, 5350+10, 40, 0, 30, \ | 
 | 	NL80211_RRF_PASSIVE_SCAN | \ | 
 | 	NL80211_RRF_NO_IBSS) | 
 |  | 
 | /* 5G chan 100 - chan 165*/ | 
 | #define RTL819x_5GHZ_5470_5850	\ | 
 | 	REG_RULE(5470-10, 5850+10, 40, 0, 30, \ | 
 | 	NL80211_RRF_PASSIVE_SCAN | \ | 
 | 	NL80211_RRF_NO_IBSS) | 
 |  | 
 | /* 5G chan 149 - chan 165*/ | 
 | #define RTL819x_5GHZ_5725_5850	\ | 
 | 	REG_RULE(5725-10, 5850+10, 40, 0, 30, \ | 
 | 	NL80211_RRF_PASSIVE_SCAN | \ | 
 | 	NL80211_RRF_NO_IBSS) | 
 |  | 
 | #define RTL819x_5GHZ_ALL	\ | 
 | 	(RTL819x_5GHZ_5150_5350, RTL819x_5GHZ_5470_5850) | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_11 = { | 
 | 	.n_reg_rules = 1, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 		      } | 
 | }; | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_12_13 = { | 
 | 	.n_reg_rules = 2, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 			  RTL819x_2GHZ_CH12_13, | 
 | 		      } | 
 | }; | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_no_midband = { | 
 | 	.n_reg_rules = 3, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 			  RTL819x_5GHZ_5150_5350, | 
 | 			  RTL819x_5GHZ_5725_5850, | 
 | 		      } | 
 | }; | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_60_64 = { | 
 | 	.n_reg_rules = 3, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 			  RTL819x_2GHZ_CH12_13, | 
 | 			  RTL819x_5GHZ_5725_5850, | 
 | 		      } | 
 | }; | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_14_60_64 = { | 
 | 	.n_reg_rules = 4, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 			  RTL819x_2GHZ_CH12_13, | 
 | 			  RTL819x_2GHZ_CH14, | 
 | 			  RTL819x_5GHZ_5725_5850, | 
 | 		      } | 
 | }; | 
 |  | 
 | static const struct ieee80211_regdomain rtl_regdom_14 = { | 
 | 	.n_reg_rules = 3, | 
 | 	.alpha2 = "99", | 
 | 	.reg_rules = { | 
 | 		      RTL819x_2GHZ_CH01_11, | 
 | 			  RTL819x_2GHZ_CH12_13, | 
 | 			  RTL819x_2GHZ_CH14, | 
 | 		      } | 
 | }; | 
 |  | 
 | static bool _rtl_is_radar_freq(u16 center_freq) | 
 | { | 
 | 	return (center_freq >= 5260 && center_freq <= 5700); | 
 | } | 
 |  | 
 | static void _rtl_reg_apply_beaconing_flags(struct wiphy *wiphy, | 
 | 					   enum nl80211_reg_initiator initiator) | 
 | { | 
 | 	enum ieee80211_band band; | 
 | 	struct ieee80211_supported_band *sband; | 
 | 	const struct ieee80211_reg_rule *reg_rule; | 
 | 	struct ieee80211_channel *ch; | 
 | 	unsigned int i; | 
 | 	u32 bandwidth = 0; | 
 | 	int r; | 
 |  | 
 | 	for (band = 0; band < IEEE80211_NUM_BANDS; band++) { | 
 |  | 
 | 		if (!wiphy->bands[band]) | 
 | 			continue; | 
 |  | 
 | 		sband = wiphy->bands[band]; | 
 |  | 
 | 		for (i = 0; i < sband->n_channels; i++) { | 
 | 			ch = &sband->channels[i]; | 
 | 			if (_rtl_is_radar_freq(ch->center_freq) || | 
 | 			    (ch->flags & IEEE80211_CHAN_RADAR)) | 
 | 				continue; | 
 | 			if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { | 
 | 				r = freq_reg_info(wiphy, ch->center_freq, | 
 | 						  bandwidth, ®_rule); | 
 | 				if (r) | 
 | 					continue; | 
 |  | 
 | 				/* | 
 | 				 *If 11d had a rule for this channel ensure | 
 | 				 *we enable adhoc/beaconing if it allows us to | 
 | 				 *use it. Note that we would have disabled it | 
 | 				 *by applying our static world regdomain by | 
 | 				 *default during init, prior to calling our | 
 | 				 *regulatory_hint(). | 
 | 				 */ | 
 |  | 
 | 				if (!(reg_rule->flags & NL80211_RRF_NO_IBSS)) | 
 | 					ch->flags &= ~IEEE80211_CHAN_NO_IBSS; | 
 | 				if (!(reg_rule-> | 
 | 				     flags & NL80211_RRF_PASSIVE_SCAN)) | 
 | 					ch->flags &= | 
 | 					    ~IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 			} else { | 
 | 				if (ch->beacon_found) | 
 | 					ch->flags &= ~(IEEE80211_CHAN_NO_IBSS | | 
 | 						  IEEE80211_CHAN_PASSIVE_SCAN); | 
 | 			} | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | /* Allows active scan scan on Ch 12 and 13 */ | 
 | static void _rtl_reg_apply_active_scan_flags(struct wiphy *wiphy, | 
 | 					     enum nl80211_reg_initiator | 
 | 					     initiator) | 
 | { | 
 | 	struct ieee80211_supported_band *sband; | 
 | 	struct ieee80211_channel *ch; | 
 | 	const struct ieee80211_reg_rule *reg_rule; | 
 | 	u32 bandwidth = 0; | 
 | 	int r; | 
 |  | 
 | 	if (!wiphy->bands[IEEE80211_BAND_2GHZ]) | 
 | 		return; | 
 | 	sband = wiphy->bands[IEEE80211_BAND_2GHZ]; | 
 |  | 
 | 	/* | 
 | 	 *If no country IE has been received always enable active scan | 
 | 	 *on these channels. This is only done for specific regulatory SKUs | 
 | 	 */ | 
 | 	if (initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { | 
 | 		ch = &sband->channels[11];	/* CH 12 */ | 
 | 		if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) | 
 | 			ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 		ch = &sband->channels[12];	/* CH 13 */ | 
 | 		if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) | 
 | 			ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 *If a country IE has been received check its rule for this | 
 | 	 *channel first before enabling active scan. The passive scan | 
 | 	 *would have been enforced by the initial processing of our | 
 | 	 *custom regulatory domain. | 
 | 	 */ | 
 |  | 
 | 	ch = &sband->channels[11];	/* CH 12 */ | 
 | 	r = freq_reg_info(wiphy, ch->center_freq, bandwidth, ®_rule); | 
 | 	if (!r) { | 
 | 		if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) | 
 | 			if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) | 
 | 				ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 	} | 
 |  | 
 | 	ch = &sband->channels[12];	/* CH 13 */ | 
 | 	r = freq_reg_info(wiphy, ch->center_freq, bandwidth, ®_rule); | 
 | 	if (!r) { | 
 | 		if (!(reg_rule->flags & NL80211_RRF_PASSIVE_SCAN)) | 
 | 			if (ch->flags & IEEE80211_CHAN_PASSIVE_SCAN) | 
 | 				ch->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  *Always apply Radar/DFS rules on | 
 |  *freq range 5260 MHz - 5700 MHz | 
 |  */ | 
 | static void _rtl_reg_apply_radar_flags(struct wiphy *wiphy) | 
 | { | 
 | 	struct ieee80211_supported_band *sband; | 
 | 	struct ieee80211_channel *ch; | 
 | 	unsigned int i; | 
 |  | 
 | 	if (!wiphy->bands[IEEE80211_BAND_5GHZ]) | 
 | 		return; | 
 |  | 
 | 	sband = wiphy->bands[IEEE80211_BAND_5GHZ]; | 
 |  | 
 | 	for (i = 0; i < sband->n_channels; i++) { | 
 | 		ch = &sband->channels[i]; | 
 | 		if (!_rtl_is_radar_freq(ch->center_freq)) | 
 | 			continue; | 
 |  | 
 | 		/* | 
 | 		 *We always enable radar detection/DFS on this | 
 | 		 *frequency range. Additionally we also apply on | 
 | 		 *this frequency range: | 
 | 		 *- If STA mode does not yet have DFS supports disable | 
 | 		 * active scanning | 
 | 		 *- If adhoc mode does not support DFS yet then disable | 
 | 		 * adhoc in the frequency. | 
 | 		 *- If AP mode does not yet support radar detection/DFS | 
 | 		 *do not allow AP mode | 
 | 		 */ | 
 | 		if (!(ch->flags & IEEE80211_CHAN_DISABLED)) | 
 | 			ch->flags |= IEEE80211_CHAN_RADAR | | 
 | 			    IEEE80211_CHAN_NO_IBSS | | 
 | 			    IEEE80211_CHAN_PASSIVE_SCAN; | 
 | 	} | 
 | } | 
 |  | 
 | static void _rtl_reg_apply_world_flags(struct wiphy *wiphy, | 
 | 				       enum nl80211_reg_initiator initiator, | 
 | 				       struct rtl_regulatory *reg) | 
 | { | 
 | 	_rtl_reg_apply_beaconing_flags(wiphy, initiator); | 
 | 	_rtl_reg_apply_active_scan_flags(wiphy, initiator); | 
 | 	return; | 
 | } | 
 |  | 
 | static int _rtl_reg_notifier_apply(struct wiphy *wiphy, | 
 | 				   struct regulatory_request *request, | 
 | 				   struct rtl_regulatory *reg) | 
 | { | 
 | 	/* We always apply this */ | 
 | 	_rtl_reg_apply_radar_flags(wiphy); | 
 |  | 
 | 	switch (request->initiator) { | 
 | 	case NL80211_REGDOM_SET_BY_DRIVER: | 
 | 	case NL80211_REGDOM_SET_BY_CORE: | 
 | 	case NL80211_REGDOM_SET_BY_USER: | 
 | 		break; | 
 | 	case NL80211_REGDOM_SET_BY_COUNTRY_IE: | 
 | 		_rtl_reg_apply_world_flags(wiphy, request->initiator, reg); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct ieee80211_regdomain *_rtl_regdomain_select( | 
 | 					       struct rtl_regulatory *reg) | 
 | { | 
 | 	switch (reg->country_code) { | 
 | 	case COUNTRY_CODE_FCC: | 
 | 		return &rtl_regdom_no_midband; | 
 | 	case COUNTRY_CODE_IC: | 
 | 		return &rtl_regdom_11; | 
 | 	case COUNTRY_CODE_ETSI: | 
 | 	case COUNTRY_CODE_TELEC_NETGEAR: | 
 | 		return &rtl_regdom_60_64; | 
 | 	case COUNTRY_CODE_SPAIN: | 
 | 	case COUNTRY_CODE_FRANCE: | 
 | 	case COUNTRY_CODE_ISRAEL: | 
 | 	case COUNTRY_CODE_WORLD_WIDE_13: | 
 | 		return &rtl_regdom_12_13; | 
 | 	case COUNTRY_CODE_MKK: | 
 | 	case COUNTRY_CODE_MKK1: | 
 | 	case COUNTRY_CODE_TELEC: | 
 | 	case COUNTRY_CODE_MIC: | 
 | 		return &rtl_regdom_14_60_64; | 
 | 	case COUNTRY_CODE_GLOBAL_DOMAIN: | 
 | 		return &rtl_regdom_14; | 
 | 	default: | 
 | 		return &rtl_regdom_no_midband; | 
 | 	} | 
 | } | 
 |  | 
 | static int _rtl_regd_init_wiphy(struct rtl_regulatory *reg, | 
 | 				struct wiphy *wiphy, | 
 | 				int (*reg_notifier) (struct wiphy *wiphy, | 
 | 						     struct regulatory_request * | 
 | 						     request)) | 
 | { | 
 | 	const struct ieee80211_regdomain *regd; | 
 |  | 
 | 	wiphy->reg_notifier = reg_notifier; | 
 |  | 
 | 	wiphy->flags |= WIPHY_FLAG_CUSTOM_REGULATORY; | 
 | 	wiphy->flags &= ~WIPHY_FLAG_STRICT_REGULATORY; | 
 | 	wiphy->flags &= ~WIPHY_FLAG_DISABLE_BEACON_HINTS; | 
 |  | 
 | 	regd = _rtl_regdomain_select(reg); | 
 | 	wiphy_apply_custom_regulatory(wiphy, regd); | 
 | 	_rtl_reg_apply_radar_flags(wiphy); | 
 | 	_rtl_reg_apply_world_flags(wiphy, NL80211_REGDOM_SET_BY_DRIVER, reg); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct country_code_to_enum_rd *_rtl_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; | 
 | } | 
 |  | 
 | int rtl_regd_init(struct ieee80211_hw *hw, | 
 | 		  int (*reg_notifier) (struct wiphy *wiphy, | 
 | 				       struct regulatory_request *request)) | 
 | { | 
 | 	struct rtl_priv *rtlpriv = rtl_priv(hw); | 
 | 	struct wiphy *wiphy = hw->wiphy; | 
 | 	struct country_code_to_enum_rd *country = NULL; | 
 |  | 
 | 	if (wiphy == NULL || &rtlpriv->regd == NULL) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* init country_code from efuse channel plan */ | 
 | 	rtlpriv->regd.country_code = rtlpriv->efuse.channel_plan; | 
 |  | 
 | 	RT_TRACE(rtlpriv, COMP_REGD, DBG_TRACE, | 
 | 		 (KERN_DEBUG "rtl: EEPROM regdomain: 0x%0x\n", | 
 | 		  rtlpriv->regd.country_code)); | 
 |  | 
 | 	if (rtlpriv->regd.country_code >= COUNTRY_CODE_MAX) { | 
 | 		RT_TRACE(rtlpriv, COMP_REGD, DBG_DMESG, | 
 | 			 (KERN_DEBUG "rtl: EEPROM indicates invalid contry code" | 
 | 			  "world wide 13 should be used\n")); | 
 |  | 
 | 		rtlpriv->regd.country_code = COUNTRY_CODE_WORLD_WIDE_13; | 
 | 	} | 
 |  | 
 | 	country = _rtl_regd_find_country(rtlpriv->regd.country_code); | 
 |  | 
 | 	if (country) { | 
 | 		rtlpriv->regd.alpha2[0] = country->iso_name[0]; | 
 | 		rtlpriv->regd.alpha2[1] = country->iso_name[1]; | 
 | 	} else { | 
 | 		rtlpriv->regd.alpha2[0] = '0'; | 
 | 		rtlpriv->regd.alpha2[1] = '0'; | 
 | 	} | 
 |  | 
 | 	RT_TRACE(rtlpriv, COMP_REGD, DBG_TRACE, | 
 | 		 (KERN_DEBUG "rtl: Country alpha2 being used: %c%c\n", | 
 | 		  rtlpriv->regd.alpha2[0], rtlpriv->regd.alpha2[1])); | 
 |  | 
 | 	_rtl_regd_init_wiphy(&rtlpriv->regd, wiphy, reg_notifier); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int rtl_reg_notifier(struct wiphy *wiphy, struct regulatory_request *request) | 
 | { | 
 | 	struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); | 
 | 	struct rtl_priv *rtlpriv = rtl_priv(hw); | 
 |  | 
 | 	RT_TRACE(rtlpriv, COMP_REGD, DBG_LOUD, ("\n")); | 
 |  | 
 | 	return _rtl_reg_notifier_apply(wiphy, request, &rtlpriv->regd); | 
 | } |