aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/wireless/ath/ath9k/recv.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ath/ath9k/recv.c')
-rw-r--r--drivers/net/wireless/ath/ath9k/recv.c194
1 files changed, 162 insertions, 32 deletions
diff --git a/drivers/net/wireless/ath/ath9k/recv.c b/drivers/net/wireless/ath/ath9k/recv.c
index b46badd21f7..5014a19b0f7 100644
--- a/drivers/net/wireless/ath/ath9k/recv.c
+++ b/drivers/net/wireless/ath/ath9k/recv.c
@@ -473,6 +473,159 @@ void ath_flushrecv(struct ath_softc *sc)
spin_unlock_bh(&sc->rx.rxflushlock);
}
+static bool ath_beacon_dtim_pending_cab(struct sk_buff *skb)
+{
+ /* Check whether the Beacon frame has DTIM indicating buffered bc/mc */
+ struct ieee80211_mgmt *mgmt;
+ u8 *pos, *end, id, elen;
+ struct ieee80211_tim_ie *tim;
+
+ mgmt = (struct ieee80211_mgmt *)skb->data;
+ pos = mgmt->u.beacon.variable;
+ end = skb->data + skb->len;
+
+ while (pos + 2 < end) {
+ id = *pos++;
+ elen = *pos++;
+ if (pos + elen > end)
+ break;
+
+ if (id == WLAN_EID_TIM) {
+ if (elen < sizeof(*tim))
+ break;
+ tim = (struct ieee80211_tim_ie *) pos;
+ if (tim->dtim_count != 0)
+ break;
+ return tim->bitmap_ctrl & 0x01;
+ }
+
+ pos += elen;
+ }
+
+ return false;
+}
+
+static void ath_rx_ps_back_to_sleep(struct ath_softc *sc)
+{
+ sc->sc_flags &= ~(SC_OP_WAIT_FOR_BEACON | SC_OP_WAIT_FOR_CAB);
+}
+
+static void ath_rx_ps_beacon(struct ath_softc *sc, struct sk_buff *skb)
+{
+ struct ieee80211_mgmt *mgmt;
+
+ if (skb->len < 24 + 8 + 2 + 2)
+ return;
+
+ mgmt = (struct ieee80211_mgmt *)skb->data;
+ if (memcmp(sc->curbssid, mgmt->bssid, ETH_ALEN) != 0)
+ return; /* not from our current AP */
+
+ if (sc->sc_flags & SC_OP_BEACON_SYNC) {
+ sc->sc_flags &= ~SC_OP_BEACON_SYNC;
+ DPRINTF(sc, ATH_DBG_PS, "Reconfigure Beacon timers based on "
+ "timestamp from the AP\n");
+ ath_beacon_config(sc, NULL);
+ }
+
+ if (!(sc->hw->conf.flags & IEEE80211_CONF_PS)) {
+ /* We are not in PS mode anymore; remain awake */
+ DPRINTF(sc, ATH_DBG_PS, "Not in PS mode anymore, remain "
+ "awake\n");
+ sc->sc_flags &= ~(SC_OP_WAIT_FOR_BEACON | SC_OP_WAIT_FOR_CAB);
+ return;
+ }
+
+ if (ath_beacon_dtim_pending_cab(skb)) {
+ /*
+ * Remain awake waiting for buffered broadcast/multicast
+ * frames.
+ */
+ DPRINTF(sc, ATH_DBG_PS, "Received DTIM beacon indicating "
+ "buffered broadcast/multicast frame(s)\n");
+ sc->sc_flags |= SC_OP_WAIT_FOR_CAB;
+ return;
+ }
+
+ if (sc->sc_flags & SC_OP_WAIT_FOR_CAB) {
+ /*
+ * This can happen if a broadcast frame is dropped or the AP
+ * fails to send a frame indicating that all CAB frames have
+ * been delivered.
+ */
+ DPRINTF(sc, ATH_DBG_PS, "PS wait for CAB frames timed out\n");
+ }
+
+ /* No more broadcast/multicast frames to be received at this point. */
+ ath_rx_ps_back_to_sleep(sc);
+}
+
+static void ath_rx_ps(struct ath_softc *sc, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+
+ /* Process Beacon and CAB receive in PS state */
+ if ((sc->sc_flags & SC_OP_WAIT_FOR_BEACON) &&
+ ieee80211_is_beacon(hdr->frame_control))
+ ath_rx_ps_beacon(sc, skb);
+ else if ((sc->sc_flags & SC_OP_WAIT_FOR_CAB) &&
+ (ieee80211_is_data(hdr->frame_control) ||
+ ieee80211_is_action(hdr->frame_control)) &&
+ is_multicast_ether_addr(hdr->addr1) &&
+ !ieee80211_has_moredata(hdr->frame_control)) {
+ DPRINTF(sc, ATH_DBG_PS, "All PS CAB frames received, back to "
+ "sleep\n");
+ /*
+ * No more broadcast/multicast frames to be received at this
+ * point.
+ */
+ ath_rx_ps_back_to_sleep(sc);
+ } else if ((sc->sc_flags & SC_OP_WAIT_FOR_PSPOLL_DATA) &&
+ !is_multicast_ether_addr(hdr->addr1) &&
+ !ieee80211_has_morefrags(hdr->frame_control)) {
+ sc->sc_flags &= ~SC_OP_WAIT_FOR_PSPOLL_DATA;
+ DPRINTF(sc, ATH_DBG_PS, "Going back to sleep after having "
+ "received PS-Poll data (0x%x)\n",
+ sc->sc_flags & (SC_OP_WAIT_FOR_BEACON |
+ SC_OP_WAIT_FOR_CAB |
+ SC_OP_WAIT_FOR_PSPOLL_DATA |
+ SC_OP_WAIT_FOR_TX_ACK));
+ }
+}
+
+static void ath_rx_send_to_mac80211(struct ath_softc *sc, struct sk_buff *skb,
+ struct ieee80211_rx_status *rx_status)
+{
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+
+ /* Send the frame to mac80211 */
+ if (is_multicast_ether_addr(hdr->addr1)) {
+ int i;
+ /*
+ * Deliver broadcast/multicast frames to all suitable
+ * virtual wiphys.
+ */
+ /* TODO: filter based on channel configuration */
+ for (i = 0; i < sc->num_sec_wiphy; i++) {
+ struct ath_wiphy *aphy = sc->sec_wiphy[i];
+ struct sk_buff *nskb;
+ if (aphy == NULL)
+ continue;
+ nskb = skb_copy(skb, GFP_ATOMIC);
+ if (nskb)
+ __ieee80211_rx(aphy->hw, nskb, rx_status);
+ }
+ __ieee80211_rx(sc->hw, skb, rx_status);
+ } else {
+ /* Deliver unicast frames based on receiver address */
+ __ieee80211_rx(ath_get_virt_hw(sc, hdr), skb, rx_status);
+ }
+}
+
int ath_rx_tasklet(struct ath_softc *sc, int flush)
{
#define PA2DESC(_sc, _pa) \
@@ -622,7 +775,7 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush)
if (!(keyix == ATH9K_RXKEYIX_INVALID) && !decrypt_error) {
rx_status.flag |= RX_FLAG_DECRYPTED;
- } else if ((le16_to_cpu(hdr->frame_control) & IEEE80211_FCTL_PROTECTED)
+ } else if (ieee80211_has_protected(fc)
&& !decrypt_error && skb->len >= hdrlen + 4) {
keyix = skb->data[hdrlen + 3] >> 6;
@@ -631,36 +784,11 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush)
}
if (ah->sw_mgmt_crypto &&
(rx_status.flag & RX_FLAG_DECRYPTED) &&
- ieee80211_is_mgmt(hdr->frame_control)) {
+ ieee80211_is_mgmt(fc)) {
/* Use software decrypt for management frames. */
rx_status.flag &= ~RX_FLAG_DECRYPTED;
}
- /* Send the frame to mac80211 */
- if (hdr->addr1[5] & 0x01) {
- int i;
- /*
- * Deliver broadcast/multicast frames to all suitable
- * virtual wiphys.
- */
- /* TODO: filter based on channel configuration */
- for (i = 0; i < sc->num_sec_wiphy; i++) {
- struct ath_wiphy *aphy = sc->sec_wiphy[i];
- struct sk_buff *nskb;
- if (aphy == NULL)
- continue;
- nskb = skb_copy(skb, GFP_ATOMIC);
- if (nskb)
- __ieee80211_rx(aphy->hw, nskb,
- &rx_status);
- }
- __ieee80211_rx(sc->hw, skb, &rx_status);
- } else {
- /* Deliver unicast frames based on receiver address */
- __ieee80211_rx(ath_get_virt_hw(sc, hdr), skb,
- &rx_status);
- }
-
/* We will now give hardware our shiny new allocated skb */
bf->bf_mpdu = requeue_skb;
bf->bf_buf_addr = dma_map_single(sc->dev, requeue_skb->data,
@@ -672,6 +800,7 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush)
bf->bf_mpdu = NULL;
DPRINTF(sc, ATH_DBG_FATAL,
"dma_mapping_error() on RX\n");
+ ath_rx_send_to_mac80211(sc, skb, &rx_status);
break;
}
bf->bf_dmacontext = bf->bf_buf_addr;
@@ -687,11 +816,12 @@ int ath_rx_tasklet(struct ath_softc *sc, int flush)
sc->rx.rxotherant = 0;
}
- if (ieee80211_is_beacon(fc) &&
- (sc->sc_flags & SC_OP_WAIT_FOR_BEACON)) {
- sc->sc_flags &= ~SC_OP_WAIT_FOR_BEACON;
- ath9k_hw_setpower(sc->sc_ah, ATH9K_PM_NETWORK_SLEEP);
- }
+ if (unlikely(sc->sc_flags & (SC_OP_WAIT_FOR_BEACON |
+ SC_OP_WAIT_FOR_PSPOLL_DATA)))
+ ath_rx_ps(sc, skb);
+
+ ath_rx_send_to_mac80211(sc, skb, &rx_status);
+
requeue:
list_move_tail(&bf->list, &sc->rx.rxbuf);
ath_rx_buf_link(sc, bf);