mac80211: allow TDLS setup code to take wdev lock
authorArik Nemtsov <arik@wizery.com>
Sun, 1 Mar 2015 07:10:09 +0000 (09:10 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 4 Mar 2015 09:34:10 +0000 (10:34 +0100)
TDLS off-channel can be allowed in channels marked with GO_CONCURRENT,
provided the device is connected to an AP on the same UNII.
When relaxing the NO-IR requirements for TDLS, we might hit flows in
cfg80211_reg_can_beacon that acquire the wdev lock. Take some measures
to allow this during TDLS setup.
Acquire the RCU read lock later in the flow that invokes
cfg80211_reg_can_beacon.
Avoid taking local->mtx when preparing the setup packet to avoid
circular deadlocks with mac80211 code that is invoked with wdev-mtx
held.

Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/tdls.c

index 5bcd542e49334f2d3f6af2e03ba1ddf2415cc1a5..bc7e4049896f5244a40b77b9159f5f9e11454cdc 100644 (file)
@@ -287,17 +287,6 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata,
        size_t offset = 0, noffset;
        u8 *pos;
 
-       rcu_read_lock();
-
-       /* we should have the peer STA if we're already responding */
-       if (action_code == WLAN_TDLS_SETUP_RESPONSE) {
-               sta = sta_info_get(sdata, peer);
-               if (WARN_ON_ONCE(!sta)) {
-                       rcu_read_unlock();
-                       return;
-               }
-       }
-
        ieee80211_add_srates_ie(sdata, skb, false, band);
        ieee80211_add_ext_srates_ie(sdata, skb, false, band);
        ieee80211_tdls_add_supp_channels(sdata, skb);
@@ -350,6 +339,17 @@ ieee80211_tdls_add_setup_start_ies(struct ieee80211_sub_if_data *sdata,
                offset = noffset;
        }
 
+       rcu_read_lock();
+
+       /* we should have the peer STA if we're already responding */
+       if (action_code == WLAN_TDLS_SETUP_RESPONSE) {
+               sta = sta_info_get(sdata, peer);
+               if (WARN_ON_ONCE(!sta)) {
+                       rcu_read_unlock();
+                       return;
+               }
+       }
+
        /*
         * with TDLS we can switch channels, and HT-caps are not necessarily
         * the same on all bands. The specification limits the setup to a
@@ -983,7 +983,7 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev,
        if (!is_zero_ether_addr(sdata->u.mgd.tdls_peer) &&
            !ether_addr_equal(sdata->u.mgd.tdls_peer, peer)) {
                ret = -EBUSY;
-               goto exit;
+               goto out_unlock;
        }
 
        /*
@@ -998,27 +998,34 @@ ieee80211_tdls_mgmt_setup(struct wiphy *wiphy, struct net_device *dev,
                if (!sta_info_get(sdata, peer)) {
                        rcu_read_unlock();
                        ret = -ENOLINK;
-                       goto exit;
+                       goto out_unlock;
                }
                rcu_read_unlock();
        }
 
        ieee80211_flush_queues(local, sdata, false);
+       memcpy(sdata->u.mgd.tdls_peer, peer, ETH_ALEN);
+       mutex_unlock(&local->mtx);
 
+       /* we cannot take the mutex while preparing the setup packet */
        ret = ieee80211_tdls_prep_mgmt_packet(wiphy, dev, peer, action_code,
                                              dialog_token, status_code,
                                              peer_capability, initiator,
                                              extra_ies, extra_ies_len, 0,
                                              NULL);
-       if (ret < 0)
-               goto exit;
+       if (ret < 0) {
+               mutex_lock(&local->mtx);
+               eth_zero_addr(sdata->u.mgd.tdls_peer);
+               mutex_unlock(&local->mtx);
+               return ret;
+       }
 
-       memcpy(sdata->u.mgd.tdls_peer, peer, ETH_ALEN);
        ieee80211_queue_delayed_work(&sdata->local->hw,
                                     &sdata->u.mgd.tdls_peer_del_work,
                                     TDLS_PEER_SETUP_TIMEOUT);
+       return 0;
 
-exit:
+out_unlock:
        mutex_unlock(&local->mtx);
        return ret;
 }