ath9k: fix processing of TX PS null data frames
authorLuis R. Rodriguez <lrodriguez@atheros.com>
Wed, 16 Dec 2009 16:51:42 +0000 (11:51 -0500)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 18 Dec 2009 22:05:22 +0000 (14:05 -0800)
This is a backport of upstream commit: e7824a50662f7f79b1a739f705b4d906c31cf221

When mac80211 was telling us to go into Powersave we listened
and immediately turned RX off. This meant hardware would not
see the ACKs from the AP we're associated with and hardware
we'd end up retransmiting the null data frame in a loop
helplessly.

Fix this by keeping track of the transmitted nullfunc frames
and only when we are sure the AP has sent back an ACK do we
go ahead and shut RX off.

Signed-off-by: Vasanthakumar Thiagarajan <vasanth@atheros.com>
Signed-off-by: Vivek Natarajan <Vivek.Natarajan@atheros.com>
Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/net/wireless/ath/ath9k/ath9k.h
drivers/net/wireless/ath/ath9k/mac.c
drivers/net/wireless/ath/ath9k/mac.h
drivers/net/wireless/ath/ath9k/main.c
drivers/net/wireless/ath/ath9k/xmit.c

index 1d59f10f68da77fd8066028fb9998a93106c5aa2..cdb90c5bf39b182f196eed5ef575063fec10b699 100644 (file)
@@ -139,6 +139,7 @@ struct ath_buf {
        dma_addr_t bf_daddr;            /* physical addr of desc */
        dma_addr_t bf_buf_addr;         /* physical addr of data buffer */
        bool bf_stale;
+       bool bf_isnullfunc;
        u16 bf_flags;
        struct ath_buf_state bf_state;
        dma_addr_t bf_dmacontext;
@@ -524,6 +525,8 @@ struct ath_led {
 #define SC_OP_BEACON_SYNC       BIT(19)
 #define SC_OP_BTCOEX_ENABLED    BIT(20)
 #define SC_OP_BT_PRIORITY_DETECTED BIT(21)
+#define SC_OP_NULLFUNC_COMPLETED   BIT(22)
+#define SC_OP_PS_ENABLED       BIT(23)
 
 struct ath_bus_ops {
        void            (*read_cachesize)(struct ath_softc *sc, int *csz);
index 800bfab94635e27d187c23556d7bcb12e38219c8..561a40ed056422204d0ea4aca3f6075eb3945689 100644 (file)
@@ -222,6 +222,8 @@ int ath9k_hw_txprocdesc(struct ath_hw *ah, struct ath_desc *ds)
        ds->ds_txstat.ts_status = 0;
        ds->ds_txstat.ts_flags = 0;
 
+       if (ads->ds_txstatus1 & AR_FrmXmitOK)
+               ds->ds_txstat.ts_status |= ATH9K_TX_ACKED;
        if (ads->ds_txstatus1 & AR_ExcessiveRetries)
                ds->ds_txstat.ts_status |= ATH9K_TXERR_XRETRY;
        if (ads->ds_txstatus1 & AR_Filtered)
index f56e77da6c3eff8593080603506be72c0116b843..ff65f85bd855e8108c20da6a68a5c429d5a244b2 100644 (file)
@@ -76,6 +76,7 @@
 #define ATH9K_TXERR_FIFO           0x04
 #define ATH9K_TXERR_XTXOP          0x08
 #define ATH9K_TXERR_TIMER_EXPIRED  0x10
+#define ATH9K_TX_ACKED            0x20
 
 #define ATH9K_TX_BA                0x01
 #define ATH9K_TX_PWRMGMT           0x02
index 43d2be9867fc08e288c5dc3eab5949e0cf1fa96c..59359e348ee3a8b650ed66aae137ac54ea247f8c 100644 (file)
@@ -2327,6 +2327,7 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
 
        if (changed & IEEE80211_CONF_CHANGE_PS) {
                if (conf->flags & IEEE80211_CONF_PS) {
+                       sc->sc_flags |= SC_OP_PS_ENABLED;
                        if (!(ah->caps.hw_caps &
                              ATH9K_HW_CAP_AUTOSLEEP)) {
                                if ((sc->imask & ATH9K_INT_TIM_TIMER) == 0) {
@@ -2334,11 +2335,17 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
                                        ath9k_hw_set_interrupts(sc->sc_ah,
                                                        sc->imask);
                                }
-                               ath9k_hw_setrxabort(sc->sc_ah, 1);
                        }
                        sc->ps_enabled = true;
+                       if ((sc->sc_flags & SC_OP_NULLFUNC_COMPLETED)) {
+                               sc->sc_flags &= ~SC_OP_NULLFUNC_COMPLETED;
+                               sc->ps_enabled = true;
+                               ath9k_hw_setrxabort(sc->sc_ah, 1);
+                       }
                } else {
                        sc->ps_enabled = false;
+                       sc->sc_flags &= ~(SC_OP_PS_ENABLED |
+                                         SC_OP_NULLFUNC_COMPLETED);
                        ath9k_hw_setpower(sc->sc_ah, ATH9K_PM_AWAKE);
                        if (!(ah->caps.hw_caps &
                              ATH9K_HW_CAP_AUTOSLEEP)) {
index 972e775b362365581d0479940795788af996123b..e78bd57680758f79a4f2a3ce596c5ff56e77e932 100644 (file)
@@ -1592,6 +1592,13 @@ static int ath_tx_setup_buffer(struct ieee80211_hw *hw, struct ath_buf *bf,
        }
 
        bf->bf_buf_addr = bf->bf_dmacontext;
+
+       if (ieee80211_is_nullfunc(fc) && ieee80211_has_pm(fc)) {
+               bf->bf_isnullfunc = true;
+               sc->sc_flags &= ~SC_OP_NULLFUNC_COMPLETED;
+       } else
+               bf->bf_isnullfunc = false;
+
        return 0;
 }
 
@@ -1989,6 +1996,15 @@ static void ath_tx_processq(struct ath_softc *sc, struct ath_txq *txq)
                if (ds == txq->axq_gatingds)
                        txq->axq_gatingds = NULL;
 
+               if (bf->bf_isnullfunc &&
+                   (ds->ds_txstat.ts_status & ATH9K_TX_ACKED)) {
+                       if ((sc->sc_flags & SC_OP_PS_ENABLED)) {
+                               sc->ps_enabled = true;
+                               ath9k_hw_setrxabort(sc->sc_ah, 1);
+                       } else
+                               sc->sc_flags |= SC_OP_NULLFUNC_COMPLETED;
+               }
+
                /*
                 * Remove ath_buf's of the same transmit unit from txq,
                 * however leave the last descriptor back as the holding