mac80211: reply only once to each PS-poll

If a PS-poll frame is retried (but was received)
there is no way to detect that since it has no
sequence number. As a consequence, the standard
asks us to not react to PS-poll frames until the
response to one made it out (was ACKed or lost).

Implement this by using the WLAN_STA_SP flags to
also indicate a PS-Poll "service period" and the
IEEE80211_TX_STATUS_EOSP flag for the response
packet to indicate the end of the "SP" as usual.

We could use separate flags, but that will most
likely completely confuse drivers, and while the
standard doesn't exclude simultaneously polling
using uAPSD and PS-Poll, doing that seems quite
problematic.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 9a703f0..32c8ee4 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1194,10 +1194,12 @@
 		return RX_CONTINUE;
 
 	if (unlikely(ieee80211_is_pspoll(hdr->frame_control))) {
-		if (!test_sta_flags(rx->sta, WLAN_STA_PS_DRIVER))
-			ieee80211_sta_ps_deliver_poll_response(rx->sta);
-		else
-			set_sta_flags(rx->sta, WLAN_STA_PSPOLL);
+		if (!test_sta_flags(rx->sta, WLAN_STA_SP)) {
+			if (!test_sta_flags(rx->sta, WLAN_STA_PS_DRIVER))
+				ieee80211_sta_ps_deliver_poll_response(rx->sta);
+			else
+				set_sta_flags(rx->sta, WLAN_STA_PSPOLL);
+		}
 
 		/* Free PS Poll skb here instead of returning RX_DROP that would
 		 * count as an dropped frame. */
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index d9cb56f..5732e4d 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -1217,13 +1217,12 @@
 	/*
 	 * Tell TX path to send this frame even though the
 	 * STA may still remain is PS mode after this frame
-	 * exchange.
+	 * exchange. Also set EOSP to indicate this packet
+	 * ends the poll/service period.
 	 */
-	info->flags |= IEEE80211_TX_CTL_POLL_RESPONSE;
-
-	if (uapsd)
-		info->flags |= IEEE80211_TX_STATUS_EOSP |
-			       IEEE80211_TX_CTL_REQ_TX_STATUS;
+	info->flags |= IEEE80211_TX_CTL_POLL_RESPONSE |
+		       IEEE80211_TX_STATUS_EOSP |
+		       IEEE80211_TX_CTL_REQ_TX_STATUS;
 
 	ieee80211_xmit(sdata, skb);
 }
@@ -1241,6 +1240,9 @@
 	unsigned long driver_release_tids = 0;
 	struct sk_buff_head frames;
 
+	/* Service or PS-Poll period starts */
+	set_sta_flags(sta, WLAN_STA_SP);
+
 	__skb_queue_head_init(&frames);
 
 	/*
@@ -1357,10 +1359,11 @@
 				/* set EOSP for the frame */
 				u8 *p = ieee80211_get_qos_ctl(hdr);
 				*p |= IEEE80211_QOS_CTL_EOSP;
-				info->flags |= IEEE80211_TX_STATUS_EOSP |
-					       IEEE80211_TX_CTL_REQ_TX_STATUS;
 			}
 
+			info->flags |= IEEE80211_TX_STATUS_EOSP |
+				       IEEE80211_TX_CTL_REQ_TX_STATUS;
+
 			__skb_queue_tail(&pending, skb);
 		}
 
@@ -1422,9 +1425,6 @@
 	if (!delivery_enabled)
 		return;
 
-	/* Ohh, finally, the service period starts :-) */
-	set_sta_flags(sta, WLAN_STA_SP);
-
 	switch (sta->sta.max_sp) {
 	case 1:
 		n_frames = 2;
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 751ad25..348847a 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -50,7 +50,7 @@
  *	keeping station in power-save mode, reply when the driver
  *	unblocks the station.
  * @WLAN_STA_SP: Station is in a service period, so don't try to
- *	reply to other uAPSD trigger frames.
+ *	reply to other uAPSD trigger frames or PS-Poll.
  */
 enum ieee80211_sta_info_flags {
 	WLAN_STA_AUTH		= 1<<0,