cfg80211: validate channel settings across interfaces
authorJohannes Berg <johannes@sipsolutions.net>
Fri, 7 Aug 2009 15:22:35 +0000 (17:22 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 14 Aug 2009 13:13:42 +0000 (09:13 -0400)
Currently, there's a problem that affects regulatory
enforcement and connection stability, in that it is
possible to switch the channel while connected to a
network or joined to an IBSS.

The problem comes from the fact that we only validate
the channel against the current interface's type, not
against any other interface. Thus, you have any type
of interface up, additionally bring up a monitor mode
interface and switch the channel on the monitor. This
will obviously also switch the channel on the other
interface.

The problem now is that if you do that while sending
beacons for IBSS mode, you can switch to a disabled
channel or a channel that doesn't allow beaconing.
Combined with a managed mode interface connected to
an AP instead of an IBSS interface, you can easily
break the connection that way.

To fix this, this patch validates any channel change
with all available interfaces, and disallows such
changes on secondary interfaces if another interface
is connected to an AP or joined to an IBSS.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/wireless/Makefile
net/wireless/chan.c [new file with mode: 0644]
net/wireless/core.h
net/wireless/ibss.c
net/wireless/nl80211.c
net/wireless/sme.c
net/wireless/wext-compat.c
net/wireless/wext-compat.h
net/wireless/wext-sme.c

index d74cc77fa57a9f49113255b479bc7c8a49532e41..3ecaa9179977c4576dfad88ccb5646056b7b2a9b 100644 (file)
@@ -5,7 +5,8 @@ obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o
 obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o
 obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o
 
-cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o mlme.o ibss.o sme.o
+cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o
+cfg80211-y += mlme.o ibss.o sme.o chan.o
 cfg80211-$(CONFIG_CFG80211_DEBUGFS) += debugfs.o
 cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o wext-sme.o
 
diff --git a/net/wireless/chan.c b/net/wireless/chan.c
new file mode 100644 (file)
index 0000000..bc00c9a
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * This file contains helper code to handle channel
+ * settings and keeping track of what is possible at
+ * any point in time.
+ *
+ * Copyright 2009      Johannes Berg <johannes@sipsolutions.net>
+ */
+
+#include <net/cfg80211.h>
+#include "core.h"
+
+struct ieee80211_channel *
+rdev_fixed_channel(struct cfg80211_registered_device *rdev,
+                  struct wireless_dev *for_wdev)
+{
+       struct wireless_dev *wdev;
+       struct ieee80211_channel *result = NULL;
+
+       WARN_ON(!mutex_is_locked(&rdev->devlist_mtx));
+
+       list_for_each_entry(wdev, &rdev->netdev_list, list) {
+               if (wdev == for_wdev)
+                       continue;
+
+               /*
+                * Lock manually to tell lockdep about allowed
+                * nesting here if for_wdev->mtx is held already.
+                * This is ok as it's all under the rdev devlist
+                * mutex and as such can only be done once at any
+                * given time.
+                */
+               mutex_lock_nested(&wdev->mtx, SINGLE_DEPTH_NESTING);
+               if (wdev->current_bss)
+                       result = wdev->current_bss->pub.channel;
+               wdev_unlock(wdev);
+
+               if (result)
+                       break;
+       }
+
+       return result;
+}
+
+int rdev_set_freq(struct cfg80211_registered_device *rdev,
+                 int freq, enum nl80211_channel_type channel_type)
+{
+       struct ieee80211_channel *chan;
+       struct ieee80211_sta_ht_cap *ht_cap;
+       int result;
+
+       if (rdev_fixed_channel(rdev, NULL))
+               return -EBUSY;
+
+       if (!rdev->ops->set_channel)
+               return -EOPNOTSUPP;
+
+       chan = ieee80211_get_channel(&rdev->wiphy, freq);
+
+       /* Primary channel not allowed */
+       if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
+               return -EINVAL;
+
+       if (channel_type == NL80211_CHAN_HT40MINUS &&
+           chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
+               return -EINVAL;
+       else if (channel_type == NL80211_CHAN_HT40PLUS &&
+                chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
+               return -EINVAL;
+
+       ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
+
+       if (channel_type != NL80211_CHAN_NO_HT) {
+               if (!ht_cap->ht_supported)
+                       return -EINVAL;
+
+               if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
+                   ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)
+                       return -EINVAL;
+       }
+
+       result = rdev->ops->set_channel(&rdev->wiphy, chan, channel_type);
+       if (result)
+               return result;
+
+       rdev->channel = chan;
+
+       return 0;
+}
index 325c17e6198c456ecc38f791b54b7636245cbca1..5696b95af9bedcee7cdd0175efd0807eacacf7ca 100644 (file)
@@ -366,4 +366,10 @@ void cfg80211_sme_disassoc(struct net_device *dev, int idx);
 void __cfg80211_scan_done(struct work_struct *wk);
 void cfg80211_upload_connect_keys(struct wireless_dev *wdev);
 
+struct ieee80211_channel *
+rdev_fixed_channel(struct cfg80211_registered_device *rdev,
+                  struct wireless_dev *for_wdev);
+int rdev_set_freq(struct cfg80211_registered_device *rdev,
+                 int freq, enum nl80211_channel_type channel_type);
+
 #endif /* __NET_WIRELESS_CORE_H */
index 4d7a084b35e2e1d2e625e68dba241241fdb33dab..42840a01be7405185b5e00785837d5ed09e9cac8 100644 (file)
@@ -78,10 +78,15 @@ int __cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
                         struct cfg80211_cached_keys *connkeys)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct ieee80211_channel *chan;
        int err;
 
        ASSERT_WDEV_LOCK(wdev);
 
+       chan = rdev_fixed_channel(rdev, wdev);
+       if (chan && chan != params->channel)
+               return -EBUSY;
+
        if (wdev->ssid_len)
                return -EALREADY;
 
@@ -112,9 +117,11 @@ int cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        int err;
 
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
        err = __cfg80211_join_ibss(rdev, dev, params, connkeys);
        wdev_unlock(wdev);
+       mutex_unlock(&rdev->devlist_mtx);
 
        return err;
 }
@@ -264,27 +271,32 @@ int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev,
 
 int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
                               struct iw_request_info *info,
-                              struct iw_freq *freq, char *extra)
+                              struct iw_freq *wextfreq, char *extra)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
-       struct ieee80211_channel *chan;
-       int err;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
+       struct ieee80211_channel *chan = NULL;
+       int err, freq;
 
        /* call only for ibss! */
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
                return -EINVAL;
 
-       if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
+       if (!rdev->ops->join_ibss)
                return -EOPNOTSUPP;
 
-       chan = cfg80211_wext_freq(wdev->wiphy, freq);
-       if (chan && IS_ERR(chan))
-               return PTR_ERR(chan);
+       freq = cfg80211_wext_freq(wdev->wiphy, wextfreq);
+       if (freq < 0)
+               return freq;
 
-       if (chan &&
-           (chan->flags & IEEE80211_CHAN_NO_IBSS ||
-            chan->flags & IEEE80211_CHAN_DISABLED))
-               return -EINVAL;
+       if (freq) {
+               chan = ieee80211_get_channel(wdev->wiphy, freq);
+               if (!chan)
+                       return -EINVAL;
+               if (chan->flags & IEEE80211_CHAN_NO_IBSS ||
+                   chan->flags & IEEE80211_CHAN_DISABLED)
+                       return -EINVAL;
+       }
 
        if (wdev->wext.ibss.channel == chan)
                return 0;
@@ -292,8 +304,7 @@ int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
        wdev_lock(wdev);
        err = 0;
        if (wdev->ssid_len)
-               err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
-                                           dev, true);
+               err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
        if (err)
@@ -307,9 +318,11 @@ int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
                wdev->wext.ibss.channel_fixed = false;
        }
 
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
-       err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_ibss_wext_join(rdev, wdev);
        wdev_unlock(wdev);
+       mutex_unlock(&rdev->devlist_mtx);
 
        return err;
 }
@@ -347,6 +360,7 @@ int cfg80211_ibss_wext_siwessid(struct net_device *dev,
                                struct iw_point *data, char *ssid)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
        size_t len = data->length;
        int err;
 
@@ -354,14 +368,13 @@ int cfg80211_ibss_wext_siwessid(struct net_device *dev,
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
                return -EINVAL;
 
-       if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
+       if (!rdev->ops->join_ibss)
                return -EOPNOTSUPP;
 
        wdev_lock(wdev);
        err = 0;
        if (wdev->ssid_len)
-               err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
-                                           dev, true);
+               err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
        if (err)
@@ -375,9 +388,11 @@ int cfg80211_ibss_wext_siwessid(struct net_device *dev,
        memcpy(wdev->wext.ibss.ssid, ssid, len);
        wdev->wext.ibss.ssid_len = len;
 
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
-       err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_ibss_wext_join(rdev, wdev);
        wdev_unlock(wdev);
+       mutex_unlock(&rdev->devlist_mtx);
 
        return err;
 }
@@ -414,6 +429,7 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
                             struct sockaddr *ap_addr, char *extra)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
        u8 *bssid = ap_addr->sa_data;
        int err;
 
@@ -421,7 +437,7 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
                return -EINVAL;
 
-       if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
+       if (!rdev->ops->join_ibss)
                return -EOPNOTSUPP;
 
        if (ap_addr->sa_family != ARPHRD_ETHER)
@@ -443,8 +459,7 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
        wdev_lock(wdev);
        err = 0;
        if (wdev->ssid_len)
-               err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
-                                           dev, true);
+               err = __cfg80211_leave_ibss(rdev, dev, true);
        wdev_unlock(wdev);
 
        if (err)
@@ -456,9 +471,11 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
        } else
                wdev->wext.ibss.bssid = NULL;
 
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
-       err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_ibss_wext_join(rdev, wdev);
        wdev_unlock(wdev);
+       mutex_unlock(&rdev->devlist_mtx);
 
        return err;
 }
index 0cd548267d4a717a11e636dce9d548dd03ebd7ea..2ff7376f35a3aa032fc07ec31e8928f5574e8b08 100644 (file)
@@ -701,15 +701,8 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
 
        if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
                enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
-               struct ieee80211_channel *chan;
-               struct ieee80211_sta_ht_cap *ht_cap;
                u32 freq;
 
-               if (!rdev->ops->set_channel) {
-                       result = -EOPNOTSUPP;
-                       goto bad_res;
-               }
-
                result = -EINVAL;
 
                if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
@@ -723,42 +716,12 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
                }
 
                freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
-               chan = ieee80211_get_channel(&rdev->wiphy, freq);
-
-               /* Primary channel not allowed */
-               if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
-                       goto bad_res;
-
-               if (channel_type == NL80211_CHAN_HT40MINUS &&
-                   (chan->flags & IEEE80211_CHAN_NO_HT40MINUS))
-                       goto bad_res;
-               else if (channel_type == NL80211_CHAN_HT40PLUS &&
-                        (chan->flags & IEEE80211_CHAN_NO_HT40PLUS))
-                       goto bad_res;
-
-               /*
-                * At this point we know if that if HT40 was requested
-                * we are allowed to use it and the extension channel
-                * exists.
-                */
 
-               ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
-
-               /* no HT capabilities or intolerant */
-               if (channel_type != NL80211_CHAN_NO_HT) {
-                       if (!ht_cap->ht_supported)
-                               goto bad_res;
-                       if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
-                           (ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
-                               goto bad_res;
-               }
-
-               result = rdev->ops->set_channel(&rdev->wiphy, chan,
-                                               channel_type);
+               mutex_lock(&rdev->devlist_mtx);
+               result = rdev_set_freq(rdev, freq, channel_type);
+               mutex_unlock(&rdev->devlist_mtx);
                if (result)
                        goto bad_res;
-
-               rdev->channel = chan;
        }
 
        changed = 0;
@@ -3453,7 +3416,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
        struct cfg80211_registered_device *rdev;
        struct net_device *dev;
        struct cfg80211_crypto_settings crypto;
-       struct ieee80211_channel *chan;
+       struct ieee80211_channel *chan, *fixedchan;
        const u8 *bssid, *ssid, *ie = NULL, *prev_bssid = NULL;
        int err, ssid_len, ie_len = 0;
        bool use_mfp = false;
@@ -3496,6 +3459,15 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
                goto out;
        }
 
+       mutex_lock(&rdev->devlist_mtx);
+       fixedchan = rdev_fixed_channel(rdev, NULL);
+       if (fixedchan && chan != fixedchan) {
+               err = -EBUSY;
+               mutex_unlock(&rdev->devlist_mtx);
+               goto out;
+       }
+       mutex_unlock(&rdev->devlist_mtx);
+
        ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
        ssid_len = nla_len(info->attrs[NL80211_ATTR_SSID]);
 
index 340934f714b2df619bddda570c013476ea760e8d..219c3bc2c37d3c1a3bf88a0b5aae1b11751d2602 100644 (file)
@@ -256,9 +256,11 @@ void cfg80211_sme_scan_done(struct net_device *dev)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
 
+       mutex_lock(&wiphy_to_dev(wdev->wiphy)->devlist_mtx);
        wdev_lock(wdev);
        __cfg80211_sme_scan_done(dev);
        wdev_unlock(wdev);
+       mutex_unlock(&wiphy_to_dev(wdev->wiphy)->devlist_mtx);
 }
 
 void cfg80211_sme_rx_auth(struct net_device *dev,
@@ -644,6 +646,7 @@ int __cfg80211_connect(struct cfg80211_registered_device *rdev,
                       struct cfg80211_cached_keys *connkeys)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct ieee80211_channel *chan;
        int err;
 
        ASSERT_WDEV_LOCK(wdev);
@@ -651,6 +654,10 @@ int __cfg80211_connect(struct cfg80211_registered_device *rdev,
        if (wdev->sme_state != CFG80211_SME_IDLE)
                return -EALREADY;
 
+       chan = rdev_fixed_channel(rdev, wdev);
+       if (chan && chan != connect->channel)
+               return -EBUSY;
+
        if (WARN_ON(wdev->connect_keys)) {
                kfree(wdev->connect_keys);
                wdev->connect_keys = NULL;
@@ -785,9 +792,11 @@ int cfg80211_connect(struct cfg80211_registered_device *rdev,
 {
        int err;
 
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(dev->ieee80211_ptr);
        err = __cfg80211_connect(rdev, dev, connect, connkeys);
        wdev_unlock(dev->ieee80211_ptr);
+       mutex_unlock(&rdev->devlist_mtx);
 
        return err;
 }
index e4e90e249bab12a1fc0d6317cf356518b9b1428a..17648dc79867f98523be4f358bf93f0793194e08 100644 (file)
@@ -267,39 +267,26 @@ EXPORT_SYMBOL_GPL(cfg80211_wext_giwrange);
  * @wiphy: the wiphy
  * @freq: the wext freq encoding
  *
- * Returns a channel, %NULL for auto, or an ERR_PTR for errors!
+ * Returns a frequency, or a negative error code, or 0 for auto.
  */
-struct ieee80211_channel *cfg80211_wext_freq(struct wiphy *wiphy,
-                                            struct iw_freq *freq)
+int cfg80211_wext_freq(struct wiphy *wiphy, struct iw_freq *freq)
 {
-       struct ieee80211_channel *chan;
-       int f;
-
        /*
-        * Parse frequency - return NULL for auto and
+        * Parse frequency - return 0 for auto and
         * -EINVAL for impossible things.
         */
        if (freq->e == 0) {
                if (freq->m < 0)
-                       return NULL;
-               f = ieee80211_channel_to_frequency(freq->m);
+                       return 0;
+               return ieee80211_channel_to_frequency(freq->m);
        } else {
                int i, div = 1000000;
                for (i = 0; i < freq->e; i++)
                        div /= 10;
                if (div <= 0)
-                       return ERR_PTR(-EINVAL);
-               f = freq->m / div;
+                       return -EINVAL;
+               return freq->m / div;
        }
-
-       /*
-        * Look up channel struct and return -EINVAL when
-        * it cannot be found.
-        */
-       chan = ieee80211_get_channel(wiphy, f);
-       if (!chan)
-               return ERR_PTR(-EINVAL);
-       return chan;
 }
 
 int cfg80211_wext_siwrts(struct net_device *dev,
@@ -761,33 +748,29 @@ EXPORT_SYMBOL_GPL(cfg80211_wext_giwencode);
 
 int cfg80211_wext_siwfreq(struct net_device *dev,
                          struct iw_request_info *info,
-                         struct iw_freq *freq, char *extra)
+                         struct iw_freq *wextfreq, char *extra)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
-       struct ieee80211_channel *chan;
-       int err;
+       int freq, err;
 
        switch (wdev->iftype) {
        case NL80211_IFTYPE_STATION:
-               return cfg80211_mgd_wext_siwfreq(dev, info, freq, extra);
+               return cfg80211_mgd_wext_siwfreq(dev, info, wextfreq, extra);
        case NL80211_IFTYPE_ADHOC:
-               return cfg80211_ibss_wext_siwfreq(dev, info, freq, extra);
+               return cfg80211_ibss_wext_siwfreq(dev, info, wextfreq, extra);
        default:
-               chan = cfg80211_wext_freq(wdev->wiphy, freq);
-               if (!chan)
+               freq = cfg80211_wext_freq(wdev->wiphy, wextfreq);
+               if (freq < 0)
+                       return freq;
+               if (freq == 0)
                        return -EINVAL;
-               if (IS_ERR(chan))
-                       return PTR_ERR(chan);
-               err = rdev->ops->set_channel(wdev->wiphy, chan,
-                                            NL80211_CHAN_NO_HT);
-               if (err)
-                       return err;
-               rdev->channel = chan;
-               return 0;
+               mutex_lock(&rdev->devlist_mtx);
+               err = rdev_set_freq(rdev, freq, NL80211_CHAN_NO_HT);
+               mutex_unlock(&rdev->devlist_mtx);
+               return err;
        }
 }
-EXPORT_SYMBOL_GPL(cfg80211_wext_siwfreq);
 
 int cfg80211_wext_giwfreq(struct net_device *dev,
                          struct iw_request_info *info,
index 9a37747495890548621dcf0c5a851e4e4fe66eb3..20b3daef69643586d2508deca0f1f52c1ebe6ac0 100644 (file)
@@ -42,8 +42,7 @@ int cfg80211_mgd_wext_giwessid(struct net_device *dev,
                               struct iw_request_info *info,
                               struct iw_point *data, char *ssid);
 
-struct ieee80211_channel *cfg80211_wext_freq(struct wiphy *wiphy,
-                                            struct iw_freq *freq);
+int cfg80211_wext_freq(struct wiphy *wiphy, struct iw_freq *freq);
 
 
 extern const struct iw_handler_def cfg80211_wext_handler;
index e4a054aceb5a803400dc9218fd301f4885eff8a9..fe1a536391226acf727625aa8dba2be76f01497a 100644 (file)
@@ -52,25 +52,31 @@ int cfg80211_mgd_wext_connect(struct cfg80211_registered_device *rdev,
 
 int cfg80211_mgd_wext_siwfreq(struct net_device *dev,
                              struct iw_request_info *info,
-                             struct iw_freq *freq, char *extra)
+                             struct iw_freq *wextfreq, char *extra)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
-       struct ieee80211_channel *chan;
-       int err;
+       struct ieee80211_channel *chan = NULL;
+       int err, freq;
 
        /* call only for station! */
        if (WARN_ON(wdev->iftype != NL80211_IFTYPE_STATION))
                return -EINVAL;
 
-       chan = cfg80211_wext_freq(wdev->wiphy, freq);
-       if (chan && IS_ERR(chan))
-               return PTR_ERR(chan);
+       freq = cfg80211_wext_freq(wdev->wiphy, wextfreq);
+       if (freq < 0)
+               return freq;
 
-       if (chan && (chan->flags & IEEE80211_CHAN_DISABLED))
-               return -EINVAL;
+       if (freq) {
+               chan = ieee80211_get_channel(wdev->wiphy, freq);
+               if (!chan)
+                       return -EINVAL;
+               if (chan->flags & IEEE80211_CHAN_DISABLED)
+                       return -EINVAL;
+       }
 
        cfg80211_lock_rdev(rdev);
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
 
        if (wdev->sme_state != CFG80211_SME_IDLE) {
@@ -84,9 +90,8 @@ int cfg80211_mgd_wext_siwfreq(struct net_device *dev,
                /* if SSID set, we'll try right again, avoid event */
                if (wdev->wext.connect.ssid_len)
                        event = false;
-               err = __cfg80211_disconnect(wiphy_to_dev(wdev->wiphy),
-                                           dev, WLAN_REASON_DEAUTH_LEAVING,
-                                           event);
+               err = __cfg80211_disconnect(rdev, dev,
+                                           WLAN_REASON_DEAUTH_LEAVING, event);
                if (err)
                        goto out;
        }
@@ -95,17 +100,15 @@ int cfg80211_mgd_wext_siwfreq(struct net_device *dev,
        wdev->wext.connect.channel = chan;
 
        /* SSID is not set, we just want to switch channel */
-       if (wdev->wext.connect.ssid_len && chan) {
-               err = -EOPNOTSUPP;
-               if (rdev->ops->set_channel)
-                       err = rdev->ops->set_channel(wdev->wiphy, chan,
-                                                    NL80211_CHAN_NO_HT);
+       if (chan && !wdev->wext.connect.ssid_len) {
+               err = rdev_set_freq(rdev, freq, NL80211_CHAN_NO_HT);
                goto out;
        }
 
-       err = cfg80211_mgd_wext_connect(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_mgd_wext_connect(rdev, wdev);
  out:
        wdev_unlock(wdev);
+       mutex_unlock(&rdev->devlist_mtx);
        cfg80211_unlock_rdev(rdev);
        return err;
 }
@@ -143,6 +146,7 @@ int cfg80211_mgd_wext_siwessid(struct net_device *dev,
                               struct iw_point *data, char *ssid)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
        size_t len = data->length;
        int err;
 
@@ -157,7 +161,8 @@ int cfg80211_mgd_wext_siwessid(struct net_device *dev,
        if (len > 0 && ssid[len - 1] == '\0')
                len--;
 
-       cfg80211_lock_rdev(wiphy_to_dev(wdev->wiphy));
+       cfg80211_lock_rdev(rdev);
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
 
        err = 0;
@@ -173,9 +178,8 @@ int cfg80211_mgd_wext_siwessid(struct net_device *dev,
                /* if SSID set now, we'll try to connect, avoid event */
                if (len)
                        event = false;
-               err = __cfg80211_disconnect(wiphy_to_dev(wdev->wiphy),
-                                           dev, WLAN_REASON_DEAUTH_LEAVING,
-                                           event);
+               err = __cfg80211_disconnect(rdev, dev,
+                                           WLAN_REASON_DEAUTH_LEAVING, event);
                if (err)
                        goto out;
        }
@@ -186,10 +190,11 @@ int cfg80211_mgd_wext_siwessid(struct net_device *dev,
 
        wdev->wext.connect.crypto.control_port = false;
 
-       err = cfg80211_mgd_wext_connect(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_mgd_wext_connect(rdev, wdev);
  out:
        wdev_unlock(wdev);
-       cfg80211_unlock_rdev(wiphy_to_dev(wdev->wiphy));
+       mutex_unlock(&rdev->devlist_mtx);
+       cfg80211_unlock_rdev(rdev);
        return err;
 }
 
@@ -230,6 +235,7 @@ int cfg80211_mgd_wext_siwap(struct net_device *dev,
                            struct sockaddr *ap_addr, char *extra)
 {
        struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
        u8 *bssid = ap_addr->sa_data;
        int err;
 
@@ -244,7 +250,8 @@ int cfg80211_mgd_wext_siwap(struct net_device *dev,
        if (is_zero_ether_addr(bssid) || is_broadcast_ether_addr(bssid))
                bssid = NULL;
 
-       cfg80211_lock_rdev(wiphy_to_dev(wdev->wiphy));
+       cfg80211_lock_rdev(rdev);
+       mutex_lock(&rdev->devlist_mtx);
        wdev_lock(wdev);
 
        if (wdev->sme_state != CFG80211_SME_IDLE) {
@@ -258,9 +265,8 @@ int cfg80211_mgd_wext_siwap(struct net_device *dev,
                    compare_ether_addr(bssid, wdev->wext.connect.bssid) == 0)
                        goto out;
 
-               err = __cfg80211_disconnect(wiphy_to_dev(wdev->wiphy),
-                                           dev, WLAN_REASON_DEAUTH_LEAVING,
-                                           false);
+               err = __cfg80211_disconnect(rdev, dev,
+                                           WLAN_REASON_DEAUTH_LEAVING, false);
                if (err)
                        goto out;
        }
@@ -271,10 +277,11 @@ int cfg80211_mgd_wext_siwap(struct net_device *dev,
        } else
                wdev->wext.connect.bssid = NULL;
 
-       err = cfg80211_mgd_wext_connect(wiphy_to_dev(wdev->wiphy), wdev);
+       err = cfg80211_mgd_wext_connect(rdev, wdev);
  out:
        wdev_unlock(wdev);
-       cfg80211_unlock_rdev(wiphy_to_dev(wdev->wiphy));
+       mutex_unlock(&rdev->devlist_mtx);
+       cfg80211_unlock_rdev(rdev);
        return err;
 }