mac80211: support direct offchannel TX offload
authorJohannes Berg <johannes.berg@intel.com>
Fri, 25 Feb 2011 14:36:57 +0000 (15:36 +0100)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 25 Feb 2011 20:33:40 +0000 (15:33 -0500)
For devices supported by iwlwifi sometimes
off-channel transmissions need to be handled
by the device completely. To support this
mac80211 needs to pass the frame directly
to the driver and not through the TX path
as the driver needs the frame and channel
information at the same time.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/status.c

index 96cc7ed35169a4e58f7d838c3661f398d90b1a3c..2b072fa99399c3f3be161170a73861cdee1bb0d4 100644 (file)
@@ -1799,6 +1799,11 @@ enum ieee80211_ampdu_mlme_action {
  *     ieee80211_remain_on_channel_expired(). This callback may sleep.
  * @cancel_remain_on_channel: Requests that an ongoing off-channel period is
  *     aborted before it expires. This callback may sleep.
+ * @offchannel_tx: Transmit frame on another channel, wait for a response
+ *     and return. Reliable TX status must be reported for the frame. If the
+ *     return value is 1, then the @remain_on_channel will be used with a
+ *     regular transmission (if supported.)
+ * @offchannel_tx_cancel_wait: cancel wait associated with offchannel TX
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
@@ -1878,6 +1883,11 @@ struct ieee80211_ops {
                                 enum nl80211_channel_type channel_type,
                                 int duration);
        int (*cancel_remain_on_channel)(struct ieee80211_hw *hw);
+       int (*offchannel_tx)(struct ieee80211_hw *hw, struct sk_buff *skb,
+                            struct ieee80211_channel *chan,
+                            enum nl80211_channel_type channel_type,
+                            unsigned int wait);
+       int (*offchannel_tx_cancel_wait)(struct ieee80211_hw *hw);
 };
 
 /**
index 140503d4c97ae4cf8188a4e291a0756cfe5c3372..8b436c768c4e33abcf92d4a1c26dd49010407bac 100644 (file)
@@ -1800,6 +1800,33 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
 
        *cookie = (unsigned long) skb;
 
+       if (is_offchan && local->ops->offchannel_tx) {
+               int ret;
+
+               IEEE80211_SKB_CB(skb)->band = chan->band;
+
+               mutex_lock(&local->mtx);
+
+               if (local->hw_offchan_tx_cookie) {
+                       mutex_unlock(&local->mtx);
+                       return -EBUSY;
+               }
+
+               /* TODO: bitrate control, TX processing? */
+               ret = drv_offchannel_tx(local, skb, chan, channel_type, wait);
+
+               if (ret == 0)
+                       local->hw_offchan_tx_cookie = *cookie;
+               mutex_unlock(&local->mtx);
+
+               /*
+                * Allow driver to return 1 to indicate it wants to have the
+                * frame transmitted with a remain_on_channel + regular TX.
+                */
+               if (ret != 1)
+                       return ret;
+       }
+
        if (is_offchan && local->ops->remain_on_channel) {
                unsigned int duration;
                int ret;
@@ -1886,6 +1913,18 @@ static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
 
        mutex_lock(&local->mtx);
 
+       if (local->ops->offchannel_tx_cancel_wait &&
+           local->hw_offchan_tx_cookie == cookie) {
+               ret = drv_offchannel_tx_cancel_wait(local);
+
+               if (!ret)
+                       local->hw_offchan_tx_cookie = 0;
+
+               mutex_unlock(&local->mtx);
+
+               return ret;
+       }
+
        if (local->ops->cancel_remain_on_channel) {
                cookie ^= 2;
                ret = ieee80211_cancel_remain_on_channel_hw(local, cookie);
index 32f05c1abbafd10cf6482f617d056e6c9d00e782..3729296f6f9580d97500f023afca38ff3004ff9f 100644 (file)
@@ -495,4 +495,35 @@ static inline int drv_cancel_remain_on_channel(struct ieee80211_local *local)
        return ret;
 }
 
+static inline int drv_offchannel_tx(struct ieee80211_local *local,
+                                   struct sk_buff *skb,
+                                   struct ieee80211_channel *chan,
+                                   enum nl80211_channel_type channel_type,
+                                   unsigned int wait)
+{
+       int ret;
+
+       might_sleep();
+
+       trace_drv_offchannel_tx(local, skb, chan, channel_type, wait);
+       ret = local->ops->offchannel_tx(&local->hw, skb, chan,
+                                       channel_type, wait);
+       trace_drv_return_int(local, ret);
+
+       return ret;
+}
+
+static inline int drv_offchannel_tx_cancel_wait(struct ieee80211_local *local)
+{
+       int ret;
+
+       might_sleep();
+
+       trace_drv_offchannel_tx_cancel_wait(local);
+       ret = local->ops->offchannel_tx_cancel_wait(&local->hw);
+       trace_drv_return_int(local, ret);
+
+       return ret;
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index e5cce19a7d65b95e6d31605785a19c7191ee9e6c..520fe24448931d4e320d7712ac13a87d61db29e6 100644 (file)
@@ -884,6 +884,39 @@ DEFINE_EVENT(local_only_evt, drv_cancel_remain_on_channel,
        TP_ARGS(local)
 );
 
+TRACE_EVENT(drv_offchannel_tx,
+       TP_PROTO(struct ieee80211_local *local, struct sk_buff *skb,
+                struct ieee80211_channel *chan,
+                enum nl80211_channel_type channel_type,
+                unsigned int wait),
+
+       TP_ARGS(local, skb, chan, channel_type, wait),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               __field(int, center_freq)
+               __field(int, channel_type)
+               __field(unsigned int, wait)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               __entry->center_freq = chan->center_freq;
+               __entry->channel_type = channel_type;
+               __entry->wait = wait;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT " freq:%dMHz, wait:%dms",
+               LOCAL_PR_ARG, __entry->center_freq, __entry->wait
+       )
+);
+
+DEFINE_EVENT(local_only_evt, drv_offchannel_tx_cancel_wait,
+       TP_PROTO(struct ieee80211_local *local),
+       TP_ARGS(local)
+);
+
 /*
  * Tracing for API calls that drivers call.
  */
index 0a570a111a842a624be2d2a118f80b752d6fb786..a404017014246c19fd9c64d6edda717015cbe140 100644 (file)
@@ -957,6 +957,7 @@ struct ieee80211_local {
        unsigned int hw_roc_duration;
        u32 hw_roc_cookie;
        bool hw_roc_for_tx;
+       unsigned long hw_offchan_tx_cookie;
 
        /* dummy netdev for use w/ NAPI */
        struct net_device napi_dev;
index 865185127f518e04ea3e893a755700404286cac9..b936dd29e92bc7bb2d73f9b75eaec55077d5b55a 100644 (file)
@@ -341,6 +341,10 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
                        cookie = local->hw_roc_cookie ^ 2;
                        local->hw_roc_skb_for_status = NULL;
                }
+
+               if (cookie == local->hw_offchan_tx_cookie)
+                       local->hw_offchan_tx_cookie = 0;
+
                cfg80211_mgmt_tx_status(
                        skb->dev, cookie, skb->data, skb->len,
                        !!(info->flags & IEEE80211_TX_STAT_ACK), GFP_ATOMIC);