mac80211: allow releasing driver-buffered frames
authorJohannes Berg <johannes.berg@intel.com>
Thu, 29 Sep 2011 14:04:32 +0000 (16:04 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 30 Sep 2011 19:57:15 +0000 (15:57 -0400)
If there are frames for a station buffered in
the driver, mac80211 announces those in the TIM
IE but there's no way to release them. Add new
API to release such frames and use it when the
station polls for a frame.

Since the API will soon also be used for uAPSD
it is easily extensible.

Note that before this change drivers announcing
driver-buffered frames in the TIM bit actually
will respond to a PS-Poll with a potentially
lower priority frame (if there are any frames
buffered in mac80211), after this patch a driver
that hasn't been changed will no longer respond
at all. This only affects ath9k, which will need
to be fixed to implement the new API.

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

index acf9eaf..dc2ea46 100644 (file)
@@ -1622,6 +1622,14 @@ enum ieee80211_tx_sync_type {
 };
 
 /**
+ * enum ieee80211_frame_release_type - frame release reason
+ * @IEEE80211_FRAME_RELEASE_PSPOLL: frame released for PS-Poll
+ */
+enum ieee80211_frame_release_type {
+       IEEE80211_FRAME_RELEASE_PSPOLL,
+};
+
+/**
  * struct ieee80211_ops - callbacks from mac80211 to the driver
  *
  * This structure contains various callbacks that the driver may
@@ -1931,6 +1939,23 @@ enum ieee80211_tx_sync_type {
  *     The callback can sleep.
  * @rssi_callback: Notify driver when the average RSSI goes above/below
  *     thresholds that were registered previously. The callback can sleep.
+ *
+ * @release_buffered_frames: Release buffered frames according to the given
+ *     parameters. In the case where the driver buffers some frames for
+ *     sleeping stations mac80211 will use this callback to tell the driver
+ *     to release some frames, either for PS-poll or uAPSD.
+ *     Note that if the @more_data paramter is %false the driver must check
+ *     if there are more frames on the given TIDs, and if there are more than
+ *     the frames being released then it must still set the more-data bit in
+ *     the frame. If the @more_data parameter is %true, then of course the
+ *     more-data bit must always be set.
+ *     The @tids parameter tells the driver which TIDs to release frames
+ *     from, for PS-poll it will always have only a single bit set.
+ *     In the case this is used for uAPSD, the @num_frames parameter may be
+ *     bigger than one, but the driver may send fewer frames (it must send
+ *     at least one, however). In this case it is also responsible for
+ *     setting the EOSP flag in the QoS header of the frames.
+ *     This callback must be atomic.
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
@@ -2045,6 +2070,12 @@ struct ieee80211_ops {
                                const struct cfg80211_bitrate_mask *mask);
        void (*rssi_callback)(struct ieee80211_hw *hw,
                              enum ieee80211_rssi_event rssi_event);
+
+       void (*release_buffered_frames)(struct ieee80211_hw *hw,
+                                       struct ieee80211_sta *sta,
+                                       u16 tids, int num_frames,
+                                       enum ieee80211_frame_release_type reason,
+                                       bool more_data);
 };
 
 /**
index 4f845c0..8fa0d2e 100644 (file)
@@ -670,4 +670,19 @@ static inline void drv_rssi_callback(struct ieee80211_local *local,
                local->ops->rssi_callback(&local->hw, event);
        trace_drv_return_void(local);
 }
+
+static inline void
+drv_release_buffered_frames(struct ieee80211_local *local,
+                           struct sta_info *sta, u16 tids, int num_frames,
+                           enum ieee80211_frame_release_type reason,
+                           bool more_data)
+{
+       trace_drv_release_buffered_frames(local, &sta->sta, tids, num_frames,
+                                         reason, more_data);
+       if (local->ops->release_buffered_frames)
+               local->ops->release_buffered_frames(&local->hw, &sta->sta, tids,
+                                                   num_frames, reason,
+                                                   more_data);
+       trace_drv_return_void(local);
+}
 #endif /* __MAC80211_DRIVER_OPS */
index a46b279..531fbd0 100644 (file)
@@ -1129,6 +1129,41 @@ TRACE_EVENT(drv_rssi_callback,
        )
 );
 
+TRACE_EVENT(drv_release_buffered_frames,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sta *sta,
+                u16 tids, int num_frames,
+                enum ieee80211_frame_release_type reason,
+                bool more_data),
+
+       TP_ARGS(local, sta, tids, num_frames, reason, more_data),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               STA_ENTRY
+               __field(u16, tids)
+               __field(int, num_frames)
+               __field(int, reason)
+               __field(bool, more_data)
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               STA_ASSIGN;
+               __entry->tids = tids;
+               __entry->num_frames = num_frames;
+               __entry->reason = reason;
+               __entry->more_data = more_data;
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT STA_PR_FMT
+               " TIDs:0x%.4x frames:%d reason:%d more:%d",
+               LOCAL_PR_ARG, STA_PR_ARG, __entry->tids, __entry->num_frames,
+               __entry->reason, __entry->more_data
+       )
+);
+
 /*
  * Tracing for API calls that drivers call.
  */
index 4d85672..b3f8419 100644 (file)
@@ -1157,8 +1157,10 @@ void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta)
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        struct ieee80211_local *local = sdata->local;
        struct sk_buff *skb = NULL;
+       bool found = false;
        bool more_data = false;
        int ac;
+       unsigned long driver_release_tids = 0;
        u8 ignore_for_response = sta->sta.uapsd_queues;
 
        /*
@@ -1173,19 +1175,40 @@ void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta)
         * Get response frame and more data bit for it.
         */
        for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+               unsigned long tids;
+
                if (ignore_for_response & BIT(ac))
                        continue;
 
-               if (!skb) {
-                       skb = skb_dequeue(&sta->tx_filtered[ac]);
-                       if (!skb) {
-                               skb = skb_dequeue(&sta->ps_tx_buf[ac]);
+               tids = ieee80211_tids_for_ac(ac);
+
+               if (!found) {
+                       driver_release_tids = sta->driver_buffered_tids & tids;
+                       if (driver_release_tids) {
+                               found = true;
+                       } else {
+                               skb = skb_dequeue(&sta->tx_filtered[ac]);
+                               if (!skb) {
+                                       skb = skb_dequeue(&sta->ps_tx_buf[ac]);
+                                       if (skb)
+                                               local->total_ps_buffered--;
+                               }
                                if (skb)
-                                       local->total_ps_buffered--;
+                                       found = true;
                        }
-               }
 
-               /* FIXME: take into account driver-buffered frames */
+                       /*
+                        * If the driver has data on more than one TID then
+                        * certainly there's more data if we release just a
+                        * single frame now (from a single TID).
+                        */
+                       if (hweight16(driver_release_tids) > 1) {
+                               more_data = true;
+                               driver_release_tids =
+                                       BIT(ffs(driver_release_tids) - 1);
+                               break;
+                       }
+               }
 
                if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
                    !skb_queue_empty(&sta->ps_tx_buf[ac])) {
@@ -1194,6 +1217,22 @@ void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta)
                }
        }
 
+       if (!found) {
+#ifdef CONFIG_MAC80211_VERBOSE_PS_DEBUG
+               /*
+                * FIXME: This can be the result of a race condition between
+                *        us expiring a frame and the station polling for it.
+                *        Should we send it a null-func frame indicating we
+                *        have nothing buffered for it?
+                */
+               printk(KERN_DEBUG "%s: STA %pM sent PS Poll even "
+                      "though there are no buffered frames for it\n",
+                      sdata->name, sta->sta.addr);
+#endif /* CONFIG_MAC80211_VERBOSE_PS_DEBUG */
+
+               return;
+       }
+
        if (skb) {
                struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
                struct ieee80211_hdr *hdr =
@@ -1220,18 +1259,29 @@ void ieee80211_sta_ps_deliver_poll_response(struct sta_info *sta)
                ieee80211_add_pending_skb(local, skb);
 
                sta_info_recalc_tim(sta);
-#ifdef CONFIG_MAC80211_VERBOSE_PS_DEBUG
        } else {
                /*
-                * FIXME: This can be the result of a race condition between
-                *        us expiring a frame and the station polling for it.
-                *        Should we send it a null-func frame indicating we
-                *        have nothing buffered for it?
+                * We need to release a frame that is buffered somewhere in the
+                * driver ... it'll have to handle that.
+                * Note that, as per the comment above, it'll also have to see
+                * if there is more than just one frame on the specific TID that
+                * we're releasing from, and it needs to set the more-data bit
+                * accordingly if we tell it that there's no more data. If we do
+                * tell it there's more data, then of course the more-data bit
+                * needs to be set anyway.
+                */
+               drv_release_buffered_frames(local, sta, driver_release_tids,
+                                           1, IEEE80211_FRAME_RELEASE_PSPOLL,
+                                           more_data);
+
+               /*
+                * Note that we don't recalculate the TIM bit here as it would
+                * most likely have no effect at all unless the driver told us
+                * that the TID became empty before returning here from the
+                * release function.
+                * Either way, however, when the driver tells us that the TID
+                * became empty we'll do the TIM recalculation.
                 */
-               printk(KERN_DEBUG "%s: STA %pM sent PS Poll even "
-                      "though there are no buffered frames for it\n",
-                      sdata->name, sta->sta.addr);
-#endif /* CONFIG_MAC80211_VERBOSE_PS_DEBUG */
        }
 }