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

Commit f30221e4 authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville
Browse files

mac80211: implement off-channel mgmt TX



This implements the new off-channel TX API
in mac80211 with a new work item type. The
operation doesn't add a new work item when
we're on the right channel and there's no
wait time so that for example p2p probe
responses will be transmitted without delay.

Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent f7ca38df
Loading
Loading
Loading
Loading
+88 −5
Original line number Diff line number Diff line
@@ -1551,6 +1551,28 @@ static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
	return ieee80211_wk_cancel_remain_on_channel(sdata, cookie);
}

static enum work_done_result
ieee80211_offchan_tx_done(struct ieee80211_work *wk, struct sk_buff *skb)
{
	/*
	 * Use the data embedded in the work struct for reporting
	 * here so if the driver mangled the SKB before dropping
	 * it (which is the only way we really should get here)
	 * then we don't report mangled data.
	 *
	 * If there was no wait time, then by the time we get here
	 * the driver will likely not have reported the status yet,
	 * so in that case userspace will have to deal with it.
	 */

	if (wk->offchan_tx.wait && wk->offchan_tx.frame)
		cfg80211_mgmt_tx_status(wk->sdata->dev,
					(unsigned long) wk->offchan_tx.frame,
					wk->ie, wk->ie_len, false, GFP_KERNEL);

	return WORK_DONE_DESTROY;
}

static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
			     struct ieee80211_channel *chan, bool offchan,
			     enum nl80211_channel_type channel_type,
@@ -1561,20 +1583,22 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
	struct ieee80211_local *local = sdata->local;
	struct sk_buff *skb;
	struct sta_info *sta;
	struct ieee80211_work *wk;
	const struct ieee80211_mgmt *mgmt = (void *)buf;
	u32 flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX |
		    IEEE80211_TX_CTL_REQ_TX_STATUS;

	if (offchan)
		return -EOPNOTSUPP;
	bool is_offchan = false;

	/* Check that we are on the requested channel for transmission */
	if (chan != local->tmp_channel &&
	    chan != local->oper_channel)
		return -EBUSY;
		is_offchan = true;
	if (channel_type_valid &&
	    (channel_type != local->tmp_channel_type &&
	     channel_type != local->_oper_channel_type))
		is_offchan = true;

	if (is_offchan && !offchan)
		return -EBUSY;

	switch (sdata->vif.type) {
@@ -1608,12 +1632,70 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
	IEEE80211_SKB_CB(skb)->flags = flags;

	skb->dev = sdata->dev;
	ieee80211_tx_skb(sdata, skb);

	*cookie = (unsigned long) skb;

	/*
	 * Can transmit right away if the channel was the
	 * right one and there's no wait involved... If a
	 * wait is involved, we might otherwise not be on
	 * the right channel for long enough!
	 */
	if (!is_offchan && !wait && !sdata->vif.bss_conf.idle) {
		ieee80211_tx_skb(sdata, skb);
		return 0;
	}

	wk = kzalloc(sizeof(*wk) + len, GFP_KERNEL);
	if (!wk) {
		kfree_skb(skb);
		return -ENOMEM;
	}

	wk->type = IEEE80211_WORK_OFFCHANNEL_TX;
	wk->chan = chan;
	wk->sdata = sdata;
	wk->done = ieee80211_offchan_tx_done;
	wk->offchan_tx.frame = skb;
	wk->offchan_tx.wait = wait;
	wk->ie_len = len;
	memcpy(wk->ie, buf, len);

	ieee80211_add_work(wk);
	return 0;
}

static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
					 struct net_device *dev,
					 u64 cookie)
{
	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_work *wk;
	int ret = -ENOENT;

	mutex_lock(&local->mtx);
	list_for_each_entry(wk, &local->work_list, list) {
		if (wk->sdata != sdata)
			continue;

		if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX)
			continue;

		if (cookie != (unsigned long) wk->offchan_tx.frame)
			continue;

		wk->timeout = jiffies;

		ieee80211_queue_work(&local->hw, &local->work_work);
		ret = 0;
		break;
	}
	mutex_unlock(&local->mtx);

	return ret;
}

static void ieee80211_mgmt_frame_register(struct wiphy *wiphy,
					  struct net_device *dev,
					  u16 frame_type, bool reg)
@@ -1698,6 +1780,7 @@ struct cfg80211_ops mac80211_config_ops = {
	.remain_on_channel = ieee80211_remain_on_channel,
	.cancel_remain_on_channel = ieee80211_cancel_remain_on_channel,
	.mgmt_tx = ieee80211_mgmt_tx,
	.mgmt_tx_cancel_wait = ieee80211_mgmt_tx_cancel_wait,
	.set_cqm_rssi_config = ieee80211_set_cqm_rssi_config,
	.mgmt_frame_register = ieee80211_mgmt_frame_register,
	.set_antenna = ieee80211_set_antenna,
+5 −0
Original line number Diff line number Diff line
@@ -260,6 +260,7 @@ enum ieee80211_work_type {
	IEEE80211_WORK_ASSOC_BEACON_WAIT,
	IEEE80211_WORK_ASSOC,
	IEEE80211_WORK_REMAIN_ON_CHANNEL,
	IEEE80211_WORK_OFFCHANNEL_TX,
};

/**
@@ -320,6 +321,10 @@ struct ieee80211_work {
		struct {
			u32 duration;
		} remain;
		struct {
			struct sk_buff *frame;
			u32 wait;
		} offchan_tx;
	};

	int ie_len;
+14 −1
Original line number Diff line number Diff line
@@ -321,10 +321,23 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
					msecs_to_jiffies(10));
	}

	if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX)
	if (info->flags & IEEE80211_TX_INTFL_NL80211_FRAME_TX) {
		struct ieee80211_work *wk;

		rcu_read_lock();
		list_for_each_entry_rcu(wk, &local->work_list, list) {
			if (wk->type != IEEE80211_WORK_OFFCHANNEL_TX)
				continue;
			if (wk->offchan_tx.frame != skb)
				continue;
			wk->offchan_tx.frame = NULL;
			break;
		}
		rcu_read_unlock();
		cfg80211_mgmt_tx_status(
			skb->dev, (unsigned long) skb, skb->data, skb->len,
			!!(info->flags & IEEE80211_TX_STAT_ACK), GFP_ATOMIC);
	}

	/* this was a transmitted frame, but now we want to reuse it */
	skb_orphan(skb);
+22 −0
Original line number Diff line number Diff line
@@ -560,6 +560,25 @@ ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk)
	return WORK_ACT_TIMEOUT;
}

static enum work_action __must_check
ieee80211_offchannel_tx(struct ieee80211_work *wk)
{
	if (!wk->started) {
		wk->timeout = jiffies + msecs_to_jiffies(wk->offchan_tx.wait);

		/*
		 * After this, offchan_tx.frame remains but now is no
		 * longer a valid pointer -- we still need it as the
		 * cookie for canceling this work.
		 */
		ieee80211_tx_skb(wk->sdata, wk->offchan_tx.frame);

		return WORK_ACT_NONE;
	}

	return WORK_ACT_TIMEOUT;
}

static enum work_action __must_check
ieee80211_assoc_beacon_wait(struct ieee80211_work *wk)
{
@@ -955,6 +974,9 @@ static void ieee80211_work_work(struct work_struct *work)
		case IEEE80211_WORK_REMAIN_ON_CHANNEL:
			rma = ieee80211_remain_on_channel_timeout(wk);
			break;
		case IEEE80211_WORK_OFFCHANNEL_TX:
			rma = ieee80211_offchannel_tx(wk);
			break;
		case IEEE80211_WORK_ASSOC_BEACON_WAIT:
			rma = ieee80211_assoc_beacon_wait(wk);
			break;