Commit 0ead2510 authored by Emmanuel Grumbach's avatar Emmanuel Grumbach Committed by Johannes Berg

mac80211: allow the driver to send EOSP when needed

This can happen when the driver needs to send less frames
than expected and then needs to close the SP.
Mac80211 still needs to set the more_data properly based
on its buffer state (ps_tx_buffer and buffered frames on
other TIDs).
To that end, refactor the code that delivers frames upon
uAPSD trigger frames to be able to get only the more_data
bit without actually delivering those frames in case the
driver is just asking to set a NDP with EOSP and MORE_DATA
bit properly set.
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 1b9df2d2
...@@ -4868,6 +4868,28 @@ void ieee80211_sta_block_awake(struct ieee80211_hw *hw, ...@@ -4868,6 +4868,28 @@ void ieee80211_sta_block_awake(struct ieee80211_hw *hw,
*/ */
void ieee80211_sta_eosp(struct ieee80211_sta *pubsta); void ieee80211_sta_eosp(struct ieee80211_sta *pubsta);
/**
* ieee80211_send_eosp_nullfunc - ask mac80211 to send NDP with EOSP
* @pubsta: the station
* @tid: the tid of the NDP
*
* Sometimes the device understands that it needs to close
* the Service Period unexpectedly. This can happen when
* sending frames that are filling holes in the BA window.
* In this case, the device can ask mac80211 to send a
* Nullfunc frame with EOSP set. When that happens, the
* driver must have called ieee80211_sta_set_buffered() to
* let mac80211 know that there are no buffered frames any
* more, otherwise mac80211 will get the more_data bit wrong.
* The low level driver must have made sure that the frame
* will be sent despite the station being in power-save.
* Mac80211 won't call allow_buffered_frames().
* Note that calling this function, doesn't exempt the driver
* from closing the EOSP properly, it will still have to call
* ieee80211_sta_eosp when the NDP is sent.
*/
void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid);
/** /**
* ieee80211_iter_keys - iterate keys programmed into the device * ieee80211_iter_keys - iterate keys programmed into the device
* @hw: pointer obtained from ieee80211_alloc_hw() * @hw: pointer obtained from ieee80211_alloc_hw()
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Copyright 2002-2005, Instant802 Networks, Inc. * Copyright 2002-2005, Instant802 Networks, Inc.
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
* Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2013-2014 Intel Mobile Communications GmbH
* Copyright (C) 2015 Intel Deutschland GmbH
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
...@@ -1244,11 +1245,11 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta) ...@@ -1244,11 +1245,11 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
ieee80211_check_fast_xmit(sta); ieee80211_check_fast_xmit(sta);
} }
static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, static void ieee80211_send_null_response(struct sta_info *sta, int tid,
struct sta_info *sta, int tid,
enum ieee80211_frame_release_type reason, enum ieee80211_frame_release_type reason,
bool call_driver) bool call_driver, bool more_data)
{ {
struct ieee80211_sub_if_data *sdata = sta->sdata;
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
struct ieee80211_qos_hdr *nullfunc; struct ieee80211_qos_hdr *nullfunc;
struct sk_buff *skb; struct sk_buff *skb;
...@@ -1288,9 +1289,13 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata, ...@@ -1288,9 +1289,13 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,
if (qos) { if (qos) {
nullfunc->qos_ctrl = cpu_to_le16(tid); nullfunc->qos_ctrl = cpu_to_le16(tid);
if (reason == IEEE80211_FRAME_RELEASE_UAPSD) if (reason == IEEE80211_FRAME_RELEASE_UAPSD) {
nullfunc->qos_ctrl |= nullfunc->qos_ctrl |=
cpu_to_le16(IEEE80211_QOS_CTL_EOSP); cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
if (more_data)
nullfunc->frame_control |=
cpu_to_le16(IEEE80211_FCTL_MOREDATA);
}
} }
info = IEEE80211_SKB_CB(skb); info = IEEE80211_SKB_CB(skb);
...@@ -1337,22 +1342,48 @@ static int find_highest_prio_tid(unsigned long tids) ...@@ -1337,22 +1342,48 @@ static int find_highest_prio_tid(unsigned long tids)
return fls(tids) - 1; return fls(tids) - 1;
} }
/* Indicates if the MORE_DATA bit should be set in the last
* frame obtained by ieee80211_sta_ps_get_frames.
* Note that driver_release_tids is relevant only if
* reason = IEEE80211_FRAME_RELEASE_PSPOLL
*/
static bool
ieee80211_sta_ps_more_data(struct sta_info *sta, u8 ignored_acs,
enum ieee80211_frame_release_type reason,
unsigned long driver_release_tids)
{
int ac;
/* 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). This will
* only happen for PS-Poll.
*/
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL &&
hweight16(driver_release_tids) > 1)
return true;
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
if (ignored_acs & BIT(ac))
continue;
if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
!skb_queue_empty(&sta->ps_tx_buf[ac]))
return true;
}
return false;
}
static void static void
ieee80211_sta_ps_deliver_response(struct sta_info *sta, ieee80211_sta_ps_get_frames(struct sta_info *sta, int n_frames, u8 ignored_acs,
int n_frames, u8 ignored_acs, enum ieee80211_frame_release_type reason,
enum ieee80211_frame_release_type reason) struct sk_buff_head *frames,
unsigned long *driver_release_tids)
{ {
struct ieee80211_sub_if_data *sdata = sta->sdata; struct ieee80211_sub_if_data *sdata = sta->sdata;
struct ieee80211_local *local = sdata->local; struct ieee80211_local *local = sdata->local;
bool more_data = false;
int ac; int ac;
unsigned long driver_release_tids = 0;
struct sk_buff_head frames;
/* Service or PS-Poll period starts */
set_sta_flag(sta, WLAN_STA_SP);
__skb_queue_head_init(&frames);
/* Get response frame(s) and more data bit for the last one. */ /* Get response frame(s) and more data bit for the last one. */
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
...@@ -1366,26 +1397,13 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ...@@ -1366,26 +1397,13 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
/* if we already have frames from software, then we can't also /* if we already have frames from software, then we can't also
* release from hardware queues * release from hardware queues
*/ */
if (skb_queue_empty(&frames)) { if (skb_queue_empty(frames)) {
driver_release_tids |= sta->driver_buffered_tids & tids; *driver_release_tids |=
driver_release_tids |= sta->txq_buffered_tids & tids; sta->driver_buffered_tids & tids;
*driver_release_tids |= sta->txq_buffered_tids & tids;
} }
if (driver_release_tids) { if (!*driver_release_tids) {
/* 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). This will
* only happen for PS-Poll.
*/
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL &&
hweight16(driver_release_tids) > 1) {
more_data = true;
driver_release_tids =
BIT(find_highest_prio_tid(
driver_release_tids));
break;
}
} else {
struct sk_buff *skb; struct sk_buff *skb;
while (n_frames > 0) { while (n_frames > 0) {
...@@ -1399,20 +1417,44 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ...@@ -1399,20 +1417,44 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
if (!skb) if (!skb)
break; break;
n_frames--; n_frames--;
__skb_queue_tail(&frames, skb); __skb_queue_tail(frames, skb);
} }
} }
/* If we have more frames buffered on this AC, then set the /* If we have more frames buffered on this AC, then abort the
* more-data bit and abort the loop since we can't send more * loop since we can't send more data from other ACs before
* data from other ACs before the buffered frames from this. * the buffered frames from this.
*/ */
if (!skb_queue_empty(&sta->tx_filtered[ac]) || if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
!skb_queue_empty(&sta->ps_tx_buf[ac])) { !skb_queue_empty(&sta->ps_tx_buf[ac]))
more_data = true;
break; break;
} }
} }
static void
ieee80211_sta_ps_deliver_response(struct sta_info *sta,
int n_frames, u8 ignored_acs,
enum ieee80211_frame_release_type reason)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
struct ieee80211_local *local = sdata->local;
unsigned long driver_release_tids = 0;
struct sk_buff_head frames;
bool more_data;
/* Service or PS-Poll period starts */
set_sta_flag(sta, WLAN_STA_SP);
__skb_queue_head_init(&frames);
ieee80211_sta_ps_get_frames(sta, n_frames, ignored_acs, reason,
&frames, &driver_release_tids);
more_data = ieee80211_sta_ps_more_data(sta, ignored_acs, reason, driver_release_tids);
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL)
driver_release_tids =
BIT(find_highest_prio_tid(driver_release_tids));
if (skb_queue_empty(&frames) && !driver_release_tids) { if (skb_queue_empty(&frames) && !driver_release_tids) {
int tid; int tid;
...@@ -1435,7 +1477,7 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ...@@ -1435,7 +1477,7 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
/* This will evaluate to 1, 3, 5 or 7. */ /* This will evaluate to 1, 3, 5 or 7. */
tid = 7 - ((ffs(~ignored_acs) - 1) << 1); tid = 7 - ((ffs(~ignored_acs) - 1) << 1);
ieee80211_send_null_response(sdata, sta, tid, reason, true); ieee80211_send_null_response(sta, tid, reason, true, false);
} else if (!driver_release_tids) { } else if (!driver_release_tids) {
struct sk_buff_head pending; struct sk_buff_head pending;
struct sk_buff *skb; struct sk_buff *skb;
...@@ -1535,8 +1577,8 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta, ...@@ -1535,8 +1577,8 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
if (need_null) if (need_null)
ieee80211_send_null_response( ieee80211_send_null_response(
sdata, sta, find_highest_prio_tid(tids), sta, find_highest_prio_tid(tids),
reason, false); reason, false, false);
sta_info_recalc_tim(sta); sta_info_recalc_tim(sta);
} else { } else {
...@@ -1674,6 +1716,22 @@ void ieee80211_sta_eosp(struct ieee80211_sta *pubsta) ...@@ -1674,6 +1716,22 @@ void ieee80211_sta_eosp(struct ieee80211_sta *pubsta)
} }
EXPORT_SYMBOL(ieee80211_sta_eosp); EXPORT_SYMBOL(ieee80211_sta_eosp);
void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid)
{
struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
enum ieee80211_frame_release_type reason;
bool more_data;
trace_api_send_eosp_nullfunc(sta->local, pubsta, tid);
reason = IEEE80211_FRAME_RELEASE_UAPSD;
more_data = ieee80211_sta_ps_more_data(sta, ~sta->sta.uapsd_queues,
reason, 0);
ieee80211_send_null_response(sta, tid, reason, false, more_data);
}
EXPORT_SYMBOL(ieee80211_send_eosp_nullfunc);
void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta, void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
u8 tid, bool buffered) u8 tid, bool buffered)
{ {
......
...@@ -2027,6 +2027,31 @@ TRACE_EVENT(api_eosp, ...@@ -2027,6 +2027,31 @@ TRACE_EVENT(api_eosp,
) )
); );
TRACE_EVENT(api_send_eosp_nullfunc,
TP_PROTO(struct ieee80211_local *local,
struct ieee80211_sta *sta,
u8 tid),
TP_ARGS(local, sta, tid),
TP_STRUCT__entry(
LOCAL_ENTRY
STA_ENTRY
__field(u8, tid)
),
TP_fast_assign(
LOCAL_ASSIGN;
STA_ASSIGN;
__entry->tid = tid;
),
TP_printk(
LOCAL_PR_FMT STA_PR_FMT " tid:%d",
LOCAL_PR_ARG, STA_PR_ARG, __entry->tid
)
);
TRACE_EVENT(api_sta_set_buffered, TRACE_EVENT(api_sta_set_buffered,
TP_PROTO(struct ieee80211_local *local, TP_PROTO(struct ieee80211_local *local,
struct ieee80211_sta *sta, struct ieee80211_sta *sta,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment