Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c8ff71e6 authored by Arik Nemtsov's avatar Arik Nemtsov Committed by Johannes Berg
Browse files

mac80211: TDLS: handle chan-switch in RTNL locked work



Move TDLS channel-switch Rx handling into an RTNL locked work. This is
required to add proper regulatory checking to incoming channel-switch
requests.
Queue incoming requests in a dedicated skb queue and handle the request
in a device-specific work to avoid deadlocking on interface removal.

Signed-off-by: default avatarArik Nemtsov <arikx.nemtsov@intel.com>
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 72bbe3d1
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -1008,7 +1008,6 @@ enum sdata_queue_type {
	IEEE80211_SDATA_QUEUE_AGG_STOP		= 2,
	IEEE80211_SDATA_QUEUE_RX_AGG_START	= 3,
	IEEE80211_SDATA_QUEUE_RX_AGG_STOP	= 4,
	IEEE80211_SDATA_QUEUE_TDLS_CHSW		= 5,
};

enum {
@@ -1351,6 +1350,10 @@ struct ieee80211_local {

	/* extended capabilities provided by mac80211 */
	u8 ext_capa[8];

	/* TDLS channel switch */
	struct work_struct tdls_chsw_work;
	struct sk_buff_head skb_queue_tdls_chsw;
};

static inline struct ieee80211_sub_if_data *
@@ -2054,9 +2057,8 @@ int ieee80211_tdls_channel_switch(struct wiphy *wiphy, struct net_device *dev,
void ieee80211_tdls_cancel_channel_switch(struct wiphy *wiphy,
					  struct net_device *dev,
					  const u8 *addr);
void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
					   struct sk_buff *skb);
void ieee80211_teardown_tdls_peers(struct ieee80211_sub_if_data *sdata);
void ieee80211_tdls_chsw_work(struct work_struct *wk);

extern const struct ethtool_ops ieee80211_ethtool_ops;

+0 −2
Original line number Diff line number Diff line
@@ -1242,8 +1242,6 @@ static void ieee80211_iface_work(struct work_struct *work)
							WLAN_BACK_RECIPIENT, 0,
							false);
			mutex_unlock(&local->sta_mtx);
		} else if (skb->pkt_type == IEEE80211_SDATA_QUEUE_TDLS_CHSW) {
			ieee80211_process_tdls_channel_switch(sdata, skb);
		} else if (ieee80211_is_action(mgmt->frame_control) &&
			   mgmt->u.action.category == WLAN_CATEGORY_BACK) {
			int len = skb->len;
+5 −0
Original line number Diff line number Diff line
@@ -629,6 +629,8 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
	INIT_WORK(&local->sched_scan_stopped_work,
		  ieee80211_sched_scan_stopped_work);

	INIT_WORK(&local->tdls_chsw_work, ieee80211_tdls_chsw_work);

	spin_lock_init(&local->ack_status_lock);
	idr_init(&local->ack_status_frames);

@@ -645,6 +647,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,

	skb_queue_head_init(&local->skb_queue);
	skb_queue_head_init(&local->skb_queue_unreliable);
	skb_queue_head_init(&local->skb_queue_tdls_chsw);

	ieee80211_alloc_led_names(local);

@@ -1161,6 +1164,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)

	cancel_work_sync(&local->restart_work);
	cancel_work_sync(&local->reconfig_filter);
	cancel_work_sync(&local->tdls_chsw_work);
	flush_work(&local->sched_scan_stopped_work);

	ieee80211_clear_tx_pending(local);
@@ -1171,6 +1175,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
		wiphy_warn(local->hw.wiphy, "skb_queue not empty\n");
	skb_queue_purge(&local->skb_queue);
	skb_queue_purge(&local->skb_queue_unreliable);
	skb_queue_purge(&local->skb_queue_tdls_chsw);

	destroy_workqueue(local->workqueue);
	wiphy_unregister(local->hw.wiphy);
+2 −3
Original line number Diff line number Diff line
@@ -2410,9 +2410,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
		    tf->category == WLAN_CATEGORY_TDLS &&
		    (tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
		     tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
			rx->skb->pkt_type = IEEE80211_SDATA_QUEUE_TDLS_CHSW;
			skb_queue_tail(&sdata->skb_queue, rx->skb);
			ieee80211_queue_work(&rx->local->hw, &sdata->work);
			skb_queue_tail(&local->skb_queue_tdls_chsw, rx->skb);
			schedule_work(&local->tdls_chsw_work);
			if (rx->sta)
				rx->sta->rx_packets++;

+32 −2
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <linux/ieee80211.h>
#include <linux/log2.h>
#include <net/cfg80211.h>
#include <linux/rtnetlink.h>
#include "ieee80211_i.h"
#include "driver-ops.h"

@@ -1800,12 +1801,15 @@ ieee80211_process_tdls_channel_switch_req(struct ieee80211_sub_if_data *sdata,
	return ret;
}

void ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
static void
ieee80211_process_tdls_channel_switch(struct ieee80211_sub_if_data *sdata,
				      struct sk_buff *skb)
{
	struct ieee80211_tdls_data *tf = (void *)skb->data;
	struct wiphy *wiphy = sdata->local->hw.wiphy;

	ASSERT_RTNL();

	/* make sure the driver supports it */
	if (!(wiphy->features & NL80211_FEATURE_TDLS_CHANNEL_SWITCH))
		return;
@@ -1847,3 +1851,29 @@ void ieee80211_teardown_tdls_peers(struct ieee80211_sub_if_data *sdata)
	}
	rcu_read_unlock();
}

void ieee80211_tdls_chsw_work(struct work_struct *wk)
{
	struct ieee80211_local *local =
		container_of(wk, struct ieee80211_local, tdls_chsw_work);
	struct ieee80211_sub_if_data *sdata;
	struct sk_buff *skb;
	struct ieee80211_tdls_data *tf;

	rtnl_lock();
	while ((skb = skb_dequeue(&local->skb_queue_tdls_chsw))) {
		tf = (struct ieee80211_tdls_data *)skb->data;
		list_for_each_entry(sdata, &local->interfaces, list) {
			if (!ieee80211_sdata_running(sdata) ||
			    sdata->vif.type != NL80211_IFTYPE_STATION ||
			    !ether_addr_equal(tf->da, sdata->vif.addr))
				continue;

			ieee80211_process_tdls_channel_switch(sdata, skb);
			break;
		}

		kfree_skb(skb);
	}
	rtnl_unlock();
}