]> Pileus Git - ~andy/linux/blobdiff - net/wireless/chan.c
ipmr/ip6mr: allow to get unresolved cache via netlink
[~andy/linux] / net / wireless / chan.c
index 48febd2160ba488a7b8fccea612de1ae76b2c3fa..bf2dfd54ff3ba6c0b2f673d37f790dca0a4aedce 100644 (file)
 #include "core.h"
 #include "rdev-ops.h"
 
-struct ieee80211_channel *
-rdev_freq_to_chan(struct cfg80211_registered_device *rdev,
-                 int freq, enum nl80211_channel_type channel_type)
+void cfg80211_chandef_create(struct cfg80211_chan_def *chandef,
+                            struct ieee80211_channel *chan,
+                            enum nl80211_channel_type chan_type)
 {
-       struct ieee80211_channel *chan;
-       struct ieee80211_sta_ht_cap *ht_cap;
+       if (WARN_ON(!chan))
+               return;
 
-       chan = ieee80211_get_channel(&rdev->wiphy, freq);
+       chandef->chan = chan;
+       chandef->center_freq2 = 0;
 
-       /* Primary channel not allowed */
-       if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
-               return NULL;
+       switch (chan_type) {
+       case NL80211_CHAN_NO_HT:
+               chandef->width = NL80211_CHAN_WIDTH_20_NOHT;
+               chandef->center_freq1 = chan->center_freq;
+               break;
+       case NL80211_CHAN_HT20:
+               chandef->width = NL80211_CHAN_WIDTH_20;
+               chandef->center_freq1 = chan->center_freq;
+               break;
+       case NL80211_CHAN_HT40PLUS:
+               chandef->width = NL80211_CHAN_WIDTH_40;
+               chandef->center_freq1 = chan->center_freq + 10;
+               break;
+       case NL80211_CHAN_HT40MINUS:
+               chandef->width = NL80211_CHAN_WIDTH_40;
+               chandef->center_freq1 = chan->center_freq - 10;
+               break;
+       default:
+               WARN_ON(1);
+       }
+}
+EXPORT_SYMBOL(cfg80211_chandef_create);
 
-       if (channel_type == NL80211_CHAN_HT40MINUS &&
-           chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
-               return NULL;
-       else if (channel_type == NL80211_CHAN_HT40PLUS &&
-                chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
-               return NULL;
+bool cfg80211_chan_def_valid(const struct cfg80211_chan_def *chandef)
+{
+       u32 control_freq;
 
-       ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
+       if (!chandef->chan)
+               return false;
 
-       if (channel_type != NL80211_CHAN_NO_HT) {
-               if (!ht_cap->ht_supported)
-                       return NULL;
+       control_freq = chandef->chan->center_freq;
 
-               if (channel_type != NL80211_CHAN_HT20 &&
-                   (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
-                   ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
-                       return NULL;
+       switch (chandef->width) {
+       case NL80211_CHAN_WIDTH_20:
+       case NL80211_CHAN_WIDTH_20_NOHT:
+               if (chandef->center_freq1 != control_freq)
+                       return false;
+               if (chandef->center_freq2)
+                       return false;
+               break;
+       case NL80211_CHAN_WIDTH_40:
+               if (chandef->center_freq1 != control_freq + 10 &&
+                   chandef->center_freq1 != control_freq - 10)
+                       return false;
+               if (chandef->center_freq2)
+                       return false;
+               break;
+       case NL80211_CHAN_WIDTH_80P80:
+               if (chandef->center_freq1 != control_freq + 30 &&
+                   chandef->center_freq1 != control_freq + 10 &&
+                   chandef->center_freq1 != control_freq - 10 &&
+                   chandef->center_freq1 != control_freq - 30)
+                       return false;
+               if (!chandef->center_freq2)
+                       return false;
+               break;
+       case NL80211_CHAN_WIDTH_80:
+               if (chandef->center_freq1 != control_freq + 30 &&
+                   chandef->center_freq1 != control_freq + 10 &&
+                   chandef->center_freq1 != control_freq - 10 &&
+                   chandef->center_freq1 != control_freq - 30)
+                       return false;
+               if (chandef->center_freq2)
+                       return false;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               if (chandef->center_freq1 != control_freq + 70 &&
+                   chandef->center_freq1 != control_freq + 50 &&
+                   chandef->center_freq1 != control_freq + 30 &&
+                   chandef->center_freq1 != control_freq + 10 &&
+                   chandef->center_freq1 != control_freq - 10 &&
+                   chandef->center_freq1 != control_freq - 30 &&
+                   chandef->center_freq1 != control_freq - 50 &&
+                   chandef->center_freq1 != control_freq - 70)
+                       return false;
+               if (chandef->center_freq2)
+                       return false;
+               break;
+       default:
+               return false;
        }
 
-       return chan;
+       return true;
 }
 
-bool cfg80211_can_beacon_sec_chan(struct wiphy *wiphy,
-                                 struct ieee80211_channel *chan,
-                                 enum nl80211_channel_type channel_type)
+static void chandef_primary_freqs(const struct cfg80211_chan_def *c,
+                                 int *pri40, int *pri80)
 {
-       struct ieee80211_channel *sec_chan;
-       int diff;
-
-       trace_cfg80211_can_beacon_sec_chan(wiphy, chan, channel_type);
+       int tmp;
 
-       switch (channel_type) {
-       case NL80211_CHAN_HT40PLUS:
-               diff = 20;
+       switch (c->width) {
+       case NL80211_CHAN_WIDTH_40:
+               *pri40 = c->center_freq1;
+               *pri80 = 0;
                break;
-       case NL80211_CHAN_HT40MINUS:
-               diff = -20;
+       case NL80211_CHAN_WIDTH_80:
+       case NL80211_CHAN_WIDTH_80P80:
+               *pri80 = c->center_freq1;
+               /* n_P20 */
+               tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
+               /* n_P40 */
+               tmp /= 2;
+               /* freq_P40 */
+               *pri40 = c->center_freq1 - 20 + 40 * tmp;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               /* n_P20 */
+               tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
+               /* n_P40 */
+               tmp /= 2;
+               /* freq_P40 */
+               *pri40 = c->center_freq1 - 60 + 40 * tmp;
+               /* n_P80 */
+               tmp /= 2;
+               *pri80 = c->center_freq1 - 40 + 80 * tmp;
                break;
        default:
-               trace_cfg80211_return_bool(true);
-               return true;
+               WARN_ON_ONCE(1);
        }
+}
+
+const struct cfg80211_chan_def *
+cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1,
+                           const struct cfg80211_chan_def *c2)
+{
+       u32 c1_pri40, c1_pri80, c2_pri40, c2_pri80;
 
-       sec_chan = ieee80211_get_channel(wiphy, chan->center_freq + diff);
-       if (!sec_chan) {
+       /* If they are identical, return */
+       if (cfg80211_chandef_identical(c1, c2))
+               return c1;
+
+       /* otherwise, must have same control channel */
+       if (c1->chan != c2->chan)
+               return NULL;
+
+       /*
+        * If they have the same width, but aren't identical,
+        * then they can't be compatible.
+        */
+       if (c1->width == c2->width)
+               return NULL;
+
+       if (c1->width == NL80211_CHAN_WIDTH_20_NOHT ||
+           c1->width == NL80211_CHAN_WIDTH_20)
+               return c2;
+
+       if (c2->width == NL80211_CHAN_WIDTH_20_NOHT ||
+           c2->width == NL80211_CHAN_WIDTH_20)
+               return c1;
+
+       chandef_primary_freqs(c1, &c1_pri40, &c1_pri80);
+       chandef_primary_freqs(c2, &c2_pri40, &c2_pri80);
+
+       if (c1_pri40 != c2_pri40)
+               return NULL;
+
+       WARN_ON(!c1_pri80 && !c2_pri80);
+       if (c1_pri80 && c2_pri80 && c1_pri80 != c2_pri80)
+               return NULL;
+
+       if (c1->width > c2->width)
+               return c1;
+       return c2;
+}
+EXPORT_SYMBOL(cfg80211_chandef_compatible);
+
+bool cfg80211_secondary_chans_ok(struct wiphy *wiphy,
+                                u32 center_freq, u32 bandwidth,
+                                u32 prohibited_flags)
+{
+       struct ieee80211_channel *c;
+       u32 freq;
+
+       for (freq = center_freq - bandwidth/2 + 10;
+            freq <= center_freq + bandwidth/2 - 10;
+            freq += 20) {
+               c = ieee80211_get_channel(wiphy, freq);
+               if (!c || c->flags & prohibited_flags)
+                       return false;
+       }
+
+       return true;
+}
+
+static bool cfg80211_check_beacon_chans(struct wiphy *wiphy,
+                                       u32 center_freq, u32 bw)
+{
+       return cfg80211_secondary_chans_ok(wiphy, center_freq, bw,
+                                          IEEE80211_CHAN_DISABLED |
+                                          IEEE80211_CHAN_PASSIVE_SCAN |
+                                          IEEE80211_CHAN_NO_IBSS |
+                                          IEEE80211_CHAN_RADAR);
+}
+
+bool cfg80211_reg_can_beacon(struct wiphy *wiphy,
+                            struct cfg80211_chan_def *chandef)
+{
+       u32 width;
+       bool res;
+
+       trace_cfg80211_reg_can_beacon(wiphy, chandef);
+
+       if (WARN_ON(!cfg80211_chan_def_valid(chandef))) {
                trace_cfg80211_return_bool(false);
                return false;
        }
 
-       /* we'll need a DFS capability later */
-       if (sec_chan->flags & (IEEE80211_CHAN_DISABLED |
-                              IEEE80211_CHAN_PASSIVE_SCAN |
-                              IEEE80211_CHAN_NO_IBSS |
-                              IEEE80211_CHAN_RADAR)) {
+       switch (chandef->width) {
+       case NL80211_CHAN_WIDTH_20_NOHT:
+       case NL80211_CHAN_WIDTH_20:
+               width = 20;
+               break;
+       case NL80211_CHAN_WIDTH_40:
+               width = 40;
+               break;
+       case NL80211_CHAN_WIDTH_80:
+       case NL80211_CHAN_WIDTH_80P80:
+               width = 80;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               width = 160;
+               break;
+       default:
+               WARN_ON_ONCE(1);
                trace_cfg80211_return_bool(false);
                return false;
        }
-       trace_cfg80211_return_bool(true);
-       return true;
+
+       res = cfg80211_check_beacon_chans(wiphy, chandef->center_freq1, width);
+
+       if (res && chandef->center_freq2)
+               res = cfg80211_check_beacon_chans(wiphy, chandef->center_freq2,
+                                                 width);
+
+       trace_cfg80211_return_bool(res);
+       return res;
 }
-EXPORT_SYMBOL(cfg80211_can_beacon_sec_chan);
+EXPORT_SYMBOL(cfg80211_reg_can_beacon);
 
 int cfg80211_set_monitor_channel(struct cfg80211_registered_device *rdev,
-                                int freq, enum nl80211_channel_type chantype)
+                                struct cfg80211_chan_def *chandef)
 {
-       struct ieee80211_channel *chan;
-
        if (!rdev->ops->set_monitor_channel)
                return -EOPNOTSUPP;
        if (!cfg80211_has_monitors_only(rdev))
                return -EBUSY;
 
-       chan = rdev_freq_to_chan(rdev, freq, chantype);
-       if (!chan)
-               return -EINVAL;
-
-       return rdev_set_monitor_channel(rdev, chan, chantype);
+       return rdev_set_monitor_channel(rdev, chandef);
 }
 
 void