MIPS: SB1250: Sort out merge mistake.
[safe/jmp/linux-2.6] / net / wireless / reg.c
index 974b127..5e14371 100644 (file)
@@ -37,7 +37,6 @@
 #include <linux/random.h>
 #include <linux/nl80211.h>
 #include <linux/platform_device.h>
-#include <net/wireless.h>
 #include <net/cfg80211.h>
 #include "core.h"
 #include "reg.h"
@@ -49,12 +48,6 @@ static struct regulatory_request *last_request;
 /* To trigger userspace events */
 static struct platform_device *reg_pdev;
 
-/* Keep the ordering from large to small */
-static u32 supported_bandwidths[] = {
-       MHZ_TO_KHZ(40),
-       MHZ_TO_KHZ(20),
-};
-
 /*
  * Central wireless core regulatory domains, we only need two,
  * the current one and a world regulatory domain in case we have no
@@ -389,6 +382,8 @@ static int call_crda(const char *alpha2)
 /* Used by nl80211 before kmalloc'ing our regulatory domain */
 bool reg_is_valid_request(const char *alpha2)
 {
+       assert_cfg80211_lock();
+
        if (!last_request)
                return false;
 
@@ -436,19 +431,20 @@ static bool is_valid_rd(const struct ieee80211_regdomain *rd)
        return true;
 }
 
-/* Returns value in KHz */
-static u32 freq_max_bandwidth(const struct ieee80211_freq_range *freq_range,
-       u32 freq)
+static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
+                           u32 center_freq_khz,
+                           u32 bw_khz)
 {
-       unsigned int i;
-       for (i = 0; i < ARRAY_SIZE(supported_bandwidths); i++) {
-               u32 start_freq_khz = freq - supported_bandwidths[i]/2;
-               u32 end_freq_khz = freq + supported_bandwidths[i]/2;
-               if (start_freq_khz >= freq_range->start_freq_khz &&
-                       end_freq_khz <= freq_range->end_freq_khz)
-                       return supported_bandwidths[i];
-       }
-       return 0;
+       u32 start_freq_khz, end_freq_khz;
+
+       start_freq_khz = center_freq_khz - (bw_khz/2);
+       end_freq_khz = center_freq_khz + (bw_khz/2);
+
+       if (start_freq_khz >= freq_range->start_freq_khz &&
+           end_freq_khz <= freq_range->end_freq_khz)
+               return true;
+
+       return false;
 }
 
 /**
@@ -848,14 +844,17 @@ static u32 map_regdom_flags(u32 rd_flags)
 
 static int freq_reg_info_regd(struct wiphy *wiphy,
                              u32 center_freq,
-                             u32 *bandwidth,
+                             u32 desired_bw_khz,
                              const struct ieee80211_reg_rule **reg_rule,
                              const struct ieee80211_regdomain *custom_regd)
 {
        int i;
        bool band_rule_found = false;
        const struct ieee80211_regdomain *regd;
-       u32 max_bandwidth = 0;
+       bool bw_fits = false;
+
+       if (!desired_bw_khz)
+               desired_bw_khz = MHZ_TO_KHZ(20);
 
        regd = custom_regd ? custom_regd : cfg80211_regdomain;
 
@@ -888,38 +887,54 @@ static int freq_reg_info_regd(struct wiphy *wiphy,
                if (!band_rule_found)
                        band_rule_found = freq_in_rule_band(fr, center_freq);
 
-               max_bandwidth = freq_max_bandwidth(fr, center_freq);
+               bw_fits = reg_does_bw_fit(fr,
+                                         center_freq,
+                                         desired_bw_khz);
 
-               if (max_bandwidth && *bandwidth <= max_bandwidth) {
+               if (band_rule_found && bw_fits) {
                        *reg_rule = rr;
-                       *bandwidth = max_bandwidth;
-                       break;
+                       return 0;
                }
        }
 
        if (!band_rule_found)
                return -ERANGE;
 
-       return !max_bandwidth;
+       return -EINVAL;
 }
 EXPORT_SYMBOL(freq_reg_info);
 
-int freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 *bandwidth,
-                        const struct ieee80211_reg_rule **reg_rule)
+int freq_reg_info(struct wiphy *wiphy,
+                 u32 center_freq,
+                 u32 desired_bw_khz,
+                 const struct ieee80211_reg_rule **reg_rule)
 {
        assert_cfg80211_lock();
-       return freq_reg_info_regd(wiphy, center_freq,
-               bandwidth, reg_rule, NULL);
+       return freq_reg_info_regd(wiphy,
+                                 center_freq,
+                                 desired_bw_khz,
+                                 reg_rule,
+                                 NULL);
 }
 
+/*
+ * Note that right now we assume the desired channel bandwidth
+ * is always 20 MHz for each individual channel (HT40 uses 20 MHz
+ * per channel, the primary and the extension channel). To support
+ * smaller custom bandwidths such as 5 MHz or 10 MHz we'll need a
+ * new ieee80211_channel.target_bw and re run the regulatory check
+ * 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,
                           unsigned int chan_idx)
 {
        int r;
-       u32 flags;
-       u32 max_bandwidth = 0;
+       u32 flags, bw_flags = 0;
+       u32 desired_bw_khz = MHZ_TO_KHZ(20);
        const struct ieee80211_reg_rule *reg_rule = NULL;
        const struct ieee80211_power_rule *power_rule = NULL;
+       const struct ieee80211_freq_range *freq_range = NULL;
        struct ieee80211_supported_band *sband;
        struct ieee80211_channel *chan;
        struct wiphy *request_wiphy = NULL;
@@ -934,8 +949,10 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,
 
        flags = chan->orig_flags;
 
-       r = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq),
-               &max_bandwidth, &reg_rule);
+       r = freq_reg_info(wiphy,
+                         MHZ_TO_KHZ(chan->center_freq),
+                         desired_bw_khz,
+                         &reg_rule);
 
        if (r) {
                /*
@@ -978,6 +995,10 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,
        }
 
        power_rule = &reg_rule->power_rule;
+       freq_range = &reg_rule->freq_range;
+
+       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
+               bw_flags = IEEE80211_CHAN_NO_HT40;
 
        if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
            request_wiphy && request_wiphy == wiphy &&
@@ -988,19 +1009,19 @@ static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,
                 * settings
                 */
                chan->flags = chan->orig_flags =
-                       map_regdom_flags(reg_rule->flags);
+                       map_regdom_flags(reg_rule->flags) | bw_flags;
                chan->max_antenna_gain = chan->orig_mag =
                        (int) MBI_TO_DBI(power_rule->max_antenna_gain);
-               chan->max_bandwidth = KHZ_TO_MHZ(max_bandwidth);
+               chan->max_bandwidth = KHZ_TO_MHZ(desired_bw_khz);
                chan->max_power = chan->orig_mpwr =
                        (int) MBM_TO_DBM(power_rule->max_eirp);
                return;
        }
 
-       chan->flags = flags | map_regdom_flags(reg_rule->flags);
+       chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
        chan->max_antenna_gain = min(chan->orig_mag,
                (int) MBI_TO_DBI(power_rule->max_antenna_gain));
-       chan->max_bandwidth = KHZ_TO_MHZ(max_bandwidth);
+       chan->max_bandwidth = KHZ_TO_MHZ(desired_bw_khz);
        if (chan->orig_mpwr)
                chan->max_power = min(chan->orig_mpwr,
                        (int) MBM_TO_DBM(power_rule->max_eirp));
@@ -1050,18 +1071,10 @@ static void handle_reg_beacon(struct wiphy *wiphy,
                              unsigned int chan_idx,
                              struct reg_beacon *reg_beacon)
 {
-#ifdef CONFIG_CFG80211_REG_DEBUG
-#define REG_DEBUG_BEACON_FLAG(desc) \
-       printk(KERN_DEBUG "cfg80211: Enabling " desc " on " \
-               "frequency: %d MHz (Ch %d) on %s\n", \
-               reg_beacon->chan.center_freq, \
-               ieee80211_frequency_to_channel(reg_beacon->chan.center_freq), \
-               wiphy_name(wiphy));
-#else
-#define REG_DEBUG_BEACON_FLAG(desc) do {} while (0)
-#endif
        struct ieee80211_supported_band *sband;
        struct ieee80211_channel *chan;
+       bool channel_changed = false;
+       struct ieee80211_channel chan_before;
 
        assert_cfg80211_lock();
 
@@ -1071,18 +1084,28 @@ static void handle_reg_beacon(struct wiphy *wiphy,
        if (likely(chan->center_freq != reg_beacon->chan.center_freq))
                return;
 
-       if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) {
+       if (chan->beacon_found)
+               return;
+
+       chan->beacon_found = true;
+
+       chan_before.center_freq = chan->center_freq;
+       chan_before.flags = chan->flags;
+
+       if ((chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) &&
+           !(chan->orig_flags & IEEE80211_CHAN_PASSIVE_SCAN)) {
                chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN;
-               REG_DEBUG_BEACON_FLAG("active scanning");
+               channel_changed = true;
        }
 
-       if (chan->flags & IEEE80211_CHAN_NO_IBSS) {
+       if ((chan->flags & IEEE80211_CHAN_NO_IBSS) &&
+           !(chan->orig_flags & IEEE80211_CHAN_NO_IBSS)) {
                chan->flags &= ~IEEE80211_CHAN_NO_IBSS;
-               REG_DEBUG_BEACON_FLAG("beaconing");
+               channel_changed = true;
        }
 
-       chan->beacon_found = true;
-#undef REG_DEBUG_BEACON_FLAG
+       if (channel_changed)
+               nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
 }
 
 /*
@@ -1155,6 +1178,93 @@ static void reg_process_beacons(struct wiphy *wiphy)
        wiphy_update_beacon_reg(wiphy);
 }
 
+static bool is_ht40_not_allowed(struct ieee80211_channel *chan)
+{
+       if (!chan)
+               return true;
+       if (chan->flags & IEEE80211_CHAN_DISABLED)
+               return true;
+       /* This would happen when regulatory rules disallow HT40 completely */
+       if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40)))
+               return true;
+       return false;
+}
+
+static void reg_process_ht_flags_channel(struct wiphy *wiphy,
+                                        enum ieee80211_band band,
+                                        unsigned int chan_idx)
+{
+       struct ieee80211_supported_band *sband;
+       struct ieee80211_channel *channel;
+       struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
+       unsigned int i;
+
+       assert_cfg80211_lock();
+
+       sband = wiphy->bands[band];
+       BUG_ON(chan_idx >= sband->n_channels);
+       channel = &sband->channels[chan_idx];
+
+       if (is_ht40_not_allowed(channel)) {
+               channel->flags |= IEEE80211_CHAN_NO_HT40;
+               return;
+       }
+
+       /*
+        * We need to ensure the extension channels exist to
+        * be able to use HT40- or HT40+, this finds them (or not)
+        */
+       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;
+       }
+
+       /*
+        * Please note that this assumes target bandwidth is 20 MHz,
+        * if that ever changes we also need to change the below logic
+        * to include that as well.
+        */
+       if (is_ht40_not_allowed(channel_before))
+               channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
+       else
+               channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
+
+       if (is_ht40_not_allowed(channel_after))
+               channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
+       else
+               channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
+}
+
+static void reg_process_ht_flags_band(struct wiphy *wiphy,
+                                     enum ieee80211_band band)
+{
+       unsigned int i;
+       struct ieee80211_supported_band *sband;
+
+       BUG_ON(!wiphy->bands[band]);
+       sband = wiphy->bands[band];
+
+       for (i = 0; i < sband->n_channels; i++)
+               reg_process_ht_flags_channel(wiphy, band, i);
+}
+
+static void reg_process_ht_flags(struct wiphy *wiphy)
+{
+       enum ieee80211_band band;
+
+       if (!wiphy)
+               return;
+
+       for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+               if (wiphy->bands[band])
+                       reg_process_ht_flags_band(wiphy, band);
+       }
+
+}
+
 void wiphy_update_regulatory(struct wiphy *wiphy,
                             enum nl80211_reg_initiator initiator)
 {
@@ -1168,6 +1278,7 @@ void wiphy_update_regulatory(struct wiphy *wiphy,
        }
 out:
        reg_process_beacons(wiphy);
+       reg_process_ht_flags(wiphy);
        if (wiphy->reg_notifier)
                wiphy->reg_notifier(wiphy, last_request);
 }
@@ -1178,9 +1289,11 @@ static void handle_channel_custom(struct wiphy *wiphy,
                                  const struct ieee80211_regdomain *regd)
 {
        int r;
-       u32 max_bandwidth = 0;
+       u32 desired_bw_khz = MHZ_TO_KHZ(20);
+       u32 bw_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;
        struct ieee80211_supported_band *sband;
        struct ieee80211_channel *chan;
 
@@ -1190,8 +1303,11 @@ static void handle_channel_custom(struct wiphy *wiphy,
        BUG_ON(chan_idx >= sband->n_channels);
        chan = &sband->channels[chan_idx];
 
-       r = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq),
-               &max_bandwidth, &reg_rule, regd);
+       r = freq_reg_info_regd(wiphy,
+                              MHZ_TO_KHZ(chan->center_freq),
+                              desired_bw_khz,
+                              &reg_rule,
+                              regd);
 
        if (r) {
                chan->flags = IEEE80211_CHAN_DISABLED;
@@ -1199,10 +1315,14 @@ static void handle_channel_custom(struct wiphy *wiphy,
        }
 
        power_rule = &reg_rule->power_rule;
+       freq_range = &reg_rule->freq_range;
+
+       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
+               bw_flags = IEEE80211_CHAN_NO_HT40;
 
-       chan->flags |= map_regdom_flags(reg_rule->flags);
+       chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
        chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
-       chan->max_bandwidth = KHZ_TO_MHZ(max_bandwidth);
+       chan->max_bandwidth = KHZ_TO_MHZ(desired_bw_khz);
        chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
 }
 
@@ -1224,13 +1344,22 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
                                   const struct ieee80211_regdomain *regd)
 {
        enum ieee80211_band band;
+       unsigned int bands_set = 0;
 
        mutex_lock(&cfg80211_mutex);
        for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
-               if (wiphy->bands[band])
-                       handle_band_custom(wiphy, band, regd);
+               if (!wiphy->bands[band])
+                       continue;
+               handle_band_custom(wiphy, band, regd);
+               bands_set++;
        }
        mutex_unlock(&cfg80211_mutex);
+
+       /*
+        * no point in calling this if it won't have any effect
+        * on your device's supportd bands.
+        */
+       WARN_ON(!bands_set);
 }
 EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
 
@@ -1436,7 +1565,7 @@ new_request:
        return call_crda(last_request->alpha2);
 }
 
-/* This currently only processes user and driver regulatory hints */
+/* This processes *all* regulatory hints */
 static void reg_process_hint(struct regulatory_request *reg_request)
 {
        int r = 0;
@@ -1551,6 +1680,13 @@ static int regulatory_hint_core(const char *alpha2)
 
        queue_regulatory_request(request);
 
+       /*
+        * This ensures last_request is populated once modules
+        * come swinging in and calling regulatory hints and
+        * wiphy_apply_custom_regulatory().
+        */
+       flush_scheduled_work();
+
        return 0;
 }
 
@@ -1993,7 +2129,12 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
                 * driver wanted to the wiphy to deal with conflicts
                 */
 
-               BUG_ON(request_wiphy->regd);
+               /*
+                * Userspace could have sent two replies with only
+                * one kernel request.
+                */
+               if (request_wiphy->regd)
+                       return -EALREADY;
 
                r = reg_copy_regd(&request_wiphy->regd, rd);
                if (r)
@@ -2035,7 +2176,13 @@ static int __set_regdom(const struct ieee80211_regdomain *rd)
         * the country IE rd with what CRDA believes that country should have
         */
 
-       BUG_ON(!country_ie_regdomain);
+       /*
+        * Userspace could have sent two replies with only
+        * one kernel request. By the second reply we would have
+        * already processed and consumed the country_ie_regdomain.
+        */
+       if (!country_ie_regdomain)
+               return -EALREADY;
        BUG_ON(rd == country_ie_regdomain);
 
        /*
@@ -2112,14 +2259,14 @@ void reg_device_remove(struct wiphy *wiphy)
 
        assert_cfg80211_lock();
 
+       kfree(wiphy->regd);
+
        if (last_request)
                request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
 
-       kfree(wiphy->regd);
-       if (!last_request || !request_wiphy)
-               return;
-       if (request_wiphy != wiphy)
+       if (!request_wiphy || request_wiphy != wiphy)
                return;
+
        last_request->wiphy_idx = WIPHY_IDX_STALE;
        last_request->country_ie_env = ENVIRON_ANY;
 }