diff options
Diffstat (limited to 'net/wireless/reg.c')
| -rw-r--r-- | net/wireless/reg.c | 214 | 
1 files changed, 173 insertions, 41 deletions
diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 4b9f8912526c..bc14caab19cd 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -48,7 +48,7 @@  #ifdef CONFIG_CFG80211_REG_DEBUG  #define REG_DBG_PRINT(format, args...) \  	do { \ -		printk(KERN_DEBUG format , ## args); \ +		printk(KERN_DEBUG "cfg80211: " format , ## args); \  	} while (0)  #else  #define REG_DBG_PRINT(args...) @@ -96,6 +96,9 @@ struct reg_beacon {  	struct ieee80211_channel chan;  }; +static void reg_todo(struct work_struct *work); +static DECLARE_WORK(reg_work, reg_todo); +  /* We keep a static world regulatory domain in case of the absence of CRDA */  static const struct ieee80211_regdomain world_regdom = {  	.n_reg_rules = 5, @@ -711,6 +714,60 @@ int freq_reg_info(struct wiphy *wiphy,  }  EXPORT_SYMBOL(freq_reg_info); +#ifdef CONFIG_CFG80211_REG_DEBUG +static const char *reg_initiator_name(enum nl80211_reg_initiator initiator) +{ +	switch (initiator) { +	case NL80211_REGDOM_SET_BY_CORE: +		return "Set by core"; +	case NL80211_REGDOM_SET_BY_USER: +		return "Set by user"; +	case NL80211_REGDOM_SET_BY_DRIVER: +		return "Set by driver"; +	case NL80211_REGDOM_SET_BY_COUNTRY_IE: +		return "Set by country IE"; +	default: +		WARN_ON(1); +		return "Set by bug"; +	} +} + +static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, +				    u32 desired_bw_khz, +				    const struct ieee80211_reg_rule *reg_rule) +{ +	const struct ieee80211_power_rule *power_rule; +	const struct ieee80211_freq_range *freq_range; +	char max_antenna_gain[32]; + +	power_rule = ®_rule->power_rule; +	freq_range = ®_rule->freq_range; + +	if (!power_rule->max_antenna_gain) +		snprintf(max_antenna_gain, 32, "N/A"); +	else +		snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain); + +	REG_DBG_PRINT("Updating information on frequency %d MHz " +		      "for %d a MHz width channel with regulatory rule:\n", +		      chan->center_freq, +		      KHZ_TO_MHZ(desired_bw_khz)); + +	REG_DBG_PRINT("%d KHz - %d KHz @  KHz), (%s mBi, %d mBm)\n", +		      freq_range->start_freq_khz, +		      freq_range->end_freq_khz, +		      max_antenna_gain, +		      power_rule->max_eirp); +} +#else +static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, +				    u32 desired_bw_khz, +				    const struct ieee80211_reg_rule *reg_rule) +{ +	return; +} +#endif +  /*   * Note that right now we assume the desired channel bandwidth   * is always 20 MHz for each individual channel (HT40 uses 20 MHz @@ -720,7 +777,9 @@ EXPORT_SYMBOL(freq_reg_info);   * on the wiphy with the target_bw specified. Then we can simply use   * that below for the desired_bw_khz below.   */ -static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band, +static void handle_channel(struct wiphy *wiphy, +			   enum nl80211_reg_initiator initiator, +			   enum ieee80211_band band,  			   unsigned int chan_idx)  {  	int r; @@ -748,8 +807,27 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,  			  desired_bw_khz,  			  ®_rule); -	if (r) +	if (r) { +		/* +		 * We will disable all channels that do not match our +		 * recieved regulatory rule unless the hint is coming +		 * from a Country IE and the Country IE had no information +		 * about a band. The IEEE 802.11 spec allows for an AP +		 * to send only a subset of the regulatory rules allowed, +		 * so an AP in the US that only supports 2.4 GHz may only send +		 * a country IE with information for the 2.4 GHz band +		 * while 5 GHz is still supported. +		 */ +		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && +		    r == -ERANGE) +			return; + +		REG_DBG_PRINT("Disabling freq %d MHz\n", chan->center_freq); +		chan->flags = IEEE80211_CHAN_DISABLED;  		return; +	} + +	chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule);  	power_rule = ®_rule->power_rule;  	freq_range = ®_rule->freq_range; @@ -784,7 +862,9 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,  		chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);  } -static void handle_band(struct wiphy *wiphy, enum ieee80211_band band) +static void handle_band(struct wiphy *wiphy, +			enum ieee80211_band band, +			enum nl80211_reg_initiator initiator)  {  	unsigned int i;  	struct ieee80211_supported_band *sband; @@ -793,24 +873,42 @@ static void handle_band(struct wiphy *wiphy, enum ieee80211_band band)  	sband = wiphy->bands[band];  	for (i = 0; i < sband->n_channels; i++) -		handle_channel(wiphy, band, i); +		handle_channel(wiphy, initiator, band, i);  }  static bool ignore_reg_update(struct wiphy *wiphy,  			      enum nl80211_reg_initiator initiator)  { -	if (!last_request) +	if (!last_request) { +		REG_DBG_PRINT("Ignoring regulatory request %s since " +			      "last_request is not set\n", +			      reg_initiator_name(initiator));  		return true; +	} +  	if (initiator == NL80211_REGDOM_SET_BY_CORE && -	    wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) +	    wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) { +		REG_DBG_PRINT("Ignoring regulatory request %s " +			      "since the driver uses its own custom " +			      "regulatory domain ", +			      reg_initiator_name(initiator));  		return true; +	} +  	/*  	 * wiphy->regd will be set once the device has its own  	 * desired regulatory domain set  	 */  	if (wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY && !wiphy->regd && -	    !is_world_regdom(last_request->alpha2)) +	    initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && +	    !is_world_regdom(last_request->alpha2)) { +		REG_DBG_PRINT("Ignoring regulatory request %s " +			      "since the driver requires its own regulaotry " +			      "domain to be set first", +			      reg_initiator_name(initiator));  		return true; +	} +  	return false;  } @@ -1030,7 +1128,7 @@ void wiphy_update_regulatory(struct wiphy *wiphy,  		goto out;  	for (band = 0; band < IEEE80211_NUM_BANDS; band++) {  		if (wiphy->bands[band]) -			handle_band(wiphy, band); +			handle_band(wiphy, band, initiator);  	}  out:  	reg_process_beacons(wiphy); @@ -1066,10 +1164,17 @@ static void handle_channel_custom(struct wiphy *wiphy,  			       regd);  	if (r) { +		REG_DBG_PRINT("Disabling freq %d MHz as custom " +			      "regd has no rule that fits a %d MHz " +			      "wide channel\n", +			      chan->center_freq, +			      KHZ_TO_MHZ(desired_bw_khz));  		chan->flags = IEEE80211_CHAN_DISABLED;  		return;  	} +	chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); +  	power_rule = ®_rule->power_rule;  	freq_range = ®_rule->freq_range; @@ -1215,6 +1320,21 @@ static int ignore_request(struct wiphy *wiphy,  	return -EINVAL;  } +static void reg_set_request_processed(void) +{ +	bool need_more_processing = false; + +	last_request->processed = true; + +	spin_lock(®_requests_lock); +	if (!list_empty(®_requests_list)) +		need_more_processing = true; +	spin_unlock(®_requests_lock); + +	if (need_more_processing) +		schedule_work(®_work); +} +  /**   * __regulatory_hint - hint to the wireless core a regulatory domain   * @wiphy: if the hint comes from country information from an AP, this @@ -1290,8 +1410,10 @@ new_request:  		 * have applied the requested regulatory domain before we just  		 * inform userspace we have processed the request  		 */ -		if (r == -EALREADY) +		if (r == -EALREADY) {  			nl80211_send_reg_change_event(last_request); +			reg_set_request_processed(); +		}  		return r;  	} @@ -1307,16 +1429,13 @@ static void reg_process_hint(struct regulatory_request *reg_request)  	BUG_ON(!reg_request->alpha2); -	mutex_lock(&cfg80211_mutex); -	mutex_lock(®_mutex); -  	if (wiphy_idx_valid(reg_request->wiphy_idx))  		wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);  	if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&  	    !wiphy) {  		kfree(reg_request); -		goto out; +		return;  	}  	r = __regulatory_hint(wiphy, reg_request); @@ -1324,28 +1443,46 @@ static void reg_process_hint(struct regulatory_request *reg_request)  	if (r == -EALREADY && wiphy &&  	    wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY)  		wiphy_update_regulatory(wiphy, initiator); -out: -	mutex_unlock(®_mutex); -	mutex_unlock(&cfg80211_mutex);  } -/* Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* */ +/* + * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* + * Regulatory hints come on a first come first serve basis and we + * must process each one atomically. + */  static void reg_process_pending_hints(void) -	{ +{  	struct regulatory_request *reg_request; +	mutex_lock(&cfg80211_mutex); +	mutex_lock(®_mutex); + +	/* When last_request->processed becomes true this will be rescheduled */ +	if (last_request && !last_request->processed) { +		REG_DBG_PRINT("Pending regulatory request, waiting " +			      "for it to be processed..."); +		goto out; +	} +  	spin_lock(®_requests_lock); -	while (!list_empty(®_requests_list)) { -		reg_request = list_first_entry(®_requests_list, -					       struct regulatory_request, -					       list); -		list_del_init(®_request->list); +	if (list_empty(®_requests_list)) {  		spin_unlock(®_requests_lock); -		reg_process_hint(reg_request); -		spin_lock(®_requests_lock); +		goto out;  	} + +	reg_request = list_first_entry(®_requests_list, +				       struct regulatory_request, +				       list); +	list_del_init(®_request->list); +  	spin_unlock(®_requests_lock); + +	reg_process_hint(reg_request); + +out: +	mutex_unlock(®_mutex); +	mutex_unlock(&cfg80211_mutex);  }  /* Processes beacon hints -- this has nothing to do with country IEs */ @@ -1392,8 +1529,6 @@ static void reg_todo(struct work_struct *work)  	reg_process_pending_beacon_hints();  } -static DECLARE_WORK(reg_work, reg_todo); -  static void queue_regulatory_request(struct regulatory_request *request)  {  	if (isalpha(request->alpha2[0])) @@ -1428,12 +1563,7 @@ static int regulatory_hint_core(const char *alpha2)  	request->alpha2[1] = alpha2[1];  	request->initiator = NL80211_REGDOM_SET_BY_CORE; -	/* -	 * This ensures last_request is populated once modules -	 * come swinging in and calling regulatory hints and -	 * wiphy_apply_custom_regulatory(). -	 */ -	reg_process_hint(request); +	queue_regulatory_request(request);  	return 0;  } @@ -1559,7 +1689,7 @@ static void restore_alpha2(char *alpha2, bool reset_user)  	if (is_user_regdom_saved()) {  		/* Unless we're asked to ignore it and reset it */  		if (reset_user) { -			REG_DBG_PRINT("cfg80211: Restoring regulatory settings " +			REG_DBG_PRINT("Restoring regulatory settings "  			       "including user preference\n");  			user_alpha2[0] = '9';  			user_alpha2[1] = '7'; @@ -1570,7 +1700,7 @@ static void restore_alpha2(char *alpha2, bool reset_user)  			 * back as they were for a full restore.  			 */  			if (!is_world_regdom(ieee80211_regdom)) { -				REG_DBG_PRINT("cfg80211: Keeping preference on " +				REG_DBG_PRINT("Keeping preference on "  				       "module parameter ieee80211_regdom: %c%c\n",  				       ieee80211_regdom[0],  				       ieee80211_regdom[1]); @@ -1578,7 +1708,7 @@ static void restore_alpha2(char *alpha2, bool reset_user)  				alpha2[1] = ieee80211_regdom[1];  			}  		} else { -			REG_DBG_PRINT("cfg80211: Restoring regulatory settings " +			REG_DBG_PRINT("Restoring regulatory settings "  			       "while preserving user preference for: %c%c\n",  			       user_alpha2[0],  			       user_alpha2[1]); @@ -1586,14 +1716,14 @@ static void restore_alpha2(char *alpha2, bool reset_user)  			alpha2[1] = user_alpha2[1];  		}  	} else if (!is_world_regdom(ieee80211_regdom)) { -		REG_DBG_PRINT("cfg80211: Keeping preference on " +		REG_DBG_PRINT("Keeping preference on "  		       "module parameter ieee80211_regdom: %c%c\n",  		       ieee80211_regdom[0],  		       ieee80211_regdom[1]);  		alpha2[0] = ieee80211_regdom[0];  		alpha2[1] = ieee80211_regdom[1];  	} else -		REG_DBG_PRINT("cfg80211: Restoring regulatory settings\n"); +		REG_DBG_PRINT("Restoring regulatory settings\n");  }  /* @@ -1661,7 +1791,7 @@ static void restore_regulatory_settings(bool reset_user)  void regulatory_hint_disconnect(void)  { -	REG_DBG_PRINT("cfg80211: All devices are disconnected, going to " +	REG_DBG_PRINT("All devices are disconnected, going to "  		      "restore regulatory settings\n");  	restore_regulatory_settings(false);  } @@ -1691,7 +1821,7 @@ int regulatory_hint_found_beacon(struct wiphy *wiphy,  	if (!reg_beacon)  		return -ENOMEM; -	REG_DBG_PRINT("cfg80211: Found new beacon on " +	REG_DBG_PRINT("Found new beacon on "  		      "frequency: %d MHz (Ch %d) on %s\n",  		      beacon_chan->center_freq,  		      ieee80211_frequency_to_channel(beacon_chan->center_freq), @@ -1959,6 +2089,8 @@ int set_regdom(const struct ieee80211_regdomain *rd)  	nl80211_send_reg_change_event(last_request); +	reg_set_request_processed(); +  	mutex_unlock(®_mutex);  	return r;  |