mac80211: implement off-channel mgmt TX
authorJohannes Berg <johannes.berg@intel.com>
Thu, 25 Nov 2010 09:02:30 +0000 (10:02 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Mon, 29 Nov 2010 20:24:35 +0000 (15:24 -0500)
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: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
net/mac80211/cfg.c
net/mac80211/ieee80211_i.h
net/mac80211/status.c
net/mac80211/work.c

index aac2d7d..db134b5 100644 (file)
@@ -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,
index 5bc0745..66b0b52 100644 (file)
@@ -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;
index bed7e32..4958710 100644 (file)
@@ -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);
index ae344d1..2b5c3f2 100644 (file)
@@ -561,6 +561,25 @@ ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk)
 }
 
 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)
 {
        if (wk->started)
@@ -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;